diff --git a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 b/app/assets/javascripts/discourse/lib/link-mentions.js.es6
new file mode 100644
index 00000000000..084564291f7
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/link-mentions.js.es6
@@ -0,0 +1,56 @@
+function replaceSpan($e, username) {
+ $e.replaceWith("@" + username + "");
+}
+
+const found = [];
+const checked = [];
+
+function updateFound($mentions, usernames) {
+ Ember.run.scheduleOnce('afterRender', function() {
+ $mentions.each((i, e) => {
+ const $e = $(e);
+ const username = usernames[i];
+ if (found.indexOf(username) !== -1) {
+ replaceSpan($e, username);
+ } else {
+ $e.removeClass('mention-loading').addClass('mention-tested');
+ }
+ });
+ });
+}
+
+
+let linking = false;
+export default function linkMentions($elem) {
+ if (linking) { return Ember.RSVP.Promise.resolve(); }
+ linking = true;
+
+ return new Ember.RSVP.Promise(function(resolve) {
+ const $mentions = $('span.mention:not(.mention-tested):not(.mention-loading)', $elem);
+ if ($mentions.length) {
+ const usernames = $mentions.map((_, e) => $(e).text().substr(1).toLowerCase());
+
+ if (usernames.length) {
+ $mentions.addClass('mention-loading');
+ const uncached = _.uniq(usernames).filter((u) => { return checked.indexOf(u) === -1; });
+
+ if (uncached.length) {
+ return Discourse.ajax("/users/is_local_username", {
+ data: { usernames: uncached}
+ }).then(function(r) {
+ found.push.apply(found, r.valid);
+ checked.push.apply(checked, uncached);
+ updateFound($mentions, usernames);
+ resolve();
+ });
+ } else {
+ updateFound($mentions, usernames);
+ }
+ }
+ }
+
+ resolve();
+ }).finally(() => { linking = false });
+}
diff --git a/app/assets/javascripts/discourse/lib/mention.js b/app/assets/javascripts/discourse/lib/mention.js
deleted file mode 100644
index 7668d7bbc93..00000000000
--- a/app/assets/javascripts/discourse/lib/mention.js
+++ /dev/null
@@ -1,59 +0,0 @@
-
-// A local cache lookup
-var localCache = [];
-
-
-/**
- Lookup a username and return whether it is exists or not.
-
- @function lookup
- @param {String} username to look up
- @return {Promise} promise that results to whether the name was found or not
-**/
-function lookup(username) {
- return new Em.RSVP.Promise(function (resolve) {
- var cached = localCache[username];
-
- // If we have a cached answer, return it
- if (typeof cached !== "undefined") {
- resolve(cached);
- } else {
- Discourse.ajax("/users/is_local_username", { data: { username: username } }).then(function(r) {
- localCache[username] = r.valid;
- resolve(r.valid);
- });
- }
- });
-}
-
-/**
- Help us link directly to a mentioned user's profile if the username exists.
-
- @class Mention
- @namespace Discourse
- @module Discourse
-**/
-Discourse.Mention = {
-
- /**
- Paints an element in the DOM with the appropriate classes and markup if the username
- it is mentioning exists.
-
- @method paint
- @param {Element} the element in the DOM to decorate
- **/
- paint: function(e) {
- var $elem = $(e);
- if ($elem.data('mention-tested')) return;
- var username = $elem.text().substr(1);
-
- $elem.addClass('mention-loading');
- lookup(username).then(function(found) {
- if (found) {
- $elem.replaceWith("@" + username + "");
- } else {
- $elem.removeClass('mention-loading').addClass('mention-tested');
- }
- });
- }
-};
diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6
index 6c6ab4ea65b..03b5c50eb60 100644
--- a/app/assets/javascripts/discourse/views/composer.js.es6
+++ b/app/assets/javascripts/discourse/views/composer.js.es6
@@ -3,6 +3,7 @@ import afterTransition from 'discourse/lib/after-transition';
import loadScript from 'discourse/lib/load-script';
import avatarTemplate from 'discourse/lib/avatar-template';
import positioningWorkaround from 'discourse/lib/safari-hacks';
+import linkMentions from 'discourse/lib/link-mentions';
const ComposerView = Discourse.View.extend(Ember.Evented, {
_lastKeyTimeout: null,
@@ -175,11 +176,10 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
$('a.onebox', $wmdPreview).each(function(i, e) {
Discourse.Onebox.load(e, refresh);
});
- $('span.mention', $wmdPreview).each(function(i, e) {
- Discourse.Mention.paint(e);
- });
- this.trigger('previewRefreshed', $wmdPreview);
+ linkMentions($wmdPreview).then(() => {
+ this.trigger('previewRefreshed', $wmdPreview);
+ });
},
_applyEmojiAutocomplete() {
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js
index cf5bbda7b19..9a3b8c092d3 100644
--- a/app/assets/javascripts/main_include.js
+++ b/app/assets/javascripts/main_include.js
@@ -48,6 +48,7 @@
//= require ./discourse/components/dropdown-button
//= require ./discourse/components/notifications-button
//= require ./discourse/components/topic-notifications-button
+//= require ./discourse/lib/link-mentions
//= require ./discourse/views/composer
//= require ./discourse/lib/show-modal
//= require ./discourse/routes/discourse
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 13bf9b2821c..3a10834b85c 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -175,10 +175,12 @@ class UsersController < ApplicationController
end
def is_local_username
- params.require(:username)
- u = params[:username].downcase
- r = User.exec_sql('select 1 from users where username_lower = ?', u).values
- render json: {valid: r.length == 1}
+ users = params[:usernames]
+ users = [params[:username]] if users.blank?
+ users.each(&:downcase!)
+
+ result = User.where(username_lower: users).pluck(:username_lower)
+ render json: {valid: result}
end
def render_available_true