From 2ebcb43d50bb319db9dcf2973f7acaf6c95c2fe3 Mon Sep 17 00:00:00 2001 From: Pleasure1234 <3196812536@qq.com> Date: Sat, 6 Sep 2025 23:15:51 +0800 Subject: [PATCH] fix: improve note sorting behavior for drag and drop operations (#9971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve note sorting behavior for drag and drop operations - Skip automatic sorting when performing same-level drag reordering - Preserve treePath during same-level moves to maintain manual ordering - Return special indicator for manual reorder operations to prevent conflicts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * fix: type safety issue --------- Co-authored-by: Claude --- src/renderer/src/pages/notes/NotesPage.tsx | 6 ++-- src/renderer/src/services/NotesService.ts | 33 ++++++++++++++----- src/renderer/src/services/NotesTreeService.ts | 21 +++++++----- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/pages/notes/NotesPage.tsx b/src/renderer/src/pages/notes/NotesPage.tsx index 64782721f4..1ca06c000e 100644 --- a/src/renderer/src/pages/notes/NotesPage.tsx +++ b/src/renderer/src/pages/notes/NotesPage.tsx @@ -563,8 +563,10 @@ const NotesPage: FC = () => { const handleMoveNode = useCallback( async (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => { try { - await moveNode(sourceNodeId, targetNodeId, position) - await sortAllLevels(sortType) + const result = await moveNode(sourceNodeId, targetNodeId, position) + if (result.success && result.type !== 'manual_reorder') { + await sortAllLevels(sortType) + } } catch (error) { logger.error('Failed to move nodes:', error as Error) } diff --git a/src/renderer/src/services/NotesService.ts b/src/renderer/src/services/NotesService.ts index fa3607270e..4e27a41366 100644 --- a/src/renderer/src/services/NotesService.ts +++ b/src/renderer/src/services/NotesService.ts @@ -19,6 +19,8 @@ const NOTES_TREE_ID = 'notes-tree-structure' const logger = loggerService.withContext('NotesService') +export type MoveNodeResult = { success: false } | { success: true; type: 'file_system_move' | 'manual_reorder' } + /** * 初始化/同步笔记树结构 */ @@ -182,7 +184,7 @@ export async function moveNode( sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside' -): Promise { +): Promise { try { const tree = await getNotesTree() @@ -192,19 +194,19 @@ export async function moveNode( if (!sourceNode || !targetNode) { logger.error(`Move nodes failed: node not found (source: ${sourceNodeId}, target: ${targetNodeId})`) - return false + return { success: false } } // 不允许文件夹被放入文件中 if (position === 'inside' && targetNode.type === 'file' && sourceNode.type === 'folder') { logger.error('Move nodes failed: cannot move a folder inside a file') - return false + return { success: false } } // 不允许将节点移动到自身内部 if (position === 'inside' && isParentNode(tree, sourceNodeId, targetNodeId)) { logger.error('Move nodes failed: cannot move a node inside itself or its descendants') - return false + return { success: false } } let targetPath: string = '' @@ -215,7 +217,7 @@ export async function moveNode( targetPath = targetNode.externalPath } else { logger.error('Cannot move node inside a file node') - return false + return { success: false } } } else { const targetParent = findParentNode(tree, targetNodeId) @@ -226,6 +228,20 @@ export async function moveNode( } } + // 检查是否为同级拖动排序 + const sourceParent = findParentNode(tree, sourceNodeId) + const sourceDir = sourceParent ? sourceParent.externalPath : getFileDirectory(sourceNode.externalPath!) + + const isSameLevelReorder = position !== 'inside' && sourceDir === targetPath + + if (isSameLevelReorder) { + // 同级拖动排序:跳过文件系统操作,只更新树结构 + logger.debug(`Same level reorder detected, skipping file system operations`) + const success = await moveNodeInTree(tree, sourceNodeId, targetNodeId, position) + // 返回一个特殊标识,告诉调用方这是手动排序,不需要重新自动排序 + return success ? { success: true, type: 'manual_reorder' } : { success: false } + } + // 构建新的文件路径 const sourceName = sourceNode.externalPath!.split('/').pop()! const sourceNameWithoutExt = sourceName.replace(sourceNode.type === 'file' ? MARKDOWN_EXT : '', '') @@ -250,14 +266,15 @@ export async function moveNode( logger.debug(`Moved external ${sourceNode.type} to: ${newPath}`) } catch (error) { logger.error(`Failed to move external ${sourceNode.type}:`, error as Error) - return false + return { success: false } } } - return await moveNodeInTree(tree, sourceNodeId, targetNodeId, position) + const success = await moveNodeInTree(tree, sourceNodeId, targetNodeId, position) + return success ? { success: true, type: 'file_system_move' } : { success: false } } catch (error) { logger.error('Move nodes failed:', error as Error) - return false + return { success: false } } } diff --git a/src/renderer/src/services/NotesTreeService.ts b/src/renderer/src/services/NotesTreeService.ts index 5ce0bc0d5c..4159948323 100644 --- a/src/renderer/src/services/NotesTreeService.ts +++ b/src/renderer/src/services/NotesTreeService.ts @@ -89,8 +89,9 @@ export async function moveNodeInTree( return false } - // 先保存源节点的副本,以防操作失败需要恢复(暂未实现恢复逻辑) - // const sourceNodeCopy = { ...sourceNode } + // 在移除节点之前先获取源节点的父节点信息,用于后续判断是否为同级排序 + const sourceParent = findParentNode(tree, sourceNodeId) + const targetParent = findParentNode(tree, targetNodeId) // 从原位置移除节点(不保存数据库,只在内存中操作) const removed = removeNodeFromTreeInMemory(tree, sourceNodeId) @@ -110,7 +111,6 @@ export async function moveNodeInTree( sourceNode.treePath = `${targetNode.treePath}/${sourceNode.name}` } else { - const targetParent = findParentNode(tree, targetNodeId) const targetList = targetParent ? targetParent.children! : tree const targetIndex = targetList.findIndex((node) => node.id === targetNodeId) @@ -123,11 +123,16 @@ export async function moveNodeInTree( const insertIndex = position === 'before' ? targetIndex : targetIndex + 1 targetList.splice(insertIndex, 0, sourceNode) - // 更新节点路径 - if (targetParent) { - sourceNode.treePath = `${targetParent.treePath}/${sourceNode.name}` - } else { - sourceNode.treePath = `/${sourceNode.name}` + // 检查是否为同级排序,如果是则保持原有的 treePath + const isSameLevelReorder = sourceParent === targetParent + + // 只有在跨级移动时才更新节点路径 + if (!isSameLevelReorder) { + if (targetParent) { + sourceNode.treePath = `${targetParent.treePath}/${sourceNode.name}` + } else { + sourceNode.treePath = `/${sourceNode.name}` + } } }