/** * A tiny app-level cache of the metadata needed to open a previously-visited * page while offline (issue #38). It maps a page URL (pond slug + page slug) to * the ids the editor and collab layer need. This is persisted by the app in * localStorage — deliberately NOT by the service worker — so API responses are * never cached, yet a visited page can still be resolved with no network. * * The store is bounded to the most recently visited pages so it cannot grow * without limit; each entry is a few hundred bytes. */ const STORAGE_KEY = 'dorfteich:offline-pages'; const MAX_ENTRIES = 50; export interface CachedPage { pondSlug: string; pondId: string; pageSlug: string; pageId: string; title: string; /** Last write time, for bounded recency-based eviction. */ savedAt: number; } type Store = Record; function keyOf(pondSlug: string, pageSlug: string): string { return `${pondSlug}/${pageSlug}`; } function readStore(): Store { try { const raw = localStorage.getItem(STORAGE_KEY); return raw ? (JSON.parse(raw) as Store) : {}; } catch { return {}; } } function writeStore(store: Store): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(store)); } catch { // Best-effort: ignore quota errors or disabled storage. } } /** Record the metadata of a page loaded online, for later offline resolution. */ export function rememberPage(entry: Omit): void { const store = readStore(); store[keyOf(entry.pondSlug, entry.pageSlug)] = { ...entry, savedAt: Date.now() }; const keys = Object.keys(store); if (keys.length > MAX_ENTRIES) { keys .sort((a, b) => (store[a]?.savedAt ?? 0) - (store[b]?.savedAt ?? 0)) .slice(0, keys.length - MAX_ENTRIES) .forEach((key) => delete store[key]); writeStore(store); } else { writeStore(store); } } /** Look up a previously-visited page's metadata by its URL slugs. */ export function recallPage(pondSlug: string, pageSlug: string): CachedPage | null { return readStore()[keyOf(pondSlug, pageSlug)] ?? null; }