M2 drag-reorder: notes.position + reorder API + native DnD
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 30s

- Migration 0008: notes.position (int). Board orders pinned -> position ->
  updated_at; new notes created at top (max position + 1). POST /api/notes/reorder
  assigns positions from the given order (owner-scoped).
- notes store: position on Note, position-aware sort, optimistic reorder().
- NoteCard reorderable (native HTML5 draggable + dragstart/drop); BoardView moves
  the dragged note before the drop target and persists.

Note: drag on a CSS-columns masonry has imperfect during-drag visuals (columns
reflow); order persists correctly. Candidate for a polish pass / layout tweak.

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:
2026-07-19 22:28:43 -04:00
co-authored by Claude Opus 4.8
parent e4c898cd1b
commit 339cc5c2d2
7 changed files with 135 additions and 7 deletions
+44 -3
View File
@@ -46,6 +46,23 @@ function openEditor(note: Note) {
function closeEditor() {
editing.value = null;
}
const draggingId = ref<string | null>(null);
function onDragStart(note: Note) {
draggingId.value = note.id;
}
async function onDrop(target: Note) {
const from = draggingId.value;
draggingId.value = null;
if (!from || from === target.id) return;
const order = notes.items.map((n) => n.id);
const fromIdx = order.indexOf(from);
const toIdx = order.indexOf(target.id);
if (fromIdx < 0 || toIdx < 0) return;
order.splice(fromIdx, 1);
order.splice(toIdx, 0, from);
await notes.reorder(order);
}
</script>
<template>
@@ -64,7 +81,15 @@ function closeEditor() {
<section v-if="pinnedNotes.length">
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">Pinned</h2>
<div class="columns-1 gap-4 sm:columns-2 lg:columns-3 xl:columns-4">
<NoteCard v-for="n in pinnedNotes" :key="n.id" :note="n" @open="openEditor" />
<NoteCard
v-for="n in pinnedNotes"
:key="n.id"
:note="n"
reorderable
@open="openEditor"
@dragstart="onDragStart"
@drop="onDrop"
/>
</div>
</section>
<section v-if="otherNotes.length" :class="pinnedNotes.length ? 'mt-8' : ''">
@@ -72,13 +97,29 @@ function closeEditor() {
Others
</h2>
<div class="columns-1 gap-4 sm:columns-2 lg:columns-3 xl:columns-4">
<NoteCard v-for="n in otherNotes" :key="n.id" :note="n" @open="openEditor" />
<NoteCard
v-for="n in otherNotes"
:key="n.id"
:note="n"
reorderable
@open="openEditor"
@dragstart="onDragStart"
@drop="onDrop"
/>
</div>
</section>
</template>
<div v-else class="columns-1 gap-4 sm:columns-2 lg:columns-3 xl:columns-4">
<NoteCard v-for="n in notes.items" :key="n.id" :note="n" @open="openEditor" />
<NoteCard
v-for="n in notes.items"
:key="n.id"
:note="n"
reorderable
@open="openEditor"
@dragstart="onDragStart"
@drop="onDrop"
/>
</div>
</template>
</div>