feat(player): the YouTube player and the file viewer are named in the address

The player names what it plays in its own history entry (watch, wrel, wtab): a reload or a
bookmark reopens it on the page that hosts it, with that page's list as the queue, or alone
from the catalog (GET /api/videos/{id}/row); a page that hosts none gets the shell's. Back
lands on the page's address without it (useBackToClose's url; ui/Modal passes it on). The
Files viewer and player do the same (fview, fplay). The Plex and channel-manager views are
always written in full, as their fallback is remembered state.
This commit is contained in:
2026-10-27 05:29:49 +01:00
parent d4f24de4ba
commit fa893c73a3
45 changed files with 1005 additions and 89 deletions
+16
View File
@@ -53,6 +53,22 @@ export function decodeSortParam(raw: string): SortState {
return parseSortState({ key, dir });
}
// The list's own order (no column sort) as a word of its own, for a page whose sort is remembered (an
// account pref, this browser's view): there the sort is always written, because a page opened without
// one takes the remembered sort, which may not be the one bookmarked.
const UNSORTED = "none";
export function encodeSortParamOrNone(s: SortState): string {
return encodeSortParam(s) ?? UNSORTED;
}
/** The sort an address names; `undefined` when it names none or a malformed one (the remembered one
* applies). */
export function decodeSortParamOrNone(raw: string | null): SortState | undefined {
if (raw == null) return undefined;
return raw === UNSORTED ? null : (decodeSortParam(raw) ?? undefined);
}
/** The header-click cycle: unsorted → ascending → descending → unsorted. */
export function cycleSort(current: SortState, key: string): SortState {
if (!current || current.key !== key) return { key, dir: "asc" };