mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-03 02:59:07 +08:00
fix: improve note sorting behavior for drag and drop operations (#9971)
* 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 <noreply@anthropic.com> * fix: type safety issue --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
826b71deba
commit
2ebcb43d50
@ -563,8 +563,10 @@ const NotesPage: FC = () => {
|
|||||||
const handleMoveNode = useCallback(
|
const handleMoveNode = useCallback(
|
||||||
async (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => {
|
async (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => {
|
||||||
try {
|
try {
|
||||||
await moveNode(sourceNodeId, targetNodeId, position)
|
const result = await moveNode(sourceNodeId, targetNodeId, position)
|
||||||
await sortAllLevels(sortType)
|
if (result.success && result.type !== 'manual_reorder') {
|
||||||
|
await sortAllLevels(sortType)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to move nodes:', error as Error)
|
logger.error('Failed to move nodes:', error as Error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,8 @@ const NOTES_TREE_ID = 'notes-tree-structure'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('NotesService')
|
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,
|
sourceNodeId: string,
|
||||||
targetNodeId: string,
|
targetNodeId: string,
|
||||||
position: 'before' | 'after' | 'inside'
|
position: 'before' | 'after' | 'inside'
|
||||||
): Promise<boolean> {
|
): Promise<MoveNodeResult> {
|
||||||
try {
|
try {
|
||||||
const tree = await getNotesTree()
|
const tree = await getNotesTree()
|
||||||
|
|
||||||
@ -192,19 +194,19 @@ export async function moveNode(
|
|||||||
|
|
||||||
if (!sourceNode || !targetNode) {
|
if (!sourceNode || !targetNode) {
|
||||||
logger.error(`Move nodes failed: node not found (source: ${sourceNodeId}, target: ${targetNodeId})`)
|
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') {
|
if (position === 'inside' && targetNode.type === 'file' && sourceNode.type === 'folder') {
|
||||||
logger.error('Move nodes failed: cannot move a folder inside a file')
|
logger.error('Move nodes failed: cannot move a folder inside a file')
|
||||||
return false
|
return { success: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不允许将节点移动到自身内部
|
// 不允许将节点移动到自身内部
|
||||||
if (position === 'inside' && isParentNode(tree, sourceNodeId, targetNodeId)) {
|
if (position === 'inside' && isParentNode(tree, sourceNodeId, targetNodeId)) {
|
||||||
logger.error('Move nodes failed: cannot move a node inside itself or its descendants')
|
logger.error('Move nodes failed: cannot move a node inside itself or its descendants')
|
||||||
return false
|
return { success: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetPath: string = ''
|
let targetPath: string = ''
|
||||||
@ -215,7 +217,7 @@ export async function moveNode(
|
|||||||
targetPath = targetNode.externalPath
|
targetPath = targetNode.externalPath
|
||||||
} else {
|
} else {
|
||||||
logger.error('Cannot move node inside a file node')
|
logger.error('Cannot move node inside a file node')
|
||||||
return false
|
return { success: false }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const targetParent = findParentNode(tree, targetNodeId)
|
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 sourceName = sourceNode.externalPath!.split('/').pop()!
|
||||||
const sourceNameWithoutExt = sourceName.replace(sourceNode.type === 'file' ? MARKDOWN_EXT : '', '')
|
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}`)
|
logger.debug(`Moved external ${sourceNode.type} to: ${newPath}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to move external ${sourceNode.type}:`, error as 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) {
|
} catch (error) {
|
||||||
logger.error('Move nodes failed:', error as Error)
|
logger.error('Move nodes failed:', error as Error)
|
||||||
return false
|
return { success: false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,8 +89,9 @@ export async function moveNodeInTree(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先保存源节点的副本,以防操作失败需要恢复(暂未实现恢复逻辑)
|
// 在移除节点之前先获取源节点的父节点信息,用于后续判断是否为同级排序
|
||||||
// const sourceNodeCopy = { ...sourceNode }
|
const sourceParent = findParentNode(tree, sourceNodeId)
|
||||||
|
const targetParent = findParentNode(tree, targetNodeId)
|
||||||
|
|
||||||
// 从原位置移除节点(不保存数据库,只在内存中操作)
|
// 从原位置移除节点(不保存数据库,只在内存中操作)
|
||||||
const removed = removeNodeFromTreeInMemory(tree, sourceNodeId)
|
const removed = removeNodeFromTreeInMemory(tree, sourceNodeId)
|
||||||
@ -110,7 +111,6 @@ export async function moveNodeInTree(
|
|||||||
|
|
||||||
sourceNode.treePath = `${targetNode.treePath}/${sourceNode.name}`
|
sourceNode.treePath = `${targetNode.treePath}/${sourceNode.name}`
|
||||||
} else {
|
} else {
|
||||||
const targetParent = findParentNode(tree, targetNodeId)
|
|
||||||
const targetList = targetParent ? targetParent.children! : tree
|
const targetList = targetParent ? targetParent.children! : tree
|
||||||
const targetIndex = targetList.findIndex((node) => node.id === targetNodeId)
|
const targetIndex = targetList.findIndex((node) => node.id === targetNodeId)
|
||||||
|
|
||||||
@ -123,11 +123,16 @@ export async function moveNodeInTree(
|
|||||||
const insertIndex = position === 'before' ? targetIndex : targetIndex + 1
|
const insertIndex = position === 'before' ? targetIndex : targetIndex + 1
|
||||||
targetList.splice(insertIndex, 0, sourceNode)
|
targetList.splice(insertIndex, 0, sourceNode)
|
||||||
|
|
||||||
// 更新节点路径
|
// 检查是否为同级排序,如果是则保持原有的 treePath
|
||||||
if (targetParent) {
|
const isSameLevelReorder = sourceParent === targetParent
|
||||||
sourceNode.treePath = `${targetParent.treePath}/${sourceNode.name}`
|
|
||||||
} else {
|
// 只有在跨级移动时才更新节点路径
|
||||||
sourceNode.treePath = `/${sourceNode.name}`
|
if (!isSameLevelReorder) {
|
||||||
|
if (targetParent) {
|
||||||
|
sourceNode.treePath = `${targetParent.treePath}/${sourceNode.name}`
|
||||||
|
} else {
|
||||||
|
sourceNode.treePath = `/${sourceNode.name}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user