From 6ab6b76978189c2e19bed3b1e78fe1545871107b Mon Sep 17 00:00:00 2001 From: hypercross Date: Wed, 8 Jul 2026 16:38:19 +0800 Subject: [PATCH] feat: pass source path to markdown parser for icon resolution Pass the file's base path through the markdown parsing pipeline to allow the icon directive to resolve relative asset paths correctly using an absolute fallback chain. --- src/components/Article.tsx | 2 +- src/components/md-deck/CardLayer.tsx | 1 + src/components/md-embed.tsx | 5 +++- src/markdown/icon.ts | 36 ++++++++++++++++++++-------- src/markdown/index.ts | 5 ++-- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/components/Article.tsx b/src/components/Article.tsx index cb5ef1c..bd97d38 100644 --- a/src/components/Article.tsx +++ b/src/components/Article.tsx @@ -135,7 +135,7 @@ export const Article: Component = (props) => {
{props.children} diff --git a/src/components/md-deck/CardLayer.tsx b/src/components/md-deck/CardLayer.tsx index 8730fbc..5d4a751 100644 --- a/src/components/md-deck/CardLayer.tsx +++ b/src/components/md-deck/CardLayer.tsx @@ -27,6 +27,7 @@ export function CardLayer(props: CardLayerProps) { function renderLayerContent(content: string) { return parseMarkdown( processVariables(content, props.cardData, props.store.state.cards), + props.store.state.cards.sourcePath, ) as string; } diff --git a/src/components/md-embed.tsx b/src/components/md-embed.tsx index 1b6db7c..f8b08a9 100644 --- a/src/components/md-embed.tsx +++ b/src/components/md-embed.tsx @@ -53,7 +53,10 @@ customElement("md-embed", { headingBase: 0 }, (props, { element }) => { {/* biome-ignore lint/security/noDangerouslySetInnerHtml: markdown render */} -
+
); diff --git a/src/markdown/icon.ts b/src/markdown/icon.ts index 37b1cb0..64febd2 100644 --- a/src/markdown/icon.ts +++ b/src/markdown/icon.ts @@ -1,25 +1,41 @@ /** * marked-directive 扩展:支持 :[icon] 和 :[icon.ext] 行内图标语法 * - * 生成的 CSS 包含从当前目录向上查找的 fallback 链: - * url('./assets/icon.png'), - * url('../assets/icon.png'), - * url('../../assets/icon.png'), - * ... + * 生成的 CSS 包含从文件所在目录向上查找的 fallback 链: + * url('/pages/sub/deep/assets/icon.png'), + * url('/pages/sub/assets/icon.png'), + * url('/pages/assets/icon.png'), + * url('/assets/icon.png') * * 支持的扩展名:.svg, .png, .gif, .jpg, .jpeg, .webp(默认 .png) */ const SUPPORTED_EXTENSIONS = ["svg", "png", "gif", "jpg", "jpeg", "webp"]; -/** 生成多层 fallback 路径,向上查找最多 10 级 */ +let globalBasePath: string | undefined; + +/** 设置当前 markdown 文件路径,用于解析 ./assets */ +export function setIconBase(basePath: string | undefined) { + globalBasePath = basePath; +} + +/** 生成多层 fallback 路径,从文件所在目录向上查找 */ function buildIconSrc(iconName: string, extension: string): string { - const levels = 10; + if (!globalBasePath) return ""; + + // 取文件所在目录,如 /pages/sub/deep/file.md → /pages/sub/deep + const dir = + globalBasePath.substring(0, globalBasePath.lastIndexOf("/")) || "/"; + const parts = dir.split("/").filter(Boolean); + const urls: string[] = []; - for (let i = 0; i < levels; i++) { - const prefix = i === 0 ? "./" : "../".repeat(i); - urls.push(`url('${prefix}assets/${iconName}.${extension}')`); + // 从当前目录开始,逐级向上 + for (let i = parts.length; i >= 0; i--) { + const prefix = "/" + parts.slice(0, i).join("/"); + const base = prefix === "/" ? "/assets" : `${prefix}/assets`; + urls.push(`url('${base}/${iconName}.${extension}')`); } + return urls.join(", "); } diff --git a/src/markdown/index.ts b/src/markdown/index.ts index 22a968c..7e64722 100644 --- a/src/markdown/index.ts +++ b/src/markdown/index.ts @@ -6,7 +6,7 @@ import markedTable from "./table"; import { gfmHeadingId } from "marked-gfm-heading-id"; import markedColumns from "./columns"; import markedCodeBlockYamlTag from "./code-block-yaml-tag"; -import { iconDirective } from "./icon"; +import { iconDirective, setIconBase } from "./icon"; // 使用 marked-directive 来支持指令语法 const marked = new Marked() @@ -33,7 +33,8 @@ const marked = new Marked() }, ); -export function parseMarkdown(content: string): string { +export function parseMarkdown(content: string, basePath?: string): string { + setIconBase(basePath); return marked.parse(content.trimStart()) as string; }