From ab1f97b038a32e696cf6ae1c4315ab76a5e68248 Mon Sep 17 00:00:00 2001 From: tohosaku Date: Sun, 27 Feb 2022 13:40:42 +0000 Subject: [PATCH 1/5] Add tag helper patch --- app/helpers/application_helper.rb | 1 + app/models/icon_set.rb | 47 +++++ config/application.rb | 7 + lib/redmine/icon.rb | 134 ++++++++++++++ lib/redmine/plugin.rb | 10 + lib/redmine/preparation.rb | 1 + lib/redmine/themes.rb | 10 + lib/redmine/views/other_formats_builder.rb | 10 +- public/stylesheets/application.css | 203 ++++++++++++--------- test/functional/issues_controller_test.rb | 2 +- test/unit/lib/redmine/icon_test.rb | 53 ++++++ 11 files changed, 389 insertions(+), 89 deletions(-) create mode 100644 app/models/icon_set.rb create mode 100644 lib/redmine/icon.rb create mode 100644 test/unit/lib/redmine/icon_test.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1dfa26cb2..cf0313eaa 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -27,6 +27,7 @@ module ApplicationHelper include Redmine::SudoMode::Helper include Redmine::Themes::Helper include Redmine::Hook::Helper + include Redmine::Icon::Helper include Redmine::Helpers::URL extend Forwardable diff --git a/app/models/icon_set.rb b/app/models/icon_set.rb new file mode 100644 index 000000000..58dda453a --- /dev/null +++ b/app/models/icon_set.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2022 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IconSet + extend ActionView::Helpers::AssetUrlHelper + extend Redmine::Icon::MapLoader + + def self.fetch(name) + @icons ||= create_map + @icons[name] + end + + def self.create_map + icon_map = load_icon_map + + Redmine::Plugin.all.each { |plugin| icon_map.merge! plugin.load_icon_map } + + current_theme = Redmine::Themes.theme(Setting.ui_theme) + icon_map.merge! current_theme.load_icon_map if current_theme.present? + + icon_map.freeze + end + + def self.icon_config_dir + Rails.application.config.redmine_svg_icon_map + end + + def self.icon_dir + '/icons' + end +end diff --git a/config/application.rb b/config/application.rb index bba468f38..2ee7f1274 100644 --- a/config/application.rb +++ b/config/application.rb @@ -75,6 +75,13 @@ module RedmineApp # for more options (same options as config.cache_store). config.redmine_search_cache_store = :memory_store + # Enable experimental svg icons + config.redmine_enable_svg_icon = false + + config.redmine_svg_icon_source = File.join(__dir__, 'icon_source.yml') + + config.redmine_svg_icon_map = File.join(__dir__, 'icons') + # Configure log level here so that additional environment file # can change it (environments/ENV.rb would take precedence over it) config.log_level = Rails.env.production? ? :info : :debug diff --git a/lib/redmine/icon.rb b/lib/redmine/icon.rb new file mode 100644 index 000000000..8bfafc447 --- /dev/null +++ b/lib/redmine/icon.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2021 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'yaml' + +module Redmine + module Icon + module Helper + def expander + tag.span ' ', class: ['expander', 'icon', 'icon-expanded'], onclick: 'toggleRowGroup(this);' + end + + def collapsible(is_new_record) + css_class = is_new_record ? 'icon-expanded' : 'icon-collapsed' + ['icon', css_class] + end + + def preload_icon_link(name) + if Rails.application.config.redmine_enable_svg_icon + icon_path = "#{Redmine::Utils.relative_url_root}#{icon_set_path(name)}" + preload_link_tag(icon_path, as: 'fetch', id: "#{name}_icon_path", crossorigin: "anonymous", type: "application/json") + else + '' + end + end + end + + module Patch + # link_to 'test', '/test', class: "icon icon-add" + # => + # test + # + # + # link_to 'test.rb', '/test.rb', class: "icon icon-file text-x-ruby" + # => + # test.rb + # + # + # # when svg icon is not found + # + # link_to 'test', '/test', class: "icon icon-foo" + # => test + # + def content_tag_string(name, content, options, escape = true) + return super unless need_process?(name, options) + + processed = content_with_icon(content, options, escape) + super name, processed, options, escape + end + DEFAULT_SVG_CSS_CLASS = 's16' + FILE_TYPE_REGEXP = /(text-|image-|application-)/.freeze + XMLNS = 'http://www.w3.org/2000/svg' + + private + + def need_process?(name, options) + Rails.application.config.redmine_enable_svg_icon && + name != :use && name != :svg && options.present? && + (options[:class].present? || options['class'].present?) + end + + def content_with_icon(content, options, escape) + options.symbolize_keys! + classes = split_tokens options[:class] + icon = classes.include?('icon') + icon_only = classes.include?('icon-only') + return content unless icon || icon_only + + svg = create_svg_icon(classes, options) + (icon_only ? svg : "#{svg}#{content}").html_safe + end + + # copy from ActionView::Helpers::TagHelper#token_list + def split_tokens(*args) + ActionView::Helpers::TagHelper.build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq + end + + def create_svg_icon(classes, options) + icon_name = classes.find { |c| c != 'icon-only' && c.start_with?('icon-') } + file_type = classes.find { |c| c.start_with?(FILE_TYPE_REGEXP) } + svg = fetch_svg(icon_name, file_type) + if svg.present? + classes << 'icon-svg' + options[:class] = classes.join(' ') + svg + else + "" + end + end + + def fetch_svg(icon_name, file_type) + if icon_name == 'icon-file' && file_type + svg_tag(file_type, DEFAULT_SVG_CSS_CLASS, IconSet.fetch('file')) + else + svg_tag(icon_name, DEFAULT_SVG_CSS_CLASS, IconSet.fetch('common')) + end + end + + def svg_tag(name, css_class, icon_map) + return '' unless icon_map.key?(name) + + content = content_tag_string(:use, nil, { :href => "#{icon_map[name]}#icon" }) + content_tag_string(:svg, content, { :class => css_class, :xmlns => XMLNS }) + end + end + + module MapLoader + def load_icon_map + pattern = File.join icon_config_dir, '*.{yaml,yml}' + Dir.glob(pattern).each_with_object({}) do |f, o| + name = File.basename f, '.*' + value = YAML.safe_load(File.open(Rails.root.join(f))) + o[name] = value.transform_values { |e| path_to_asset File.join(icon_dir, e) } + end + end + end + end +end diff --git a/lib/redmine/plugin.rb b/lib/redmine/plugin.rb index cdc6defb3..632980f9d 100644 --- a/lib/redmine/plugin.rb +++ b/lib/redmine/plugin.rb @@ -50,6 +50,8 @@ module Redmine # # See: http://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial class Plugin + include Redmine::Icon::MapLoader + # Absolute path to the directory where plugins are located cattr_accessor :directory self.directory = PluginLoader.directory @@ -525,5 +527,13 @@ module Redmine super(version.to_s + "-" + current_plugin.id.to_s) end end + + def icon_config_dir + File.join directory, 'config', 'icons' + end + + def icon_dir + File.join public_directory.sub(Rails.public_path.to_s, ''), 'icons' + end end end diff --git a/lib/redmine/preparation.rb b/lib/redmine/preparation.rb index 2ba3b5447..75f49696e 100644 --- a/lib/redmine/preparation.rb +++ b/lib/redmine/preparation.rb @@ -23,6 +23,7 @@ module Redmine ActiveRecord::Base.include Redmine::Acts::Positioned ActiveRecord::Base.include Redmine::Acts::Mentionable ActiveRecord::Base.include Redmine::I18n + ActionView::Helpers::TagHelper::TagBuilder.prepend Redmine::Icon::Patch Scm::Base.add "Subversion" Scm::Base.add "Mercurial" diff --git a/lib/redmine/themes.rb b/lib/redmine/themes.rb index 477df057b..b41425d4d 100644 --- a/lib/redmine/themes.rb +++ b/lib/redmine/themes.rb @@ -43,6 +43,8 @@ module Redmine # Class used to represent a theme class Theme + include Redmine::Icon::MapLoader + attr_reader :path, :name, :dir def initialize(path) @@ -104,6 +106,14 @@ module Redmine "/themes/#{dir}/favicon/#{favicon}" end + def icon_config_dir + "/themes/#{dir}" + end + + def icon_dir + Rails.public_path.join("#{icon_config_dir}/icons") + end + private def assets(dir, ext=nil) diff --git a/lib/redmine/views/other_formats_builder.rb b/lib/redmine/views/other_formats_builder.rb index 165030418..29992b8b4 100644 --- a/lib/redmine/views/other_formats_builder.rb +++ b/lib/redmine/views/other_formats_builder.rb @@ -27,7 +27,7 @@ module Redmine def link_to(name, options={}) url = {:format => name.to_s.downcase}.merge(options.delete(:url) || {}).except('page') caption = options.delete(:caption) || name - html_options = {:class => name.to_s.downcase, :rel => 'nofollow'}.merge(options) + html_options = {:class => icon(name), :rel => 'nofollow'}.merge(options) @view.content_tag('span', @view.link_to(caption, url, html_options)) end @@ -37,9 +37,15 @@ module Redmine url = {:params => params, :page => nil, :format => name.to_s.downcase}.merge(url) caption = options.delete(:caption) || name - html_options = {:class => name.to_s.downcase, :rel => 'nofollow'}.merge(options) + html_options = {:class => icon(name), :rel => 'nofollow'}.merge(options) @view.content_tag('span', @view.link_to(caption, url, html_options)) end + + def icon(name) + icon_map = IconSet.fetch('common') + format_name = name.to_s.downcase + icon_map["icon-#{format_name}"] ? "icon icon-#{format_name}" : format_name + end end end end diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 6a043ff2f..b910938e3 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -160,7 +160,8 @@ a.user.user-mention { #sidebar a.selected:hover {text-decoration:none;} #sidebar .query.default {font-weight: bold;} #admin-menu a {line-height:1.7em;} -#admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;} +#admin-menu a.selected {background-position: 2px 40%;} +#admin-menu a.selected:not(.icon-svg) {padding-left: 20px !important;} a.collapsible {padding-left: 12px; } @@ -571,7 +572,8 @@ body.controller-issues h2.inline-flex {padding-right: 0} #issue-form .assign-to-me-link { padding-left: 5px; } fieldset.collapsible {border-width: 1px 0 0 0;} -fieldset.collapsible>legend { cursor:pointer; padding-left: 18px; background-position: 4px;} +fieldset.collapsible>legend { cursor:pointer;} +fieldset.collapsible>legend:not(.icon-svg) { padding-left: 18px; background-position: 4px;} fieldset#date-range p { margin: 2px 0 2px 0; } fieldset#filters table { border-collapse: collapse; } @@ -722,7 +724,7 @@ ul.projects div.description li {list-style-type:initial;} padding-top: 0.5em; } #projects-index a.icon-user, a.icon-bookmarked-project {padding-left:0; padding-right:20px; background-position:98% 50%;} -#projects-index a.icon-user.icon-bookmarked-project { +#projects-index a.icon-user.icon-bookmarked-project:not(.icon-svg) { background-image: url(../images/tag_blue.png), url(../images/user.png); background-position: bottom 0px right 0px, bottom 0px right 20px; padding-right: 40px; @@ -963,7 +965,7 @@ div.thumbnails img {margin: 3px; vertical-align: middle;} p.other-formats { text-align: right; font-size:0.9em; color: #666; } .other-formats span + span:before { content: "| "; } -a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } +.icon-atom:not(.icon-svg) { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } em.info {font-style:normal;display:block;font-size:90%;color:#888;} em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;} @@ -1521,13 +1523,29 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { white-space: pre-wrap; } +/***** SVGs ******/ +.s16 { + width: 16px; + height: 16px; + vertical-align: text-bottom; +} + /***** Icons *****/ -.icon { +.icon:not(.icon-svg) { background-position: 0% 50%; background-repeat: no-repeat; padding-left: 20px; } -.icon-only { + +a svg, .icon-svg svg { + color: var(--color-link-icon); + margin-right: 4px; +} +a:hover svg, a:active svg { + color: var(--color-active-icon); +} + +.icon-only:not(.icon-svg) { background-position: 0% 50%; background-repeat: no-repeat; padding-left: 16px; @@ -1544,85 +1562,90 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { content: "\a0"; } -.icon-add { background-image: url(../images/add.png); } -.icon-edit { background-image: url(../images/edit.png); } -.icon-copy { background-image: url(../images/copy.png); } -.icon-duplicate { background-image: url(../images/duplicate.png); } -.icon-del { background-image: url(../images/delete.png); } -.icon-move { background-image: url(../images/move.png); } -.icon-save { background-image: url(../images/save.png); } -.icon-download { background-image: url(../images/download.png); } -.icon-cancel { background-image: url(../images/cancel.png); } -.icon-multiple { background-image: url(../images/table_multiple.png); } -.icon-folder { background-image: url(../images/folder.png); } -.open .icon-folder { background-image: url(../images/folder_open.png); } -.icon-package { background-image: url(../images/package.png); } -.icon-user { background-image: url(../images/user.png); } -.icon-project, .icon-projects { background-image: url(../images/projects.png); } -.icon-help { background-image: url(../images/help.png); } -.icon-attachment { background-image: url(../images/attachment.png); } -.icon-history { background-image: url(../images/history.png); } -.icon-time-entry, .icon-time { background-image: url(../images/time.png); } -.icon-time-add { background-image: url(../images/time_add.png); } -.icon-stats { background-image: url(../images/stats.png); } -.icon-warning { background-image: url(../images/warning.png); } -.icon-error { background-image: url(../images/exclamation.png); } -.icon-fav { background-image: url(../images/fav.png); } -.icon-fav-off { background-image: url(../images/fav_off.png); } -.icon-reload { background-image: url(../images/reload.png); } -.icon-lock, .icon-locked { background-image: url(../images/locked.png); } -.icon-unlock { background-image: url(../images/unlock.png); } -.icon-checked { background-image: url(../images/toggle_check.png); } -.icon-report { background-image: url(../images/report.png); } -.icon-comment, .icon-comments { background-image: url(../images/comment.png); } -.icon-summary { background-image: url(../images/lightning.png); } -.icon-server-authentication { background-image: url(../images/server_key.png); } -.icon-issue { background-image: url(../images/ticket.png); } -.icon-zoom-in { background-image: url(../images/zoom_in.png); } -.icon-zoom-out { background-image: url(../images/zoom_out.png); } -.icon-magnifier { background-image: url(../images/magnifier.png); } -.icon-passwd { background-image: url(../images/textfield_key.png); } -.icon-arrow-right, .icon-test, .icon-sticky { background-image: url(../images/bullet_go.png); } -.icon-email { background-image: url(../images/email.png); } -.icon-email-disabled { background-image: url(../images/email_disabled.png); } -.icon-email-add { background-image: url(../images/email_add.png); } -.icon-ok { background-image: url(../images/true.png); } -.icon-not-ok { background-image: url(../images/false.png); } -.icon-link-break { background-image: url(../images/link_break.png); } -.icon-list { background-image: url(../images/text_list_bullets.png); } -.icon-close { background-image: url(../images/close.png); } -.icon-close:hover { background-image: url(../images/close_hl.png); } -.icon-settings { background-image: url(../images/changeset.png); } -.icon-group, .icon-groupnonmember, .icon-groupanonymous { background-image: url(../images/group.png); } -.icon-roles { background-image: url(../images/database_key.png); } -.icon-issue-edit { background-image: url(../images/ticket_edit.png); } -.icon-workflows { background-image: url(../images/ticket_go.png); } -.icon-custom-fields { background-image: url(../images/textfield.png); } -.icon-plugins { background-image: url(../images/plugin.png); } -.icon-news { background-image: url(../images/news.png); } -.icon-issue-closed { background-image: url(../images/ticket_checked.png); } -.icon-issue-note { background-image: url(../images/ticket_note.png); } -.icon-changeset { background-image: url(../images/changeset.png); } -.icon-message { background-image: url(../images/message.png); } -.icon-reply { background-image: url(../images/comments.png); } -.icon-wiki-page { background-image: url(../images/wiki_edit.png); } -.icon-document { background-image: url(../images/document.png); } -.icon-project { background-image: url(../images/projects.png); } -.icon-add-bullet { background-image: url(../images/bullet_add.png); } -.icon-shared { background-image: url(../images/link.png); } -.icon-actions { background-image: url(../images/3_bullets.png); } -.icon-sort-handle { background-image: url(../images/reorder.png); } -.icon-expanded { background-image: url(../images/arrow_down.png); } -.icon-collapsed { background-image: url(../images/arrow_right.png); } -.icon-bookmark { background-image: url(../images/tag_blue_delete.png); } -.icon-bookmark-off { background-image: url(../images/tag_blue_add.png); } -.icon-bookmarked-project { background-image: url(../images/tag_blue.png); } -.icon-sorted-asc { background-image: url(../images/arrow_down.png); } -.icon-sorted-desc { background-image: url(../images/arrow_up.png); } -.icon-toggle-plus { background-image: url(../images/bullet_toggle_plus.png) } -.icon-toggle-minus { background-image: url(../images/bullet_toggle_minus.png) } -.icon-clear-query { background-image: url(../images/close_hl.png); } -.icon-import { background-image: url(../images/database_go.png); } +.icon-add:not(.icon-svg) { background-image: url(../images/add.png); } +.icon-edit:not(.icon-svg) { background-image: url(../images/edit.png); } +.icon-copy:not(.icon-svg) { background-image: url(../images/copy.png); } +.icon-duplicate:not(.icon-svg) { background-image: url(../images/duplicate.png); } +.icon-del:not(.icon-svg) { background-image: url(../images/delete.png); } +.icon-move:not(.icon-svg) { background-image: url(../images/move.png); } +.icon-save:not(.icon-svg) { background-image: url(../images/save.png); } +.icon-download:not(.icon-svg) { background-image: url(../images/download.png); } +.icon-cancel:not(.icon-svg) { background-image: url(../images/cancel.png); } +.icon-multiple:not(.icon-svg) { background-image: url(../images/table_multiple.png); } +.icon-folder:not(.icon-svg) { background-image: url(../images/folder.png); } +.open .icon-folder:not(.icon-svg) { background-image: url(../images/folder_open.png); } +.icon-package:not(.icon-svg) { background-image: url(../images/package.png); } +.icon-user:not(.icon-svg) { background-image: url(../images/user.png); } +.icon-project:not(.icon-svg), .icon-projects:not(.icon-svg) { background-image: url(../images/projects.png); } +.icon-help:not(.icon-svg) { background-image: url(../images/help.png); } +.icon-attachment:not(.icon-svg) { background-image: url(../images/attachment.png); } +.icon-history:not(.icon-svg) { background-image: url(../images/history.png); } +.icon-time-entry:not(.icon-svg), .icon-time:not(.icon-svg) { background-image: url(../images/time.png); } +.icon-time-add:not(.icon-svg) { background-image: url(../images/time_add.png); } +.icon-stats:not(.icon-svg) { background-image: url(../images/stats.png); } +.icon-warning:not(.icon-svg) { background-image: url(../images/warning.png); } +.icon-warning.icon-svg svg { color: var(--color-warning-icon) } +.icon-error:not(.icon-svg) { background-image: url(../images/exclamation.png); } +.icon-error.icon-svg svg { color: var(--color-error-icon) } +.icon-fav:not(.icon-svg) { background-image: url(../images/fav.png); } +.icon-fav-off:not(.icon-svg) { background-image: url(../images/fav_off.png); } +.icon-reload:not(.icon-svg) { background-image: url(../images/reload.png); } +.icon-lock:not(.icon-svg), .icon-locked:not(.icon-svg) { background-image: url(../images/locked.png); } +.icon-unlock:not(.icon-svg) { background-image: url(../images/unlock.png); } +.icon-checked:not(.icon-svg) { background-image: url(../images/toggle_check.png); } +.icon-checked.icon-svg svg { color: var(--color-success-icon) } +.icon-report:not(.icon-svg) { background-image: url(../images/report.png); } +.icon-comment:not(.icon-svg), .icon-comments:not(.icon-svg) { background-image: url(../images/comment.png); } +.icon-summary:not(.icon-svg) { background-image: url(../images/lightning.png); } +.icon-server-authentication:not(.icon-svg) { background-image: url(../images/server_key.png); } +.icon-issue:not(.icon-svg) { background-image: url(../images/ticket.png); } +.icon-zoom-in:not(.icon-svg) { background-image: url(../images/zoom_in.png); } +.icon-zoom-out:not(.icon-svg) { background-image: url(../images/zoom_out.png); } +.icon-magnifier:not(.icon-svg) { background-image: url(../images/magnifier.png); } +.icon-passwd:not(.icon-svg) { background-image: url(../images/textfield_key.png); } +.icon-arrow-right:not(.icon-svg), .icon-test:not(.icon-svg), .icon-sticky:not(.icon-svg) { background-image: url(../images/bullet_go.png); } +.icon-email:not(.icon-svg) { background-image: url(../images/email.png); } +.icon-email-disabled:not(.icon-svg) { background-image: url(../images/email_disabled.png); } +.icon-email-add:not(.icon-svg) { background-image: url(../images/email_add.png); } +.icon-ok:not(.icon-svg) { background-image: url(../images/true.png); } +.icon-ok.icon-svg svg { color: var(--color-success-icon) } +.icon-not-ok:not(.icon-svg) { background-image: url(../images/false.png); } +.icon-not-ok.icon-svg svg { color: var(--color-error-icon) } +.icon-link-break:not(.icon-svg) { background-image: url(../images/link_break.png); } +.icon-list:not(.icon-svg) { background-image: url(../images/text_list_bullets.png); } +.icon-close:not(.icon-svg) { background-image: url(../images/close.png); } +.icon-close:hover:not(.icon-svg) { background-image: url(../images/close_hl.png); } +.icon-settings:not(.icon-svg) { background-image: url(../images/changeset.png); } +.icon-group:not(.icon-svg), .icon-groupnonmember:not(.icon-svg), .icon-groupanonymous:not(.icon-svg) { background-image: url(../images/group.png); } +.icon-roles:not(.icon-svg) { background-image: url(../images/database_key.png); } +.icon-issue-edit:not(.icon-svg) { background-image: url(../images/ticket_edit.png); } +.icon-workflows:not(.icon-svg) { background-image: url(../images/ticket_go.png); } +.icon-custom-fields:not(.icon-svg) { background-image: url(../images/textfield.png); } +.icon-plugins:not(.icon-svg) { background-image: url(../images/plugin.png); } +.icon-news:not(.icon-svg) { background-image: url(../images/news.png); } +.icon-issue-closed:not(.icon-svg) { background-image: url(../images/ticket_checked.png); } +.icon-issue-note:not(.icon-svg) { background-image: url(../images/ticket_note.png); } +.icon-changeset:not(.icon-svg) { background-image: url(../images/changeset.png); } +.icon-message:not(.icon-svg) { background-image: url(../images/message.png); } +.icon-reply:not(.icon-svg) { background-image: url(../images/comments.png); } +.icon-wiki-page:not(.icon-svg) { background-image: url(../images/wiki_edit.png); } +.icon-document:not(.icon-svg) { background-image: url(../images/document.png); } +.icon-project:not(.icon-svg) { background-image: url(../images/projects.png); } +.icon-add-bullet:not(.icon-svg) { background-image: url(../images/bullet_add.png); } +.icon-shared:not(.icon-svg) { background-image: url(../images/link.png); } +.icon-actions:not(.icon-svg) { background-image: url(../images/3_bullets.png); } +.icon-sort-handle:not(.icon-svg) { background-image: url(../images/reorder.png); } +.icon-expanded:not(.icon-svg) { background-image: url(../images/arrow_down.png); } +.icon-collapsed:not(.icon-svg) { background-image: url(../images/arrow_right.png); } +.icon-bookmark:not(.icon-svg) { background-image: url(../images/tag_blue_delete.png); } +.icon-bookmark-off:not(.icon-svg) { background-image: url(../images/tag_blue_add.png); } +.icon-bookmarked-project:not(.icon-svg) { background-image: url(../images/tag_blue.png); } +.icon-sorted-asc:not(.icon-svg) { background-image: url(../images/arrow_down.png); } +.icon-sorted-desc:not(.icon-svg) { background-image: url(../images/arrow_up.png); } +.icon-toggle-plus:not(.icon-svg) { background-image: url(../images/bullet_toggle_plus.png) } +.icon-toggle-minus:not(.icon-svg) { background-image: url(../images/bullet_toggle_minus.png) } +.icon-clear-query:not(.icon-svg) { background-image: url(../images/close_hl.png); } +.icon-import:not(.icon-svg) { background-image: url(../images/database_go.png); } .icon-file { background-image: url(../images/files/default.png); } .icon-file.text-plain { background-image: url(../images/files/text.png); } @@ -1642,7 +1665,7 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-file.application-pdf { background-image: url(../images/files/pdf.png); } .icon-file.application-zip { background-image: url(../images/files/zip.png); } .icon-file.application-gzip { background-image: url(../images/files/zip.png); } -.icon-copy-link { background-image: url(../images/copy_link.png); } +.icon-copy-link:not(.icon-svg) { background-image: url(../images/copy_link.png); } .sort-handle.ajax-loading { background-image: url(../images/loading.gif); } tr.ui-sortable-helper { border:1px solid #e4e4e4; } @@ -1883,3 +1906,11 @@ th[role=columnheader]:not(.no-sort):hover:after { display: inline; opacity: 1; } + +:root { + --color-link-icon: #169; + --color-active-icon: #c61a1a; + --color-success-icon: #55ab4f; + --color-warning-icon: #ffae42; + --color-error-icon: #EC0B19; +} diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 9b1842e0d..c8872d62e 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -764,7 +764,7 @@ class IssuesControllerTest < Redmine::ControllerTest def test_index_should_omit_page_param_in_export_links get(:index, :params => {:page => 2}) assert_response :success - assert_select 'a.atom[href="/issues.atom"]' + assert_select 'a.icon-atom[href="/issues.atom"]' assert_select 'a.csv[href="/issues.csv"]' assert_select 'a.pdf[href="/issues.pdf"]' assert_select 'form#csv-export-form[action="/issues.csv"]' diff --git a/test/unit/lib/redmine/icon_test.rb b/test/unit/lib/redmine/icon_test.rb new file mode 100644 index 000000000..b1f4ac6fe --- /dev/null +++ b/test/unit/lib/redmine/icon_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2022 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../../../test_helper', __FILE__) + +class IconsHelperTest < Redmine::HelperTest + def setup + Rails.application.config.redmine_enable_svg_icon = true + end + + def teardown + Rails.application.config.redmine_enable_svg_icon = false + end + + def test_common_svg_icon + html1 = Regexp.quote 'test' + assert_match /#{html1}\d{10}#{html2}/, link_to('test', '/test', class: "icon icon-add") + end + + def test_class_attrs_as_array + html1 = Regexp.quote 'test' + assert_match /#{html1}\d{10}#{html2}/, link_to('test', '/test', class: %w(icon icon-add)) + end + + def test_file_svg_icon + html1 = Regexp.quote 'test.rb' + assert_match /#{html1}\d{10}#{html2}/, link_to('test.rb', '/test.rb', class: "icon icon-file text-x-ruby") + end + + def test_unknown_svg_icon + html = 'test' + assert_equal html, link_to('test', '/test', class: "icon icon-foo") + end +end -- 2.30.2