diff --git a/src/components/md-table.tsx b/src/components/md-table.tsx index 49efc05..dd4cb7d 100644 --- a/src/components/md-table.tsx +++ b/src/components/md-table.tsx @@ -1,8 +1,8 @@ import { customElement, noShadowDOM } from 'solid-element'; import { createSignal, For, Show, createEffect, createMemo, createResource } from 'solid-js'; import { marked } from '../markdown'; +import { loadCSV, CSV, processVariables, isCSV } from './utils/csv-loader'; import { resolvePath } from './utils/path'; -import {loadCSV, CSV, processVariables} from './utils/csv-loader'; export interface TableProps { roll?: boolean; @@ -23,8 +23,8 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) => const [bodyHtml, setBodyHtml] = createSignal(''); let tabsContainer: HTMLDivElement | undefined; - // 从 element 的 textContent 获取 CSV 路径 - const src = element?.textContent?.trim() || ''; + // 从 element 的 textContent 获取 CSV 路径或 inline CSV 数据 + const rawContent = element?.textContent?.trim() || ''; // 隐藏原始文本内容 if (element) { @@ -35,11 +35,11 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) => const articleEl = element?.closest('article[data-src]'); const articlePath = articleEl?.getAttribute('data-src') || ''; - // 解析相对路径 - const resolvedSrc = resolvePath(articlePath, src); + // 如果是 inline CSV,直接使用;否则解析相对路径 + const contentOrPath = isCSV(rawContent) ? rawContent : resolvePath(articlePath, rawContent); // 使用 createResource 加载 CSV,自动响应路径变化并避免重复加载 - const [csvData] = createResource(() => resolvedSrc, loadCSV); + const [csvData] = createResource(() => contentOrPath, loadCSV); // 当数据加载完成后更新 rows createEffect(() => { diff --git a/src/components/utils/csv-loader.ts b/src/components/utils/csv-loader.ts index 92e4357..73fd5c2 100644 --- a/src/components/utils/csv-loader.ts +++ b/src/components/utils/csv-loader.ts @@ -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 + * @param csvString CSV 字符串内容 + * @param sourcePath 源路径标识(用于标记数据来源) + * @returns 解析后的 CSV 数据 + */ +export function parseCSVString>(csvString: string, sourcePath: string = 'inline'): CSV { + // 解析 front matter + const { frontmatter, remainingContent } = parseFrontMatter(csvString); + + const records = parse(remainingContent, { + columns: true, + comment: '#', + trim: true, + skipEmptyLines: true + }); + + const result = records as Record[]; + // 添加 front matter 到结果中 + const csvResult = result as CSV; + if (frontmatter) { + csvResult.frontmatter = frontmatter; + for(const each of result){ + Object.assign(each, frontmatter); + } + } + csvResult.sourcePath = sourcePath; + return csvResult; +} + /** * 加载 CSV 文件 * @template T 返回数据的类型,默认为 Record + * @param pathOrContent 文件路径或 inline CSV 字符串 + * @returns 解析后的 CSV 数据 */ -export async function loadCSV>(path: string): Promise> { - // 尝试从索引获取 - const content = await getIndexedData(path); +export async function loadCSV>(pathOrContent: string): Promise> { + // 检测是否是 inline CSV 数据 + if (isCSV(pathOrContent)) { + return parseCSVString(pathOrContent, 'inline'); + } + + // 从索引获取文件内容 + const content = await getIndexedData(pathOrContent); // 解析 front matter const { frontmatter, remainingContent } = parseFrontMatter(content); @@ -58,7 +132,7 @@ export async function loadCSV>(path: string): Promise Object.assign(each, frontmatter); } } - csvResult.sourcePath = path; + csvResult.sourcePath = pathOrContent; return csvResult; }