From f28ac7c7cbc92972547c30f60f563bd93ec0d91e Mon Sep 17 00:00:00 2001 From: awer123456 <127011722+awer123456@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:52:48 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E6=A0=87=E9=A2=98=E7=9A=84?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=8F=91=E7=94=9F=E4=BA=86=E5=8F=98=E5=8C=96?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/invidious/frontend/watch_page_spec.cr | 56 ++++++++++++++++++++++ src/invidious/frontend/watch_page.cr | 41 ++++++++++++++++ src/invidious/views/watch.ecr | 2 +- 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 spec/invidious/frontend/watch_page_spec.cr diff --git a/spec/invidious/frontend/watch_page_spec.cr b/spec/invidious/frontend/watch_page_spec.cr new file mode 100644 index 000000000..a44d36c15 --- /dev/null +++ b/spec/invidious/frontend/watch_page_spec.cr @@ -0,0 +1,56 @@ +require "../../spec_helper" +require "../../../src/invidious/frontend/watch_page" + +Spectator.describe Invidious::Frontend::WatchPage do + describe ".linkify_title_hashtags" do + it "links hashtags while preserving surrounding punctuation" do + result = Invidious::Frontend::WatchPage.linkify_title_hashtags( + "Watch (#music), then #news!" + ) + + expect(result).to eq( + %(Watch (#music), then #news!) + ) + end + + it "does not link hashes inside words or without a name" do + result = Invidious::Frontend::WatchPage.linkify_title_hashtags( + "C# test#music #" + ) + + expect(result).to eq("C# test#music #") + end + + it "links and encodes Unicode hashtags" do + hashtag = "\u{97F3}\u{4E50}" + + result = Invidious::Frontend::WatchPage.linkify_title_hashtags( + "Listen ##{hashtag}" + ) + + expect(result).to eq( + %(Listen ##{hashtag}) + ) + end + + it "escapes title markup and link text" do + result = Invidious::Frontend::WatchPage.linkify_title_hashtags( + %( & #news) + ) + + expect(result).to eq( + %(<script>#music</script> & #news) + ) + end + + it "does not turn character-reference-like text into hashtag links" do + result = Invidious::Frontend::WatchPage.linkify_title_hashtags( + "Literal A and A plus #music" + ) + + expect(result).to eq( + %(Literal &#65; and &#x41; plus #music) + ) + end + end +end diff --git a/src/invidious/frontend/watch_page.cr b/src/invidious/frontend/watch_page.cr index 2f640e8cf..2eda3e732 100644 --- a/src/invidious/frontend/watch_page.cr +++ b/src/invidious/frontend/watch_page.cr @@ -1,6 +1,47 @@ module Invidious::Frontend::WatchPage extend self + def linkify_title_hashtags(title : String) : String + characters = title.chars + + String.build(title.bytesize) do |html| + index = 0 + + while index < characters.size + character = characters[index] + starts_hashtag = character == '#' && + (index == 0 || !hashtag_prefix_character?(characters[index - 1])) + + if starts_hashtag + hashtag_end = index + 1 + while hashtag_end < characters.size && hashtag_character?(characters[hashtag_end]) + hashtag_end += 1 + end + + if hashtag_end > index + 1 + hashtag = characters[index + 1...hashtag_end].join + + html << %(#) + html << HTML.escape(hashtag) << "" + index = hashtag_end + next + end + end + + html << HTML.escape(character.to_s) + index += 1 + end + end + end + + private def hashtag_character?(character : Char) : Bool + character.alphanumeric? || character == '_' + end + + private def hashtag_prefix_character?(character : Char) : Bool + hashtag_character?(character) || character == '&' + end + # A handy structure to pass many elements at # once to the download widget function struct VideoAssets diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 3dc19c9e2..819847c31 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -93,7 +93,7 @@ we're going to need to do it here in order to allow for translations.

- <%= title %> + <%= Invidious::Frontend::WatchPage.linkify_title_hashtags(video.title) %> <% if params.listen %> " id="link-iv-listen" data-base-url="/watch?<%= env.params.query %>&listen=0" href="/watch?<%= env.params.query %>&listen=0">