Resolves the 5 /code-review high findings on this branch: - a11y sweep: extend aria-label to the remaining icon-only Plex card buttons the first pass missed — PlexPosterCard Info, SeasonCard watched-toggle + add-season (EpisodeCard already had them). "Fix a principle everywhere, in one sweep." - new shared lib/parseId.ts `parsePositiveIntId`, used by BOTH readSelectedId (now guarding the stored ?pl fallback the same as the URL — was unguarded) and plexSubUrl's ?v=playlist decode; removes the hand-duplicated guard. - PAGE_KEY exported from navigationInit and imported by NavigationProvider (was defined in both). - hoist a `watchedLabel` const per card so title + aria-label can't drift. +8 tests (parseId unit + guarded-stored ?pl); full FE gate green (tsc/eslint/ prettier/knip, 317 vitest).
40 lines
1.9 KiB
TypeScript
40 lines
1.9 KiB
TypeScript
// URL-derived navigation seeds, split out of NavigationProvider so they stay unit-testable AND the
|
|
// provider file keeps react-refresh happy (component-only exports). `loadInitialPage` picks the landing
|
|
// module from a bookmark's params; `channelViewFrom` is the shared _chan/?chan resolver behind BOTH the
|
|
// channelView seed and the onPop handler.
|
|
import { hasFilterParams, isPage, readPage, type Page } from "../lib/urlState";
|
|
import { getAccountRaw, LS } from "../lib/storage";
|
|
|
|
export const PAGE_KEY = LS.page;
|
|
|
|
export interface ChannelView {
|
|
id: string;
|
|
name?: string;
|
|
}
|
|
|
|
export function loadInitialPage(): Page {
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.has("page")) return readPage();
|
|
// A view-specific param implies its page, so a bookmarked deep link lands on the right module even
|
|
// without an explicit ?page. Feed filters (or a channel overlay) imply the feed.
|
|
if (params.has("v")) return "plex";
|
|
if (params.has("pl")) return "playlists";
|
|
if (hasFilterParams(params) || params.has("chan")) return "feed";
|
|
// A bare URL → the account's stored landing page.
|
|
const stored = getAccountRaw(PAGE_KEY);
|
|
return isPage(stored) ? stored : "feed";
|
|
}
|
|
|
|
/** Resolve the open channel page from a landed history entry: the in-app `_chan` id (with its optional
|
|
* `_chanName`), else the ?chan URL param — a fresh ?chan bookmark's entry is URL-only. The seed AND
|
|
* the popstate handler share this ONE resolver so a popstate that lands on a bookmark entry (no
|
|
* `_chan`) restores the channel from the URL instead of wrongly closing it. */
|
|
export function channelViewFrom(
|
|
state: { _chan?: unknown; _chanName?: unknown } | null | undefined,
|
|
search: string,
|
|
): ChannelView | null {
|
|
const id =
|
|
(state?._chan as string | undefined) ?? new URLSearchParams(search).get("chan") ?? undefined;
|
|
return id ? { id, name: (state?._chanName as string) ?? undefined } : null;
|
|
}
|