SECURITY: Query @usernames in bulk
Otherwise you could add many requests at once while composing.
This commit is contained in:
parent
9572b28986
commit
c58b495e15
|
@ -0,0 +1,56 @@
|
|||
function replaceSpan($e, username) {
|
||||
$e.replaceWith("<a href='" +
|
||||
Discourse.getURL("/users/") + username.toLowerCase() +
|
||||
"' class='mention'>@" + username + "</a>");
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
|
@ -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("<a href='" + Discourse.getURL("/users/") + username.toLowerCase() + "' class='mention'>@" + username + "</a>");
|
||||
} else {
|
||||
$elem.removeClass('mention-loading').addClass('mention-tested');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue