sources: add artist profile links to fetch source data box.

Add site icons linking to all the artist's sites in the fetch source
data box.

Some artist entries have a large number of URLs. Various heuristics are
applied to try to present the most useful URLs first. Dead URLs and
redundant URLs (Pixiv stacc and Twitter intent URLs) are filtered out.
Remaining URLs are sorted first by site (to put sites like Pixiv and
Twitter first), then by URL (to break ties when an artist has multiple
accounts on the same site).

Some sites have shitty hard-to-read icons. It can't be helped. The icons
are the official favicons of each site.
This commit is contained in:
evazion
2021-02-26 01:04:44 -06:00
parent e1ef94faf7
commit 7b60a476e5
35 changed files with 248 additions and 64 deletions

View File

@@ -2,9 +2,15 @@
class SourceDataComponent < ApplicationComponent class SourceDataComponent < ApplicationComponent
attr_reader :source attr_reader :source
delegate :spinner_icon, to: :helpers delegate :spinner_icon, :external_site_icon, to: :helpers
def initialize(source:) def initialize(source:)
@source = source @source = source
end end
def profile_urls(artist)
artist.urls.active.reject(&:secondary_url?).sort_by do |artist_url|
[artist_url.priority, artist_url.domain, artist_url.url]
end
end
end end

View File

@@ -3,46 +3,55 @@
<%= spinner_icon class: "source-data-loading" %> <%= spinner_icon class: "source-data-loading" %>
<% if @source.present? %> <% if @source.present? %>
<dl class="source-data-content"> <table class="source-data-content mt-2">
<div class="source-data-artist"> <tbody>
<dt>Artist</dt> <% if @source.artist_name.blank? %>
<dd> <tr>
<% if @source.artist_name.blank? %> <th>Artist</th>
<em>None</em> <td><em>None</em></td>
<% else %> </tr>
<%= external_link_to @source.profile_url, @source.artist_name, class: "source-data-artist-profile" %> <% elsif @source.artists.empty? %>
<tr>
<th>Artist</th>
<td>
<%= external_link_to @source.profile_url, @source.artist_name %>
(<%= link_to "Create new artist", new_artist_path(artist: { source: @source.canonical_url }) %>)
</td>
</tr>
<% else %>
<% @source.artists.each do |artist| %>
<tr>
<th>Artist</th>
<td>
<%= link_to artist.name, artist_path(artist), class: tag_class(artist.tag) %>
<% if @source.artists.empty? %> <ul class="list-inline">
(<%= link_to "Create new artist", new_artist_path(artist: { source: @source.canonical_url }), class: "source-data-create-new-artist" %>) <% profile_urls(artist).each do |artist_url| %>
<%= external_link_to artist_url.url, external_site_icon(artist_url.site_name), title: artist_url.url %>
<% end %>
</ul>
</td>
</tr>
<% end %>
<% end %>
<tr class="source-data-tags">
<th>Tags</th>
<td>
<% if @source.tags.empty? %>
<em>None</em>
<% else %> <% else %>
(<ul class="source-data-translated-artists"> <ul class="list-inline">
<% @source.artists.each do |artist| %> <% @source.tags.each do |tag, href| %>
<li><%= link_to artist.name, artist_path(artist), class: tag_class(artist.tag) %></li> <li>
<%= external_link_to href, tag, class: "source-data-tag" %>
</li>
<% end %> <% end %>
</ul>) </ul>
<% end %> <% end %>
<% end %> </td>
</dd> </tr>
</div> </tbody>
</table>
<div class="source-data-tags">
<dt>Tags</dt>
<dd>
<% if @source.tags.empty? %>
<em>None</em>
<% else %>
<ul>
<% @source.tags.each do |tag, href| %>
<li><%= external_link_to href, tag %></li>
<% end %>
</ul>
<% if @source.image_urls.length > 1 %>
<p class="source-data-gallery-warning">Gallery. Tags may not apply to all images.</p>
<% end %>
<% end %>
</dd>
</li>
</dl>
<% end %> <% end %>
</div> </div>

View File

@@ -6,15 +6,27 @@ div.source-data {
&.loading .source-data-content { display: none; } &.loading .source-data-content { display: none; }
&.loading .source-data-fetch { display: none; } &.loading .source-data-fetch { display: none; }
ul { th {
padding-right: 1rem;
padding-bottom: 0.25rem;
}
td {
vertical-align: top;
}
.icon {
height: 1rem;
font-size: 1rem;
vertical-align: bottom;
}
.source-data-tag {
display: inline-block; display: inline-block;
} background-color: var(--wiki-page-other-name-background-color);
padding: 0 0.25rem;
dt, dd, li { margin-right: 0.25rem;
display: inline; margin-bottom: 0.25rem;
} border-radius: 0.25rem;
dt, .source-data-tags li {
margin-right: 1em;
} }
} }

View File

@@ -11,6 +11,11 @@ module IconHelper
end end
end end
def image_icon_tag(filename, class: nil, **options)
klass = binding.local_variable_get(:class)
tag.img(src: "/images/#{filename}", class: "icon #{klass}", **options)
end
# fontawesome.com/icons/arrow-alt-up # fontawesome.com/icons/arrow-alt-up
def upvote_icon(**options) def upvote_icon(**options)
svg_icon_tag("upvote-icon", "M272 480h-96c-13.3 0-24-10.7-24-24V256H48.2c-21.4 0-32.1-25.8-17-41L207 39c9.4-9.4 24.6-9.4 34 0l175.8 176c15.1 15.1 4.4 41-17 41H296v200c0 13.3-10.7 24-24 24z", **options) svg_icon_tag("upvote-icon", "M272 480h-96c-13.3 0-24-10.7-24-24V256H48.2c-21.4 0-32.1-25.8-17-41L207 39c9.4-9.4 24.6-9.4 34 0l175.8 176c15.1 15.1 4.4 41-17 41H296v200c0 13.3-10.7 24-24 24z", **options)
@@ -148,4 +153,83 @@ module IconHelper
def plus_icon(**options) def plus_icon(**options)
icon_tag("fas fa-plus", **options) icon_tag("fas fa-plus", **options)
end end
def globe_icon(**options)
icon_tag("fas fa-globe", **options)
end
def discord_icon(**options)
image_icon_tag("discord-logo.png", **options)
end
def github_icon(**options)
image_icon_tag("github-logo.png", **options)
end
def twitter_icon(**options)
image_icon_tag("twitter-logo.png", **options)
end
def external_site_icon(site_name, **options)
case site_name
when "ArtStation"
image_icon_tag("artstation-logo.png", **options)
when "BCY"
image_icon_tag("bcy-logo.png", **options)
when "Booth.pm"
image_icon_tag("booth-pm-logo.png", **options)
when "Circle.ms"
image_icon_tag("circle-ms-logo.png", **options)
when "DLSite"
image_icon_tag("dlsite-logo.png", **options)
when "Deviant Art"
image_icon_tag("deviantart-logo.png", **options)
when "Facebook"
image_icon_tag("facebook-logo.png", **options)
when "Fantia"
image_icon_tag("fantia-logo.png", **options)
when "FC2"
image_icon_tag("fc2-logo.png", **options)
when "Gumroad"
image_icon_tag("gumroad-logo.png", **options)
when "Instagram"
image_icon_tag("instagram-logo.png", **options)
when "Lofter"
image_icon_tag("lofter-logo.png", **options)
when "Melonbooks"
image_icon_tag("melonbooks-logo.png", **options)
when "Nico Seiga"
image_icon_tag("nicoseiga-logo.png", **options)
when "Nijie"
image_icon_tag("nijie-logo.png", **options)
when "Patreon"
image_icon_tag("patreon-logo.png", **options)
when "pawoo.net"
image_icon_tag("pawoo-logo.png", **options)
when "Pixiv"
image_icon_tag("pixiv-logo.png", **options)
when "Pixiv Fanbox"
image_icon_tag("pixiv-fanbox-logo.png", **options)
when "Pixiv Sketch"
image_icon_tag("pixiv-sketch-logo.png", **options)
when "Privatter"
image_icon_tag("privatter-logo.png", **options)
when "Skeb"
image_icon_tag("skeb-logo.png", **options)
when "Tinami"
image_icon_tag("tinami-logo.png", **options)
when "Tumblr"
image_icon_tag("tumblr-logo.png", **options)
when "Twitter"
image_icon_tag("twitter-logo.png", **options)
when "Toranoana"
image_icon_tag("toranoana-logo.png", **options)
when "Weibo"
image_icon_tag("weibo-logo.png", **options)
when "Youtube"
image_icon_tag("youtube-logo.png", **options)
else
globe_icon(**options)
end
end
end end

View File

@@ -60,7 +60,47 @@ module Sources
end end
def site_name def site_name
Addressable::URI.heuristic_parse(url)&.host host = Addressable::URI.heuristic_parse(url)&.host
# XXX should go in dedicated strategies.
case host
when /bcy\.net\z/i
"BCY"
when /booth\.pm\z/i
"Booth.pm"
when /circle\.ms\z/i
"Circle.ms"
when /dlsite\.(com|net)\z/i
"DLSite"
when /facebook\.com\z/i
"Facebook"
when /fantia\.jp\z/i
"Fantia"
when /fc2\.com\z/i
"FC2"
when /gumroad\.com\z/i
"Gumroad"
when /instagram\.com\z/i
"Instagram"
when /lofter\.com\z/i
"Lofter"
when /melonbooks\.co\.jp\z/i
"Melonbooks"
when /patreon\.com\z/i
"Patreon"
when /privatter\.net\z/i
"Privatter"
when /skeb\.jp\z/i
"Skeb"
when /tinami\.com\z/i
"Tinami"
when /toranoana\.(jp|shop)\z/i
"Toranoana"
when /youtube\.com\z/i
"Youtube"
else
host
end
rescue Addressable::URI::InvalidURIError rescue Addressable::URI::InvalidURIError
nil nil
end end

View File

@@ -90,7 +90,12 @@ module Sources
end end
def site_name def site_name
"Pixiv" # XXX pixiv sketch should be in a separate strategy
if parsed_url.host.in?(%w[sketch.pixiv.net img-sketch.pixiv.net img-sketch.pximg.net])
"Pixiv Sketch"
else
"Pixiv"
end
end end
def image_urls def image_urls

View File

@@ -7,6 +7,7 @@ class ArtistUrl < ApplicationRecord
scope :url_matches, ->(url) { url_attribute_matches(:url, url) } scope :url_matches, ->(url) { url_attribute_matches(:url, url) }
scope :normalized_url_matches, ->(url) { url_attribute_matches(:normalized_url, url) } scope :normalized_url_matches, ->(url) { url_attribute_matches(:normalized_url, url) }
scope :active, -> { where(is_active: true) }
def self.parse_prefix(url) def self.parse_prefix(url)
prefix, url = url.match(/\A(-)?(.*)/)[1, 2] prefix, url = url.match(/\A(-)?(.*)/)[1, 2]
@@ -68,24 +69,51 @@ class ArtistUrl < ApplicationRecord
end end
end end
def priority def domain
if normalized_url =~ /pixiv\.net\/member\.php/ uri = Addressable::URI.parse(normalized_url)
10 uri.domain
end
elsif normalized_url =~ /seiga\.nicovideo\.jp\/user\/illust/ def site_name
10 source = Sources::Strategies.find(normalized_url)
source.site_name
elsif normalized_url =~ /twitter\.com/ && normalized_url !~ /status/ end
15
elsif normalized_url =~ /tumblr|patreon|deviantart|artstation/
20
# A secondary URL is an artist URL that we don't normally want to display,
# usually because it's redundant with the primary profile URL.
def secondary_url?
case url
when %r!pixiv\.net/stacc!i
true
when %r!pixiv\.net/fanbox!i
true
when %r!twitter\.com/intent!i
true
when %r!lohas\.nicoseiga\.jp!i
true
when %r!(?:www|com|dic)\.nicovideo\.jp!i
true
when %r!pawoo\.net/web/accounts!i
true
when %r!www\.artstation\.com!i
true
else else
100 false
end end
end end
# The sort order of sites in artist URL lists.
def priority
sites = %w[
Pixiv Twitter
ArtStation Deviant\ Art Nico\ Seiga Nijie pawoo.net Pixiv\ Fanbox Pixiv\ Sketch Tinami Tumblr
Booth.pm Facebook Fantia FC2 Gumroad Instagram Lofter Patreon Privatter Skeb Weibo Youtube
Circle.ms DLSite Melonbooks Toranoana
]
sites.index(site_name) || 1000
end
def normalize def normalize
# Perform some normalization with Addressable on the URL itself # Perform some normalization with Addressable on the URL itself
# - Converts scheme and hostname to downcase # - Converts scheme and hostname to downcase

View File

@@ -6,17 +6,17 @@
<span class="social-icons"> <span class="social-icons">
<% if Danbooru.config.source_code_url.present? %> <% if Danbooru.config.source_code_url.present? %>
<%= link_to Danbooru.config.source_code_url, title: "Running commit: #{Rails.application.config.x.git_hash&.first(9)}", class: "social-icon" do %> <%= link_to Danbooru.config.source_code_url, title: "Running commit: #{Rails.application.config.x.git_hash&.first(9)}", class: "social-icon" do %>
<img src="/images/github-logo.png"> <%= github_icon %>
<% end %> <% end %>
<% end %> <% end %>
<% if Danbooru.config.twitter_username.present? %> <% if Danbooru.config.twitter_username.present? %>
<%= link_to "https://twitter.com/#{Danbooru.config.twitter_username}", class: "social-icon" do %> <%= link_to "https://twitter.com/#{Danbooru.config.twitter_username}", class: "social-icon" do %>
<img src="/images/twitter-logo.png"> <%= twitter_icon %>
<% end %> <% end %>
<% end %> <% end %>
<% if Danbooru.config.discord_server_url.present? %> <% if Danbooru.config.discord_server_url.present? %>
<%= link_to Danbooru.config.discord_server_url, class: "social-icon" do %> <%= link_to Danbooru.config.discord_server_url, class: "social-icon" do %>
<img src="/images/discord-logo.png"> <%= discord_icon %>
<% end %> <% end %>
<% end %> <% end %>
</span> </span>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/images/bcy-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/images/fantia-logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
public/images/fc2-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/images/skeb-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB