autocomplete: highlight matches in autocomplete menu.
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.
This commit is contained in:
@@ -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*") => "<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
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
<div class="ui-menu-item-wrapper" tabindex="-1">
|
||||
<%= link_to_result result do %>
|
||||
<% if result.antecedent.present? %>
|
||||
<span class="autocomplete-antecedent"><%= result.antecedent.tr("_", " ") %></span>
|
||||
<span class="autocomplete-antecedent"><%= highlight_antecedent(result) %></span>
|
||||
<span class="autocomplete-arrow">→</span>
|
||||
<%= result.label %>
|
||||
<% else %>
|
||||
<%= highlight_result(result) %>
|
||||
<% end %>
|
||||
|
||||
<%= result.label %>
|
||||
<% end %>
|
||||
|
||||
<% if result.post_count %>
|
||||
|
||||
Reference in New Issue
Block a user