diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 index 97cca511783..d35500b9912 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 @@ -1,32 +1,12 @@ function addMention(buffer, matches, state) { let username = matches[1] || matches[2]; - let { getURL, mentionLookup, formatUsername } = state.md.options.discourse; - - let type = mentionLookup && mentionLookup(username); - - let tag = "a"; + let tag = "span"; let className = "mention"; - let href = null; - - if (type === "user") { - href = getURL("/u/") + username.toLowerCase(); - } else if (type === "group") { - href = getURL("/groups/") + username; - className = "mention-group"; - } else { - tag = "span"; - } let token = new state.Token("mention_open", tag, 1); token.attrs = [["class", className]]; - if (href) { - token.attrs.push(["href", href]); - } buffer.push(token); - if (formatUsername) { - username = formatUsername(username); - } token = new state.Token("text", "", 0); token.content = "@" + username; diff --git a/app/assets/javascripts/pretty-text/pretty-text.js.es6 b/app/assets/javascripts/pretty-text/pretty-text.js.es6 index 7050e38aa17..410a47e5a18 100644 --- a/app/assets/javascripts/pretty-text/pretty-text.js.es6 +++ b/app/assets/javascripts/pretty-text/pretty-text.js.es6 @@ -31,7 +31,6 @@ export function buildOptions(state) { previewing, linkify, censoredWords, - mentionLookup, invalidateOneboxes } = state; @@ -67,7 +66,6 @@ export function buildOptions(state) { lookupAvatarByPostNumber, lookupPrimaryUserGroupByPostNumber, formatUsername, - mentionLookup, emojiUnicodeReplacer, lookupInlineOnebox, lookupImageUrls, diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index f43dcdbd934..d30a4f84ac5 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -156,7 +156,6 @@ module PrettyText __optInput.formatUsername = __formatUsername; __optInput.getTopicInfo = __getTopicInfo; __optInput.categoryHashtagLookup = __categoryLookup; - __optInput.mentionLookup = __mentionLookup; __optInput.customEmoji = #{custom_emoji.to_json}; __optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer; __optInput.lookupInlineOnebox = __lookupInlineOnebox; @@ -265,6 +264,8 @@ module PrettyText add_s3_cdn(doc) end + add_mentions(doc) if SiteSetting.enable_mentions + doc.to_html end @@ -419,4 +420,67 @@ module PrettyText end end + private + + USER_TYPE ||= 'user' + GROUP_TYPE ||= 'group' + + def self.add_mentions(doc) + elements = doc.css("span.mention") + names = elements.map { |element| element.text[1..-1] } + + mentions = lookup_mentions(names) + + doc.css("span.mention").each do |element| + name = element.text[1..-1] + name.downcase! + + if type = mentions[name] + element.name = 'a' + + element.children = PrettyText::Helpers.format_username( + element.children.text + ) + + case type + when USER_TYPE + element['href'] = "/u/#{name}" + when GROUP_TYPE + element['class'] = 'mention-group' + element['href'] = "/groups/#{name}" + end + end + end + end + + def self.lookup_mentions(names) + sql = <<~SQL + ( + SELECT + :user_type AS type, + username_lower AS name + FROM users + WHERE username_lower IN (:names) + ) + UNION + ( + SELECT + :group_type AS type, + name + FROM groups + WHERE name IN (:names) + ) + SQL + + results = DB.query(sql, + names: names, + user_type: USER_TYPE, + group_type: GROUP_TYPE + ) + + mentions = {} + results.each { |result| mentions[result.name] = result.type } + mentions + end + end diff --git a/lib/pretty_text/helpers.rb b/lib/pretty_text/helpers.rb index 54b33a27ea1..05df0cc18c8 100644 --- a/lib/pretty_text/helpers.rb +++ b/lib/pretty_text/helpers.rb @@ -39,12 +39,6 @@ module PrettyText username end - def mention_lookup(name) - return false if name.blank? - return "user" if User.exists?(username_lower: name.downcase) - return "group" if Group.exists?(name: name) - end - def category_hashtag_lookup(category_slug) if category = Category.query_from_hashtag_slug(category_slug) [category.url_with_id, category_slug] diff --git a/lib/pretty_text/shims.js b/lib/pretty_text/shims.js index 680177914c9..b2ce2cd7469 100644 --- a/lib/pretty_text/shims.js +++ b/lib/pretty_text/shims.js @@ -1,20 +1,26 @@ -__PrettyText = require('pretty-text/pretty-text').default; -__buildOptions = require('pretty-text/pretty-text').buildOptions; -__performEmojiUnescape = require('pretty-text/emoji').performEmojiUnescape; +__PrettyText = require("pretty-text/pretty-text").default; +__buildOptions = require("pretty-text/pretty-text").buildOptions; +__performEmojiUnescape = require("pretty-text/emoji").performEmojiUnescape; -__utils = require('discourse/lib/utilities'); +__utils = require("discourse/lib/utilities"); __emojiUnicodeReplacer = null; __setUnicode = function(replacements) { - let unicodeRegexp = new RegExp(Object.keys(replacements).sort().reverse().join("|"), "g"); + let unicodeRegexp = new RegExp( + Object.keys(replacements) + .sort() + .reverse() + .join("|"), + "g" + ); __emojiUnicodeReplacer = function(text) { unicodeRegexp.lastIndex = 0; let m; while ((m = unicodeRegexp.exec(text)) !== null) { let replacement = ":" + replacements[m[0]] + ":"; - const before = text.charAt(m.index-1); + const before = text.charAt(m.index - 1); if (!/\B/.test(before)) { replacement = "\u200b" + replacement; } @@ -23,7 +29,7 @@ __setUnicode = function(replacements) { // fixes Safari VARIATION SELECTOR-16 issue with some emojis // https://meta.discourse.org/t/emojis-selected-on-ios-displaying-additional-rectangles/86132 - text = text.replace(/\ufe0f/g, ''); + text = text.replace(/\ufe0f/g, ""); return text; }; @@ -35,9 +41,13 @@ function __getURLNoCDN(url) { if (!url) return url; // if it's a non relative URL, return it. - if (url !== '/' && !/^\/[^\/]/.test(url)) { return url; } + if (url !== "/" && !/^\/[^\/]/.test(url)) { + return url; + } - if (url.indexOf(__paths.baseUri) !== -1) { return url; } + if (url.indexOf(__paths.baseUri) !== -1) { + return url; + } if (url[0] !== "/") url = "/" + url; return __paths.baseUri + url; @@ -76,12 +86,11 @@ function __categoryLookup(c) { return __helpers.category_tag_hashtag_lookup(c); } -function __mentionLookup(u) { - return __helpers.mention_lookup(u); -} - function __lookupAvatar(p) { - return __utils.avatarImg({size: "tiny", avatarTemplate: __helpers.avatar_template(p) }, __getURL); + return __utils.avatarImg( + { size: "tiny", avatarTemplate: __helpers.avatar_template(p) }, + __getURL + ); } function __formatUsername(username) { @@ -97,5 +106,7 @@ function __getCurrentUser(userId) { } I18n = { - t: function(a,b) { return __helpers.t(a,b); } + t: function(a, b) { + return __helpers.t(a, b); + } }; diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index adb4bec8fda..b3de8d0a210 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -220,18 +220,37 @@ describe PrettyText do expect(PrettyText.cook("hi\n@.s.s")).to eq("

hi
\n@.s.s

") end - it "can handle mention with hyperlinks" do - Fabricate(:user, username: "sam") - expect(PrettyText.cook("hi @sam! hi")).to match_html '

hi @sam! hi

' - expect(PrettyText.cook("hi\n@sam.")).to eq("

hi
\n@sam.

") - end + it "handles user and group mentions correctly" do + ['user', 'user2'].each do |username | + Fabricate(:user, username: username) + end - it "can handle group mention" do group = Fabricate(:group) - expect(PrettyText.cook("hi @#{group.name}! hi")).to match_html( - %Q{

hi @#{group.name}! hi

} - ) + [ + [ + 'hi @user! @user2 hi', + '

hi @user! @user2 hi

' + ], + [ + "hi\n@user. @#{group.name} @somemention", + %Q|

hi
\n@user. @#{group.name} @somemention

| + ] + ].each do |input, expected| + expect(PrettyText.cook(input)).to eq(expected) + end + end + + describe 'when mentions are disabled' do + before do + SiteSetting.enable_mentions = false + end + + it 'should not convert mentions to links' do + user = Fabricate(:user) + + expect(PrettyText.cook('hi @user')).to eq('

hi @user

') + end end it "can handle mentions inside a hyperlink" do diff --git a/test/javascripts/lib/pretty-text-test.js.es6 b/test/javascripts/lib/pretty-text-test.js.es6 index 5c4846a6f5e..f929945de14 100644 --- a/test/javascripts/lib/pretty-text-test.js.es6 +++ b/test/javascripts/lib/pretty-text-test.js.es6 @@ -435,16 +435,9 @@ QUnit.test("Quotes", assert => { }); QUnit.test("Mentions", assert => { - const alwaysTrue = { - mentionLookup: function() { - return "user"; - } - }; - - assert.cookedOptions( + assert.cooked( "Hello @sam", - alwaysTrue, - '

Hello @sam

', + '

Hello @sam

', "translates mentions to links" ); @@ -454,9 +447,8 @@ QUnit.test("Mentions", assert => { "it doesn't do mentions within links" ); - assert.cookedOptions( + assert.cooked( "[@codinghorror](https://twitter.com/codinghorror)", - alwaysTrue, '

@codinghorror

', "it doesn't do link mentions within links" ); @@ -557,17 +549,9 @@ QUnit.test("Mentions", assert => { "handles mentions separated by a slash." ); - assert.cookedOptions( - "@eviltrout", - alwaysTrue, - '

@eviltrout

', - "it doesn't onebox mentions" - ); - - assert.cookedOptions( + assert.cooked( "a @sam c", - alwaysTrue, - '

a @sam c

', + '

a @sam c

', "it allows mentions within HTML tags" ); });