mirror of
https://github.com/discourse/discourse.git
synced 2025-06-13 00:25:02 +08:00
Refactor full page search for style, remove lookups
This commit is contained in:
@ -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() {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
|
@ -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'));
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user