Highlight the matching part of the tag in the autocomplete menu. For example, if you search "hair", then the word "hair" will be bolded in every matching tag. This is so users can tell why a particular tag was matched.
82 lines
3.0 KiB
Ruby
82 lines
3.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class AutocompleteComponent < ApplicationComponent
|
|
attr_reader :autocomplete_service
|
|
|
|
delegate :humanized_number, to: :helpers
|
|
delegate :autocomplete_results, :query, :metatag, to: :autocomplete_service
|
|
|
|
def initialize(autocomplete_service:)
|
|
@autocomplete_service = autocomplete_service
|
|
end
|
|
|
|
def link_to_result(result, &block)
|
|
case result.type
|
|
when "user"
|
|
link_to user_path(result.id), class: "user-#{result.level}", "@click.prevent": "", &block
|
|
when "pool"
|
|
link_to pool_path(result.id), class: "pool-category-#{result.category}", "@click.prevent": "", &block
|
|
else
|
|
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*") => "<span>very_</span><b>long</b><span>_</span><b>hair</b>"
|
|
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*") => "<span>very_</span><b>long</b><span>_hair</span>"
|
|
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
|