diff --git a/app/components/autocomplete_component.rb b/app/components/autocomplete_component.rb index 7ab304bb6..1a257bc31 100644 --- a/app/components/autocomplete_component.rb +++ b/app/components/autocomplete_component.rb @@ -4,7 +4,7 @@ class AutocompleteComponent < ApplicationComponent attr_reader :autocomplete_service delegate :humanized_number, to: :helpers - delegate :autocomplete_results, to: :autocomplete_service + delegate :autocomplete_results, :query, :metatag, to: :autocomplete_service def initialize(autocomplete_service:) @autocomplete_service = autocomplete_service @@ -20,4 +20,62 @@ class AutocompleteComponent < ApplicationComponent link_to posts_path(tags: result.value), class: "tag-type-#{result.category}", "@click.prevent": "", &block end end + + def highlight_antecedent(result) + if result.type == "tag-word" + highlight_matching_words(result.antecedent, query) + else + highlight_wildcard_match(result.antecedent, query) + end + end + + def highlight_result(result) + if result.type == "tag-word" + highlight_matching_words(result.value, query) + elsif metatag.present? && metatag.value.include?("*") + highlight_wildcard_match(result.label, metatag.value) + elsif metatag.present? && metatag.name.in?(%w[pool favgroup]) + highlight_wildcard_match(result.label, "*" + metatag.value + "*") + elsif metatag.present? + highlight_wildcard_match(result.label, metatag.value + "*") + else + highlight_wildcard_match(result.value, query) + end + end + + # Highlight the words in the `target` string matching the words in the search `pattern`. + # + # highlight_matching_words("very_long_hair", "long_ha*") => "very_long_hair" + def highlight_matching_words(target, pattern) + pattern_words = Tag.parse_query(pattern) + pattern_words.sort_by! { |word| [word.include?("*") ? 0 : 1, -word.size] } + + target_words = Tag.split_words(target) + target_words.map do |word| + pat = pattern_words.find { |pat| word.ilike?(pat) } + highlight_wildcard_match(word, pat) + end.join("").html_safe + end + + # Highlight the parts of the `target` string that match the wildcard search `pattern`. + # + # highlight_wildcard_match("very_long_hair", "*long*") => "very_long_hair" + def highlight_wildcard_match(target, pattern) + return tag.span(target.tr("_", " ")) if !target.ilike?(pattern.to_s) + + words = pattern.split(/(\*)/).compact_blank # "*black*" => ["*", "black", "*"] + regexp = words.map { |w| w == "*" ? "(.*)" : "(#{Regexp.escape(w)})" }.join # "*black*" => "(.*)(black)(.*)" + regexp = Regexp.new(regexp, "i") + captures = target.match(regexp).captures # "black_thighhighs" =~ /(.*)(black)(.*)/ => ["", "black", "_thighhighs"] + + captures.zip(words).map do |substring, word| + if substring == "" + "" + elsif word == "*" + tag.span(substring.tr("_", " ")) + else + tag.b(substring.tr("_", " ")) + end + end.join.html_safe + end end diff --git a/app/components/autocomplete_component/autocomplete_component.html.erb b/app/components/autocomplete_component/autocomplete_component.html.erb index 992742dcc..335bad548 100644 --- a/app/components/autocomplete_component/autocomplete_component.html.erb +++ b/app/components/autocomplete_component/autocomplete_component.html.erb @@ -4,11 +4,12 @@