mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 06:19:05 +08:00
refactor: use TypeScript function overloading for uploadNotes type safety (#11833)
* Initial plan * refactor: use TypeScript function overloading for uploadNotes Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
This commit is contained in:
parent
377614713b
commit
82628edbd2
@ -13,7 +13,8 @@ import {
|
|||||||
loadTree,
|
loadTree,
|
||||||
renameNode as renameEntry,
|
renameNode as renameEntry,
|
||||||
sortTree,
|
sortTree,
|
||||||
uploadNotes
|
uploadNotes,
|
||||||
|
type FileEntryData
|
||||||
} from '@renderer/services/NotesService'
|
} from '@renderer/services/NotesService'
|
||||||
import {
|
import {
|
||||||
addUniquePath,
|
addUniquePath,
|
||||||
@ -652,10 +653,7 @@ const NotesPage: FC = () => {
|
|||||||
|
|
||||||
// 处理文件上传
|
// 处理文件上传
|
||||||
const handleUploadFiles = useCallback(
|
const handleUploadFiles = useCallback(
|
||||||
async (
|
async (files: File[] | FileEntryData[], overrideTargetFolderPath?: string) => {
|
||||||
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'))
|
||||||
|
|||||||
@ -2,6 +2,7 @@ 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, findParent } from '@renderer/services/NotesTreeService'
|
import { findNode, findParent } from '@renderer/services/NotesTreeService'
|
||||||
|
import { type FileEntryData } from '@renderer/services/NotesService'
|
||||||
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'
|
||||||
@ -38,10 +39,7 @@ interface NotesSidebarProps {
|
|||||||
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: (
|
onUploadFiles: (files: File[] | FileEntryData[], targetFolderPath?: string) => void
|
||||||
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
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { type FileEntryData } from '@renderer/services/NotesService'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const logger = loggerService.withContext('useNotesFileUpload')
|
const logger = loggerService.withContext('useNotesFileUpload')
|
||||||
|
|
||||||
interface UseNotesFileUploadProps {
|
interface UseNotesFileUploadProps {
|
||||||
onUploadFiles: (
|
onUploadFiles: (files: File[] | FileEntryData[], targetFolderPath?: string) => void
|
||||||
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>
|
||||||
@ -36,12 +34,7 @@ export const useNotesFileUpload = ({
|
|||||||
if (items.length === 0) return
|
if (items.length === 0) return
|
||||||
|
|
||||||
// Collect all entries with their fullPath preserved
|
// Collect all entries with their fullPath preserved
|
||||||
const entryDataList: Array<{
|
const entryDataList: FileEntryData[] = []
|
||||||
fullPath: string
|
|
||||||
isFile: boolean
|
|
||||||
isDirectory: boolean
|
|
||||||
systemPath: string
|
|
||||||
}> = []
|
|
||||||
|
|
||||||
const processEntry = async (entry: FileSystemEntry): Promise<void> => {
|
const processEntry = async (entry: FileSystemEntry): Promise<void> => {
|
||||||
if (entry.isFile) {
|
if (entry.isFile) {
|
||||||
@ -117,7 +110,7 @@ export const useNotesFileUpload = ({
|
|||||||
|
|
||||||
if (entryDataList.length > 0) {
|
if (entryDataList.length > 0) {
|
||||||
// Pass entry data list to parent for recursive upload with optional target override
|
// Pass entry data list to parent for recursive upload with optional target override
|
||||||
onUploadFiles(entryDataList as any, overrideTargetFolderPath)
|
onUploadFiles(entryDataList, overrideTargetFolderPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback for browsers without FileSystemEntry API
|
// Fallback for browsers without FileSystemEntry API
|
||||||
|
|||||||
@ -15,6 +15,13 @@ export interface UploadResult {
|
|||||||
folderCount: number
|
folderCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileEntryData {
|
||||||
|
fullPath: string
|
||||||
|
isFile: boolean
|
||||||
|
isDirectory: boolean
|
||||||
|
systemPath: string
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadTree(rootPath: string): Promise<NotesTreeNode[]> {
|
export async function loadTree(rootPath: string): Promise<NotesTreeNode[]> {
|
||||||
return window.api.file.getDirectoryStructure(normalizePath(rootPath))
|
return window.api.file.getDirectoryStructure(normalizePath(rootPath))
|
||||||
}
|
}
|
||||||
@ -83,13 +90,16 @@ export async function renameNode(node: NotesTreeNode, newName: string): Promise<
|
|||||||
return { path: `${parentDir}/${safeName}`, name: safeName }
|
return { path: `${parentDir}/${safeName}`, name: safeName }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function overloads for type safety
|
||||||
|
export async function uploadNotes(files: File[], targetPath: string): Promise<UploadResult>
|
||||||
|
export async function uploadNotes(entries: FileEntryData[], targetPath: string): Promise<UploadResult>
|
||||||
|
|
||||||
|
// Implementation signature
|
||||||
export async function uploadNotes(
|
export async function uploadNotes(
|
||||||
files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
|
filesOrEntries: File[] | FileEntryData[],
|
||||||
targetPath: string
|
targetPath: string
|
||||||
): Promise<UploadResult> {
|
): Promise<UploadResult> {
|
||||||
const basePath = normalizePath(targetPath)
|
if (filesOrEntries.length === 0) {
|
||||||
|
|
||||||
if (files.length === 0) {
|
|
||||||
return {
|
return {
|
||||||
uploadedNodes: [],
|
uploadedNodes: [],
|
||||||
totalFiles: 0,
|
totalFiles: 0,
|
||||||
@ -99,23 +109,26 @@ export async function uploadNotes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstItem = files[0]
|
// Check if we're dealing with FileEntryData by looking at the first item
|
||||||
const isEntryDataList =
|
const firstItem = filesOrEntries[0]
|
||||||
typeof firstItem === 'object' && 'fullPath' in firstItem && 'systemPath' in firstItem && 'isFile' in firstItem
|
if ('fullPath' in firstItem && 'systemPath' in firstItem) {
|
||||||
|
return uploadNotesFromEntries(filesOrEntries as FileEntryData[], targetPath)
|
||||||
if (isEntryDataList) {
|
|
||||||
const entries = files as Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>
|
|
||||||
return uploadNotesRecursive(entries, targetPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy approach: File objects (for browser File API compatibility)
|
return uploadNotesFromFiles(filesOrEntries as File[], targetPath)
|
||||||
const fileList = files as File[]
|
}
|
||||||
const totalFiles = fileList.length
|
|
||||||
|
/**
|
||||||
|
* Upload notes from File objects (browser File API)
|
||||||
|
*/
|
||||||
|
async function uploadNotesFromFiles(files: File[], targetPath: string): Promise<UploadResult> {
|
||||||
|
const basePath = normalizePath(targetPath)
|
||||||
|
const totalFiles = files.length
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filePaths: string[] = []
|
const filePaths: string[] = []
|
||||||
|
|
||||||
for (const file of fileList) {
|
for (const file of files) {
|
||||||
const filePath = window.api.file.getPathForFile(file)
|
const filePath = window.api.file.getPathForFile(file)
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
@ -156,7 +169,7 @@ export async function uploadNotes(
|
|||||||
folderCount: result.folderCount
|
folderCount: result.folderCount
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Legacy file upload failed:', error as Error)
|
logger.error('File upload failed:', error as Error)
|
||||||
return {
|
return {
|
||||||
uploadedNodes: [],
|
uploadedNodes: [],
|
||||||
totalFiles,
|
totalFiles,
|
||||||
@ -167,14 +180,18 @@ export async function uploadNotes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload notes from FileEntryData (drag-and-drop with directory structure)
|
||||||
|
*/
|
||||||
|
async function uploadNotesFromEntries(entries: FileEntryData[], targetPath: string): Promise<UploadResult> {
|
||||||
|
return uploadNotesRecursive(entries, targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursive upload for drag-and-drop with fullPath preserved (VS Code approach)
|
* Recursive upload for drag-and-drop with fullPath preserved (VS Code approach)
|
||||||
* Uses batch processing for better performance
|
* Uses batch processing for better performance
|
||||||
*/
|
*/
|
||||||
async function uploadNotesRecursive(
|
async function uploadNotesRecursive(entryDataList: FileEntryData[], targetPath: string): Promise<UploadResult> {
|
||||||
entryDataList: Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
|
|
||||||
targetPath: string
|
|
||||||
): Promise<UploadResult> {
|
|
||||||
const basePath = normalizePath(targetPath)
|
const basePath = normalizePath(targetPath)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user