/** * Persistence for the half-written dump in the create modal. * * The modal is dismissable by Escape, a backdrop click and the ✕, and it holds * a URL, a title, a rich-text "why", categories and a visibility choice — so a * stray keystroke used to destroy a few minutes of writing. Everything except * the attached file (a `File` cannot survive a reload) is mirrored here on each * change and restored the next time the modal opens; a successful post clears * it. * * Deliberately a single draft rather than one per tab: someone who closed the * modal by accident wants the thing they were just writing, wherever they * reopen it. */ const STORAGE_KEY = "gerbeur.dumpDraft"; export interface DumpDraft { url: string; title: string; /** Whether `title` was typed by the poster, rather than taken from the preview. */ titleFromUser: boolean; comment: string; isPublic: boolean; categoryIds: string[]; } export const EMPTY_DUMP_DRAFT: DumpDraft = { url: "", title: "", titleFromUser: false, comment: "", isPublic: true, categoryIds: [], }; /** Whether a draft holds anything worth restoring or warning about. */ export function isDumpDraftEmpty(draft: DumpDraft): boolean { return !draft.url.trim() && !draft.comment.trim() && !(draft.titleFromUser && draft.title.trim()) && draft.categoryIds.length === 0 && draft.isPublic; } export function loadDumpDraft(): DumpDraft | null { let raw: string | null; try { raw = localStorage.getItem(STORAGE_KEY); } catch { return null; // Storage disabled (private mode, blocked cookies) — no drafts. } if (!raw) return null; try { const parsed = JSON.parse(raw) as Partial; const draft: DumpDraft = { url: typeof parsed.url === "string" ? parsed.url : "", title: typeof parsed.title === "string" ? parsed.title : "", titleFromUser: parsed.titleFromUser === true, comment: typeof parsed.comment === "string" ? parsed.comment : "", isPublic: parsed.isPublic !== false, categoryIds: Array.isArray(parsed.categoryIds) ? parsed.categoryIds.filter((id): id is string => typeof id === "string") : [], }; return isDumpDraftEmpty(draft) ? null : draft; } catch { return null; } } export function saveDumpDraft(draft: DumpDraft): void { try { if (isDumpDraftEmpty(draft)) localStorage.removeItem(STORAGE_KEY); else localStorage.setItem(STORAGE_KEY, JSON.stringify(draft)); } catch { // Storage full or unavailable — the draft is a convenience, never a // precondition for posting, so a failure here is silent by design. } } export function clearDumpDraft(): void { try { localStorage.removeItem(STORAGE_KEY); } catch { /* see saveDumpDraft */ } }