feat: add notes app

This commit is contained in:
2026-10-01 19:39:20 +02:00
parent ccb8dd3d15
commit ddbb6f37b5
35 changed files with 706 additions and 52 deletions
@@ -0,0 +1,51 @@
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 }
}