mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
UX: move search to its own route
previously search was bundled with discovery, something that makes stuff confusing internally
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import DiscourseController from 'discourse/controllers/controller';
|
||||
import { translateResults } from 'discourse/lib/search-for-term';
|
||||
|
||||
export default DiscourseController.extend({
|
||||
loading: Em.computed.not('model'),
|
||||
queryParams: ['q'],
|
||||
q: null,
|
||||
modelChanged: function(){
|
||||
if (this.get('searchTerm') !== this.get('q')) {
|
||||
this.set('searchTerm', this.get('q'));
|
||||
}
|
||||
}.observes('model'),
|
||||
|
||||
qChanged: function(){
|
||||
var model = this.get('model');
|
||||
if (model && this.get('model.q') !== this.get('q')){
|
||||
this.set('searchTerm', this.get('q'));
|
||||
this.send('search');
|
||||
}
|
||||
}.observes('q'),
|
||||
actions: {
|
||||
search: function(){
|
||||
var self = this;
|
||||
this.set('q', this.get('searchTerm'));
|
||||
this.set('model', null);
|
||||
|
||||
Discourse.ajax('/search2', {data: {q: this.get('searchTerm')}}).then(function(results) {
|
||||
self.set('model', translateResults(results) || {});
|
||||
self.set('model.q', self.get('q'));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,67 @@
|
||||
import Topic from 'discourse/models/topic';
|
||||
|
||||
export function translateResults(results, opts) {
|
||||
if (!opts) opts = {};
|
||||
|
||||
// Topics might not be included
|
||||
if (!results.topics) { results.topics = []; }
|
||||
if (!results.users) { results.users = []; }
|
||||
if (!results.posts) { results.posts = []; }
|
||||
if (!results.categories) { results.categories = []; }
|
||||
|
||||
const topicMap = {};
|
||||
results.topics = results.topics.map(function(topic){
|
||||
topic = Topic.create(topic);
|
||||
topicMap[topic.id] = topic;
|
||||
return topic;
|
||||
});
|
||||
|
||||
results.posts = results.posts.map(function(post){
|
||||
post = Discourse.Post.create(post);
|
||||
post.set('topic', topicMap[post.topic_id]);
|
||||
return post;
|
||||
});
|
||||
|
||||
results.users = results.users.map(function(user){
|
||||
user = Discourse.User.create(user);
|
||||
return user;
|
||||
});
|
||||
|
||||
results.categories = results.categories.map(function(category){
|
||||
return Discourse.Category.list().findProperty('id', category.id);
|
||||
}).compact();
|
||||
|
||||
const r = results.grouped_search_result;
|
||||
results.resultTypes = [];
|
||||
|
||||
// TODO: consider refactoring front end to take a better structure
|
||||
[['topic','posts'],['user','users'],['category','categories']].forEach(function(pair){
|
||||
const type = pair[0], name = pair[1];
|
||||
if (results[name].length > 0) {
|
||||
var result = {
|
||||
results: results[name],
|
||||
componentName: "search-result-" + ((opts.searchContext && opts.searchContext.type === 'topic' && type === 'topic') ? 'post' : type),
|
||||
type,
|
||||
more: r['more_' + name]
|
||||
};
|
||||
|
||||
if (result.more && name === "posts" && opts.fullSearchUrl) {
|
||||
result.more = false;
|
||||
result.moreUrl = opts.fullSearchUrl;
|
||||
}
|
||||
|
||||
results.resultTypes.push(result);
|
||||
}
|
||||
});
|
||||
|
||||
const noResults = !!(results.topics.length === 0 &&
|
||||
results.posts.length === 0 &&
|
||||
results.users.length === 0 &&
|
||||
results.categories.length === 0);
|
||||
|
||||
return noResults ? null : Em.Object.create(results);
|
||||
}
|
||||
|
||||
function searchForTerm(term, opts) {
|
||||
if (!opts) opts = {};
|
||||
|
||||
@@ -16,63 +78,7 @@ function searchForTerm(term, opts) {
|
||||
}
|
||||
|
||||
return Discourse.ajax('/search/query', { data: data }).then(function(results){
|
||||
// Topics might not be included
|
||||
if (!results.topics) { results.topics = []; }
|
||||
if (!results.users) { results.users = []; }
|
||||
if (!results.posts) { results.posts = []; }
|
||||
if (!results.categories) { results.categories = []; }
|
||||
|
||||
const topicMap = {};
|
||||
results.topics = results.topics.map(function(topic){
|
||||
topic = Topic.create(topic);
|
||||
topicMap[topic.id] = topic;
|
||||
return topic;
|
||||
});
|
||||
|
||||
results.posts = results.posts.map(function(post){
|
||||
post = Discourse.Post.create(post);
|
||||
post.set('topic', topicMap[post.topic_id]);
|
||||
return post;
|
||||
});
|
||||
|
||||
results.users = results.users.map(function(user){
|
||||
user = Discourse.User.create(user);
|
||||
return user;
|
||||
});
|
||||
|
||||
results.categories = results.categories.map(function(category){
|
||||
return Discourse.Category.list().findProperty('id', category.id);
|
||||
}).compact();
|
||||
|
||||
const r = results.grouped_search_result;
|
||||
results.resultTypes = [];
|
||||
|
||||
// TODO: consider refactoring front end to take a better structure
|
||||
[['topic','posts'],['user','users'],['category','categories']].forEach(function(pair){
|
||||
const type = pair[0], name = pair[1];
|
||||
if (results[name].length > 0) {
|
||||
var result = {
|
||||
results: results[name],
|
||||
componentName: "search-result-" + ((opts.searchContext && opts.searchContext.type === 'topic' && type === 'topic') ? 'post' : type),
|
||||
type,
|
||||
more: r['more_' + name]
|
||||
};
|
||||
|
||||
if (result.more && name === "posts" && opts.fullSearchUrl) {
|
||||
result.more = false;
|
||||
result.moreUrl = opts.fullSearchUrl;
|
||||
}
|
||||
|
||||
results.resultTypes.push(result);
|
||||
}
|
||||
});
|
||||
|
||||
const noResults = !!(results.topics.length === 0 &&
|
||||
results.posts.length === 0 &&
|
||||
results.users.length === 0 &&
|
||||
results.categories.length === 0);
|
||||
|
||||
return noResults ? null : Em.Object.create(results);
|
||||
return translateResults(results, opts);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -99,4 +99,6 @@ export default function() {
|
||||
});
|
||||
|
||||
this.resource('queued-posts', { path: '/queued-posts' });
|
||||
|
||||
this.route('full-page-search', {path: '/search2'});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { translateResults } from 'discourse/lib/search-for-term';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: {
|
||||
q: {
|
||||
}
|
||||
},
|
||||
model: function(params) {
|
||||
return PreloadStore.getAndRemove("search", function() {
|
||||
return Discourse.ajax('/search2', {data: {q: params.q}});
|
||||
}).then(function(results){
|
||||
var model = translateResults(results) || {};
|
||||
model.q = params.q;
|
||||
return model;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
<div class="search row">
|
||||
{{input type="text" value=searchTerm class="input-xxlarge search no-blur" action="search"}}
|
||||
<button {{action "search"}} class="btn btn-primary"><i class='fa fa-search'></i></button>
|
||||
</div>
|
||||
|
||||
{{#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>
|
||||
</h3>
|
||||
{{/unless}}
|
||||
|
||||
{{#each model.posts as |result|}}
|
||||
<div class='fps-result'>
|
||||
<div class='topic'>
|
||||
{{avatar result imageSize="tiny"}}
|
||||
<a class='search-link' href='{{unbound result.url}}'>
|
||||
{{topic-status topic=result.topic disableActions=true}}<span class='topic-title'>{{unbound result.topic.title}}</span>
|
||||
</a>{{category-link result.topic.category}}
|
||||
</div>
|
||||
<div class='blurb container'>
|
||||
{{format-age result.created_at}}{{#if result.blurb}}
|
||||
–
|
||||
{{{unbound result.blurb}}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</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}}
|
||||
|
||||
{{/conditional-loading-spinner}}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import ScrollTop from 'discourse/mixins/scroll-top';
|
||||
|
||||
export default Ember.View.extend(ScrollTop, {
|
||||
|
||||
_highlightOnInsert: function() {
|
||||
const term = this.get('controller.q');
|
||||
const self = this;
|
||||
|
||||
if(!_.isEmpty(term)) {
|
||||
Em.run.next(function(){
|
||||
self.$('.blurb').highlight(term.split(/\s+/), {className: 'search-highlight'});
|
||||
self.$('.topic-title').highlight(term.split(/\s+/), {className: 'search-highlight'} );
|
||||
});
|
||||
}
|
||||
}.observes('controller.model').on('didInsertElement')
|
||||
});
|
||||
43
app/assets/stylesheets/common/base/search.scss
Normal file
43
app/assets/stylesheets/common/base/search.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
.fps-result {
|
||||
margin-bottom: 25px;
|
||||
max-width: 675px;
|
||||
.topic {
|
||||
a {
|
||||
color: $primary;
|
||||
}
|
||||
line-height: 20px;
|
||||
}
|
||||
.avatar {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.search-link {
|
||||
.topic-statuses, .topic-title {
|
||||
font-size: 1.15em;
|
||||
}
|
||||
}
|
||||
.blurb {
|
||||
font-size: 1.0em;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
clear: both;
|
||||
color: scale-color($primary, $lightness: 45%);
|
||||
|
||||
.search-highlight {
|
||||
color: scale-color($primary, $lightness: 25%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search.row {
|
||||
margin-bottom: 15px;
|
||||
input {
|
||||
height: 22px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-footer {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
@@ -2,10 +2,30 @@ require_dependency 'search'
|
||||
|
||||
class SearchController < ApplicationController
|
||||
|
||||
skip_before_filter :check_xhr, only: :show
|
||||
|
||||
def self.valid_context_types
|
||||
%w{user topic category private_messages}
|
||||
end
|
||||
|
||||
def show
|
||||
search = Search.new(params[:q], type_filter: 'topic', guardian: guardian, include_blurbs: true, blurb_length: 300)
|
||||
result = search.execute
|
||||
|
||||
serializer = serialize_data(result, GroupedSearchResultSerializer, :result => result)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
store_preloaded("search", MultiJson.dump(serializer))
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_json_dump(serializer)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def query
|
||||
params.require(:term)
|
||||
|
||||
|
||||
0
app/views/search/show.html.erb
Normal file
0
app/views/search/show.html.erb
Normal file
Reference in New Issue
Block a user