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:
Copilot 2025-12-11 12:59:50 +08:00 committed by GitHub
parent 377614713b
commit 82628edbd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 40 deletions

View File

@ -13,7 +13,8 @@ import {
loadTree,
renameNode as renameEntry,
sortTree,
uploadNotes
uploadNotes,
type FileEntryData
} from '@renderer/services/NotesService'
import {
addUniquePath,
@ -652,10 +653,7 @@ const NotesPage: FC = () => {
// 处理文件上传
const handleUploadFiles = useCallback(
async (
files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
overrideTargetFolderPath?: string
) => {
async (files: File[] | FileEntryData[], overrideTargetFolderPath?: string) => {
try {
if (!files || files.length === 0) {
window.toast.warning(t('notes.no_file_selected'))

View File

@ -2,6 +2,7 @@ import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useActiveNode } from '@renderer/hooks/useNotesQuery'
import NotesSidebarHeader from '@renderer/pages/notes/NotesSidebarHeader'
import { findNode, findParent } from '@renderer/services/NotesTreeService'
import { type FileEntryData } from '@renderer/services/NotesService'
import { useAppSelector } from '@renderer/store'
import { selectSortType } from '@renderer/store/note'
import type { NotesSortType, NotesTreeNode } from '@renderer/types/note'
@ -38,10 +39,7 @@ interface NotesSidebarProps {
onToggleStar: (nodeId: string) => void
onMoveNode: (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => void
onSortNodes: (sortType: NotesSortType) => void
onUploadFiles: (
files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
targetFolderPath?: string
) => void
onUploadFiles: (files: File[] | FileEntryData[], targetFolderPath?: string) => void
notesTree: NotesTreeNode[]
selectedFolderId?: string | null
notesPath?: string

View File

@ -1,14 +1,12 @@
import { loggerService } from '@logger'
import { type FileEntryData } from '@renderer/services/NotesService'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
const logger = loggerService.withContext('useNotesFileUpload')
interface UseNotesFileUploadProps {
onUploadFiles: (
files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
targetFolderPath?: string
) => void
onUploadFiles: (files: File[] | FileEntryData[], targetFolderPath?: string) => void
setIsDragOverSidebar: (isDragOver: boolean) => void
getTargetFolderPath?: () => string | null
refreshTree?: () => Promise<void>
@ -36,12 +34,7 @@ export const useNotesFileUpload = ({
if (items.length === 0) return
// Collect all entries with their fullPath preserved
const entryDataList: Array<{
fullPath: string
isFile: boolean
isDirectory: boolean
systemPath: string
}> = []
const entryDataList: FileEntryData[] = []
const processEntry = async (entry: FileSystemEntry): Promise<void> => {
if (entry.isFile) {
@ -117,7 +110,7 @@ export const useNotesFileUpload = ({
if (entryDataList.length > 0) {
// Pass entry data list to parent for recursive upload with optional target override
onUploadFiles(entryDataList as any, overrideTargetFolderPath)
onUploadFiles(entryDataList, overrideTargetFolderPath)
}
} else {
// Fallback for browsers without FileSystemEntry API

View File

@ -15,6 +15,13 @@ export interface UploadResult {
folderCount: number
}
export interface FileEntryData {
fullPath: string
isFile: boolean
isDirectory: boolean
systemPath: string
}
export async function loadTree(rootPath: string): Promise<NotesTreeNode[]> {
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 }
}
// 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(
files: File[] | Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
filesOrEntries: File[] | FileEntryData[],
targetPath: string
): Promise<UploadResult> {
const basePath = normalizePath(targetPath)
if (files.length === 0) {
if (filesOrEntries.length === 0) {
return {
uploadedNodes: [],
totalFiles: 0,
@ -99,23 +109,26 @@ export async function uploadNotes(
}
}
const firstItem = files[0]
const isEntryDataList =
typeof firstItem === 'object' && 'fullPath' in firstItem && 'systemPath' in firstItem && 'isFile' in firstItem
if (isEntryDataList) {
const entries = files as Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>
return uploadNotesRecursive(entries, targetPath)
// Check if we're dealing with FileEntryData by looking at the first item
const firstItem = filesOrEntries[0]
if ('fullPath' in firstItem && 'systemPath' in firstItem) {
return uploadNotesFromEntries(filesOrEntries as FileEntryData[], targetPath)
}
// Legacy approach: File objects (for browser File API compatibility)
const fileList = files as File[]
const totalFiles = fileList.length
return uploadNotesFromFiles(filesOrEntries as File[], targetPath)
}
/**
* 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 {
const filePaths: string[] = []
for (const file of fileList) {
for (const file of files) {
const filePath = window.api.file.getPathForFile(file)
if (filePath) {
@ -156,7 +169,7 @@ export async function uploadNotes(
folderCount: result.folderCount
}
} catch (error) {
logger.error('Legacy file upload failed:', error as Error)
logger.error('File upload failed:', error as Error)
return {
uploadedNodes: [],
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)
* Uses batch processing for better performance
*/
async function uploadNotesRecursive(
entryDataList: Array<{ fullPath: string; isFile: boolean; isDirectory: boolean; systemPath: string }>,
targetPath: string
): Promise<UploadResult> {
async function uploadNotesRecursive(entryDataList: FileEntryData[], targetPath: string): Promise<UploadResult> {
const basePath = normalizePath(targetPath)
try {