You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

99 lines
2.7 KiB

export type PostPreviewDraftPayload = {
title: string
excerpt: string
bodyMarkdown: string
visibility: 'private' | 'unlisted' | 'public'
}
const PREVIEW_DRAFT_KEY_PREFIX = 'post-preview-draft:'
const PREVIEW_DRAFT_TTL_MS = 10 * 60 * 1000
type StoredPostPreviewDraftPayload = PostPreviewDraftPayload & {
createdAt: number
}
function isPostPreviewDraftKey(key: string): boolean {
return key.startsWith(PREVIEW_DRAFT_KEY_PREFIX)
}
export function createPostPreviewDraft(payload: PostPreviewDraftPayload): string {
const uniqueId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
? crypto.randomUUID()
: `${Date.now()}-${Math.random().toString(16).slice(2)}`
const key = `${PREVIEW_DRAFT_KEY_PREFIX}${uniqueId}`
const storedPayload: StoredPostPreviewDraftPayload = {
...payload,
createdAt: Date.now(),
}
localStorage.setItem(key, JSON.stringify(storedPayload))
return uniqueId
}
export function readPostPreviewDraft(uniqueId: string): PostPreviewDraftPayload | null {
const key = `${PREVIEW_DRAFT_KEY_PREFIX}${uniqueId}`
const raw = localStorage.getItem(key)
if (!raw) {
return null
}
try {
const parsed = JSON.parse(raw) as Partial<StoredPostPreviewDraftPayload>
if (
typeof parsed.title !== 'string' ||
typeof parsed.excerpt !== 'string' ||
typeof parsed.bodyMarkdown !== 'string' ||
typeof parsed.createdAt !== 'number' ||
(parsed.visibility !== 'private' && parsed.visibility !== 'unlisted' && parsed.visibility !== 'public')
) {
localStorage.removeItem(key)
return null
}
if (Date.now() - parsed.createdAt > PREVIEW_DRAFT_TTL_MS) {
localStorage.removeItem(key)
return null
}
return {
title: parsed.title,
excerpt: parsed.excerpt,
bodyMarkdown: parsed.bodyMarkdown,
visibility: parsed.visibility,
}
} catch {
localStorage.removeItem(key)
return null
}
}
export function clearPostPreviewDrafts(options?: { onlyExpired?: boolean }) {
const onlyExpired = options?.onlyExpired === true
const now = Date.now()
for (let index = localStorage.length - 1; index >= 0; index -= 1) {
const key = localStorage.key(index)
if (!key || !isPostPreviewDraftKey(key)) {
continue
}
if (!onlyExpired) {
localStorage.removeItem(key)
continue
}
const raw = localStorage.getItem(key)
if (!raw) {
localStorage.removeItem(key)
continue
}
try {
const parsed = JSON.parse(raw) as Partial<StoredPostPreviewDraftPayload>
if (typeof parsed.createdAt !== 'number' || now - parsed.createdAt > PREVIEW_DRAFT_TTL_MS) {
localStorage.removeItem(key)
}
} catch {
localStorage.removeItem(key)
}
}
}