import { Component, createSignal, onMount, onCleanup, Show } from 'solid-js'; import { parseMarkdown } from '../markdown'; import { fetchData } from '../data-loader'; export interface ArticleProps { src: string; section?: string; // 指定要显示的标题(不含 #) onLoaded?: () => void; onError?: (error: Error) => void; } /** * 从 markdown 内容中提取指定标题下的内容 */ function extractSection(content: string, sectionTitle: string): string { // 匹配标题(支持 1-6 级标题) const sectionRegex = new RegExp( `^(#{1,6})\\s*${escapeRegExp(sectionTitle)}\\s*$`, 'im' ); const match = content.match(sectionRegex); if (!match) { // 没有找到指定标题,返回空字符串 return ''; } const headerLevel = match[1].length; const startIndex = match.index!; // 查找下一个同级或更高级别的标题 const nextHeaderRegex = new RegExp( `^#{1,${headerLevel}}\\s+.+$`, 'gm' ); // 从标题后开始搜索 nextHeaderRegex.lastIndex = startIndex + match[0].length; const nextMatch = nextHeaderRegex.exec(content); if (nextMatch) { // 返回从当前标题到下一个同级/更高级标题之间的内容 return content.slice(startIndex, nextMatch.index).trim(); } // 返回从当前标题到文件末尾的内容 return content.slice(startIndex).trim(); } /** * 转义正则表达式特殊字符 */ function escapeRegExp(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** * Article 组件 * 用于将特定 src 位置的 md 文件显示为 markdown 文章 */ export const Article: Component = (props) => { const [content, setContent] = createSignal(''); const [loading, setLoading] = createSignal(true); const [error, setError] = createSignal(null); let articleRef: HTMLArticleElement | undefined; onMount(async () => { setLoading(true); try { const text = await fetchData(props.src); // 如果指定了 section,提取对应内容 const finalContent = props.section ? extractSection(text, props.section) : text; setContent(finalContent); setLoading(false); props.onLoaded?.(); } catch (err) { const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); setLoading(false); props.onError?.(errorObj); } }); onCleanup(() => { // 清理时清空内容,触发内部组件的销毁 setContent(''); }); return (
加载中...
加载失败:{error()?.message}
); }; export default Article;