more domain handling, inline link rewriting

This commit is contained in:
Dylan Knutson
2025-02-22 23:38:34 +00:00
parent 1801d475e7
commit 3ea8dbfe83
8 changed files with 146 additions and 24 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -5,6 +5,7 @@ module Domain::DescriptionsHelper
extend T::Helpers
include HelpersInterface
include Domain::PostsHelper
include Domain::DomainsHelper
requires_ancestor { Object }
abstract!
@@ -33,6 +34,13 @@ module Domain::DescriptionsHelper
nil
end
sig { params(text: String, url: String).returns(T::Boolean) }
def text_same_as_url?(text, url)
text = text.strip
url = url.strip
["", "http://", "https://"].any? { |prefix| "#{prefix}#{text}" == url }
end
sig { params(model: HasDescriptionHtmlForView).returns(T.nilable(String)) }
def sanitize_description_html(model)
html = model.description_html_for_view
@@ -87,7 +95,32 @@ module Domain::DescriptionsHelper
next { node_whitelist: [] } if url.nil?
found_link = link_for_source(url.to_s)
next { node_whitelist: [] } if found_link.nil?
if found_link.nil?
if ALLOWED_EXTERNAL_LINK_DOMAINS.any? { |domain|
url_matches_domain?(domain, url.host)
}
if text_same_as_url?(node.text, url.to_s)
title = title_for_url(url.to_s)
else
title = node.text
end
replacements[node] = Nokogiri::HTML5.fragment(
render(
partial:
"domain/has_description_html/inline_link_external",
locals: {
url: url.to_s,
text: title,
icon_path: icon_path_for_domain(url.host),
},
),
)
next { node_whitelist: [node] }
else
next { node_whitelist: [] }
end
end
found_model = found_link.model
partial, as =

View File

@@ -0,0 +1,99 @@
# typed: strict
module Domain::DomainsHelper
extend T::Sig
extend T::Helpers
include HelpersInterface
abstract!
ALLOWED_EXTERNAL_LINK_DOMAINS = %w[
youtube.com
x.com
weasyl.com
vimeo.com
twitter.com
twitch.tv
tumblr.com
t.me
spreadshirt.de
spreadshirt.com
redbubble.com
pixiv.net
pinterest.com
patreon.com
mstdn.social
livejournal.com
ko-fi.com
instagram.com
facebook.com
dribbble.com
discord.gg
deviantart.com
bsky.app
behance.net
gumroad.com
bigcartel.com
].freeze
DOMAIN_TO_ICON_PATH =
T.let(
{
"x.com" => "x-twitter.png",
"wixmp.com" => "deviantart.png",
"weasyl.com" => "weasyl.png",
"twitter.com" => "x-twitter.png",
"t.me" => "telegram.png",
"pixiv.net" => "pixiv.png",
"patreon.com" => "patreon.png",
"newgrounds.com" => "newgrounds.png",
"itaku.ee" => "itaku.png",
"inkbunny.net" => "inkbunny.png",
"ib.metapix.net" => "inkbunny.png",
"furaffinity.net" => "fa.png",
"e621.net" => "e621.png",
"deviantart.com" => "deviantart.png",
"bsky.app" => "bsky.png",
"redbubble.com" => "redbubble.png",
"spreadshirt.de" => "spreadshirt.png",
"spreadshirt.com" => "spreadshirt.png",
}.freeze,
T::Hash[String, String],
)
DOMAIN_TITLE_MAPPERS =
T.let(
[
[%r{://t.me/([^/]+)}, ->(match) { match[1] }],
[%r{://bsky.app/profile/([^/]+)}, ->(match) { match[1] }],
[%r{://(.*\.)?x.com/([^/]+)}, ->(match) { match[2] }],
[%r{://(.*\.)?twitter.com/([^/]+)}, ->(match) { match[2] }],
[%r{://(.*\.)?patreon.com/([^/]+)}, ->(match) { match[2] }],
],
T::Array[[Regexp, T.proc.params(match: MatchData).returns(String)]],
)
sig { params(domain: String, host: String).returns(T::Boolean) }
def url_matches_domain?(domain, host)
host == domain || host.end_with?(".#{domain}")
end
sig { params(domain: String).returns(T.nilable(String)) }
def icon_path_for_domain(domain)
for test_domain, icon in DOMAIN_TO_ICON_PATH
if url_matches_domain?(test_domain, domain)
return asset_path("domain-icons/#{icon}")
end
end
nil
end
sig { params(url: String).returns(String) }
def title_for_url(url)
url = url.to_s
for mapper in DOMAIN_TITLE_MAPPERS
if (match = mapper[0].match(url)) && (group = mapper[1].call(match))
return group
end
end
url
end
end

View File

@@ -6,6 +6,7 @@ module Domain::PostsHelper
include LogEntriesHelper
include Domain::UsersHelper
include PathsHelper
include Domain::DomainsHelper
abstract!
class DomainData < T::Struct
@@ -121,29 +122,7 @@ module Domain::PostsHelper
def icon_asset_for_url(url)
domain = extract_domain(url)
return nil unless domain
domain_patterns = {
%w[*.e621.net e621.net] => "e621.png",
%w[*.furaffinity.net furaffinity.net] => "fa.png",
%w[*.bsky.app bsky.app] => "bsky.png",
%w[*.itaku.ee itaku.ee] => "itaku.png",
%w[*.deviantart.com deviantart.com *.wixmp.com] => "deviantart.png",
%w[*.twitter.com twitter.com *.x.com x.com] => "x-twitter.png",
%w[*.inkbunny.net inkbunny.net *.ib.metapix.net ib.metapix.net] =>
"inkbunny.png",
%w[*.newgrounds.com newgrounds.com] => "newgrounds.png",
%w[*.patreon.com patreon.com] => "patreon.png",
%w[*.pixiv.net pixiv.net *.pximg.net pximg.net] => "pixiv.png",
}
domain_patterns.each do |patterns, icon|
patterns.each do |pattern|
if File.fnmatch?(pattern, domain, File::FNM_PATHNAME)
return asset_path("domain-icons/#{icon}")
end
end
end
nil
icon_path_for_domain(domain)
end
sig { params(category: Symbol).returns(String) }

View File

@@ -0,0 +1,11 @@
<%= link_to(
url,
target: "_blank noopener noreferrer nofollow",
class:
"hover:underline text-slate-200 bg-slate-800 hover:text-slate-100 hover:bg-slate-700 border-slate-500 border rounded-md px-1 whitespace-nowrap inline-flex items-center gap-1 align-top",
) do %>
<% if local_assigns[:icon_path] %>
<%= image_tag local_assigns[:icon_path], class: "w-4 h-4 inline-block rounded-sm" %>
<% end %>
<span><%= text %></span>
<% end %>