Project

General

Profile

Feature #23980 » 0001-Add-tag-helper-patch.patch

Takashi Kato, 2022-03-21 22:31

View differences:

app/helpers/application_helper.rb
27 27
  include Redmine::SudoMode::Helper
28 28
  include Redmine::Themes::Helper
29 29
  include Redmine::Hook::Helper
30
  include Redmine::Icon::Helper
30 31
  include Redmine::Helpers::URL
31 32

  
32 33
  extend Forwardable
app/models/icon_set.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2022  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
class IconSet
21
  extend ActionView::Helpers::AssetUrlHelper
22
  extend Redmine::Icon::MapLoader
23

  
24
  def self.fetch(name)
25
    @icons ||= create_map
26
    @icons[name]
27
  end
28

  
29
  def self.create_map
30
    icon_map = load_icon_map
31

  
32
    Redmine::Plugin.all.each { |plugin| icon_map.merge! plugin.load_icon_map }
33

  
34
    current_theme = Redmine::Themes.theme(Setting.ui_theme)
35
    icon_map.merge! current_theme.load_icon_map if current_theme.present?
36

  
37
    icon_map.freeze
38
  end
39

  
40
  def self.icon_config_dir
41
    Rails.application.config.redmine_svg_icon_map
42
  end
43

  
44
  def self.icon_dir
45
    '/icons'
46
  end
47
end
config/application.rb
75 75
    # for more options (same options as config.cache_store).
76 76
    config.redmine_search_cache_store = :memory_store
77 77

  
78
    # Enable experimental svg icons
79
    config.redmine_enable_svg_icon = false
80

  
81
    config.redmine_svg_icon_source = File.join(__dir__, 'icon_source.yml')
82

  
83
    config.redmine_svg_icon_map = File.join(__dir__, 'icons')
84

  
78 85
    # Configure log level here so that additional environment file
79 86
    # can change it (environments/ENV.rb would take precedence over it)
80 87
    config.log_level = Rails.env.production? ? :info : :debug
lib/redmine/icon.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2021  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require 'yaml'
21

  
22
module Redmine
23
  module Icon
24
    module Helper
25
      def expander
26
        tag.span ' ', class: ['expander', 'icon', 'icon-expanded'], onclick: 'toggleRowGroup(this);'
27
      end
28

  
29
      def collapsible(is_new_record)
30
        css_class = is_new_record ? 'icon-expanded' : 'icon-collapsed'
31
        ['icon', css_class]
32
      end
33

  
34
      def preload_icon_link(name)
35
        if Rails.application.config.redmine_enable_svg_icon
36
          icon_path = "#{Redmine::Utils.relative_url_root}#{icon_set_path(name)}"
37
          preload_link_tag(icon_path, as: 'fetch', id: "#{name}_icon_path", crossorigin: "anonymous", type: "application/json")
38
        else
39
          ''
40
        end
41
      end
42
    end
43

  
44
    module Patch
45
      # link_to 'test', '/test', class: "icon icon-add"
46
      # => <a class="icon icon-add icon-svg" href="/test">
47
      #      <svg class="s16" xmlns="http://www.w3.org/2000/svg"><use href="/icons/plus-circle.svg#icon"></use></svg>test
48
      #    </a>
49
      #
50
      # link_to 'test.rb', '/test.rb', class: "icon icon-file text-x-ruby"
51
      # => <a class="icon icon-file text-x-ruby icon-svg" href="/test.rb">
52
      #      <svg class="s16" xmlns="http://www.w3.org/2000/svg"><use href="/icons/language-ruby.svg#icon"></use></svg>test.rb
53
      #    </a>
54
      #
55
      # # when svg icon is not found
56
      #
57
      # link_to 'test', '/test', class: "icon icon-foo"
58
      # => <a href="/test" class="icon icon-foo"><!-- SVG icon-foo not found -->test</a>
59
      #
60
      def content_tag_string(name, content, options, escape = true)
61
        return super unless need_process?(name, options)
62

  
63
        processed = content_with_icon(content, options, escape)
64
        super name, processed, options, escape
65
      end
66
      DEFAULT_SVG_CSS_CLASS = 's16'
67
      FILE_TYPE_REGEXP = /(text-|image-|application-)/.freeze
68
      XMLNS = 'http://www.w3.org/2000/svg'
69

  
70
      private
71

  
72
      def need_process?(name, options)
73
        Rails.application.config.redmine_enable_svg_icon &&
74
        name != :use && name != :svg && options.present? &&
75
        (options[:class].present? || options['class'].present?)
76
      end
77

  
78
      def content_with_icon(content, options, escape)
79
        options.symbolize_keys!
80
        classes = split_tokens options[:class]
81
        icon = classes.include?('icon')
82
        icon_only = classes.include?('icon-only')
83
        return content unless icon || icon_only
84

  
85
        svg = create_svg_icon(classes, options)
86
        (icon_only ? svg : "#{svg}#{content}").html_safe
87
      end
88

  
89
      # copy from ActionView::Helpers::TagHelper#token_list
90
      def split_tokens(*args)
91
        ActionView::Helpers::TagHelper.build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
92
      end
93

  
94
      def create_svg_icon(classes, options)
95
        icon_name = classes.find { |c| c != 'icon-only' && c.start_with?('icon-') }
96
        file_type = classes.find { |c| c.start_with?(FILE_TYPE_REGEXP) }
97
        svg = fetch_svg(icon_name, file_type)
98
        if svg.present?
99
          classes << 'icon-svg'
100
          options[:class] = classes.join(' ')
101
          svg
102
        else
103
          "<!-- SVG #{file_type || icon_name} not found -->"
104
        end
105
      end
106

  
107
      def fetch_svg(icon_name, file_type)
108
        if icon_name == 'icon-file' && file_type
109
          svg_tag(file_type, DEFAULT_SVG_CSS_CLASS, IconSet.fetch('file'))
110
        else
111
          svg_tag(icon_name, DEFAULT_SVG_CSS_CLASS, IconSet.fetch('common'))
112
        end
113
      end
114

  
115
      def svg_tag(name, css_class, icon_map)
116
        return '' unless icon_map.key?(name)
117

  
118
        content = content_tag_string(:use, nil, { :href => "#{icon_map[name]}#icon" })
119
        content_tag_string(:svg, content, { :class => css_class, :xmlns => XMLNS })
120
      end
121
    end
122

  
123
    module MapLoader
124
      def load_icon_map
125
        pattern = File.join icon_config_dir, '*.{yaml,yml}'
126
        Dir.glob(pattern).each_with_object({}) do |f, o|
127
          name    = File.basename f, '.*'
128
          value   = YAML.safe_load(File.open(Rails.root.join(f)))
129
          o[name] = value.transform_values { |e| path_to_asset File.join(icon_dir, e) }
130
        end
131
      end
132
    end
133
  end
134
end
lib/redmine/plugin.rb
50 50
  #
51 51
  # See: http://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial
52 52
  class Plugin
53
    include Redmine::Icon::MapLoader
54

  
53 55
    # Absolute path to the directory where plugins are located
54 56
    cattr_accessor :directory
55 57
    self.directory = PluginLoader.directory
......
525 527
        super(version.to_s + "-" + current_plugin.id.to_s)
526 528
      end
527 529
    end
530

  
531
    def icon_config_dir
532
      File.join directory, 'config', 'icons'
533
    end
534

  
535
    def icon_dir
536
      File.join public_directory.sub(Rails.public_path.to_s, ''), 'icons'
537
    end
528 538
  end
529 539
end
lib/redmine/preparation.rb
23 23
      ActiveRecord::Base.include Redmine::Acts::Positioned
24 24
      ActiveRecord::Base.include Redmine::Acts::Mentionable
25 25
      ActiveRecord::Base.include Redmine::I18n
26
      ActionView::Helpers::TagHelper::TagBuilder.prepend Redmine::Icon::Patch
26 27

  
27 28
      Scm::Base.add "Subversion"
28 29
      Scm::Base.add "Mercurial"
lib/redmine/themes.rb
43 43

  
44 44
    # Class used to represent a theme
45 45
    class Theme
46
      include Redmine::Icon::MapLoader
47

  
46 48
      attr_reader :path, :name, :dir
47 49

  
48 50
      def initialize(path)
......
104 106
        "/themes/#{dir}/favicon/#{favicon}"
105 107
      end
106 108

  
109
      def icon_config_dir
110
        "/themes/#{dir}"
111
      end
112

  
113
      def icon_dir
114
        Rails.public_path.join("#{icon_config_dir}/icons")
115
      end
116

  
107 117
      private
108 118

  
109 119
      def assets(dir, ext=nil)
lib/redmine/views/other_formats_builder.rb
27 27
      def link_to(name, options={})
28 28
        url = {:format => name.to_s.downcase}.merge(options.delete(:url) || {}).except('page')
29 29
        caption = options.delete(:caption) || name
30
        html_options = {:class => name.to_s.downcase, :rel => 'nofollow'}.merge(options)
30
        html_options = {:class => icon(name), :rel => 'nofollow'}.merge(options)
31 31
        @view.content_tag('span', @view.link_to(caption, url, html_options))
32 32
      end
33 33

  
......
37 37
        url = {:params => params, :page => nil, :format => name.to_s.downcase}.merge(url)
38 38

  
39 39
        caption = options.delete(:caption) || name
40
        html_options = {:class => name.to_s.downcase, :rel => 'nofollow'}.merge(options)
40
        html_options = {:class => icon(name), :rel => 'nofollow'}.merge(options)
41 41
        @view.content_tag('span', @view.link_to(caption, url, html_options))
42 42
      end
43

  
44
      def icon(name)
45
        icon_map = IconSet.fetch('common')
46
        format_name = name.to_s.downcase
47
        icon_map["icon-#{format_name}"] ? "icon icon-#{format_name}" : format_name
48
      end
43 49
    end
44 50
  end
45 51
end
public/stylesheets/application.css
160 160
#sidebar a.selected:hover {text-decoration:none;}
161 161
#sidebar .query.default {font-weight: bold;}
162 162
#admin-menu a {line-height:1.7em;}
163
#admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
163
#admin-menu a.selected {background-position: 2px 40%;}
164
#admin-menu a.selected:not(.icon-svg) {padding-left: 20px !important;}
164 165

  
165 166
a.collapsible {padding-left: 12px; }
166 167

  
......
571 572
#issue-form .assign-to-me-link { padding-left: 5px; }
572 573

  
573 574
fieldset.collapsible {border-width: 1px 0 0 0;}
574
fieldset.collapsible>legend { cursor:pointer; padding-left: 18px; background-position: 4px;}
575
fieldset.collapsible>legend { cursor:pointer;}
576
fieldset.collapsible>legend:not(.icon-svg) { padding-left: 18px; background-position: 4px;}
575 577

  
576 578
fieldset#date-range p { margin: 2px 0 2px 0; }
577 579
fieldset#filters table { border-collapse: collapse; }
......
722 724
  padding-top: 0.5em;
723 725
}
724 726
#projects-index a.icon-user, a.icon-bookmarked-project {padding-left:0; padding-right:20px; background-position:98% 50%;}
725
#projects-index a.icon-user.icon-bookmarked-project {
727
#projects-index a.icon-user.icon-bookmarked-project:not(.icon-svg) {
726 728
  background-image: url(../images/tag_blue.png), url(../images/user.png);
727 729
  background-position: bottom 0px right 0px, bottom 0px right 20px;
728 730
  padding-right: 40px;
......
963 965
p.other-formats { text-align: right; font-size:0.9em; color: #666; }
964 966
.other-formats span + span:before { content: "| "; }
965 967

  
966
a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
968
.icon-atom:not(.icon-svg) { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
967 969

  
968 970
em.info {font-style:normal;display:block;font-size:90%;color:#888;}
969 971
em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
......
1521 1523
  white-space: pre-wrap;
1522 1524
}
1523 1525

  
1526
/***** SVGs ******/
1527
.s16 {
1528
  width: 16px;
1529
  height: 16px;
1530
  vertical-align: text-bottom;
1531
}
1532

  
1524 1533
/***** Icons *****/
1525
.icon {
1534
.icon:not(.icon-svg) {
1526 1535
  background-position: 0% 50%;
1527 1536
  background-repeat: no-repeat;
1528 1537
  padding-left: 20px;
1529 1538
}
1530
.icon-only {
1539

  
1540
a svg, .icon-svg svg {
1541
  color: var(--color-link-icon);
1542
  margin-right: 4px;
1543
}
1544
a:hover svg, a:active svg {
1545
  color: var(--color-active-icon);
1546
}
1547

  
1548
.icon-only:not(.icon-svg) {
1531 1549
  background-position: 0% 50%;
1532 1550
  background-repeat: no-repeat;
1533 1551
  padding-left: 16px;
......
1544 1562
  content: "\a0";
1545 1563
}
1546 1564

  
1547
.icon-add { background-image: url(../images/add.png); }
1548
.icon-edit { background-image: url(../images/edit.png); }
1549
.icon-copy { background-image: url(../images/copy.png); }
1550
.icon-duplicate { background-image: url(../images/duplicate.png); }
1551
.icon-del { background-image: url(../images/delete.png); }
1552
.icon-move { background-image: url(../images/move.png); }
1553
.icon-save { background-image: url(../images/save.png); }
1554
.icon-download { background-image: url(../images/download.png); }
1555
.icon-cancel { background-image: url(../images/cancel.png); }
1556
.icon-multiple { background-image: url(../images/table_multiple.png); }
1557
.icon-folder { background-image: url(../images/folder.png); }
1558
.open .icon-folder { background-image: url(../images/folder_open.png); }
1559
.icon-package { background-image: url(../images/package.png); }
1560
.icon-user { background-image: url(../images/user.png); }
1561
.icon-project, .icon-projects { background-image: url(../images/projects.png); }
1562
.icon-help { background-image: url(../images/help.png); }
1563
.icon-attachment  { background-image: url(../images/attachment.png); }
1564
.icon-history  { background-image: url(../images/history.png); }
1565
.icon-time-entry, .icon-time  { background-image: url(../images/time.png); }
1566
.icon-time-add  { background-image: url(../images/time_add.png); }
1567
.icon-stats  { background-image: url(../images/stats.png); }
1568
.icon-warning  { background-image: url(../images/warning.png); }
1569
.icon-error { background-image: url(../images/exclamation.png); }
1570
.icon-fav  { background-image: url(../images/fav.png); }
1571
.icon-fav-off  { background-image: url(../images/fav_off.png); }
1572
.icon-reload  { background-image: url(../images/reload.png); }
1573
.icon-lock, .icon-locked  { background-image: url(../images/locked.png); }
1574
.icon-unlock  { background-image: url(../images/unlock.png); }
1575
.icon-checked  { background-image: url(../images/toggle_check.png); }
1576
.icon-report  { background-image: url(../images/report.png); }
1577
.icon-comment, .icon-comments  { background-image: url(../images/comment.png); }
1578
.icon-summary  { background-image: url(../images/lightning.png); }
1579
.icon-server-authentication { background-image: url(../images/server_key.png); }
1580
.icon-issue { background-image: url(../images/ticket.png); }
1581
.icon-zoom-in { background-image: url(../images/zoom_in.png); }
1582
.icon-zoom-out { background-image: url(../images/zoom_out.png); }
1583
.icon-magnifier { background-image: url(../images/magnifier.png); }
1584
.icon-passwd { background-image: url(../images/textfield_key.png); }
1585
.icon-arrow-right, .icon-test, .icon-sticky { background-image: url(../images/bullet_go.png); }
1586
.icon-email { background-image: url(../images/email.png); }
1587
.icon-email-disabled { background-image: url(../images/email_disabled.png); }
1588
.icon-email-add { background-image: url(../images/email_add.png); }
1589
.icon-ok { background-image: url(../images/true.png); }
1590
.icon-not-ok { background-image: url(../images/false.png); }
1591
.icon-link-break { background-image: url(../images/link_break.png); }
1592
.icon-list { background-image: url(../images/text_list_bullets.png); }
1593
.icon-close { background-image: url(../images/close.png); }
1594
.icon-close:hover { background-image: url(../images/close_hl.png); }
1595
.icon-settings { background-image: url(../images/changeset.png); }
1596
.icon-group, .icon-groupnonmember, .icon-groupanonymous { background-image: url(../images/group.png); }
1597
.icon-roles { background-image: url(../images/database_key.png); }
1598
.icon-issue-edit { background-image: url(../images/ticket_edit.png); }
1599
.icon-workflows { background-image: url(../images/ticket_go.png); }
1600
.icon-custom-fields { background-image: url(../images/textfield.png); }
1601
.icon-plugins { background-image: url(../images/plugin.png); }
1602
.icon-news { background-image: url(../images/news.png); }
1603
.icon-issue-closed { background-image: url(../images/ticket_checked.png); }
1604
.icon-issue-note { background-image: url(../images/ticket_note.png); }
1605
.icon-changeset { background-image: url(../images/changeset.png); }
1606
.icon-message { background-image: url(../images/message.png); }
1607
.icon-reply { background-image: url(../images/comments.png); }
1608
.icon-wiki-page { background-image: url(../images/wiki_edit.png); }
1609
.icon-document { background-image: url(../images/document.png); }
1610
.icon-project { background-image: url(../images/projects.png); }
1611
.icon-add-bullet { background-image: url(../images/bullet_add.png); }
1612
.icon-shared { background-image: url(../images/link.png); }
1613
.icon-actions { background-image: url(../images/3_bullets.png); }
1614
.icon-sort-handle { background-image: url(../images/reorder.png); }
1615
.icon-expanded { background-image: url(../images/arrow_down.png); }
1616
.icon-collapsed { background-image: url(../images/arrow_right.png); }
1617
.icon-bookmark { background-image: url(../images/tag_blue_delete.png); }
1618
.icon-bookmark-off { background-image: url(../images/tag_blue_add.png); }
1619
.icon-bookmarked-project { background-image: url(../images/tag_blue.png); }
1620
.icon-sorted-asc { background-image: url(../images/arrow_down.png); }
1621
.icon-sorted-desc { background-image: url(../images/arrow_up.png); }
1622
.icon-toggle-plus { background-image: url(../images/bullet_toggle_plus.png) }
1623
.icon-toggle-minus { background-image: url(../images/bullet_toggle_minus.png) }
1624
.icon-clear-query { background-image: url(../images/close_hl.png); }
1625
.icon-import { background-image: url(../images/database_go.png); }
1565
.icon-add:not(.icon-svg) { background-image: url(../images/add.png); }
1566
.icon-edit:not(.icon-svg) { background-image: url(../images/edit.png); }
1567
.icon-copy:not(.icon-svg) { background-image: url(../images/copy.png); }
1568
.icon-duplicate:not(.icon-svg) { background-image: url(../images/duplicate.png); }
1569
.icon-del:not(.icon-svg) { background-image: url(../images/delete.png); }
1570
.icon-move:not(.icon-svg) { background-image: url(../images/move.png); }
1571
.icon-save:not(.icon-svg) { background-image: url(../images/save.png); }
1572
.icon-download:not(.icon-svg) { background-image: url(../images/download.png); }
1573
.icon-cancel:not(.icon-svg) { background-image: url(../images/cancel.png); }
1574
.icon-multiple:not(.icon-svg) { background-image: url(../images/table_multiple.png); }
1575
.icon-folder:not(.icon-svg) { background-image: url(../images/folder.png); }
1576
.open .icon-folder:not(.icon-svg) { background-image: url(../images/folder_open.png); }
1577
.icon-package:not(.icon-svg) { background-image: url(../images/package.png); }
1578
.icon-user:not(.icon-svg) { background-image: url(../images/user.png); }
1579
.icon-project:not(.icon-svg), .icon-projects:not(.icon-svg) { background-image: url(../images/projects.png); }
1580
.icon-help:not(.icon-svg) { background-image: url(../images/help.png); }
1581
.icon-attachment:not(.icon-svg)  { background-image: url(../images/attachment.png); }
1582
.icon-history:not(.icon-svg)  { background-image: url(../images/history.png); }
1583
.icon-time-entry:not(.icon-svg), .icon-time:not(.icon-svg)  { background-image: url(../images/time.png); }
1584
.icon-time-add:not(.icon-svg)  { background-image: url(../images/time_add.png); }
1585
.icon-stats:not(.icon-svg)  { background-image: url(../images/stats.png); }
1586
.icon-warning:not(.icon-svg)  { background-image: url(../images/warning.png); }
1587
.icon-warning.icon-svg svg { color: var(--color-warning-icon) }
1588
.icon-error:not(.icon-svg) { background-image: url(../images/exclamation.png); }
1589
.icon-error.icon-svg svg { color: var(--color-error-icon) }
1590
.icon-fav:not(.icon-svg)  { background-image: url(../images/fav.png); }
1591
.icon-fav-off:not(.icon-svg)  { background-image: url(../images/fav_off.png); }
1592
.icon-reload:not(.icon-svg)  { background-image: url(../images/reload.png); }
1593
.icon-lock:not(.icon-svg), .icon-locked:not(.icon-svg)  { background-image: url(../images/locked.png); }
1594
.icon-unlock:not(.icon-svg)  { background-image: url(../images/unlock.png); }
1595
.icon-checked:not(.icon-svg)  { background-image: url(../images/toggle_check.png); }
1596
.icon-checked.icon-svg svg { color: var(--color-success-icon) }
1597
.icon-report:not(.icon-svg)  { background-image: url(../images/report.png); }
1598
.icon-comment:not(.icon-svg), .icon-comments:not(.icon-svg)  { background-image: url(../images/comment.png); }
1599
.icon-summary:not(.icon-svg)  { background-image: url(../images/lightning.png); }
1600
.icon-server-authentication:not(.icon-svg) { background-image: url(../images/server_key.png); }
1601
.icon-issue:not(.icon-svg) { background-image: url(../images/ticket.png); }
1602
.icon-zoom-in:not(.icon-svg) { background-image: url(../images/zoom_in.png); }
1603
.icon-zoom-out:not(.icon-svg) { background-image: url(../images/zoom_out.png); }
1604
.icon-magnifier:not(.icon-svg) { background-image: url(../images/magnifier.png); }
1605
.icon-passwd:not(.icon-svg) { background-image: url(../images/textfield_key.png); }
1606
.icon-arrow-right:not(.icon-svg), .icon-test:not(.icon-svg), .icon-sticky:not(.icon-svg) { background-image: url(../images/bullet_go.png); }
1607
.icon-email:not(.icon-svg) { background-image: url(../images/email.png); }
1608
.icon-email-disabled:not(.icon-svg) { background-image: url(../images/email_disabled.png); }
1609
.icon-email-add:not(.icon-svg) { background-image: url(../images/email_add.png); }
1610
.icon-ok:not(.icon-svg) { background-image: url(../images/true.png); }
1611
.icon-ok.icon-svg svg { color: var(--color-success-icon) }
1612
.icon-not-ok:not(.icon-svg) { background-image: url(../images/false.png); }
1613
.icon-not-ok.icon-svg svg { color: var(--color-error-icon) }
1614
.icon-link-break:not(.icon-svg) { background-image: url(../images/link_break.png); }
1615
.icon-list:not(.icon-svg) { background-image: url(../images/text_list_bullets.png); }
1616
.icon-close:not(.icon-svg) { background-image: url(../images/close.png); }
1617
.icon-close:hover:not(.icon-svg) { background-image: url(../images/close_hl.png); }
1618
.icon-settings:not(.icon-svg) { background-image: url(../images/changeset.png); }
1619
.icon-group:not(.icon-svg), .icon-groupnonmember:not(.icon-svg), .icon-groupanonymous:not(.icon-svg) { background-image: url(../images/group.png); }
1620
.icon-roles:not(.icon-svg) { background-image: url(../images/database_key.png); }
1621
.icon-issue-edit:not(.icon-svg) { background-image: url(../images/ticket_edit.png); }
1622
.icon-workflows:not(.icon-svg) { background-image: url(../images/ticket_go.png); }
1623
.icon-custom-fields:not(.icon-svg) { background-image: url(../images/textfield.png); }
1624
.icon-plugins:not(.icon-svg) { background-image: url(../images/plugin.png); }
1625
.icon-news:not(.icon-svg) { background-image: url(../images/news.png); }
1626
.icon-issue-closed:not(.icon-svg) { background-image: url(../images/ticket_checked.png); }
1627
.icon-issue-note:not(.icon-svg) { background-image: url(../images/ticket_note.png); }
1628
.icon-changeset:not(.icon-svg) { background-image: url(../images/changeset.png); }
1629
.icon-message:not(.icon-svg) { background-image: url(../images/message.png); }
1630
.icon-reply:not(.icon-svg) { background-image: url(../images/comments.png); }
1631
.icon-wiki-page:not(.icon-svg) { background-image: url(../images/wiki_edit.png); }
1632
.icon-document:not(.icon-svg) { background-image: url(../images/document.png); }
1633
.icon-project:not(.icon-svg) { background-image: url(../images/projects.png); }
1634
.icon-add-bullet:not(.icon-svg) { background-image: url(../images/bullet_add.png); }
1635
.icon-shared:not(.icon-svg) { background-image: url(../images/link.png); }
1636
.icon-actions:not(.icon-svg) { background-image: url(../images/3_bullets.png); }
1637
.icon-sort-handle:not(.icon-svg) { background-image: url(../images/reorder.png); }
1638
.icon-expanded:not(.icon-svg) { background-image: url(../images/arrow_down.png); }
1639
.icon-collapsed:not(.icon-svg) { background-image: url(../images/arrow_right.png); }
1640
.icon-bookmark:not(.icon-svg) { background-image: url(../images/tag_blue_delete.png); }
1641
.icon-bookmark-off:not(.icon-svg) { background-image: url(../images/tag_blue_add.png); }
1642
.icon-bookmarked-project:not(.icon-svg) { background-image: url(../images/tag_blue.png); }
1643
.icon-sorted-asc:not(.icon-svg) { background-image: url(../images/arrow_down.png); }
1644
.icon-sorted-desc:not(.icon-svg) { background-image: url(../images/arrow_up.png); }
1645
.icon-toggle-plus:not(.icon-svg) { background-image: url(../images/bullet_toggle_plus.png) }
1646
.icon-toggle-minus:not(.icon-svg) { background-image: url(../images/bullet_toggle_minus.png) }
1647
.icon-clear-query:not(.icon-svg) { background-image: url(../images/close_hl.png); }
1648
.icon-import:not(.icon-svg) { background-image: url(../images/database_go.png); }
1626 1649

  
1627 1650
.icon-file { background-image: url(../images/files/default.png); }
1628 1651
.icon-file.text-plain { background-image: url(../images/files/text.png); }
......
1642 1665
.icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
1643 1666
.icon-file.application-zip { background-image: url(../images/files/zip.png); }
1644 1667
.icon-file.application-gzip { background-image: url(../images/files/zip.png); }
1645
.icon-copy-link { background-image: url(../images/copy_link.png); }
1668
.icon-copy-link:not(.icon-svg) { background-image: url(../images/copy_link.png); }
1646 1669

  
1647 1670
.sort-handle.ajax-loading { background-image: url(../images/loading.gif); }
1648 1671
tr.ui-sortable-helper { border:1px solid #e4e4e4; }
......
1883 1906
	display: inline;
1884 1907
	opacity: 1;
1885 1908
}
1909

  
1910
:root {
1911
  --color-link-icon: #169;
1912
  --color-active-icon: #c61a1a;
1913
  --color-success-icon: #55ab4f;
1914
  --color-warning-icon: #ffae42;
1915
  --color-error-icon: #EC0B19;
1916
}
test/functional/issues_controller_test.rb
764 764
  def test_index_should_omit_page_param_in_export_links
765 765
    get(:index, :params => {:page => 2})
766 766
    assert_response :success
767
    assert_select 'a.atom[href="/issues.atom"]'
767
    assert_select 'a.icon-atom[href="/issues.atom"]'
768 768
    assert_select 'a.csv[href="/issues.csv"]'
769 769
    assert_select 'a.pdf[href="/issues.pdf"]'
770 770
    assert_select 'form#csv-export-form[action="/issues.csv"]'
test/unit/lib/redmine/icon_test.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2022  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require File.expand_path('../../../../test_helper', __FILE__)
21

  
22
class IconsHelperTest < Redmine::HelperTest
23
  def setup
24
    Rails.application.config.redmine_enable_svg_icon = true
25
  end
26

  
27
  def teardown
28
    Rails.application.config.redmine_enable_svg_icon = false
29
  end
30

  
31
  def test_common_svg_icon
32
    html1 = Regexp.quote '<a class="icon icon-add icon-svg" href="/test"><svg class="s16" xmlns="http://www.w3.org/2000/svg"><use href="/icons/circle-plus.svg?'
33
    html2 = Regexp.quote '#icon"></use></svg>test</a>'
34
    assert_match /#{html1}\d{10}#{html2}/, link_to('test', '/test', class: "icon icon-add")
35
  end
36

  
37
  def test_class_attrs_as_array
38
    html1 = Regexp.quote '<a class="icon icon-add icon-svg" href="/test"><svg class="s16" xmlns="http://www.w3.org/2000/svg"><use href="/icons/circle-plus.svg?'
39
    html2 = Regexp.quote '#icon"></use></svg>test</a>'
40
    assert_match /#{html1}\d{10}#{html2}/, link_to('test', '/test', class: %w(icon icon-add))
41
  end
42

  
43
  def test_file_svg_icon
44
    html1 = Regexp.quote '<a class="icon icon-file text-x-ruby icon-svg" href="/test.rb"><svg class="s16" xmlns="http://www.w3.org/2000/svg"><use href="/icons/language-ruby.svg?'
45
    html2 = Regexp.quote '#icon"></use></svg>test.rb</a>'
46
    assert_match /#{html1}\d{10}#{html2}/, link_to('test.rb', '/test.rb', class: "icon icon-file text-x-ruby")
47
  end
48

  
49
  def test_unknown_svg_icon
50
    html = '<a class="icon icon-foo" href="/test"><!-- SVG icon-foo not found -->test</a>'
51
    assert_equal html, link_to('test', '/test', class: "icon icon-foo")
52
  end
53
end
(21-21/54)