- Replace `scanDeclareBlocks` with `parseDeclareCsv` in block processor - Add error handling for declare block parsing - Optimize `ReactiveVariableManager` by pre-computing variable keys - Store template text and keys in data attributes for faster updates - Adjust `VariableView` popup width via CSS class
144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
/**
|
|
* CLI block processor — wraps block-scanner with Node-specific index injection
|
|
* and content stripping.
|
|
*
|
|
* Uses Node `crypto` and `path` — not safe for browser import.
|
|
*/
|
|
|
|
import { posix } from "path";
|
|
import { createHash } from "crypto";
|
|
import { parseDeclareCsv, type VarDeclaration, type TagModifier } from "./declare-parser.js";
|
|
import {
|
|
FENCED_BLOCK_RE,
|
|
parseBlockAttrs,
|
|
resolveBlockAs,
|
|
} from "./block-scanner.js";
|
|
|
|
// Re-export shared pieces for convenience
|
|
export {
|
|
FENCED_BLOCK_RE,
|
|
parseBlockAttrs,
|
|
resolveBlockAs,
|
|
type BlockAttrs,
|
|
} from "./block-scanner.js";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ProcessedBlocks {
|
|
declarations: VarDeclaration[];
|
|
tagModifiers: TagModifier[];
|
|
}
|
|
|
|
export interface BlockResult {
|
|
/** Content with blocks processed (stripped or replaced with directives) */
|
|
stripped: string;
|
|
/** Parsed blocks for completions */
|
|
blocks: ProcessedBlocks;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Content hash
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function contentHash(body: string): string {
|
|
return createHash("md5").update(body).digest("hex").slice(0, 8);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main processor
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Process all attributed fenced code blocks in a markdown file.
|
|
*
|
|
* - Strips/replaces blocks based on `as`
|
|
* - Injects `role=file` bodies into the content index
|
|
* - Collects declare blocks for completions
|
|
* - role=spark-table blocks are converted to :md-table directives
|
|
* (spark table completions are collected later by the directive scanner)
|
|
*/
|
|
export function processBlocks(
|
|
content: string,
|
|
fileRelativePath: string,
|
|
index: Record<string, string>,
|
|
): BlockResult {
|
|
const fileDir = posix.dirname(fileRelativePath);
|
|
|
|
const blocks: ProcessedBlocks = {
|
|
declarations: [],
|
|
tagModifiers: [],
|
|
};
|
|
|
|
const stripped = content.replace(
|
|
FENCED_BLOCK_RE,
|
|
(
|
|
_match: string,
|
|
lang: string,
|
|
infoString: string,
|
|
body: string,
|
|
): string => {
|
|
const attrs = parseBlockAttrs(infoString);
|
|
attrs.lang = attrs.lang || lang;
|
|
const effectiveAs = resolveBlockAs(attrs.role, attrs.as);
|
|
|
|
// ---- Dispatch by role ----
|
|
|
|
if (attrs.role === "declare") {
|
|
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") {
|
|
const filename = attrs.id
|
|
? `${attrs.id}.${attrs.lang || "txt"}`
|
|
: `_inline_${contentHash(body)}.${attrs.lang || "txt"}`;
|
|
const resolvedPath = posix.join(fileDir, filename);
|
|
index[resolvedPath] = body;
|
|
}
|
|
|
|
// ---- Render by as ----
|
|
|
|
if (effectiveAs === "codeblock") {
|
|
return _match; // keep as-is
|
|
}
|
|
|
|
if (effectiveAs === "none") {
|
|
return ""; // strip
|
|
}
|
|
|
|
// Directive: :md-table[./file.csv], :md-card[./file.csv], :md-dice[./file.csv]
|
|
if (effectiveAs.startsWith("md-")) {
|
|
const filename = attrs.id
|
|
? `${attrs.id}.${attrs.lang || "txt"}`
|
|
: `_inline_${contentHash(body)}.${attrs.lang || "txt"}`;
|
|
const resolvedPath = posix.join(fileDir, filename);
|
|
|
|
// Ensure body is in the index for directive rendering
|
|
if (!index[resolvedPath]) {
|
|
index[resolvedPath] = body;
|
|
}
|
|
|
|
// Collect extra attrs for the directive
|
|
const extra = { ...attrs.extra };
|
|
const extraStr = Object.keys(extra).length
|
|
? `{${Object.entries(extra)
|
|
.map(([k, v]) => `${k}=${v}`)
|
|
.join(" ")}}`
|
|
: "";
|
|
return `:${effectiveAs}[./${filename}]${extraStr}`;
|
|
}
|
|
|
|
// Unknown as → strip
|
|
return "";
|
|
},
|
|
);
|
|
|
|
return { stripped, blocks };
|
|
} |