FEATURE: on mobile take users to full page search

UX: improve styling on full page search page
FEATURE: allow search context in full page search
FEATURE: visited color link for full page search
FIX: broken search help on fulls page search page
FEATURE: allow preload store to return a null
FEATURE: "mobileAction" for the header buttons
This commit is contained in:
Sam 2015-09-08 11:03:51 +10:00
parent e37dd5a393
commit e13ed24122
15 changed files with 203 additions and 43 deletions

View File

@ -13,6 +13,12 @@ export default Ember.Component.extend({
actions: {
toggle() {
if (Discourse.Mobile.mobileView && this.get('mobileAction')) {
this.sendAction('mobileAction');
return;
}
if (this.siteSettings.login_required && !this.currentUser) {
this.sendAction('loginAction');
} else {

View File

@ -1,4 +1,4 @@
import searchForTerm from 'discourse/lib/search-for-term';
import {searchForTerm, searchContextDescription} 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';
@ -48,18 +48,7 @@ export default Ember.Component.extend({
@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');
}
}
return searchContextDescription(Em.get(ctx, 'type'), Em.get(ctx, 'user.username') || Em.get(ctx, 'category.name'));
},
@observes('searchService.searchContextEnabled')

View File

@ -1,30 +1,65 @@
import { translateResults } from "discourse/lib/search-for-term";
import { translateResults, searchContextDescription } from "discourse/lib/search";
import showModal from 'discourse/lib/show-modal';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import Category from 'discourse/models/category';
export default Ember.Controller.extend({
needs: ["application"],
loading: Em.computed.not("model"),
queryParams: ["q"],
queryParams: ["q", "context_id", "context", "skip_context"],
q: null,
selected: [],
context_id: null,
context: null,
modelChanged: function() {
@computed('skip_context', 'context')
searchContextEnabled: {
get(skip,context){
return (!skip && context) || skip === "false";
},
set(val) {
this.set('skip_context', val ? "false" : "true" )
}
},
@computed('context', 'context_id')
searchContextDescription(context, id){
var name = id;
if (context === 'category') {
var category = Category.findById(id);
if (!category) {return;}
name = category.get('name');
}
return searchContextDescription(context, name);
},
@computed('q')
searchActive(q){
return q && q.length > 0;
},
@observes('model')
modelChanged() {
if (this.get("searchTerm") !== this.get("q")) {
this.set("searchTerm", this.get("q"));
}
}.observes("model"),
},
qChanged: function() {
@observes('q')
qChanged() {
const model = this.get("model");
if (model && this.get("model.q") !== this.get("q")) {
this.set("searchTerm", this.get("q"));
this.send("search");
}
}.observes("q"),
},
_showFooter: function() {
@observes('loading')
_showFooter() {
this.set("controllers.application.showFooter", !this.get("loading"));
}.observes("loading"),
},
canBulkSelect: Em.computed.alias('currentUser.staff'),
@ -32,9 +67,19 @@ export default Ember.Controller.extend({
this.set("q", this.get("searchTerm"));
this.set("model", null);
Discourse.ajax("/search", { data: { q: this.get("searchTerm") } }).then(results => {
var args = { q: this.get("searchTerm") };
const skip = this.get("skip_context");
if ((!skip && this.get('context')) || skip==="false"){
args.search_context = {
type: this.get('context'),
id: this.get('context_id')
};
}
Discourse.ajax("/search", { data: args }).then(results => {
this.set("model", translateResults(results) || {});
this.set("model.q", this.get("q"));
// this.set("model.q", this.get("q"));
});
},
@ -51,6 +96,13 @@ export default Ember.Controller.extend({
this.search();
},
showSearchHelp() {
// TODO: dupe code should be centralized
Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then((model) => {
showModal('searchHelp', { model });
});
},
search() {
this.search();
}

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
const HeaderController = Ember.Controller.extend({
topic: null,
showExtraInfo: null,
@ -18,6 +20,17 @@ const HeaderController = Ember.Controller.extend({
actions: {
fullPageSearch() {
const searchService = this.container.lookup('search-service:main');
const context = searchService.get('searchContext');
var params = "";
if (context) {
params = `?context=${context.type}&context_id=${context.id}`;
}
DiscourseURL.routeTo('/search' + params);
},
toggleMenuPanel(visibleProp) {
this.toggleProperty(visibleProp);
this.appEvents.trigger('dropdowns:closeAll');

View File

@ -86,4 +86,19 @@ function searchForTerm(term, opts) {
return promise;
}
export default searchForTerm;
const searchContextDescription = function(type, name){
if (type) {
switch(type) {
case 'topic':
return I18n.t('search.context.topic');
case 'user':
return I18n.t('search.context.user', {username: name});
case 'category':
return I18n.t('search.context.category', {category: name});
case 'private_messages':
return I18n.t('search.context.private_messages');
}
}
};
export { searchForTerm, searchContextDescription };

View File

@ -1,15 +1,24 @@
import { translateResults } from "discourse/lib/search-for-term";
import { translateResults } from "discourse/lib/search";
export default Discourse.Route.extend({
queryParams: { q: {} },
queryParams: { q: {}, "context-id": {}, context: {} },
model(params) {
return PreloadStore.getAndRemove("search", function() {
return Discourse.ajax("/search", { data: { q: params.q } });
if (params.q && params.q.length > 2) {
var args = { q: params.q };
if (params.context_id && !args.skip_context) {
args.search_context = {
type: params.context,
id: params.context_id
}
}
return Discourse.ajax("/search", { data: args });
} else {
return null;
}
}).then(results => {
const model = translateResults(results) || {};
model.q = params.q;
return model;
return (results && translateResults(results)) || {};
});
},

View File

@ -9,11 +9,22 @@
{{/if}}
</div>
{{#if context}}
<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}}
<h3>
{{i18n "search.no_results"}} <a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a>
{{#if searchActive}}
{{i18n "search.no_results"}}
{{/if}}
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "search.search_help"}}</a>
</h3>
{{/unless}}

View File

@ -23,6 +23,7 @@
{{#header-dropdown iconId="search-button"
icon="search"
toggleVisible=searchVisible
mobileAction="fullPageSearch"
loginAction="showLogin"
title="search.title"}}
{{/header-dropdown}}

View File

@ -1,5 +1,5 @@
import debounce from 'discourse/lib/debounce';
import searchForTerm from 'discourse/lib/search-for-term';
import { searchForTerm } from 'discourse/lib/search';
export default Ember.View.extend({
templateName: 'choose_topic',

View File

@ -35,7 +35,7 @@
//= require_tree ./discourse/mixins
//= require ./discourse/lib/ajax-error
//= require ./discourse/lib/markdown
//= require ./discourse/lib/search-for-term
//= require ./discourse/lib/search
//= require ./discourse/lib/user-search
//= require ./discourse/lib/export-csv
//= require ./discourse/lib/autocomplete

View File

@ -42,7 +42,7 @@ window.PreloadStore = {
var result = finder();
// If the finder returns a promise, we support that too
if (result.then) {
if (result && result.then) {
result.then(function(result) {
return resolve(result);
}, function(result) {

View File

@ -18,10 +18,20 @@
top: -3px;
margin-right: 4px;
}
a.search-link:visited .topic-title {
color: scale-color($tertiary, $lightness: 15%);
}
.search-link {
.topic-statuses, .topic-title {
font-size: 1.25em;
}
.topic-statuses {
float: none;
display: inline-block;
color: $primary;
font-size: 1.0em;
}
}
.blurb {
font-size: 1.0em;
@ -50,3 +60,7 @@
.search-footer {
margin-bottom: 30px;
}
.panel-body-contents .search-context label {
float: left;
}

View File

@ -19,6 +19,7 @@
@import "mobile/history";
@import "mobile/directory";
@import "mobile/menu-panel";
@import "mobile/search";
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */

View File

@ -0,0 +1,16 @@
.search button.btn-primary, .search button.btn {
float: none;
}
.search.row {
margin-top: 5px;
}
.search.row input.search {
height: 25px;
width: 69%;
}
.fps-search-context {
margin-bottom: 15px;
}

View File

@ -9,7 +9,20 @@ class SearchController < ApplicationController
end
def show
search = Search.new(params[:q], type_filter: 'topic', guardian: guardian, include_blurbs: true, blurb_length: 300)
search_args = {
type_filter: 'topic',
guardian: guardian,
include_blurbs: true,
blurb_length: 300
}
context, type = lookup_search_context
if context
search_args[:search_context] = context
search_args[:type_filter] = type if type
end
search = Search.new(params[:q], search_args)
result = search.execute
serializer = serialize_data(result, GroupedSearchResultSerializer, result: result)
@ -34,7 +47,29 @@ class SearchController < ApplicationController
search_args[:include_blurbs] = params[:include_blurbs] == "true" if params[:include_blurbs].present?
search_args[:search_for_id] = true if params[:search_for_id].present?
context,type = lookup_search_context
if context
search_args[:search_context] = context
search_args[:type_filter] = type if type
end
search = Search.new(params[:term], search_args.symbolize_keys)
result = search.execute
render_serialized(result, GroupedSearchResultSerializer, result: result)
end
protected
def lookup_search_context
return if params[:skip_context] == "true"
search_context = params[:search_context]
unless search_context
if (context = params[:context]) && (id = params[:context_id])
search_context = {type: context, id: id}
end
end
if search_context.present?
raise Discourse::InvalidParameters.new(:search_context) unless SearchController.valid_context_types.include?(search_context[:type])
@ -43,23 +78,21 @@ class SearchController < ApplicationController
# A user is found by username
context_obj = nil
if ['user','private_messages'].include? search_context[:type]
context_obj = User.find_by(username_lower: params[:search_context][:id].downcase)
context_obj = User.find_by(username_lower: search_context[:id].downcase)
else
klass = search_context[:type].classify.constantize
context_obj = klass.find_by(id: params[:search_context][:id])
context_obj = klass.find_by(id: search_context[:id])
end
type_filter = nil
if search_context[:type] == 'private_messages'
search_args[:type_filter] = 'private_messages'
type_filter = 'private_messages'
end
guardian.ensure_can_see!(context_obj)
search_args[:search_context] = context_obj
end
search = Search.new(params[:term], search_args.symbolize_keys)
result = search.execute
render_serialized(result, GroupedSearchResultSerializer, result: result)
[context_obj, type_filter]
end
end
end