From 2b10fdc97f1d421e024fb518ab43d6727307a5c5 Mon Sep 17 00:00:00 2001
From: Sam <sam.saffron@gmail.com>
Date: Mon, 17 Feb 2014 13:54:51 +1100
Subject: [PATCH] FEATURE: search auto scopes on topic first

---
 config/locales/server.en.yml   |  1 +
 lib/search.rb                  | 23 ++++++++++++++--------
 lib/search/search_result.rb    | 25 ++++++++++++++++++++----
 spec/components/search_spec.rb | 35 ++++++++++++++++++++++++++++++++++
 4 files changed, 72 insertions(+), 12 deletions(-)

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') }