redmineup 1.1.14 → 1.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/icons.svg +9 -0
  3. data/app/assets/javascripts/select2_helpers.js +2 -0
  4. data/app/assets/stylesheets/redmineup.css +28 -10
  5. data/config/locales/ar.yml +13 -0
  6. data/config/locales/az.yml +13 -0
  7. data/config/locales/bg.yml +13 -0
  8. data/config/locales/bs.yml +13 -0
  9. data/config/locales/ca.yml +13 -0
  10. data/config/locales/cs.yml +1 -0
  11. data/config/locales/da.yml +13 -0
  12. data/config/locales/de.yml +1 -0
  13. data/config/locales/el.yml +13 -0
  14. data/config/locales/en-GB.yml +14 -0
  15. data/config/locales/es-PA.yml +14 -0
  16. data/config/locales/es.yml +1 -0
  17. data/config/locales/et.yml +13 -0
  18. data/config/locales/eu.yml +13 -0
  19. data/config/locales/fa.yml +13 -0
  20. data/config/locales/fi.yml +13 -0
  21. data/config/locales/fr.yml +13 -0
  22. data/config/locales/gl.yml +13 -0
  23. data/config/locales/he.yml +13 -0
  24. data/config/locales/hr.yml +13 -0
  25. data/config/locales/hu.yml +13 -0
  26. data/config/locales/id.yml +13 -0
  27. data/config/locales/it.yml +13 -0
  28. data/config/locales/ja.yml +13 -0
  29. data/config/locales/ko.yml +13 -0
  30. data/config/locales/lt.yml +13 -0
  31. data/config/locales/lv.yml +13 -0
  32. data/config/locales/mk.yml +13 -0
  33. data/config/locales/mn.yml +13 -0
  34. data/config/locales/nl.yml +13 -0
  35. data/config/locales/no.yml +13 -0
  36. data/config/locales/pl.yml +13 -0
  37. data/config/locales/pt-BR.yml +13 -0
  38. data/config/locales/pt.yml +13 -0
  39. data/config/locales/ro.yml +13 -0
  40. data/config/locales/ru.yml +1 -1
  41. data/config/locales/sk.yml +13 -0
  42. data/config/locales/sl.yml +13 -0
  43. data/config/locales/sq.yml +13 -0
  44. data/config/locales/sr-YU.yml +13 -0
  45. data/config/locales/sr.yml +13 -0
  46. data/config/locales/sv.yml +13 -0
  47. data/config/locales/ta-IN.yml +13 -0
  48. data/config/locales/th.yml +13 -0
  49. data/config/locales/tr.yml +13 -0
  50. data/config/locales/uk.yml +13 -0
  51. data/config/locales/vi.yml +13 -0
  52. data/config/locales/zh-TW.yml +13 -0
  53. data/config/locales/zh.yml +13 -0
  54. data/doc/CHANGELOG +14 -0
  55. data/doc/tagging-and-select2.md +7 -0
  56. data/lib/action_cable/server/rup_server.rb +5 -1
  57. data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +23 -23
  58. data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +7 -2
  59. data/lib/redmineup/currency/formatting.rb +1 -0
  60. data/lib/redmineup/helpers/tags_helper.rb +67 -0
  61. data/lib/redmineup/version.rb +1 -1
  62. data/redmineup.gemspec +1 -1
  63. data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +12 -0
  64. data/test/currency_test.rb +7 -0
  65. data/test/tags_helper_view_test.rb +61 -0
  66. metadata +47 -2
@@ -0,0 +1,13 @@
1
+ sl:
2
+ label_redmineup_settings: Nastavitve valute
3
+ label_redmineup_money: Valuta
4
+ label_redmineup_disable_taxes: Onemogoči davke
5
+ label_redmineup_default_tax: Privzeta vrednost davka
6
+ label_redmineup_tax_type: Vrsta davka
7
+ label_redmineup_tax_type_exclusive: Davek ni vključen
8
+ label_redmineup_tax_type_inclusive: Davek je vključen
9
+ label_redmineup_default_currency: Privzeta valuta
10
+ label_redmineup_major_currencies: Glavne valute
11
+ label_redmineup_thousands_delimiter: Ločilo tisočic
12
+ label_redmineup_decimal_separator: Decimalno ločilo
13
+ lable_redmineup_add_tag: dodaj oznako
@@ -0,0 +1,13 @@
1
+ sq:
2
+ label_redmineup_settings: Cilësimet e parasë
3
+ label_redmineup_money: Para
4
+ label_redmineup_disable_taxes: Çaktivizo taksat
5
+ label_redmineup_default_tax: Vlera e parazgjedhur e taksës
6
+ label_redmineup_tax_type: Lloji i taksës
7
+ label_redmineup_tax_type_exclusive: Pa taksë të përfshirë
8
+ label_redmineup_tax_type_inclusive: Me taksë të përfshirë
9
+ label_redmineup_default_currency: Monedha e parazgjedhur
10
+ label_redmineup_major_currencies: Monedhat kryesore
11
+ label_redmineup_thousands_delimiter: Ndarësi i mijësheve
12
+ label_redmineup_decimal_separator: Ndarësi dhjetor
13
+ lable_redmineup_add_tag: shto etiketë
@@ -0,0 +1,13 @@
1
+ sr-YU:
2
+ label_redmineup_settings: Podešavanja novca
3
+ label_redmineup_money: Novac
4
+ label_redmineup_disable_taxes: Onemogući poreze
5
+ label_redmineup_default_tax: Podrazumevana vrednost poreza
6
+ label_redmineup_tax_type: Vrsta poreza
7
+ label_redmineup_tax_type_exclusive: Porez nije uključen
8
+ label_redmineup_tax_type_inclusive: Porez je uključen
9
+ label_redmineup_default_currency: Podrazumevana valuta
10
+ label_redmineup_major_currencies: Glavne valute
11
+ label_redmineup_thousands_delimiter: Razdvajač hiljada
12
+ label_redmineup_decimal_separator: Decimalni razdvajač
13
+ lable_redmineup_add_tag: dodaj oznaku
@@ -0,0 +1,13 @@
1
+ sr:
2
+ label_redmineup_settings: Подешавања новца
3
+ label_redmineup_money: Новац
4
+ label_redmineup_disable_taxes: Онемогући порезе
5
+ label_redmineup_default_tax: Подразумевана вредност пореза
6
+ label_redmineup_tax_type: Врста пореза
7
+ label_redmineup_tax_type_exclusive: Порез није укључен
8
+ label_redmineup_tax_type_inclusive: Порез је укључен
9
+ label_redmineup_default_currency: Подразумевана валута
10
+ label_redmineup_major_currencies: Главне валуте
11
+ label_redmineup_thousands_delimiter: Раздвајач хиљада
12
+ label_redmineup_decimal_separator: Децимални раздвајач
13
+ lable_redmineup_add_tag: додај ознаку
@@ -0,0 +1,13 @@
1
+ sv:
2
+ label_redmineup_settings: Penninginställningar
3
+ label_redmineup_money: Pengar
4
+ label_redmineup_disable_taxes: Inaktivera skatter
5
+ label_redmineup_default_tax: Standardskattevärde
6
+ label_redmineup_tax_type: Skatttyp
7
+ label_redmineup_tax_type_exclusive: Exklusive skatt
8
+ label_redmineup_tax_type_inclusive: Inklusive skatt
9
+ label_redmineup_default_currency: Standardvaluta
10
+ label_redmineup_major_currencies: Huvudvalutor
11
+ label_redmineup_thousands_delimiter: Tusentalsavgränsare
12
+ label_redmineup_decimal_separator: Decimalavgränsare
13
+ lable_redmineup_add_tag: lägg till tagg
@@ -0,0 +1,13 @@
1
+ ta-IN:
2
+ label_redmineup_settings: நாணய அமைப்புகள்
3
+ label_redmineup_money: நாணயம்
4
+ label_redmineup_disable_taxes: வரிகளை முடக்கு
5
+ label_redmineup_default_tax: இயல்புநிலை வரி மதிப்பு
6
+ label_redmineup_tax_type: வரி வகை
7
+ label_redmineup_tax_type_exclusive: வரி சேர்க்கப்படாதது
8
+ label_redmineup_tax_type_inclusive: வரி சேர்க்கப்பட்டது
9
+ label_redmineup_default_currency: இயல்புநிலை நாணயம்
10
+ label_redmineup_major_currencies: முக்கிய நாணயங்கள்
11
+ label_redmineup_thousands_delimiter: ஆயிரம் பிரிப்பான்
12
+ label_redmineup_decimal_separator: தசம பிரிப்பான்
13
+ lable_redmineup_add_tag: குறிச்சொல் சேர்
@@ -0,0 +1,13 @@
1
+ th:
2
+ label_redmineup_settings: การตั้งค่าเงิน
3
+ label_redmineup_money: เงิน
4
+ label_redmineup_disable_taxes: ปิดใช้งานภาษี
5
+ label_redmineup_default_tax: ค่าภาษีเริ่มต้น
6
+ label_redmineup_tax_type: ประเภทภาษี
7
+ label_redmineup_tax_type_exclusive: ไม่รวมภาษี
8
+ label_redmineup_tax_type_inclusive: รวมภาษี
9
+ label_redmineup_default_currency: สกุลเงินเริ่มต้น
10
+ label_redmineup_major_currencies: สกุลเงินหลัก
11
+ label_redmineup_thousands_delimiter: ตัวคั่นหลักพัน
12
+ label_redmineup_decimal_separator: ตัวคั่นทศนิยม
13
+ lable_redmineup_add_tag: เพิ่มแท็ก
@@ -0,0 +1,13 @@
1
+ tr:
2
+ label_redmineup_settings: Para ayarları
3
+ label_redmineup_money: Para
4
+ label_redmineup_disable_taxes: Vergileri devre dışı bırak
5
+ label_redmineup_default_tax: Varsayılan vergi değeri
6
+ label_redmineup_tax_type: Vergi türü
7
+ label_redmineup_tax_type_exclusive: Vergi hariç
8
+ label_redmineup_tax_type_inclusive: Vergi dahil
9
+ label_redmineup_default_currency: Varsayılan para birimi
10
+ label_redmineup_major_currencies: Ana para birimleri
11
+ label_redmineup_thousands_delimiter: Binlik ayracı
12
+ label_redmineup_decimal_separator: Ondalık ayracı
13
+ lable_redmineup_add_tag: etiket ekle
@@ -0,0 +1,13 @@
1
+ uk:
2
+ label_redmineup_settings: Налаштування грошей
3
+ label_redmineup_money: Гроші
4
+ label_redmineup_disable_taxes: Вимкнути податки
5
+ label_redmineup_default_tax: Типове значення податку
6
+ label_redmineup_tax_type: Тип податку
7
+ label_redmineup_tax_type_exclusive: Податок не включено
8
+ label_redmineup_tax_type_inclusive: Податок включено
9
+ label_redmineup_default_currency: Типова валюта
10
+ label_redmineup_major_currencies: Основні валюти
11
+ label_redmineup_thousands_delimiter: Роздільник тисяч
12
+ label_redmineup_decimal_separator: Десятковий роздільник
13
+ lable_redmineup_add_tag: додати тег
@@ -0,0 +1,13 @@
1
+ vi:
2
+ label_redmineup_settings: Cài đặt tiền tệ
3
+ label_redmineup_money: Tiền tệ
4
+ label_redmineup_disable_taxes: Tắt thuế
5
+ label_redmineup_default_tax: Giá trị thuế mặc định
6
+ label_redmineup_tax_type: Loại thuế
7
+ label_redmineup_tax_type_exclusive: Chưa bao gồm thuế
8
+ label_redmineup_tax_type_inclusive: Đã bao gồm thuế
9
+ label_redmineup_default_currency: Tiền tệ mặc định
10
+ label_redmineup_major_currencies: Tiền tệ chính
11
+ label_redmineup_thousands_delimiter: Dấu phân cách hàng nghìn
12
+ label_redmineup_decimal_separator: Dấu phân cách thập phân
13
+ lable_redmineup_add_tag: thêm thẻ
@@ -0,0 +1,13 @@
1
+ zh-TW:
2
+ label_redmineup_settings: 貨幣設定
3
+ label_redmineup_money: 貨幣
4
+ label_redmineup_disable_taxes: 停用稅金
5
+ label_redmineup_default_tax: 預設稅額
6
+ label_redmineup_tax_type: 稅金類型
7
+ label_redmineup_tax_type_exclusive: 未含稅
8
+ label_redmineup_tax_type_inclusive: 含稅
9
+ label_redmineup_default_currency: 預設貨幣
10
+ label_redmineup_major_currencies: 主要貨幣
11
+ label_redmineup_thousands_delimiter: 千位分隔符
12
+ label_redmineup_decimal_separator: 小數分隔符
13
+ lable_redmineup_add_tag: 新增標籤
@@ -0,0 +1,13 @@
1
+ zh:
2
+ label_redmineup_settings: 货币设置
3
+ label_redmineup_money: 货币
4
+ label_redmineup_disable_taxes: 禁用税费
5
+ label_redmineup_default_tax: 默认税额
6
+ label_redmineup_tax_type: 税费类型
7
+ label_redmineup_tax_type_exclusive: 不含税
8
+ label_redmineup_tax_type_inclusive: 含税
9
+ label_redmineup_default_currency: 默认货币
10
+ label_redmineup_major_currencies: 主要货币
11
+ label_redmineup_thousands_delimiter: 千位分隔符
12
+ label_redmineup_decimal_separator: 小数分隔符
13
+ lable_redmineup_add_tag: 添加标签
data/doc/CHANGELOG CHANGED
@@ -4,6 +4,20 @@ Redmine UP gem - general functions for plugins (tags, vote, viewing, currency)
4
4
  Copyright (C) 2011-2026 Kirill Bezrukov (RedmineUP)
5
5
  https://www.redmineup.com/
6
6
 
7
+ == 2026-10-24 v1.1.16
8
+
9
+ * Added shared styles for tag chips: colored, gray, link
10
+ * Added tag_label, tag_label_link(s), tag_sidebar_cloud helpers
11
+ * Added tag and tag-merge icons
12
+ * Added translations for all languages supported by Redmine
13
+ * Fixed websocket reconnection every minute behind nginx
14
+ * Fixed error on deleting a person when the viewed model loads later
15
+ * Fixed trailing ".0" in amounts of currencies without a fractional unit
16
+
17
+ == 2026-10-11 v1.1.15
18
+
19
+ * Speeded up the tags list query (available_tags) on pages with a lot of tagged records
20
+
7
21
  == 2026-10-09 v1.1.14
8
22
 
9
23
  * Fixed missing calendar icons and extra padding in Redmine 6/7
@@ -11,6 +11,13 @@ Provided by `Redmineup::TagsHelper`:
11
11
  - `tag_links(tag_list, options = {}) { |tag| url }`
12
12
  - `tag_cloud(tags, classes) { |tag, css_class| ... }`
13
13
  - `tag_cloud_links(tags, key, options = {})`
14
+
15
+ Since 1.1.16 (the display mode, sidebar style, counts and order come from the Tags plugin when it is installed; without it tags are colored chips in a cloud):
16
+
17
+ - `tag_display_mode` returns `color`, `plain` or `link`
18
+ - `tag_label(inner, color)` wraps markup in a chip (`span.tag-label.tag-label-color` / `.tag-label-plain`) or `span.tag-link`
19
+ - `tag_label_link(url, tag_name, options = {})`, `tag_label_links(tag_list, options = {}) { |tag| url }`, `tag_label_separator`
20
+ - `tag_sidebar_cloud(tags, key)` renders the sidebar tags (list, cloud or simple cloud) and resolves links through `{key}_tag_url`
14
21
  - `tag_separator`
15
22
 
16
23
  ### Recommended pattern
@@ -1,6 +1,8 @@
1
1
  module ActionCable
2
2
  module Server
3
3
  class RupServer < ActionCable::Server::Base
4
+ BEAT_INTERVAL = 10
5
+
4
6
  attr_reader :config
5
7
 
6
8
  def initialize(config: self.class.config)
@@ -10,7 +12,9 @@ module ActionCable
10
12
  end
11
13
 
12
14
  def setup_heartbeat_timer
13
- Rails.logger.info('Heartbeat timer disabled for RupServer')
15
+ @heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do
16
+ event_loop.post { connections.each(&:beat) }
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -129,43 +129,43 @@ module Redmineup
129
129
  #Return all avalible tags for a project or global
130
130
  #Example: Question.available_tags(:project => @project_id )
131
131
  def available_tags(options = {})
132
- projects = [[options[:project]], options[:projects]].flatten.compact
133
132
  limit = options[:limit].to_i.zero? ? 30 : options[:limit].to_i
134
- scope = Tag.where({})
135
- class_name = quote_string_value(base_class.name)
136
- join = []
137
- join << "JOIN #{Tagging.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id "
138
- join << "JOIN #{table_name} ON #{table_name}.id = #{Tagging.table_name}.taggable_id
139
- AND #{Tagging.table_name}.taggable_type = #{class_name} "
140
- if attribute_names.include?('project_id') && projects.any?
141
- join << "JOIN #{Project.table_name} ON #{Project.table_name}.id = #{table_name}.project_id"
142
- scope = scope.where("#{table_name}.project_id IN (%s)", projects.map(&:id).join(','))
143
- end
133
+ counts_sql = Tag.connection.unprepared_statement { tagged_records_scope(options).to_sql }
144
134
 
135
+ scope = Tag.joins("JOIN (#{counts_sql}) tag_counts ON tag_counts.tag_id = #{Tag.table_name}.id")
136
+ scope = scope.select("#{Tag.table_name}.*, tag_counts.count AS count")
145
137
  if options[:name_like].present?
146
138
  scope = scope.where("LOWER(#{Tag.table_name}.name) LIKE LOWER(?)", "%#{options[:name_like]}%")
147
139
  end
140
+ scope.order("#{Tag.table_name}.name").limit(limit)
141
+ end
148
142
 
149
- group_fields = ''
150
- group_fields << ", #{Tag.table_name}.created_at" if Tag.respond_to?(:created_at)
151
- group_fields << ", #{Tag.table_name}.updated_at" if Tag.respond_to?(:updated_at)
152
- group_fields << ", #{Tag.table_name}.color" if Tag.column_names.include?('color')
143
+ # tag_id => number of visible taggables. Extension point for available_tags.
144
+ def tagged_records_scope(options = {})
145
+ projects = [[options[:project]], options[:projects]].flatten.compact
146
+ scope = Tagging.unscoped.
147
+ joins("JOIN #{table_name} ON #{table_name}.id = #{Tagging.table_name}.taggable_id").
148
+ where("#{Tagging.table_name}.taggable_type = ?", base_class.name).
149
+ group("#{Tagging.table_name}.tag_id").
150
+ select("#{Tagging.table_name}.tag_id, COUNT(DISTINCT #{Tagging.table_name}.taggable_id) AS count")
151
+
152
+ projects_joined = false
153
+ if attribute_names.include?('project_id') && projects.any?
154
+ scope = scope.joins("JOIN #{Project.table_name} ON #{Project.table_name}.id = #{table_name}.project_id").
155
+ where("#{table_name}.project_id IN (?)", projects.map(&:id))
156
+ projects_joined = true
157
+ end
153
158
 
154
159
  if base_class.respond_to?(:visible_condition)
155
160
  visible_condition = base_class.visible_condition(User.current)
156
- if visible_condition.include?('project_id') && join.all? { |jn| jn.exclude?('JOIN projects') }
157
- join << "JOIN #{Project.table_name} ON #{Project.table_name}.id = #{table_name}.project_id"
161
+ if visible_condition.include?('project_id') && !projects_joined
162
+ scope = scope.joins("JOIN #{Project.table_name} ON #{Project.table_name}.id = #{table_name}.project_id")
158
163
  end
159
164
  scope = scope.where(visible_condition)
160
165
  end
161
- scope = scope.joins(join.join(' '))
162
- scope = scope.select("#{Tag.table_name}.*, COUNT(DISTINCT #{Tagging.table_name}.taggable_id) AS count")
163
- scope = scope.group("#{Tag.table_name}.id, #{Tag.table_name}.name #{group_fields}")
164
- scope = scope.having('COUNT(*) > 0')
165
- scope = scope.order("#{Tag.table_name}.name")
166
- scope = scope.limit(limit)
167
166
  scope
168
167
  end
168
+
169
169
  # Returns an array of related tags.
170
170
  # Related tags are all the other tags that are found on the models tagged with the provided tags.
171
171
  #
@@ -113,8 +113,13 @@ module Redmineup
113
113
  # Add to the User (or whatever the viewer is) a has_many viewings
114
114
  viewer_as_class = viewer_class.constantize
115
115
  return if viewer_as_class.instance_methods.include?('find_in_viewings')
116
- viewer_as_class.class_eval do
117
- has_many :viewings, foreign_key: :viewer_id, class_name: viewing_class.to_s, dependent: :delete_all
116
+ # Subclasses loaded earlier (Person < User) keep their own association set, so add it to them as well.
117
+ ([viewer_as_class] + viewer_as_class.descendants).each do |klass|
118
+ next if klass.reflect_on_association(:viewings)
119
+
120
+ klass.class_eval do
121
+ has_many :viewings, foreign_key: :viewer_id, class_name: viewing_class.to_s, dependent: :delete_all
122
+ end
118
123
  end
119
124
  end
120
125
  end
@@ -59,6 +59,7 @@ module Redmineup
59
59
  decimal_part << '0' while decimal_part.length < (currency.decimal_places + 1)
60
60
  decimal_part
61
61
  end
62
+ formatted.sub!(/\..*/, '') if currency.decimal_places.zero?
62
63
  formatted.gsub!(/\./, rules[:decimal_mark]) unless '.' == rules[:decimal_mark]
63
64
  end
64
65
 
@@ -50,5 +50,72 @@ module Redmineup
50
50
  end
51
51
  end
52
52
 
53
+
54
+ def tag_display_mode
55
+ if defined?(RedmineupTags::TagDisplay)
56
+ RedmineupTags::TagDisplay.mode
57
+ elsif defined?(RedmineupTags) && RedmineupTags.respond_to?(:use_colors?)
58
+ RedmineupTags.use_colors? ? 'color' : 'plain'
59
+ else
60
+ 'color'
61
+ end
62
+ end
63
+
64
+ def tag_label_separator
65
+ tag_display_mode == 'link' ? ', ' : ' '
66
+ end
67
+
68
+ def tag_label(inner, color)
69
+ case tag_display_mode
70
+ when 'color' then content_tag(:span, inner, class: 'tag-label tag-label-color', style: "background-color: #{color}")
71
+ when 'plain' then content_tag(:span, inner, class: 'tag-label tag-label-plain')
72
+ else content_tag(:span, inner, class: 'tag-link')
73
+ end
74
+ end
75
+
76
+ def tag_label_link(url, tag_name, options = {})
77
+ options = options.dup
78
+ count = options.delete(:count)
79
+ color = options.delete(:color) || Redmineup::ActsAsTaggable::Tag.color_from_name(tag_name)
80
+ inner = link_to(tag_name, url, options)
81
+ inner << content_tag('span', "(#{count})", class: 'tag-count') if count
82
+ tag_label(inner, color)
83
+ end
84
+
85
+ def tag_label_links(tag_list, options = {}, &url_block)
86
+ return if tag_list.blank?
87
+
88
+ content_tag(:span, class: 'tag_list') do
89
+ safe_join(Array(tag_list).map { |tag| tag_label_link(url_block.call(tag), tag, options) }, tag_label_separator)
90
+ end
91
+ end
92
+
93
+ def tag_sidebar_cloud(tags, key)
94
+ return if tags.blank?
95
+
96
+ settings = defined?(RedmineupTags::TagDisplay) ? RedmineupTags::TagDisplay : nil
97
+ style = settings ? settings.sidebar_style : 'cloud'
98
+ counts = settings ? settings.sidebar_counts? : true
99
+ sort_by, sort_order = settings ? [settings.sort_by, settings.sort_order] : %w[name asc]
100
+
101
+ url_method = :"#{key}_tag_url"
102
+ raise NoMethodError, "#{url_method} is not defined in view context" unless respond_to?(url_method, true)
103
+
104
+ tags = tags.to_a.sort_by { |tag| sort_by == 'count' ? tag.count.to_i : tag.name.downcase }
105
+ tags.reverse! if sort_order == 'desc'
106
+ group_by_letter = style == 'list' && sort_by == 'name'
107
+ groups = group_by_letter ? tags.group_by { |tag| tag.name.downcase.first } : { nil => tags }
108
+ list_el, item_el = style == 'list' ? %w[ul li] : %w[div span]
109
+ separator = style == 'simple_cloud' ? tag_label_separator : ' '
110
+ content = groups.map do |letter, grouped|
111
+ items = []
112
+ tag_cloud(grouped, (1..8).to_a) do |tag, weight|
113
+ items << content_tag(item_el, tag_label_link(send(url_method, tag.name), tag.name, count: (tag.count if counts && tag.count.to_i > 0), color: (tag.color if tag.respond_to?(:color))), class: "tag-nube-#{weight}", style: ('font-size: 1em;' if style == 'simple_cloud'))
114
+ end
115
+ safe_join([(content_tag(item_el, letter.upcase, class: 'letter') if letter), safe_join(items, separator)])
116
+ end
117
+ content_tag(list_el, safe_join(content), class: 'tags-cloud', style: ('text-align: left;' if style == 'simple_cloud'))
118
+ end
119
+
53
120
  end
54
121
  end
@@ -1,3 +1,3 @@
1
1
  module Redmineup
2
- VERSION = '1.1.14'
2
+ VERSION = '1.1.16'
3
3
  end
data/redmineup.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://www.redmineup.com"
14
14
  spec.license = "GPL-2.0"
15
15
 
16
- spec.files = `git ls-files -z`.split("\x0")
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.start_with?("doc/dev/") || %w[AGENTS.md GLOSSARY.md].include?(f) }
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
@@ -44,4 +44,16 @@ class RupActsAsViewedTest < ActiveSupport::TestCase
44
44
  issue.view '127.0.0.1', user
45
45
  assert_equal [issue], Issue.find_viewed_by(user)
46
46
  end
47
+
48
+ def test_viewer_subclass_loaded_before_the_macro_gets_viewings
49
+ Object.const_set(:TmpViewer, Class.new(ActiveRecord::Base) { self.table_name = 'users' })
50
+ Object.const_set(:TmpViewerChild, Class.new(TmpViewer) { has_many :own_issues, class_name: 'Issue', foreign_key: :author_id })
51
+ viewed = Class.new(ActiveRecord::Base) { self.table_name = 'issues'; def self.name; 'TmpViewed'; end }
52
+ viewed.up_acts_as_viewed(viewer_class: 'TmpViewer')
53
+ assert TmpViewer.reflect_on_association(:viewings)
54
+ assert TmpViewerChild.reflect_on_association(:viewings), 'subclass has no viewings association'
55
+ ensure
56
+ Object.send(:remove_const, :TmpViewerChild) if Object.const_defined?(:TmpViewerChild)
57
+ Object.send(:remove_const, :TmpViewer) if Object.const_defined?(:TmpViewer)
58
+ end
47
59
  end
@@ -283,6 +283,13 @@ module Redmineup
283
283
  assert Currency.new(:usd).decimal_places == 2
284
284
  end
285
285
 
286
+ def test_format_zero_decimal_currency_has_no_fraction
287
+ assert_equal '₩3,500,000', Currency.format(3500000, Currency.new(:krw))
288
+ assert_equal '¥1,200', Currency.format(1200, Currency.new(:jpy))
289
+ assert_equal '₫3.500.000', Currency.format(3500000, Currency.new(:vnd))
290
+ assert_equal '$1,234.50', Currency.format(1234.5, Currency.new(:usd))
291
+ end
292
+
286
293
  def test_proper_place_for_custom_currency
287
294
  register_foo
288
295
  assert_equal 2, Currency.new(:foo).decimal_places
@@ -29,6 +29,67 @@ class TagsHelperViewTest < ActionView::TestCase
29
29
  assert_raises(NoMethodError) { tag_cloud_links(Issue.tag_counts, :unknown_xyz) }
30
30
  end
31
31
 
32
+ def with_tag_display(mode, style: 'cloud', counts: true)
33
+ settings = Module.new do
34
+ define_singleton_method(:mode) { mode }
35
+ define_singleton_method(:sidebar_style) { style }
36
+ define_singleton_method(:sidebar_counts?) { counts }
37
+ define_singleton_method(:sort_by) { 'name' }
38
+ define_singleton_method(:sort_order) { 'asc' }
39
+ end
40
+ created = !Object.const_defined?(:RedmineupTags)
41
+ Object.const_set(:RedmineupTags, Module.new) if created
42
+ RedmineupTags.const_set(:TagDisplay, settings)
43
+ yield
44
+ ensure
45
+ RedmineupTags.send(:remove_const, :TagDisplay) if RedmineupTags.const_defined?(:TagDisplay, false)
46
+ Object.send(:remove_const, :RedmineupTags) if created
47
+ end
48
+
49
+ def test_tag_label_without_tags_plugin_is_color
50
+ result = tag_label('x', '#112233')
51
+ assert_includes result, 'tag-label tag-label-color'
52
+ assert_includes result, 'background-color: #112233'
53
+ assert_equal ' ', tag_label_separator
54
+ end
55
+
56
+ def test_tag_label_modes
57
+ with_tag_display('plain') do
58
+ assert_includes tag_label('x', '#112233'), 'tag-label tag-label-plain'
59
+ assert_not_includes tag_label('x', '#112233'), 'background-color'
60
+ end
61
+ with_tag_display('link') do
62
+ assert_equal '<span class="tag-link">x</span>', tag_label('x', '#112233')
63
+ assert_equal ', ', tag_label_separator
64
+ end
65
+ end
66
+
67
+ def test_tag_label_link_and_links
68
+ result = tag_label_link('/tags/error', 'error', count: 3, color: '#abcdef', class: 'x')
69
+ assert_includes result, 'background-color: #abcdef'
70
+ assert_includes result, '(3)'
71
+ assert_includes result, 'class="x"'
72
+ links = tag_label_links(%w[a b]) { |t| "/tags/#{t}" }
73
+ assert_includes links, 'class="tag_list"'
74
+ assert_includes links, '/tags/a'
75
+ assert_nil tag_label_links([]) { |t| "/tags/#{t}" }
76
+ end
77
+
78
+ def test_tag_sidebar_cloud_styles
79
+ tags = Issue.tag_counts
80
+ assert_nil tag_sidebar_cloud([], :stub)
81
+ assert_includes tag_sidebar_cloud(tags, :stub), 'class="tags-cloud"'
82
+ with_tag_display('plain', style: 'list') do
83
+ result = tag_sidebar_cloud(tags, :stub)
84
+ assert_includes result, '<ul'
85
+ assert_includes result, 'class="letter"'
86
+ end
87
+ with_tag_display('color', counts: false) do
88
+ assert_not_includes tag_sidebar_cloud(tags, :stub), 'tag-count'
89
+ end
90
+ assert_raises(NoMethodError) { tag_sidebar_cloud(tags, :unknown_xyz) }
91
+ end
92
+
32
93
  def test_tag_list_field_tag
33
94
  result = tag_list_field_tag('issue[tag_list]', ['bug'],
34
95
  url: '/auto_completes/taggable_tags',
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redmineup
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.14
4
+ version: 1.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - RedmineUP
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-09 00:00:00.000000000 Z
11
+ date: 2026-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -131,11 +131,56 @@ files:
131
131
  - app/views/redmineup/_money.html.erb
132
132
  - app/views/redmineup/settings.html.erb
133
133
  - config/currency_iso.json
134
+ - config/locales/ar.yml
135
+ - config/locales/az.yml
136
+ - config/locales/bg.yml
137
+ - config/locales/bs.yml
138
+ - config/locales/ca.yml
134
139
  - config/locales/cs.yml
140
+ - config/locales/da.yml
135
141
  - config/locales/de.yml
142
+ - config/locales/el.yml
143
+ - config/locales/en-GB.yml
136
144
  - config/locales/en.yml
145
+ - config/locales/es-PA.yml
137
146
  - config/locales/es.yml
147
+ - config/locales/et.yml
148
+ - config/locales/eu.yml
149
+ - config/locales/fa.yml
150
+ - config/locales/fi.yml
151
+ - config/locales/fr.yml
152
+ - config/locales/gl.yml
153
+ - config/locales/he.yml
154
+ - config/locales/hr.yml
155
+ - config/locales/hu.yml
156
+ - config/locales/id.yml
157
+ - config/locales/it.yml
158
+ - config/locales/ja.yml
159
+ - config/locales/ko.yml
160
+ - config/locales/lt.yml
161
+ - config/locales/lv.yml
162
+ - config/locales/mk.yml
163
+ - config/locales/mn.yml
164
+ - config/locales/nl.yml
165
+ - config/locales/no.yml
166
+ - config/locales/pl.yml
167
+ - config/locales/pt-BR.yml
168
+ - config/locales/pt.yml
169
+ - config/locales/ro.yml
138
170
  - config/locales/ru.yml
171
+ - config/locales/sk.yml
172
+ - config/locales/sl.yml
173
+ - config/locales/sq.yml
174
+ - config/locales/sr-YU.yml
175
+ - config/locales/sr.yml
176
+ - config/locales/sv.yml
177
+ - config/locales/ta-IN.yml
178
+ - config/locales/th.yml
179
+ - config/locales/tr.yml
180
+ - config/locales/uk.yml
181
+ - config/locales/vi.yml
182
+ - config/locales/zh-TW.yml
183
+ - config/locales/zh.yml
139
184
  - config/routes.rb
140
185
  - doc/CHANGELOG
141
186
  - doc/LICENSE.txt