From bcf1fa90ed01a8b248031625f6c016df16666bc1 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 8 Dec 2025 00:32:43 +0800 Subject: [PATCH] fix: update file upload handling and remove unused root selection --- src/renderer/src/pages/notes/NotesPage.tsx | 16 ++- src/renderer/src/pages/notes/NotesSidebar.tsx | 97 ++++++++++++------- .../pages/notes/hooks/useNotesFileUpload.ts | 13 ++- 3 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/renderer/src/pages/notes/NotesPage.tsx b/src/renderer/src/pages/notes/NotesPage.tsx index f88fa34a23..4f7efca1ef 100644 --- a/src/renderer/src/pages/notes/NotesPage.tsx +++ b/src/renderer/src/pages/notes/NotesPage.tsx @@ -574,12 +574,6 @@ const NotesPage: FC = () => { [dispatch, handleToggleExpanded, invalidateFileContent] ) - // 选中根(清空选择) - const handleSelectRoot = useCallback(() => { - dispatch(setActiveFilePath(undefined)) - setSelectedFolderId(null) - }, [dispatch]) - // 删除节点 const handleDeleteNode = useCallback( async (nodeId: string) => { @@ -658,14 +652,19 @@ const NotesPage: FC = () => { // 处理文件上传 const handleUploadFiles = useCallback( - async (files: File[]) => { + async ( + files: + | File[] + | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>, + overrideTargetFolderPath?: string + ) => { try { if (!files || files.length === 0) { window.toast.warning(t('notes.no_file_selected')) return } - const targetFolderPath = getTargetFolderPath() + const targetFolderPath = overrideTargetFolderPath || getTargetFolderPath() if (!targetFolderPath) { throw new Error('No folder path selected') } @@ -937,7 +936,6 @@ const NotesPage: FC = () => { notesTree={notesTree} selectedFolderId={selectedFolderId} onSelectNode={handleSelectNode} - onSelectRoot={handleSelectRoot} onCreateFolder={handleCreateFolder} onCreateNote={handleCreateNote} onDeleteNode={handleDeleteNode} diff --git a/src/renderer/src/pages/notes/NotesSidebar.tsx b/src/renderer/src/pages/notes/NotesSidebar.tsx index 94ccd59033..9580bf95de 100644 --- a/src/renderer/src/pages/notes/NotesSidebar.tsx +++ b/src/renderer/src/pages/notes/NotesSidebar.tsx @@ -1,7 +1,7 @@ import { DynamicVirtualList } from '@renderer/components/VirtualList' import { useActiveNode } from '@renderer/hooks/useNotesQuery' import NotesSidebarHeader from '@renderer/pages/notes/NotesSidebarHeader' -import { findNode } from '@renderer/services/NotesService' +import { findNode, findParent } from '@renderer/services/NotesTreeService' import { useAppSelector } from '@renderer/store' import { selectSortType } from '@renderer/store/note' import type { NotesSortType, NotesTreeNode } from '@renderer/types/note' @@ -32,14 +32,16 @@ interface NotesSidebarProps { onCreateFolder: (name: string, targetFolderId?: string) => void onCreateNote: (name: string, targetFolderId?: string) => void onSelectNode: (node: NotesTreeNode) => void - onSelectRoot: () => void onDeleteNode: (nodeId: string) => void onRenameNode: (nodeId: string, newName: string) => void onToggleExpanded: (nodeId: string) => void onToggleStar: (nodeId: string) => void onMoveNode: (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => void onSortNodes: (sortType: NotesSortType) => void - onUploadFiles: (files: File[]) => void + onUploadFiles: ( + files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>, + targetFolderPath?: string + ) => void notesTree: NotesTreeNode[] selectedFolderId?: string | null notesPath?: string @@ -50,7 +52,6 @@ const NotesSidebar: FC = ({ onCreateFolder, onCreateNote, onSelectNode, - onSelectRoot, onDeleteNode, onRenameNode, onToggleExpanded, @@ -97,6 +98,7 @@ const NotesSidebar: FC = ({ onUploadFiles, setIsDragOverSidebar, getTargetFolderPath: () => { + // Default target folder path (used by select dialogs and as fallback) if (selectedFolderId) { const selectedNode = findNode(notesTree, selectedFolderId) if (selectedNode && selectedNode.type === 'folder') { @@ -394,7 +396,24 @@ const NotesSidebar: FC = ({ onDragLeave={() => setIsDragOverSidebar(false)} onDrop={(e) => { if (!draggedNodeId) { - handleDropFiles(e) + // External drop: determine precise target folder by hit element + let overrideTarget: string | undefined + const targetEl = (e.target as HTMLElement).closest('[data-node-id]') as HTMLElement | null + if (targetEl) { + const nodeId = targetEl.getAttribute('data-node-id') || '' + const node = nodeId ? findNode(notesTree, nodeId) : null + if (node) { + if (node.type === 'folder') { + overrideTarget = node.externalPath + } else { + const parent = findParent(notesTree, node.id) + if (parent && parent.type === 'folder') { + overrideTarget = parent.externalPath + } + } + } + } + handleDropFiles(e, overrideTarget) } }}> = ({ trigger={['contextMenu']} open={openDropdownKey === 'empty-area'} onOpenChange={(open) => setOpenDropdownKey(open ? 'empty-area' : null)}> - 28} - scrollerStyle={{ flex: 1, minHeight: 0, height: 'auto' }} - itemContainerStyle={{ padding: '8px 8px 0 8px' }} - overscan={10} - isSticky={isSticky} - getItemDepth={getItemDepth} - containerProps={{ - onContextMenu: (e) => { - const target = e.target as HTMLElement - if (!target.closest('[data-index]')) { - onSelectRoot() - setOpenDropdownKey('empty-area') - } - }, - onClick: (e) => { - const target = e.target as HTMLElement - if (!target.closest('[data-index]')) { - onSelectRoot() - } +
{ + const target = e.target as HTMLElement + // Right-click on empty space should not change selection + if (!target.closest('[data-index]')) { + setOpenDropdownKey('empty-area') + e.preventDefault() } }}> - {({ node, depth }) => ( - - )} - + 28} + scrollerStyle={{ flex: 1, minHeight: 0, height: '100%' }} + itemContainerStyle={{ padding: '8px 8px 0 8px' }} + overscan={10} + isSticky={isSticky} + getItemDepth={getItemDepth} + containerProps={{ + // Double click on empty area: create a new note (.md) + onDoubleClick: (e) => { + const target = e.target as HTMLElement + if (!target.closest('[data-index]')) { + handleCreateNote() + } + } + }}> + {({ node, depth }) => ( + + )} + +
diff --git a/src/renderer/src/pages/notes/hooks/useNotesFileUpload.ts b/src/renderer/src/pages/notes/hooks/useNotesFileUpload.ts index 4eec1c615b..fcbdb2c007 100644 --- a/src/renderer/src/pages/notes/hooks/useNotesFileUpload.ts +++ b/src/renderer/src/pages/notes/hooks/useNotesFileUpload.ts @@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next' const logger = loggerService.withContext('useNotesFileUpload') interface UseNotesFileUploadProps { - onUploadFiles: (files: File[]) => void + onUploadFiles: ( + files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>, + targetFolderPath?: string + ) => void setIsDragOverSidebar: (isDragOver: boolean) => void getTargetFolderPath?: () => string | null refreshTree?: () => Promise @@ -25,7 +28,7 @@ export const useNotesFileUpload = ({ * This ensures dragging ~/Users/me/tmp/xxx creates target/tmp/xxx */ const handleDropFiles = useCallback( - async (e: React.DragEvent) => { + async (e: React.DragEvent, overrideTargetFolderPath?: string) => { e.preventDefault() setIsDragOverSidebar(false) @@ -113,14 +116,14 @@ export const useNotesFileUpload = ({ await Promise.all(promises) if (entryDataList.length > 0) { - // Pass entry data list to parent for recursive upload - onUploadFiles(entryDataList as any) + // Pass entry data list to parent for recursive upload with optional target override + onUploadFiles(entryDataList as any, overrideTargetFolderPath) } } else { // Fallback for browsers without FileSystemEntry API const regularFiles = Array.from(e.dataTransfer.files) if (regularFiles.length > 0) { - onUploadFiles(regularFiles) + onUploadFiles(regularFiles, overrideTargetFolderPath) } } },