Refactor full page search for style, remove lookups

This commit is contained in:
Robin Ward
2016-08-09 13:22:14 -04:00
parent d8808aa9ab
commit b2134aa173
6 changed files with 85 additions and 77 deletions

View File

@ -4,6 +4,7 @@ import showModal from 'discourse/lib/show-modal';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import Category from 'discourse/models/category'; import Category from 'discourse/models/category';
import { escapeExpression } from 'discourse/lib/utilities'; import { escapeExpression } from 'discourse/lib/utilities';
import { setTransient } from 'discourse/lib/page-tracker';
const SortOrders = [ const SortOrders = [
{name: I18n.t('search.relevance'), id: 0}, {name: I18n.t('search.relevance'), id: 0},
@ -14,6 +15,7 @@ const SortOrders = [
export default Ember.Controller.extend({ export default Ember.Controller.extend({
needs: ["application"], needs: ["application"],
bulkSelectEnabled: null,
loading: Em.computed.not("model"), loading: Em.computed.not("model"),
queryParams: ["q", "context_id", "context", "skip_context"], queryParams: ["q", "context_id", "context", "skip_context"],
@ -26,10 +28,15 @@ export default Ember.Controller.extend({
sortOrders: SortOrders, sortOrders: SortOrders,
@computed('model.posts') @computed('model.posts')
resultCount(posts){ resultCount(posts) {
return posts && posts.length; return posts && posts.length;
}, },
@computed('resultCount')
hasResults(resultCount) {
return (resultCount || 0) > 0;
},
@computed('q') @computed('q')
hasAutofocus(q) { hasAutofocus(q) {
return Em.isEmpty(q); return Em.isEmpty(q);
@ -85,7 +92,7 @@ export default Ember.Controller.extend({
setSearchTerm(term) { setSearchTerm(term) {
this._searchOnSortChange = false; this._searchOnSortChange = false;
if (term) { if (term) {
SortOrders.forEach((order) => { SortOrders.forEach(order => {
if (term.indexOf(order.term) > -1){ if (term.indexOf(order.term) > -1){
this.set('sortOrder', order.id); this.set('sortOrder', order.id);
term = term.replace(order.term, ""); term = term.replace(order.term, "");
@ -98,7 +105,7 @@ export default Ember.Controller.extend({
}, },
@observes('sortOrder') @observes('sortOrder')
triggerSearch(){ triggerSearch() {
if (this._searchOnSortChange) { if (this._searchOnSortChange) {
this.search(); this.search();
} }
@ -130,13 +137,22 @@ export default Ember.Controller.extend({
this.set("controllers.application.showFooter", !this.get("loading")); this.set("controllers.application.showFooter", !this.get("loading"));
}, },
canBulkSelect: Em.computed.alias('currentUser.staff'), @computed('hasResults')
canBulkSelect(hasResults) {
return this.currentUser && this.currentUser.staff && hasResults;
},
@computed
canCreateTopic() {
return this.currentUser && !this.site.mobileView;
},
search(){ search(){
if (this.get("searching")) return; if (this.get("searching")) return;
this.set("searching", true); this.set("searching", true);
const router = Discourse.__container__.lookup('router:main'); this.set('bulkSelectEnabled', false);
this.get('selected').clear();
var args = { q: this.get("searchTerm") }; var args = { q: this.get("searchTerm") };
@ -160,9 +176,9 @@ export default Ember.Controller.extend({
ajax("/search", { data: args }).then(results => { ajax("/search", { data: args }).then(results => {
const model = translateResults(results) || {}; const model = translateResults(results) || {};
router.transientCache('lastSearch', { searchKey, model }, 5); setTransient('lastSearch', { searchKey, model }, 5);
this.set("model", model); this.set("model", model);
}).finally(() => { this.set("searching",false); }); }).finally(() => this.set("searching", false));
}, },
actions: { actions: {
@ -188,16 +204,12 @@ export default Ember.Controller.extend({
}, },
refresh() { refresh() {
this.set('bulkSelectEnabled', false);
this.get('selected').clear();
this.search(); this.search();
}, },
showSearchHelp() { showSearchHelp() {
// TODO: dupe code should be centralized // TODO: dupe code should be centralized
ajax("/static/search_help.html", { dataType: 'html' }).then((model) => { ajax("/static/search_help.html", { dataType: 'html' }).then(model => showModal('searchHelp', { model }));
showModal('searchHelp', { model });
});
}, },
search() { search() {

View File

@ -7,30 +7,13 @@ export default {
initialize(container) { initialize(container) {
const cache = {};
var transitionCount = 0;
// Tell our AJAX system to track a page transition // Tell our AJAX system to track a page transition
const router = container.lookup('router:main'); const router = container.lookup('router:main');
router.on('willTransition', viewTrackingRequired); router.on('willTransition', viewTrackingRequired);
router.on('didTransition', function() { router.on('didTransition', function() {
Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM); Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM);
transitionCount++;
_.each(cache, (v,k) => {
if (v && v.target && v.target < transitionCount) {
delete cache[k];
}
}); });
});
router.transientCache = function(key, data, count) {
if (data === undefined) {
return cache[key];
} else {
return cache[key] = {data, target: transitionCount + count};
}
};
startPageTracking(router); startPageTracking(router);

View File

@ -2,6 +2,18 @@ const PageTracker = Ember.Object.extend(Ember.Evented);
let _pageTracker = PageTracker.create(); let _pageTracker = PageTracker.create();
let _started = false; let _started = false;
const cache = {};
let transitionCount = 0;
export function setTransient(key, data, count) {
cache[key] = {data, target: transitionCount + count};
}
export function getTransient(key) {
return cache[key];
}
export function startPageTracking(router) { export function startPageTracking(router) {
if (_started) { return; } if (_started) { return; }
@ -11,8 +23,13 @@ export function startPageTracking(router) {
// Refreshing the title is debounced, so we need to trigger this in the // Refreshing the title is debounced, so we need to trigger this in the
// next runloop to have the correct title. // next runloop to have the correct title.
Em.run.next(() => { Em.run.next(() => _pageTracker.trigger('change', url, Discourse.get('_docTitle')));
_pageTracker.trigger('change', url, Discourse.get('_docTitle'));
transitionCount++;
_.each(cache, (v,k) => {
if (v && v.target && v.target < transitionCount) {
delete cache[k];
}
}); });
}); });
_started = true; _started = true;

View File

@ -2,13 +2,13 @@ import { ajax } from 'discourse/lib/ajax';
import { translateResults, getSearchKey, isValidSearchTerm } from "discourse/lib/search"; import { translateResults, getSearchKey, isValidSearchTerm } from "discourse/lib/search";
import Composer from 'discourse/models/composer'; import Composer from 'discourse/models/composer';
import PreloadStore from 'preload-store'; import PreloadStore from 'preload-store';
import { getTransient, setTransient } from 'discourse/lib/page-tracker';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
queryParams: { q: {}, context_id: {}, context: {}, skip_context: {} }, queryParams: { q: {}, context_id: {}, context: {}, skip_context: {} },
model(params) { model(params) {
const router = Discourse.__container__.lookup('router:main'); const cached = getTransient('lastSearch');
var cached = router.transientCache('lastSearch');
var args = { q: params.q }; var args = { q: params.q };
if (params.context_id && !args.skip_context) { if (params.context_id && !args.skip_context) {
args.search_context = { args.search_context = {
@ -21,7 +21,7 @@ export default Discourse.Route.extend({
if (cached && cached.data.searchKey === searchKey) { if (cached && cached.data.searchKey === searchKey) {
// extend expiry // extend expiry
router.transientCache('lastSearch', { searchKey, model: cached.data.model }, 5); setTransient('lastSearch', { searchKey, model: cached.data.model }, 5);
return cached.data.model; return cached.data.model;
} }
@ -33,7 +33,7 @@ export default Discourse.Route.extend({
} }
}).then(results => { }).then(results => {
const model = (results && translateResults(results)) || {}; const model = (results && translateResults(results)) || {};
router.transientCache('lastSearch', { searchKey, model }, 5); setTransient('lastSearch', { searchKey, model }, 5);
return model; return model;
}); });
}, },

View File

@ -1,39 +1,35 @@
<div class="search row clearfix"> <div class="search row clearfix">
{{search-text-field value=searchTerm class="full-page-search input-xxlarge search no-blur" action="search" hasAutofocus=hasAutofocus}} {{search-text-field value=searchTerm class="full-page-search input-xxlarge search no-blur" action="search" hasAutofocus=hasAutofocus}}
{{d-button action="search" icon="search" class="btn-primary" disabled=searchButtonDisabled}} {{d-button action="search" icon="search" class="btn-primary" disabled=searchButtonDisabled}}
{{#if currentUser}}
{{#unless site.mobileView}} {{#if canCreateTopic}}
<span class="new-topic-btn">{{d-button id="create-topic" class="btn-default" action="createTopic" actionParam=searchTerm icon="plus" label="topic.create"}}</span> <span class="new-topic-btn">{{d-button id="create-topic" class="btn-default" action="createTopic" actionParam=searchTerm icon="plus" label="topic.create"}}</span>
{{/unless}}
{{/if}} {{/if}}
{{#if canBulkSelect}} {{#if canBulkSelect}}
{{#if model.posts}}
{{d-button icon="list" class="bulk-select" title="topics.bulk.toggle" action="toggleBulkSelect"}} {{d-button icon="list" class="bulk-select" title="topics.bulk.toggle" action="toggleBulkSelect"}}
{{bulk-select-button selected=selected action="refresh"}} {{bulk-select-button selected=selected action="refresh"}}
{{/if}} {{/if}}
{{/if}}
</div> </div>
{{#if model.posts}} {{#if bulkSelectEnabled}}
{{#if bulkSelectEnabled}}
<div class='fps-select'> <div class='fps-select'>
<a href {{action "selectAll"}}>{{i18n "search.select_all"}}</a> {{d-link action="selectAll" label="search.select_all"}}
<a href {{action "clearAll"}}>{{i18n "search.clear_all"}}</a> {{d-link action="clearAll" label="search.clear_all"}}
</div> </div>
{{/if}}
{{/if}} {{/if}}
{{#if context}} {{#if context}}
<div class='fps-search-context'> <div class='fps-search-context'>
<label> <label>
{{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}} {{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}}
</label> </label>
</div> </div>
{{/if}} {{/if}}
{{#conditional-loading-spinner condition=loading}} {{#conditional-loading-spinner condition=loading}}
{{#unless model.posts}} {{#unless hasResults}}
<h3> <h3>
{{#if searchActive}} {{#if searchActive}}
{{i18n "search.no_results"}} {{i18n "search.no_results"}}
@ -42,7 +38,7 @@
</h3> </h3>
{{/unless}} {{/unless}}
{{#if model.posts}} {{#if hasResults}}
<div class='search-title clearfix'> <div class='search-title clearfix'>
<div class='result-count'> <div class='result-count'>
<span> <span>
@ -101,7 +97,7 @@
{{#if showLikeCount}} {{#if showLikeCount}}
{{#if result.like_count}} {{#if result.like_count}}
<span class='like-count'> <span class='like-count'>
{{result.like_count}} <i class="icon fa fa-heart"></i> {{result.like_count}} {{fa-icon "heart"}}
</span> </span>
{{/if}} {{/if}}
{{/if}} {{/if}}
@ -109,7 +105,7 @@
</div> </div>
{{/each}} {{/each}}
{{#if model.posts}} {{#if hasResults}}
<h3 class="search-footer"> <h3 class="search-footer">
{{i18n "search.no_more_results"}} {{i18n "search.no_more_results"}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a> <a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a>

View File

@ -6,16 +6,16 @@ test("perform various searches", assert => {
andThen(() => { andThen(() => {
assert.ok(find('input.search').length > 0); assert.ok(find('input.search').length > 0);
assert.ok(find('.topic').length === 0); assert.ok(find('.fps-topic').length === 0);
}); });
fillIn('.search input', 'none'); fillIn('.search input', 'none');
click('.search .btn-primary'); click('.search .btn-primary');
andThen(() => assert.ok(find('.topic').length === 0), 'has no results'); andThen(() => assert.ok(find('.fps-topic').length === 0), 'has no results');
fillIn('.search input', 'posts'); fillIn('.search input', 'posts');
click('.search .btn-primary'); click('.search .btn-primary');
andThen(() => assert.ok(find('.topic').length === 1, 'has one post')); andThen(() => assert.ok(find('.fps-topic').length === 1, 'has one post'));
}); });