feat(views): saved-views UI — sidebar widget, apply/rename/default/reorder/share

A new "Saved views" filter-sidebar widget (SavedViewsWidget): save the
current filters under a name, apply one with a click (active-highlighted
when it matches), rename, delete (confirm), drag-reorder, share by link
(reuses shareUrl), and star one as the default. The default view's filters
are mirrored to localStorage so loadInitialFilters applies it synchronously
on load/F5 (an api/url share link still wins; no default → last-session
filters as before). Hidden for the demo account. EN/HU/DE.
This commit is contained in:
npeter83 2026-07-01 03:17:36 +02:00
parent 3056734231
commit 0a358fa82e
12 changed files with 429 additions and 3 deletions

View file

@ -39,6 +39,7 @@ import { notify } from "../lib/notifications";
import { useDebounced } from "../lib/useDebounced";
import { Switch } from "./ui/form";
import TagManager from "./TagManager";
import SavedViewsWidget from "./SavedViewsWidget";
// Filter ids; display labels resolved at render time via t("sidebar.sort.<id>") etc.
const SORT_IDS = [
@ -118,12 +119,14 @@ export default function Sidebar({
layout,
setLayout,
onFocusChannel,
isDemo = false,
}: {
filters: FeedFilters;
setFilters: (f: FeedFilters) => void;
layout: SidebarLayout;
setLayout: (l: SidebarLayout) => void;
onFocusChannel: (name: string) => void;
isDemo?: boolean;
}) {
const { t } = useTranslation();
const tagsQuery = useQuery({ queryKey: ["tags"], queryFn: api.tags });
@ -228,6 +231,9 @@ export default function Sidebar({
}
const available: Record<WidgetId, boolean> = {
// Saved views are personal state; hide the widget for the shared demo account (the API
// also rejects the writes via require_human).
savedviews: !isDemo,
date: true,
language: languages.length > 0,
topic: topics.length > 0,
@ -251,6 +257,8 @@ export default function Sidebar({
function widgetBody(id: WidgetId): React.ReactNode {
switch (id) {
case "savedviews":
return <SavedViewsWidget filters={filters} onApply={setFilters} />;
case "date":
return (
<>