Migrate search drop down to `menu-panel` component.

This commit is contained in:
Robin Ward 2015-08-26 16:55:01 -04:00
parent 3bc79f6885
commit d4b987ff32
26 changed files with 442 additions and 422 deletions

View File

@ -1,13 +1,14 @@
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
const PANEL_BODY_MARGIN = 30;
const mutationSupport = !!window['MutationObserver'];
export default Ember.Component.extend({
classNameBindings: [':menu-panel', 'visible::hidden', 'viewMode'],
showClose: Ember.computed.equal('viewMode', 'slide-in'),
_resizeComponent() {
_layoutComponent() {
if (!this.get('visible')) { return; }
const viewMode = this.get('viewMode');
@ -27,26 +28,22 @@ export default Ember.Component.extend({
this.$().css({ left: posLeft + "px", top: posTop + "px" });
// adjust panel height
let contentHeight = parseInt($('.panel-body-contents').height());
let contentHeight = parseInt(this.$('.panel-body-contents').height());
const fullHeight = parseInt($(window).height());
const offsetTop = this.$().offset().top;
if (contentHeight + offsetTop + PANEL_BODY_MARGIN > fullHeight) {
contentHeight = fullHeight - (offsetTop - $(window).scrollTop()) - PANEL_BODY_MARGIN;
const scrollTop = $(window).scrollTop();
if (contentHeight + (offsetTop - scrollTop) + PANEL_BODY_MARGIN > fullHeight) {
contentHeight = fullHeight - (offsetTop - scrollTop) - PANEL_BODY_MARGIN;
}
$panelBody.height(contentHeight);
} else {
$panelBody.height('auto');
const headerHeight = parseInt($('header.d-header').height() + 3);
this.$().css({ left: "auto", top: headerHeight + "px" });
}
},
_needsResize() {
Ember.run.scheduleOnce('afterRender', this, this._resizeComponent);
},
@computed('force')
viewMode() {
const force = this.get('force');
@ -61,6 +58,10 @@ export default Ember.Component.extend({
const markActive = this.get('markActive');
if (this.get('visible')) {
this.appEvents.on('dropdowns:closeAll', this, this.hide);
// Allow us to hook into things being shown
Ember.run.scheduleOnce('afterRender', () => this.sendAction('onVisible'));
if (isDropdown && markActive) {
$(markActive).addClass('active');
@ -72,14 +73,16 @@ export default Ember.Component.extend({
if ($target.closest('.menu-panel').length > 0) { return; }
this.hide();
});
this.performLayout();
this._watchSizeChanges();
} else {
Ember.run.scheduleOnce('afterRender', () => this.sendAction('onHidden'));
if (markActive) {
$(markActive).removeClass('active');
}
$('html').off('click.close-menu-panel');
this._stopWatchingSize();
}
this._needsResize();
},
@computed()
@ -102,9 +105,38 @@ export default Ember.Component.extend({
return this.siteSettings.faq_url ? this.siteSettings.faq_url : Discourse.getURL('/faq');
},
performLayout() {
Ember.run.scheduleOnce('afterRender', this, this._layoutComponent);
},
_watchSizeChanges() {
if (mutationSupport) {
this._observer.disconnect();
this._observer.observe(this.element, { childList: true, subtree: true });
} else {
clearInterval(this._resizeInterval);
this._resizeInterval = setInterval(() => {
Ember.run(() => {
const contentHeight = parseInt(this.$('.panel-body-contents').height());
if (contentHeight !== this._lastHeight) { this.performLayout(); }
this._lastHeight = contentHeight;
});
}, 500);
}
},
_stopWatchingSize() {
if (mutationSupport) {
this._observer.disconnect();
} else {
clearInterval(this._resizeInterval);
}
},
@on('didInsertElement')
_bindEvents() {
this.$().on('click.discourse-menu-panel', 'a', () => {
this.$().on('click.discourse-menu-panel', 'a', (e) => {
if ($(e.target).data('ember-action')) { return; }
this.hide();
});
@ -116,11 +148,16 @@ export default Ember.Component.extend({
}
});
// Recompute styles on resize
$(window).on('resize.discourse-menu-panel', () => {
this.propertyDidChange('viewMode');
this._needsResize();
this.performLayout();
});
if (mutationSupport) {
this._observer = new MutationObserver(() => {
Ember.run(() => this.performLayout());
});
}
},
@on('willDestroyElement')

View File

@ -0,0 +1,174 @@
import searchForTerm from 'discourse/lib/search-for-term';
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) {
if (ctx) {
switch(Em.get(ctx, 'type')) {
case 'topic':
return I18n.t('search.context.topic');
case 'user':
return I18n.t('search.context.user', {username: Em.get(ctx, 'user.username')});
case 'category':
return I18n.t('search.context.category', {category: Em.get(ctx, 'category.name')});
case 'private_messages':
return I18n.t('search.context.private_messages');
}
}
},
@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') || '').trim();
if (term.length >= Discourse.SiteSettings.min_search_term_length) {
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();
},
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) {
const term = this.get('searchService.term');
if (e.which === 13 && term && term.length >= this.siteSettings.min_search_term_length) {
this.set('searchVisible', false);
this.send('fullSearch');
}
}
});

View File

@ -1,7 +1,9 @@
import computed from 'ember-addons/ember-computed-decorators';
import TextField from 'discourse/components/text-field';
export default TextField.extend({
placeholder: function() {
return this.get('searchContextEnabled') ? "" : I18n.t('search.title');
}.property('searchContextEnabled')
@computed('searchService.searchContextEnabled')
placeholder: function(searchContextEnabled) {
return searchContextEnabled ? "" : I18n.t('search.title');
}
});

View File

@ -15,5 +15,5 @@ export default Ember.Controller.extend({
@computed
loginRequired() {
return Discourse.SiteSettings.login_required && !Discourse.User.current();
}
},
});

View File

@ -4,6 +4,7 @@ const HeaderController = Ember.Controller.extend({
notifications: null,
loadingNotifications: false,
hamburgerVisible: false,
searchVisible: false,
needs: ['application'],
loginRequired: Em.computed.alias('controllers.application.loginRequired'),
@ -71,9 +72,16 @@ const HeaderController = Ember.Controller.extend({
headerView.showDropdownBySelector("#user-notifications");
},
toggleSearchMenu() {
this.appEvents.trigger('dropdowns:closeAll');
this.toggleProperty('searchVisible');
},
toggleHamburgerMenu() {
this.appEvents.trigger('dropdowns:closeAll');
this.toggleProperty('hamburgerVisible');
}
}
});

View File

@ -1,170 +0,0 @@
import searchForTerm from 'discourse/lib/search-for-term';
import DiscourseURL from 'discourse/lib/url';
import computed from 'ember-addons/ember-computed-decorators';
let _dontSearch = false;
export default Em.Controller.extend({
typeFilter: null,
@computed('searchContext')
contextType: {
get(searchContext) {
if (searchContext) {
return Ember.get(searchContext, 'type');
}
},
set(value, searchContext) {
// a bit hacky, consider cleaning this up, need to work through all observers though
const context = $.extend({}, searchContext);
context.type = value;
this.set('searchContext', context);
return this.get('searchContext.type');
}
},
contextChanged: function(){
if (this.get('searchContextEnabled')) {
_dontSearch = true;
this.set('searchContextEnabled', false);
_dontSearch = false;
}
}.observes('searchContext'),
fullSearchUrlRelative: function(){
if (this.get('searchContextEnabled') && this.get('searchContext.type') === 'topic') {
return null;
}
let url = '/search?q=' + encodeURIComponent(this.get('term'));
const searchContext = this.get('searchContext');
if (this.get('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;
}.property('searchContext','term','searchContextEnabled'),
fullSearchUrl: function(){
const url = this.get('fullSearchUrlRelative');
if (url) {
return Discourse.getURL(url);
}
}.property('fullSearchUrlRelative'),
searchContextDescription: function(){
const ctx = this.get('searchContext');
if (ctx) {
switch(Em.get(ctx, 'type')) {
case 'topic':
return I18n.t('search.context.topic');
case 'user':
return I18n.t('search.context.user', {username: Em.get(ctx, 'user.username')});
case 'category':
return I18n.t('search.context.category', {category: Em.get(ctx, 'category.name')});
case 'private_messages':
return I18n.t('search.context.private_messages');
}
}
}.property('searchContext'),
searchContextEnabledChanged: function(){
if (_dontSearch) { return; }
this.newSearchNeeded();
}.observes('searchContextEnabled'),
// If we need to perform another search
newSearchNeeded: function() {
this.set('noResults', false);
const term = (this.get('term') || '').trim();
if (term.length >= Discourse.SiteSettings.min_search_term_length) {
this.set('loading', true);
Ember.run.debounce(this, 'searchTerm', term, this.get('typeFilter'), 400);
} else {
this.setProperties({ content: null });
}
this.set('selectedIndex', 0);
}.observes('term', 'typeFilter'),
searchTerm(term, typeFilter) {
const self = this;
// for cancelling debounced search
if (this._cancelSearch){
this._cancelSearch = null;
return;
}
if (this._search) {
this._search.abort();
}
const searchContext = this.get('searchContextEnabled') ? this.get('searchContext') : null;
this._search = searchForTerm(term, {
typeFilter,
searchContext,
fullSearchUrl: this.get('fullSearchUrl')
});
this._search.then(function(results) {
self.setProperties({ noResults: !results, content: results });
}).finally(function() {
self.set('loading', false);
self._search = null;
});
},
showCancelFilter: function() {
if (this.get('loading')) return false;
return !Ember.isEmpty(this.get('typeFilter'));
}.property('typeFilter', 'loading'),
termChanged: function() {
this.cancelTypeFilter();
}.observes('term'),
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();
}
},
cancelTypeFilter() {
this.set('typeFilter', null);
}
});

View File

@ -8,14 +8,13 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
needs: ['header', 'modal', 'composer', 'quote-button', 'topic-progress', 'application'],
multiSelect: false,
needs: ['header', 'modal', 'composer', 'quote-button', 'search', 'topic-progress', 'application'],
allPostsSelected: false,
editingTopic: false,
selectedPosts: null,
selectedReplies: null,
queryParams: ['filter', 'username_filters', 'show_deleted'],
searchHighlight: null,
loadedAllPosts: false,
enteredAt: null,
firstPostExpanded: false,
@ -23,10 +22,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
maxTitleLength: setting('max_topic_title_length'),
contextChanged: function() {
this.set('controllers.search.searchContext', this.get('model.searchContext'));
}.observes('topic'),
_titleChanged: function() {
const title = this.get('model.title');
if (!Ember.isEmpty(title)) {
@ -37,20 +32,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
}
}.observes('model.title', 'category'),
termChanged: function() {
const dropdown = this.get('controllers.header.visibleDropdown');
const term = this.get('controllers.search.term');
if(dropdown === 'search-dropdown' && term){
this.set('searchHighlight', term);
} else {
if(this.get('searchHighlight')){
this.set('searchHighlight', null);
}
}
}.observes('controllers.search.term', 'controllers.header.visibleDropdown'),
postStreamLoadedAllPostsChanged: function() {
// semantics of loaded all posts are slightly diff at topic level,
// it just means that we "once" loaded all posts, this means we don't

View File

@ -3,6 +3,7 @@ import KeyboardShortcuts from 'discourse/lib/keyboard-shortcuts';
export default {
name: "keyboard-shortcuts",
initialize(container) {
KeyboardShortcuts.bindEvents(Mousetrap, container);
}

View File

@ -63,6 +63,9 @@ export default {
this.container = container;
this._stopCallback();
this.searchService = this.container.lookup('search-service:main');
_.each(PATH_BINDINGS, this._bindToPath, this);
_.each(CLICK_BINDINGS, this._bindToClick, this);
_.each(SELECTED_POST_BINDINGS, this._bindToSelectedPost, this);
@ -131,10 +134,7 @@ export default {
},
showBuiltinSearch() {
if ($('#search-dropdown').is(':visible')) {
this._toggleSearch(false);
return true;
}
this.searchService.set('searchContextEnabled', false);
const currentPath = this.container.lookup('controller:application').get('currentPath'),
blacklist = [ /^discovery\.categories/ ],
@ -144,11 +144,12 @@ export default {
// If we're viewing a topic, only intercept search if there are cloaked posts
if (showSearch && currentPath.match(/^topic\./)) {
showSearch = $('.cooked').length < this.container.lookup('controller:topic').get('postStream.stream.length');
showSearch = $('.cooked').length < this.container.lookup('controller:topic').get('model.postStream.stream.length');
}
if (showSearch) {
this._toggleSearch(true);
this.searchService.set('searchContextEnabled', true);
this.showSearch();
return false;
}
@ -168,8 +169,7 @@ export default {
},
showSearch() {
this._toggleSearch(false);
return false;
this.container.lookup('controller:header').send('toggleSearchMenu');
},
toggleHamburgerMenu() {
@ -370,12 +370,5 @@ export default {
return oldStopCallback(e, element, combo);
};
},
_toggleSearch(selectContext) {
$('#search-button').click();
if (selectContext) {
this.container.lookup('controller:search').set('searchContextEnabled', true);
}
},
}
};

View File

@ -3,6 +3,7 @@ import AppEvents from 'discourse/lib/app-events';
import Store from 'discourse/models/store';
import DiscourseURL from 'discourse/lib/url';
import DiscourseLocation from 'discourse/lib/discourse-location';
import SearchService from 'discourse/services/search';
function inject() {
const app = arguments[0],
@ -35,6 +36,9 @@ export default {
app.register('site-settings:main', Discourse.SiteSettings, { instantiate: false });
injectAll(app, 'siteSettings');
app.register('search-service:main', SearchService);
injectAll(app, 'searchService');
app.register('session:main', Session.current(), { instantiate: false });
injectAll(app, 'session');

View File

@ -99,13 +99,6 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
showModal('keyboard-shortcuts-help', { title: 'keyboard_shortcuts_help.title'});
},
showSearchHelp() {
// TODO: @EvitTrout how do we get a loading indicator here?
Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(model){
showModal('searchHelp', { model });
});
},
// Close the current modal, and destroy its state.
closeModal() {
this.render('hide-modal', { into: 'modal', outlet: 'modalBody' });

View File

@ -81,7 +81,7 @@ export default function(filter, params) {
expandAllPinned: true
});
this.controllerFor('search').set('searchContext', model.get('searchContext'));
this.searchService.set('searchContext', model.get('searchContext'));
this.set('topics', null);
this.openTopicDraft(topics);
@ -98,7 +98,7 @@ export default function(filter, params) {
deactivate: function() {
this._super();
this.controllerFor('search').set('searchContext', null);
this.searchService.set('searchContext', null);
},
actions: {

View File

@ -25,11 +25,11 @@ export default (viewName, path) => {
});
this.controllerFor("user").set("pmView", viewName);
this.controllerFor("search").set("contextType", "private_messages");
this.searchService.set('contextType', 'private_messages');
},
deactivate() {
this.controllerFor("search").set("contextType", "user");
this.searchService.set('contextType', 'private_messages');
}
});
};

View File

@ -175,14 +175,12 @@ const TopicRoute = Discourse.Route.extend({
const topic = this.modelFor('topic');
this.session.set('lastTopicIdViewed', parseInt(topic.get('id'), 10));
this.controllerFor('search').set('searchContext', topic.get('searchContext'));
},
deactivate() {
this._super();
// Clear the search context
this.controllerFor('search').set('searchContext', null);
this.searchService.set('searchContext', null);
this.controllerFor('user-card').set('visible', false);
const topicController = this.controllerFor('topic'),
@ -220,11 +218,8 @@ const TopicRoute = Discourse.Route.extend({
Discourse.TopicRoute.trigger('setupTopicController', this);
this.controllerFor('header').setProperties({
topic: model,
showExtraInfo: false
});
this.controllerFor('header').setProperties({ topic: model, showExtraInfo: false });
this.searchService.set('searchContext', model.get('searchContext'));
this.controllerFor('topic-admin-menu').set('model', model);
this.controllerFor('composer').set('topic', model);

View File

@ -65,9 +65,7 @@ export default Discourse.Route.extend({
setupController(controller, user) {
controller.set('model', user);
// Add a search context
this.controllerFor('search').set('searchContext', user.get('searchContext'));
this.searchService.set('searchContext', user.get('searchContext'))
},
activate() {
@ -83,7 +81,7 @@ export default Discourse.Route.extend({
this.messageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));
// Remove the search context
this.controllerFor('search').set('searchContext', null);
this.searchService.set('searchContext', null)
}
});

View File

@ -0,0 +1,30 @@
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
export default Ember.Object.extend({
searchContextEnabled: false,
searchContext: null,
term: null,
highlightTerm: null,
@observes('term')
_sethighlightTerm() {
this.set('highlightTerm', this.get('term'));
},
@computed('searchContext')
contextType: {
get(searchContext) {
if (searchContext) {
return Ember.get(searchContext, 'type');
}
},
set(value, searchContext) {
// a bit hacky, consider cleaning this up, need to work through all observers though
const context = $.extend({}, searchContext);
context.type = value;
this.set('searchContext', context);
return this.get('searchContext.type');
}
},
});

View File

@ -0,0 +1,44 @@
{{#menu-panel visible=searchVisible
markActive=".search-dropdown"
onVisible="showedSearch"
onHidden="cancelHighlight"}}
{{plugin-outlet "above-search"}}
{{search-text-field value=searchService.term id="search-term"}}
<div class="search-context">
{{#if searchService.searchContext}}
<label>
{{input type="checkbox" name="searchContext" checked=searchService.searchContextEnabled}} {{searchContextDescription}}
</label>
{{/if}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "show_help"}}</a>
<div class='clearfix'></div>
</div>
{{#if loading}}
<div class="searching">{{loading-spinner}}</div>
{{else}}
<div class="results">
{{#if noResults}}
<div class="no-results">
{{i18n "search.no_results"}}
</div>
{{else}}
{{#each content.resultTypes as |resultType|}}
<ul>
<li class="heading row">{{resultType.name}}</li>
{{component resultType.componentName results=resultType.results term=searchService.term}}
</ul>
<div class="no-results">
{{#if resultType.moreUrl}}
<a href={{resultType.moreUrl}} class="filter">{{i18n "show_more"}} {{fa-icon "chevron-down"}}</a>
{{/if}}
{{#if resultType.more}}
<a href class="filter filter-type" {{action "moreOfType" resultType.type bubbles=false}}>{{i18n "show_more"}} {{fa-icon "chevron-down"}}</a>
{{/if}}
</div>
{{/each}}
{{/if}}
</div>
{{/if}}
{{/menu-panel}}

View File

@ -1,4 +1,5 @@
{{hamburger-menu hamburgerVisible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}}
{{search-menu searchVisible=searchVisible}}
<div class='wrap'>
<div class='contents clearfix'>
@ -28,17 +29,15 @@
{{/if}}
</li>
{{/if}}
<li>
<li class="search-dropdown">
{{#if loginRequired}}
<a id='search-button' class='icon expand' href='#' aria-hidden="true" {{action "showLogin"}}>
<a id='search-button' class='icon expand' href aria-hidden="true" {{action "showLogin"}}>
{{fa-icon "search"}}
</a>
{{else}}
<a id='search-button'
class='icon expand'
href='#'
data-dropdown="search-dropdown"
title='{{i18n 'search.title'}}'>
<a {{action "toggleSearchMenu"}} class='icon' href
id='search-button'
title={{i18n 'search.title'}}>
{{fa-icon "search" label="search.title"}}
</a>
{{/if}}
@ -81,7 +80,6 @@
{{#if view.renderDropdowns}}
{{plugin-outlet "header-before-dropdowns"}}
{{render "search"}}
{{render "notifications" notifications}}
{{render "user-dropdown"}}
{{/if}}

View File

@ -1,37 +0,0 @@
{{plugin-outlet "above-search"}}
{{search-text-field value=term searchContextEnabled=searchContextEnabled id="search-term"}}
<div class="search-context">
{{#if searchContext}}
<label>
{{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}}
</label>
{{/if}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "show_help"}}</a>
</div>
{{#if loading}}
<div class="searching">{{loading-spinner}}</div>
{{else}}
<div class="results">
{{#if noResults}}
<div class="no-results">
{{i18n "search.no_results"}}
</div>
{{else}}
{{#each content.resultTypes as |resultType|}}
<ul>
<li class="heading row">{{resultType.name}}</li>
{{component resultType.componentName results=resultType.results term=term}}
</ul>
<div class="no-results">
{{#if resultType.moreUrl}}
<a href="{{resultType.moreUrl}}" class="filter">{{i18n "show_more"}} {{fa-icon "chevron-down"}}</a>
{{/if}}
{{#if resultType.more}}
<a href class="filter filter-type" {{action "moreOfType" resultType.type bubbles=false}}>{{i18n "show_more"}} {{fa-icon "chevron-down"}}</a>
{{/if}}
</div>
{{/each}}
{{/if}}
</div>
{{/if}}

View File

@ -314,23 +314,23 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
}.on('willInsertElement'),
_applySearchHighlight: function() {
const highlight = this.get('controller.searchHighlight');
const highlight = this.get('searchService.highlightTerm');
const cooked = this.$('.cooked');
if(!cooked){ return; }
if (!cooked) { return; }
if(highlight && highlight.length > 2){
if(this._highlighted){
if (highlight && highlight.length > 2) {
if (this._highlighted) {
cooked.unhighlight();
}
cooked.highlight(highlight.split(/\s+/));
this._highlighted = true;
} else if(this._highlighted){
} else if (this._highlighted) {
cooked.unhighlight();
this._highlighted = false;
}
}.observes('controller.searchHighlight', 'cooked')
}.observes('searchService.highlightTerm', 'cooked')
});
export default PostView;

View File

@ -1,12 +0,0 @@
export default Ember.View.extend({
tagName: 'div',
classNames: ['d-dropdown'],
elementId: 'search-dropdown',
templateName: 'search',
keyDown: function(e){
var term = this.get('controller.term');
if (e.which === 13 && term && term.length >= Discourse.SiteSettings.min_search_term_length) {
this.get('controller').send('fullSearch');
}
}
});

View File

@ -104,3 +104,4 @@
//= require_tree ./discourse/routes
//= require_tree ./discourse/pre-initializers
//= require_tree ./discourse/initializers
//= require_tree ./discourse/services

View File

@ -154,27 +154,6 @@
list-style: none;
}
li:not(.category):not(.heading) {
font-size: 0.929em;
line-height: 16px;
.fa {
font-size: inherit;
}
a {
display: block;
padding: 5px;
transition: all linear .15s;
}
&:hover a:not(.badge-notification) {
background-color: dark-light-diff($highlight, $secondary, 50%, -55%);
}
button {margin-left: 5px;}
}
.heading a:hover {
background-color: dark-light-diff($highlight, $secondary, 50%, -55%);
}
@ -229,47 +208,6 @@
// Search
&#search-dropdown {
.heading {
padding: 5px 0 5px 5px;
.filter {
padding: 0 5px;
}
}
}
input[type='text'] {
width: 518px;
height: 22px;
margin: 5px;
padding: 5px;
}
.search-context {
padding: 0 5px;
label { margin-bottom: 0; }
}
.searching {
position: absolute;
top: 0;
right: 0;
.spinner {
width: 10px;
height: 10px;
border-width: 2px;
margin: 20px 0 0 0;
}
}
// I am ghetto using this to display "Show More".. be warned
.no-results {
padding: 5px;
text-align: center;
}
.filter {
padding: 0;
&:hover {background: transparent;}
}
// Categories
@ -286,24 +224,6 @@
}
}
.search-link {
.badge-category-parent {
line-height: 0.8em;
}
.topic-title {
margin-right: 6px;
}
.topic-statuses {
float: none;
display: inline-block;
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
margin: 0;
.fa {
margin: 0;
}
}
}
.highlight-strong {
background-color: dark-light-diff($highlight, $secondary, 40%, -45%);
@ -313,27 +233,6 @@
font-weight: bold;
}
.search-context .show-help {
position: absolute;
right: 10px;
top: 0;
}
.search-context {
min-height: 30px;
position: relative;
}
.d-dropdown#search-dropdown {
max-height: none;
overflow: inherit;
}
#search-dropdown .results {
max-height: 300px;
overflow: auto;
}
#search-help table td {
padding-right: 10px;
}

View File

@ -7,6 +7,7 @@
position: absolute;
top: 40px;
bottom: 37px;
width: 96%;
}
}
@ -48,6 +49,7 @@
overflow-y: auto;
overflow-x: hidden;
}
}
.hamburger-panel {
@ -83,3 +85,89 @@
}
}
.search-menu {
.search-context .show-help {
float: right;
}
.heading {
padding: 5px 0 5px 5px;
.filter {
padding: 0 5px;
}
}
input[type='text'] {
width: 93%;
height: 22px;
margin: 5px;
padding: 5px;
}
.search-context {
padding: 0 5px;
label { margin-bottom: 0; }
}
.searching {
position: absolute;
top: 0.1em;
right: 1.25em;
.spinner {
width: 10px;
height: 10px;
border-width: 2px;
margin: 20px 0 0 0;
}
}
// I am ghetto using this to display "Show More".. be warned
.no-results {
padding: 5px;
text-align: center;
}
.filter {
padding: 0;
&:hover {background: transparent;}
}
.search-link {
.badge-category-parent {
line-height: 0.8em;
}
.topic-title {
margin-right: 6px;
}
.topic-statuses {
float: none;
display: inline-block;
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
margin: 0;
.fa {
margin: 0;
}
}
}
li:not(.category):not(.heading) {
font-size: 0.929em;
line-height: 16px;
.fa {
font-size: inherit;
}
a {
display: block;
padding: 5px;
transition: all linear .15s;
}
&:hover a:not(.badge-notification) {
background-color: dark-light-diff($highlight, $secondary, 50%, -55%);
}
button {margin-left: 5px;}
}
}

View File

@ -23,15 +23,8 @@ test("header", () => {
// Search
click("#search-button");
andThen(() => {
ok(exists("#search-dropdown:visible"), "after clicking a button search box opens");
not(exists("#search-dropdown .heading"), "initially, immediately after opening, search box is empty");
ok(exists(".search-menu:visible"), "after clicking a button search box opens");
not(exists(".search-menu .heading"), "initially, immediately after opening, search box is empty");
});
// Perform Search
// TODO how do I fix the fixture to be a POST instead of a GET @eviltrout
// fillIn("#search-term", "hello");
// andThen(() => {
// ok(exists("#search-dropdown .heading"), "when user completes a search, search box shows search results");
// equal(find("#search-dropdown .results a:first").attr("href"), "/t/hello-bar-integration-issues/17638", "there is a search result");
// });
});

View File

@ -8,11 +8,11 @@ test("search", (assert) => {
andThen(() => {
assert.ok(exists('#search-term'), 'it shows the search bar');
assert.ok(!exists('#search-dropdown .results ul li'), 'no results by default');
assert.ok(!exists('.search-menu .results ul li'), 'no results by default');
});
fillIn('#search-term', 'dev');
andThen(() => {
assert.ok(exists('#search-dropdown .results ul li'), 'it shows results');
assert.ok(exists('.search-menu .results ul li'), 'it shows results');
});
});