diff options
67 files changed, 216 insertions, 57 deletions
diff --git a/app/assets/javascripts/application-legacy.js b/app/assets/javascripts/application-legacy.js index 286e3e2e6..deaaa66b6 100644 --- a/app/assets/javascripts/application-legacy.js +++ b/app/assets/javascripts/application-legacy.js @@ -679,7 +679,7 @@ function copyDataClipboardTextToClipboard(target) { } function setupCopyButtonsToPreElements() { - document.querySelectorAll('pre:not(.pre-wrapper pre)').forEach((pre) => { + document.querySelectorAll('.wiki pre:not(.pre-wrapper pre)').forEach((pre) => { // Wrap the <pre> element with a container and add a copy button const wrapper = document.createElement("div"); wrapper.classList.add("pre-wrapper"); diff --git a/app/controllers/reactions_controller.rb b/app/controllers/reactions_controller.rb index f768f939d..71b37e5f8 100644 --- a/app/controllers/reactions_controller.rb +++ b/app/controllers/reactions_controller.rb @@ -60,6 +60,6 @@ class ReactionsController < ApplicationController end def authorize_reactable - render_403 unless Redmine::Reaction.writable?(@object, User.current) + render_403 unless Redmine::Reaction.editable?(@object, User.current) end end diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index b39427bda..88a571b62 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -44,6 +44,7 @@ module AvatarsHelper if user.respond_to?(:mail) email = user.mail options[:title] = user.name unless options[:title] + options[:initials] = user.initials if options[:default] == "initials" elsif user.to_s =~ %r{<(.+?)>} email = $1 end diff --git a/app/helpers/reactions_helper.rb b/app/helpers/reactions_helper.rb index 911da7127..5c0b807ee 100644 --- a/app/helpers/reactions_helper.rb +++ b/app/helpers/reactions_helper.rb @@ -26,15 +26,15 @@ module ReactionsHelper detail = object.reaction_detail - reaction = detail.user_reaction + user_reaction = detail.user_reaction count = detail.reaction_count visible_user_names = detail.visible_users.take(DISPLAY_REACTION_USERS_LIMIT).map(&:name) tooltip = build_reaction_tooltip(visible_user_names, count) - if Redmine::Reaction.writable?(object, User.current) - if reaction&.persisted? - reaction_button_reacted(object, reaction, count, tooltip) + if Redmine::Reaction.editable?(object, User.current) + if user_reaction.present? + reaction_button_reacted(object, user_reaction, count, tooltip) else reaction_button_not_reacted(object, count, tooltip) end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 39a836a03..c1f989805 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -244,6 +244,7 @@ module SettingsHelper ['Mystery man', 'mm'], ['Retro', 'retro'], ['Robohash', 'robohash'], - ['Wavatars', 'wavatar']] + ['Wavatars', 'wavatar'], + ['Initials', 'initials']] end end diff --git a/app/models/reaction.rb b/app/models/reaction.rb index 84c982043..184ed2d6e 100644 --- a/app/models/reaction.rb +++ b/app/models/reaction.rb @@ -25,39 +25,34 @@ class Reaction < ApplicationRecord scope :by, ->(user) { where(user: user) } scope :for_reactable, ->(reactable) { where(reactable: reactable) } + scope :visible, ->(user) { where(user: User.visible(user)) } # Represents reaction details for a reactable object Detail = Struct.new( - # Total number of reactions - :reaction_count, # Users who reacted and are visible to the target user :visible_users, # Reaction of the target user :user_reaction ) do - def initialize(reaction_count: 0, visible_users: [], user_reaction: nil) + def initialize(visible_users: [], user_reaction: nil) super end + + def reaction_count = visible_users.size end def self.build_detail_map_for(reactables, user) - reactions = preload(:user) + reactions = visible(user) .for_reactable(reactables) + .preload(:user) .select(:id, :reactable_id, :user_id) .order(id: :desc) - # Prepare IDs of users who reacted and are visible to the user - visible_user_ids = User.visible(user) - .joins(:reactions) - .where(reactions: for_reactable(reactables)) - .pluck(:id).to_set - reactions.each_with_object({}) do |reaction, m| m[reaction.reactable_id] ||= Detail.new m[reaction.reactable_id].then do |detail| - detail.reaction_count += 1 - detail.visible_users << reaction.user if visible_user_ids.include?(reaction.user.id) + detail.visible_users << reaction.user detail.user_reaction = reaction if reaction.user == user end end diff --git a/app/models/user.rb b/app/models/user.rb index 9f74a60fb..c1a860f5a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,46 +28,55 @@ class User < Principal USER_FORMATS = { :firstname_lastname => { :string => '#{firstname} #{lastname}', + :initials => '#{firstname.to_s.first}#{lastname.to_s.first}', :order => %w(firstname lastname id), :setting_order => 1 }, :firstname_lastinitial => { :string => '#{firstname} #{lastname.to_s.chars.first}.', + :initials => '#{firstname.to_s.first}#{lastname.to_s.first}', :order => %w(firstname lastname id), :setting_order => 2 }, :firstinitial_lastname => { :string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}', + :initials => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\').first}#{lastname.to_s.first}', :order => %w(firstname lastname id), :setting_order => 2 }, :firstname => { :string => '#{firstname}', + :initials => '#{firstname.to_s.first(2)}', :order => %w(firstname id), :setting_order => 3 }, :lastname_firstname => { :string => '#{lastname} #{firstname}', + :initials => '#{lastname.to_s.first}#{firstname.to_s.first}', :order => %w(lastname firstname id), :setting_order => 4 }, :lastnamefirstname => { :string => '#{lastname}#{firstname}', + :initials => '#{lastname.to_s.first}#{firstname.to_s.first}', :order => %w(lastname firstname id), :setting_order => 5 }, :lastname_comma_firstname => { :string => '#{lastname}, #{firstname}', + :initials => '#{lastname.to_s.first}#{firstname.to_s.first}', :order => %w(lastname firstname id), :setting_order => 6 }, :lastname => { :string => '#{lastname}', + :initials => '#{lastname.to_s.first(2)}', :order => %w(lastname id), :setting_order => 7 }, :username => { :string => '#{login}', + :initials => '#{login.to_s.first(2)}', :order => %w(login id), :setting_order => 8 }, @@ -275,6 +284,14 @@ class User < Principal end end + # Return user's initials based on name format + def initials(formatter = nil) + f = self.class.name_formatter(formatter) + format = f[:initials] || USER_FORMATS[:firstname_lastname][:initials] + initials = eval('"' + format + '"') + initials.upcase + end + def registered? self.status == STATUS_REGISTERED end diff --git a/app/views/settings/_display.html.erb b/app/views/settings/_display.html.erb index 62c53dfbb..3b2f95798 100644 --- a/app/views/settings/_display.html.erb +++ b/app/views/settings/_display.html.erb @@ -22,7 +22,12 @@ <p><%= setting_check_box :gravatar_enabled, :data => {:enables => '#settings_gravatar_default'} %> <em class="info"><%= t(:text_avatar_server_config_html, :url => Redmine::Configuration['avatar_server_url']) %></em></p> -<p><%= setting_select :gravatar_default, gravatar_default_setting_options, :blank => :label_none %></p> +<p> + <%= setting_select :gravatar_default, gravatar_default_setting_options, :blank => :label_none %> + <em class="<%= Setting.gravatar_default == "initials" ? "info" : "hidden" %>"> + <%= t(:text_setting_gravatar_default_initials_html) %> + </em> +</p> <p><%= setting_check_box :thumbnails_enabled, :data => {:enables => '#settings_thumbnails_size'} %></p> @@ -35,3 +40,18 @@ <%= submit_tag l(:button_save) %> <% end %> + +<%= javascript_tag do %> + $('#settings_gravatar_default').on('change', function(e){ + const gravatar_default = e.target.value; + const em = e.target.parentElement.getElementsByTagName('em')[0]; + + if (gravatar_default === 'initials') { + em.classList.remove('hidden'); + em.classList.add('info'); + } else { + em.classList.add('hidden'); + em.classList.remove('info'); + } + }); +<% end %>
\ No newline at end of file diff --git a/config/locales/ar.yml b/config/locales/ar.yml index e291b2cf7..b3c5e11cc 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1511,3 +1511,5 @@ ar: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/az.yml b/config/locales/az.yml index ca5ba2e71..68c1669a1 100644 --- a/config/locales/az.yml +++ b/config/locales/az.yml @@ -1602,3 +1602,5 @@ az: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 3d7ee8f1a..0a310a9b8 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -211,6 +211,7 @@ bg: error_can_not_delete_custom_field: Невъзможност за изтриване на потребителско поле error_can_not_delete_tracker_html: Този тракер съдържа задачи и не може да бъде изтрит.<p>The following projects have issues with this tracker:<br>%{projects}</p> error_can_not_remove_role: Тази роля се използва и не може да бъде изтрита. + error_can_not_remove_role_reason_members_html: "<p>Следващите проекти имат членове с тази роля:<br>%{projects}</p>" error_can_not_reopen_issue_on_closed_version: Задача, асоциирана със затворена версия не може да бъде отворена отново error_can_not_archive_project: Този проект не може да бъде архивиран error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен. @@ -239,6 +240,7 @@ bg: error_exceeds_maximum_hours_per_day: Не можете да запишете повече от %{max_hours} часа на един ден (%{logged_hours} часове вече са записани) error_can_not_delete_auth_source: Този режим за идентификация се използва и не може да бъде премахнат. error_spent_on_future_date: Не е възможно да се отчете изразходвано време на дата в бъдещето + error_spent_on_closed_issue: Не е възможно да се отчете изразходвано време за затворена задача error_not_allowed_to_log_time_for_other_users: Вие нямате разрешение да записвате изразходвано време за други потребители error_can_not_execute_macro_html: Грешка при изпълнение на <strong>%{name}</strong> макрос (%{error}) @@ -523,9 +525,13 @@ bg: setting_timelog_accept_0_hours: Приемане на записи с 0 часа setting_timelog_max_hours_per_day: Максимум часове, които могат да бъдат записани за ден и за потребител setting_timelog_accept_future_dates: Разрешено отчитане на изразходвано време на дата в бъдещето + setting_timelog_accept_closed_issues: Разрешено отчитане на изразходвано време за затворени задачи setting_show_status_changes_in_mail_subject: Показване на промените на състоянието на задачите в поле Относно на имейлите setting_project_list_defaults: Проектен списък setting_twofa: Двуфакторна автентикация + setting_related_issues_default_columns: Колони по подразбиране за свързани и подзадачи + setting_display_related_issues_table_headers: Показване на заглавия на таблиците + setting_reactions_enabled: Разрешаване на реакциите permission_add_project: Създаване на проект permission_add_subprojects: Създаване на подпроекти @@ -1390,6 +1396,7 @@ bg: label_import_time_entries: Импортиране на записи за използвано време label_import_users: Импортиране на потребители sudo_mode_new_info_html: "<strong>Какво се случва?</strong> Трябва да потвърдите вашата парола преди да предприемете административни действия. Това осигурява вашият акаунт." + label_progressbar: Прогрес бар twofa__totp__name: Authenticator app twofa__totp__text_pairing_info_html: Сканирайте този QR код или въведете текстовия ключ @@ -1444,15 +1451,9 @@ bg: За да потвърдите, въведете името (%{login}) по-долу. text_project_destroy_enter_identifier: За да потвърдите действието, въведете идентификатора на проекта (%{identifier}) по-долу. field_name_or_email_or_login: Име, e-mail или login име - setting_wiki_tablesort_enabled: Javascript based table sorting in wiki content - label_progressbar: Progress bar - error_spent_on_closed_issue: Cannot log time on a closed issue - setting_timelog_accept_closed_issues: Accept time logs on closed issues - setting_related_issues_default_columns: Related and sub issues list defaults - setting_display_related_issues_table_headers: Show table headers - error_can_not_remove_role_reason_members_html: "<p>The following projects have members - with this role:<br>%{projects}</p>" - setting_reactions_enabled: Enable reactions + setting_wiki_tablesort_enabled: Сортиране на таблици чрез Javascript във wiki страници reaction_text_x_other_users: - one: 1 other - other: "%{count} others" + one: 1 друг + other: "%{count} други" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/bs.yml b/config/locales/bs.yml index d1c0a5cec..df4886133 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -1497,3 +1497,5 @@ bs: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/ca.yml b/config/locales/ca.yml index fc0345104..30204cda2 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1498,3 +1498,5 @@ ca: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/cs.yml b/config/locales/cs.yml index c5e4d679d..4101a8a3d 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1493,3 +1493,5 @@ cs: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/da.yml b/config/locales/da.yml index 0f577d1bd..c053f2e43 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1528,3 +1528,5 @@ da: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/de.yml b/config/locales/de.yml index c320099dc..5b0f9bd35 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1475,3 +1475,5 @@ de: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/el.yml b/config/locales/el.yml index df14ae051..a99dcc6bb 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1511,3 +1511,5 @@ el: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 3a119d7a2..2084749f4 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1512,3 +1512,5 @@ en-GB: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f7291593..dca278e23 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1342,6 +1342,7 @@ en: text_no_subject: no subject text_allowed_queries_to_select: Public (to any users) queries only selectable text_setting_config_change: You can configure the behaviour in config/configuration.yml. Please restart the application after editing it. + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> to generate their avatars. default_role_manager: Manager default_role_developer: Developer diff --git a/config/locales/es-PA.yml b/config/locales/es-PA.yml index 195f8df94..4ba2fb881 100644 --- a/config/locales/es-PA.yml +++ b/config/locales/es-PA.yml @@ -1542,3 +1542,5 @@ es-PA: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/es.yml b/config/locales/es.yml index 4539055d5..1f4b20047 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1578,3 +1578,5 @@ es: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/et.yml b/config/locales/et.yml index d503f28ce..d842dbb93 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1516,3 +1516,5 @@ et: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 1334715ee..fd706c42c 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1512,3 +1512,5 @@ eu: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 71f617f3c..48e2ff769 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -1445,3 +1445,5 @@ fa: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 01249963b..470c40c6e 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1529,3 +1529,5 @@ fi: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6093796b6..602546b5a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1488,3 +1488,5 @@ fr: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/gl.yml b/config/locales/gl.yml index ef76a3dab..9ca59ab25 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1517,3 +1517,5 @@ gl: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/he.yml b/config/locales/he.yml index 62b9f78b7..c2886f451 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1516,3 +1516,5 @@ he: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/hr.yml b/config/locales/hr.yml index 04dc5717a..61456ac24 100644 --- a/config/locales/hr.yml +++ b/config/locales/hr.yml @@ -1508,3 +1508,5 @@ hr: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/hu.yml b/config/locales/hu.yml index ecde7c812..b3852a60e 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1500,3 +1500,5 @@ reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/id.yml b/config/locales/id.yml index 9ac93271f..1a3080b69 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -1513,3 +1513,5 @@ id: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/it.yml b/config/locales/it.yml index ff63fbf98..bd71f356c 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1450,3 +1450,5 @@ it: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 9bb11c884..c088546c3 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1461,3 +1461,5 @@ ja: reaction_text_x_other_users: one: 他1人 other: "他%{count}人" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 04f44e11c..af8311a39 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1528,3 +1528,5 @@ ko: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 5ef2561f0..b3d741048 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -1472,3 +1472,5 @@ lt: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/lv.yml b/config/locales/lv.yml index d813060f4..85b660821 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1505,3 +1505,5 @@ lv: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/mk.yml b/config/locales/mk.yml index f75c6f92b..eeb0b17c6 100644 --- a/config/locales/mk.yml +++ b/config/locales/mk.yml @@ -1511,3 +1511,5 @@ mk: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/mn.yml b/config/locales/mn.yml index 4bf0d3755..fe451153e 100644 --- a/config/locales/mn.yml +++ b/config/locales/mn.yml @@ -1511,3 +1511,5 @@ mn: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 3ba829886..66f729afc 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1486,3 +1486,5 @@ nl: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/no.yml b/config/locales/no.yml index 96f2591c5..85151f8d7 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1502,3 +1502,5 @@ reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 0a048d896..934c824f1 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1455,3 +1455,5 @@ pl: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 46f766a7d..e7172c32b 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1516,3 +1516,5 @@ pt-BR: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/pt.yml b/config/locales/pt.yml index e3ec71bae..2bd109c28 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -1504,3 +1504,5 @@ pt: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 1de200dfd..95bd68520 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -1506,3 +1506,5 @@ ro: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 3dbe719fc..60504bbae 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1580,3 +1580,5 @@ ru: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/sk.yml b/config/locales/sk.yml index d7ca435fb..c6b6b91c0 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -1500,3 +1500,5 @@ sk: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 1879c3f99..0c5e1e1ec 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -1511,3 +1511,5 @@ sl: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/sq.yml b/config/locales/sq.yml index b0f4991f0..55f577441 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1473,3 +1473,5 @@ sq: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml index 91926f1f3..7c049c178 100644 --- a/config/locales/sr-YU.yml +++ b/config/locales/sr-YU.yml @@ -1513,3 +1513,5 @@ sr-YU: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 109e60f74..43ca65c14 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -1512,3 +1512,5 @@ sr: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 2afeb2dcf..54a0e4024 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1544,3 +1544,5 @@ sv: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/ta-IN.yml b/config/locales/ta-IN.yml index cbac93a2f..526209edb 100644 --- a/config/locales/ta-IN.yml +++ b/config/locales/ta-IN.yml @@ -1466,3 +1466,5 @@ ta-IN: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/th.yml b/config/locales/th.yml index 89fb4c70d..12232eb22 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -1507,3 +1507,5 @@ th: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/tr.yml b/config/locales/tr.yml index d5ab86b79..9d710f971 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1510,3 +1510,5 @@ tr: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/uk.yml b/config/locales/uk.yml index d1218083d..4a7f70b47 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1499,3 +1499,5 @@ uk: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 6f08cad0c..c96dbd5c4 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1515,3 +1515,5 @@ vi: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 906c11be6..efce04fa1 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1525,3 +1525,5 @@ reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 84ba6e80f..9f0cd8ef1 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1447,3 +1447,5 @@ zh: reaction_text_x_other_users: one: 1 other other: "%{count} others" + text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> + to generate their avatars. diff --git a/lib/plugins/gravatar/lib/gravatar.rb b/lib/plugins/gravatar/lib/gravatar.rb index 4dc27db52..316a01b19 100644 --- a/lib/plugins/gravatar/lib/gravatar.rb +++ b/lib/plugins/gravatar/lib/gravatar.rb @@ -69,7 +69,7 @@ module GravatarHelper options[:default] = CGI::escape(options[:default]) unless options[:default].nil? gravatar_api_url(email_hash).tap do |url| opts = [] - [:rating, :size, :default].each do |opt| + [:rating, :size, :default, :initials].each do |opt| unless options[opt].nil? value = h(options[opt]) opts << [opt, value].join('=') diff --git a/lib/redmine/reaction.rb b/lib/redmine/reaction.rb index b6f2bf075..09fb78ef8 100644 --- a/lib/redmine/reaction.rb +++ b/lib/redmine/reaction.rb @@ -22,13 +22,13 @@ module Redmine # Types of objects that can have reactions REACTABLE_TYPES = %w(Journal Issue Message News Comment) - # Returns true if the user can view the reaction information of the object + # Returns true if the user can view the reaction of the object def self.visible?(object, user = User.current) Setting.reactions_enabled? && object.visible?(user) end # Returns true if the user can add/remove a reaction to/from the object - def self.writable?(object, user = User.current) + def self.editable?(object, user = User.current) user.logged? && visible?(object, user) && object&.project&.active? end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 040667c39..38d69e7c8 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -72,8 +72,8 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase # Should not depend on locale since Redmine displays login page # using default browser locale which depend on system locale for "real" browsers drivers def log_user(login, password) + visit '/my/page' assert_current_path '/login', :ignore_query => true - assert_equal '/login', current_path within('#login-form form') do fill_in 'username', :with => login fill_in 'password', :with => password diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index b7e0321d4..1230ec1eb 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -3347,8 +3347,8 @@ class IssuesControllerTest < Redmine::ControllerTest # The current_user can only see members who belong to projects that the current_user has access to. # Since the Redmine Admin user does not belong to any projects visible to the current_user, # the Redmine Admin user's name is not displayed in the reaction user list. Instead, "1 other" is shown. - assert_select 'a.reaction-button[title=?]', 'Dave Lopper, John Smith, and 1 other' do - assert_select 'span.icon-label', '3' + assert_select 'a.reaction-button[title=?]', 'Dave Lopper and John Smith' do + assert_select 'span.icon-label', '2' end end diff --git a/test/helpers/avatars_helper_test.rb b/test/helpers/avatars_helper_test.rb index f407ae09e..baa64a653 100644 --- a/test/helpers/avatars_helper_test.rb +++ b/test/helpers/avatars_helper_test.rb @@ -68,6 +68,18 @@ class AvatarsHelperTest < Redmine::HelperTest assert_include 'class="gravatar picture"', avatar('jsmith <jsmith@somenet.foo>', :class => 'picture') end + def test_avatar_with_initials + with_settings :gravatar_default => 'initials' do + assert_include 'initials="RA"', avatar(User.find(1)) + end + end + + def test_avatar_should_reject_initials_if_default_is_not_initials + with_settings :gravatar_default => 'identicon' do + assert_not_include 'initials="RA"', avatar(User.find(1)) + end + end + def test_avatar_disabled with_settings :gravatar_enabled => '0' do assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo')) diff --git a/test/helpers/reactions_helper_test.rb b/test/helpers/reactions_helper_test.rb index f3a4e38d8..ab722e3ca 100644 --- a/test/helpers/reactions_helper_test.rb +++ b/test/helpers/reactions_helper_test.rb @@ -106,12 +106,10 @@ class ReactionsHelperTest < ActionView::TestCase assert_select_in result, 'a.reaction-button[title=?]', expected_tooltip end - test 'reaction_button displays non-visible users as "X other" in the tooltip' do + test 'reaction_button should not count and display non-visible users' do issue2 = issues(:issues_002) issue2.reaction_detail = Reaction::Detail.new( - # The remaining 3 users are non-visible users - reaction_count: 5, visible_users: users(:users_002, :users_003) ) @@ -119,11 +117,10 @@ class ReactionsHelperTest < ActionView::TestCase reaction_button(issue2) end - assert_select_in result, 'a.reaction-button[title=?]', 'John Smith, Dave Lopper, and 3 others' + assert_select_in result, 'a.reaction-button[title=?]', 'John Smith and Dave Lopper' # When all users are non-visible users issue2.reaction_detail = Reaction::Detail.new( - reaction_count: 2, visible_users: [] ) @@ -131,7 +128,10 @@ class ReactionsHelperTest < ActionView::TestCase reaction_button(issue2) end - assert_select_in result, 'a.reaction-button[title=?]', '2 others' + assert_select_in result, 'a.reaction-button[title]', false + assert_select_in result, 'a.reaction-button' do + assert_select 'span.icon-label', '0' + end end test 'reaction_button formats the tooltip content based on the support.array settings of each locale' do diff --git a/test/unit/lib/redmine/reaction_test.rb b/test/unit/lib/redmine/reaction_test.rb index bed4600d0..f3228a3bd 100644 --- a/test/unit/lib/redmine/reaction_test.rb +++ b/test/unit/lib/redmine/reaction_test.rb @@ -42,7 +42,6 @@ class Redmine::ReactionTest < ActiveSupport::TestCase Issue.preload_reaction_details([issue1, issue2]) expected_issue1_reaction_detail = Reaction::Detail.new( - reaction_count: 3, visible_users: [users(:users_003), users(:users_002), users(:users_001)], user_reaction: reactions(:reaction_002) ) @@ -53,7 +52,6 @@ class Redmine::ReactionTest < ActiveSupport::TestCase # Even when an object has no reactions, an empty ReactionDetail is set. assert_equal Reaction::Detail.new( - reaction_count: 0, visible_users: [], user_reaction: nil ), issue2.reaction_detail @@ -107,7 +105,6 @@ class Redmine::ReactionTest < ActiveSupport::TestCase assert_nil message7.instance_variable_get(:@reaction_detail) assert_equal Reaction::Detail.new( - reaction_count: 1, visible_users: [users(:users_002)], user_reaction: reactions(:reaction_009) ), message7.reaction_detail @@ -122,7 +119,6 @@ class Redmine::ReactionTest < ActiveSupport::TestCase comment1.load_reaction_detail assert_equal Reaction::Detail.new( - reaction_count: 1, visible_users: [users(:users_002)], user_reaction: nil ), comment1.reaction_detail @@ -153,7 +149,7 @@ class Redmine::ReactionTest < ActiveSupport::TestCase assert_not Redmine::Reaction.visible?(object, user) end - test 'writable? returns true for various reactable objects when user is logged in, object is visible, and project is active' do + test 'editable? returns true for various reactable objects when user is logged in, object is visible, and project is active' do reactable_objects = { issue: issues(:issues_007), message: messages(:messages_001), @@ -164,30 +160,30 @@ class Redmine::ReactionTest < ActiveSupport::TestCase user = users(:users_002) reactable_objects.each do |type, object| - assert Redmine::Reaction.writable?(object, user), "Expected writable? to return true for #{type}" + assert Redmine::Reaction.editable?(object, user), "Expected editable? to return true for #{type}" end end - test 'writable? returns false when user is not logged in' do + test 'editable? returns false when user is not logged in' do object = issues(:issues_007) user = User.anonymous - assert_not Redmine::Reaction.writable?(object, user) + assert_not Redmine::Reaction.editable?(object, user) end - test 'writable? returns false when project is inactive' do + test 'editable? returns false when project is inactive' do object = issues(:issues_007) user = users(:users_002) object.project.update!(status: Project::STATUS_ARCHIVED) - assert_not Redmine::Reaction.writable?(object, user) + assert_not Redmine::Reaction.editable?(object, user) end - test 'writable? returns false when project is closed' do + test 'editable? returns false when project is closed' do object = issues(:issues_007) user = users(:users_002) object.project.update!(status: Project::STATUS_CLOSED) - assert_not Redmine::Reaction.writable?(object, user) + assert_not Redmine::Reaction.editable?(object, user) end end diff --git a/test/unit/reaction_test.rb b/test/unit/reaction_test.rb index 2690da351..9b3da0738 100644 --- a/test/unit/reaction_test.rb +++ b/test/unit/reaction_test.rb @@ -75,12 +75,10 @@ class ReactionTest < ActiveSupport::TestCase expected = { 1 => Reaction::Detail.new( - reaction_count: 3, visible_users: [users(:users_003), users(:users_002), users(:users_001)], user_reaction: reactions(:reaction_003) ), 6 => Reaction::Detail.new( - reaction_count: 1, visible_users: [users(:users_002)], user_reaction: nil ) diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index aeae62df8..8474e174b 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -589,6 +589,27 @@ class UserTest < ActiveSupport::TestCase end end + def test_initials_format + assert_equal 'JS', @jsmith.initials(:firstname_lastinitial) + assert_equal 'SJ', @jsmith.initials(:lastname_comma_firstname) + assert_equal 'SJ', @jsmith.initials(:lastname_firstname) + assert_equal 'JS', @jsmith.initials(:firstinitial_lastname) + assert_equal 'JL', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').initials(:firstinitial_lastname) + assert_equal 'JS', @jsmith.initials(:undefined_format) + end + + def test_initials_should_use_setting_as_default_format + with_settings :user_format => :firstname_lastname do + assert_equal 'JS', @jsmith.reload.initials + end + with_settings :user_format => :username do + assert_equal 'JS', @jsmith.reload.initials + end + with_settings :user_format => :lastname do + assert_equal 'SM', @jsmith.reload.initials + end + end + def test_lastname_should_accept_255_characters u = User.first u.lastname = 'a' * 255 |