[Misc] External link favicons (now without a spritesheet) (#610)
* [Misc] Add favicons to some external links * RuboCop changes from master * Use packs for favicon images This works better with caching. If the file changes, the url changes * Add favicons to an artists domain list * Fix aliases The lookup is done by strings, the keys were symbols * Add old deviantart cdn alias * Guard against links with that parse down to no host http:twitter.com for example * Tweak flow * Add basic tests, fix some of what I probably broke --------- Co-authored-by: Earlopain <14981592+Earlopain@users.noreply.github.com>
173
app/helpers/link_helper.rb
Normal file
@ -0,0 +1,173 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module LinkHelper
|
||||
NONE = "empty"
|
||||
DECORATABLE_DOMAINS = [
|
||||
"e621.net",
|
||||
#
|
||||
# Aggregators
|
||||
"linktr.ee",
|
||||
"carrd.co",
|
||||
#
|
||||
# Art sites
|
||||
"artfight.net",
|
||||
"artstation.com",
|
||||
"archiveofourown.com",
|
||||
"aryion.com",
|
||||
"derpibooru.org",
|
||||
"deviantart.com",
|
||||
"furaffinity.net",
|
||||
"furrynetwork.com",
|
||||
"furrystation.com",
|
||||
"hentai-foundry.com",
|
||||
"hiccears.com",
|
||||
"imgur.com",
|
||||
"inkbunny.net",
|
||||
"itaku.ee",
|
||||
"pillowfort.social",
|
||||
"pixiv.net",
|
||||
"skeb.jp",
|
||||
"sofurry.com",
|
||||
"toyhou.se",
|
||||
"tumblr.com",
|
||||
"newgrounds.com",
|
||||
"weasyl.com",
|
||||
"webtoons.com",
|
||||
#
|
||||
# Social media
|
||||
"aethy.com",
|
||||
"baraag.net",
|
||||
"bsky.app",
|
||||
"cohost.org",
|
||||
"facebook.com",
|
||||
"instagram.com",
|
||||
"pawoo.net",
|
||||
"plurk.com",
|
||||
"privatter.net",
|
||||
"reddit.com",
|
||||
"tiktok.com",
|
||||
"twitter.com",
|
||||
"vk.com",
|
||||
"weibo.com",
|
||||
"youtube.com",
|
||||
#
|
||||
# Livestreams
|
||||
"picarto.tv",
|
||||
"piczel.tv",
|
||||
"twitch.tv",
|
||||
#
|
||||
# Paysites
|
||||
"artconomy.com",
|
||||
"boosty.to",
|
||||
"buymeacoffee.com",
|
||||
"commishes.com",
|
||||
"gumroad.com",
|
||||
"etsy.com",
|
||||
"fanbox.cc",
|
||||
"itch.io",
|
||||
"ko-fi.com",
|
||||
"patreon.com",
|
||||
"redbubble.com",
|
||||
"subscribestar.adult",
|
||||
#
|
||||
# Bulk storage
|
||||
"amazonaws.com",
|
||||
"catbox.moe",
|
||||
"drive.google.com",
|
||||
"dropbox.com",
|
||||
"mega.nz",
|
||||
"onedrive.live.com",
|
||||
#
|
||||
# Imageboards
|
||||
"4chan.org",
|
||||
"danbooru.donmai.us",
|
||||
"desuarchive.org",
|
||||
"e-hentai.org",
|
||||
"gelbooru.com",
|
||||
"rule34.paheal.net",
|
||||
"rule34.xxx",
|
||||
"u18chan.com",
|
||||
#
|
||||
# Other
|
||||
"curiouscat.me",
|
||||
"discord.com",
|
||||
"steamcommunity.com",
|
||||
"t.me",
|
||||
"trello.com",
|
||||
"web.archive.org",
|
||||
].freeze
|
||||
|
||||
DECORATABLE_ALIASES = {
|
||||
# alt names
|
||||
"e926.net" => "e621.net",
|
||||
"discord.gg" => "discord.com",
|
||||
"pixiv.me" => "pixiv.net",
|
||||
"x.com" => "twitter.com",
|
||||
|
||||
# same icon
|
||||
"cloudfront.net" => "amazonaws.com",
|
||||
"mastodon.art" => "baraag.net",
|
||||
"meow.social" => "baraag.net",
|
||||
"sta.sh" => "deviantart.com",
|
||||
|
||||
# image servers
|
||||
"4cdn.org" => "4chan.org",
|
||||
"discordapp.com" => "discord.com",
|
||||
"derpicdn.net" => "derpibooru.org",
|
||||
"deviantart.net" => "deviantart.com",
|
||||
"dropboxusercontent.com" => "dropbox.com",
|
||||
"facdn.net" => "furaffinity.net",
|
||||
"fbcdn.net" => "facebook.com",
|
||||
"ib.metapix.net" => "inkbunny.net",
|
||||
"ngfiles.com" => "newgrounds.com",
|
||||
"pximg.net" => "pixiv.net",
|
||||
"redd.it" => "reddit.com",
|
||||
"twimg.com" => "twitter.com",
|
||||
"ungrounded.net" => "newgrounds.com",
|
||||
"wixmp.com" => "deviantart.com",
|
||||
}.freeze
|
||||
|
||||
def decorated_link_to(text, path, **)
|
||||
link_to(path, class: "decorated", **) do
|
||||
favicon_for_link(path) + text
|
||||
end
|
||||
end
|
||||
|
||||
def favicon_for_link(path)
|
||||
hostname = hostname_for_link(path)
|
||||
image_url = asset_pack_path("static/#{hostname}.png")
|
||||
tag.span(
|
||||
class: "link-decoration",
|
||||
style: "background-image: url(#{image_url})",
|
||||
data: {
|
||||
hostname: hostname,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
def hostname_for_link(path)
|
||||
begin
|
||||
uri = URI.parse(path)
|
||||
rescue URI::InvalidURIError
|
||||
return NONE
|
||||
end
|
||||
return NONE unless uri.host
|
||||
|
||||
hostname = uri.host.delete_prefix("www.")
|
||||
|
||||
# 1: direct match
|
||||
return hostname if DECORATABLE_DOMAINS.include?(hostname)
|
||||
|
||||
# 2: aliases
|
||||
return DECORATABLE_ALIASES[hostname] if DECORATABLE_ALIASES[hostname]
|
||||
|
||||
# 3: Try the same, this time with the leftmost subdomain removed
|
||||
if hostname.count(".") > 1
|
||||
_removed, remaining_hostname = hostname.split(".", 2)
|
||||
return remaining_hostname if DECORATABLE_DOMAINS.include?(remaining_hostname)
|
||||
return DECORATABLE_ALIASES[remaining_hostname] if DECORATABLE_ALIASES[remaining_hostname]
|
||||
end
|
||||
|
||||
NONE
|
||||
end
|
||||
end
|
@ -22,7 +22,7 @@ module PostsHelper
|
||||
def post_source_tag(source)
|
||||
# Only allow http:// and https:// links. Disallow javascript: links.
|
||||
if source =~ %r{\Ahttps?://}i
|
||||
source_link = link_to(source.sub(%r{\Ahttps?://(?:www\.)?}i, ""), source, target: "_blank", rel: "nofollow noreferrer noopener")
|
||||
source_link = decorated_link_to(source.sub(%r{\Ahttps?://(?:www\.)?}i, ""), source, target: "_blank", rel: "nofollow noreferrer noopener")
|
||||
|
||||
if CurrentUser.is_janitor?
|
||||
source_link += " ".html_safe + link_to("»", posts_path(tags: "source:#{source.sub(%r{[^/]*$}, '')}"), rel: "nofollow")
|
||||
|
@ -32,6 +32,8 @@ require('../src/styles/base.scss');
|
||||
|
||||
importAll(require.context('../src/javascripts', true, /\.js(\.erb)?$/));
|
||||
|
||||
require.context("../../../public/images/favicons", true)
|
||||
|
||||
export { default as Artist } from '../src/javascripts/artist.js';
|
||||
export { default as Autocomplete } from '../src/javascripts/autocomplete.js.erb';
|
||||
export { default as Blacklist } from '../src/javascripts/blacklists.js';
|
||||
|
@ -19,6 +19,7 @@
|
||||
@import "common/forms.scss";
|
||||
@import "common/inline.scss";
|
||||
@import "common/jquery_ui_custom.scss";
|
||||
@import "common/link_decorator.scss";
|
||||
@import "common/main_layout.scss";
|
||||
@import "common/news.scss";
|
||||
@import "common/notices.scss";
|
||||
|
9
app/javascript/src/styles/common/link_decorator.scss
Normal file
@ -0,0 +1,9 @@
|
||||
span.link-decoration {
|
||||
vertical-align: sub;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
line-height: 0;
|
||||
background-size: 16px 16px;
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
<li><strong>Domains</strong></li>
|
||||
<ul>
|
||||
<% artist.domains.each do |url, count| %>
|
||||
<li><%= url %>: <%= count %></li>
|
||||
<li><%= favicon_for_link("https://#{url}") %> <%= url %>: <%= count %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
@ -29,7 +29,7 @@
|
||||
<% artist.sorted_urls.each do |url| %>
|
||||
<li>
|
||||
<% if url.is_active? %>
|
||||
<%= link_to h(url.to_s), h(url) %>
|
||||
<%= decorated_link_to(h(url.to_s), h(url)) %>
|
||||
<% else %>
|
||||
<del><%= h(url.url) %></del>
|
||||
<% end %>
|
||||
|
BIN
public/images/favicons/4chan.org.png
Normal file
After Width: | Height: | Size: 164 B |
BIN
public/images/favicons/aethy.com.png
Normal file
After Width: | Height: | Size: 749 B |
BIN
public/images/favicons/amazonaws.com.png
Normal file
After Width: | Height: | Size: 391 B |
BIN
public/images/favicons/archiveofourown.com.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
public/images/favicons/artconomy.com.png
Normal file
After Width: | Height: | Size: 463 B |
BIN
public/images/favicons/artfight.net.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/favicons/artstation.com.png
Normal file
After Width: | Height: | Size: 457 B |
BIN
public/images/favicons/aryion.com.png
Normal file
After Width: | Height: | Size: 490 B |
BIN
public/images/favicons/baraag.net.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/favicons/boosty.to.png
Normal file
After Width: | Height: | Size: 424 B |
BIN
public/images/favicons/bsky.app.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
public/images/favicons/buymeacoffee.com.png
Normal file
After Width: | Height: | Size: 913 B |
BIN
public/images/favicons/carrd.co.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
public/images/favicons/catbox.moe.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/favicons/cohost.org.png
Normal file
After Width: | Height: | Size: 739 B |
BIN
public/images/favicons/commishes.com.png
Normal file
After Width: | Height: | Size: 876 B |
BIN
public/images/favicons/curiouscat.me.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
public/images/favicons/danbooru.donmai.us.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
public/images/favicons/derpibooru.org.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
public/images/favicons/desuarchive.org.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public/images/favicons/deviantart.com.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
public/images/favicons/discord.com.png
Normal file
After Width: | Height: | Size: 571 B |
BIN
public/images/favicons/drive.google.com.png
Normal file
After Width: | Height: | Size: 551 B |
BIN
public/images/favicons/dropbox.com.png
Normal file
After Width: | Height: | Size: 750 B |
BIN
public/images/favicons/e-hentai.org.png
Normal file
After Width: | Height: | Size: 120 B |
BIN
public/images/favicons/e621.net.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
public/images/favicons/empty.png
Normal file
After Width: | Height: | Size: 96 B |
BIN
public/images/favicons/etsy.com.png
Normal file
After Width: | Height: | Size: 519 B |
BIN
public/images/favicons/facebook.com.png
Normal file
After Width: | Height: | Size: 379 B |
BIN
public/images/favicons/fanbox.cc.png
Normal file
After Width: | Height: | Size: 443 B |
BIN
public/images/favicons/furaffinity.net.png
Normal file
After Width: | Height: | Size: 718 B |
BIN
public/images/favicons/furrynetwork.com.png
Normal file
After Width: | Height: | Size: 552 B |
BIN
public/images/favicons/furrystation.com.png
Normal file
After Width: | Height: | Size: 462 B |
BIN
public/images/favicons/gelbooru.com.png
Normal file
After Width: | Height: | Size: 725 B |
BIN
public/images/favicons/gumroad.com.png
Normal file
After Width: | Height: | Size: 601 B |
BIN
public/images/favicons/hentai-foundry.com.png
Normal file
After Width: | Height: | Size: 215 B |
BIN
public/images/favicons/hiccears.com.png
Normal file
After Width: | Height: | Size: 631 B |
BIN
public/images/favicons/imgur.com.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/favicons/inkbunny.net.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/favicons/instagram.com.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
public/images/favicons/itaku.ee.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
public/images/favicons/itch.io.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
public/images/favicons/ko-fi.com.png
Normal file
After Width: | Height: | Size: 507 B |
BIN
public/images/favicons/linktr.ee.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
public/images/favicons/mega.nz.png
Normal file
After Width: | Height: | Size: 388 B |
BIN
public/images/favicons/newgrounds.com.png
Normal file
After Width: | Height: | Size: 445 B |
BIN
public/images/favicons/onedrive.live.com.png
Normal file
After Width: | Height: | Size: 565 B |
BIN
public/images/favicons/patreon.com.png
Normal file
After Width: | Height: | Size: 344 B |
BIN
public/images/favicons/pawoo.net.png
Normal file
After Width: | Height: | Size: 423 B |
BIN
public/images/favicons/picarto.tv.png
Normal file
After Width: | Height: | Size: 672 B |
BIN
public/images/favicons/piczel.tv.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/favicons/pillowfort.social.png
Normal file
After Width: | Height: | Size: 526 B |
BIN
public/images/favicons/pixiv.net.png
Normal file
After Width: | Height: | Size: 660 B |
BIN
public/images/favicons/plurk.com.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
public/images/favicons/privatter.net.png
Normal file
After Width: | Height: | Size: 552 B |
BIN
public/images/favicons/redbubble.com.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
public/images/favicons/reddit.com.png
Normal file
After Width: | Height: | Size: 600 B |
BIN
public/images/favicons/rule34.paheal.net.png
Normal file
After Width: | Height: | Size: 538 B |
BIN
public/images/favicons/rule34.xxx.png
Normal file
After Width: | Height: | Size: 543 B |
BIN
public/images/favicons/skeb.jp.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/favicons/sofurry.com.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/favicons/steamcommunity.com.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/favicons/subscribestar.adult.png
Normal file
After Width: | Height: | Size: 572 B |
BIN
public/images/favicons/t.me.png
Normal file
After Width: | Height: | Size: 822 B |
BIN
public/images/favicons/tiktok.com.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
public/images/favicons/toyhou.se.png
Normal file
After Width: | Height: | Size: 363 B |
BIN
public/images/favicons/trello.com.png
Normal file
After Width: | Height: | Size: 388 B |
BIN
public/images/favicons/tumblr.com.png
Normal file
After Width: | Height: | Size: 322 B |
BIN
public/images/favicons/twitch.tv.png
Normal file
After Width: | Height: | Size: 249 B |
BIN
public/images/favicons/twitter.com.png
Normal file
After Width: | Height: | Size: 381 B |
BIN
public/images/favicons/u18chan.com.png
Normal file
After Width: | Height: | Size: 895 B |
BIN
public/images/favicons/vk.com.png
Normal file
After Width: | Height: | Size: 425 B |
BIN
public/images/favicons/weasyl.com.png
Normal file
After Width: | Height: | Size: 843 B |
BIN
public/images/favicons/web.archive.org.png
Normal file
After Width: | Height: | Size: 396 B |
BIN
public/images/favicons/webtoons.com.png
Normal file
After Width: | Height: | Size: 487 B |
BIN
public/images/favicons/weibo.com.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/favicons/youtube.com.png
Normal file
After Width: | Height: | Size: 622 B |
44
test/helpers/link_helper_test.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class LinkHelperTest < ActionView::TestCase
|
||||
test "for a non-handled url" do
|
||||
assert_equal(LinkHelper::NONE, hostname_for_link("https://example.com"))
|
||||
end
|
||||
|
||||
test "for a invalid url" do
|
||||
assert_equal(LinkHelper::NONE, hostname_for_link("https:example.com"))
|
||||
end
|
||||
|
||||
test "for a non-url" do
|
||||
assert_equal(LinkHelper::NONE, hostname_for_link("text"))
|
||||
end
|
||||
|
||||
test "for a normal domain" do
|
||||
assert_equal("e621.net", hostname_for_link("https://e621.net"))
|
||||
assert_equal("e621.net", hostname_for_link("https://www.e621.net"))
|
||||
|
||||
assert_equal("imgur.com", hostname_for_link("https://imgur.com"))
|
||||
assert_equal("imgur.com", hostname_for_link("https://www.imgur.com"))
|
||||
end
|
||||
|
||||
test "for a domain with aliases" do
|
||||
assert_equal("discord.com", hostname_for_link("https://discordapp.com"))
|
||||
assert_equal("furaffinity.net", hostname_for_link("https://d.facdn.net"))
|
||||
|
||||
assert_equal("inkbunny.net", hostname_for_link("https://ib.metapix.net"))
|
||||
assert_equal("inkbunny.net", hostname_for_link("https://qb.ib.metapix.net"))
|
||||
end
|
||||
|
||||
test "all listed images exist" do
|
||||
favicon_folder = Rails.public_path.join("images/favicons")
|
||||
all_domains = LinkHelper::DECORATABLE_DOMAINS + LinkHelper::DECORATABLE_ALIASES.values + [LinkHelper::NONE]
|
||||
all_domains.each do |domain|
|
||||
assert(favicon_folder.join("#{domain}.png").exist?, "missing #{domain}")
|
||||
end
|
||||
# No extraneous files
|
||||
all_files = favicon_folder.children.map { |file| file.basename.to_s.delete_suffix(".png") }
|
||||
assert_empty(all_files - all_domains, "unused files in favicon folder")
|
||||
end
|
||||
end
|