mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 17:46:05 -05:00
ES6: user-search lib and autocomplete. Cancels many promises rather than
leaving them as pending forever.
This commit is contained in:
parent
d7f28baf77
commit
3b76fd82fd
7 changed files with 119 additions and 126 deletions
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
Default settings for bootbox
|
|
||||||
**/
|
|
||||||
export default {
|
|
||||||
name: "bootbox",
|
|
||||||
initialize: function() {
|
|
||||||
bootbox.animate(false);
|
|
||||||
|
|
||||||
// clicking outside a bootbox modal closes it
|
|
||||||
bootbox.backdrop(true);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import autocomplete from 'discourse/lib/autocomplete';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "jquery-plugins",
|
||||||
|
initialize: function() {
|
||||||
|
|
||||||
|
// Settings for bootbox
|
||||||
|
bootbox.animate(false);
|
||||||
|
bootbox.backdrop(true);
|
||||||
|
|
||||||
|
// Initialize the autocomplete tool
|
||||||
|
$.fn.autocomplete = autocomplete;
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,6 +4,8 @@
|
||||||
@module $.fn.autocomplete
|
@module $.fn.autocomplete
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
export var CANCELLED_STATUS = "__CANCELLED";
|
||||||
|
|
||||||
var shiftMap = [];
|
var shiftMap = [];
|
||||||
shiftMap[192] = "~";
|
shiftMap[192] = "~";
|
||||||
shiftMap[49] = "!";
|
shiftMap[49] = "!";
|
||||||
|
@ -43,7 +45,7 @@ function mapKeyPressToActualCharacter(isShiftKey, characterCode) {
|
||||||
return stringValue;
|
return stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.fn.autocomplete = function(options) {
|
export default function(options) {
|
||||||
var autocompletePlugin = this;
|
var autocompletePlugin = this;
|
||||||
|
|
||||||
if (this.length === 0) return;
|
if (this.length === 0) return;
|
||||||
|
@ -239,6 +241,12 @@ $.fn.autocomplete = function(options) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow an update method to cancel. This allows us to debounce
|
||||||
|
// promises without leaking
|
||||||
|
if (r === CANCELLED_STATUS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
autocompleteOptions = r;
|
autocompleteOptions = r;
|
||||||
if (!r || r.length === 0) {
|
if (!r || r.length === 0) {
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
|
@ -409,4 +417,4 @@ $.fn.autocomplete = function(options) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
91
app/assets/javascripts/discourse/lib/user-search.js.es6
Normal file
91
app/assets/javascripts/discourse/lib/user-search.js.es6
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import { CANCELLED_STATUS } from 'discourse/lib/autocomplete';
|
||||||
|
|
||||||
|
var cache = {},
|
||||||
|
cacheTopicId,
|
||||||
|
cacheTime,
|
||||||
|
currentTerm;
|
||||||
|
|
||||||
|
function performSearch(term, topicId, includeGroups, resultsFn) {
|
||||||
|
var cached = cache[term];
|
||||||
|
if (cached) {
|
||||||
|
resultsFn(cached);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Discourse.ajax('/users/search/users', {
|
||||||
|
data: { term: term,
|
||||||
|
topic_id: topicId,
|
||||||
|
include_groups: includeGroups }
|
||||||
|
}).then(function (r) {
|
||||||
|
cache[term] = r;
|
||||||
|
cacheTime = new Date();
|
||||||
|
|
||||||
|
// If there is a newer search term, return null
|
||||||
|
if (term !== currentTerm) { r = CANCELLED_STATUS; }
|
||||||
|
resultsFn(r);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var debouncedSearch = _.debounce(performSearch, 300);
|
||||||
|
|
||||||
|
function organizeResults(r, options) {
|
||||||
|
if (r === CANCELLED_STATUS) { return r; }
|
||||||
|
|
||||||
|
var exclude = options.exclude || [],
|
||||||
|
limit = options.limit || 5,
|
||||||
|
users = [],
|
||||||
|
groups = [],
|
||||||
|
results = [];
|
||||||
|
|
||||||
|
r.users.every(function(u) {
|
||||||
|
if (exclude.indexOf(u.username) === -1) {
|
||||||
|
users.push(u);
|
||||||
|
results.push(u);
|
||||||
|
}
|
||||||
|
return results.length <= limit;
|
||||||
|
});
|
||||||
|
|
||||||
|
r.groups.every(function(g) {
|
||||||
|
if (results.length > limit) return false;
|
||||||
|
if (exclude.indexOf(g.name) === -1) {
|
||||||
|
groups.push(g);
|
||||||
|
results.push(g);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
results.users = users;
|
||||||
|
results.groups = groups;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function userSearch(options) {
|
||||||
|
var term = options.term || "",
|
||||||
|
includeGroups = !!options.include_groups,
|
||||||
|
topicId = options.topicId;
|
||||||
|
|
||||||
|
currentTerm = term;
|
||||||
|
|
||||||
|
return new Ember.RSVP.Promise(function(resolve) {
|
||||||
|
// TODO site setting for allowed regex in username
|
||||||
|
if (term.match(/[^a-zA-Z0-9_\.]/)) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (((new Date() - cacheTime) > 30000) || (cacheTopicId !== topicId)) {
|
||||||
|
cache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheTopicId = topicId;
|
||||||
|
var executed = debouncedSearch(term, topicId, includeGroups, function(r) {
|
||||||
|
resolve(organizeResults(r, options));
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: This doesn't cancel all debounced promises, we should figure out
|
||||||
|
// a way to handle that.
|
||||||
|
if (!executed) {
|
||||||
|
resolve(CANCELLED_STATUS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,103 +0,0 @@
|
||||||
/**
|
|
||||||
Helper for searching for Users
|
|
||||||
|
|
||||||
@class UserSearch
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
var cache = {};
|
|
||||||
var cacheTopicId = null;
|
|
||||||
var cacheTime = null;
|
|
||||||
|
|
||||||
var currentTerm;
|
|
||||||
|
|
||||||
var debouncedSearch = _.debounce(function(term, topicId, include_groups, resultsFn) {
|
|
||||||
|
|
||||||
Discourse.ajax('/users/search/users', {
|
|
||||||
data: {
|
|
||||||
term: term,
|
|
||||||
topic_id: topicId,
|
|
||||||
include_groups: include_groups
|
|
||||||
}
|
|
||||||
}).then(function (r) {
|
|
||||||
|
|
||||||
cache[term] = r;
|
|
||||||
cacheTime = new Date();
|
|
||||||
|
|
||||||
if(term === currentTerm){
|
|
||||||
resultsFn(r);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
Discourse.UserSearch = {
|
|
||||||
|
|
||||||
search: function(options) {
|
|
||||||
var term = options.term || "";
|
|
||||||
currentTerm = term;
|
|
||||||
|
|
||||||
var include_groups = options.include_groups || false;
|
|
||||||
var exclude = options.exclude || [];
|
|
||||||
var topicId = options.topicId;
|
|
||||||
var limit = options.limit || 5;
|
|
||||||
|
|
||||||
var promise = Ember.Deferred.create();
|
|
||||||
|
|
||||||
// TODO site setting for allowed regex in username
|
|
||||||
if (term.match(/[^a-zA-Z0-9_\.]/)) {
|
|
||||||
promise.resolve([]);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
if ((new Date() - cacheTime) > 30000) {
|
|
||||||
cache = {};
|
|
||||||
}
|
|
||||||
if (cacheTopicId !== topicId) {
|
|
||||||
cache = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheTopicId = topicId;
|
|
||||||
|
|
||||||
var organizeResults = function(r) {
|
|
||||||
var users = [], groups = [], results = [];
|
|
||||||
_.each(r.users,function(u) {
|
|
||||||
if (exclude.indexOf(u.username) === -1) {
|
|
||||||
users.push(u);
|
|
||||||
results.push(u);
|
|
||||||
}
|
|
||||||
return results.length <= limit;
|
|
||||||
});
|
|
||||||
|
|
||||||
_.each(r.groups,function(g) {
|
|
||||||
if (results.length > limit) return false;
|
|
||||||
if (exclude.indexOf(g.name) === -1) {
|
|
||||||
groups.push(g);
|
|
||||||
results.push(g);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
results.users = users;
|
|
||||||
results.groups = groups;
|
|
||||||
|
|
||||||
promise.resolve(results);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cache[term]) {
|
|
||||||
// inject a delay to avoid too much repainting
|
|
||||||
setTimeout(function(){
|
|
||||||
if(term !== currentTerm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
organizeResults(cache[term]);
|
|
||||||
}, 300);
|
|
||||||
} else {
|
|
||||||
debouncedSearch(term, topicId, include_groups, organizeResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
/*global assetPath:true */
|
/*global assetPath:true */
|
||||||
|
|
||||||
/**
|
import userSearch from 'discourse/lib/user-search';
|
||||||
This view handles rendering of the composer
|
|
||||||
|
|
||||||
@class ComposerView
|
|
||||||
@extends Discourse.View
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
var ComposerView = Discourse.View.extend(Ember.Evented, {
|
var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
templateName: 'composer',
|
templateName: 'composer',
|
||||||
elementId: 'reply-control',
|
elementId: 'reply-control',
|
||||||
|
@ -183,7 +177,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
$wmdInput.autocomplete({
|
$wmdInput.autocomplete({
|
||||||
template: template,
|
template: template,
|
||||||
dataSource: function(term) {
|
dataSource: function(term) {
|
||||||
return Discourse.UserSearch.search({
|
return userSearch({
|
||||||
term: term,
|
term: term,
|
||||||
topicId: self.get('controller.controllers.topic.model.id'),
|
topicId: self.get('controller.controllers.topic.model.id'),
|
||||||
include_groups: true
|
include_groups: true
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import TextField from 'discourse/components/text-field';
|
import TextField from 'discourse/components/text-field';
|
||||||
|
import userSearch from 'discourse/lib/user-search';
|
||||||
|
|
||||||
var compiled;
|
var compiled;
|
||||||
function templateFunction() {
|
function templateFunction() {
|
||||||
|
@ -61,7 +62,7 @@ var UserSelector = TextField.extend({
|
||||||
allowAny: this.get('allowAny'),
|
allowAny: this.get('allowAny'),
|
||||||
|
|
||||||
dataSource: function(term) {
|
dataSource: function(term) {
|
||||||
return Discourse.UserSearch.search({
|
return userSearch({
|
||||||
term: term,
|
term: term,
|
||||||
topicId: userSelectorView.get('topicId'),
|
topicId: userSelectorView.get('topicId'),
|
||||||
exclude: excludedUsernames(),
|
exclude: excludedUsernames(),
|
||||||
|
|
Loading…
Reference in a new issue