diff --git a/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 b/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6
index c48aa54ff..d6077dd09 100644
--- a/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6
+++ b/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6
@@ -7,7 +7,8 @@ export var queryParams = {
status: { replace: true },
state: { replace: true },
search: { replace: true },
- max_posts: { replace: true }
+ max_posts: { replace: true },
+ q: { replace: true }
};
// Basic controller options
diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
index 15c8d66c2..d36adc423 100644
--- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
+++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
@@ -16,6 +16,8 @@ var controllerOpts = {
expandGloballyPinned: false,
expandAllPinned: false,
+ isSearch: Em.computed.equal('model.filter', 'search'),
+
actions: {
changeSort: function(sortBy) {
diff --git a/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 b/app/assets/javascripts/discourse/controllers/navigation/default.js.es6
index 65bf57319..54f63d6d0 100644
--- a/app/assets/javascripts/discourse/controllers/navigation/default.js.es6
+++ b/app/assets/javascripts/discourse/controllers/navigation/default.js.es6
@@ -13,13 +13,13 @@ export default DiscourseController.extend({
isSearch: Em.computed.equal('filterMode', 'search'),
- searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.search'),
+ searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.q'),
actions: {
search: function(){
var discovery = this.get('controllers.discovery/topics');
var model = discovery.get('model');
- discovery.set('search', this.get("searchTerm"));
+ discovery.set('q', this.get("searchTerm"));
model.refreshSort();
}
}
diff --git a/app/assets/javascripts/discourse/helpers/topic-link.js.es6 b/app/assets/javascripts/discourse/helpers/topic-link.js.es6
index f0703c6d9..105ec123a 100644
--- a/app/assets/javascripts/discourse/helpers/topic-link.js.es6
+++ b/app/assets/javascripts/discourse/helpers/topic-link.js.es6
@@ -2,5 +2,6 @@ import registerUnbound from 'discourse/helpers/register-unbound';
registerUnbound('topic-link', function(topic) {
var title = topic.get('fancyTitle');
- return new Handlebars.SafeString("" + title + "");
+ var url = topic.linked_post_number ? topic.urlForPostNumber(topic.linked_post_number) : topic.get('lastUnreadUrl');
+ return new Handlebars.SafeString("" + title + "");
});
diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6
index 7f158b924..ab14099c8 100644
--- a/app/assets/javascripts/discourse/models/topic-list.js.es6
+++ b/app/assets/javascripts/discourse/models/topic-list.js.es6
@@ -40,8 +40,8 @@ const TopicList = RestModel.extend({
},
refreshSort: function(order, ascending) {
- const self = this,
- params = this.get('params') || {};
+ const self = this;
+ var params = this.get('params') || {};
params.order = order || params.order;
@@ -51,6 +51,11 @@ const TopicList = RestModel.extend({
params.ascending = ascending;
}
+ if (params.q) {
+ // search is unique, nothing else allowed with it
+ params = {q: params.q};
+ }
+
this.set('loaded', false);
const store = this.store;
store.findFiltered('topicList', {filter: this.get('filter'), params}).then(function(tl) {
diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6
index bba5b4c2e..798053e3c 100644
--- a/app/assets/javascripts/discourse/models/topic.js.es6
+++ b/app/assets/javascripts/discourse/models/topic.js.es6
@@ -363,8 +363,7 @@ const Topic = RestModel.extend({
);
},
- excerptNotEmpty: Em.computed.notEmpty('excerpt'),
- hasExcerpt: Em.computed.and('pinned', 'excerptNotEmpty'),
+ hasExcerpt: Em.computed.notEmpty('excerpt'),
excerptTruncated: function() {
const e = this.get('excerpt');
diff --git a/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs
index f8afeb575..6d323d7aa 100644
--- a/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs
+++ b/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs
@@ -3,7 +3,9 @@
{{topic-list
showParticipants=showParticipants
hideCategory=hideCategory
- topics=topics}}
+ topics=topics
+ expandExcerpts=expandExcerpts
+ }}
{{else}}
{{i18n 'choose_topic.none_found'}}
diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
index f3e452a4a..ec4c3d822 100644
--- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
@@ -46,6 +46,7 @@
selected=selected
expandGloballyPinned=expandGloballyPinned
expandAllPinned=expandAllPinned
+ expandExcerpts=isSearch
topics=model.topics}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 b/app/assets/javascripts/discourse/views/topic-list-item.js.es6
index d1d0ca7e6..f70fa98e6 100644
--- a/app/assets/javascripts/discourse/views/topic-list-item.js.es6
+++ b/app/assets/javascripts/discourse/views/topic-list-item.js.es6
@@ -65,6 +65,10 @@ export default Discourse.View.extend(StringBuffer, {
},
expandPinned: function() {
+ if (this.get('controller.expandExcerpts')) {
+ return true;
+ }
+
const pinned = this.get('topic.pinned');
if (!pinned) {
return false;
diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb
index 927519cb4..45d00fbc9 100644
--- a/app/controllers/list_controller.rb
+++ b/app/controllers/list_controller.rb
@@ -248,7 +248,8 @@ class ListController < ApplicationController
status: params[:status],
filter: params[:filter],
state: params[:state],
- search: params[:search]
+ search: params[:search],
+ q: params[:q]
}
options[:no_subcategories] = true if params[:no_subcategories] == 'true'
options[:slow_platform] = true if slow_platform?
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 6f444e1ab..a5be02dbe 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -106,7 +106,9 @@ class Topic < ActiveRecord::Base
has_one :first_post, -> {where post_number: 1}, class_name: Post
# When we want to temporarily attach some data to a forum topic (usually before serialization)
+ attr_accessor :search_data
attr_accessor :user_data
+
attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code
attr_accessor :participants
attr_accessor :topic_list
diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb
index 5620bccf0..79ffae02f 100644
--- a/app/serializers/listable_topic_serializer.rb
+++ b/app/serializers/listable_topic_serializer.rb
@@ -11,6 +11,7 @@ class ListableTopicSerializer < BasicTopicSerializer
:bumped_at,
:unseen,
:last_read_post_number,
+ :linked_post_number,
:unread,
:new_posts,
:pinned,
@@ -77,6 +78,22 @@ class ListableTopicSerializer < BasicTopicSerializer
!!object.user_data
end
+ def excerpt
+ if object.search_data
+ object.search_data[:excerpt]
+ else
+ object.excerpt
+ end
+ end
+
+ def include_linked_post_number?
+ object.search_data
+ end
+
+ def linked_post_number
+ object.search_data[:post_number]
+ end
+
alias :include_last_read_post_number? :has_user_data
def unread
@@ -90,7 +107,7 @@ class ListableTopicSerializer < BasicTopicSerializer
alias :include_new_posts? :has_user_data
def include_excerpt?
- pinned
+ pinned || object.search_data
end
def pinned
diff --git a/lib/search.rb b/lib/search.rb
index 97779db68..ad622435c 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -452,10 +452,6 @@ class Search
# double wrapping so we get correct row numbers
post_sql = "SELECT *, row_number() over() row_number FROM (#{post_sql}) xxx"
- # p Topic.exec_sql(post_sql).to_a
- # puts post_sql
- # p Topic.exec_sql("SELECT topic_id FROM topic_allowed_users WHERE user_id = 2").to_a
-
posts = Post.includes(:topic => :category)
.joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number")
.order('row_number')
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 13c3f9bb4..1146ceb6d 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -27,6 +27,7 @@ class TopicQuery
search
slow_platform
filter
+ q
).map(&:to_sym)
# Maps `order` to a columns in `topics`
@@ -71,7 +72,47 @@ class TopicQuery
end
def list_search
- create_list(:latest, {}, latest_results)
+
+ results = nil
+
+ if @options[:q].present?
+ search = Search.execute(@options[:q],
+ type_filter: 'topic',
+ guardian: Guardian.new(@user))
+
+ topic_ids = search.posts.map(&:topic_id)
+
+ if topic_ids.present?
+ sql = topic_ids.each_with_index.map do |id, idx|
+ "SELECT #{idx} pos, #{id} id"
+ end.join(" UNION ALL ")
+
+ results = Topic
+ .unscoped
+ .joins("JOIN (#{sql}) X on X.id = topics.id")
+ .order("X.pos")
+
+ posts_map = Hash[*search.posts.map{|p| [p.topic_id, p]}.flatten]
+ end
+ end
+
+ results ||= Topic.where("1=0")
+
+ if @user
+ results = results.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user.id.to_i})")
+ .references('tu')
+ end
+
+ list = create_list(:latest, {unordered: true}, results)
+
+
+ list.topics.each do |topic|
+ if post = posts_map[topic.id]
+ topic.search_data = {excerpt: search.blurb(post), post_number: post.post_number}
+ end
+ end
+
+ list
end
def list_read
diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb
index 54882d966..8bd9c5a11 100644
--- a/spec/components/topic_query_spec.rb
+++ b/spec/components/topic_query_spec.rb
@@ -43,15 +43,33 @@ describe TopicQuery do
context "list_topics_by" do
it "allows users to view their own invisible topics" do
- topic = Fabricate(:topic, user: user)
- invisible_topic = Fabricate(:topic, user: user, visible: false)
+ _topic = Fabricate(:topic, user: user)
+ _invisible_topic = Fabricate(:topic, user: user, visible: false)
expect(TopicQuery.new(nil).list_topics_by(user).topics.count).to eq(1)
expect(TopicQuery.new(user).list_topics_by(user).topics.count).to eq(2)
+
+ # search should return nothing normally
+ expect(TopicQuery.new(nil).list_search.topics.count).to eq(0)
end
end
+ context 'search' do
+ it 'can correctly search' do
+ # got to enable indexing
+ ActiveRecord::Base.observers.enable :all
+
+ p = create_post(raw: "I am super awesome and search will find me")
+ create_post(topic_id: p.topic_id, raw: "I am super spectacular post of doom")
+
+ results = TopicQuery.new(nil, q: "doom").list_search
+
+ expect(results.topics.count).to eq(1)
+
+ end
+ end
+
context 'bookmarks' do
it "filters and returns bookmarks correctly" do
post = Fabricate(:post)