Initial commit

This commit is contained in:
2026-03-08 01:34:54 +08:00
commit 1f104f73c8
441 changed files with 64911 additions and 0 deletions

237
src/pages/NoteBrowser.tsx Normal file
View File

@@ -0,0 +1,237 @@
import { useCallback, useEffect } from 'react'
import { TitleBar } from '@/components/layout/TitleBar'
import { Sidebar } from '@/components/file-system/Sidebar'
import { AppHeader } from '@/components/layout/AppHeader'
import { ActivityBar } from '@/components/layout/ActivityBar'
import { TabContentCache } from '@/components/tabs'
import { UnsavedChangesDialog } from '@/components/editor/UnsavedChangesHandler'
import { useUnsavedChangesHandler } from '@/hooks/domain/useUnsavedChangesHandler'
import type { FileItem } from '@/lib/api'
import { useNotebookEvents } from '@/hooks/events/useNotebookEvents'
import { useSidebarResize } from '@/hooks/ui/useSidebarResize'
import { useWallpaper, useTabStore, isSpecialTab, isRegularMarkdownFile, getDerivedEditState } from '@/stores'
import { FileSystemManager } from '@/components/file-system/FileSystemManager'
import { useNoteBrowser } from '@/hooks/domain/useNoteBrowser'
import { getFileItem, getModuleFileItem } from '@/lib/module-registry'
import { useAutoLoadTabContent } from '@/hooks/domain/useAutoLoadTabContent'
const getSettingsFileItem = () => getModuleFileItem('settings')
const getRecycleBinFileItem = () => getModuleFileItem('recycle-bin')
export const NoteBrowser = () => {
useAutoLoadTabContent()
const { opacity } = useWallpaper()
const { sidebarWidth, startResizing } = useSidebarResize(250)
const {
tabs,
activeTabId,
selectFile,
setActiveFile,
closeFile,
closeOtherFiles,
closeAllFiles,
renamePath,
handleDeletePath,
} = useTabStore()
const openFiles = Array.from(tabs.values()).map(t => t.file)
const selectedFile = activeTabId ? tabs.get(activeTabId)?.file ?? null : null
const derivedState = getDerivedEditState(selectedFile?.path ?? null)
const isSpecial = selectedFile ? isSpecialTab(selectedFile) : false
const isMarkdown = selectedFile ? isRegularMarkdownFile(selectedFile) : false
const {
sidebarOpen,
setSidebarOpen,
refreshKey,
bumpRefresh,
showTOC,
tocItems,
showTOCButton,
bgTimestamp,
handleTOCClick,
handleTOCClose,
handleTOCItemClick,
handleTocUpdated,
handleSave,
handleToggleEdit,
isAsyncImportProcessing
} = useNoteBrowser()
const { isConfirmOpen, requestAction, handleConfirm, handleCancel } = useUnsavedChangesHandler({
isEditing: derivedState.isCurrentTabEditing,
hasUnsavedChanges: derivedState.hasCurrentTabUnsavedChanges
})
useNotebookEvents({
selectedFile,
onRefresh: bumpRefresh,
onFileChanged: (filePath) => {
const tab = tabs.get(filePath)
if (tab && !tab.isEditing && !isSpecial) {
// 重新加载文件内容
}
},
})
const handleFileSelect = useCallback(
(file: FileItem) => {
requestAction(() => selectFile(file))
},
[selectFile, requestAction],
)
const handleTabClick = useCallback(
(file: FileItem) => {
requestAction(() => setActiveFile(file))
},
[setActiveFile, requestAction],
)
const handleTabClose = useCallback(
(file: FileItem, e: React.MouseEvent) => {
e.stopPropagation()
if (file.path === selectedFile?.path) {
requestAction(() => closeFile(file))
} else {
closeFile(file)
}
},
[closeFile, selectedFile, requestAction],
)
const handleSettingsClick = useCallback(() => {
selectFile(getSettingsFileItem())
}, [selectFile])
const handleRecycleBinClick = useCallback(() => {
selectFile(getRecycleBinFileItem())
}, [selectFile])
const handleModuleClick = useCallback((moduleId: string) => {
const fileItem = getFileItem(moduleId)
if (fileItem) {
selectFile(fileItem)
}
}, [selectFile])
useEffect(() => {
if (!isMarkdown && showTOC) {
handleTOCClose()
}
}, [selectedFile, showTOC, isMarkdown, handleTOCClose])
const handleFileDelete = useCallback((path: string, type: 'file' | 'dir') => {
handleDeletePath(path, type)
}, [handleDeletePath])
return (
<div
className="flex h-screen overflow-hidden flex-col"
style={{
backgroundImage: `url('/background.png?t=${bgTimestamp}')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>
<div className="relative z-10 flex flex-col h-full w-full">
<TitleBar
openFiles={openFiles}
activeFile={selectedFile}
onTabClick={handleTabClick}
onTabClose={handleTabClose}
onCloseOther={closeOtherFiles}
onCloseAll={closeAllFiles}
opacity={opacity}
/>
<div className="flex-1 flex overflow-hidden min-h-0 relative">
<ActivityBar
onSettingsClick={handleSettingsClick}
onRecycleBinClick={handleRecycleBinClick}
sidebarOpen={sidebarOpen}
onToggleSidebar={() => setSidebarOpen(!sidebarOpen)}
onModuleClick={handleModuleClick}
/>
<div className="flex-1 flex overflow-hidden min-h-0 relative">
<FileSystemManager
onRefresh={bumpRefresh}
onFileSelect={handleFileSelect}
onPathRename={renamePath}
onFileDelete={handleFileDelete}
onPathMove={renamePath}
>
<Sidebar
isOpen={sidebarOpen}
width={sidebarWidth}
refreshKey={refreshKey}
selectedFile={selectedFile}
onFileSelect={handleFileSelect}
onResizeStart={startResizing}
onTOCClick={handleTOCClick}
tocOpen={showTOC}
showTOC={!showTOCButton}
tocItems={tocItems}
onTOCItemClick={handleTOCItemClick}
onTOCClose={handleTOCClose}
/>
<div className="flex-1 flex flex-col min-w-0 h-full">
<div className="relative flex-1 flex flex-col min-h-0 overflow-hidden">
{selectedFile && !isAsyncImportProcessing && !isSpecialTab(selectedFile) && (
<div
className="w-full h-12 flex items-center justify-end px-3 border-b border-gray-200 dark:border-gray-700/60 backdrop-blur-sm sticky top-0 z-20 shrink-0"
style={{
backgroundColor: `rgba(var(--app-content-bg-rgb), ${opacity})`
}}
>
<AppHeader
selectedFile={selectedFile}
isEditing={derivedState.isCurrentTabEditing}
onSave={handleSave}
onToggleEdit={handleToggleEdit}
/>
</div>
)}
<div
className="flex-1 min-h-0 backdrop-blur-sm"
style={{
backgroundColor: `rgba(var(--app-content-bg-rgb), ${opacity})`,
}}
>
<TabContentCache
openFiles={openFiles}
activeFile={selectedFile}
containerClassName="p-8 pb-40"
onTocUpdated={handleTocUpdated}
/>
</div>
</div>
</div>
{selectedFile && !isSpecial && (
<div className="fixed bottom-4 right-4 z-50">
<div className="text-xs text-gray-500 bg-white/80 backdrop-blur border border-gray-200 rounded px-2 py-1 dark:text-gray-300 dark:bg-gray-900/70 dark:border-gray-700">
{derivedState.currentWordCount}
</div>
</div>
)}
<UnsavedChangesDialog
isOpen={isConfirmOpen}
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
</FileSystemManager>
</div>
</div>
</div>
</div>
)
}