tags: refactor tag lists to use ViewComponent.

This commit is contained in:
evazion
2021-01-30 14:01:07 -06:00
parent 9d60046f1d
commit 1f637867a4
25 changed files with 256 additions and 131 deletions

View 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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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 %>

View File

@@ -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

View File

@@ -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

View File

@@ -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">&ndash;</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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 }) %>

View File

@@ -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" %>

View File

@@ -52,6 +52,6 @@
<% end %>
</div>
<%= @post.presenter.inline_tag_list_html %>
<%= render_inline_tag_list(@post) %>
</div>
</div>

View File

@@ -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">

View File

@@ -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| %>

View File

@@ -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 %>

View File

@@ -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 %>

View File

@@ -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 %>

View 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

View File

@@ -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