fix: update file upload handling and remove unused root selection

This commit is contained in:
suyao 2025-12-08 00:32:43 +08:00
parent 2863961349
commit bcf1fa90ed
No known key found for this signature in database
3 changed files with 75 additions and 51 deletions

View File

@ -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}

View File

@ -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<NotesSidebarProps> = ({
onCreateFolder,
onCreateNote,
onSelectNode,
onSelectRoot,
onDeleteNode,
onRenameNode,
onToggleExpanded,
@ -97,6 +98,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
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<NotesSidebarProps> = ({
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)
}
}}>
<NotesSidebarHeader
@ -436,40 +455,44 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
trigger={['contextMenu']}
open={openDropdownKey === 'empty-area'}
onOpenChange={(open) => setOpenDropdownKey(open ? 'empty-area' : null)}>
<DynamicVirtualList
ref={virtualListRef}
list={flattenedNodes}
size={0}
estimateSize={() => 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()
}
<div
style={{ flex: 1, minHeight: 0 }}
onContextMenu={(e) => {
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 }) => (
<TreeNode
node={node}
depth={depth}
renderChildren={false}
onHintClick={node.type === 'hint' ? handleSelectFolder : undefined}
/>
)}
</DynamicVirtualList>
<DynamicVirtualList
ref={virtualListRef}
list={flattenedNodes}
estimateSize={() => 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 }) => (
<TreeNode
node={node}
depth={depth}
renderChildren={false}
onHintClick={node.type === 'hint' ? handleSelectFolder : undefined}
/>
)}
</DynamicVirtualList>
</div>
</Dropdown>
</NotesTreeContainer>

View File

@ -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<void>
@ -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)
}
}
},