diff --git a/app/logical/post_query_builder.rb b/app/logical/post_query_builder.rb index 663a71153..0172ee8e1 100644 --- a/app/logical/post_query_builder.rb +++ b/app/logical/post_query_builder.rb @@ -1033,20 +1033,36 @@ class PostQueryBuilder return q end - def parse_tag(tag, output) - if tag[0] == "-" && tag.size > 1 - output[:exclude] << tag[1..-1].mb_chars.downcase - - elsif tag[0] == "~" && tag.size > 1 - output[:include] << tag[1..-1].mb_chars.downcase - - elsif tag =~ /\*/ - matches = Tag.name_matches(tag).select("name").limit(Danbooru.config.tag_query_limit).order("post_count DESC").map(&:name) - matches = ["~no_matches~"] if matches.empty? - output[:include] += matches + def parse_tag_operator(tag) + tag = Tag.normalize_name(tag) + if tag.starts_with?("-") + ["-", tag.delete_prefix("-")] + elsif tag.starts_with?("~") + ["~", tag.delete_prefix("~")] else - output[:related] << tag.mb_chars.downcase + [nil, tag] + end + end + + def parse_tag(tag, output) + operator, tag = parse_tag_operator(tag) + + if tag.include?("*") + tags = Tag.wildcard_matches(tag) + + if operator == "-" + output[:exclude] += tags + else + tags = ["~no_matches~"] if tags.empty? # force empty results if wildcard found no matches. + output[:include] += tags + end + elsif operator == "-" + output[:exclude] << tag + elsif operator == "~" + output[:include] << tag + else + output[:related] << tag end end diff --git a/app/models/tag.rb b/app/models/tag.rb index 5f78f26a8..993e58188 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -279,6 +279,10 @@ class Tag < ApplicationRecord name_matches(name).or(alias_matches(name)) end + def wildcard_matches(tag, limit: 25) + nonempty.name_matches(tag).order(post_count: :desc, name: :asc).limit(limit).pluck(:name) + end + def search(params) q = super diff --git a/app/presenters/post_set_presenters/post.rb b/app/presenters/post_set_presenters/post.rb index f34763267..bd67b567e 100644 --- a/app/presenters/post_set_presenters/post.rb +++ b/app/presenters/post_set_presenters/post.rb @@ -48,7 +48,7 @@ module PostSetPresenters end def pattern_tags - Tag.name_matches(post_set.tag_string).order(post_count: :desc).limit(MAX_TAGS).pluck(:name) + Tag.wildcard_matches(post_set.tag_string) end def saved_search_tags diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb index 52d332bf9..3985ec857 100644 --- a/test/unit/post_test.rb +++ b/test/unit/post_test.rb @@ -1962,6 +1962,18 @@ class PostTest < ActiveSupport::TestCase assert_tag_match([post2], "a* bbb") end + should "return posts for a negated pattern" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaab bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post3], "-a*") + assert_tag_match([post3], "bbb -a*") + assert_tag_match([post3], "~bbb -a*") + assert_tag_match([post1], "a* -*b") + assert_tag_match([post2], "-*c -a*a") + end + should "return posts for the id: metatag" do posts = FactoryBot.create_list(:post, 3)