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
+21 -5
View File
@@ -36,15 +36,31 @@ describe("channelsParamUpdates", () => {
expect(channelsParamUpdates({ ...BASE, search: " " }).cq).toBeNull();
});
it("emits nothing for the all-default state", () => {
expect(channelsParamUpdates(BASE)).toEqual({ ctab: null, csort: null, cf: null, cq: null });
it("writes the default view in full: this browser remembers one a link must not inherit", () => {
expect(channelsParamUpdates(BASE)).toEqual({
ctab: "subscribed",
csort: "none",
cf: "",
cq: null,
});
});
it("a link to the default view opens it over another remembered one", () => {
const params = applyUpdates(channelsParamUpdates(BASE));
const back = channelsParamsToState(params, {
view: "hidden",
sort: { key: "subscribers", dir: "desc" },
filters: { sync: "fully" },
search: "",
});
expect(back).toEqual(BASE);
});
it("clears every key when passed null (off-manager page)", () => {
expect(channelsParamUpdates(null)).toEqual({ ctab: null, csort: null, cf: null, cq: null });
});
it("emits only non-default keys", () => {
it("writes the view picked", () => {
const u = channelsParamUpdates({
view: "hidden",
sort: { key: "subscribers", dir: "desc" },
@@ -57,8 +73,8 @@ describe("channelsParamUpdates", () => {
expect(u.cq).toBe("tom");
});
it("drops empty filter entries so a cleared funnel emits no cf", () => {
expect(channelsParamUpdates({ ...BASE, filters: { tags: [], sync: "" } }).cf).toBeNull();
it("drops empty filter entries so a cleared funnel writes an empty cf", () => {
expect(channelsParamUpdates({ ...BASE, filters: { tags: [], sync: "" } }).cf).toBe("");
});
});