fix: editor content not displaying after single-instance refactor
The single BlockNote editor instance was calling replaceBlocks before the editor was mounted in the DOM, causing content swaps to silently fail. Now tracks mount state via editor.onMount() and defers content swaps until the editor is ready. Also guards against stale async parses when the user switches tabs before markdown parsing completes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,7 @@ vi.mock('@blocknote/react', () => ({
|
||||
replaceBlocks: () => {},
|
||||
document: [],
|
||||
insertInlineContent: () => {},
|
||||
onMount: (cb: () => void) => { cb(); return () => {} },
|
||||
}),
|
||||
SuggestionMenuController: () => null,
|
||||
}))
|
||||
|
||||
@@ -19,6 +19,7 @@ vi.mock('@blocknote/react', () => ({
|
||||
replaceBlocks: () => {},
|
||||
document: [],
|
||||
insertInlineContent: () => {},
|
||||
onMount: (cb: () => void) => { cb(); return () => {} },
|
||||
}),
|
||||
SuggestionMenuController: () => null,
|
||||
}))
|
||||
|
||||
@@ -163,6 +163,21 @@ export const Editor = memo(function Editor({
|
||||
// Cache parsed blocks per tab path for instant switching
|
||||
const tabCacheRef = useRef<Map<string, any[]>>(new Map())
|
||||
const prevActivePathRef = useRef<string | null>(null)
|
||||
const editorMountedRef = useRef(false)
|
||||
const pendingSwapRef = useRef<(() => void) | null>(null)
|
||||
|
||||
// Track editor mount state
|
||||
useEffect(() => {
|
||||
const cleanup = editor.onMount(() => {
|
||||
editorMountedRef.current = true
|
||||
// Execute any pending content swap that was queued before mount
|
||||
if (pendingSwapRef.current) {
|
||||
pendingSwapRef.current()
|
||||
pendingSwapRef.current = null
|
||||
}
|
||||
})
|
||||
return cleanup
|
||||
}, [editor])
|
||||
|
||||
// Swap document content when active tab changes
|
||||
useEffect(() => {
|
||||
@@ -170,7 +185,7 @@ export const Editor = memo(function Editor({
|
||||
const prevPath = prevActivePathRef.current
|
||||
|
||||
// Save current editor state for the tab we're leaving
|
||||
if (prevPath && prevPath !== activeTabPath) {
|
||||
if (prevPath && prevPath !== activeTabPath && editorMountedRef.current) {
|
||||
cache.set(prevPath, editor.document)
|
||||
}
|
||||
prevActivePathRef.current = activeTabPath
|
||||
@@ -180,23 +195,34 @@ export const Editor = memo(function Editor({
|
||||
const tab = tabs.find(t => t.entry.path === activeTabPath)
|
||||
if (!tab) return
|
||||
|
||||
const applyBlocks = (blocks: any[]) => {
|
||||
editor.replaceBlocks(editor.document, blocks)
|
||||
}
|
||||
|
||||
try {
|
||||
if (cache.has(activeTabPath)) {
|
||||
// Instant switch — use cached blocks
|
||||
editor.replaceBlocks(editor.document, cache.get(activeTabPath)!)
|
||||
const cached = cache.get(activeTabPath)!
|
||||
if (editorMountedRef.current) {
|
||||
applyBlocks(cached)
|
||||
} else {
|
||||
pendingSwapRef.current = () => applyBlocks(cached)
|
||||
}
|
||||
} else {
|
||||
// First open — parse markdown
|
||||
const [, body] = splitFrontmatter(tab.content)
|
||||
const preprocessed = preProcessWikilinks(body)
|
||||
const targetPath = activeTabPath
|
||||
editor.tryParseMarkdownToBlocks(preprocessed).then(blocks => {
|
||||
const withWikilinks = injectWikilinks(blocks)
|
||||
try {
|
||||
editor.replaceBlocks(editor.document, withWikilinks)
|
||||
} catch (err) {
|
||||
console.error('Failed to replace blocks:', err)
|
||||
return
|
||||
// Guard: skip if user switched tabs while we were parsing
|
||||
if (prevActivePathRef.current !== targetPath) return
|
||||
cache.set(targetPath, withWikilinks)
|
||||
if (editorMountedRef.current) {
|
||||
applyBlocks(withWikilinks)
|
||||
} else {
|
||||
pendingSwapRef.current = () => applyBlocks(withWikilinks)
|
||||
}
|
||||
cache.set(activeTabPath, withWikilinks)
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user