feat: organize lists with drag and drop
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m22s

This commit is contained in:
2026-09-09 20:28:29 +08:00
parent 30705e1cec
commit 0e291ddb9c
8 changed files with 679 additions and 8 deletions
+48
View File
@@ -676,6 +676,54 @@ describe('sidebar layout', () => {
expect(app).toContain(':title="list.name" :aria-label="list.name"')
})
it('supports dragging movable lists into folders, out to My Lists, and within one scope', () => {
expect(app).toContain('class="section-title list-root-drop"')
expect(app).toContain(':data-folder-id="folder.id"')
expect(app).toContain(':data-list-id="list.id"')
expect(app).toContain('class="list-drag-handle"')
expect(app).toContain("api(`/lists/${list.id}/move`, { method: 'PUT', body: JSON.stringify({ folder_id: folderId, list_ids: result.orderedIds }) })")
expect(app).toContain("} else {\n await api('/lists/reorder'")
expect(app).toContain('lists.value = previous')
expect(app).toContain('expandedFolders.value = new Set(expandedFolders.value).add(folderId)')
expect(app).not.toContain('list.is_inbox" class="list-drag-handle"')
})
it('uses the handle-only touch contract and exposes same-scope move controls', () => {
expect(app).not.toContain('@pointerdown="startListLongPress(list, $event)"')
expect(app).not.toContain('function startListLongPress')
expect(app).not.toContain('listLongPressTimer')
expect(app).toContain('@pointerdown.stop="startListHandlePress(list,$event)"')
expect(app).toContain('listHandlePending = { id: list.id, pointer }')
expect(app).toContain('window.setTimeout(() => beginListDrag(list, pointer), 450)')
expect(app).toContain('@pointermove.stop="moveListHandle(list,$event)"')
expect(app).toContain("if (!listDrag.value && listHandlePending?.id === list.id && listHandlePending.pointer.pointerId === event.pointerId)")
expect(app).toContain('hasExceededLongPressMovement(')
expect(app).toContain('clearListHandlePress()')
expect(app).toContain('moveListDrag(list, event)')
expect(app).toContain('@pointerup.stop="finishListDrag(list,$event)"')
expect(app).toContain('@pointercancel.stop="cancelListDrag"')
expect(app).toContain('listHandlePending = undefined')
expect(app).toContain('aria-label="上移清单"')
expect(app).toContain('aria-label="下移清单"')
expect(app).toContain("moveListWithinScope(sidebarAction.item as TaskList, 'up')")
expect(app).toContain("moveListWithinScope(sidebarAction.item as TaskList, 'down')")
expect(app).toContain('aria-label="移动到文件夹"')
expect(app).toContain('role="menu"')
expect(app).toContain('role="menuitem"')
expect(app).toContain('移出文件夹')
})
it('shows drag lift, folder highlighting, and insertion targets', () => {
expect(css).toContain('.list-row.list-dragging{')
expect(css).toContain('.folder-row.list-drop-target{')
expect(css).toContain('.list-root-drop.list-drop-target{')
expect(css).toContain('.list-row.list-reorder-target{')
expect(css).toContain('.list-drag-handle{width:44px;min-width:44px;height:44px;')
expect(css).toContain('.list-row{touch-action:pan-y}')
expect(css).not.toContain('.list-drag-handle{display:none}')
expect(css).not.toContain('.list-row.list-dragging{touch-action:none}')
})
it('only restores archived lists from the explicit restore action', () => {
expect(app).toContain('class="list-row-main archived-row-label"')
expect(app).not.toContain(':aria-label="`恢复清单:${list.name}`" @click="restoreList(list)"')