|
| 1 | +/** |
| 2 | + * 笔记状态仓库 — 文件夹 CRUD slice |
| 3 | + * Note store — folder CRUD slice |
| 4 | + * |
| 5 | + * @ai-context: 从 useNoteStore 拆出。数据层 NoteFolder 支持 parentId 嵌套 |
| 6 | + * (UI 仅渲染一级),删除必须整树处理:deleteFolder 把整树笔记移到根目录, |
| 7 | + * deleteFolderWithNotes 真删除整树笔记并清理搜索/链接索引,避免 parentId |
| 8 | + * 悬挂与孤儿笔记。 |
| 9 | + * @ai-context: Extracted from useNoteStore. Deletion always walks the whole |
| 10 | + * folder subtree — either relocating its notes to root or deleting them. |
| 11 | + */ |
| 12 | +import { noteStore, noteFolderStore } from '@/lib/storage'; |
| 13 | +import { createWithLog, updateWithLog, deleteWithLog } from '@/lib/storage/writeWithLog'; |
| 14 | +import { dexieSearchIndexer } from '@/lib/search/dexieSearchIndexer'; |
| 15 | +import { removeLinks } from '../lib/links/noteLinkStore'; |
| 16 | +import { collectFolderTreeIds } from '../lib/folderTree'; |
| 17 | +import type { NoteSlice, NoteState } from './noteStoreTypes'; |
| 18 | + |
| 19 | +type FolderSliceActions = Pick<NoteState, |
| 20 | + | 'loadFolders' | 'createFolder' | 'updateFolder' |
| 21 | + | 'deleteFolder' | 'deleteFolderWithNotes' | 'selectFolder' |
| 22 | +>; |
| 23 | + |
| 24 | +export const createFolderSlice: NoteSlice<FolderSliceActions> = (set, get) => ({ |
| 25 | + loadFolders: async () => { |
| 26 | + const folders = await noteFolderStore.getAll(); |
| 27 | + set({ folders }); |
| 28 | + }, |
| 29 | + |
| 30 | + createFolder: async (name, parentId?, color?) => { |
| 31 | + const folderData = { |
| 32 | + name, |
| 33 | + parentId, |
| 34 | + color, |
| 35 | + createdAt: new Date(), |
| 36 | + order: Date.now(), |
| 37 | + }; |
| 38 | + const id = await createWithLog(noteFolderStore, 'noteFolders', folderData); |
| 39 | + await get().loadFolders(); |
| 40 | + return id; |
| 41 | + }, |
| 42 | + |
| 43 | + updateFolder: async (id, changes) => { |
| 44 | + await updateWithLog(noteFolderStore, 'noteFolders', id, changes); |
| 45 | + await get().loadFolders(); |
| 46 | + }, |
| 47 | + |
| 48 | + deleteFolder: async (id) => { |
| 49 | + // 递归收集分组树(数据层支持 parentId 嵌套,UI 仅渲染一级) |
| 50 | + const { notes, folders, selectedFolderId } = get(); |
| 51 | + const treeIds = collectFolderTreeIds(folders, id); |
| 52 | + // 整棵分组树下的笔记移到根目录(folderId 设为 undefined) |
| 53 | + const affected = notes.filter((n) => n.folderId && treeIds.includes(n.folderId)); |
| 54 | + for (const note of affected) { |
| 55 | + if (note.id !== undefined) { |
| 56 | + await updateWithLog(noteStore, 'notes', note.id, { folderId: undefined }); |
| 57 | + } |
| 58 | + } |
| 59 | + // 删除分组树(含根与全部子孙,避免 parentId 悬挂) |
| 60 | + for (const folderId of treeIds) { |
| 61 | + await deleteWithLog(noteFolderStore, 'noteFolders', folderId); |
| 62 | + } |
| 63 | + if (selectedFolderId && treeIds.includes(selectedFolderId)) { |
| 64 | + set({ selectedFolderId: null }); |
| 65 | + } |
| 66 | + await get().loadFolders(); |
| 67 | + await get().loadNotes(); |
| 68 | + }, |
| 69 | + |
| 70 | + deleteFolderWithNotes: async (id) => { |
| 71 | + const { notes, folders, selectedNoteId, selectedFolderId } = get(); |
| 72 | + const treeIds = collectFolderTreeIds(folders, id); |
| 73 | + // 整棵分组树下的笔记全部真删除(清理搜索/链接索引) |
| 74 | + const noteIds = notes |
| 75 | + .filter((n) => n.folderId && treeIds.includes(n.folderId)) |
| 76 | + .map((n) => n.id) |
| 77 | + .filter((nid): nid is string => nid !== undefined); |
| 78 | + for (const noteId of noteIds) { |
| 79 | + await deleteWithLog(noteStore, 'notes', noteId); |
| 80 | + try { await dexieSearchIndexer.remove(noteId); } catch { /* 忽略 */ } |
| 81 | + removeLinks(noteId).catch((err) => { |
| 82 | + console.debug('[noteStore] removeLinks failed (folder delete)', noteId, err); |
| 83 | + }); |
| 84 | + } |
| 85 | + // 删除分组树(含根与全部子孙) |
| 86 | + for (const folderId of treeIds) { |
| 87 | + await deleteWithLog(noteFolderStore, 'noteFolders', folderId); |
| 88 | + } |
| 89 | + if (selectedNoteId && noteIds.includes(selectedNoteId)) { |
| 90 | + set({ selectedNoteId: null }); |
| 91 | + } |
| 92 | + if (selectedFolderId && treeIds.includes(selectedFolderId)) { |
| 93 | + set({ selectedFolderId: null }); |
| 94 | + } |
| 95 | + await get().loadFolders(); |
| 96 | + await get().loadNotes(); |
| 97 | + }, |
| 98 | + |
| 99 | + selectFolder: (id) => { |
| 100 | + set({ selectedFolderId: id }); |
| 101 | + }, |
| 102 | +}); |
0 commit comments