perf: single editor instance with document swapping
Replace per-tab BlockNote instances with one shared editor that swaps content via replaceBlocks(). Cached blocks per tab path make subsequent switches instant — only first open parses markdown. Cache entries are cleaned up when tabs close. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,25 +85,12 @@ const schema = BlockNoteSchema.create({
|
||||
},
|
||||
})
|
||||
|
||||
/** Inner component that creates/manages BlockNote for a single tab */
|
||||
function BlockNoteTab({ content, entries, onNavigateWikilink }: { content: string; entries: VaultEntry[]; onNavigateWikilink: (target: string) => void }) {
|
||||
const [, body] = useMemo(() => splitFrontmatter(content), [content])
|
||||
/** Single BlockNote editor view — content is swapped via replaceBlocks */
|
||||
function SingleEditorView({ editor, entries, onNavigateWikilink }: { editor: ReturnType<typeof useCreateBlockNote>; entries: VaultEntry[]; onNavigateWikilink: (target: string) => void }) {
|
||||
const navigateRef = useRef(onNavigateWikilink)
|
||||
navigateRef.current = onNavigateWikilink
|
||||
const { cssVars } = useEditorTheme()
|
||||
|
||||
const editor = useCreateBlockNote({ schema })
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
const preprocessed = preProcessWikilinks(body)
|
||||
const blocks = await editor.tryParseMarkdownToBlocks(preprocessed)
|
||||
const withWikilinks = injectWikilinks(blocks)
|
||||
editor.replaceBlocks(editor.document, withWikilinks)
|
||||
}
|
||||
load()
|
||||
}, [body, editor])
|
||||
|
||||
useEffect(() => {
|
||||
const container = document.querySelector('.editor__blocknote-container')
|
||||
if (!container) return
|
||||
@@ -171,6 +158,55 @@ export const Editor = memo(function Editor({
|
||||
const [diffContent, setDiffContent] = useState<string | null>(null)
|
||||
const [diffLoading, setDiffLoading] = useState(false)
|
||||
|
||||
// Single editor instance — reused across all tabs
|
||||
const editor = useCreateBlockNote({ schema })
|
||||
// Cache parsed blocks per tab path for instant switching
|
||||
const tabCacheRef = useRef<Map<string, any[]>>(new Map())
|
||||
const prevActivePathRef = useRef<string | null>(null)
|
||||
|
||||
// Swap document content when active tab changes
|
||||
useEffect(() => {
|
||||
const cache = tabCacheRef.current
|
||||
const prevPath = prevActivePathRef.current
|
||||
|
||||
// Save current editor state for the tab we're leaving
|
||||
if (prevPath && prevPath !== activeTabPath) {
|
||||
cache.set(prevPath, editor.document)
|
||||
}
|
||||
prevActivePathRef.current = activeTabPath
|
||||
|
||||
if (!activeTabPath) return
|
||||
|
||||
const tab = tabs.find(t => t.entry.path === activeTabPath)
|
||||
if (!tab) return
|
||||
|
||||
if (cache.has(activeTabPath)) {
|
||||
// Instant switch — use cached blocks
|
||||
editor.replaceBlocks(editor.document, cache.get(activeTabPath)!)
|
||||
} else {
|
||||
// First open — parse markdown
|
||||
const [, body] = splitFrontmatter(tab.content)
|
||||
const preprocessed = preProcessWikilinks(body)
|
||||
editor.tryParseMarkdownToBlocks(preprocessed).then(blocks => {
|
||||
const withWikilinks = injectWikilinks(blocks)
|
||||
editor.replaceBlocks(editor.document, withWikilinks)
|
||||
cache.set(activeTabPath, withWikilinks)
|
||||
})
|
||||
}
|
||||
}, [activeTabPath, tabs, editor])
|
||||
|
||||
// Clean up cache entries when tabs are closed
|
||||
const tabPathsRef = useRef<Set<string>>(new Set())
|
||||
useEffect(() => {
|
||||
const currentPaths = new Set(tabs.map(t => t.entry.path))
|
||||
for (const path of tabPathsRef.current) {
|
||||
if (!currentPaths.has(path)) {
|
||||
tabCacheRef.current.delete(path)
|
||||
}
|
||||
}
|
||||
tabPathsRef.current = currentPaths
|
||||
}, [tabs])
|
||||
|
||||
const activeTab = tabs.find((t) => t.entry.path === activeTabPath) ?? null
|
||||
const isLoadingNewTab = activeTabPath !== null && !activeTab
|
||||
const showDiffToggle = activeTab && isModified?.(activeTab.entry.path)
|
||||
@@ -424,23 +460,22 @@ export const Editor = memo(function Editor({
|
||||
<DiffView diff={diffContent ?? ''} />
|
||||
</div>
|
||||
)}
|
||||
{tabs.map((tab) => (
|
||||
{!diffMode && activeTab && (
|
||||
<div
|
||||
key={tab.entry.path}
|
||||
style={{
|
||||
display: !diffMode && tab.entry.path === activeTabPath ? 'flex' : 'none',
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<BlockNoteTab
|
||||
content={tab.content}
|
||||
<SingleEditorView
|
||||
editor={editor}
|
||||
entries={entries}
|
||||
onNavigateWikilink={onNavigateWikilink}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
{isLoadingNewTab && !diffMode && (
|
||||
<div className="flex flex-1 flex-col gap-3 p-8 animate-pulse" style={{ minHeight: 0 }}>
|
||||
<div className="h-6 w-2/5 rounded bg-muted" />
|
||||
|
||||
Reference in New Issue
Block a user