tags: refactor tag lists to use ViewComponent.
This commit is contained in:
51
app/components/tag_list_component.rb
Normal file
51
app/components/tag_list_component.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TagListComponent < ApplicationComponent
|
||||
attr_reader :tags, :current_query, :show_extra_links
|
||||
|
||||
def initialize(tags: [], current_query: nil, show_extra_links: false)
|
||||
@tags = tags
|
||||
@current_query = current_query
|
||||
@show_extra_links = show_extra_links
|
||||
end
|
||||
|
||||
def self.tags_from_names(tag_names)
|
||||
names_to_tags = Tag.where(name: tag_names).map { |tag| [tag.name, tag] }.to_h
|
||||
|
||||
tag_names.map do |name|
|
||||
names_to_tags.fetch(name) { Tag.new(name: name).freeze }
|
||||
end
|
||||
end
|
||||
|
||||
def categorized_tags(categories, &block)
|
||||
return to_enum(:categorized_tags, categories) unless block_given?
|
||||
|
||||
categories.each do |category|
|
||||
tags = tags_for_category(category)
|
||||
yield category, tags if tags.present?
|
||||
end
|
||||
end
|
||||
|
||||
def tags_for_category(category_name)
|
||||
category = TagCategory.mapping[category_name.downcase]
|
||||
tags_by_category[category] || []
|
||||
end
|
||||
|
||||
def tags_by_category
|
||||
@tags_by_category ||= tags.sort_by(&:name).group_by(&:category)
|
||||
end
|
||||
|
||||
def is_underused_tag?(tag)
|
||||
tag.post_count <= 1 && tag.general?
|
||||
end
|
||||
|
||||
def humanized_post_count(tag)
|
||||
if tag.post_count >= 10_000
|
||||
"#{tag.post_count / 1_000}k"
|
||||
elsif tag.post_count >= 1_000
|
||||
"%.1fk" % (tag.post_count / 1_000.0)
|
||||
else
|
||||
tag.post_count.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="tag-list categorized-tag-list">
|
||||
<% categorized_tags(TagCategory.split_header_list).each do |category_name, tags| %>
|
||||
<h3 class="<%= category_name %>-tag-list">
|
||||
<%= category_name.capitalize.pluralize(tags) %>
|
||||
</h3>
|
||||
|
||||
<ul class="<%= category_name %>-tag-list">
|
||||
<% tags.each do |t| %>
|
||||
<li class="tag-type-<%= t.category %>" data-tag-name="<%= t.name %>">
|
||||
<% if t.artist? %>
|
||||
<%= link_to "?", show_or_new_artists_path(t.name), class: "wiki-link" %>
|
||||
<% elsif t.name =~ /\A\d+\z/ %>
|
||||
<%= link_to "?", wiki_page_path("~#{t.name}"), class: "wiki-link" %>
|
||||
<% else %>
|
||||
<%= link_to "?", wiki_page_path(t.name), class: "wiki-link" %>
|
||||
<% end %>
|
||||
|
||||
<% if show_extra_links && current_query.present? %>
|
||||
<%= link_to "+", posts_path(tags: "#{current_query} #{t.name}"), class: "search-inc-tag" %>
|
||||
<%= link_to "-", posts_path(tags: "#{current_query} -#{t.name}"), class: "search-exl-tag" %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to t.pretty_name, posts_path(tags: t.name), class: "search-tag" %>
|
||||
|
||||
<%= tag.span humanized_post_count(t), class: ["post-count", ("low-post-count" if is_underused_tag?(t))], title: t.post_count %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="tag-list inline-tag-list">
|
||||
<% categorized_tags(TagCategory.categorized_list).each do |category_name, tags| %>
|
||||
<% tags.each do |t| %>
|
||||
<%= link_to t.name, posts_path(tags: t.name), class: "search-tag tag-type-#{t.category}", "data-tag-name": t.name %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<ul class="tag-list search-tag-list">
|
||||
<% tags.each do |t| %>
|
||||
<li class="tag-type-<%= t.category %>" data-tag-name="<%= t.name %>">
|
||||
<%# ignore search:foo metatags %>
|
||||
<% if t.artist? %>
|
||||
<%= link_to "?", show_or_new_artists_path(t.name), class: "wiki-link" %>
|
||||
<% elsif t.name =~ /\A\d+\z/ %>
|
||||
<%= link_to "?", wiki_page_path("~#{t.name}"), class: "wiki-link" %>
|
||||
<% else %>
|
||||
<%= link_to "?", wiki_page_path(t.name), class: "wiki-link" %>
|
||||
<% end %>
|
||||
|
||||
<% if show_extra_links && current_query.present? %>
|
||||
<%= link_to "+", posts_path(tags: "#{current_query} #{t.name}"), class: "search-inc-tag" %>
|
||||
<%= link_to "-", posts_path(tags: "#{current_query} -#{t.name}"), class: "search-exl-tag" %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to t.pretty_name, posts_path(tags: t.name), class: "search-tag" %>
|
||||
<%= tag.span humanized_post_count(t), class: "post-count", title: t.post_count %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="tag-list simple-tag-list">
|
||||
<% tags.each do |t| %>
|
||||
<%= link_to t.pretty_name, posts_path(tags: t.name), class: "search-tag tag-type-#{t.category}", "data-tag-name": t.name %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
.tag-list {
|
||||
a.search-tag {
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
&.inline-tag-list {
|
||||
display: inline;
|
||||
|
||||
a {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.categorized-tag-list {
|
||||
ul {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.simple-tag-list {
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,32 @@ module ComponentsHelper
|
||||
def render_post_navbar(post, **options)
|
||||
render PostNavbarComponent.new(post: post, **options)
|
||||
end
|
||||
|
||||
# A simple vertical tag list with no post counts. Used in related tags.
|
||||
def render_simple_tag_list(tag_names, **options)
|
||||
tags = TagListComponent.tags_from_names(tag_names)
|
||||
render TagListComponent.new(tags: tags, **options).with_variant(:simple)
|
||||
end
|
||||
|
||||
# A horizontal tag list, with tags grouped by category. Used in post
|
||||
# tooltips, on the comments index, and in the modqueue.
|
||||
def render_inline_tag_list(post, **options)
|
||||
render TagListComponent.new(tags: post.tags, **options).with_variant(:inline)
|
||||
end
|
||||
|
||||
def render_inline_tag_list_from_names(tag_names, **options)
|
||||
tags = TagListComponent.tags_from_names(tag_names)
|
||||
render TagListComponent.new(tags: tags, **options).with_variant(:inline)
|
||||
end
|
||||
|
||||
# A vertical tag list, with tags split into categories. Used on post show pages.
|
||||
def render_categorized_tag_list(post, **options)
|
||||
render TagListComponent.new(tags: post.tags, **options).with_variant(:categorized)
|
||||
end
|
||||
|
||||
# A vertical tag list, used in the post index sidebar.
|
||||
def render_search_tag_list(tag_names, **options)
|
||||
tags = TagListComponent.tags_from_names(tag_names)
|
||||
render TagListComponent.new(tags: tags, **options).with_variant(:search)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,18 +34,3 @@
|
||||
}
|
||||
}
|
||||
<% end %>
|
||||
|
||||
a.search-tag {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.inline-tag-list {
|
||||
ul {
|
||||
display: inline;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ div#c-modqueue {
|
||||
}
|
||||
|
||||
<% Danbooru.config.modqueue_warning_tags.each do |tag| %>
|
||||
li[data-tag-name="<%= tag %>"] {
|
||||
a[data-tag-name="<%= tag %>"] {
|
||||
background-color: var(--modqueue-tag-warning-color);
|
||||
}
|
||||
<% end %>
|
||||
|
||||
@@ -201,14 +201,6 @@ module PostSets
|
||||
searches = ["search:all"] + SavedSearch.labels_for(CurrentUser.user.id).map {|x| "search:#{x}"}
|
||||
searches.take(MAX_SIDEBAR_TAGS)
|
||||
end
|
||||
|
||||
def tag_set_presenter
|
||||
@tag_set_presenter ||= TagSetPresenter.new(related_tags)
|
||||
end
|
||||
|
||||
def tag_list_html(**options)
|
||||
tag_set_presenter.tag_list_html(name_only: query.is_metatag?(:search), **options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class PostPresenter
|
||||
attr_reader :pool, :next_post_in_pool
|
||||
delegate :tag_list_html, :split_tag_list_html, :split_tag_list_text, :inline_tag_list_html, to: :tag_set_presenter
|
||||
delegate :split_tag_list_text, to: :tag_set_presenter
|
||||
|
||||
def initialize(post)
|
||||
@post = post
|
||||
|
||||
@@ -14,48 +14,6 @@ class TagSetPresenter
|
||||
@tag_names = tag_names
|
||||
end
|
||||
|
||||
def tag_list_html(current_query: "", show_extra_links: false, name_only: false)
|
||||
html = ""
|
||||
|
||||
if ordered_tags.present?
|
||||
html << '<ul>'
|
||||
ordered_tags.each do |tag|
|
||||
html << build_list_item(tag, current_query: current_query, show_extra_links: show_extra_links, name_only: name_only)
|
||||
end
|
||||
html << "</ul>"
|
||||
end
|
||||
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
def split_tag_list_html(headers: true, category_list: TagCategory.split_header_list, current_query: "", show_extra_links: false, name_only: false, humanize_tags: true)
|
||||
html = ""
|
||||
|
||||
category_list.each do |category|
|
||||
typetags = tags_for_category(category)
|
||||
|
||||
if typetags.any?
|
||||
if headers
|
||||
html << %{<h3 class="#{category}-tag-list">#{category.capitalize.pluralize(typetags.size)}</h3>}
|
||||
end
|
||||
|
||||
html << %{<ul class="#{category}-tag-list">}
|
||||
typetags.each do |tag|
|
||||
html << build_list_item(tag, current_query: current_query, show_extra_links: show_extra_links, name_only: name_only, humanize_tags: humanize_tags)
|
||||
end
|
||||
html << "</ul>"
|
||||
end
|
||||
end
|
||||
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
# compact (horizontal) list, as seen in the /comments index.
|
||||
def inline_tag_list_html(humanize_tags: false)
|
||||
html = split_tag_list_html(category_list: TagCategory.categorized_list, headers: false, show_extra_links: false, name_only: true, humanize_tags: humanize_tags)
|
||||
%{<span class="inline-tag-list">#{html}</span>}.html_safe
|
||||
end
|
||||
|
||||
# the list of tags inside the tag box in the post edit form.
|
||||
def split_tag_list_text(category_list: TagCategory.categorized_list)
|
||||
category_list.map do |category|
|
||||
@@ -104,57 +62,5 @@ class TagSetPresenter
|
||||
end
|
||||
end
|
||||
|
||||
def build_list_item(tag, name_only: false, humanize_tags: true, show_extra_links: false, current_query: "")
|
||||
name = tag.name
|
||||
count = tag.post_count
|
||||
category = tag.category
|
||||
|
||||
html = %{<li class="tag-type-#{tag.category}" data-tag-name="#{h(name)}">}
|
||||
|
||||
unless name_only
|
||||
if tag.artist?
|
||||
html << %{<a class="wiki-link" href="/artists/show_or_new?name=#{u(name)}">?</a> }
|
||||
elsif name =~ /\A\d+\z/
|
||||
html << %{<a class="wiki-link" href="/wiki_pages/~#{u(name)}">?</a> }
|
||||
else
|
||||
html << %{<a class="wiki-link" href="/wiki_pages/#{u(name)}">?</a> }
|
||||
end
|
||||
|
||||
if show_extra_links && current_query.present?
|
||||
html << %{<a href="/posts?tags=#{u(current_query)}+#{u(name)}" class="search-inc-tag">+</a> }
|
||||
html << %{<a href="/posts?tags=#{u(current_query)}+-#{u(name)}" class="search-exl-tag">–</a> }
|
||||
end
|
||||
end
|
||||
|
||||
humanized_tag = humanize_tags ? name.tr("_", " ") : name
|
||||
html << %{<a class="search-tag" href="/posts?tags=#{u(name)}">#{h(humanized_tag)}</a> }
|
||||
|
||||
unless name_only || tag.new_record?
|
||||
if count >= 10_000
|
||||
post_count = "#{count / 1_000}k"
|
||||
elsif count >= 1_000
|
||||
post_count = format("%.1fk", (count / 1_000.0))
|
||||
else
|
||||
post_count = count
|
||||
end
|
||||
|
||||
is_underused_tag = count <= 1 && tag.general?
|
||||
klass = "post-count#{is_underused_tag ? " low-post-count" : ""}"
|
||||
|
||||
html << %{<span class="#{klass}" title="#{count}">#{post_count}</span>}
|
||||
end
|
||||
|
||||
html << "</li>"
|
||||
html
|
||||
end
|
||||
|
||||
def h(s)
|
||||
CGI.escapeHTML(s)
|
||||
end
|
||||
|
||||
def u(s)
|
||||
CGI.escape(s)
|
||||
end
|
||||
|
||||
memoize :tags, :tags_by_category, :ordered_tags, :humanized_essential_tag_string
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div class="row list-of-tags">
|
||||
<strong>Tags</strong>
|
||||
<%= post.presenter.inline_tag_list_html %>
|
||||
<%= render_inline_tag_list(post) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="row list-of-tags">
|
||||
<span class="info">
|
||||
<strong>Tags</strong>
|
||||
<%= post.presenter.inline_tag_list_html %>
|
||||
<%= render_inline_tag_list(post) %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
<%= post_version_field(post_version, :rating) %>
|
||||
<%= post_version_field(post_version, :parent_id) %>
|
||||
</div>
|
||||
<div><b>Tags:</b> <%= TagSetPresenter.new(post_version.tag_array).inline_tag_list_html %></div>
|
||||
<div>
|
||||
<b>Tags:</b>
|
||||
<%= render_inline_tag_list_from_names(post_version.tag_array) %>
|
||||
</div>
|
||||
<div>
|
||||
<%= post_source_tag(post_version.source) %>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<%= post_preview(vote.post, show_deleted: true) %>
|
||||
<% end %>
|
||||
<% t.column "Tags", td: {class: "col-expand"} do |vote| %>
|
||||
<%= TagSetPresenter.new(vote.post.tag_array).inline_tag_list_html %>
|
||||
<%= render_inline_tag_list(vote.post) %>
|
||||
<% end %>
|
||||
<% t.column "Score" do |vote| %>
|
||||
<%= link_to sprintf("%+d", vote.score), post_votes_path(search: { score: vote.score }) %>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<section id="tag-box">
|
||||
<h2>Tags</h2>
|
||||
<%= @post_set.tag_list_html(current_query: params[:tags], show_extra_links: policy(Post).show_extra_links?) %>
|
||||
<%= render_search_tag_list(@post_set.related_tags, current_query: params[:tags], show_extra_links: policy(Post).show_extra_links?) %>
|
||||
</section>
|
||||
|
||||
<%= render "posts/partials/index/options" %>
|
||||
|
||||
@@ -52,6 +52,6 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= @post.presenter.inline_tag_list_html %>
|
||||
<%= render_inline_tag_list(@post) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%= render "posts/partials/index/blacklist" %>
|
||||
|
||||
<section id="tag-list">
|
||||
<%= @post.presenter.split_tag_list_html(current_query: params[:q], show_extra_links: policy(@post).show_extra_links?) %>
|
||||
<%= render_categorized_tag_list(@post, current_query: params[:q], show_extra_links: policy(@post).show_extra_links?) %>
|
||||
</section>
|
||||
|
||||
<section id="post-information">
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
none
|
||||
<% else %>
|
||||
<% if source.artists.any?(&:is_banned?) %>
|
||||
<%= TagSetPresenter.new(["banned_artist"]).tag_list_html(name_only: true) %>
|
||||
<%= render_simple_tag_list(["banned_artist"]) %>
|
||||
<% end %>
|
||||
|
||||
<% source.artists.each do |artist| %>
|
||||
<%= TagSetPresenter.new([artist.name]).tag_list_html(name_only: true) %>
|
||||
<%= render_simple_tag_list(["banned_artist"]) %>
|
||||
|
||||
<ul>
|
||||
<% artist.sorted_urls.each do |url| %>
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<%= content_tag(:div, class: ["tag-column", local_assigns[:class] || "#{title.parameterize}-related-tags-column", "is-empty-#{tags.empty?}"].join(" ")) do %>
|
||||
<% if tags.present? %>
|
||||
<h3><%= title %></h3>
|
||||
<%= TagSetPresenter.new(tags).tag_list_html(name_only: true) %>
|
||||
<%= render_simple_tag_list(tags) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%= post_preview(upload_report.becomes(Post), show_deleted: true, tags: "user:#{upload_report.uploader.name}") %>
|
||||
<% end %>
|
||||
<% t.column "Tags added by uploader", width: "45%" do |upload_report| %>
|
||||
<%= TagSetPresenter.new(upload_report.uploader_tags_array).inline_tag_list_html %>
|
||||
<%= render_inline_tag_list(upload_report.uploader_tags_array) %>
|
||||
<% end %>
|
||||
<% t.column "Tags changed by other users", width: "45%" do |upload_report| %>
|
||||
<%= render "reports/tag_diff_list", added_tags: upload_report.added_tags_array, removed_tags: upload_report.removed_tags_array %>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<% if policy(upload).can_view_tags? %>
|
||||
<span class="info">
|
||||
<strong>Tags</strong>
|
||||
<%= TagSetPresenter.new(upload.tag_string.split).inline_tag_list_html %>
|
||||
<%= render_inline_tag_list_from_names(upload.tag_string.split) %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
63
test/components/tag_list_component_test.rb
Normal file
63
test/components/tag_list_component_test.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
require "test_helper"
|
||||
|
||||
class TagListComponentTest < ViewComponent::TestCase
|
||||
def render_tag_list(tags, variant, **options)
|
||||
with_variant(variant) do
|
||||
render_inline(TagListComponent.new(tags: tags, **options))
|
||||
end
|
||||
end
|
||||
|
||||
context "The TagListComponent" do
|
||||
setup do
|
||||
@arttag = create(:artist_tag)
|
||||
@copytag = create(:copyright_tag)
|
||||
@chartag = create(:character_tag)
|
||||
@gentag = create(:general_tag)
|
||||
@metatag = create(:meta_tag)
|
||||
@tags = Tag.all
|
||||
end
|
||||
|
||||
context "for a simple tag list" do
|
||||
should "render" do
|
||||
render_tag_list(@tags, :simple)
|
||||
|
||||
assert_css(".simple-tag-list a.search-tag", count: 5)
|
||||
end
|
||||
end
|
||||
|
||||
context "for an inline tag list" do
|
||||
should "render" do
|
||||
render_tag_list(@tags, :inline)
|
||||
|
||||
assert_css(".inline-tag-list a.search-tag", count: 5)
|
||||
end
|
||||
end
|
||||
|
||||
context "for a search tag list" do
|
||||
context "with +/- links" do
|
||||
should "render" do
|
||||
render_tag_list(@tags, :search, current_query: "touhou", show_extra_links: true)
|
||||
|
||||
assert_css(".search-tag-list li a.search-tag", count: 5)
|
||||
end
|
||||
end
|
||||
|
||||
context "without +/- links" do
|
||||
should "render" do
|
||||
render_tag_list(@tags, :search)
|
||||
|
||||
assert_css(".search-tag-list li a.search-tag", count: 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for a categorized tag list" do
|
||||
should "render" do
|
||||
render_tag_list(@tags, :categorized)
|
||||
|
||||
assert_css(".categorized-tag-list li a.search-tag", count: 5)
|
||||
assert_css(".categorized-tag-list h3", count: 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,10 @@ FactoryBot.define do
|
||||
post_count { 100 }
|
||||
category {Tag.categories.general}
|
||||
|
||||
factory(:general_tag) do
|
||||
category {Tag.categories.general}
|
||||
end
|
||||
|
||||
factory(:artist_tag) do
|
||||
category {Tag.categories.artist}
|
||||
end
|
||||
@@ -15,5 +19,9 @@ FactoryBot.define do
|
||||
factory(:character_tag) do
|
||||
category {Tag.categories.character}
|
||||
end
|
||||
|
||||
factory(:meta_tag) do
|
||||
category {Tag.categories.meta}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user