ttrpg-tools/src/components/Article.tsx

68 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Component, createSignal, createEffect, onCleanup, Show, createResource, createMemo } from 'solid-js';
import { parseMarkdown } from '../markdown';
import { extractSection } from '../data-loader';
import mermaid from 'mermaid';
import {getIndexedData} from "../data-loader/file-index";
import { resolvePath } from './utils/path';
export interface ArticleProps {
src: string;
section?: string; // 指定要显示的标题(不含 #
iconPath?: string; // 图标路径前缀,默认为 "./assets",空字符串表示禁用
onLoaded?: () => void;
onError?: (error: Error) => void;
class?: string; // 额外的 class 用于样式控制
}
async function fetchArticleContent(params: { src: string; section?: string }): Promise<string> {
const text = await getIndexedData(params.src);
// 如果指定了 section提取对应内容
return params.section ? extractSection(text, params.section) : text;
}
/**
* Article 组件
* 用于将特定 src 位置的 md 文件显示为 markdown 文章
*/
export const Article: Component<ArticleProps> = (props) => {
const [content, { refetch }] = createResource(
() => ({ src: props.src, section: props.section }),
fetchArticleContent
);
// 解析 iconPath默认为 "./assets",空字符串表示禁用
const iconPrefix = createMemo(() => {
if (props.iconPath === '') return undefined; // 空字符串禁用图标前缀
return resolvePath(props.src, props.iconPath ?? "./assets");
});
createEffect(() => {
const data = content();
if (data) {
props.onLoaded?.();
// 内容加载完成后,渲染 mermaid 图表
void mermaid.run();
}
});
onCleanup(() => {
// 清理时清空内容,触发内部组件的销毁
});
return (
<article class={`prose ${props.class || ''}`} data-src={props.src}>
<Show when={content.loading}>
<div class="text-gray-500">...</div>
</Show>
<Show when={content.error}>
<div class="text-red-500">{content.error?.message}</div>
</Show>
<Show when={!content.loading && !content.error && content()}>
<div class="relative" innerHTML={parseMarkdown(content()!, iconPrefix())} />
</Show>
</article>
);
};
export default Article;