diff --git a/app/assets/javascripts/discourse/app/components/search-advanced-options.js b/app/assets/javascripts/discourse/app/components/search-advanced-options.js index 4667653051d..908e3914315 100644 --- a/app/assets/javascripts/discourse/app/components/search-advanced-options.js +++ b/app/assets/javascripts/discourse/app/components/search-advanced-options.js @@ -114,7 +114,7 @@ export default Component.extend({ this.setSearchedTermValueForTags(); let regExpInMatch = this.inOptions.map((option) => option.value).join("|"); - const REGEXP_IN_MATCH = new RegExp(`(in|with):(${regExpInMatch})`); + const REGEXP_IN_MATCH = new RegExp(`(in|with):(${regExpInMatch})`, "i"); this.setSearchedTermValue( "searchedTerms.in", @@ -145,7 +145,10 @@ export default Component.extend({ let regExpStatusMatch = this.statusOptions .map((status) => status.value) .join("|"); - const REGEXP_STATUS_MATCH = new RegExp(`status:(${regExpStatusMatch})`); + const REGEXP_STATUS_MATCH = new RegExp( + `status:(${regExpStatusMatch})`, + "i" + ); this.setSearchedTermValue( "searchedTerms.status", @@ -203,7 +206,7 @@ export default Component.extend({ let val = this.get(key); if (match.length !== 0) { - const userInput = match[0].replace(replaceRegEx, ""); + const userInput = match[0].replace(replaceRegEx, "").toLowerCase(); if (val !== userInput && userInput.length) { this.set(key, userInput); @@ -298,7 +301,9 @@ export default Component.extend({ if (match.length !== 0) { const existingInputWhen = this.get("searchedTerms.time.when"); - const userInputWhen = match[0].match(REGEXP_POST_TIME_WHEN)[0]; + const userInputWhen = match[0] + .match(REGEXP_POST_TIME_WHEN)[0] + .toLowerCase(); const existingInputDays = this.get("searchedTerms.time.days"); const userInputDays = match[0].replace(REGEXP_POST_TIME_PREFIX, ""); const properties = {}; @@ -549,7 +554,7 @@ export default Component.extend({ _updateSearchTermForIn() { let regExpInMatch = this.inOptions.map((option) => option.value).join("|"); - const REGEXP_IN_MATCH = new RegExp(`(in|with):(${regExpInMatch})`); + const REGEXP_IN_MATCH = new RegExp(`(in|with):(${regExpInMatch})`, "i"); const match = this.filterBlocks(REGEXP_IN_MATCH); const inFilter = this.get("searchedTerms.in"); @@ -577,7 +582,10 @@ export default Component.extend({ let regExpStatusMatch = this.statusOptions .map((status) => status.value) .join("|"); - const REGEXP_STATUS_MATCH = new RegExp(`status:(${regExpStatusMatch})`); + const REGEXP_STATUS_MATCH = new RegExp( + `status:(${regExpStatusMatch})`, + "i" + ); const match = this.filterBlocks(REGEXP_STATUS_MATCH); const statusFilter = this.get("searchedTerms.status"); diff --git a/lib/search.rb b/lib/search.rb index f2ded80c485..189e0f4f941 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -305,7 +305,7 @@ class Search Array.wrap(@custom_topic_eager_loads) end - advanced_filter(/^in:personal-direct$/) do |posts| + advanced_filter(/^in:personal-direct$/i) do |posts| if @guardian.user posts .joins("LEFT JOIN topic_allowed_groups tg ON posts.topic_id = tg.topic_id") @@ -325,27 +325,27 @@ class Search end end - advanced_filter(/^in:tagged$/) do |posts| + advanced_filter(/^in:tagged$/i) do |posts| posts .where('EXISTS (SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = posts.topic_id)') end - advanced_filter(/^in:untagged$/) do |posts| + advanced_filter(/^in:untagged$/i) do |posts| posts .joins("LEFT JOIN topic_tags ON topic_tags.topic_id = posts.topic_id") .where("topic_tags.id IS NULL") end - advanced_filter(/^status:open$/) do |posts| + advanced_filter(/^status:open$/i) do |posts| posts.where('NOT topics.closed AND NOT topics.archived') end - advanced_filter(/^status:closed$/) do |posts| + advanced_filter(/^status:closed$/i) do |posts| posts.where('topics.closed') end - advanced_filter(/^status:public$/) do |posts| + advanced_filter(/^status:public$/i) do |posts| category_ids = Category .where(read_restricted: false) .pluck(:id) @@ -353,39 +353,39 @@ class Search posts.where("topics.category_id in (?)", category_ids) end - advanced_filter(/^status:archived$/) do |posts| + advanced_filter(/^status:archived$/i) do |posts| posts.where('topics.archived') end - advanced_filter(/^status:noreplies$/) do |posts| + advanced_filter(/^status:noreplies$/i) do |posts| posts.where("topics.posts_count = 1") end - advanced_filter(/^status:single_user$/) do |posts| + advanced_filter(/^status:single_user$/i) do |posts| posts.where("topics.participant_count = 1") end - advanced_filter(/^posts_count:(\d+)$/) do |posts, match| + advanced_filter(/^posts_count:(\d+)$/i) do |posts, match| posts.where("topics.posts_count = ?", match.to_i) end - advanced_filter(/^min_post_count:(\d+)$/) do |posts, match| + advanced_filter(/^min_post_count:(\d+)$/i) do |posts, match| posts.where("topics.posts_count >= ?", match.to_i) end - advanced_filter(/^in:first|^f$/) do |posts| + advanced_filter(/^in:first|^f$/i) do |posts| posts.where("posts.post_number = 1") end - advanced_filter(/^in:pinned$/) do |posts| + advanced_filter(/^in:pinned$/i) do |posts| posts.where("topics.pinned_at IS NOT NULL") end - advanced_filter(/^in:wiki$/) do |posts, match| + advanced_filter(/^in:wiki$/i) do |posts, match| posts.where(wiki: true) end - advanced_filter(/^badge:(.*)$/) do |posts, match| + advanced_filter(/^badge:(.*)$/i) do |posts, match| badge_id = Badge.where('name ilike ? OR id = ?', match, match.to_i).pluck_first(:id) if badge_id posts.where('posts.user_id IN (SELECT ub.user_id FROM user_badges ub WHERE ub.badge_id = ?)', badge_id) @@ -403,34 +403,34 @@ class Search )") end - advanced_filter(/^in:(likes)$/) do |posts, match| + advanced_filter(/^in:(likes)$/i) do |posts, match| if @guardian.user post_action_type_filter(posts, PostActionType.types[:like]) end end - advanced_filter(/^in:(bookmarks)$/) do |posts, match| + advanced_filter(/^in:(bookmarks)$/i) do |posts, match| if @guardian.user posts.where("posts.id IN (SELECT post_id FROM bookmarks WHERE bookmarks.user_id = #{@guardian.user.id})") end end - advanced_filter(/^in:posted$/) do |posts| + advanced_filter(/^in:posted$/i) do |posts| posts.where("posts.user_id = #{@guardian.user.id}") if @guardian.user end - advanced_filter(/^in:created$/) do |posts| + advanced_filter(/^in:created$/i) do |posts| posts.where(user_id: @guardian.user.id, post_number: 1) if @guardian.user end - advanced_filter(/^created:@(.*)$/) do |posts, match| + advanced_filter(/^created:@(.*)$/i) do |posts, match| user_id = User.where(username: match.downcase).pluck_first(:id) posts.where(user_id: user_id, post_number: 1) end - advanced_filter(/^in:(watching|tracking)$/) do |posts, match| + advanced_filter(/^in:(watching|tracking)$/i) do |posts, match| if @guardian.user - level = TopicUser.notification_levels[match.to_sym] + level = TopicUser.notification_levels[match.downcase.to_sym] posts.where("posts.topic_id IN ( SELECT tu.topic_id FROM topic_users tu WHERE tu.user_id = :user_id AND @@ -440,7 +440,7 @@ class Search end end - advanced_filter(/^in:seen$/) do |posts| + advanced_filter(/^in:seen$/i) do |posts| if @guardian.user posts .joins("INNER JOIN post_timings ON @@ -451,7 +451,7 @@ class Search end end - advanced_filter(/^in:unseen$/) do |posts| + advanced_filter(/^in:unseen$/i) do |posts| if @guardian.user posts .joins("LEFT JOIN post_timings ON @@ -463,11 +463,11 @@ class Search end end - advanced_filter(/^with:images$/) do |posts| + advanced_filter(/^with:images$/i) do |posts| posts.where("posts.image_upload_id IS NOT NULL") end - advanced_filter(/^category:(.+)$/) do |posts, match| + advanced_filter(/^category:(.+)$/i) do |posts, match| exact = false if match[0] == "=" @@ -491,7 +491,7 @@ class Search end end - advanced_filter(/^\#([\p{L}\p{M}0-9\-:=]+)$/) do |posts, match| + advanced_filter(/^\#([\p{L}\p{M}0-9\-:=]+)$/i) do |posts, match| exact = true @@ -570,7 +570,7 @@ class Search end end - advanced_filter(/^group:(.+)$/) do |posts, match| + advanced_filter(/^group:(.+)$/i) do |posts, match| group_id = Group.where('name ilike ? OR (id = ? AND id > 0)', match, match.to_i).pluck_first(:id) if group_id posts.where("posts.user_id IN (select gu.user_id from group_users gu where gu.group_id = ?)", group_id) @@ -579,7 +579,7 @@ class Search end end - advanced_filter(/^user:(.+)$/) do |posts, match| + advanced_filter(/^user:(.+)$/i) do |posts, match| user_id = User.where(staged: false).where('username_lower = ? OR id = ?', match.downcase, match.to_i).pluck_first(:id) if user_id posts.where("posts.user_id = #{user_id}") @@ -588,7 +588,7 @@ class Search end end - advanced_filter(/^\@([a-zA-Z0-9_\-.]+)$/) do |posts, match| + advanced_filter(/^\@([a-zA-Z0-9_\-.]+)$/i) do |posts, match| user_id = User.where(staged: false).where(username_lower: match.downcase).pluck_first(:id) if user_id posts.where("posts.user_id = #{user_id}") @@ -597,7 +597,7 @@ class Search end end - advanced_filter(/^before:(.*)$/) do |posts, match| + advanced_filter(/^before:(.*)$/i) do |posts, match| if date = Search.word_to_date(match) posts.where("posts.created_at < ?", date) else @@ -605,7 +605,7 @@ class Search end end - advanced_filter(/^after:(.*)$/) do |posts, match| + advanced_filter(/^after:(.*)$/i) do |posts, match| if date = Search.word_to_date(match) posts.where("posts.created_at > ?", date) else @@ -613,15 +613,15 @@ class Search end end - advanced_filter(/^tags?:([\p{L}\p{M}0-9,\-_+]+)$/) do |posts, match| + advanced_filter(/^tags?:([\p{L}\p{M}0-9,\-_+]+)$/i) do |posts, match| search_tags(posts, match, positive: true) end - advanced_filter(/^\-tags?:([\p{L}\p{M}0-9,\-_+]+)$/) do |posts, match| + advanced_filter(/^\-tags?:([\p{L}\p{M}0-9,\-_+]+)$/i) do |posts, match| search_tags(posts, match, positive: false) end - advanced_filter(/^filetypes?:([a-zA-Z0-9,\-_]+)$/) do |posts, match| + advanced_filter(/^filetypes?:([a-zA-Z0-9,\-_]+)$/i) do |posts, match| file_extensions = match.split(",").map(&:downcase) posts.where("posts.id IN ( SELECT post_id @@ -682,13 +682,13 @@ class Search if word == 'l' @order = :latest nil - elsif word =~ /order:\w+/ - @order = word.gsub('order:', '').to_sym + elsif word =~ /^order:\w+$/i + @order = word.downcase.gsub('order:', '').to_sym nil - elsif word == 'in:title' || word == 't' + elsif word =~ /^in:title$/i || word == 't' @in_title = true nil - elsif word =~ /topic:(\d+)/ + elsif word =~ /^topic:(\d+)$/i topic_id = $1.to_i if topic_id > 1 topic = Topic.find_by(id: topic_id) @@ -697,16 +697,16 @@ class Search end end nil - elsif word == 'in:all' + elsif word =~ /^in:all$/i @search_all_topics = true nil - elsif word == 'in:personal' + elsif word =~ /^in:personal$/i @search_pms = true nil - elsif word == "in:personal-direct" + elsif word =~ /^in:personal-direct$/i @search_pms = true nil - elsif word =~ /^personal_messages:(.+)$/ + elsif word =~ /^personal_messages:(.+)$/i if user = User.find_by_username($1) @search_pms = true @search_context = user diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 97c1cf58754..62accfd186b 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -290,12 +290,10 @@ describe Search do TopicAllowedGroup.create!(group_id: group.id, topic_id: topic.id) - results = Search.execute( - 'mars in:personal', - guardian: Guardian.new(user) - ) - - expect(results.posts).to contain_exactly(reply) + ["mars in:personal", "mars IN:PERSONAL"].each do |query| + results = Search.execute(query, guardian: Guardian.new(user)) + expect(results.posts).to contain_exactly(reply) + end end context 'personal_messages filter' do @@ -359,19 +357,27 @@ describe Search do _pm_2 = create_pm(users: [participant_2, participant]) pm_3 = create_pm(users: [participant, current]) pm_4 = create_pm(users: [participant_2, current]) - results = Search.execute("in:personal-direct", guardian: Guardian.new(current)) - expect(results.posts.size).to eq(3) - expect(results.posts.map(&:topic_id)).to eq([pm_4.id, pm_3.id, pm.id]) + + ["in:personal-direct", "In:PeRsOnAl-DiReCt"].each do |query| + results = Search.execute(query, guardian: Guardian.new(current)) + expect(results.posts.size).to eq(3) + expect(results.posts.map(&:topic_id)).to eq([pm_4.id, pm_3.id, pm.id]) + end end it 'can filter direct PMs by @username' do pm = create_pm(users: [current, participant]) pm_2 = create_pm(users: [participant, current]) _pm_3 = create_pm(users: [participant_2, current]) - results = Search.execute("@#{participant.username} in:personal-direct", guardian: Guardian.new(current)) - expect(results.posts.size).to eq(2) - expect(results.posts.map(&:topic_id)).to eq([pm_2.id, pm.id]) - expect(results.posts.map(&:user_id).uniq).to eq([participant.id]) + [ + "@#{participant.username} in:personal-direct", + "@#{participant.username} iN:pErSoNaL-dIrEcT" + ].each do |query| + results = Search.execute(query, guardian: Guardian.new(current)) + expect(results.posts.size).to eq(2) + expect(results.posts.map(&:topic_id)).to eq([pm_2.id, pm.id]) + expect(results.posts.map(&:user_id).uniq).to eq([participant.id]) + end end it "doesn't include PMs that have more than 2 participants" do @@ -406,6 +412,10 @@ describe Search do TopicAllowedUser.create!(user_id: u1.id, topic_id: private_topic.id) TopicAllowedUser.create!(user_id: u2.id, topic_id: private_topic.id) + # case insensitive only + results = Search.execute('iN:aLL cheese', guardian: Guardian.new(u1)) + expect(results.posts).to contain_exactly(private_post1) + # private only results = Search.execute('in:all cheese', guardian: Guardian.new(u1)) expect(results.posts).to contain_exactly(private_post1) @@ -1110,6 +1120,7 @@ describe Search do guardian = Guardian.new(user) expect(Search.execute('boom in:pinned').posts.length).to eq(1) + expect(Search.execute('boom IN:PINNED').posts.length).to eq(1) end it 'supports wiki' do @@ -1120,6 +1131,7 @@ describe Search do expect(Search.execute('test 248').posts.length).to eq(2) expect(Search.execute('test 248 in:wiki').posts.first).to eq(post) + expect(Search.execute('test 248 IN:WIKI').posts.first).to eq(post) end it 'supports searching for posts that the user has seen/unseen' do @@ -1144,6 +1156,9 @@ describe Search do expect(Search.execute('longan in:seen', guardian: Guardian.new(post.user)).posts) .to eq([post]) + expect(Search.execute('longan IN:SEEN', guardian: Guardian.new(post.user)).posts) + .to eq([post]) + expect(Search.execute('longan in:seen').posts.sort).to eq([post, post_2]) expect(Search.execute('longan in:seen', guardian: Guardian.new(post_2.user)).posts) @@ -1157,6 +1172,9 @@ describe Search do expect(Search.execute('longan in:unseen', guardian: Guardian.new(post.user)).posts) .to eq([post_2]) + + expect(Search.execute('longan IN:UNSEEN', guardian: Guardian.new(post.user)).posts) + .to eq([post_2]) end it 'supports before and after filters' do @@ -1180,6 +1198,7 @@ describe Search do post_2 = Fabricate(:post, raw: 'boom boom shake the room test', topic: topic) expect(Search.execute('test in:first').posts).to contain_exactly(post_1) + expect(Search.execute('test IN:FIRST').posts).to contain_exactly(post_1) expect(Search.execute('boom').posts).to contain_exactly(post_2) @@ -1216,6 +1235,7 @@ describe Search do UserBadge.create!(user_id: post.user_id, badge_id: badge.id, granted_at: 1.minute.ago, granted_by_id: -1) expect(Search.execute('badge:"like a boss"').posts.length).to eq(1) + expect(Search.execute('BADGE:"LIKE A BOSS"').posts.length).to eq(1) expect(Search.execute('badge:"test"').posts.length).to eq(0) end @@ -1255,6 +1275,7 @@ describe Search do expect(Search.execute('test status:public').posts.length).to eq(1) expect(Search.execute('test status:closed').posts.length).to eq(0) expect(Search.execute('test status:open').posts.length).to eq(1) + expect(Search.execute('test STATUS:OPEN').posts.length).to eq(1) expect(Search.execute('test posts_count:1').posts.length).to eq(1) expect(Search.execute('test min_post_count:1').posts.length).to eq(1) @@ -1277,6 +1298,7 @@ describe Search do expect(Search.execute('test in:likes', guardian: Guardian.new(topic.user)).posts.length).to eq(0) expect(Search.execute('test in:posted', guardian: Guardian.new(topic.user)).posts.length).to eq(2) + expect(Search.execute('test In:PoStEd', guardian: Guardian.new(topic.user)).posts.length).to eq(2) in_created = Search.execute('test in:created', guardian: Guardian.new(topic.user)).posts created_by_user = Search.execute("test created:@#{topic.user.username}", guardian: Guardian.new(topic.user)).posts @@ -1309,7 +1331,7 @@ describe Search do post2 = Fabricate(:post, raw: 'that Sam I am, that Sam I am', created_at: 5.minutes.ago) expect(Search.execute('sam').posts.map(&:id)).to eq([post1.id, post2.id]) - expect(Search.execute('sam order:latest').posts.map(&:id)).to eq([post2.id, post1.id]) + expect(Search.execute('sam ORDER:LATEST').posts.map(&:id)).to eq([post2.id, post1.id]) expect(Search.execute('sam l').posts.map(&:id)).to eq([post2.id, post1.id]) expect(Search.execute('l sam').posts.map(&:id)).to eq([post2.id, post1.id]) end @@ -1662,6 +1684,9 @@ describe Search do results = Search.execute('title in:title') expect(results.posts.map(&:id)).to eq([post.id]) + results = Search.execute('title iN:tItLe') + expect(results.posts.map(&:id)).to eq([post.id]) + results = Search.execute('first in:title') expect(results.posts).to eq([]) end @@ -1770,6 +1795,9 @@ describe Search do results = Search.execute('in:tagged') expect(results.posts.length).to eq(1) + + results = Search.execute('In:TaGgEd') + expect(results.posts.length).to eq(1) end end @@ -1778,7 +1806,7 @@ describe Search do topic = Fabricate(:topic, title: 'I am testing a untagged search') _post = Fabricate(:post, topic: topic, raw: 'this is the first post') - results = Search.execute('in:untagged') + results = Search.execute('iN:uNtAgGeD') expect(results.posts.length).to eq(1) results = Search.execute('in:tagged')