Refactor, refresh UI and unify the structure of journals, replies and comments (#42972, #40744).

git-svn-id: https://svn.redmine.org/redmine/trunk@23887 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Marius Balteanu
2025-07-14 21:33:33 +00:00
parent 36446be97c
commit e940540e2e
14 changed files with 257 additions and 182 deletions

View File

@@ -426,7 +426,7 @@ function showIssueHistory(journal, url) {
tab_content.find('.journal').show(); tab_content.find('.journal').show();
tab_content.find('.journal:not(.has-notes)').hide(); tab_content.find('.journal:not(.has-notes)').hide();
tab_content.find('.journal .wiki').show(); tab_content.find('.journal .wiki').show();
tab_content.find('.journal .contextual .journal-actions > *').show(); tab_content.find('.journal .journal-actions > *').show();
// always show thumbnails in notes tab // always show thumbnails in notes tab
var thumbnails = tab_content.find('.journal .thumbnails'); var thumbnails = tab_content.find('.journal .thumbnails');
@@ -439,15 +439,15 @@ function showIssueHistory(journal, url) {
tab_content.find('.journal:not(.has-details)').hide(); tab_content.find('.journal:not(.has-details)').hide();
tab_content.find('.journal .wiki').hide(); tab_content.find('.journal .wiki').hide();
tab_content.find('.journal .thumbnails').hide(); tab_content.find('.journal .thumbnails').hide();
tab_content.find('.journal .contextual .journal-actions > *').hide(); tab_content.find('.journal .journal-actions > *').hide();
// Show reaction button in properties tab // Show reaction button in properties tab
tab_content.find('.journal .contextual .journal-actions .reaction-button-wrapper').show(); tab_content.find('.journal .journal-actions .reaction-button-wrapper').show();
break; break;
default: default:
tab_content.find('.journal').show(); tab_content.find('.journal').show();
tab_content.find('.journal .wiki').show(); tab_content.find('.journal .wiki').show();
tab_content.find('.journal .thumbnails').show(); tab_content.find('.journal .thumbnails').show();
tab_content.find('.journal .contextual .journal-actions > *').show(); tab_content.find('.journal .journal-actions > *').show();
} }
return false; return false;

View File

@@ -306,11 +306,31 @@ div + .drdn-items {border-top:1px solid #ccc;}
} }
.drdn-items>span {color:#999;} .drdn-items>span {color:#999;}
.contextual .drdn-content {top:18px;} .contextual .drdn-content, .journal-actions .drdn-content {
.contextual .drdn-items {padding:2px; min-width: 160px;} top: 18px;
.contextual .drdn-items>a {display: flex; padding: 5px 8px;} }
.contextual .drdn-items>a.icon:not(:has(svg)) {padding-left: 24px; background-position-x: 4px;}
.contextual .drdn-items>a:hover {color:#2A5685; border:1px solid #628db6; background-color:#eef5fd; border-radius:3px;} .contextual .drdn-items, .journal-actions .drdn-items {
padding: 2px;
min-width: 160px;
}
.contextual .drdn-items > a, .journal-actions .drdn-items > a {
display: flex;
padding: 5px 8px;
}
.contextual .drdn-items > a.icon:not(:has(svg)), .journal-actions .drdn-items > a.icon:not(:has(svg)) {
padding-left: 24px;
background-position-x: 4px;
}
.contextual .drdn-items > a:hover, .journal-actions .drdn-items > a:hover {
color: #2A5685;
border: 1px solid #628db6;
background-color: #eef5fd;
border-radius: 3px;
}
#project-jump.drdn {width:200px;display:inline-block;} #project-jump.drdn {width:200px;display:inline-block;}
#project-jump .drdn-trigger { #project-jump .drdn-trigger {
@@ -436,10 +456,6 @@ tr.message td.last_message { font-size: 93%; white-space: nowrap; }
tr.message.sticky td.subject { font-weight: bold; } tr.message.sticky td.subject { font-weight: bold; }
tr.message td.subject:not(:has(.icon)) { padding-left: 20px; } tr.message td.subject:not(:has(.icon)) { padding-left: 20px; }
body.avatars-on #replies .message.reply {padding-left: 32px;}
#replies .reply:target h4.reply-header {background-color:#DDEEFF;}
#replies h4 img.gravatar {margin-left:-32px;}
tr.version.closed, tr.version.closed a { color: #999; } tr.version.closed, tr.version.closed a { color: #999; }
tr.version:not(.shared) td.name { padding-left: 20px; } tr.version:not(.shared) td.name { padding-left: 20px; }
tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; } tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
@@ -593,7 +609,6 @@ div.square {
} }
.contextual {float:right; white-space: nowrap; line-height:1.4em;margin:5px 0px; padding-left: 10px; font-size:0.9em;} .contextual {float:right; white-space: nowrap; line-height:1.4em;margin:5px 0px; padding-left: 10px; font-size:0.9em;}
.contextual input, .contextual select {font-size:0.9em;} .contextual input, .contextual select {font-size:0.9em;}
.message .contextual, #comments .contextual { margin-top: 0; }
.splitcontent {overflow: auto; display: flex; flex-wrap: wrap;} .splitcontent {overflow: auto; display: flex; flex-wrap: wrap;}
.splitcontentleft {flex: 1; margin-right: 5px;} .splitcontentleft {flex: 1; margin-right: 5px;}
@@ -775,28 +790,8 @@ div#issue-changesets div.changeset {border-bottom: 1px solid #ddd; padding: 4px;
div#issue-changesets p { margin-top: 0; margin-bottom: 1em;} div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
.changeset-comments {margin-bottom:1em;} .changeset-comments {margin-bottom:1em;}
div.journal .contextual {margin-top: 0;}
div.journal.private-notes .wiki {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;}
div.journal ul.details, ul.revision-info {color:#959595; margin-bottom: 1.5em;}
div.journal ul.details a, ul.revision-info a {color:#70A7CD;}
div.journal ul.details a:hover, ul.revision-info a:hover {color:#D14848;}
body.avatars-on div.journal {padding-left:32px;}
div.journal h4 img.gravatar {margin-left:-32px;}
div.journal span.update-info {color: #666; font-size: 0.9em;}
#update {margin-bottom: 1.4em;} #update {margin-bottom: 1.4em;}
#history .tab-content {
padding: 0 8px;
margin-bottom: 10px;
border-right: 1px solid #d0d7de;
border-bottom: 1px solid #d0d7de;
border-left: 1px solid #d0d7de;
border-radius: 0 0 3px 3px / 0 0 3px 3px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
#history div:target h4.note-header {background-color:#DDEEFF;}
#history p.nodata {display: none;} #history p.nodata {display: none;}
/* Prevent content from being hidden behind a #sticky-issue-header when scrolling via anchor links. */ /* Prevent content from being hidden behind a #sticky-issue-header when scrolling via anchor links. */
.controller-issues.action-show div.wiki a[name], .controller-issues.action-show div.wiki a[name],
@@ -1193,7 +1188,6 @@ div.attachments span.author { font-size: 0.9em; color: #888; }
div.thumbnails {margin:0.6em;} div.thumbnails {margin:0.6em;}
div.thumbnail {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;} div.thumbnail {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
div.thumbnail img {margin: 3px; vertical-align: middle;} div.thumbnail img {margin: 3px; vertical-align: middle;}
#history div.thumbnails {margin-left: 2em;}
p.other-formats { text-align: right; font-size:0.9em; color: #666; } p.other-formats { text-align: right; font-size:0.9em; color: #666; }
.other-formats span + span:before { content: "| "; } .other-formats span + span:before { content: "| "; }
@@ -1745,6 +1739,7 @@ div.wiki .task-list input.task-list-item-checkbox {
.handle {cursor: move;} .handle {cursor: move;}
#my-page .list th.checkbox, #my-page .list td.checkbox {display:none;} #my-page .list th.checkbox, #my-page .list td.checkbox {display:none;}
/***** Gantt chart *****/ /***** Gantt chart *****/
table.gantt-table { table.gantt-table {
width: 100%; width: 100%;
@@ -1858,6 +1853,68 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container {
.version-behind-schedule a, .issue-behind-schedule a {color: #f66914;} .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
.version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;} .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
/***** User events (ex: journal, notes, replies, comments) *****/
.journals h4.journal-header {
background-color: #f6f7f8;
border-bottom: 0;
padding: 8px;
align-items: center;
display: flex;
justify-content: space-between;
}
.journals h4.journal-header .update-info {
color: #666;
font-size: 0.9em;
}
.journals h4.journal-header .badge {
position: static;
}
.journals div:target h4.journal-header {
background-color:#DDEEFF;
}
.journals .journal-content {
padding-left: 8px;
margin-bottom: 1.2em;
}
.journals .journal .journal-content .wiki {
margin-left: 0.6em;
}
.journals .private-notes {
border-left: 2px solid #d22;
}
.journals .journal-meta, .journals .journal-actions {
display: inline-flex;
gap: 10px;
}
.journals .journal-meta .journal-link {
color: #555;
}
.journals .journal-actions .reaction-button-wrapper {
display: inline-flex;
}
.journals .journal-details, ul.revision-info {
color: #959595;
margin-bottom: 1.5em;
}
.journals .journal-details a, ul.revision-info a {
color: #70A7CD;
}
.journals .journal-details a:hover, ul.revision-info a:hover {
color: #D14848;
}
/***** Badges *****/ /***** Badges *****/
.badge { .badge {
position:relative; position:relative;
@@ -2135,7 +2192,6 @@ div.gravatar-with-child > img.gravatar:nth-child(2) {
} }
h2 img.gravatar, h3 img.gravatar {margin-right: 4px;} h2 img.gravatar, h3 img.gravatar {margin-right: 4px;}
h4 img.gravatar {margin: -2px 4px -4px 0;}
td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;} td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
#activity dt img.gravatar {margin: 0 1em 0 0;} #activity dt img.gravatar {margin: 0 1em 0 0;}
/* Used on 12px Gravatar img tags without the icon background */ /* Used on 12px Gravatar img tags without the icon background */

View File

@@ -70,7 +70,7 @@ module JournalsHelper
def render_notes(issue, journal, options={}) def render_notes(issue, journal, options={})
content_tag('div', textilizable(journal, :notes), content_tag('div', textilizable(journal, :notes),
id: "journal-#{journal.id}-notes", class: "wiki", data: { quote_reply_target: 'content' }) id: "journal-#{journal.id}-notes", class: "wiki journal-note", data: { quote_reply_target: 'content' })
end end
def render_private_notes_indicator(journal) def render_private_notes_indicator(journal)

View File

@@ -131,7 +131,7 @@ end %>
<%= render partial: 'action_menu_edit' if User.current.wants_comments_in_reverse_order? %> <%= render partial: 'action_menu_edit' if User.current.wants_comments_in_reverse_order? %>
<div id="history"> <div id="history" class="journals">
<%= render_tabs issue_history_tabs, issue_history_default_tab %> <%= render_tabs issue_history_tabs, issue_history_default_tab %>
</div> </div>

View File

@@ -1,27 +1,29 @@
<% @changesets.each do |changeset| %> <% @changesets.each do |changeset| %>
<div id="changeset-<%= changeset.id %>" class="changeset journal"> <div id="changeset-<%= changeset.id %>" class="changeset journal">
<div class="note"> <h4 class="journal-header">
<h4 class='note-header'> <span class="journal-info">
<%= avatar(changeset.user, :size => "24") %> <%= avatar(changeset.user, :size => "24") %>
<%= authoring changeset.committed_on, changeset.author, :label => :label_added_time_by %> <%= authoring changeset.committed_on, changeset.author, :label => :label_added_time_by %>
</span>
</h4> </h4>
<p> <div class="journal-content">
<%= "#{changeset.project.name} - " unless changeset.project == project %> <p>
<%= link_to_revision(changeset, changeset.repository, <%= "#{changeset.project.name} - " unless changeset.project == project %>
:text => "#{l(:label_revision)} #{changeset.format_identifier}") %> <%= link_to_revision(changeset, changeset.repository,
<% if changeset.filechanges.any? && User.current.allowed_to?(:browse_repository, changeset.project) %> :text => "#{l(:label_revision)} #{changeset.format_identifier}") %>
(<%= link_to(l(:label_diff), <% if changeset.filechanges.any? && User.current.allowed_to?(:browse_repository, changeset.project) %>
:controller => 'repositories', (<%= link_to(l(:label_diff),
:action => 'diff', :controller => 'repositories',
:id => changeset.project, :action => 'diff',
:repository_id => changeset.repository.identifier_param, :id => changeset.project,
:path => "", :repository_id => changeset.repository.identifier_param,
:rev => changeset.identifier) %>) :path => "",
<% end %></p> :rev => changeset.identifier) %>)
<% end %>
<div class="wiki changeset-comments"> </p>
<%= format_changeset_comments changeset %> <div class="wiki changeset-comments">
</div> <%= format_changeset_comments changeset %>
</div>
</div> </div>
</div> </div>
<%= call_hook(:view_issues_history_changeset_bottom, { :changeset => changeset }) %> <%= call_hook(:view_issues_history_changeset_bottom, { :changeset => changeset }) %>

View File

@@ -7,32 +7,37 @@
<% for journal in journals %> <% for journal in journals %>
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>" data-controller="quote-reply"> <div id="change-<%= journal.id %>" class="<%= journal.css_classes %>" data-controller="quote-reply">
<div id="note-<%= journal.indice %>" class="note"> <div id="note-<%= journal.indice %>" class="note">
<div class="contextual"> <h4 class="journal-header">
<span class="journal-actions"><%= render_journal_actions(issue, journal, :reply_links => reply_links) %></span> <span class="journal-info">
<a href="#note-<%= journal.indice %>" class="journal-link">#<%= journal.indice %></a> <%= avatar(journal.user) %>
</div> <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>
<h4 class='note-header'> <%= render_private_notes_indicator(journal) %>
<%= avatar(journal.user) %> <%= render_journal_update_info(journal) %>
<%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %> </span>
<%= render_private_notes_indicator(journal) %> <span class="journal-meta">
<%= render_journal_update_info(journal) %> <span class="journal-actions">
</h4> <%= render_journal_actions(issue, journal, :reply_links => reply_links) %>
</span>
<% if journal.details.any? %> <a href="#note-<%= journal.indice %>" class="journal-link">#<%= journal.indice %></a>
<ul class="details"> </span>
<% details_to_strings(journal.visible_details).each do |string| %> </h4>
<li><%= string %></li> <div class="journal-content">
<% end %> <% if journal.details.any? %>
</ul> <ul class="journal-details">
<% if Setting.thumbnails_enabled? && (thumbnail_attachments = journal_thumbnail_attachments(journal)).any? %> <% details_to_strings(journal.visible_details).each do |string| %>
<div class="thumbnails"> <li><%= string %></li>
<% thumbnail_attachments.each do |attachment| %> <% end %>
<%= thumbnail_tag(attachment) %> </ul>
<% if Setting.thumbnails_enabled? && (thumbnail_attachments = journal_thumbnail_attachments(journal)).any? %>
<div class="thumbnails">
<% thumbnail_attachments.each do |attachment| %>
<%= thumbnail_tag(attachment) %>
<% end %>
</div>
<% end %>
<% end %> <% end %>
<%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %>
</div> </div>
<% end %>
<% end %>
<%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %>
</div> </div>
</div> </div>
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>

View File

@@ -1,31 +1,33 @@
<% for time_entry in time_entries%> <% for time_entry in time_entries %>
<div id="time-entry-<%= time_entry.id %>" class="time_entry journal"> <div id="time-entry-<%= time_entry.id %>" class="time_entry journal">
<div class="note"> <h4 class="journal-header">
<% if time_entry.editable_by?(User.current) -%> <span class="journal-info">
<div class="contextual"> <%= avatar(time_entry.user, :size => "24") %>
<span class="journal-actions"> <%= authoring time_entry.created_on, time_entry.user, :label => :label_added_time_by %>
<%= link_to sprite_icon('edit', l(:button_edit)), edit_time_entry_path(time_entry),
:title => l(:button_edit),
:class => 'icon-only icon-edit ' %>
<%= link_to sprite_icon('del', l(:button_delete)), time_entry_path(time_entry),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:button_delete),
:class => 'icon-only icon-del ' %>
</span> </span>
</div> <% if time_entry.editable_by?(User.current) -%>
<% end -%> <span class="journal-meta">
<h4 class='note-header'> <%= link_to sprite_icon('edit', l(:button_edit)), edit_time_entry_path(time_entry),
<%= avatar(time_entry.user, :size => "24") %> :title => l(:button_edit),
<%= authoring time_entry.created_on, time_entry.user, :label => :label_added_time_by %> :class => 'icon-only icon-edit' %>
<%= link_to sprite_icon('del', l(:button_delete)), time_entry_path(time_entry),
:data => { :confirm => l(:text_are_you_sure) },
:method => :delete,
:title => l(:button_delete),
:class => 'icon-only icon-del' %>
</span>
<% end -%>
</h4> </h4>
<ul class="details"> <div class="journal-content">
<li> <ul class="journal-details">
<strong><%= l(:label_time_entry_plural) %></strong>: <li>
<%= l_hours_short time_entry.hours %> <strong><%= l(:label_time_entry_plural) %></strong>:
</li> <%= l_hours_short time_entry.hours %>
</ul> </li>
<p><%= time_entry.comments %></p> </ul>
<div class="journal-note">
<%= time_entry.comments %>
</div>
</div> </div>
</div> </div>
<%= call_hook(:view_issues_history_time_entry_bottom, { :time_entry => time_entry }) %> <%= call_hook(:view_issues_history_time_entry_bottom, { :time_entry => time_entry }) %>

View File

@@ -7,7 +7,7 @@
$("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>'); $("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>');
$("#journal-<%= @journal.id %>-notes").show(); $("#journal-<%= @journal.id %>-notes").show();
$("#journal-<%= @journal.id %>-form").remove(); $("#journal-<%= @journal.id %>-form").remove();
var journal_header = $("#change-<%= @journal.id %>>div.note>h4.note-header"); var journal_header = $("#change-<%= @journal.id %>>div.note>h4.journal-header>.journal-info");
var journal_updated_info = journal_header.find("span.update-info"); var journal_updated_info = journal_header.find("span.update-info");
if (journal_updated_info.length > 0) { if (journal_updated_info.length > 0) {
journal_updated_info.replaceWith('<%= escape_javascript(render_journal_update_info(@journal)) %>'); journal_updated_info.replaceWith('<%= escape_javascript(render_journal_update_info(@journal)) %>');

View File

@@ -8,14 +8,14 @@
) if !@topic.locked? && authorize_for('messages', 'reply') %> ) if !@topic.locked? && authorize_for('messages', 'reply') %>
<%= link_to( <%= link_to(
sprite_icon('edit', l(:button_edit)), sprite_icon('edit', l(:button_edit)),
{:action => 'edit', :id => @topic}, { :action => 'edit', :id => @topic },
:class => 'icon icon-edit' :class => 'icon icon-edit'
) if @message.editable_by?(User.current) %> ) if @message.editable_by?(User.current) %>
<%= link_to( <%= link_to(
sprite_icon('del', l(:button_delete)), sprite_icon('del', l(:button_delete)),
{:action => 'destroy', :id => @topic}, { :action => 'destroy', :id => @topic },
:method => :post, :method => :post,
:data => {:confirm => l(:text_are_you_sure)}, :data => { :confirm => l(:text_are_you_sure) },
:class => 'icon icon-del' :class => 'icon icon-del'
) if @message.destroyable_by?(User.current) %> ) if @message.destroyable_by?(User.current) %>
</div> </div>
@@ -33,69 +33,72 @@
<%= link_to_attachments @topic, :author => false, :thumbnails => true %> <%= link_to_attachments @topic, :author => false, :thumbnails => true %>
</div> </div>
</div> </div>
<br /> <br/>
<% unless @replies.empty? %> <% unless @replies.empty? %>
<div id="replies"> <div id="replies" class="journals">
<h3 class="comments icon icon-comments"><%= sprite_icon('comments', l(:label_reply_plural)) %> (<%= @reply_count %>)</h3> <h3 class="comments icon icon-comments"><%= sprite_icon('comments', l(:label_reply_plural)) %>
<% if !@topic.locked? && authorize_for('messages', 'reply') && @replies.size >= 3 %> (<%= @reply_count %>)</h3>
<p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content', :scroll => "message_content" %></p> <% if !@topic.locked? && authorize_for('messages', 'reply') && @replies.size >= 3 %>
<% end %> <p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content', :scroll => "message_content" %></p>
<% @replies.each do |message| %> <% end %>
<div class="message reply" id="<%= "message-#{message.id}" %>" data-controller="quote-reply"> <% @replies.each do |message| %>
<div class="contextual"> <div class="message reply journal" id="<%= "message-#{message.id}" %>" data-controller="quote-reply">
<%= reaction_button message %> <h4 class='reply-header journal-header'>
<%= quote_reply_button( <span class="journal-info">
url: url_for(action: 'quote', id: message, format: 'js'), <%= avatar(message.author) %>
icon_only: true <%= link_to message.subject, { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :r => message, :anchor => "message-#{message.id}" } %>
) if !@topic.locked? && authorize_for('messages', 'reply') %> -
<%= link_to( <%= authoring message.created_on, message.author %>
sprite_icon('edit', l(:button_edit), icon_only: true), </span>
{:action => 'edit', :id => message}, <span class="journal-meta">
:title => l(:button_edit), <%= reaction_button message %>
:class => 'icon icon-edit' <%= quote_reply_button(
) if message.editable_by?(User.current) %> url: url_for(action: 'quote', id: message, format: 'js'),
<%= link_to( icon_only: true
sprite_icon('del', l(:button_delete), icon_only: true), ) if !@topic.locked? && authorize_for('messages', 'reply') %>
{:action => 'destroy', :id => message}, <%= link_to(
:method => :post, sprite_icon('edit', l(:button_edit), icon_only: true),
:data => {:confirm => l(:text_are_you_sure)}, { :action => 'edit', :id => message },
:title => l(:button_delete), :title => l(:button_edit),
:class => 'icon icon-del' :class => 'icon icon-edit'
) if message.destroyable_by?(User.current) %> ) if message.editable_by?(User.current) %>
</div> <%= link_to(
<h4 class='reply-header'> sprite_icon('del', l(:button_delete), icon_only: true),
<%= avatar(message.author) %> { :action => 'destroy', :id => message },
<%= link_to message.subject, { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :r => message, :anchor => "message-#{message.id}" } %> :method => :post,
- :data => { :confirm => l(:text_are_you_sure) },
<%= authoring message.created_on, message.author %> :title => l(:button_delete),
</h4> :class => 'icon icon-del'
<div class="wiki" data-quote-reply-target="content"> ) if message.destroyable_by?(User.current) %>
<%= textilizable message, :content, :attachments => message.attachments %> </span>
</h4>
<div class="wiki journal-content" data-quote-reply-target="content">
<%= textilizable message, :content, :attachments => message.attachments %>
</div>
<%= link_to_attachments message, :author => false, :thumbnails => true %>
</div>
<% end %>
</div> </div>
<%= link_to_attachments message, :author => false, :thumbnails => true %> <span class="pagination"><%= pagination_links_full @reply_pages, @reply_count, :per_page_links => false %></span>
</div>
<% end %>
</div>
<span class="pagination"><%= pagination_links_full @reply_pages, @reply_count, :per_page_links => false %></span>
<% end %> <% end %>
<% if !@topic.locked? && authorize_for('messages', 'reply') %> <% if !@topic.locked? && authorize_for('messages', 'reply') %>
<p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p> <p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p>
<div id="reply" style="display:none;"> <div id="reply" style="display:none;">
<%= form_for @reply, :as => :reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true, :id => 'message-form'} do |f| %> <%= form_for @reply, :as => :reply, :url => { :action => 'reply', :id => @topic }, :html => { :multipart => true, :id => 'message-form' } do |f| %>
<%= render :partial => 'form', :locals => {:f => f, :replying => true} %> <%= render :partial => 'form', :locals => { :f => f, :replying => true } %>
<%= submit_tag l(:button_submit) %> <%= submit_tag l(:button_submit) %>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<% html_title @topic.subject %> <% html_title @topic.subject %>
<% content_for :sidebar do %> <% content_for :sidebar do %>
<% if User.current.allowed_to?(:add_message_watchers, @project) || <% if User.current.allowed_to?(:add_message_watchers, @project) ||
(@topic.watchers.present? && User.current.allowed_to?(:view_message_watchers, @project)) %> (@topic.watchers.present? && User.current.allowed_to?(:view_message_watchers, @project)) %>
<div id="watchers"> <div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @topic} %> <%= render :partial => 'watchers/watchers', :locals => { :watched => @topic } %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>

View File

@@ -35,23 +35,30 @@
</div> </div>
<br /> <br />
<div id="comments" style="margin-bottom:16px;"> <div id="comments" class="journals">
<h3 class="comments"><%= l(:label_comment_plural) %></h3> <h3 class="comments"><%= l(:label_comment_plural) %></h3>
<% if @news.commentable? && @comments.size >= 3 %> <% if @news.commentable? && @comments.size >= 3 %>
<p><%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments", :scroll => "comment_comments" %></p> <p><%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments", :scroll => "comment_comments" %></p>
<% end %> <% end %>
<% @comments.each do |comment| %> <% @comments.each do |comment| %>
<% next if comment.new_record? %> <div class="message reply journal-entry" id="<%= "message-#{comment.id}" %>">
<div class="contextual"> <% next if comment.new_record? %>
<%= reaction_button comment %> <h4 class="reply-header journal-header">
<%= link_to_if_authorized sprite_icon('del', l(:button_delete)), { :controller => 'comments', :action => 'destroy', :id => @news, :comment_id => comment}, <span class="journal-info">
:data => {:confirm => l(:text_are_you_sure)}, :method => :delete, <%= avatar(comment.author) %>
:title => l(:button_delete), <%= authoring comment.created_on, comment.author %>
:class => 'icon-only icon-del' %> </span>
</div> <span class="journal-meta">
<h4><%= avatar(comment.author) %><%= authoring comment.created_on, comment.author %></h4> <%= reaction_button comment %>
<div class="wiki"> <%= link_to_if_authorized sprite_icon('del', l(:button_delete)), { :controller => 'comments', :action => 'destroy', :id => @news, :comment_id => comment},
<%= textilizable(comment.comments) %> :data => {:confirm => l(:text_are_you_sure)}, :method => :delete,
:title => l(:button_delete),
:class => 'icon-only icon-del' %>
</span>
</h4>
<div class="wiki journal-content">
<%= textilizable(comment.comments) %>
</div>
</div> </div>
<% end if @comments.any? %> <% end if @comments.any? %>
</div> </div>

View File

@@ -2485,7 +2485,7 @@ class IssuesControllerTest < Redmine::ControllerTest
end end
assert_select 'div#tab-content-history' do assert_select 'div#tab-content-history' do
assert_select 'div[id=?]', "change-#{Issue.find(1).journals.last.id}" do assert_select 'div[id=?]', "change-#{Issue.find(1).journals.last.id}" do
assert_select 'ul.details', :text => "Subtask ##{issue.id} added" assert_select 'ul.journal-details', :text => "Subtask ##{issue.id} added"
end end
end end
end end
@@ -3305,7 +3305,7 @@ class IssuesControllerTest < Redmine::ControllerTest
assert_select 'a[title=?][href=?]', 'Edit', '/time_entries/3/edit' assert_select 'a[title=?][href=?]', 'Edit', '/time_entries/3/edit'
assert_select 'a[title=?][href=?]', 'Delete', '/time_entries/3' assert_select 'a[title=?][href=?]', 'Delete', '/time_entries/3'
assert_select 'ul[class=?]', 'details', :text => /1.00 h/ assert_select 'ul[class=?]', 'journal-details', :text => /1.00 h/
end end
end end
@@ -8697,7 +8697,7 @@ class IssuesControllerTest < Redmine::ControllerTest
assert_select 'div#tab-content-history' do assert_select 'div#tab-content-history' do
assert_select 'div[id=?]', "change-#{parent.journals.last.id}" do assert_select 'div[id=?]', "change-#{parent.journals.last.id}" do
assert_select 'ul.details', :text => "Subtask deleted (##{child.id})" assert_select 'ul.journal-details', :text => "Subtask deleted (##{child.id})"
end end
end end
end end

View File

@@ -101,9 +101,9 @@ class IssuesCustomFieldsVisibilityTest < Redmine::ControllerTest
get(:show, :params => {:id => @issue.id}) get(:show, :params => {:id => @issue.id})
@fields.each_with_index do |field, i| @fields.each_with_index do |field, i|
if fields.include?(field) if fields.include?(field)
assert_select 'ul.details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change" assert_select 'ul.journal-details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change"
else else
assert_select 'ul.details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change" assert_select 'ul.journal-details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change"
end end
end end
end end

View File

@@ -355,7 +355,7 @@ class IssuesTest < Redmine::IntegrationTest
end end
# Issue view # Issue view
follow_redirect! follow_redirect!
assert_select 'ul.details li', :text => "Tester changed from #{tester} to #{new_tester}" assert_select 'ul.journal-details li', :text => "Tester changed from #{tester} to #{new_tester}"
end end
end end

View File

@@ -93,7 +93,7 @@ class IssuesReplyTest < ApplicationSystemTestCase
# Select the entire details of the note#1 and the part of the note#1's text. # Select the entire details of the note#1 and the part of the note#1's text.
page.execute_script <<-JS page.execute_script <<-JS
const range = document.createRange(); const range = document.createRange();
range.setStartBefore(document.querySelector('#change-1 .details')); range.setStartBefore(document.querySelector('#change-1 .journal-details'));
// Select only the text "Journal" from the text "Journal notes" in the note-1. // Select only the text "Journal" from the text "Journal notes" in the note-1.
range.setEnd(document.querySelector('#change-1 .wiki > p').childNodes[0], 7); range.setEnd(document.querySelector('#change-1 .wiki > p').childNodes[0], 7);