M6 1908b: foreground reminder delivery + recurrence/snooze UI (frontend)
Completes 1908 without Web Push. While the app is open, due reminders now actually surface; recurring reminders + snooze/done are manageable. - reminders store (singleton): polls /api/notes/reminders every 45s while the app is open; each due reminder fires ONCE as a toast (with an "Open" action) and, if the user opts in, a page-context OS Notification — no service worker, no PWA. Silently primes a stale backlog on first load; only announces recently-due ones. AppShell starts/stops it. - Editor reminder section: a Repeat picker (Does not repeat / Daily / Weekly / Monthly / Yearly) + Done (advances a recurring reminder / clears a one-off) + Snooze 1h/1d, shown when a reminder is set. - RemindersView rebuilt as a chronological list: per row a due time + recurrence badge + Done / 1h / 1d, click to open; plus an "Enable notifications" opt-in and a note that background alerts come with native. - Note type gains `recurrence`; notes store setRecurrence / completeReminder / snoozeReminder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -68,6 +68,7 @@ const draftNote = computed<Note>(() => ({
|
||||
archived: false,
|
||||
trashed: false,
|
||||
remind_at: null,
|
||||
recurrence: null,
|
||||
labels: labelList.value,
|
||||
items: [],
|
||||
attachments: [],
|
||||
@@ -377,6 +378,17 @@ async function onReminderChange(e: Event) {
|
||||
if (!id) return;
|
||||
await notes.setReminder(id, fromLocalInput((e.target as HTMLInputElement).value));
|
||||
}
|
||||
async function onRecurrenceChange(e: Event) {
|
||||
const id = await ensureDraft();
|
||||
if (!id) return;
|
||||
await notes.setRecurrence(id, (e.target as HTMLSelectElement).value || null);
|
||||
}
|
||||
async function completeReminder() {
|
||||
if (noteId.value) await notes.completeReminder(noteId.value);
|
||||
}
|
||||
async function snoozeReminder(minutes: number) {
|
||||
if (noteId.value) await notes.snoozeReminder(noteId.value, minutes);
|
||||
}
|
||||
|
||||
// ---- labels ----
|
||||
async function onLabelsChange(next: NoteLabel[]) {
|
||||
@@ -755,6 +767,34 @@ defineExpose({ open });
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="richEnabled && liveNote.remind_at" class="flex flex-wrap items-center gap-2 pl-6 text-xs">
|
||||
<label class="text-neutral-400">Repeat</label>
|
||||
<select
|
||||
:value="liveNote.recurrence ?? ''"
|
||||
class="rounded-md border border-neutral-300 bg-white px-2 py-1 text-xs text-neutral-700 outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200"
|
||||
@change="onRecurrenceChange"
|
||||
>
|
||||
<option value="">Does not repeat</option>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
<option value="yearly">Yearly</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-2 py-1 text-neutral-600 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-800"
|
||||
@click="completeReminder"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
<button type="button" class="text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200" @click="snoozeReminder(60)">
|
||||
Snooze 1h
|
||||
</button>
|
||||
<button type="button" class="text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200" @click="snoozeReminder(1440)">
|
||||
1d
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!inline && !isCreate && (outgoingLinks.length || backlinks.length)"
|
||||
class="flex flex-col gap-2 border-t border-neutral-100 pt-2 dark:border-neutral-800"
|
||||
|
||||
Reference in New Issue
Block a user