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
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)
|
|
}
|
|
}
|
|
}
|
|
|