import { useCallback, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { useClient } from 'hooks/useClient'; import { Folder, Settings2, Layers, ChevronUp, ChevronDown } from 'lucide-react'; import { SettingsForm } from './SettingsForm'; /** * The lists, and a way back to the settings. * * Selection is `?list=` in the URL rather than a channel, so which list you are looking * at survives a reload and can be linked to — `docs/navigation-audit.md`. The links panel reads the same * parameter rather than being handed the id. * * The settings live behind the gear rather than on a screen of their own. They were a screen of their * own for an afternoon and it was the wrong shape: nothing else was built, so opening the plugin put you * on a configuration form with no way forward, which reads as broken rather than unfinished. */ /** Karakeep list ids are strings, and lists nest through `parentId`. */ type List = { id: string; name: string; icon?: string | null; parentId?: string | null }; /** `?list=` for "everything", which is a real choice and not the absence of one. */ export const ALL = 'all'; export const ListsPanel = () => { const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const [rows, setRows] = useState(null); const [error, setError] = useState(null); const [showSettings, setShowSettings] = useState(false); const [params, setParams] = useSearchParams(); const selected = params.get('list') ?? ALL; const load = useCallback(async () => { try { const body = await clientRef.current.get<{ lists: List[] }>('/karakeep/api/v1/lists'); setRows(body.lists ?? []); setError(null); } catch (failure) { // The message matters here. "No account is mapped to you" is something the owner can fix and is // the state every user starts in, so it must not read as an outage. setError(failure instanceof Error ? failure.message : 'Could not read your lists.'); setRows(null); setShowSettings(true); } }, []); useEffect(() => { void load(); }, [load]); const select = (id: string) => setParams( (p) => { p.set('list', id); return p; }, { replace: true }, ); return (
Lists
{error &&

{error}

}
  • {rows?.map((row) => (
  • ))}
{/* Settings, pinned to the bottom. It was a gear in the header, which is where a settings control is easy to WRITE and not where anybody looks for one: the owner asked for this after failing to find it. The bottom of a sidebar is the conventional home for the thing you configure once, and being a labelled row rather than an icon means it says what it is. The form opens ABOVE the button rather than below it, so the control does not move when you use it. A disclosure that walks away from the pointer as it expands is the small kind of wrong that makes something feel unfinished. */} {showSettings && }
); };