Initial commit
This commit is contained in:
237
src/pages/NoteBrowser.tsx
Normal file
237
src/pages/NoteBrowser.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user