mirror of
https://github.com/iv-org/invidious.git
synced 2026-09-26 11:35:27 -05:00
Link hashtags in the visible watch title
This commit is contained in:
parent
c88230067b
commit
c3fe2c94b0
50
spec/invidious/videos/title_spec.cr
Normal file
50
spec/invidious/videos/title_spec.cr
Normal file
@ -0,0 +1,50 @@
|
||||
require "spectator"
|
||||
require "../../../src/invidious/videos/title"
|
||||
|
||||
Spectator.describe Invidious::Videos::Title do
|
||||
it "links hashtags at the start, middle and end of a title" do
|
||||
expect(described_class.to_html("#First with #TwoWords and #last")).to eq(
|
||||
%(<a href="/hashtag/First">#First</a> with <a href="/hashtag/TwoWords">#TwoWords</a> and <a href="/hashtag/last">#last</a>)
|
||||
)
|
||||
end
|
||||
|
||||
it "keeps punctuation outside hashtag links" do
|
||||
expect(described_class.to_html(%((#one), [#two]! "#three"))).to eq(
|
||||
%((<a href="/hashtag/one">#one</a>), [<a href="/hashtag/two">#two</a>]! "<a href="/hashtag/three">#three</a>")
|
||||
)
|
||||
end
|
||||
|
||||
it "preserves Unicode letters, combining marks, numbers and underscores" do
|
||||
expect(described_class.to_html("🎵 #cafe\u0301 #東京 #हिन्दी #2026_live")).to eq(
|
||||
%(🎵 <a href="/hashtag/cafe%CC%81">#cafe\u0301</a> <a href="/hashtag/%E6%9D%B1%E4%BA%AC">#東京</a> <a href="/hashtag/%E0%A4%B9%E0%A4%BF%E0%A4%A8%E0%A5%8D%E0%A4%A6%E0%A5%80">#हिन्दी</a> <a href="/hashtag/2026_live">#2026_live</a>)
|
||||
)
|
||||
end
|
||||
|
||||
it "recognizes tabs, newlines and non-breaking spaces as boundaries" do
|
||||
expect(described_class.to_html("a\t#one\n#two\u00a0#three")).to eq(
|
||||
%(a\t<a href="/hashtag/one">#one</a>\n<a href="/hashtag/two">#two</a>\u00a0<a href="/hashtag/three">#three</a>)
|
||||
)
|
||||
end
|
||||
|
||||
it "recognizes Unicode opening brackets and quotation marks" do
|
||||
expect(described_class.to_html("「#東京」 ‘#music’")).to eq(
|
||||
%(「<a href="/hashtag/%E6%9D%B1%E4%BA%AC">#東京</a>」 ‘<a href="/hashtag/music">#music</a>’)
|
||||
)
|
||||
end
|
||||
|
||||
it "does not link fragments, suffixes, bare hashes or adjacent hashes" do
|
||||
title = "https://example.com/#fragment file#part C# ##tag # #\u0301"
|
||||
expect(described_class.to_html(title)).to eq(HTML.escape(title))
|
||||
end
|
||||
|
||||
it "escapes markup and entities without creating extra elements" do
|
||||
expect(described_class.to_html(%(<script>alert("x")</script> & #safe <img src=x> {))).to eq(
|
||||
%(<script>alert("x")</script> & <a href="/hashtag/safe">#safe</a> <img src=x> &#123;)
|
||||
)
|
||||
end
|
||||
|
||||
it "preserves empty and ordinary titles" do
|
||||
expect(described_class.to_html("")).to eq("")
|
||||
expect(described_class.to_html("Rock & roll")).to eq("Rock & roll")
|
||||
end
|
||||
end
|
||||
18
src/invidious/videos/title.cr
Normal file
18
src/invidious/videos/title.cr
Normal file
@ -0,0 +1,18 @@
|
||||
require "html"
|
||||
require "uri"
|
||||
|
||||
module Invidious::Videos::Title
|
||||
def self.to_html(title : String) : String
|
||||
String.build do |html|
|
||||
offset = 0
|
||||
title.scan(/(?:\A|[\s\p{Z}\p{Ps}\p{Pi}"'])#([\p{L}\p{N}_][\p{L}\p{M}\p{N}_]*)/) do |match|
|
||||
start = match.byte_begin(1) - 1
|
||||
html << HTML.escape(title.byte_slice(offset, start - offset))
|
||||
html << %(<a href="/hashtag/#{URI.encode_path_segment(match[1])}">)
|
||||
html << HTML.escape("##{match[1]}") << "</a>"
|
||||
offset = match.byte_end(1)
|
||||
end
|
||||
html << HTML.escape(title.byte_slice(offset))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -93,7 +93,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
|
||||
<div class="h-box">
|
||||
<h1>
|
||||
<%= title %>
|
||||
<%= Invidious::Videos::Title.to_html(video.title) %>
|
||||
<% if params.listen %>
|
||||
<a title="<%=I18n.translate(locale, "Video mode")%>" id="link-iv-listen" data-base-url="/watch?<%= env.params.query %>&listen=0" href="/watch?<%= env.params.query %>&listen=0">
|
||||
<i class="icon ion-ios-videocam"></i>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user