additional_tags 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql-analysis.yml +6 -6
  3. data/.github/workflows/linters.yml +15 -3
  4. data/.github/workflows/tests.yml +7 -4
  5. data/.gitignore +2 -0
  6. data/.rubocop.yml +5 -0
  7. data/.stylelintrc.json +12 -155
  8. data/README.md +26 -7
  9. data/app/controllers/additional_tags_controller.rb +9 -9
  10. data/app/controllers/issue_tags_controller.rb +3 -1
  11. data/app/helpers/additional_tags_helper.rb +39 -30
  12. data/app/helpers/additional_tags_issues_helper.rb +1 -3
  13. data/app/helpers/additional_tags_wiki_helper.rb +6 -5
  14. data/app/models/additional_tag.rb +98 -0
  15. data/app/views/additional_tags/_tag_list.html.slim +10 -4
  16. data/app/views/additional_tags/merge.html.slim +0 -1
  17. data/app/views/additional_tags/settings/_general.html.slim +7 -1
  18. data/app/views/additional_tags/settings/_manage_tags.html.slim +8 -1
  19. data/app/views/context_menus/_issues_tags.html.slim +7 -1
  20. data/app/views/dashboards/blocks/_issue_tags.html.slim +3 -1
  21. data/app/views/issue_tags/_edit_modal.html.slim +7 -7
  22. data/app/views/issues/_tags.html.slim +6 -1
  23. data/app/views/issues/_tags_bulk_edit.html.slim +2 -1
  24. data/app/views/wiki/_tags_form.html.slim +4 -0
  25. data/app/views/wiki/_tags_show.html.slim +1 -1
  26. data/assets/javascripts/tags.js +3 -3
  27. data/assets/stylesheets/tags.css +41 -16
  28. data/config/locales/bg.yml +18 -11
  29. data/config/locales/cs.yml +19 -12
  30. data/config/locales/de.yml +19 -12
  31. data/config/locales/en.yml +19 -12
  32. data/config/locales/es.yml +19 -12
  33. data/config/locales/fr.yml +19 -12
  34. data/config/locales/it.yml +19 -12
  35. data/config/locales/ja.yml +18 -11
  36. data/config/locales/ko.yml +18 -11
  37. data/config/locales/pl.yml +19 -12
  38. data/config/locales/pt-BR.yml +18 -11
  39. data/config/locales/ru.yml +34 -27
  40. data/config/settings.yml +5 -5
  41. data/doc/images/additional-tags-framework.png +0 -0
  42. data/doc/images/additional-tags.gif +0 -0
  43. data/doc/images/tag-overview.png +0 -0
  44. data/lib/additional_tags/hooks/view_hook.rb +15 -2
  45. data/lib/additional_tags/patches/issue_patch.rb +53 -7
  46. data/lib/additional_tags/patches/issue_query_patch.rb +18 -0
  47. data/lib/additional_tags/patches/queries_helper_patch.rb +1 -3
  48. data/lib/additional_tags/patches/query_patch.rb +22 -1
  49. data/lib/additional_tags/patches/wiki_page_patch.rb +6 -1
  50. data/lib/additional_tags/plugin_version.rb +1 -1
  51. data/lib/additional_tags/tags.rb +34 -12
  52. data/lib/additional_tags.rb +5 -0
  53. data/package.json +8 -0
  54. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05656ef90d3c7c796b1bd3d510f6a349fc77c8b1adbf43ca6135af8e48ca705e
4
- data.tar.gz: a22552cd9d096d6963dd9349c207da4755b1e3ba1d8dec560d404b3207ee1458
3
+ metadata.gz: d187e05dfc7655030d62f26afc35392085f43cbac4bb4b4d654da66c643cd58f
4
+ data.tar.gz: 88901a08b7c4b38df8b65fd20282f5e4aae393866e5accf7c3a096d7c82f4788
5
5
  SHA512:
6
- metadata.gz: 2eb18bdccf79be5d1f1b4327c11c3c838e434c8737edcfa096d62d61925170e53be4a6ae45fb486e0c5b3a008933dc30e3bd2c36de9db929fe1fb1e71225adf2
7
- data.tar.gz: 97fd974a51bcdf337c0246cd95c0313639d43aa73f19ac9a81f0f9efa87da0e3c02736970b5c8148566ded5bb3e670a2fb332a03f99d85f59caed219d12410be
6
+ metadata.gz: 34e99763fad968c1f2498dc73b662fb756c4b436e0a4521acd731674c3bebdf8bee52cc55e995457908d39816b61a230fd29d4ac1f3a1662a3e12bb886e6d60e
7
+ data.tar.gz: 4aeb8f7bcbaad55d596dd6f816cedbde29f1c707748ff9ce275f7f131959df258c10e2683ce279cf4c26f8a459d53992ae6e4202d9ff9963a637d88b85661c98
@@ -13,10 +13,10 @@ name: "CodeQL"
13
13
 
14
14
  on:
15
15
  push:
16
- branches: [ master ]
16
+ branches: [ main ]
17
17
  pull_request:
18
18
  # The branches below must be a subset of the branches above
19
- branches: [ master ]
19
+ branches: [ main ]
20
20
  schedule:
21
21
  - cron: '35 20 * * 2'
22
22
 
@@ -38,11 +38,11 @@ jobs:
38
38
 
39
39
  steps:
40
40
  - name: Checkout repository
41
- uses: actions/checkout@v2
41
+ uses: actions/checkout@v3
42
42
 
43
43
  # Initializes the CodeQL tools for scanning.
44
44
  - name: Initialize CodeQL
45
- uses: github/codeql-action/init@v1
45
+ uses: github/codeql-action/init@v2
46
46
  with:
47
47
  languages: ${{ matrix.language }}
48
48
  # If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +53,7 @@ jobs:
53
53
  # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
54
  # If this step fails, then you should remove it and run the build manually (see below)
55
55
  - name: Autobuild
56
- uses: github/codeql-action/autobuild@v1
56
+ uses: github/codeql-action/autobuild@v2
57
57
 
58
58
  # ℹ️ Command-line programs to run using the OS shell.
59
59
  # 📚 https://git.io/JvXDl
@@ -67,4 +67,4 @@ jobs:
67
67
  # make release
68
68
 
69
69
  - name: Perform CodeQL Analysis
70
- uses: github/codeql-action/analyze@v1
70
+ uses: github/codeql-action/analyze@v2
@@ -8,7 +8,7 @@ jobs:
8
8
  runs-on: ubuntu-latest
9
9
 
10
10
  steps:
11
- - uses: actions/checkout@v2
11
+ - uses: actions/checkout@v3
12
12
 
13
13
  - name: Install package dependencies
14
14
  run: >
@@ -23,9 +23,8 @@ jobs:
23
23
  uses: ruby/setup-ruby@v1
24
24
  with:
25
25
  ruby-version: 3.1
26
- bundler-cache: true
27
26
 
28
- - name: Setup gems
27
+ - name: Run bundle install
29
28
  run: |
30
29
  bundle install --jobs 4 --retry 3
31
30
 
@@ -41,3 +40,16 @@ jobs:
41
40
  - name: Run Brakeman
42
41
  run: |
43
42
  bundle exec brakeman -5
43
+
44
+ - name: Setup node
45
+ uses: actions/setup-node@v2
46
+ with:
47
+ node-version: '16'
48
+
49
+ - run: yarn install
50
+
51
+ - name: Run Stylelint
52
+ run: node_modules/.bin/stylelint assets/stylesheets/
53
+
54
+ - name: Run ESLint
55
+ run: node_modules/.bin/eslint assets/javascripts/
@@ -10,9 +10,12 @@ jobs:
10
10
 
11
11
  strategy:
12
12
  matrix:
13
- ruby: ['2.7', '3.0', '3.1']
13
+ ruby: ['2.7', '3.0', '3.1', '3.2']
14
14
  redmine: ['5.0-stable', 'master']
15
15
  db: ['postgres', 'mysql']
16
+ exclude:
17
+ - ruby: '3.2'
18
+ redmine: 5.0-stable
16
19
  fail-fast: false
17
20
 
18
21
  services:
@@ -51,20 +54,20 @@ jobs:
51
54
  if: matrix.db == 'mysql'
52
55
 
53
56
  - name: Checkout Redmine
54
- uses: actions/checkout@v2
57
+ uses: actions/checkout@v3
55
58
  with:
56
59
  repository: redmine/redmine
57
60
  ref: ${{ matrix.redmine }}
58
61
  path: redmine
59
62
 
60
63
  - name: Checkout additionals
61
- uses: actions/checkout@v2
64
+ uses: actions/checkout@v3
62
65
  with:
63
66
  repository: AlphaNodes/additionals
64
67
  path: redmine/plugins/additionals
65
68
 
66
69
  - name: Checkout additional_tags
67
- uses: actions/checkout@v2
70
+ uses: actions/checkout@v3
68
71
  with:
69
72
  path: redmine/plugins/additional_tags
70
73
 
data/.gitignore CHANGED
@@ -8,4 +8,6 @@ Gemfile.lock
8
8
  .enable_*
9
9
  ._*
10
10
  *.gem
11
+ /node_modules
12
+ /yarn.lock
11
13
  rails_best_practices_output.html
data/.rubocop.yml CHANGED
@@ -131,3 +131,8 @@ Layout/LineContinuationLeadingSpace:
131
131
  # redmine does not use load_defaults: https://rails.rubystyle.guide/#config-defaults
132
132
  Rails/RedundantPresenceValidationOnBelongsTo:
133
133
  Enabled: false
134
+
135
+ # see https://github.com/rubocop/rubocop-rails/issues/825
136
+ # should be removed and corrected in code, if this bug is fixed
137
+ Rails/ActionControllerFlashBeforeRender:
138
+ Enabled: false
data/.stylelintrc.json CHANGED
@@ -1,163 +1,20 @@
1
1
  {
2
+ "extends": "stylelint-config-standard",
2
3
  "rules": {
3
- "at-rule-no-unknown": true,
4
- "block-no-empty": true,
5
- "color-no-invalid-hex": true,
6
- "comment-no-empty": true,
7
- "declaration-block-no-duplicate-properties": [
4
+ "string-quotes": "single",
5
+ "selector-class-pattern": "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$|block_column",
6
+ "font-family-no-missing-generic-family-keyword": [
8
7
  true,
9
8
  {
10
- "ignore": [
11
- "consecutive-duplicates-with-different-values"
9
+ "ignoreFontFamilies": [
10
+ "Font Awesome 5 Free",
11
+ "Font Awesome 5 Brands",
12
+ "Apple Color Emoji",
13
+ "Segoe UI Emoji",
14
+ "Segoe UI Symbol",
15
+ "Noto Color Emoji"
12
16
  ]
13
17
  }
14
- ],
15
- "declaration-block-no-redundant-longhand-properties": true,
16
- "declaration-block-no-shorthand-property-overrides": true,
17
- "font-family-no-duplicate-names": true,
18
- "font-family-no-missing-generic-family-keyword": true,
19
- "function-calc-no-unspaced-operator": true,
20
- "function-linear-gradient-no-nonstandard-direction": true,
21
- "keyframe-declaration-no-important": true,
22
- "media-feature-name-no-unknown": true,
23
- "no-descending-specificity": true,
24
- "no-duplicate-at-import-rules": true,
25
- "no-duplicate-selectors": true,
26
- "no-empty-source": true,
27
- "no-extra-semicolons": true,
28
- "no-invalid-double-slash-comments": true,
29
- "property-no-unknown": true,
30
- "selector-pseudo-class-no-unknown": true,
31
- "selector-pseudo-element-no-unknown": true,
32
- "selector-type-no-unknown": true,
33
- "string-no-newline": true,
34
- "unit-no-unknown": true,
35
- "at-rule-empty-line-before": [
36
- "always",
37
- {
38
- "except": [
39
- "blockless-after-same-name-blockless",
40
- "first-nested"
41
- ],
42
- "ignore": [
43
- "after-comment"
44
- ]
45
- }
46
- ],
47
- "at-rule-name-case": "lower",
48
- "at-rule-name-space-after": "always-single-line",
49
- "at-rule-semicolon-newline-after": "always",
50
- "block-closing-brace-empty-line-before": "never",
51
- "block-closing-brace-newline-after": "always",
52
- "block-closing-brace-newline-before": "always-multi-line",
53
- "block-closing-brace-space-before": "always-single-line",
54
- "block-opening-brace-newline-after": "always-multi-line",
55
- "block-opening-brace-space-after": "always-single-line",
56
- "block-opening-brace-space-before": "always",
57
- "color-hex-case": "lower",
58
- "color-hex-length": "short",
59
- "comment-empty-line-before": [
60
- "always",
61
- {
62
- "except": [
63
- "first-nested"
64
- ],
65
- "ignore": [
66
- "stylelint-commands"
67
- ]
68
- }
69
- ],
70
- "comment-whitespace-inside": "always",
71
- "custom-property-empty-line-before": [
72
- "always",
73
- {
74
- "except": [
75
- "after-custom-property",
76
- "first-nested"
77
- ],
78
- "ignore": [
79
- "after-comment",
80
- "inside-single-line-block"
81
- ]
82
- }
83
- ],
84
- "declaration-bang-space-after": "never",
85
- "declaration-bang-space-before": "always",
86
- "declaration-block-semicolon-newline-after": "always-multi-line",
87
- "declaration-block-semicolon-space-after": "always-single-line",
88
- "declaration-block-semicolon-space-before": "never",
89
- "declaration-block-single-line-max-declarations": 1,
90
- "declaration-block-trailing-semicolon": "always",
91
- "declaration-colon-newline-after": "always-multi-line",
92
- "declaration-colon-space-after": "always-single-line",
93
- "declaration-colon-space-before": "never",
94
- "declaration-empty-line-before": [
95
- "always",
96
- {
97
- "except": [
98
- "after-declaration",
99
- "first-nested"
100
- ],
101
- "ignore": [
102
- "after-comment",
103
- "inside-single-line-block"
104
- ]
105
- }
106
- ],
107
- "function-comma-newline-after": "always-multi-line",
108
- "function-comma-space-after": "always-single-line",
109
- "function-comma-space-before": "never",
110
- "function-max-empty-lines": 0,
111
- "function-name-case": "lower",
112
- "function-parentheses-newline-inside": "always-multi-line",
113
- "function-parentheses-space-inside": "never-single-line",
114
- "function-whitespace-after": "always",
115
- "indentation": 2,
116
- "length-zero-no-unit": true,
117
- "max-empty-lines": 1,
118
- "media-feature-colon-space-after": "always",
119
- "media-feature-colon-space-before": "never",
120
- "media-feature-name-case": "lower",
121
- "media-feature-parentheses-space-inside": "never",
122
- "media-feature-range-operator-space-after": "always",
123
- "media-feature-range-operator-space-before": "always",
124
- "media-query-list-comma-newline-after": "always-multi-line",
125
- "media-query-list-comma-space-after": "always-single-line",
126
- "media-query-list-comma-space-before": "never",
127
- "no-eol-whitespace": true,
128
- "no-missing-end-of-source-newline": true,
129
- "number-leading-zero": "always",
130
- "number-no-trailing-zeros": true,
131
- "property-case": "lower",
132
- "rule-empty-line-before": [
133
- "always-multi-line",
134
- {
135
- "except": [
136
- "first-nested"
137
- ],
138
- "ignore": [
139
- "after-comment"
140
- ]
141
- }
142
- ],
143
- "selector-attribute-brackets-space-inside": "never",
144
- "selector-attribute-operator-space-after": "never",
145
- "selector-attribute-operator-space-before": "never",
146
- "selector-combinator-space-after": "always",
147
- "selector-combinator-space-before": "always",
148
- "selector-descendant-combinator-no-non-space": true,
149
- "selector-list-comma-newline-after": "always",
150
- "selector-list-comma-space-before": "never",
151
- "selector-max-empty-lines": 0,
152
- "selector-pseudo-class-case": "lower",
153
- "selector-pseudo-class-parentheses-space-inside": "never",
154
- "selector-pseudo-element-case": "lower",
155
- "selector-pseudo-element-colon-notation": "double",
156
- "selector-type-case": "lower",
157
- "unit-case": "lower",
158
- "value-list-comma-newline-after": "always-multi-line",
159
- "value-list-comma-space-after": "always-single-line",
160
- "value-list-comma-space-before": "never",
161
- "value-list-max-empty-lines": 0
18
+ ]
162
19
  }
163
20
  }
data/README.md CHANGED
@@ -4,16 +4,35 @@
4
4
 
5
5
  ## Features
6
6
 
7
- - Tags for issues
8
- - Tags for wiki pages
7
+ - Tags for issues. To use them you need to:
8
+ - *Activate issue tags* in the plugin configuration
9
+ - and update your role permissions in the Redmine administration *Roles & permissions / Issue tracking*.
10
+ - Tags for wiki pages. To use them you need to:
11
+ - *Activate wiki tags* in the plugin configuration
12
+ - and update your role permissions in the Redmine administration *Roles & permissions / Wiki*
13
+ - Available role permissions for issue tags (section *Issue tracking*):
14
+ - Add issue tags
15
+ - Edit issue tags
16
+ - Display issue tags
17
+ - Available role permissions wiki tags (section *Wiki*):
18
+ - Add wiki tags
19
+ - Managing tags centrally in the plugin settings (edit, delete, merge)-
20
+ - Grouped tags.
21
+ - Grouping of tags possible, when using a colon in tag (all tags with same base name get the same color). Typo example: ``Plugin:HRM``
22
+ - Scoped tags:
23
+ - Grouping of tags via *Scoped tags* possible, when using two colons in tag. Typo example: ``Product::Sprint 1``
24
+ - Only one tag of the same base name is allowed for an entity
25
+ - Base name and tag value are displayed seperatly
9
26
  - Accented and non-latin characters supported for tag order
10
- - View, edit and create tag permissions for issues
11
- - Create permission for wiki tags
12
- - Managing tags
13
- - Custom tags and tagging tables (additional_tags and additional_taggings). If a other plugin
14
- used tags or tagging tables for issue or wiki tagging, there tags will be migrated automatically
27
+ - Color theme selection possible
28
+ - Custom tags and tagging tables (additional_tags and additional_taggings). If another plugin
29
+ used tags or tagging tables for issue or wiki tagging, tags will be migrated automatically there
15
30
  - Based on the very popular [acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on)
16
31
 
32
+ ![screenshot](https://raw.githubusercontent.com/AlphaNodes/additional_tags/master/doc/images/tag-overview.png)
33
+
34
+ The screenshot shows: regular tags, grouped tags and scoped tags. The colors are assigned randomly. But you can change the color by choosing a *Color theme* in the plugin settings.
35
+
17
36
  ![screenshot](https://raw.githubusercontent.com/AlphaNodes/additional_tags/master/doc/images/additional-tags.gif)
18
37
 
19
38
  Other plugins use additional_tags as framework in order to support tags for their entities.
@@ -33,15 +33,6 @@ class AdditionalTagsController < ApplicationController
33
33
 
34
34
  def edit; end
35
35
 
36
- def destroy
37
- @tags.each do |tag|
38
- tag.reload.destroy!
39
- rescue ::ActiveRecord::RecordNotFound, ::ActiveRecord::RecordNotDestroyed
40
- Rails.logger.warn "Tag #{tag} could not be deleted"
41
- end
42
- redirect_back_or_default @tag_list_path
43
- end
44
-
45
36
  def update
46
37
  @tag.name = params[:tag][:name] if params[:tag]
47
38
  if @tag.save
@@ -59,6 +50,15 @@ class AdditionalTagsController < ApplicationController
59
50
  end
60
51
  end
61
52
 
53
+ def destroy
54
+ @tags.each do |tag|
55
+ tag.reload.destroy!
56
+ rescue ::ActiveRecord::RecordNotFound, ::ActiveRecord::RecordNotDestroyed
57
+ Rails.logger.warn "Tag #{tag} could not be deleted"
58
+ end
59
+ redirect_back_or_default @tag_list_path
60
+ end
61
+
62
62
  def context_menu
63
63
  @tag = @tags.first if @tags.size == 1
64
64
  @back = back_url
@@ -19,6 +19,7 @@ class IssueTagsController < ApplicationController
19
19
 
20
20
  @issue_tags.sort!
21
21
  @most_used_tags = Issue.available_tags.most_used 10
22
+ @append = params[:append] == 'true'
22
23
  end
23
24
 
24
25
  def update
@@ -33,7 +34,8 @@ class IssueTagsController < ApplicationController
33
34
 
34
35
  Issue.transaction do
35
36
  @issues.each do |issue|
36
- issue.tag_list = tags
37
+ # add tags added in placeholder for a single/multiple issue or overwrite tags for single issue
38
+ params[:append] == 'true' ? issue.tag_list << tags : issue.tag_list = tags
37
39
  issue.save!
38
40
  end
39
41
  end
@@ -66,7 +66,11 @@ module AdditionalTagsHelper
66
66
  def render_tags_list(tags, **options)
67
67
  return if tags.blank?
68
68
 
69
- style = options.delete :style
69
+ options[:show_count] = AdditionalTags.setting? :show_with_count unless options.key? :show_count
70
+ options[:color_theme] = AdditionalTags.setting :tags_color_theme unless options.key? :color_theme
71
+ options[:use_colors] = AdditionalTags.use_colors? unless options.key? :use_colors
72
+
73
+ style = options.key?(:style) ? options.delete(:style) : AdditionalTags.setting(:tags_sidebar).to_sym
70
74
  tags = tags.all.to_a if tags.respond_to? :all
71
75
  tags = sort_tags_for_list tags
72
76
 
@@ -94,22 +98,36 @@ module AdditionalTagsHelper
94
98
  content_tag(list_el, content, class: 'tags-cloud', style: (style == :simple_cloud ? 'text-align: left;' : ''))
95
99
  end
96
100
 
97
- def additional_tag_link(tag_object, link: nil, link_wiki_tag: false, show_count: false, use_colors: nil, name: nil, **options)
98
- tag_name = []
99
- tag_name << if name.nil?
100
- tag_object.name
101
- else
102
- name
103
- end
104
-
101
+ def additional_tag_link(tag_object,
102
+ link: nil,
103
+ link_wiki_tag: false,
104
+ show_count: false,
105
+ use_colors: nil,
106
+ name: nil,
107
+ color_theme: nil,
108
+ **options)
105
109
  options[:project] = @project if options[:project].blank? && @project.present?
106
- use_colors = AdditionalTags.setting? :use_colors if use_colors.nil?
110
+ if !options.key?(:display_type) && @query && @query.display_type != @query.default_display_type
111
+ options[:display_type] = @query.display_type
112
+ end
113
+
114
+ use_colors = AdditionalTags.use_colors? if use_colors.nil?
115
+ color_theme = AdditionalTags.setting :tags_color_theme if color_theme.nil?
116
+
117
+ tag_info = AdditionalTag.new name: name.nil? ? tag_object.name : name,
118
+ disable_grouping: !use_colors,
119
+ color_theme: color_theme
120
+ tag_name = [tag_info.tag_name]
121
+
122
+ tag_style = "background-color: #{tag_info.tag_bg_color}; color: #{tag_info.tag_fg_color}" if use_colors
107
123
 
108
- tag_style = if use_colors
109
- tag_bg_color = additional_tag_color tag_object.name
110
- tag_fg_color = additional_tag_fg_color tag_bg_color
111
- "background-color: #{tag_bg_color}; color: #{tag_fg_color}"
112
- end
124
+ if tag_info.scoped?
125
+ tag_name << if show_count
126
+ tag.span tag_info.group_value, class: 'tag-group-value'
127
+ else
128
+ tag.span tag_info.group_value, class: 'tag-group-value tag-group-nocount'
129
+ end
130
+ end
113
131
 
114
132
  tag_name << tag.span(tag_object.count, class: 'tag-count') if show_count
115
133
 
@@ -139,20 +157,6 @@ module AdditionalTagsHelper
139
157
  tag.span content, **style
140
158
  end
141
159
 
142
- def additional_tag_color(tag_name)
143
- "##{Digest::SHA256.hexdigest(tag_name)[0..5]}"
144
- end
145
-
146
- def additional_tag_fg_color(bg_color)
147
- # calculate contrast text color according to YIQ method
148
- # https://24ways.org/2010/calculating-color-contrast/
149
- # https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
150
- r = bg_color[1..2].hex
151
- g = bg_color[3..4].hex
152
- b = bg_color[5..6].hex
153
- (r * 299 + g * 587 + b * 114) >= 128_000 ? 'black' : 'white'
154
- end
155
-
156
160
  # plain list of tags
157
161
  def additional_plain_tag_list(tags, sep: nil)
158
162
  sep ||= "#{Query.additional_csv_separator} "
@@ -177,6 +181,10 @@ module AdditionalTagsHelper
177
181
  unsorted = options.delete :unsorted
178
182
  tag_list = AdditionalTags::Tags.sort_tag_list tag_list unless unsorted
179
183
 
184
+ # set defaults if not defined
185
+ options[:use_colors] = AdditionalTags.use_colors? unless options.key? :use_colors
186
+ options[:color_theme] = AdditionalTags.setting :tags_color_theme unless options.key? :color_theme
187
+
180
188
  safe_join tag_list.map { |tag| additional_tag_link tag, **options },
181
189
  additional_tag_sep(use_colors: options[:use_colors])
182
190
  end
@@ -208,7 +216,7 @@ module AdditionalTagsHelper
208
216
 
209
217
  private
210
218
 
211
- def tag_url(tag_name, filter: nil, tag_action: nil, tag_controller: nil, project: nil)
219
+ def tag_url(tag_name, filter: nil, tag_action: nil, tag_controller: nil, project: nil, display_type: nil)
212
220
  action = tag_action.presence || (controller_name == 'hrm_user_resources' ? 'show' : 'index')
213
221
 
214
222
  fields = [:tags]
@@ -225,6 +233,7 @@ module AdditionalTagsHelper
225
233
  { controller: tag_controller.presence || controller_name,
226
234
  action: action,
227
235
  set_filter: 1,
236
+ display_type: display_type,
228
237
  project_id: project,
229
238
  f: fields,
230
239
  v: values,
@@ -44,9 +44,7 @@ module AdditionalTagsIssuesHelper
44
44
  end
45
45
 
46
46
  def render_sidebar_tags
47
- options = { show_count: AdditionalTags.setting?(:show_with_count),
48
- filter: AdditionalTags.setting?(:open_issues_only) ? { field: :status_id, operator: 'o' } : nil,
49
- style: AdditionalTags.setting(:tags_sidebar).to_sym,
47
+ options = { filter: AdditionalTags.setting?(:open_issues_only) ? { field: :status_id, operator: 'o' } : nil,
50
48
  project: @project }
51
49
 
52
50
  options[:tag_action] = 'show' if %w[gantts calendars].include? controller_name
@@ -10,9 +10,7 @@ module AdditionalTagsWikiHelper
10
10
  end
11
11
 
12
12
  def render_sidebar_tags
13
- options = { show_count: AdditionalTags.setting?(:show_with_count),
14
- style: AdditionalTags.setting(:tags_sidebar).to_sym,
15
- link_wiki_tag: true,
13
+ options = { link_wiki_tag: true,
16
14
  project: @project }
17
15
 
18
16
  render_tags_list sidebar_tags, **options
@@ -20,12 +18,15 @@ module AdditionalTagsWikiHelper
20
18
 
21
19
  def render_wiki_index_title(project: nil, name: nil, tag: nil, title: :label_wiki)
22
20
  if tag.present?
21
+ tag_object = ActsAsTaggableOn::Tag.new name: tag
22
+
23
23
  if project
24
- t :label_wiki_index_for_tag_html, tag: tag
24
+ safe_join [l(:label_wiki_index_for_tag), additional_tag_link(tag_object, link: '#')], ' '
25
25
  else
26
26
  title = [link_to(l(title), wiki_index_path)]
27
27
  title << Additionals::LIST_SEPARATOR
28
- title << t(:label_wiki_index_for_tag_html, tag: tag)
28
+ title << l(:label_wiki_index_for_tag)
29
+ title << additional_tag_link(tag_object, link: '#')
29
30
  safe_join title, ' '
30
31
  end
31
32
  elsif name.present?
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AdditionalTag
4
+ GROUP_SEP = ':'
5
+ SCOPE_SEP = '::'
6
+
7
+ class << self
8
+ def valid_mutually_exclusive_tag(tag_list)
9
+ return true if tag_list.blank?
10
+
11
+ tags = tag_list.select { |t| t.include? SCOPE_SEP }
12
+ return true if tags.blank?
13
+
14
+ groups = tags.map { |t| new(name: t).group_name }
15
+ groups == groups.uniq
16
+ end
17
+ end
18
+
19
+ # NOTE: only use bg_color parameter, if background color should not
20
+ # calculated by AdditionalTag - if you want to assign manual color
21
+ def initialize(name:, disable_grouping: false, color_theme: nil, bg_color: nil)
22
+ @tag_name = name.to_s
23
+ @disable_grouping = disable_grouping
24
+ @color_theme = color_theme.to_s
25
+ @bg_color = bg_color
26
+ end
27
+
28
+ def name_for_color
29
+ # different colors for non-grouped, grouped and scoped tag
30
+ name = if scoped? || grouped?
31
+ "#{group_name}#{sep}"
32
+ else
33
+ tag_name
34
+ end
35
+
36
+ if @color_theme.present? && @color_theme != '0' && @color_theme != '1'
37
+ "#{name}#{@color_theme}"
38
+ else
39
+ name
40
+ end
41
+ end
42
+
43
+ def tag_bg_color
44
+ @tag_bg_color ||= @bg_color || "##{Digest::SHA256.hexdigest(name_for_color)[0..5]}"
45
+ end
46
+
47
+ # calculate contrast text color according to YIQ method
48
+ # https://24ways.org/2010/calculating-color-contrast/
49
+ # https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
50
+ def tag_fg_color
51
+ @tag_fg_color ||= begin
52
+ r = tag_bg_color[1..2].hex
53
+ g = tag_bg_color[3..4].hex
54
+ b = tag_bg_color[5..6].hex
55
+ (r * 299 + g * 587 + b * 114) >= 128_000 ? 'black' : 'white'
56
+ end
57
+ end
58
+
59
+ def sep
60
+ scoped? ? SCOPE_SEP : GROUP_SEP
61
+ end
62
+
63
+ def tag_name
64
+ scoped? ? group_name : @tag_name
65
+ end
66
+
67
+ def labels
68
+ @labels ||= scoped? ? scope_labels : group_labels
69
+ end
70
+
71
+ def scope_labels
72
+ @scope_labels ||= @tag_name.split(SCOPE_SEP).map(&:strip)
73
+ end
74
+
75
+ def group_labels
76
+ @group_labels ||= @tag_name.split(GROUP_SEP).map(&:strip)
77
+ end
78
+
79
+ def group_name
80
+ if labels.length > 2
81
+ labels[0...-1].join sep
82
+ else
83
+ labels.first
84
+ end
85
+ end
86
+
87
+ def group_value
88
+ labels.last
89
+ end
90
+
91
+ def scoped?
92
+ !@disable_grouping && scope_labels.length > 1
93
+ end
94
+
95
+ def grouped?
96
+ !@disable_grouping && group_labels.length > 1
97
+ end
98
+ end