import type { Byline } from '@/lib/articleTypes' import { SEO_KEYS, type ArticleSeo, type SeoKey } from '@/lib/articleSeo' export interface StoredDraft { title?: string body?: string meta?: { site_id: number | null; authors: Byline[] } seo?: ArticleSeo mirrored_at: string } const DRAFT_PREFIX = 'renewcms:article-draft:' const NULLABLE_NUMBER_KEYS: SeoKey[] = ['cover_image_id', 'social_image_id'] const BOOLEAN_KEYS: SeoKey[] = ['noindex'] type UnknownRecord = Record function isRecord(value: unknown): value is UnknownRecord { return typeof value === 'object' && value !== null && !Array.isArray(value) } function isOptionalString(value: unknown) { return value === undefined || typeof value === 'string' } function isNullableNumber(value: unknown) { return value === null || typeof value === 'number' } function isByline(value: unknown) { return ( isRecord(value) && typeof value.id === 'number' && typeof value.name === 'string' && typeof value.slug === 'string' ) } function isMeta(value: unknown) { return ( value === undefined || (isRecord(value) && isNullableNumber(value.site_id) && Array.isArray(value.authors) && value.authors.every(isByline)) ) } function isSeoValue(key: SeoKey, value: unknown) { if (NULLABLE_NUMBER_KEYS.includes(key)) { return isNullableNumber(value) } return BOOLEAN_KEYS.includes(key) ? typeof value === 'boolean' : typeof value === 'string' } function isSeo(value: unknown) { return ( value === undefined || (isRecord(value) && SEO_KEYS.every((key) => value[key] === undefined || isSeoValue(key, value[key]))) ) } function isMirrorTime(value: unknown) { return typeof value === 'string' && !Number.isNaN(new Date(value).getTime()) } function isStoredDraft(value: unknown): value is StoredDraft { return ( isRecord(value) && isOptionalString(value.title) && isOptionalString(value.body) && isMeta(value.meta) && isSeo(value.seo) && isMirrorTime(value.mirrored_at) ) } function keyOf(userId: number, articleId: number) { return `${DRAFT_PREFIX}${userId}:${articleId}` } export function readStoredDraft(userId: number | null, articleId: number): StoredDraft | null { if (userId === null) { return null } try { const raw = sessionStorage.getItem(keyOf(userId, articleId)) const parsed: unknown = raw === null ? null : JSON.parse(raw) return isStoredDraft(parsed) ? parsed : null } catch { return null } } export function writeStoredDraft(userId: number | null, articleId: number, draft: StoredDraft) { if (userId === null) { return } try { sessionStorage.setItem(keyOf(userId, articleId), JSON.stringify(draft)) } catch { return } } export function removeStoredDraft(userId: number | null, articleId: number) { if (userId === null) { return } try { sessionStorage.removeItem(keyOf(userId, articleId)) } catch { return } } export function removeStoredDraftsOf(userId: number | undefined) { if (userId === undefined) { return } try { const prefix = `${DRAFT_PREFIX}${userId}:` const owned = Object.keys(sessionStorage).filter((key) => key.startsWith(prefix)) owned.forEach((key) => sessionStorage.removeItem(key)) } catch { return } }