Project

General

Profile

Patch #42938 » adds_subitem_icon.patch

patch - Liane Hampe, 2025-06-30 17:01

View differences:

app/assets/stylesheets/application.css
388 388
table.issues td.block_column>span {font-weight: bold; display: block; margin-bottom: 4px;}
389 389
table.issues td.block_column>pre {white-space:normal;}
390 390

  
391
tr.idnt td.subject, tr.idnt td.name {background: url(/chevron-right-idnt.svg) no-repeat 2px 50%;}
392
tr.idnt-1 td.subject, tr.idnt-1 td.name {padding-left: 24px; background-position: 4px 50%;}
393
tr.idnt-2 td.subject, tr.idnt-2 td.name {padding-left: 40px; background-position: 20px 50%;}
394
tr.idnt-3 td.subject, tr.idnt-3 td.name {padding-left: 56px; background-position: 36px 50%;}
395
tr.idnt-4 td.subject, tr.idnt-4 td.name {padding-left: 72px; background-position: 52px 50%;}
396
tr.idnt-5 td.subject, tr.idnt-5 td.name {padding-left: 88px; background-position: 68px 50%;}
397
tr.idnt-6 td.subject, tr.idnt-6 td.name {padding-left: 104px; background-position: 84px 50%;}
398
tr.idnt-7 td.subject, tr.idnt-7 td.name {padding-left: 120px; background-position: 100px 50%;}
399
tr.idnt-8 td.subject, tr.idnt-8 td.name {padding-left: 136px; background-position: 116px 50%;}
400
tr.idnt-9 td.subject, tr.idnt-9 td.name {padding-left: 152px; background-position: 132px 50%;}
391
tr.idnt-1 td.subject, tr.idnt-1 td.name {padding-left: 0;}
392
tr.idnt-2 td.subject, tr.idnt-2 td.name {padding-left: 20px;}
393
tr.idnt-3 td.subject, tr.idnt-3 td.name {padding-left: 36px;}
394
tr.idnt-4 td.subject, tr.idnt-4 td.name {padding-left: 52px;}
395
tr.idnt-5 td.subject, tr.idnt-5 td.name {padding-left: 68px;}
396
tr.idnt-6 td.subject, tr.idnt-6 td.name {padding-left: 84px;}
397
tr.idnt-7 td.subject, tr.idnt-7 td.name {padding-left: 100px;}
398
tr.idnt-8 td.subject, tr.idnt-8 td.name {padding-left: 116px;}
399
tr.idnt-9 td.subject, tr.idnt-9 td.name {padding-left: 132px;}
401 400

  
402 401
table.issue-report {table-layout:fixed;}
403 402
table.issue-report tr.total, table.issue-report-detailed tr.total { font-weight: bold; border-top:2px solid #d0d7de;}
app/helpers/application_helper.rb
191 191
      h(project.name)
192 192
    else
193 193
      link_to project.name,
194
              project_url(project, {:only_path => true}.merge(options)),
194
              # if it is a Struct it comes from ProjectQueriesHelper#project_with_optional_icon
195
              project_url(project.is_a?(Struct) ? project.item : project, {:only_path => true}.merge(options)),
195 196
              html_options
196 197
    end
197 198
  end
app/helpers/projects_queries_helper.rb
24 24
      case column.name
25 25
      when :name
26 26
        link_to_project(item) +
27
          (tag.span(sprite_icon('user', l(:label_my_projects), icon_only: true), class: 'icon-only icon-user my-project') if User.current.member_of?(item)) +
28
          (tag.span(sprite_icon('bookmarked', l(:label_my_bookmarks), icon_only: true), class: 'icon-only icon-bookmarked-project') if User.current.bookmarked_project_ids.include?(item.id))
27
          user_project_icons(item)
29 28
      when :short_description
30 29
        if item.description?
31 30
          # Sets :inline_attachments to false to avoid performance issues
......
56 55
    end
57 56
  end
58 57

  
58
  def column_content_with_subitem_icon(column, item, value, level)
59
    if item.is_a?(Project)
60
      content =
61
        case column.name
62
        when :name
63
          name = (item.child? && level&.positive?) ? sprite_icon('angle-right', item.name) : item.name
64
          link_to_project(project_with_subitem_icon(item, name)) +
65
            user_project_icons(item)
66
        else
67
          column_value(column, item, value)
68
        end
69

  
70
      content
71
    end
72
  end
73

  
59 74
  def csv_value(column, object, value)
60 75
    if object.is_a?(Project)
61 76
      case column.name
......
71 86

  
72 87
  private
73 88

  
89
  def user_project_icons(item)
90
    content = + ''
91
    content += tag.span(sprite_icon('user', l(:label_my_projects), icon_only: true), class: 'icon-only icon-user my-project') if User.current.member_of?(item)
92
    content += tag.span(sprite_icon('bookmarked', l(:label_my_bookmarks), icon_only: true), class: 'icon-only icon-bookmarked-project') if User.current.bookmarked_project_ids.include?(item.id)
93
    content.html_safe
94
  end
95

  
96
  # Struct object that behaves like Project#archived? and Project#name
97
  #
98
  # @param item [Project] Instance of a Project
99
  # @param name [String|ActiveSupport::SafeBuffer] If name is not a String
100
  #                                                the object should hold the angle-right icon
101
  #                                                with the subprojects name
102
  # @return [Struct] Object responding to item, name, and archived?
103
  def project_with_subitem_icon(item, name)
104
    Struct.new(:item, :name) do
105
      def archived?
106
        item.archived?
107
      end
108
    end.new(item, name)
109
  end
110

  
74 111
  def get_project_status_label
75 112
    {
76 113
      Project::STATUS_ACTIVE => l(:project_status_active),
app/helpers/queries_helper.rb
237 237
    content_tag('th', content, :class => column.css_classes)
238 238
  end
239 239

  
240
  def column_content(column, item)
240
  def column_content(column, item, level = nil)
241 241
    value = column.value_object(item)
242 242
    content =
243 243
      if value.is_a?(Array)
244 244
        values = value.filter_map {|v| column_value(column, item, v)}
245 245
        safe_join(values, ', ')
246 246
      else
247
        column_value(column, item, value)
247
        level ? column_content_with_subitem_icon(column, item, value, level) : column_value(column, item, value)
248 248
      end
249 249

  
250 250
    call_hook(:helper_queries_column_content,
251
              {:content => content, :column => column, :item => item})
251
              {:content => content, :column => column, :item => item, :level =>level})
252

  
253
    content
254
  end
255

  
256
  def column_content_with_subitem_icon(column, item, value, level)
257
    content =
258
      case column.name
259
      when :subject
260
        subject = (item.child? && level&.positive?) ? sprite_icon('angle-right', value) : value
261
        link_to subject, issue_path(item)
262
      else
263
        column_value(column, item, value)
264
      end
265

  
266
    call_hook(:helper_queries_column_value,
267
              {:content => content, :column => column, :item => item, :value => value})
252 268

  
253 269
    content
254 270
  end
app/views/issues/_list.html.erb
34 34
  <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
35 35
    <td class="checkbox hide-when-print"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
36 36
    <% query.inline_columns.each do |column| %>
37
    <%= content_tag('td', column_content(column, issue), :class => column.css_classes) %>
37
    <%= content_tag('td', column_content(column, issue, level), :class => column.css_classes) %>
38 38
    <% end %>
39 39
    <td class="buttons"><%= link_to_context_menu %></td>
40 40
  </tr>
app/views/projects/_list.html.erb
49 49
      <% end %>
50 50
    <% end %>
51 51
    <% @query.inline_columns.each do |column| %>
52
    <%= content_tag('td', column_content(column, entry), :class => column.css_classes) %>
52
    <%= content_tag('td', column_content(column, entry, level), :class => column.css_classes) %>
53 53
    <% end %>
54 54
    <% if @admin_list %>
55 55
      <% if !entry.scheduled_for_deletion? %>
test/helpers/application_helper_test.rb
22 22
class ApplicationHelperTest < Redmine::HelperTest
23 23
  include ERB::Util
24 24
  include AvatarsHelper
25
  include ProjectsQueriesHelper
25 26

  
26 27
  def setup
27 28
    super
......
1974 1975
                 link_to_project(project, {:only_path => false, :jump => 'blah'})
1975 1976
  end
1976 1977

  
1978
  def test_link_to_project_with_subitem_icon
1979
    item = Project.find(3)
1980
    level = 1
1981
    name = (item.child? && level&.positive?) ? sprite_icon('angle-right', item.name) : item.name
1982
    # requires ProjectsQueriesHelper
1983
    project = project_with_subitem_icon(item, name)
1984
    assert_equal %(<a href=\"/projects/subproject1\"><svg class=\"s18 icon-svg\" aria-hidden=\"true\"><use href=\"/assets/icons-1857f271.svg#icon--angle-right\"></use></svg><span class=\"icon-label\">eCookbook Subproject 1</span></a>),
1985
                 link_to_project(project)
1986
  end
1987

  
1977 1988
  def test_link_to_project_settings
1978 1989
    project = Project.find(1)
1979 1990
    assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
test/helpers/projects_queries_helper_test.rb
29 29
    assert_equal "active", csv_value(c_status, Project.find(1), 1)
30 30
    assert_equal "eCookbook", csv_value(c_parent_id, Project.find(4), 1)
31 31
  end
32

  
33
  def test_column_content_with_subitem_icon
34
    column = QueryColumn.new(:name)
35
    item = Project.find(3)
36
    value = Project.name
37
    level = 1
38

  
39
    content = column_content_with_subitem_icon(column, item, value, level)
40
    name_with_subitem_icon = %(<a href=\"/projects/subproject1\"><svg class=\"s18 icon-svg\" aria-hidden=\"true\"><use href=\"/assets/icons-1857f271.svg#icon--angle-right\"></use></svg><span class=\"icon-label\">eCookbook Subproject 1</span></a>)
41
    assert_equal name_with_subitem_icon, content
42
  end
32 43
end
test/helpers/queries_helper_test.rb
109 109
    assert_select_in options, 'option[value=?]', "cf_#{i_cf.id}.cf_#{u_cf.id}", text: "User's Phone number"
110 110
    assert_select_in options, 'optgroup[label=?]', 'User', 1
111 111
  end
112

  
113
  def test_column_content_should_use_column_content_with_subitem_icon
114
    item =  Issue.generate!(parent_id: 2)
115
    column = QueryColumn.new(:subject)
116
    value = item.subject
117
    level = 1
118
    content = column_content(column, item, level)
119
    assert_equal name_with_subitem_icon(item), content
120
  end
121

  
122
  private
123

  
124
  def name_with_subitem_icon(item)
125
    %(<a href=\"/issues/#{item.id}\"><svg class=\"s18 icon-svg\" aria-hidden=\"true\"><use href=\"/assets/icons-1857f271.svg#icon--angle-right\"></use></svg><span class=\"icon-label\">Generated</span></a>)
126
  end
112 127
end
(3-3/3)