M12 — the Android client, end to end #2
@@ -32,6 +32,10 @@ const emit = defineEmits<{
|
||||
/** The card this one was dropped ON, by id — the dragged card hit-tests the DOM
|
||||
* for it, so an id is all it can know without looking the note up again. */
|
||||
(e: "drop", targetId: string): void;
|
||||
/** A reminder was completed or snoozed. The store reconciles itself, so the board
|
||||
* needs nothing — this is for hosts holding their OWN list (the reminders lens
|
||||
* fetches separately) that must re-query to drop the note they just cleared. */
|
||||
(e: "reminder-changed"): void;
|
||||
}>();
|
||||
const notes = useNotesStore();
|
||||
const config = useConfigStore();
|
||||
@@ -127,6 +131,16 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
async function completeReminder(): Promise<void> {
|
||||
await notes.completeReminder(props.note.id);
|
||||
emit("reminder-changed");
|
||||
}
|
||||
|
||||
async function snoozeReminder(minutes: number): Promise<void> {
|
||||
await notes.snoozeReminder(props.note.id, minutes);
|
||||
emit("reminder-changed");
|
||||
}
|
||||
|
||||
function cardClass(color: NoteColor): string {
|
||||
return NOTE_CARD_CLASSES[color] ?? NOTE_CARD_CLASSES.default;
|
||||
}
|
||||
@@ -261,7 +275,7 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-if="note.remind_at" class="mt-2">
|
||||
<div v-if="note.remind_at" class="mt-2 flex flex-wrap items-center gap-1.5">
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs"
|
||||
:class="
|
||||
@@ -283,7 +297,23 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
||||
<polyline points="12 6 12 12 16 14" />
|
||||
</svg>
|
||||
{{ formatReminder(note.remind_at) }}
|
||||
<!-- Recurrence was only ever shown in the reminders list; carried onto the
|
||||
card so unifying them didn't quietly drop it, and so a repeating note
|
||||
reads as repeating on the board too. -->
|
||||
<span v-if="note.recurrence" :title="`Repeats ${note.recurrence}`">· ↻ {{ note.recurrence }}</span>
|
||||
</span>
|
||||
|
||||
<!-- Clearing a reminder is UPKEEP, and upkeep that costs three clicks is
|
||||
upkeep people stop doing — so it happens right here rather than by
|
||||
opening the note. Shown wherever a note carries a reminder, the board
|
||||
included, not only in the reminders lens.
|
||||
Always visible rather than hover-revealed: a finger cannot hover, and
|
||||
these are the primary action for a due note (task 2697). -->
|
||||
<button type="button" class="chip-btn" title="Mark this reminder done" @click.stop="completeReminder">
|
||||
Done
|
||||
</button>
|
||||
<button type="button" class="chip-btn" title="Snooze 1 hour" @click.stop="snoozeReminder(60)">1h</button>
|
||||
<button type="button" class="chip-btn" title="Snooze 1 day" @click.stop="snoozeReminder(1440)">1d</button>
|
||||
</div>
|
||||
|
||||
<div v-if="trashCountdown" class="mt-2">
|
||||
|
||||
@@ -29,6 +29,7 @@ defineEmits<{
|
||||
(e: "dragstart", note: Note): void;
|
||||
(e: "dragend", note: Note): void;
|
||||
(e: "drop", targetId: string): void;
|
||||
(e: "reminder-changed"): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -51,6 +52,7 @@ defineEmits<{
|
||||
@dragstart="$emit('dragstart', $event)"
|
||||
@dragend="$emit('dragend', $event)"
|
||||
@drop="$emit('drop', $event)"
|
||||
@reminder-changed="$emit('reminder-changed')"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
@@ -156,6 +156,21 @@ body {
|
||||
@apply inline-flex min-h-[2.75rem] min-w-[2.75rem] items-center justify-center;
|
||||
}
|
||||
}
|
||||
/* A small action sitting beside a chip on a card — reminder Done/snooze today.
|
||||
* Quiet at rest so a board full of reminders doesn't read as a wall of buttons,
|
||||
* and it earns contrast on hover/focus. */
|
||||
.chip-btn {
|
||||
@apply rounded-full px-2 py-0.5 text-xs text-neutral-500 transition hover:bg-black/5
|
||||
hover:text-neutral-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand
|
||||
dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-neutral-100;
|
||||
}
|
||||
/* Same finger rule as .icon-btn: comfortable for a cursor is too small for a
|
||||
* thumb, and these are the primary action on a due note. */
|
||||
@media (pointer: coarse) {
|
||||
.chip-btn {
|
||||
@apply inline-flex min-h-[2.25rem] items-center justify-center px-3;
|
||||
}
|
||||
}
|
||||
.nav-link {
|
||||
@apply flex items-center gap-2 rounded-lg px-3 py-2 font-medium text-neutral-600 transition
|
||||
hover:bg-neutral-200/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { useNotesStore, type Note } from "../stores/notes";
|
||||
import { useReminderStore } from "../stores/reminders";
|
||||
import { formatReminder, isOverdue } from "../notes/datetime";
|
||||
import { useNoteList } from "../composables/useNoteList";
|
||||
import { useNoteEditor } from "../composables/useNoteEditor";
|
||||
import AsyncState from "../components/AsyncState.vue";
|
||||
import EmptyState from "../components/EmptyState.vue";
|
||||
import NoteEditor from "../components/NoteEditor.vue";
|
||||
import NoteGrid from "../components/NoteGrid.vue";
|
||||
|
||||
const notes = useNotesStore();
|
||||
const reminders = useReminderStore();
|
||||
|
||||
const { items, loading, error, load } = useNoteList(() => reminders.fetchReminders(), "Couldn't load reminders.");
|
||||
@@ -19,20 +17,14 @@ const { editing, open: openEditor, close: closeEditor, navigate: onNavigate } =
|
||||
onClose: load,
|
||||
});
|
||||
|
||||
async function done(n: Note) {
|
||||
await notes.completeReminder(n.id);
|
||||
await load();
|
||||
}
|
||||
async function snooze(n: Note, minutes: number) {
|
||||
await notes.snoozeReminder(n.id, minutes);
|
||||
await load();
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto w-full max-w-2xl px-4 py-6">
|
||||
<!-- max-w-6xl, matching the board and every other lens: this is the same masonry
|
||||
now, and a narrower column would reintroduce the "different page" feeling the
|
||||
card unification just removed. -->
|
||||
<div class="mx-auto w-full max-w-6xl px-4 py-6">
|
||||
<!-- Titled by the shell's persistent lens name (task 1913). -->
|
||||
<div class="mb-2 flex items-center justify-end gap-3">
|
||||
<button
|
||||
@@ -55,45 +47,12 @@ onMounted(load);
|
||||
title="No reminders"
|
||||
subtitle="Set a reminder on a note (in its editor) to see it here."
|
||||
/>
|
||||
<ul v-else class="divide-y divide-neutral-100 dark:divide-neutral-800">
|
||||
<li v-for="n in items" :key="n.id" class="flex items-center gap-3 py-3">
|
||||
<button
|
||||
type="button"
|
||||
class="min-w-0 flex-1 rounded text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-brand"
|
||||
@click="openEditor(n)"
|
||||
>
|
||||
<p class="truncate text-sm font-medium text-neutral-800 dark:text-neutral-100">
|
||||
{{ n.display_title || "Untitled" }}
|
||||
</p>
|
||||
<p class="text-xs" :class="isOverdue(n.remind_at) ? 'text-red-500 dark:text-red-400' : 'text-neutral-400'">
|
||||
{{ formatReminder(n.remind_at) }}<span v-if="n.recurrence"> · ↻ {{ n.recurrence }}</span>
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 rounded-md border border-neutral-300 px-2 py-1 text-xs text-neutral-600 hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-800"
|
||||
@click="done(n)"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 text-xs text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200"
|
||||
title="Snooze 1 hour"
|
||||
@click="snooze(n, 60)"
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 text-xs text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200"
|
||||
title="Snooze 1 day"
|
||||
@click="snooze(n, 1440)"
|
||||
>
|
||||
1d
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- The same cards, same grid, same motion as the board — reminders is a lens
|
||||
on those notes, not its own kind of thing (task 1913). Done/snooze moved
|
||||
onto the card itself, so one-tap triage survived the change; `load` on
|
||||
reminder-changed because this list is fetched separately from the store
|
||||
and a cleared note has to leave it. -->
|
||||
<NoteGrid v-else :notes="items" @open="openEditor" @reminder-changed="load" />
|
||||
</AsyncState>
|
||||
|
||||
<template v-if="editing">
|
||||
|
||||
Reference in New Issue
Block a user