refactor: move spark table parsing to directive scanner

Relocate spark table logic from the block processor to a dedicated
directive scanner. This allows spark tables to be treated as
`:md-table` directives and ensures they are processed in a second pass
after the content index is fully populated.
This commit is contained in:
2026-07-10 15:55:45 +08:00
parent 89b32ef15e
commit d4ebca5fd0
7 changed files with 419 additions and 100 deletions
+42 -5
View File
@@ -15,6 +15,10 @@ import {
processBlocks,
type ProcessedBlocks,
} from "../completions/block-processor.js";
import {
scanDirectives,
type DirectiveScanResult,
} from "../completions/directive-scanner.js";
import type { StatSheet } from "../completions/types.js";
interface ContentIndex {
@@ -106,14 +110,16 @@ function labelFromId(id: string): string {
export function scanDirectory(dir: string): {
index: ContentIndex;
blocks: ProcessedBlocks;
directiveResults: DirectiveScanResult[];
} {
const index: ContentIndex = {};
const blocks: ProcessedBlocks = {
stats: [],
statTemplates: [],
sparkTables: [],
statSheets: [],
};
const directiveResults: DirectiveScanResult[] = [];
const mdFiles: { content: string; relPath: string }[] = [];
function scan(currentPath: string, relativePath: string) {
const entries = readdirSync(currentPath);
@@ -141,7 +147,7 @@ export function scanDirectory(dir: string): {
index[normalizedRelPath] = result.stripped;
blocks.stats.push(...result.blocks.stats);
blocks.statTemplates.push(...result.blocks.statTemplates);
blocks.sparkTables.push(...result.blocks.sparkTables);
mdFiles.push({ content: result.stripped, relPath: normalizedRelPath });
} else if (entry.endsWith(".sheet.svg")) {
index[normalizedRelPath] = content;
const id = entry.replace(/\.sheet\.svg$/i, "");
@@ -164,7 +170,32 @@ export function scanDirectory(dir: string): {
}
scan(dir, "");
return { index, blocks };
// ---- Directive scanning pass (after all blocks processed) ----
const posixDir = dir.split(sep).join("/");
for (const { content, relPath } of mdFiles) {
const fileDir = posixRelDir(relPath);
const result = scanDirectives(content, relPath, index, fileDir);
// Apply rewritten content back to index
index[relPath] = result.rewritten;
// Inject new index entries (inline CSV bodies)
for (const [key, value] of Object.entries(result.newIndexEntries)) {
index[key] = value;
}
directiveResults.push(result);
}
return { index, blocks, directiveResults };
}
/** Get the POSIX directory of a file path */
function posixRelDir(filePath: string): string {
const parts = filePath.split("/");
parts.pop();
return parts.join("/") || ".";
}
/**
@@ -310,9 +341,9 @@ export function createContentServer(
let collectedBlocks: ProcessedBlocks = {
stats: [],
statTemplates: [],
sparkTables: [],
statSheets: [],
};
let directiveResults: DirectiveScanResult[] = [];
let completionsIndex: CompletionsPayload = {
dice: [],
links: [],
@@ -324,7 +355,7 @@ export function createContentServer(
/** 从当前内容索引和已收集的块重新扫描补全数据 */
function recomputeCompletions(): void {
completionsIndex = scanCompletions(contentIndex, collectedBlocks);
completionsIndex = scanCompletions(contentIndex, collectedBlocks, directiveResults);
console.log(
`[completions] dice=${completionsIndex.dice.length} links=${completionsIndex.links.length} sparkTables=${completionsIndex.sparkTables.length} stats=${completionsIndex.stats.length} templates=${completionsIndex.statTemplates.length} sheets=${completionsIndex.statSheets.length}`,
);
@@ -335,6 +366,7 @@ export function createContentServer(
const scanResult = scanDirectory(contentDir);
contentIndex = scanResult.index;
collectedBlocks = scanResult.blocks;
directiveResults = scanResult.directiveResults;
console.log(`已索引 ${Object.keys(contentIndex).length} 个文件`);
recomputeCompletions();
@@ -363,12 +395,14 @@ export function createContentServer(
// Re-scan to get fresh blocks (simpler than per-file merge)
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
directiveResults = rescan.directiveResults;
recomputeCompletions();
} else {
contentIndex[relPath] = content;
if (relPath.endsWith(".sheet.svg")) {
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
directiveResults = rescan.directiveResults;
recomputeCompletions();
}
}
@@ -393,12 +427,14 @@ export function createContentServer(
contentIndex[relPath] = result.stripped;
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
directiveResults = rescan.directiveResults;
recomputeCompletions();
} else {
contentIndex[relPath] = content;
if (relPath.endsWith(".sheet.svg")) {
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
directiveResults = rescan.directiveResults;
recomputeCompletions();
}
}
@@ -421,6 +457,7 @@ export function createContentServer(
if (relPath.endsWith(".md") || relPath.endsWith(".sheet.svg")) {
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
directiveResults = rescan.directiveResults;
recomputeCompletions();
}
}