diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index f5fa7501..931ce423 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -72,12 +72,21 @@ function BlockNoteTab({ content, onNavigateWikilink }: { content: string; onNavi const navigateRef = useRef(onNavigateWikilink) navigateRef.current = onNavigateWikilink + // Extract wiki-link targets from the raw markdown + const wikiTargets = useMemo(() => { + const targets = new Set() + const re = /\[\[([^\]]+)\]\]/g + let m + while ((m = re.exec(body))) targets.add(m[1]) + return targets + }, [body]) + const editor = useCreateBlockNote({}) // Load markdown content into editor useEffect(() => { async function load() { - // Convert [[target]] wiki-links to markdown links with wikilink: protocol + // Convert [[target]] wiki-links to markdown links — BlockNote renders them as underlined text const preprocessed = body.replace(/\[\[([^\]]+)\]\]/g, (_match, target) => `[${target}](https://wikilink.internal/${encodeURIComponent(target)})`) const blocks = await editor.tryParseMarkdownToBlocks(preprocessed) editor.replaceBlocks(editor.document, blocks) @@ -86,40 +95,31 @@ function BlockNoteTab({ content, onNavigateWikilink }: { content: string; onNavi // eslint-disable-next-line react-hooks/exhaustive-deps }, [body]) - // Patch wikilink anchors: remove href (prevents browser navigation) and attach click handler + // Intercept all clicks in the editor — check if target is a wikilink anchor useEffect(() => { const WIKILINK_PREFIX = 'https://wikilink.internal/' - function patchWikilinks(root: Element) { - root.querySelectorAll(`a[href^="${WIKILINK_PREFIX}"]`).forEach((a) => { - const target = decodeURIComponent(a.getAttribute('href')!.replace(WIKILINK_PREFIX, '')) - a.removeAttribute('href') - a.style.cursor = 'pointer' - a.style.color = 'var(--accent-blue)' - a.style.textDecoration = 'underline' - a.dataset.wikilink = target - a.onclick = (e) => { - e.preventDefault() - e.stopPropagation() - navigateRef.current(target) - } - }) + const handler = (e: MouseEvent) => { + // Walk up from click target to find an with wikilink href + let el = e.target as HTMLElement | null + while (el && el.tagName !== 'A') el = el.parentElement + if (!el) return + const href = (el as HTMLAnchorElement).getAttribute('href') || '' + if (href.startsWith(WIKILINK_PREFIX)) { + e.preventDefault() + e.stopPropagation() + e.stopImmediatePropagation() + const target = decodeURIComponent(href.replace(WIKILINK_PREFIX, '')) + navigateRef.current(target) + return false + } } - // Patch on initial render and watch for DOM changes (BlockNote re-renders blocks) + // Capture phase on the container const container = document.querySelector('.editor__blocknote-container') if (!container) return - - // Initial patch (delayed to let BlockNote render) - const timer = setTimeout(() => patchWikilinks(container), 200) - - const observer = new MutationObserver(() => patchWikilinks(container)) - observer.observe(container, { childList: true, subtree: true }) - - return () => { - clearTimeout(timer) - observer.disconnect() - } + container.addEventListener('click', handler as EventListener, true) + return () => container.removeEventListener('click', handler as EventListener, true) }, [editor]) const isDark = typeof document !== 'undefined' && document.documentElement.getAttribute('data-theme') !== 'light'