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> +

+ +

+
+ )}
); }