import { useState, useEffect, useCallback } from 'react'; import { useStore } from './store'; import { invoke } from './ipc-client'; import { IPC } from '@hunter/shared'; import { useDialog } from './features/dialog/dialog-store'; import { useEditorShared } from './features/editor/editor-shared-store'; import { FileTree } from './features/file-tree/FileTree'; import { Editor } from './features/editor/Editor'; import { Toolbar } from './features/toolbar/Toolbar'; import { TitleBar } from './features/shell/TitleBar'; import { InputDialog } from './features/dialog/InputDialog'; import { NotifyLayer } from './features/dialog/NotifyLayer'; import { TabBar } from './features/tabs/TabBar'; import { useTabs } from './features/tabs/tab-store'; import { GraphView } from './features/graph/GraphView'; import { SettingsPanel } from './features/settings/SettingsPanel'; import { AppearancePanel } from './features/appearance/AppearancePanel'; import { useAppearance } from './features/appearance/appearance-store'; import { CheatSheet } from './features/keymap/CheatSheet'; import { useGlobalHotkeys } from './features/keymap/use-hotkeys'; import { useKeymap } from './features/keymap/keymap-service'; import { useLayout } from './features/shell/layout-store'; import { Resizer } from './features/shell/Resizer'; import { EffectLayer } from './features/effects/EffectLayer'; import { useSettings } from './features/settings/settings-store'; import { SearchBox } from './features/search/SearchBox'; import { TagFilter } from './features/search/TagFilter'; import { OutlinePanel } from './features/outline/OutlinePanel'; import { DocInfoPanel } from './features/doc-info/DocInfoPanel'; export function App() { const loadVault = useStore(s => s.loadVault); const loadTree = useStore(s => s.loadTree); const openDoc = useStore(s => s.openDoc); const { restore, tabs, activeId } = useTabs(); const [rightTab, setRightTab] = useState<'outline' | 'info'>('outline'); const [view, setView] = useState<'editor' | 'graph'>('editor'); const [showSettings, setShowSettings] = useState(false); const [showCheat, setShowCheat] = useState(false); const [showAppearance, setShowAppearance] = useState(false); const appearance = useAppearance(); const settings = useSettings(); const layout = useLayout(); const switchToEditor = useCallback(() => setView('editor'), []); useEffect(() => { loadVault(); loadTree(); restore(); appearance.load(); useKeymap.getState().load(); useSettings.getState().loadGpu(); }, []); useEffect(() => { if (activeId && tabs.length > 0) { const t = tabs.find(x => x.id === activeId); if (t) openDoc(t.path).then(() => { // FR-T5:恢复标签页保存的滚动位置 if (t.scrollTop > 0) { setTimeout(() => { const ed = document.querySelector('.ProseMirror'); const c = ed?.closest('[class*="editor"]') as HTMLElement | null; if (c) c.scrollTop = t.scrollTop; }, 100); } }).catch(() => {}); } }, [activeId]); // FR-S4:关窗前冲刷未保存内容 useEffect(() => { const onBeforeUnload = () => { try { window.dispatchEvent(new CustomEvent('dsh-flush-body')); useStore.getState().saveNow(); } catch {} }; window.addEventListener('beforeunload', onBeforeUnload); return () => window.removeEventListener('beforeunload', onBeforeUnload); }, []); useGlobalHotkeys({ onNewDoc: async () => { const { showInput } = useDialog.getState(); const name = await showInput('新建文档', '输入文档标题…'); if (!name) return; const { path } = await invoke(IPC.fileCreate, { basePath: '', name, isFolder: false }); await useStore.getState().loadTree(); await useStore.getState().openDoc(path); }, onSave: () => { window.dispatchEvent(new CustomEvent('dsh-flush-body')); useStore.getState().saveNow(); }, onFocusSearch: () => { const el = document.querySelector('input[placeholder*="搜索"]') as HTMLInputElement; el?.focus(); }, onToggleOutline: () => layout.toggleOutline(), onCheat: () => setShowCheat(true), onSettings: () => setShowSettings(true), onFind: () => { window.dispatchEvent(new CustomEvent('dsh-toggle-find')); }, onClearFormat: () => { const ed = useEditorShared.getState().editor; ed?.chain().focus().clearNodes().unsetAllMarks().run(); }, }); // 编辑器样式随设置生效(FR-编辑器设置真实生效) useEffect(() => { document.documentElement.style.setProperty('--editor-font-size', settings.fontSize + 'px'); document.documentElement.style.setProperty('--editor-font-family', settings.fontFamily); document.documentElement.style.setProperty('--editor-white-space', settings.wordWrap ? 'pre-wrap' : 'pre'); }, [settings.fontSize, settings.fontFamily, settings.wordWrap]); return (