summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/application-legacy.js2
-rw-r--r--app/assets/stylesheets/wiki_syntax.css11
-rw-r--r--app/assets/stylesheets/wiki_syntax_detailed.css20
-rw-r--r--app/controllers/reactions_controller.rb2
-rw-r--r--app/helpers/avatars_helper.rb1
-rw-r--r--app/helpers/reactions_helper.rb8
-rw-r--r--app/helpers/settings_helper.rb3
-rw-r--r--app/models/reaction.rb19
-rw-r--r--app/models/user.rb17
-rw-r--r--app/views/help/wiki_syntax/common_mark/en/wiki_syntax_common_mark.html.erb8
-rw-r--r--app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb48
-rw-r--r--app/views/settings/_display.html.erb22
12 files changed, 141 insertions, 20 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/assets/stylesheets/wiki_syntax.css b/app/assets/stylesheets/wiki_syntax.css
index 41e780c75..89b117419 100644
--- a/app/assets/stylesheets/wiki_syntax.css
+++ b/app/assets/stylesheets/wiki_syntax.css
@@ -72,3 +72,14 @@ a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
.syntaxhl .s1 { background-color: #fff0f0 }
span.more_info { font-weight: normal; }
+
+.markdown-alert {
+ border-left: 4px solid;
+ padding-left: 10px;
+ margin-left: 10px;
+}
+.markdown-alert-title {
+ font-weight: bold;
+}
+.markdown-alert-note { border-color: #169; }
+.markdown-alert-note .markdown-alert-title { color: #1e40af; } \ No newline at end of file
diff --git a/app/assets/stylesheets/wiki_syntax_detailed.css b/app/assets/stylesheets/wiki_syntax_detailed.css
index e90279641..ad3c8c65f 100644
--- a/app/assets/stylesheets/wiki_syntax_detailed.css
+++ b/app/assets/stylesheets/wiki_syntax_detailed.css
@@ -63,3 +63,23 @@ table.list td { background-color: #f5f5f5; vertical-align: middle; padding: 0.3e
.syntaxhl .o { color: #333333 }
.syntaxhl .s2 { background-color: #fff0f0 }
.syntaxhl .si { background-color: #eeeeee }
+
+
+.markdown-alert {
+ border-left: 4px solid;
+ padding-left: 10px;
+ margin-left: 20px;
+}
+.markdown-alert-title {
+ font-weight: bold;
+}
+.markdown-alert-tip { border-color: #5db651; }
+.markdown-alert-tip .markdown-alert-title { color: #005f00; }
+.markdown-alert-important { border-color: #800080; }
+.markdown-alert-important .markdown-alert-title { color: #4b006e; }
+.markdown-alert-caution { border-color: #c22; }
+.markdown-alert-caution .markdown-alert-title { color: #880000; }
+.markdown-alert-warning { border-color: #e4bc4b; }
+.markdown-alert-warning .markdown-alert-title { color: #a7760c; }
+.markdown-alert-note { border-color: #169; }
+.markdown-alert-note .markdown-alert-title { color: #1e40af; } \ No newline at end of file
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/help/wiki_syntax/common_mark/en/wiki_syntax_common_mark.html.erb b/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_common_mark.html.erb
index 486b96424..a650b2751 100644
--- a/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_common_mark.html.erb
+++ b/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_common_mark.html.erb
@@ -81,6 +81,14 @@
<th></th><td>HTML is &lt;del&gt;not&lt;/del&gt; &lt;u&gt;allowed&lt;/u&gt;.</td><td>HTML is <del>not</del> <u>allowed</u>.</td>
</tr>
+<tr><th colspan="3">Alerts <span class="more_info">(<a href="<%= help_wiki_syntax_path(:detailed, anchor: "16") %>" target="_blank">more</a>)</span></th></tr>
+<tr><th></th><td>> [!NOTE]<br>> You can use alerts like [!NOTE], [!TIP], [!IMPORTANT], [!WARNING], and [!CAUTION].</td><td>
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>You can use alerts like [!NOTE], [!TIP], [!IMPORTANT], [!WARNING], and [!CAUTION].</p>
+</div>
+</td></tr>
+
</table>
<p><a href="<%= help_wiki_syntax_path(:detailed) %>" onclick="window.open('<%= help_wiki_syntax_path(:detailed) %>', '', ''); return false;">More Information</a></p>
diff --git a/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb b/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb
index 193606ab2..a74094460 100644
--- a/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb
+++ b/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb
@@ -27,6 +27,7 @@
<li><a href='#12'>Macros</a></li>
<li><a href='#13'>Code highlighting</a></li>
<li><a href='#15'>Raw HTML</a></li>
+ <li><a href='#16'>Alerts</a></li>
</ul>
<h2><a name="2" class="wiki-page"></a>Links</h2>
@@ -369,5 +370,52 @@ It can be expanded by clicking a link.
float
</code></pre>
+ <h2><a name="16" class="wiki-page"></a>Alerts</h2>
+
+ <p>
+ <dl>
+ <dt><code>NOTE</code></dt>
+ <dd>
+ <pre><code>> [!NOTE]<br>> Wiki page edits are preserved as history, allowing you to restore previous versions if needed.</code></pre>
+ <div class="markdown-alert markdown-alert-note">
+ <p class="markdown-alert-title">Note</p>
+ <p>Wiki page edits are preserved as history, allowing you to restore previous versions if needed.</p>
+ </div>
+ </dd>
+ <dt><code>TIP</code></dt>
+ <dd>
+ <pre><code>> [!TIP]<br>> To quickly review the update history of an issue, use the "History" tab for convenient access.</code></pre>
+ <div class="markdown-alert markdown-alert-tip">
+ <p class="markdown-alert-title">Tip</p>
+ <p>To quickly review the update history of an issue, use the "History" tab for convenient access.</p>
+ </div>
+ </dd>
+ <dt><code>WARNING</code></dt>
+ <dd>
+ <pre><code>> [!WARNING]<br>> Deleting an issue is a permanent action. Be certain it is truly necessary before proceeding.</code></pre>
+ <div class="markdown-alert markdown-alert-warning">
+ <p class="markdown-alert-title">Warning</p>
+ <p>Deleting an issue is a permanent action. Be certain it is truly necessary before proceeding.</p>
+ </div>
+ </dd>
+ <dt><code>IMPORTANT</code></dt>
+ <dd>
+ <pre><code>> [!IMPORTANT]<br>> Changing role permissions can affect user access across all projects, so be mindful of potential impacts.</code></pre>
+ <div class="markdown-alert markdown-alert-important">
+ <p class="markdown-alert-title">Important</p>
+ <p>Changing role permissions can affect user access across all projects, so be mindful of potential impacts.</p>
+ </div>
+ </dd>
+ <dt><code>CAUTION</code></dt>
+ <dd>
+ <pre><code>> [!CAUTION]<br>> When installing plugins, make sure to verify compatibility. Version differences can cause unexpected behavior.</code></pre>
+ <div class="markdown-alert markdown-alert-caution">
+ <p class="markdown-alert-title">Caution</p>
+ <p>When installing plugins, make sure to verify compatibility. Version differences can cause unexpected behavior.</p>
+ </div>
+ </dd>
+ </dl>
+ </p>
+
</body>
</html>
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