From e60c74d3c1c0dcacd3bf9b0e17fabb290b6e9b95 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 7 Aug 2020 12:43:09 +0800 Subject: [PATCH] FEATURE: Use PG `ts_headline` for highlighting topic title in search. --- .../discourse/app/lib/topic-fancy-title.js | 18 +++++++++++ .../discourse/app/models/bookmark.js | 16 ++-------- .../javascripts/discourse/app/models/post.js | 14 +++++++++ .../javascripts/discourse/app/models/topic.js | 14 ++------- .../app/templates/full-page-search.hbs | 10 ++++++- app/serializers/search_post_serializer.rb | 14 ++++++++- config/site_settings.yml | 1 + lib/search.rb | 7 ++++- spec/components/search_spec.rb | 6 +++- spec/requests/search_controller_spec.rb | 30 ++++++++++++++----- 10 files changed, 93 insertions(+), 37 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/lib/topic-fancy-title.js diff --git a/app/assets/javascripts/discourse/app/lib/topic-fancy-title.js b/app/assets/javascripts/discourse/app/lib/topic-fancy-title.js new file mode 100644 index 00000000000..1da4ddaad8e --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/topic-fancy-title.js @@ -0,0 +1,18 @@ +import Site from "discourse/models/site"; +import { censor } from "pretty-text/censored-words"; +import { emojiUnescape } from "discourse/lib/text"; +import { isRTL } from "discourse/lib/text-direction"; + +export function fancyTitle(title, supportMixedTextDirection) { + let fancyTitle = censor( + emojiUnescape(title) || "", + Site.currentProp("censored_regexp") + ); + + if (supportMixedTextDirection) { + const titleDir = isRTL(title) ? "rtl" : "ltr"; + return `${fancyTitle}`; + } + + return fancyTitle; +} diff --git a/app/assets/javascripts/discourse/app/models/bookmark.js b/app/assets/javascripts/discourse/app/models/bookmark.js index 1fb54720f8c..51da517b8c4 100644 --- a/app/assets/javascripts/discourse/app/models/bookmark.js +++ b/app/assets/javascripts/discourse/app/models/bookmark.js @@ -2,10 +2,7 @@ import getURL from "discourse-common/lib/get-url"; import I18n from "I18n"; import Category from "discourse/models/category"; import User from "discourse/models/user"; -import { isRTL } from "discourse/lib/text-direction"; -import { censor } from "pretty-text/censored-words"; -import { emojiUnescape } from "discourse/lib/text"; -import Site from "discourse/models/site"; +import { fancyTitle } from "discourse/lib/topic-fancy-title"; import { longDate } from "discourse/lib/formatter"; import { none } from "@ember/object/computed"; import { computed } from "@ember/object"; @@ -78,16 +75,7 @@ const Bookmark = RestModel.extend({ @discourseComputed("title") fancyTitle(title) { - let fancyTitle = censor( - emojiUnescape(title) || "", - Site.currentProp("censored_regexp") - ); - - if (this.siteSettings.support_mixed_text_direction) { - const titleDir = isRTL(title) ? "rtl" : "ltr"; - return `${fancyTitle}`; - } - return fancyTitle; + return fancyTitle(title, this.siteSettings.support_mixed_text_direction); }, @discourseComputed("created_at") diff --git a/app/assets/javascripts/discourse/app/models/post.js b/app/assets/javascripts/discourse/app/models/post.js index 6c211a8fe44..f45a9668c1e 100644 --- a/app/assets/javascripts/discourse/app/models/post.js +++ b/app/assets/javascripts/discourse/app/models/post.js @@ -16,6 +16,7 @@ import { Promise } from "rsvp"; import Site from "discourse/models/site"; import User from "discourse/models/user"; import showModal from "discourse/lib/show-modal"; +import { fancyTitle } from "discourse/lib/topic-fancy-title"; const Post = RestModel.extend({ @discourseComputed("url") @@ -102,6 +103,19 @@ const Post = RestModel.extend({ ); }, + @discourseComputed( + "siteSettings.use_pg_headlines_for_excerpt", + "topic_title_headline" + ) + useTopicTitleHeadline(enabled, title) { + return enabled && title; + }, + + @discourseComputed("topic_title_headline") + topicTitleHead(title) { + return fancyTitle(title, this.siteSettings.support_mixed_text_direction); + }, + afterUpdate(res) { if (res.category) { this.site.updateCategory(res.category); diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js index 5815893c99f..e3e6b01f620 100644 --- a/app/assets/javascripts/discourse/app/models/topic.js +++ b/app/assets/javascripts/discourse/app/models/topic.js @@ -7,13 +7,12 @@ import { flushMap } from "discourse/models/store"; import RestModel from "discourse/models/rest"; import { propertyEqual, fmt } from "discourse/lib/computed"; import { longDate } from "discourse/lib/formatter"; -import { isRTL } from "discourse/lib/text-direction"; import ActionSummary from "discourse/models/action-summary"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { censor } from "pretty-text/censored-words"; import { emojiUnescape } from "discourse/lib/text"; import PreloadStore from "discourse/lib/preload-store"; import { userPath } from "discourse/lib/url"; +import { fancyTitle } from "discourse/lib/topic-fancy-title"; import discourseComputed, { observes, on @@ -119,16 +118,7 @@ const Topic = RestModel.extend({ @discourseComputed("fancy_title") fancyTitle(title) { - let fancyTitle = censor( - emojiUnescape(title) || "", - Site.currentProp("censored_regexp") - ); - - if (this.siteSettings.support_mixed_text_direction) { - const titleDir = isRTL(title) ? "rtl" : "ltr"; - return `${fancyTitle}`; - } - return fancyTitle; + return fancyTitle(title, this.siteSettings.support_mixed_text_direction); }, // returns createdAt if there's no bumped date diff --git a/app/assets/javascripts/discourse/app/templates/full-page-search.hbs b/app/assets/javascripts/discourse/app/templates/full-page-search.hbs index dedf5bed201..3f1657c013a 100644 --- a/app/assets/javascripts/discourse/app/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/app/templates/full-page-search.hbs @@ -88,7 +88,15 @@ {{topic-status topic=result.topic disableActions=true showPrivateMessageIcon=true}} - {{#highlight-search highlight=q}}{{html-safe result.topic.fancyTitle}}{{/highlight-search}} + + {{#if result.useTopicTitleHeadline}} + {{html-safe result.topicTitleHead}} + {{else}} + {{#highlight-search highlight=q}} + {{html-safe result.topic.fancyTitle}} + {{/highlight-search}} + {{/if}} +
diff --git a/app/serializers/search_post_serializer.rb b/app/serializers/search_post_serializer.rb index 0759dac792f..bca2f7e04eb 100644 --- a/app/serializers/search_post_serializer.rb +++ b/app/serializers/search_post_serializer.rb @@ -3,7 +3,19 @@ class SearchPostSerializer < BasicPostSerializer has_one :topic, serializer: SearchTopicListItemSerializer - attributes :like_count, :blurb, :post_number + attributes :like_count, :blurb, :post_number, :topic_title_headline + + def include_topic_title_headline? + if SiteSetting.use_pg_headlines_for_excerpt + object.topic_title_headline.present? + else + false + end + end + + def topic_title_headline + object.topic_title_headline + end def blurb options[:result].blurb(object) diff --git a/config/site_settings.yml b/config/site_settings.yml index 47d8d60383c..a325968330c 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1796,6 +1796,7 @@ search: use_pg_headlines_for_excerpt: default: false hidden: true + client: true search_ranking_normalization: default: "0" hidden: true diff --git a/lib/search.rb b/lib/search.rb index d8f2e40bb3c..fe19f17937e 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -1166,10 +1166,15 @@ class Search def posts_scope(default_scope = Post.all) if SiteSetting.use_pg_headlines_for_excerpt + search_term = @term.present? ? PG::Connection.escape_string(@term) : nil + ts_config = default_ts_config + default_scope .joins("INNER JOIN post_search_data pd ON pd.post_id = posts.id") + .joins("INNER JOIN topics t1 ON t1.id = posts.topic_id") .select( - "TS_HEADLINE(#{default_ts_config}, pd.raw_data, PLAINTO_TSQUERY(#{default_ts_config}, '#{@term.present? ? PG::Connection.escape_string(@term) : nil}'), 'ShortWord=0, MaxFragments=1, MinWords=50, MaxWords=51, StartSel='''', StopSel=''''') AS headline", + "TS_HEADLINE(#{ts_config}, t1.fancy_title, PLAINTO_TSQUERY(#{ts_config}, '#{search_term}'), 'StartSel='''', StopSel=''''') AS topic_title_headline", + "TS_HEADLINE(#{ts_config}, pd.raw_data, PLAINTO_TSQUERY(#{ts_config}, '#{search_term}'), 'ShortWord=0, MaxFragments=1, MinWords=50, MaxWords=51, StartSel='''', StopSel=''''') AS headline", default_scope.arel.projections ) else diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 00ceb74a535..85c0183b8f2 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -422,7 +422,11 @@ describe Search do ) expect(result.posts.map(&:id)).to contain_exactly(reply.id) - expect(result.blurb(result.posts.first)).to eq(expected_blurb) + + post = result.posts.first + + expect(result.blurb(post)).to eq(expected_blurb) + expect(post.topic_title_headline).to eq(topic.fancy_title) end it 'returns the right post and blurb for searches with phrase' do diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 7a54ced1a24..45ff4eab6ad 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -101,6 +101,10 @@ describe SearchController do it "can search correctly" do SiteSetting.use_pg_headlines_for_excerpt = true + awesome_post_3 = Fabricate(:post, + topic: Fabricate(:topic, title: 'this is an awesome title') + ) + get "/search/query.json", params: { term: 'awesome' } @@ -109,14 +113,26 @@ describe SearchController do data = response.parsed_body - expect(data['posts'].length).to eq(2) - expect(data['posts'][0]['id']).to eq(awesome_post_2.id) - expect(data['posts'][0]['blurb']).to eq("this is my really awesome post") - expect(data['topics'][0]['id']).to eq(awesome_post_2.topic_id) + expect(data['posts'].length).to eq(3) - expect(data['posts'][1]['id']).to eq(awesome_post.id) - expect(data['posts'][1]['blurb']).to eq("this is my really awesome post") - expect(data['topics'][1]['id']).to eq(awesome_post.topic_id) + expect(data['posts'][0]['id']).to eq(awesome_post_3.id) + expect(data['posts'][0]['blurb']).to eq(awesome_post_3.raw) + expect(data['posts'][0]['topic_title_headline']).to eq( + "This is an awesome title" + ) + expect(data['topics'][0]['id']).to eq(awesome_post_3.topic_id) + + expect(data['posts'][1]['id']).to eq(awesome_post_2.id) + expect(data['posts'][1]['blurb']).to eq( + "this is my really awesome post" + ) + expect(data['topics'][1]['id']).to eq(awesome_post_2.topic_id) + + expect(data['posts'][2]['id']).to eq(awesome_post.id) + expect(data['posts'][2]['blurb']).to eq( + "this is my really awesome post" + ) + expect(data['topics'][2]['id']).to eq(awesome_post.topic_id) end it "can search correctly with advanced search filters" do