FIX: Do not replace in mentions and hashtags (#14260)

FIX: Do not replace in mentions and hashtags (#14260)

Watched words of type ‘replace’ or ‘link’ replaced the text inside mentions or hashtags too, which broke these. These types of watched words must skip any match that has an @ or # before it.

diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/watched-words.js b/app/assets/javascripts/pretty-text/engines/discourse-markdown/watched-words.js
index cce3925..ec7f26f 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/watched-words.js
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/watched-words.js
@@ -119,6 +119,14 @@ export function setup(helper) {
                 continue;
               }
 
+              if (
+                matches[ln].index > 0 &&
+                (text[matches[ln].index - 1] === "@" ||
+                  text[matches[ln].index - 1] === "#")
+              ) {
+                continue;
+              }
+
               if (matches[ln].index > lastPos) {
                 token = new state.Token("text", "", 0);
                 token.content = text.slice(lastPos, matches[ln].index);
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index cf9a214..7acb5e0 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -1465,6 +1465,20 @@ HTML
       expect(PrettyText.cook("f.o")).to match_html("<p>test</p>")
     end
 
+    it "does not replace hashtags and mentions" do
+      Fabricate(:user, username: "test")
+      category = Fabricate(:category, slug: "test")
+      Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "test", replacement: "discourse")
+
+      expect(PrettyText.cook("@test #test test")).to match_html(<<~HTML)
+        <p>
+          <a class="mention" href="/u/test">@test</a>
+          <a class="hashtag" href="/c/test/#{category.id}">#<span>test</span></a>
+          discourse
+        </p>
+      HTML
+    end
+
     it "supports overlapping words" do
       Fabricate(:watched_word, action: WatchedWord.actions[:link], word: "meta", replacement: "https://meta.discourse.org")
       Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "iz", replacement: "is")

GitHub sha: 0532a5a43e85cbd2834c19cd749d6db1223ba20e

This commit appears in #14260 which was approved by ZogStriP and tgxworld. It was merged by nbianca.