From f734da6acd1c3d3af9e4d17eee822fca6ded9c7d Mon Sep 17 00:00:00 2001 From: hyper Date: Thu, 25 Jun 2026 20:43:40 +0800 Subject: [PATCH] feat(viewer): Add md-embed component --- content/index.md | 2 ++ content/style-test.md | 1 + src/components/index.ts | 2 ++ src/components/md-embed.tsx | 66 +++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 content/style-test.md create mode 100644 src/components/md-embed.tsx diff --git a/content/index.md b/content/index.md index c46c9d1..39ec7c1 100644 --- a/content/index.md +++ b/content/index.md @@ -53,3 +53,5 @@ bler > [!CAUTION] > Negative potential consequences of an action. + +:md-embed[./style-test.md] diff --git a/content/style-test.md b/content/style-test.md new file mode 100644 index 0000000..ffc6550 --- /dev/null +++ b/content/style-test.md @@ -0,0 +1 @@ +:md-font[Pacifico]{source=google} hello? diff --git a/src/components/index.ts b/src/components/index.ts index 11b8faf..765db39 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -5,6 +5,7 @@ import "./md-link"; import "./md-pins"; import "./md-bg"; import "./md-font"; +import "./md-embed"; import "./md-deck"; import "./md-commander/index"; import "./md-yarn-spinner"; @@ -23,6 +24,7 @@ export type { DiceProps } from "./md-dice"; export type { TableProps } from "./md-table"; export type { BgProps } from "./md-bg"; export type { FontProps } from "./md-font"; +export type { EmbedProps } from "./md-embed"; export type { YarnSpinnerProps } from "./md-yarn-spinner"; export type { TokenProps } from "./md-token"; diff --git a/src/components/md-embed.tsx b/src/components/md-embed.tsx new file mode 100644 index 0000000..b5fdba4 --- /dev/null +++ b/src/components/md-embed.tsx @@ -0,0 +1,66 @@ +import { customElement, noShadowDOM } from "solid-element"; +import { createResource, Show, createMemo, createEffect } from "solid-js"; +import { getIndexedData } from "../data-loader/file-index"; +import { extractSection } from "../data-loader"; +import { parseMarkdown } from "../markdown"; +import { resolvePath } from "./utils/path"; +import mermaid from "mermaid"; + +export interface EmbedProps { + /** 是否在嵌入内容的每一级标题前提升 heading 层级 */ + headingBase?: number; +} + +customElement("md-embed", { headingBase: 0 }, (props, { element }) => { + noShadowDOM(); + + const rawPath = element?.textContent?.trim() || ""; + if (element) element.textContent = ""; + + // 解析 section(支持 path#section 语法) + const hashIndex = rawPath.indexOf("#"); + const filePath = hashIndex >= 0 ? rawPath.slice(0, hashIndex) : rawPath; + const section = hashIndex >= 0 ? rawPath.slice(hashIndex + 1) : undefined; + + // 从父节点 article 获取路径上下文 + const articleEl = element?.closest("article[data-src]") as HTMLElement; + const articlePath = articleEl?.getAttribute("data-src") || ""; + const resolvedPath = resolvePath(articlePath, filePath); + + // 计算嵌入文件的 iconPrefix,用 createMemo 包装避免每个 embed 都重新计算 + const iconPrefix = createMemo(() => resolvePath(resolvedPath, "./assets")); + + const [content] = createResource( + () => ({ path: resolvedPath, section }), + async ({ path, section }) => { + const text = await getIndexedData(path); + return section ? extractSection(text, section) : text; + }, + ); + + createEffect(() => { + if (content() && !content.loading) { + void mermaid.run(); + } + }); + + return ( +
+ +
加载中...
+
+ +
+ 加载失败:{content.error?.message || "未知错误"} +
+
+ + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: markdown render */} +
+ +
+ ); +});