M2 drag-reorder: notes.position + reorder API + native DnD
- 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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user