refactor: consolidate block processing logic

Introduce a centralized `block-processor` and `block-scanner` to
handle markdown fenced code blocks. This replaces the previous
`inline-blocks.ts` and individual completion sources with a unified
approach that handles block stripping, directive replacement, and
metadata extraction (stats, templates, and spark tables) in a single
pass.
This commit is contained in:
2026-07-09 10:56:54 +08:00
parent 1c69bf394c
commit a4cfcde613
10 changed files with 358 additions and 379 deletions
+43 -25
View File
@@ -11,7 +11,10 @@ import {
scanCompletions,
type CompletionsPayload,
} from "../completions/index.js";
import { extractInlineBlocks } from "../inline-blocks.js";
import {
processBlocks,
type ProcessedBlocks,
} from "../completions/block-processor.js";
interface ContentIndex {
[path: string]: string;
@@ -83,8 +86,16 @@ function getBestIP(): string {
/**
* 扫描目录内的 .md 文件,生成索引
*/
export function scanDirectory(dir: string): ContentIndex {
export function scanDirectory(dir: string): {
index: ContentIndex;
blocks: ProcessedBlocks;
} {
const index: ContentIndex = {};
const blocks: ProcessedBlocks = {
stats: [],
statTemplates: [],
sparkTables: [],
};
function scan(currentPath: string, relativePath: string) {
const entries = readdirSync(currentPath);
@@ -107,12 +118,11 @@ export function scanDirectory(dir: string): ContentIndex {
try {
const content = readFileSync(fullPath, "utf-8");
if (entry.endsWith(".md")) {
const stripped = extractInlineBlocks(
content,
normalizedRelPath,
index,
);
index[normalizedRelPath] = stripped;
const result = processBlocks(content, normalizedRelPath, index);
index[normalizedRelPath] = result.stripped;
blocks.stats.push(...result.blocks.stats);
blocks.statTemplates.push(...result.blocks.statTemplates);
blocks.sparkTables.push(...result.blocks.sparkTables);
} else {
index[normalizedRelPath] = content;
}
@@ -124,7 +134,7 @@ export function scanDirectory(dir: string): ContentIndex {
}
scan(dir, "");
return index;
return { index, blocks };
}
/**
@@ -267,6 +277,11 @@ export function createContentServer(
host: string = "0.0.0.0",
): ContentServer {
let contentIndex: ContentIndex = {};
let collectedBlocks: ProcessedBlocks = {
stats: [],
statTemplates: [],
sparkTables: [],
};
let completionsIndex: CompletionsPayload = {
dice: [],
links: [],
@@ -275,9 +290,9 @@ export function createContentServer(
statTemplates: [],
};
/** Re-scan completions from current content index (cached) */
/** Re-scan completions from current content index and collected blocks */
function recomputeCompletions(): void {
completionsIndex = scanCompletions(contentIndex);
completionsIndex = scanCompletions(contentIndex, collectedBlocks);
console.log(
`[completions] dice=${completionsIndex.dice.length} links=${completionsIndex.links.length} sparkTables=${completionsIndex.sparkTables.length} stats=${completionsIndex.stats.length} templates=${completionsIndex.statTemplates.length}`,
);
@@ -285,7 +300,9 @@ export function createContentServer(
// 扫描内容目录生成索引
console.log("扫描内容目录...");
contentIndex = scanDirectory(contentDir);
const scanResult = scanDirectory(contentDir);
contentIndex = scanResult.index;
collectedBlocks = scanResult.blocks;
console.log(`已索引 ${Object.keys(contentIndex).length} 个文件`);
recomputeCompletions();
@@ -308,12 +325,11 @@ export function createContentServer(
const content = readFileSync(path, "utf-8");
const relPath = "/" + relative(contentDir, path).split(sep).join("/");
if (relPath.endsWith(".md")) {
const stripped = extractInlineBlocks(
content,
relPath,
contentIndex,
);
contentIndex[relPath] = stripped;
const result = processBlocks(content, relPath, contentIndex);
contentIndex[relPath] = result.stripped;
// Re-scan to get fresh blocks (simpler than per-file merge)
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
recomputeCompletions();
} else {
contentIndex[relPath] = content;
@@ -334,12 +350,10 @@ export function createContentServer(
const content = readFileSync(path, "utf-8");
const relPath = "/" + relative(contentDir, path).split(sep).join("/");
if (relPath.endsWith(".md")) {
const stripped = extractInlineBlocks(
content,
relPath,
contentIndex,
);
contentIndex[relPath] = stripped;
const result = processBlocks(content, relPath, contentIndex);
contentIndex[relPath] = result.stripped;
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
recomputeCompletions();
} else {
contentIndex[relPath] = content;
@@ -359,7 +373,11 @@ export function createContentServer(
const relPath = "/" + relative(contentDir, path).split(sep).join("/");
delete contentIndex[relPath];
console.log(`[删除] ${relPath}`);
if (relPath.endsWith(".md")) recomputeCompletions();
if (relPath.endsWith(".md")) {
const rescan = scanDirectory(contentDir);
collectedBlocks = rescan.blocks;
recomputeCompletions();
}
}
});