From 41e427bd2ec95d1351b0bea33dd426ad8317173a Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 18:09:08 +1000 Subject: [PATCH] Work in progress, full page search --- .../controllers/discovery-sortable.js.es6 | 3 +- .../controllers/discovery/topics.js.es6 | 2 + .../controllers/navigation/default.js.es6 | 4 +- .../discourse/helpers/topic-link.js.es6 | 3 +- .../discourse/models/topic-list.js.es6 | 9 +++- .../javascripts/discourse/models/topic.js.es6 | 3 +- .../templates/components/basic-topic-list.hbs | 4 +- .../discourse/templates/discovery/topics.hbs | 1 + .../discourse/views/topic-list-item.js.es6 | 4 ++ app/controllers/list_controller.rb | 3 +- app/models/topic.rb | 2 + app/serializers/listable_topic_serializer.rb | 19 +++++++- lib/search.rb | 4 -- lib/topic_query.rb | 43 ++++++++++++++++++- spec/components/topic_query_spec.rb | 22 +++++++++- 15 files changed, 108 insertions(+), 18 deletions(-) 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)