2026-06-11 19:21:48 +02:00
|
|
|
// Per-user customization of the filter sidebar: order of the widget cards, which are
|
|
|
|
|
// collapsed, and which are hidden. Persisted to localStorage and the server-side
|
|
|
|
|
// `preferences` blob so it follows the account.
|
|
|
|
|
|
feat(feed): Show chips in the toolbar + key+direction sort
1) Move the Show view filter (Unwatched/In progress/All/Watched/Hidden) up into the
toolbar chip row as its own single-select group, divided from the content-type chips;
removed the 'show' sidebar widget (sidebar now = Upload date / Language / Topic).
2) Rework ordering like the Playlists page: a sort-key dropdown (Date, Popular, Duration,
Name, Channel subscribers, Channel priority, Surprise me) + a single asc/desc arrow
toggle, instead of separate directional entries. 'Most viewed' is now 'Popular'.
Backend gains the missing directions (views_asc, title_desc, subscribers_asc,
priority_asc); the frontend maps (key, dir) -> the backend sort string, so FeedFilters
is unchanged. Trilingual (feed.sortKey.*, feed.dirAsc/dirDesc). Feed.tsx normalized to LF.
2026-06-16 02:46:26 +02:00
|
|
|
// `show`, `sort` and `content` moved to the feed toolbar (above the cards); they are no
|
|
|
|
|
// longer sidebar widgets. normalizeLayout drops them from any persisted layout automatically.
|
fix(state): scope all per-account localStorage by account id
Multi-account-in-one-browser (esp. with per-tab accounts) leaked one account's client state
into another via shared localStorage keys. Scope every account-specific key by the tab's active
account (accountKey/readAccount/writeAccount/useAccountPersistedState helpers), so nothing bleeds
across accounts or tabs:
- Real leaks: selected playlist, client notification history + settings, onboarding-dismissed.
- UI position: feed page, channel-manager filter/view + tables, playlist sort, Settings/Stats/
Users/Config tabs.
- Previously DB-adopted caches (theme, hints, performance mode, sidebar layout, nav/filter
collapse) — now the cache is per-account too, so there's no flash of the other account's value
on login.
Kept intentionally global: siftlode.lang (needed pre-login on the Welcome page; the DB pref still
scopes it per-account after sign-in) and siftlode.seenVersion (a per-browser 'new version' banner).
E2EE private keys (IndexedDB) and the chat-dock key were already per-user.
2026-07-02 01:45:16 +02:00
|
|
|
import { LS, readAccount, writeAccount } from "./storage";
|
2026-06-26 03:30:19 +02:00
|
|
|
|
2026-07-01 03:17:36 +02:00
|
|
|
export type WidgetId = "savedviews" | "date" | "language" | "topic" | "tags";
|
2026-06-11 19:21:48 +02:00
|
|
|
|
2026-07-01 04:29:38 +02:00
|
|
|
export const ALL_WIDGETS: WidgetId[] = ["savedviews", "date", "tags", "language", "topic"];
|
2026-06-11 19:21:48 +02:00
|
|
|
|
|
|
|
|
export const WIDGET_TITLES: Record<WidgetId, string> = {
|
2026-07-01 03:17:36 +02:00
|
|
|
savedviews: "Saved views",
|
2026-06-11 19:21:48 +02:00
|
|
|
date: "Upload date",
|
|
|
|
|
language: "Language",
|
|
|
|
|
topic: "Topic",
|
2026-06-18 01:17:31 +02:00
|
|
|
tags: "Tags",
|
2026-06-11 19:21:48 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export interface SidebarLayout {
|
|
|
|
|
order: WidgetId[];
|
|
|
|
|
collapsed: Partial<Record<WidgetId, boolean>>;
|
|
|
|
|
hidden: Partial<Record<WidgetId, boolean>>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const DEFAULT_LAYOUT: SidebarLayout = {
|
|
|
|
|
order: [...ALL_WIDGETS],
|
|
|
|
|
collapsed: {},
|
|
|
|
|
hidden: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Tolerate stale/partial data: keep only known widgets and append any that are missing
|
|
|
|
|
// (e.g. a widget added in a later version) so nothing silently disappears.
|
|
|
|
|
export function normalizeLayout(raw: unknown): SidebarLayout {
|
|
|
|
|
const r = (raw ?? {}) as Partial<SidebarLayout>;
|
|
|
|
|
const order: WidgetId[] = Array.isArray(r.order)
|
|
|
|
|
? (r.order.filter((x) => ALL_WIDGETS.includes(x as WidgetId)) as WidgetId[])
|
|
|
|
|
: [];
|
|
|
|
|
for (const w of ALL_WIDGETS) if (!order.includes(w)) order.push(w);
|
|
|
|
|
return {
|
|
|
|
|
order,
|
|
|
|
|
collapsed: { ...(r.collapsed ?? {}) },
|
|
|
|
|
hidden: { ...(r.hidden ?? {}) },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function loadLayout(): SidebarLayout {
|
fix(state): scope all per-account localStorage by account id
Multi-account-in-one-browser (esp. with per-tab accounts) leaked one account's client state
into another via shared localStorage keys. Scope every account-specific key by the tab's active
account (accountKey/readAccount/writeAccount/useAccountPersistedState helpers), so nothing bleeds
across accounts or tabs:
- Real leaks: selected playlist, client notification history + settings, onboarding-dismissed.
- UI position: feed page, channel-manager filter/view + tables, playlist sort, Settings/Stats/
Users/Config tabs.
- Previously DB-adopted caches (theme, hints, performance mode, sidebar layout, nav/filter
collapse) — now the cache is per-account too, so there's no flash of the other account's value
on login.
Kept intentionally global: siftlode.lang (needed pre-login on the Welcome page; the DB pref still
scopes it per-account after sign-in) and siftlode.seenVersion (a per-browser 'new version' banner).
E2EE private keys (IndexedDB) and the chat-dock key were already per-user.
2026-07-02 01:45:16 +02:00
|
|
|
return normalizeLayout(readAccount<unknown>(LS.sidebarLayout, {}));
|
2026-06-11 19:21:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function saveLayoutLocal(l: SidebarLayout): void {
|
fix(state): scope all per-account localStorage by account id
Multi-account-in-one-browser (esp. with per-tab accounts) leaked one account's client state
into another via shared localStorage keys. Scope every account-specific key by the tab's active
account (accountKey/readAccount/writeAccount/useAccountPersistedState helpers), so nothing bleeds
across accounts or tabs:
- Real leaks: selected playlist, client notification history + settings, onboarding-dismissed.
- UI position: feed page, channel-manager filter/view + tables, playlist sort, Settings/Stats/
Users/Config tabs.
- Previously DB-adopted caches (theme, hints, performance mode, sidebar layout, nav/filter
collapse) — now the cache is per-account too, so there's no flash of the other account's value
on login.
Kept intentionally global: siftlode.lang (needed pre-login on the Welcome page; the DB pref still
scopes it per-account after sign-in) and siftlode.seenVersion (a per-browser 'new version' banner).
E2EE private keys (IndexedDB) and the chat-dock key were already per-user.
2026-07-02 01:45:16 +02:00
|
|
|
writeAccount(LS.sidebarLayout, l);
|
2026-06-11 19:21:48 +02:00
|
|
|
}
|