import {searchForTerm, searchContextDescription, isValidSearchTerm } from 'discourse/lib/search'; import DiscourseURL from 'discourse/lib/url'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import showModal from 'discourse/lib/show-modal'; let _dontSearch = false; export default Ember.Component.extend({ searchService: Ember.inject.service('search'), classNames: ['search-menu'], typeFilter: null, @observes('searchService.searchContext') contextChanged: function() { if (this.get('searchService.searchContextEnabled')) { _dontSearch = true; this.set('searchService.searchContextEnabled', false); _dontSearch = false; } }, @computed('searchService.searchContext', 'searchService.term', 'searchService.searchContextEnabled') fullSearchUrlRelative(searchContext, term, searchContextEnabled) { if (searchContextEnabled && Ember.get(searchContext, 'type') === 'topic') { return null; } let url = '/search?q=' + encodeURIComponent(this.get('searchService.term')); if (searchContextEnabled) { if (searchContext.id.toString().toLowerCase() === this.get('currentUser.username_lower') && searchContext.type === "private_messages" ) { url += ' in:private'; } else { url += encodeURIComponent(" " + searchContext.type + ":" + searchContext.id); } } return url; }, @computed('fullSearchUrlRelative') fullSearchUrl(fullSearchUrlRelative) { if (fullSearchUrlRelative) { return Discourse.getURL(fullSearchUrlRelative); } }, @computed('searchService.searchContext') searchContextDescription(ctx) { return searchContextDescription(Em.get(ctx, 'type'), Em.get(ctx, 'user.username') || Em.get(ctx, 'category.name')); }, @observes('searchService.searchContextEnabled') searchContextEnabledChanged() { if (_dontSearch) { return; } this.newSearchNeeded(); }, // If we need to perform another search @observes('searchService.term', 'typeFilter') newSearchNeeded() { this.set('noResults', false); const term = this.get('searchService.term'); if (isValidSearchTerm(term)) { this.set('loading', true); Ember.run.debounce(this, 'searchTerm', term, this.get('typeFilter'), 400); } else { this.setProperties({ content: null }); } this.set('selectedIndex', 0); }, searchTerm(term, typeFilter) { // for cancelling debounced search if (this._cancelSearch){ this._cancelSearch = null; return; } if (this._search) { this._search.abort(); } const searchContext = this.get('searchService.searchContextEnabled') ? this.get('searchService.searchContext') : null; this._search = searchForTerm(term, { typeFilter, searchContext, fullSearchUrl: this.get('fullSearchUrl') }); this._search.then((content) => { this.setProperties({ noResults: !content, content }); }).finally(() => { this.set('loading', false); this._search = null; }); }, @computed('typeFilter', 'loading') showCancelFilter(typeFilter, loading) { if (loading) { return false; } return !Ember.isEmpty(typeFilter); }, @observes('searchService.term') termChanged() { this.cancelTypeFilter(); }, actions: { fullSearch() { const self = this; if (this._search) { this._search.abort(); } // maybe we are debounced and delayed // stop that as well this._cancelSearch = true; Em.run.later(function() { self._cancelSearch = false; }, 400); const url = this.get('fullSearchUrlRelative'); if (url) { DiscourseURL.routeTo(url); } }, moreOfType(type) { this.set('typeFilter', type); }, cancelType() { this.cancelTypeFilter(); }, showedSearch() { $('#search-term').focus().select(); }, showSearchHelp() { // TODO: @EvitTrout how do we get a loading indicator here? Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then((model) => { showModal('searchHelp', { model }); }); }, cancelHighlight() { this.set('searchService.highlightTerm', null); } }, cancelTypeFilter() { this.set('typeFilter', null); }, keyDown(e) { if (e.which === 13 && isValidSearchTerm(this.get('searchService.term'))) { this.set('visible', false); this.send('fullSearch'); } } });