feat: inline md-table

This commit is contained in:
hypercross 2026-03-21 17:48:30 +08:00
parent 3d9617f06c
commit 6b411c2f24
2 changed files with 84 additions and 10 deletions

View File

@ -1,8 +1,8 @@
import { customElement, noShadowDOM } from 'solid-element'; import { customElement, noShadowDOM } from 'solid-element';
import { createSignal, For, Show, createEffect, createMemo, createResource } from 'solid-js'; import { createSignal, For, Show, createEffect, createMemo, createResource } from 'solid-js';
import { marked } from '../markdown'; import { marked } from '../markdown';
import { loadCSV, CSV, processVariables, isCSV } from './utils/csv-loader';
import { resolvePath } from './utils/path'; import { resolvePath } from './utils/path';
import {loadCSV, CSV, processVariables} from './utils/csv-loader';
export interface TableProps { export interface TableProps {
roll?: boolean; roll?: boolean;
@ -23,8 +23,8 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
const [bodyHtml, setBodyHtml] = createSignal(''); const [bodyHtml, setBodyHtml] = createSignal('');
let tabsContainer: HTMLDivElement | undefined; let tabsContainer: HTMLDivElement | undefined;
// 从 element 的 textContent 获取 CSV 路径 // 从 element 的 textContent 获取 CSV 路径或 inline CSV 数据
const src = element?.textContent?.trim() || ''; const rawContent = element?.textContent?.trim() || '';
// 隐藏原始文本内容 // 隐藏原始文本内容
if (element) { if (element) {
@ -35,11 +35,11 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
const articleEl = element?.closest('article[data-src]'); const articleEl = element?.closest('article[data-src]');
const articlePath = articleEl?.getAttribute('data-src') || ''; const articlePath = articleEl?.getAttribute('data-src') || '';
// 解析相对路径 // 如果是 inline CSV直接使用否则解析相对路径
const resolvedSrc = resolvePath(articlePath, src); const contentOrPath = isCSV(rawContent) ? rawContent : resolvePath(articlePath, rawContent);
// 使用 createResource 加载 CSV自动响应路径变化并避免重复加载 // 使用 createResource 加载 CSV自动响应路径变化并避免重复加载
const [csvData] = createResource(() => resolvedSrc, loadCSV); const [csvData] = createResource(() => contentOrPath, loadCSV);
// 当数据加载完成后更新 rows // 当数据加载完成后更新 rows
createEffect(() => { createEffect(() => {

View File

@ -31,13 +31,87 @@ 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>
* @param csvString CSV
* @param sourcePath
* @returns CSV
*/
export function parseCSVString<T = Record<string, string>>(csvString: string, sourcePath: string = 'inline'): CSV<T> {
// 解析 front matter
const { frontmatter, remainingContent } = parseFrontMatter(csvString);
const records = parse(remainingContent, {
columns: true,
comment: '#',
trim: true,
skipEmptyLines: true
});
const result = records as Record<string, string>[];
// 添加 front matter 到结果中
const csvResult = result as CSV<T>;
if (frontmatter) {
csvResult.frontmatter = frontmatter;
for(const each of result){
Object.assign(each, frontmatter);
}
}
csvResult.sourcePath = sourcePath;
return csvResult;
}
/** /**
* CSV * CSV
* @template T Record<string, string> * @template T Record<string, string>
* @param pathOrContent inline CSV
* @returns CSV
*/ */
export async function loadCSV<T = Record<string, string>>(path: string): Promise<CSV<T>> { export async function loadCSV<T = Record<string, string>>(pathOrContent: string): Promise<CSV<T>> {
// 尝试从索引获取 // 检测是否是 inline CSV 数据
const content = await getIndexedData(path); if (isCSV(pathOrContent)) {
return parseCSVString<T>(pathOrContent, 'inline');
}
// 从索引获取文件内容
const content = await getIndexedData(pathOrContent);
// 解析 front matter // 解析 front matter
const { frontmatter, remainingContent } = parseFrontMatter(content); const { frontmatter, remainingContent } = parseFrontMatter(content);
@ -58,7 +132,7 @@ export async function loadCSV<T = Record<string, string>>(path: string): Promise
Object.assign(each, frontmatter); Object.assign(each, frontmatter);
} }
} }
csvResult.sourcePath = path; csvResult.sourcePath = pathOrContent;
return csvResult; return csvResult;
} }