feat: section link
This commit is contained in:
parent
a250762ebe
commit
dd4068926c
|
|
@ -4,10 +4,56 @@ 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 文章
|
||||
|
|
@ -23,7 +69,11 @@ export const Article: Component<ArticleProps> = (props) => {
|
|||
setLoading(true);
|
||||
try {
|
||||
const text = await fetchData(props.src);
|
||||
setContent(text);
|
||||
// 如果指定了 section,提取对应内容
|
||||
const finalContent = props.section
|
||||
? extractSection(text, props.section)
|
||||
: text;
|
||||
setContent(finalContent);
|
||||
setLoading(false);
|
||||
props.onLoaded?.();
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@ customElement("md-link", {}, (props, { element }) => {
|
|||
let articleContainer: HTMLDivElement | undefined;
|
||||
let disposeArticle: (() => void) | null = null;
|
||||
|
||||
// 从 element 的 textContent 获取链接目标
|
||||
// 从 element 的 textContent 获取链接目标(支持 path#section 语法)
|
||||
const rawLinkSrc = element?.textContent?.trim() || "";
|
||||
|
||||
// 解析 section(支持 path#section 语法)
|
||||
const hashIndex = rawLinkSrc.indexOf('#');
|
||||
const path = hashIndex >= 0 ? rawLinkSrc.slice(0, hashIndex) : rawLinkSrc;
|
||||
const section = hashIndex >= 0 ? rawLinkSrc.slice(hashIndex + 1) : undefined;
|
||||
|
||||
// 隐藏原始文本内容
|
||||
if (element) {
|
||||
element.textContent = "";
|
||||
|
|
@ -31,7 +36,7 @@ customElement("md-link", {}, (props, { element }) => {
|
|||
return baseDir + relative;
|
||||
};
|
||||
|
||||
const linkSrc = resolvePath(articlePath, rawLinkSrc);
|
||||
const linkSrc = resolvePath(articlePath, path);
|
||||
|
||||
// 查找包含此元素的 p 标签
|
||||
const parentP = element?.closest("p");
|
||||
|
|
@ -61,6 +66,7 @@ customElement("md-link", {}, (props, { element }) => {
|
|||
disposeArticle = render(() => (
|
||||
<Article
|
||||
src={linkSrc}
|
||||
section={section}
|
||||
onLoaded={() => console.log("Article loaded:", linkSrc)}
|
||||
onError={(err) => console.error("Article error:", err)}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue