From 14dab91334ea676197544861fe6c52384e8d5e43 Mon Sep 17 00:00:00 2001 From: vincent Date: Thu, 3 Sep 2026 23:47:27 +0200 Subject: [PATCH] feat: the player's title opens the description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The title is the link now, and looks like nothing: it keeps its colour and carries no underline, since dressing it up would compete with the video for attention. The pointer and the tooltip say the rest. Views and age move onto that line, in the same small quiet grey, and the "› Description" disclosure underneath is gone — one line instead of two, and the description gets a proper modal with room to read it. Addresses in a description are plain text as YouTube stores them; they are found and made clickable, and open in the real browser. Trailing punctuation is trimmed from the target: a full stop ends the sentence, not the address. Verified in the running app on a description with three consecutive affiliate links, all three of which came out as links — the test for one uses startsWith rather than a global regex, whose lastIndex carries between calls and would have left every other address as plain text. --- src/components/Player.tsx | 84 +++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/src/components/Player.tsx b/src/components/Player.tsx index 9ee9e91..8c9ae13 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -6,7 +6,7 @@ import { import { DEFAULT_SUB_LANG, SUB_FONTS, SUB_PLACES, type FeedItem, type SubStyle } from "../types"; import { compactViews, relativeTime, subtitleLabel } from "./format"; import PlayerControls from "./PlayerControls"; -import { Badge, BTN, Spinner } from "./ui"; +import { Badge, BTN, Dialog, Spinner } from "./ui"; interface Props { item: FeedItem; @@ -108,6 +108,40 @@ const RESUME_EDGE_S = 5; * so watching still happens here rather than in a browser. The iframe embed * cannot be used: it rejects a `tauri://` origin with "Error 153". */ +/** Web addresses in a YouTube description, which are plain text as it stores them. */ +const URL_IN_TEXT = /(https?:\/\/[^\s<>"']+)/g; + +/** + * A description with its addresses made clickable. + * + * They open in the real browser: a YouTube link is the one thing in here this + * app has no way to show, and the rest belong to whoever wrote them. + */ +function Linked({ text }: { text: string }) { + return ( + <> + {text.split(URL_IN_TEXT).map((part, i) => + // Not URL_IN_TEXT.test: a global regex carries lastIndex between calls, + // so every other address would come out as plain text. + part.startsWith("http") ? ( + // Trailing punctuation is sentence, not address. + + ) : ( + {part} + ), + )} + + ); +} + /** * Rewrites every cue's settings to one placement. * @@ -154,6 +188,7 @@ export default function Player({ const [rawTracks, setRawTracks] = useState>([]); const [tracks, setTracks] = useState>([]); const [fetchingSubs, setFetchingSubs] = useState(false); + const [showDescription, setShowDescription] = useState(false); // The language to fetch, which is NOT the preference: turning subtitles on // from the player's menu moves the preference from "off" to that language, // and refetching then would tear down the tracks — and the stream with them — @@ -521,13 +556,29 @@ export default function Player({ dark:border-slate-800 dark:bg-slate-900" >
-
-

{item.title}

-
+ {/* Title and figures on one line. The title opens the description + rather than announcing itself as a link: it keeps its colour and + stays unadorned, and the pointer says the rest. */} +
+ {item.description ? ( + + ) : ( +

+ {item.title} +

+ )} + {[compactViews(item.views), relativeTime(item.published)] .filter(Boolean) .join(" · ")} -
+
{onDownload && ( @@ -575,22 +626,15 @@ export default function Player({
- {/* Collapsed by default — the description is rarely what you came for. */} - {item.description && ( -
- - {" "} - Description - -

- {item.description} -

-
- )} + + {showDescription && ( + setShowDescription(false)} wide> +

+ +

+
+ )}
); }