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

View file

@ -7,31 +7,14 @@ export default {
initialize(container) {
const cache = {};
var transitionCount = 0;
// Tell our AJAX system to track a page transition
const router = container.lookup('router:main');
router.on('willTransition', viewTrackingRequired);
router.on('didTransition', function() {
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);
// Out of the box, Discourse tries to track google analytics

View file

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

View file

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

View file

@ -1,61 +1,57 @@
<div class="search row clearfix">
{{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}}
{{#if currentUser}}
{{#unless site.mobileView}}
<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 canCreateTopic}}
<span class="new-topic-btn">{{d-button id="create-topic" class="btn-default" action="createTopic" actionParam=searchTerm icon="plus" label="topic.create"}}</span>
{{/if}}
{{#if canBulkSelect}}
{{#if model.posts}}
{{d-button icon="list" class="bulk-select" title="topics.bulk.toggle" action="toggleBulkSelect"}}
{{bulk-select-button selected=selected action="refresh"}}
{{/if}}
{{d-button icon="list" class="bulk-select" title="topics.bulk.toggle" action="toggleBulkSelect"}}
{{bulk-select-button selected=selected action="refresh"}}
{{/if}}
</div>
{{#if model.posts}}
{{#if bulkSelectEnabled}}
{{#if bulkSelectEnabled}}
<div class='fps-select'>
<a href {{action "selectAll"}}>{{i18n "search.select_all"}}</a>
<a href {{action "clearAll"}}>{{i18n "search.clear_all"}}</a>
{{d-link action="selectAll" label="search.select_all"}}
{{d-link action="clearAll" label="search.clear_all"}}
</div>
{{/if}}
{{/if}}
{{#if context}}
<div class='fps-search-context'>
<label>
{{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}}
</label>
</div>
<div class='fps-search-context'>
<label>
{{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}}
</label>
</div>
{{/if}}
{{#conditional-loading-spinner condition=loading}}
{{#unless model.posts}}
{{#unless hasResults}}
<h3>
{{#if searchActive}}
{{i18n "search.no_results"}}
{{i18n "search.no_results"}}
{{/if}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a>
</h3>
{{/unless}}
{{#if model.posts}}
<div class='search-title clearfix'>
<div class='result-count'>
<span>
{{{i18n "search.result_count" count=resultCount term=noSortQ}}}
</span>
{{#if hasResults}}
<div class='search-title clearfix'>
<div class='result-count'>
<span>
{{{i18n "search.result_count" count=resultCount term=noSortQ}}}
</span>
</div>
<div class='sort-by'>
<span class='desc'>
{{i18n "search.sort_by"}}
</span>
{{combo-box value=sortOrder content=sortOrders castInteger="true"}}
</div>
</div>
<div class='sort-by'>
<span class='desc'>
{{i18n "search.sort_by"}}
</span>
{{combo-box value=sortOrder content=sortOrders castInteger="true"}}
</div>
</div>
{{/if}}
{{#each model.posts as |result|}}
@ -101,7 +97,7 @@
{{#if showLikeCount}}
{{#if result.like_count}}
<span class='like-count'>
{{result.like_count}} <i class="icon fa fa-heart"></i>
{{result.like_count}} {{fa-icon "heart"}}
</span>
{{/if}}
{{/if}}
@ -109,11 +105,11 @@
</div>
{{/each}}
{{#if model.posts}}
<h3 class="search-footer">
{{i18n "search.no_more_results"}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a>
</h3>
{{#if hasResults}}
<h3 class="search-footer">
{{i18n "search.no_more_results"}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a>
</h3>
{{/if}}
{{/conditional-loading-spinner}}

View file

@ -6,16 +6,16 @@ test("perform various searches", assert => {
andThen(() => {
assert.ok(find('input.search').length > 0);
assert.ok(find('.topic').length === 0);
assert.ok(find('.fps-topic').length === 0);
});
fillIn('.search input', 'none');
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');
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'));
});