diff --git a/src/cli/completions/block-processor.ts b/src/cli/completions/block-processor.ts index 9ee59b0..9b777de 100644 --- a/src/cli/completions/block-processor.ts +++ b/src/cli/completions/block-processor.ts @@ -7,7 +7,7 @@ import { posix } from "path"; import { createHash } from "crypto"; -import { scanDeclareBlocks, type VarDeclaration, type TagModifier } from "./declare-parser.js"; +import { parseDeclareCsv, type VarDeclaration, type TagModifier } from "./declare-parser.js"; import { FENCED_BLOCK_RE, parseBlockAttrs, @@ -86,9 +86,13 @@ export function processBlocks( // ---- Dispatch by role ---- if (attrs.role === "declare") { - const result = scanDeclareBlocks(_match, fileRelativePath); - blocks.declarations.push(...result.variables); - blocks.tagModifiers.push(...result.tagModifiers); + try { + const result = parseDeclareCsv(body, fileRelativePath); + blocks.declarations.push(...result.variables); + blocks.tagModifiers.push(...result.tagModifiers); + } catch (e) { + console.warn(`[block-processor] ${fileRelativePath}: ${e}`); + } } if (attrs.role === "file") { diff --git a/src/components/journal/ReactiveVariableManager.tsx b/src/components/journal/ReactiveVariableManager.tsx index 833c736..b8e5359 100644 --- a/src/components/journal/ReactiveVariableManager.tsx +++ b/src/components/journal/ReactiveVariableManager.tsx @@ -15,8 +15,11 @@ import { useJournalStream } from "../stores/journalStream"; const TEMPLATE_RE = /\{\{(\$[a-zA-Z_][a-zA-Z0-9_]*)\}\}/g; -/** Tag name used for marker spans — must stay in sync with the scan pass. */ -const MARKER = "data-reactive-var"; +/** Attribute storing the original template text on marker spans. */ +const ATTR_TEMPLATE = "data-reactive-var"; + +/** Attribute storing pre-computed key list (comma-separated) for fast updates. */ +const ATTR_KEYS = "data-reactive-keys"; export const ReactiveVariableManager: Component = () => { const contentDom = useArticleDom(); @@ -32,7 +35,7 @@ export const ReactiveVariableManager: Component = () => { // Only scan once — after the first scan, the spans exist and we // switch to the reactive update effect below. - if (dom.querySelector(`[${MARKER}]`)) return; + if (dom.querySelector(`[${ATTR_TEMPLATE}]`)) return; scanAndWrap(dom); }); @@ -49,17 +52,14 @@ export const ReactiveVariableManager: Component = () => { if (!dom) return; const v = values(); - const spans = dom.querySelectorAll(`[${MARKER}]`); + const spans = dom.querySelectorAll(`[${ATTR_TEMPLATE}]`); for (const span of spans) { - const template = span.getAttribute(MARKER) ?? ""; + const template = span.getAttribute(ATTR_TEMPLATE) ?? ""; + const keys = span.getAttribute(ATTR_KEYS)?.split(",") ?? []; let result = template; - TEMPLATE_RE.lastIndex = 0; - let m: RegExpExecArray | null; - while ((m = TEMPLATE_RE.exec(template)) !== null) { - const key = m[1]; // includes $ prefix - const resolved = v[key] ?? ""; - result = result.replace(`{{${key}}}`, resolved); + for (const key of keys) { + result = result.replaceAll(`{{${key}}}`, v[key] ?? ""); } if (span.textContent !== result) { span.textContent = result; @@ -90,7 +90,8 @@ function scanAndWrap(root: Element): void { TEMPLATE_RE.lastIndex = 0; if (!TEMPLATE_RE.test(text)) continue; - // Build replacement HTML: split on {{$key}} and wrap each key span + // Build replacement HTML: split on {{$key}}, wrap each match in a span + // with pre-computed key for fast updates. TEMPLATE_RE.lastIndex = 0; const parts: string[] = []; let lastIndex = 0; @@ -99,8 +100,9 @@ function scanAndWrap(root: Element): void { if (m.index > lastIndex) { parts.push(escapeHtml(text.slice(lastIndex, m.index))); } + const key = m[1]; // includes $ prefix parts.push( - `${escapeHtml(m[0])}`, + `${escapeHtml(m[0])}`, ); lastIndex = TEMPLATE_RE.lastIndex; } @@ -123,13 +125,13 @@ function scanAndWrap(root: Element): void { * Called on cleanup so subsequent remounts get a clean slate. */ function unwrapAll(root: Element): void { - const spans = root.querySelectorAll(`[${MARKER}]`); + const spans = root.querySelectorAll(`[${ATTR_TEMPLATE}]`); // Process in reverse to avoid DOM mutation issues for (let i = spans.length - 1; i >= 0; i--) { const span = spans[i]; const parent = span.parentNode; if (!parent) continue; - const text = span.getAttribute(MARKER) ?? span.textContent ?? ""; + const text = span.getAttribute(ATTR_TEMPLATE) ?? span.textContent ?? ""; span.replaceWith(document.createTextNode(text)); } // Normalize to merge adjacent text nodes diff --git a/src/components/journal/VariableView.tsx b/src/components/journal/VariableView.tsx index bb68b4b..5ae7910 100644 --- a/src/components/journal/VariableView.tsx +++ b/src/components/journal/VariableView.tsx @@ -90,7 +90,7 @@ const VariableRow: Component<{ {/* Hover popup */} -
+
{/* Declaration expression */}