FEATURE: Mention @here to notify users in topic (#14900)

FEATURE: Mention @here to notify users in topic (#14900)

Use @here to mention all users that were allowed to topic directly or through group, who liked topics or read the topic. Only first 10 users will be notified.

diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index 317bdd9..a32f47e 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -561,10 +561,11 @@ export default Component.extend(ComposerUpload, {
   _renderUnseenMentions(preview, unseen) {
     // 'Create a New Topic' scenario is not supported (per conversation with codinghorror)
     // https://meta.discourse.org/t/taking-another-1-7-release-task/51986/7
-    fetchUnseenMentions(unseen, this.get("composer.topic.id")).then(() => {
+    fetchUnseenMentions(unseen, this.get("composer.topic.id")).then((r) => {
       linkSeenMentions(preview, this.siteSettings);
       this._warnMentionedGroups(preview);
       this._warnCannotSeeMention(preview);
+      this._warnHereMention(r.here_count);
     });
   },
 
@@ -639,6 +640,20 @@ export default Component.extend(ComposerUpload, {
     });
   },
 
+  _warnHereMention(hereCount) {
+    if (!hereCount || hereCount === 0) {
+      return;
+    }
+
+    later(
+      this,
+      () => {
+        this.hereMention(hereCount);
+      },
+      2000
+    );
+  },
+
   @bind
   _handleImageScaleButtonClick(event) {
     if (!event.target.classList.contains("scale-btn")) {
diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js
index 47a8fc8..016ed8d 100644
--- a/app/assets/javascripts/discourse/app/controllers/composer.js
+++ b/app/assets/javascripts/discourse/app/controllers/composer.js
@@ -719,6 +719,17 @@ export default Controller.extend({
       });
     },
 
+    hereMention(count) {
+      this.appEvents.trigger("composer-messages:create", {
+        extraClass: "custom-body",
+        templateName: "custom-body",
+        body: I18n.t("composer.here_mention", {
+          here: this.siteSettings.here_mention,
+          count,
+        }),
+      });
+    },
+
     applyUnorderedList() {
       this.toolbarEvent.applyList("* ", "list_item");
     },
diff --git a/app/assets/javascripts/discourse/app/templates/composer.hbs b/app/assets/javascripts/discourse/app/templates/composer.hbs
index 4d6a4c3..26f8f21 100644
--- a/app/assets/javascripts/discourse/app/templates/composer.hbs
+++ b/app/assets/javascripts/discourse/app/templates/composer.hbs
@@ -129,6 +129,7 @@
           uploadProgress=uploadProgress
           groupsMentioned=(action "groupsMentioned")
           cannotSeeMention=(action "cannotSeeMention")
+          hereMention=(action "hereMention")
           importQuote=(action "importQuote")
           togglePreview=(action "togglePreview")
           processPreview=showPreview
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 6cf2549..b4919f5 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -494,10 +494,19 @@ class UsersController < ApplicationController
     usernames.each(&:downcase!)
 
     cannot_see = []
+    here_count = nil
+
     topic_id = params[:topic_id]
-    unless topic_id.blank?
-      topic = Topic.find_by(id: topic_id)
-      usernames.each { |username| cannot_see.push(username) unless Guardian.new(User.find_by_username(username)).can_see?(topic) }
+    if topic_id.present? && topic = Topic.find_by(id: topic_id)
+      usernames.each do |username|
+        if !Guardian.new(User.find_by_username(username)).can_see?(topic)
+          cannot_see.push(username)
+        end
+      end
+
+      if usernames.include?(SiteSetting.here_mention) && guardian.can_mention_here?
+        here_count = PostAlerter.new.expand_here_mention(topic.first_post, exclude_ids: [current_user.id]).size
+      end
     end
 
     result = User.where(staged: false)
@@ -509,6 +518,7 @@ class UsersController < ApplicationController
       valid_groups: groups,
       mentionable_groups: mentionable_groups,
       cannot_see: cannot_see,
+      here_count: here_count,
       max_users_notified_per_group_mention: SiteSetting.max_users_notified_per_group_mention
     }
   end
diff --git a/app/models/user.rb b/app/models/user.rb
index 3bc2adb..40c48bd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -285,6 +285,8 @@ class User < ActiveRecord::Base
   def self.reserved_username?(username)
     username = normalize_username(username)
 
+    return true if SiteSetting.here_mention == username
+
     SiteSetting.reserved_usernames.unicode_normalize.split("|").any? do |reserved|
       username.match?(/^#{Regexp.escape(reserved).gsub('\*', '.*')}$/)
     end
diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb
index 1847fa4..31efae4 100644
--- a/app/services/post_alerter.rb
+++ b/app/services/post_alerter.rb
@@ -111,9 +111,9 @@ class PostAlerter
     notified = [post.user, post.last_editor].uniq
 
     # mentions (users/groups)
-    mentioned_groups, mentioned_users = extract_mentions(post)
+    mentioned_groups, mentioned_users, mentioned_here = extract_mentions(post)
 
-    if mentioned_groups || mentioned_users
+    if mentioned_groups || mentioned_users || mentioned_here
       mentioned_opts = {}
       editor = post.last_editor
 
@@ -131,6 +131,12 @@ class PostAlerter
         users = only_allowed_users(users, post)
         notified += notify_users(users - notified, :group_mentioned, post, mentioned_opts.merge(group: group))
       end
+
+      if mentioned_here
+        users = expand_here_mention(post, exclude_ids: notified.map(&:id))
+        users = only_allowed_users(users, post)
+        notified += notify_users(users - notified, :mentioned, post, mentioned_opts)
+      end
     end
 
     # replies
@@ -543,6 +549,21 @@ class PostAlerter
 
   end
 
+  def expand_here_mention(post, exclude_ids: nil)
+    posts = Post.where(topic_id: post.topic_id)
+    posts = posts.where.not(user_id: exclude_ids) if exclude_ids.present?
+
+    if post.user.staff?
+      posts = posts.where(post_type: [Post.types[:regular], Post.types[:whisper]])
+    else
+      posts = posts.where(post_type: Post.types[:regular])
+    end
+
+    User.real
+      .where(id: posts.select(:user_id))
+      .limit(SiteSetting.max_here_mentioned)
+  end
+
   # TODO: Move to post-analyzer?
   def extract_mentions(post)
     mentions = post.raw_mentions
@@ -557,7 +578,10 @@ class PostAlerter
       users = nil if users.empty?
     end
 
-    [groups, users]
+    # @here can be a user mention and then this feature is disabled
+    here = mentions.include?(SiteSetting.here_mention) && Guardian.new(post.user).can_mention_here?
+
+    [groups, users, here]
   end
 
   # TODO: Move to post-analyzer?
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 5546027..30d77f9 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2105,6 +2105,9 @@ en:
       cannot_see_mention:
         category: "You mentioned %{username} but they won't be notified because they do not have access to this category. You will need to add them to a group that has access to this category."
         private: "You mentioned %{username} but they won't be notified because they are unable to see this personal message. You will need to invite them to this PM."
+      here_mention:
+        one: "By mentioning <b>@%{here}</b>, you are about to notify %{count} user – are you sure?"
+        other: "By mentioning <b>@%{here}</b>, you are about to notify %{count} users – are you sure?"
       duplicate_link: "It looks like your link to <b>%{domain}</b> was already posted in the topic by <b>@%{username}</b> in <a href='%{post_url}'>a reply on %{ago}</a> – are you sure you want to post it again?"
       reference_topic_title: "RE: %{title}"
 
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 209a271..191dcf3 100644
--- a/config/locales/server.en.yml

[... diff too long, it was truncated ...]

GitHub sha: 73760c77d9fa004fce499251cbc1c98e21891957

This commit appears in #14900 which was approved by tgxworld. It was merged by nbianca.