From e805970c17bb7dfc45a0cfe57c35e038ed64bdab Mon Sep 17 00:00:00 2001 From: lucaronin Date: Tue, 17 Feb 2026 12:08:03 +0100 Subject: [PATCH] refactor: extract wikilink utils and DiffView from Editor.tsx --- src/components/Editor.tsx | 70 ++------------------------------------- src/utils/wikilinks.ts | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 68 deletions(-) create mode 100644 src/utils/wikilinks.ts diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index f2e83fe0..15a22406 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -21,6 +21,7 @@ import { Sparkle, DotsThree, } from '@phosphor-icons/react' +import { splitFrontmatter, preProcessWikilinks, injectWikilinks, countWords } from '../utils/wikilinks' import './Editor.css' import './EditorTheme.css' @@ -84,67 +85,7 @@ const schema = BlockNoteSchema.create({ }, }) -/** Strip YAML frontmatter from markdown, returning [frontmatter, body] */ -function splitFrontmatter(content: string): [string, string] { - if (!content.startsWith('---')) return ['', content] - const end = content.indexOf('\n---', 3) - if (end === -1) return ['', content] - let to = end + 4 - if (content[to] === '\n') to++ - return [content.slice(0, to), content.slice(to)] -} - -// Wikilink placeholder tokens for markdown round-trip -const WL_START = '\u2039WIKILINK:' -const WL_END = '\u203A' -const WL_RE = new RegExp(`${WL_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^${WL_END}]+)${WL_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'g') - -/** Pre-process markdown: replace [[target]] with placeholder tokens */ -function preProcessWikilinks(md: string): string { - return md.replace(/\[\[([^\]]+)\]\]/g, (_m, target) => `${WL_START}${target}${WL_END}`) -} - -/** Walk blocks and replace placeholder text with wikilink inline content */ -function injectWikilinks(blocks: any[]): any[] { - return blocks.map(block => { - if (block.content && Array.isArray(block.content)) { - block.content = expandWikilinksInContent(block.content) - } - if (block.children && Array.isArray(block.children)) { - block.children = injectWikilinks(block.children) - } - return block - }) -} - -function expandWikilinksInContent(content: any[]): any[] { - const result: any[] = [] - for (const item of content) { - if (item.type === 'text' && typeof item.text === 'string' && item.text.includes(WL_START)) { - const text = item.text as string - let lastIndex = 0 - WL_RE.lastIndex = 0 - let match - while ((match = WL_RE.exec(text)) !== null) { - if (match.index > lastIndex) { - result.push({ ...item, text: text.slice(lastIndex, match.index) }) - } - result.push({ - type: 'wikilink', - props: { target: match[1] }, - content: undefined, - }) - lastIndex = match.index + match[0].length - } - if (lastIndex < text.length) { - result.push({ ...item, text: text.slice(lastIndex) }) - } - } else { - result.push(item) - } - } - return result -} +// Wikilink utilities and splitFrontmatter extracted to ../utils/wikilinks.ts // DiffView extracted to ./DiffView.tsx @@ -216,13 +157,6 @@ function BlockNoteTab({ content, entries, onNavigateWikilink }: { content: strin ) } -function countWords(content: string): number { - const [, body] = splitFrontmatter(content) - const text = body.replace(/[#*_\[\]`>~\-|]/g, '').trim() - if (!text) return 0 - return text.split(/\s+/).filter(Boolean).length -} - export function Editor({ tabs, activeTabPath, entries, onSwitchTab, onCloseTab, onNavigateWikilink, onLoadDiff, isModified, onCreateNote, inspectorCollapsed, onToggleInspector, inspectorWidth, onInspectorResize, diff --git a/src/utils/wikilinks.ts b/src/utils/wikilinks.ts new file mode 100644 index 00000000..2fafa685 --- /dev/null +++ b/src/utils/wikilinks.ts @@ -0,0 +1,68 @@ +// Wikilink placeholder tokens for markdown round-trip +const WL_START = '\u2039WIKILINK:' +const WL_END = '\u203A' +const WL_RE = new RegExp(`${WL_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^${WL_END}]+)${WL_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'g') + +/** Pre-process markdown: replace [[target]] with placeholder tokens */ +export function preProcessWikilinks(md: string): string { + return md.replace(/\[\[([^\]]+)\]\]/g, (_m, target) => `${WL_START}${target}${WL_END}`) +} + +/** Walk blocks and replace placeholder text with wikilink inline content */ +export function injectWikilinks(blocks: any[]): any[] { + return blocks.map(block => { + if (block.content && Array.isArray(block.content)) { + block.content = expandWikilinksInContent(block.content) + } + if (block.children && Array.isArray(block.children)) { + block.children = injectWikilinks(block.children) + } + return block + }) +} + +function expandWikilinksInContent(content: any[]): any[] { + const result: any[] = [] + for (const item of content) { + if (item.type !== 'text' || typeof item.text !== 'string' || !item.text.includes(WL_START)) { + result.push(item) + continue + } + const text = item.text as string + let lastIndex = 0 + WL_RE.lastIndex = 0 + let match + while ((match = WL_RE.exec(text)) !== null) { + if (match.index > lastIndex) { + result.push({ ...item, text: text.slice(lastIndex, match.index) }) + } + result.push({ + type: 'wikilink', + props: { target: match[1] }, + content: undefined, + }) + lastIndex = match.index + match[0].length + } + if (lastIndex < text.length) { + result.push({ ...item, text: text.slice(lastIndex) }) + } + } + return result +} + +/** Strip YAML frontmatter from markdown, returning [frontmatter, body] */ +export function splitFrontmatter(content: string): [string, string] { + if (!content.startsWith('---')) return ['', content] + const end = content.indexOf('\n---', 3) + if (end === -1) return ['', content] + let to = end + 4 + if (content[to] === '\n') to++ + return [content.slice(0, to), content.slice(to)] +} + +export function countWords(content: string): number { + const [, body] = splitFrontmatter(content) + const text = body.replace(/[#*_\[\]`>~\-|]/g, '').trim() + if (!text) return 0 + return text.split(/\s+/).filter(Boolean).length +}