feat: csv front matter
This commit is contained in:
parent
1dca41b36a
commit
a2ac902129
|
|
@ -2,7 +2,7 @@ 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 { resolvePath } from './utils/path';
|
import { resolvePath } from './utils/path';
|
||||||
import { loadCSV } from './utils/csv-loader';
|
import {loadCSV, CSV, processVariables} from './utils/csv-loader';
|
||||||
|
|
||||||
export interface TableProps {
|
export interface TableProps {
|
||||||
roll?: boolean;
|
roll?: boolean;
|
||||||
|
|
@ -17,7 +17,7 @@ interface TableRow {
|
||||||
|
|
||||||
customElement('md-table', { roll: false, remix: false }, (props, { element }) => {
|
customElement('md-table', { roll: false, remix: false }, (props, { element }) => {
|
||||||
noShadowDOM();
|
noShadowDOM();
|
||||||
const [rows, setRows] = createSignal<TableRow[]>([]);
|
const [rows, setRows] = createSignal<CSV<TableRow>>([]);
|
||||||
const [activeTab, setActiveTab] = createSignal(0);
|
const [activeTab, setActiveTab] = createSignal(0);
|
||||||
const [activeGroup, setActiveGroup] = createSignal<string | null>(null);
|
const [activeGroup, setActiveGroup] = createSignal<string | null>(null);
|
||||||
const [bodyHtml, setBodyHtml] = createSignal('');
|
const [bodyHtml, setBodyHtml] = createSignal('');
|
||||||
|
|
@ -78,21 +78,8 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
|
||||||
|
|
||||||
// 处理 body 内容中的 {{prop}} 语法并解析 markdown
|
// 处理 body 内容中的 {{prop}} 语法并解析 markdown
|
||||||
const processBody = (body: string, currentRow: TableRow): string => {
|
const processBody = (body: string, currentRow: TableRow): string => {
|
||||||
let processedBody = body;
|
|
||||||
|
|
||||||
if (!props.remix) {
|
|
||||||
// 不启用 remix 时,只替换当前行的引用
|
|
||||||
processedBody = body.replace(/\{\{(\w+)\}\}/g, (_, key) => currentRow[key] || '');
|
|
||||||
} else {
|
|
||||||
// 启用 remix 时,每次引用使用随机行的内容
|
|
||||||
processedBody = body.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
||||||
const randomRow = rows()[Math.floor(Math.random() * rows().length)];
|
|
||||||
return randomRow?.[key] || '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 marked 解析 markdown
|
// 使用 marked 解析 markdown
|
||||||
return marked.parse(processedBody) as string;
|
return marked.parse(processVariables(body, currentRow, rows(), filteredRows(), props.remix)) as string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新 body 内容
|
// 更新 body 内容
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,61 @@
|
||||||
import { parse } from 'csv-parse/browser/esm/sync';
|
import { parse } from 'csv-parse/browser/esm/sync';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局缓存已加载的 CSV 内容
|
* 全局缓存已加载的 CSV 内容
|
||||||
*/
|
*/
|
||||||
const csvCache = new Map<string, Record<string, string>[]>();
|
const csvCache = new Map<string, Record<string, string>[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 front matter
|
||||||
|
* @param content 包含 front matter 的内容
|
||||||
|
* @returns 解析结果,包含 front matter 和剩余内容
|
||||||
|
*/
|
||||||
|
function parseFrontMatter(content: string): { frontmatter?: JSONObject; remainingContent: string } {
|
||||||
|
// 检查是否以 --- 开头
|
||||||
|
if (!content.trim().startsWith('---')) {
|
||||||
|
return { remainingContent: content };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割内容
|
||||||
|
const parts = content.split(/(?:^|\n)---\s*\n/);
|
||||||
|
|
||||||
|
// 至少需要三个部分:空字符串、front matter、剩余内容
|
||||||
|
if (parts.length < 3) {
|
||||||
|
return { remainingContent: content };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析 YAML front matter
|
||||||
|
const frontmatterStr = parts[1].trim();
|
||||||
|
const frontmatter = yaml.load(frontmatterStr) as JSONObject;
|
||||||
|
|
||||||
|
// 剩余内容是第三部分及之后的所有内容
|
||||||
|
const remainingContent = parts.slice(2).join('---\n').trimStart();
|
||||||
|
|
||||||
|
return { frontmatter, remainingContent };
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse front matter:', error);
|
||||||
|
return { remainingContent: content };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载 CSV 文件
|
* 加载 CSV 文件
|
||||||
* @template T 返回数据的类型,默认为 Record<string, string>
|
* @template T 返回数据的类型,默认为 Record<string, string>
|
||||||
*/
|
*/
|
||||||
export async function loadCSV<T = Record<string, string>>(path: string): Promise<T[]> {
|
export async function loadCSV<T = Record<string, string>>(path: string): Promise<CSV<T>> {
|
||||||
if (csvCache.has(path)) {
|
if (csvCache.has(path)) {
|
||||||
return csvCache.get(path)! as T[];
|
return csvCache.get(path)! as CSV<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(path);
|
const response = await fetch(path);
|
||||||
const content = await response.text();
|
const content = await response.text();
|
||||||
const records = parse(content, {
|
|
||||||
|
// 解析 front matter
|
||||||
|
const { frontmatter, remainingContent } = parseFrontMatter(content);
|
||||||
|
|
||||||
|
const records = parse(remainingContent, {
|
||||||
columns: true,
|
columns: true,
|
||||||
comment: '#',
|
comment: '#',
|
||||||
trim: true,
|
trim: true,
|
||||||
|
|
@ -24,6 +63,36 @@ export async function loadCSV<T = Record<string, string>>(path: string): Promise
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = records as Record<string, string>[];
|
const result = records as Record<string, string>[];
|
||||||
|
// 添加 front matter 到结果中
|
||||||
|
const csvResult = result as CSV<T>;
|
||||||
|
if (frontmatter) {
|
||||||
|
csvResult.frontmatter = frontmatter;
|
||||||
|
}
|
||||||
|
|
||||||
csvCache.set(path, result);
|
csvCache.set(path, result);
|
||||||
return result as T[];
|
return csvResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JSONData = JSONArray | JSONObject | string | number | boolean | null;
|
||||||
|
interface JSONArray extends Array<JSONData> {}
|
||||||
|
interface JSONObject extends Record<string, JSONData> {}
|
||||||
|
|
||||||
|
export type CSV<T> = T[] & {
|
||||||
|
frontmatter?: JSONObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processVariables<T extends JSONObject> (body: string, currentRow: T, csv: CSV<T>, filtered?: T[], remix?: boolean): string {
|
||||||
|
const rolled = filtered || csv;
|
||||||
|
|
||||||
|
function replaceProp(key: string) {
|
||||||
|
const row = remix ?
|
||||||
|
rolled[Math.floor(Math.random() * rolled.length)] :
|
||||||
|
currentRow;
|
||||||
|
const frontMatter = csv.frontmatter;
|
||||||
|
if(key in row) return row[key];
|
||||||
|
if(frontMatter && key in frontMatter) return frontMatter[key];
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return body.replace(/\{\{(\w+)\}\}/g, (_, key) => `${replaceProp(key)}`);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue