refactor: remove implicit content sniffing and table conversion

- Split loadCSV into parseCSVString (content) and loadCSVFromPath
  (path); drop isCSV/looksLikeCsv heuristics
- Delete coerceSparkTables and markedTable label-header magic;
  plain markdown tables now render as plain tables
- Add explicit markdown role=spark-table fence syntax that converts
  pipe tables to CSV at scan time, with dice-header validation
- Map ESM-only github-slugger and csv-parse browser build to CJS in
  jest config; add content-registry tests
This commit is contained in:
2026-09-08 21:33:12 +08:00
parent 5061c4d19d
commit 256c685f6c
13 changed files with 216 additions and 253 deletions
@@ -2,7 +2,7 @@ import { createStore } from "solid-js/store";
import type { MdCommanderCommand, MdCommanderCommandMap } from "../types";
import { setupHelpCommand, clearCommand, rollCommand, trackCommand, untrackCommand, listTrackCommand } from "../commands";
import {resolvePath} from "../../utils/path";
import {loadCSV} from "../../utils/csv-loader";
import {loadCSVFromPath} from "../../utils/csv-loader";
const defaultCommands: MdCommanderCommandMap = {
help: setupHelpCommand({}),
@@ -111,7 +111,7 @@ export async function loadCommandTemplatesFromCSV(
setCommandsError(undefined);
try {
const csv = await loadCSV<CommandTemplateRow>(resolvePath(articlePath, path));
const csv = await loadCSVFromPath<CommandTemplateRow>(resolvePath(articlePath, path));
// 按命令分组模板
const templatesByCommand = new Map<string, CommandTemplateRow[]>();
+2 -2
View File
@@ -1,7 +1,7 @@
import { createStore } from "solid-js/store";
import yaml from "js-yaml";
import { calculateDimensions } from "./dimensions";
import { loadCSV, CSV } from "../../utils/csv-loader";
import { loadCSVFromPath, CSV } from "../../utils/csv-loader";
import { formatLayers } from "./layer-parser";
import * as layerCrud from "./layer-crud";
import type {
@@ -461,7 +461,7 @@ export function createDeckStore(initialSrc: string = ""): DeckStore {
setState({ isLoading: true, error: null, src: path, rawSrc: rawSrc });
try {
const data = await loadCSV(path);
const data = await loadCSVFromPath(path);
if (data.length === 0) {
setState({
+2 -2
View File
@@ -8,7 +8,7 @@ import {
createResource,
} from "solid-js";
import { parseMarkdown } from "../markdown";
import { loadCSV, CSV, processVariables } from "./utils/csv-loader";
import { parseCSVString, CSV, processVariables } from "./utils/csv-loader";
import { resolveContentRef } from "./utils/resolve-content";
import {
areAllLabelsNumeric,
@@ -59,7 +59,7 @@ customElement(
if (content === null) {
throw new Error(`Failed to resolve table content: "${ref}"`);
}
return loadCSV(content);
return parseCSVString(content);
},
);
+4 -46
View File
@@ -31,42 +31,6 @@ function parseFrontMatter(content: string): { frontmatter?: JSONObject; remainin
}
}
/**
* 检测字符串是否是 CSV 格式
* @param str 待检测的字符串
* @returns 如果是 CSV 格式返回 true
*/
export function isCSV(str: string): boolean {
const trimmed = str.trim();
// 检查是否以 YAML front matter 开头
if (trimmed.startsWith('---\n') || trimmed.startsWith('---\r\n')) {
return true;
}
// 检查是否包含 CSV 特征:多行且有分隔符
const lines = trimmed.split(/\r?\n/).filter(line => line.trim() !== '');
if (lines.length < 2) {
return false;
}
// 检测常见 CSV 分隔符
const separators = [',', '\t', ';', '|'];
const firstLine = lines[0];
for (const sep of separators) {
if (firstLine.includes(sep)) {
// 检查其他行是否也有相同的分隔符
const hasSeparatorInOtherLines = lines.slice(1).some(line => line.includes(sep));
if (hasSeparatorInOtherLines) {
return true;
}
}
}
return false;
}
/**
* 解析 CSV 字符串
* @template T 返回数据的类型,默认为 Record<string, string>
@@ -102,18 +66,12 @@ export function parseCSVString<T = Record<string, string>>(csvString: string, so
/**
* 加载 CSV 文件
* @template T 返回数据的类型,默认为 Record<string, string>
* @param pathOrContent 文件路径或 inline CSV 字符串
* @param path 文件路径(通过 file-index 获取内容)
* @returns 解析后的 CSV 数据
*/
export async function loadCSV<T = Record<string, string>>(pathOrContent: string): Promise<CSV<T>> {
// 检测是否是 inline CSV 数据
if (isCSV(pathOrContent)) {
return parseCSVString<T>(pathOrContent, 'inline');
}
// 从索引获取文件内容
const content = await getIndexedData(pathOrContent);
return parseCSVString<T>(content, pathOrContent);
export async function loadCSVFromPath<T = Record<string, string>>(path: string): Promise<CSV<T>> {
const content = await getIndexedData(path);
return parseCSVString<T>(content, path);
}
type JSONData = JSONArray | JSONObject | string | number | boolean | null;