Files
website/layers/notes/app/composables/useNotesAutosave.ts
T
2026-10-01 19:39:20 +02:00

52 lines
1.6 KiB
TypeScript

const SAVE_DELAY_MS = 800
export type NotesSaveState = 'saved' | 'unsaved' | 'saving' | 'error'
/** Collects edits to a note and saves them shortly after typing stops, and when leaving the note or the page */
export function useNotesAutosave(id: number, onSaved: (summary: NotesDocSummary) => void) {
const url = `/api/notes/docs/${id}`
const state = ref<NotesSaveState>('saved')
let pending: NotesDocUpdate = {}
let timer: ReturnType<typeof setTimeout> | undefined
const hasPending = () => Object.keys(pending).length > 0
async function flush() {
clearTimeout(timer)
if (!hasPending()) return
const body = pending
pending = {}
state.value = 'saving'
try {
onSaved(await $fetch<NotesDocSummary>(url, { method: 'PATCH', body }))
state.value = hasPending() ? 'unsaved' : 'saved'
} catch {
pending = { ...body, ...pending }
state.value = 'error'
}
}
function queue(changes: NotesDocUpdate) {
Object.assign(pending, changes)
state.value = 'unsaved'
clearTimeout(timer)
timer = setTimeout(flush, SAVE_DELAY_MS)
}
// A normal request may not finish when the tab closes; keepalive lets it
function saveOnUnload() {
if (!hasPending()) return
fetch(url, { method: 'PATCH', keepalive: true, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(pending) })
pending = {}
}
onMounted(() => window.addEventListener('pagehide', saveOnUnload))
onBeforeUnmount(() => {
window.removeEventListener('pagehide', saveOnUnload)
flush()
})
onBeforeRouteLeave(() => flush())
return { state, queue, flush }
}