diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 71f393c5796..bded28da848 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -828,6 +828,7 @@ en: invitee_accepted: "%{display_username} accepted your invitation" search: + within_post: "#%{post_number} by %{username}: %{excerpt}" types: category: 'Categories' topic: 'Topics' diff --git a/lib/search.rb b/lib/search.rb index 8bec5a9a9f2..6339bcb92f6 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -94,7 +94,7 @@ class Search extra_posts = posts_query(expected_topics * Search.burst_factor) extra_posts = extra_posts.where("posts.topic_id NOT in (?)", @results.topic_ids) if @results.topic_ids.present? extra_posts.each do |p| - @results.add_result(SearchResult.from_post(p)) + @results.add_result(SearchResult.from_post(p, @search_context, @term)) end end end @@ -162,6 +162,9 @@ class Search elsif @search_context.is_a?(Category) # If the context is a category, restrict posts to that category posts = posts.order("CASE WHEN topics.category_id = #{@search_context.id} THEN 0 ELSE 1 END") + elsif @search_context.is_a?(Topic) + posts = posts.order("CASE WHEN topics.id = #{@search_context.id} THEN 0 ELSE 1 END, + CASE WHEN topics.id = #{@search_context.id} THEN posts.post_number ELSE 999999 END") end end @@ -192,16 +195,20 @@ class Search def topic_search - # If we have a user filter, search all posts by default with a higher limit - posts = if @search_context.present? and @search_context.is_a?(User) - posts_query(@limit * Search.burst_factor) - else - posts_query(@limit).where(post_number: 1) - end + posts = if @search_context.is_a?(User) + # If we have a user filter, search all posts by default with a higher limit + posts_query(@limit * Search.burst_factor) + elsif @search_context.is_a?(Topic) + posts_query(@limit).where('posts.post_number = 1 OR posts.topic_id = ?', @search_context.id) + else + posts_query(@limit).where(post_number: 1) + end + posts.each do |p| - @results.add_result(SearchResult.from_post(p)) + @results.add_result(SearchResult.from_post(p, @search_context, @term)) end + end end diff --git a/lib/search/search_result.rb b/lib/search/search_result.rb index 039c96f705b..4785c59f039 100644 --- a/lib/search/search_result.rb +++ b/lib/search/search_result.rb @@ -1,6 +1,10 @@ class Search class SearchResult + class TextHelper + extend ActionView::Helpers::TextHelper + end + attr_accessor :type, :id # Category attributes @@ -36,14 +40,27 @@ class Search end end - def self.from_topic(t) - SearchResult.new(type: :topic, id: t.id, title: t.title, url: t.relative_url) + def self.from_topic(t, custom_title=nil) + SearchResult.new(type: :topic, id: t.id, title: custom_title || t.title, url: t.relative_url) end - def self.from_post(p) + def self.from_post(p, context, term) + custom_title = + if context && context.id == p.topic_id + excerpt = TextHelper.excerpt(p.raw, term.split(/\s+/)[0], radius: 30) + excerpt = TextHelper.truncate(p.raw, length: 50) if excerpt.blank? + I18n.t("search.within_post", + post_number: p.post_number, + username: p.user && p.user.username, + excerpt: excerpt + ) + end + if p.post_number == 1 # we want the topic link when it's the OP - SearchResult.from_topic(p.topic) + SearchResult.from_topic(p.topic, custom_title) + elsif context && context.id == p.topic_id + SearchResult.new(type: :topic, id: "_#{p.id}", title: custom_title, url: p.url) else SearchResult.new(type: :topic, id: p.topic.id, title: p.topic.title, url: p.url) end diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 0bf0ac91644..69e28061b84 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -125,6 +125,41 @@ describe Search do context 'topics' do let(:topic) { Fabricate(:topic) } + + context 'search within topic' do + + def new_post(raw, topic) + Fabricate(:post, topic: topic, topic_id: topic.id, user: topic.user, raw: raw) + end + + it 'displays multiple results within a topic' do + topic = Fabricate(:topic) + topic2 = Fabricate(:topic) + + new_post('this is the other post I am posting', topic2) + post1 = new_post('this is the other post I am posting', topic) + post2 = new_post('this is my first post I am posting', topic) + post3 = new_post('this is a real long and complicated bla this is my second post I am Posting birds + with more stuff bla bla', topic) + post4 = new_post('this is my fourth post I am posting', topic) + new_post('this is my fifth post I am posting', topic2) + + results = Search.new('posting', search_context: post1.topic).execute.find do |r| + r[:type] == "topic" + end[:results] + + results.find{|r| r[:title].include? 'birds'}.should_not be_nil + + results.map{|r| r[:id]}.should == [ + post1.topic_id, + "_#{post2.id}", + "_#{post3.id}", + "_#{post4.id}", + topic2.id] + + end + end + context 'searching the OP' do let!(:post) { Fabricate(:post, topic: topic, user: topic.user) } let(:result) { first_of_type(Search.new('hello', type_filter: 'topic').execute, 'topic') }