feat: section link
This commit is contained in:
parent
a250762ebe
commit
dd4068926c
|
|
@ -4,10 +4,56 @@ import { fetchData } from '../data-loader';
|
||||||
|
|
||||||
export interface ArticleProps {
|
export interface ArticleProps {
|
||||||
src: string;
|
src: string;
|
||||||
|
section?: string; // 指定要显示的标题(不含 #)
|
||||||
onLoaded?: () => void;
|
onLoaded?: () => void;
|
||||||
onError?: (error: Error) => 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 组件
|
* Article 组件
|
||||||
* 用于将特定 src 位置的 md 文件显示为 markdown 文章
|
* 用于将特定 src 位置的 md 文件显示为 markdown 文章
|
||||||
|
|
@ -23,7 +69,11 @@ export const Article: Component<ArticleProps> = (props) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const text = await fetchData(props.src);
|
const text = await fetchData(props.src);
|
||||||
setContent(text);
|
// 如果指定了 section,提取对应内容
|
||||||
|
const finalContent = props.section
|
||||||
|
? extractSection(text, props.section)
|
||||||
|
: text;
|
||||||
|
setContent(finalContent);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
props.onLoaded?.();
|
props.onLoaded?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,14 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
let articleContainer: HTMLDivElement | undefined;
|
let articleContainer: HTMLDivElement | undefined;
|
||||||
let disposeArticle: (() => void) | null = null;
|
let disposeArticle: (() => void) | null = null;
|
||||||
|
|
||||||
// 从 element 的 textContent 获取链接目标
|
// 从 element 的 textContent 获取链接目标(支持 path#section 语法)
|
||||||
const rawLinkSrc = element?.textContent?.trim() || "";
|
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) {
|
if (element) {
|
||||||
element.textContent = "";
|
element.textContent = "";
|
||||||
|
|
@ -31,7 +36,7 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
return baseDir + relative;
|
return baseDir + relative;
|
||||||
};
|
};
|
||||||
|
|
||||||
const linkSrc = resolvePath(articlePath, rawLinkSrc);
|
const linkSrc = resolvePath(articlePath, path);
|
||||||
|
|
||||||
// 查找包含此元素的 p 标签
|
// 查找包含此元素的 p 标签
|
||||||
const parentP = element?.closest("p");
|
const parentP = element?.closest("p");
|
||||||
|
|
@ -61,6 +66,7 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
disposeArticle = render(() => (
|
disposeArticle = render(() => (
|
||||||
<Article
|
<Article
|
||||||
src={linkSrc}
|
src={linkSrc}
|
||||||
|
section={section}
|
||||||
onLoaded={() => console.log("Article loaded:", linkSrc)}
|
onLoaded={() => console.log("Article loaded:", linkSrc)}
|
||||||
onError={(err) => console.error("Article error:", err)}
|
onError={(err) => console.error("Article error:", err)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue