import { SvelteMap } from 'svelte/reactivity'; import { withoutMarkers, type Attached } from './attachments'; // What was typed in a chat's composer and not yet sent. The composer is // destroyed with its window when another board is shown or the page goes, so // a draft lives here, by session, and in localStorage. Only the text is kept // there: images are too big for it, so a reload drops them and their markers. const KEY = 'albedo.draft.v1:'; /** A draft untouched this long is not worth keeping; the session may be gone. */ const STALE_MS = 30 * 24 * 60 * 60 * 1000; const SAVE_AFTER_MS = 300; function read(session: string): string { try { const saved = JSON.parse(localStorage.getItem(KEY + session) ?? ''); return typeof saved?.text === 'string' ? withoutMarkers(saved.text) : ''; } catch { return ''; } } /** Drops the saved drafts nobody came back to. */ function prune() { try { for (const key of Object.keys(localStorage).filter((name) => name.startsWith(KEY))) { const saved = JSON.parse(localStorage.getItem(key) ?? 'null'); if (!(Date.now() - saved?.at < STALE_MS)) localStorage.removeItem(key); } } catch {} } const unsaved = new Set(); let timer: ReturnType | undefined; function flush() { clearTimeout(timer); timer = undefined; for (const draft of unsaved) draft.save(); unsaved.clear(); } export class Draft { readonly images = new SvelteMap(); /** The last image marker handed out. */ next = 0; #text: string; #session: string; constructor(session: string) { this.#session = session; this.#text = read(session); } get text(): string { return this.#text; } /** Setting the text saves it a moment later, once typing pauses. */ set text(value: string) { if (value === this.#text) return; this.#text = value; unsaved.add(this); timer ??= setTimeout(flush, SAVE_AFTER_MS); } save() { try { const text = withoutMarkers(this.#text); if (text.trim()) localStorage.setItem(KEY + this.#session, JSON.stringify({ text, at: Date.now() })); else localStorage.removeItem(KEY + this.#session); } catch {} } } const drafts = new Map(); export function draftOf(session: string): Draft { let draft = drafts.get(session); if (!draft) drafts.set(session, (draft = new Draft(session))); return draft; } if (typeof window !== 'undefined') { prune(); // The last words typed before a reload or a closed tab are still waiting on the timer. window.addEventListener('pagehide', flush); }