mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
fix: update file upload handling and remove unused root selection
This commit is contained in:
parent
2863961349
commit
bcf1fa90ed
@ -574,12 +574,6 @@ const NotesPage: FC = () => {
|
|||||||
[dispatch, handleToggleExpanded, invalidateFileContent]
|
[dispatch, handleToggleExpanded, invalidateFileContent]
|
||||||
)
|
)
|
||||||
|
|
||||||
// 选中根(清空选择)
|
|
||||||
const handleSelectRoot = useCallback(() => {
|
|
||||||
dispatch(setActiveFilePath(undefined))
|
|
||||||
setSelectedFolderId(null)
|
|
||||||
}, [dispatch])
|
|
||||||
|
|
||||||
// 删除节点
|
// 删除节点
|
||||||
const handleDeleteNode = useCallback(
|
const handleDeleteNode = useCallback(
|
||||||
async (nodeId: string) => {
|
async (nodeId: string) => {
|
||||||
@ -658,14 +652,19 @@ const NotesPage: FC = () => {
|
|||||||
|
|
||||||
// 处理文件上传
|
// 处理文件上传
|
||||||
const handleUploadFiles = useCallback(
|
const handleUploadFiles = useCallback(
|
||||||
async (files: File[]) => {
|
async (
|
||||||
|
files:
|
||||||
|
| File[]
|
||||||
|
| Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
|
||||||
|
overrideTargetFolderPath?: string
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
if (!files || files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
window.toast.warning(t('notes.no_file_selected'))
|
window.toast.warning(t('notes.no_file_selected'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetFolderPath = getTargetFolderPath()
|
const targetFolderPath = overrideTargetFolderPath || getTargetFolderPath()
|
||||||
if (!targetFolderPath) {
|
if (!targetFolderPath) {
|
||||||
throw new Error('No folder path selected')
|
throw new Error('No folder path selected')
|
||||||
}
|
}
|
||||||
@ -937,7 +936,6 @@ const NotesPage: FC = () => {
|
|||||||
notesTree={notesTree}
|
notesTree={notesTree}
|
||||||
selectedFolderId={selectedFolderId}
|
selectedFolderId={selectedFolderId}
|
||||||
onSelectNode={handleSelectNode}
|
onSelectNode={handleSelectNode}
|
||||||
onSelectRoot={handleSelectRoot}
|
|
||||||
onCreateFolder={handleCreateFolder}
|
onCreateFolder={handleCreateFolder}
|
||||||
onCreateNote={handleCreateNote}
|
onCreateNote={handleCreateNote}
|
||||||
onDeleteNode={handleDeleteNode}
|
onDeleteNode={handleDeleteNode}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
||||||
import { useActiveNode } from '@renderer/hooks/useNotesQuery'
|
import { useActiveNode } from '@renderer/hooks/useNotesQuery'
|
||||||
import NotesSidebarHeader from '@renderer/pages/notes/NotesSidebarHeader'
|
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 { useAppSelector } from '@renderer/store'
|
||||||
import { selectSortType } from '@renderer/store/note'
|
import { selectSortType } from '@renderer/store/note'
|
||||||
import type { NotesSortType, NotesTreeNode } from '@renderer/types/note'
|
import type { NotesSortType, NotesTreeNode } from '@renderer/types/note'
|
||||||
@ -32,14 +32,16 @@ interface NotesSidebarProps {
|
|||||||
onCreateFolder: (name: string, targetFolderId?: string) => void
|
onCreateFolder: (name: string, targetFolderId?: string) => void
|
||||||
onCreateNote: (name: string, targetFolderId?: string) => void
|
onCreateNote: (name: string, targetFolderId?: string) => void
|
||||||
onSelectNode: (node: NotesTreeNode) => void
|
onSelectNode: (node: NotesTreeNode) => void
|
||||||
onSelectRoot: () => void
|
|
||||||
onDeleteNode: (nodeId: string) => void
|
onDeleteNode: (nodeId: string) => void
|
||||||
onRenameNode: (nodeId: string, newName: string) => void
|
onRenameNode: (nodeId: string, newName: string) => void
|
||||||
onToggleExpanded: (nodeId: string) => void
|
onToggleExpanded: (nodeId: string) => void
|
||||||
onToggleStar: (nodeId: string) => void
|
onToggleStar: (nodeId: string) => void
|
||||||
onMoveNode: (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => void
|
onMoveNode: (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => void
|
||||||
onSortNodes: (sortType: NotesSortType) => 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[]
|
notesTree: NotesTreeNode[]
|
||||||
selectedFolderId?: string | null
|
selectedFolderId?: string | null
|
||||||
notesPath?: string
|
notesPath?: string
|
||||||
@ -50,7 +52,6 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
onCreateNote,
|
onCreateNote,
|
||||||
onSelectNode,
|
onSelectNode,
|
||||||
onSelectRoot,
|
|
||||||
onDeleteNode,
|
onDeleteNode,
|
||||||
onRenameNode,
|
onRenameNode,
|
||||||
onToggleExpanded,
|
onToggleExpanded,
|
||||||
@ -97,6 +98,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
onUploadFiles,
|
onUploadFiles,
|
||||||
setIsDragOverSidebar,
|
setIsDragOverSidebar,
|
||||||
getTargetFolderPath: () => {
|
getTargetFolderPath: () => {
|
||||||
|
// Default target folder path (used by select dialogs and as fallback)
|
||||||
if (selectedFolderId) {
|
if (selectedFolderId) {
|
||||||
const selectedNode = findNode(notesTree, selectedFolderId)
|
const selectedNode = findNode(notesTree, selectedFolderId)
|
||||||
if (selectedNode && selectedNode.type === 'folder') {
|
if (selectedNode && selectedNode.type === 'folder') {
|
||||||
@ -394,7 +396,24 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
onDragLeave={() => setIsDragOverSidebar(false)}
|
onDragLeave={() => setIsDragOverSidebar(false)}
|
||||||
onDrop={(e) => {
|
onDrop={(e) => {
|
||||||
if (!draggedNodeId) {
|
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
|
<NotesSidebarHeader
|
||||||
@ -436,40 +455,44 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
trigger={['contextMenu']}
|
trigger={['contextMenu']}
|
||||||
open={openDropdownKey === 'empty-area'}
|
open={openDropdownKey === 'empty-area'}
|
||||||
onOpenChange={(open) => setOpenDropdownKey(open ? 'empty-area' : null)}>
|
onOpenChange={(open) => setOpenDropdownKey(open ? 'empty-area' : null)}>
|
||||||
<DynamicVirtualList
|
<div
|
||||||
ref={virtualListRef}
|
style={{ flex: 1, minHeight: 0 }}
|
||||||
list={flattenedNodes}
|
onContextMenu={(e) => {
|
||||||
size={0}
|
const target = e.target as HTMLElement
|
||||||
estimateSize={() => 28}
|
// Right-click on empty space should not change selection
|
||||||
scrollerStyle={{ flex: 1, minHeight: 0, height: 'auto' }}
|
if (!target.closest('[data-index]')) {
|
||||||
itemContainerStyle={{ padding: '8px 8px 0 8px' }}
|
setOpenDropdownKey('empty-area')
|
||||||
overscan={10}
|
e.preventDefault()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{({ node, depth }) => (
|
<DynamicVirtualList
|
||||||
<TreeNode
|
ref={virtualListRef}
|
||||||
node={node}
|
list={flattenedNodes}
|
||||||
depth={depth}
|
estimateSize={() => 28}
|
||||||
renderChildren={false}
|
scrollerStyle={{ flex: 1, minHeight: 0, height: '100%' }}
|
||||||
onHintClick={node.type === 'hint' ? handleSelectFolder : undefined}
|
itemContainerStyle={{ padding: '8px 8px 0 8px' }}
|
||||||
/>
|
overscan={10}
|
||||||
)}
|
isSticky={isSticky}
|
||||||
</DynamicVirtualList>
|
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>
|
</Dropdown>
|
||||||
</NotesTreeContainer>
|
</NotesTreeContainer>
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next'
|
|||||||
const logger = loggerService.withContext('useNotesFileUpload')
|
const logger = loggerService.withContext('useNotesFileUpload')
|
||||||
|
|
||||||
interface UseNotesFileUploadProps {
|
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
|
setIsDragOverSidebar: (isDragOver: boolean) => void
|
||||||
getTargetFolderPath?: () => string | null
|
getTargetFolderPath?: () => string | null
|
||||||
refreshTree?: () => Promise<void>
|
refreshTree?: () => Promise<void>
|
||||||
@ -25,7 +28,7 @@ export const useNotesFileUpload = ({
|
|||||||
* This ensures dragging ~/Users/me/tmp/xxx creates target/tmp/xxx
|
* This ensures dragging ~/Users/me/tmp/xxx creates target/tmp/xxx
|
||||||
*/
|
*/
|
||||||
const handleDropFiles = useCallback(
|
const handleDropFiles = useCallback(
|
||||||
async (e: React.DragEvent) => {
|
async (e: React.DragEvent, overrideTargetFolderPath?: string) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setIsDragOverSidebar(false)
|
setIsDragOverSidebar(false)
|
||||||
|
|
||||||
@ -113,14 +116,14 @@ export const useNotesFileUpload = ({
|
|||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
|
||||||
if (entryDataList.length > 0) {
|
if (entryDataList.length > 0) {
|
||||||
// Pass entry data list to parent for recursive upload
|
// Pass entry data list to parent for recursive upload with optional target override
|
||||||
onUploadFiles(entryDataList as any)
|
onUploadFiles(entryDataList as any, overrideTargetFolderPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback for browsers without FileSystemEntry API
|
// Fallback for browsers without FileSystemEntry API
|
||||||
const regularFiles = Array.from(e.dataTransfer.files)
|
const regularFiles = Array.from(e.dataTransfer.files)
|
||||||
if (regularFiles.length > 0) {
|
if (regularFiles.length > 0) {
|
||||||
onUploadFiles(regularFiles)
|
onUploadFiles(regularFiles, overrideTargetFolderPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user