From d6b5b9436ca9f8a5fb905cc6c8282593cb29f0a0 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 5 Jan 2016 12:50:26 +0800 Subject: [PATCH] UX: Prioritize categories autocomplete with new rules. --- .../discourse/components/d-editor.js.es6 | 16 +------ .../discourse/models/category.js.es6 | 45 ++++++++++++++++++ test/javascripts/models/category-test.js.es6 | 47 +++++++++++++++++++ 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 6b02fad5ee4..631429f37e7 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -258,21 +258,7 @@ export default Ember.Component.extend({ return category.get('slug'); }, dataSource(term) { - const limit = 5; - const regexp = new RegExp(term, 'i'); - var count = 0; - var data = []; - - Category.listByActivity().some(category => { - if (category.get('name').match(regexp)) { - count++; - data.push(category); - } - - return count === limit; - }); - - return data; + return Category.search(term); }, triggerRule(textarea, opts) { const result = Discourse.Utilities.caretRowCol(textarea); diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index ed82eb9786d..7ad63c5a936 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -285,6 +285,51 @@ Category.reopenClass({ reloadById(id) { return Discourse.ajax(`/c/${id}/show.json`); + }, + + search(term, opts) { + var limit = 5; + + if (opts) { + if (opts.limit === 0) { + return []; + } else if (opts.limit) { + limit = opts.limit; + } + } + + const emptyTerm = (term === ""); + const categories = Category.listByActivity(); + const length = categories.length; + var i; + var count = 0; + var data = []; + + const done = () => { + return data.length === limit; + } + + for (i = 0; i < length && !done(); i++) { + const category = categories[i]; + if ((emptyTerm) || + (!emptyTerm && category.get('name').indexOf(term) === 0)) { + data.push(category); + } + } + + if (!done()) { + for (i = 0; i < length && !done(); i++) { + const category = categories[i]; + + if ((!emptyTerm && category.get('name').indexOf(term) > 0)) { + if (data.indexOf(category) === -1) data.push(category); + } + } + } + + return _.sortBy(data, (category) => { + return category.get('read_restricted'); + }); } }); diff --git a/test/javascripts/models/category-test.js.es6 b/test/javascripts/models/category-test.js.es6 index 48735f88e42..68e56da40d8 100644 --- a/test/javascripts/models/category-test.js.es6 +++ b/test/javascripts/models/category-test.js.es6 @@ -1,4 +1,5 @@ import createStore from 'helpers/create-store'; +import Category from 'discourse/models/category'; module("model:category"); @@ -50,6 +51,8 @@ test('findBySlug', function() { deepEqual(Discourse.Category.findBySlug('뉴스피드', '熱帶風暴畫眉'), newsFeed, 'we can find a category with CJK slug whose parent slug is also CJK'); deepEqual(Discourse.Category.findBySlug('时间', 'darth'), time, 'we can find a category with CJK slug whose parent slug is english'); deepEqual(Discourse.Category.findBySlug('bah', '熱帶風暴畫眉'), bah, 'we can find a category with english slug whose parent slug is CJK'); + + sandbox.restore(); }); test('findSingleBySlug', function() { @@ -122,3 +125,47 @@ test('postCountStats', function() { result = category5.get('postCountStats'); equal(result.length, 0, "should show nothing"); }); + +test('search', () => { + const result = (term, opts) => { + return Category.search(term, opts).map((category) => category.get('id')); + } + + const store = createStore(), + category1 = store.createRecord('category', { id: 1, name: 'middle term' }), + category2 = store.createRecord('category', { id: 2, name: 'middle term' }); + + sandbox.stub(Category, "listByActivity").returns([category1, category2]); + + deepEqual(result('term', { limit: 0 }), [], "returns an empty array when limit is 0"); + deepEqual(result(''), [category1.get('id'), category2.get('id')], "orders by activity if no term is matched"); + deepEqual(result('term'), [category1.get('id'), category2.get('id')], "orders by activity"); + + category2.set('name', 'term start'); + deepEqual(result('term'), [category2.get('id'), category1.get('id')], "orders matching begin with and then contains"); + + sandbox.restore(); + + const category3 = store.createRecord('category', { id: 3, name: 'term start', parent_category_id: category1.get('id') }), + category4 = store.createRecord('category', { id: 4, name: 'some term', read_restricted: true }); + + sandbox.stub(Category, "listByActivity").returns([category4, category1, category3, category2]); + + deepEqual(result(''), + [category1.get('id'), category3.get('id'), category2.get('id'), category4.get('id')], + "prioritize non read_restricted categories when term is blank"); + + deepEqual(result('', { limit: 3 }), + [category1.get('id'), category3.get('id'), category4.get('id')], + "prioritize non read_restricted categories when term is blank with limit"); + + deepEqual(result('term'), + [category3.get('id'), category2.get('id'), category1.get('id'), category4.get('id')], + "prioritize non read_restricted"); + + deepEqual(result('term', { limit: 3 }), + [category3.get('id'), category2.get('id'), category4.get('id')], + "prioritize non read_restricted with limit"); + + sandbox.restore(); +});