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.
This commit is contained in:
hypercross 2026-07-08 16:38:19 +08:00
parent 2dba19e1f8
commit 6ab6b76978
5 changed files with 35 additions and 14 deletions

View File

@ -135,7 +135,7 @@ export const Article: Component<ArticleProps & ParentProps> = (props) => {
<div
class="relative"
ref={setContentDom}
innerHTML={parseMarkdown(content()!)}
innerHTML={parseMarkdown(content()!, props.src)}
/>
{props.children}
</ArticleDomCtx.Provider>

View File

@ -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;
}

View File

@ -53,7 +53,10 @@ customElement("md-embed", { headingBase: 0 }, (props, { element }) => {
</Show>
<Show when={!content.loading && !content.error && content()}>
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: markdown render */}
<div class="prose" innerHTML={parseMarkdown(content()!)} />
<div
class="prose"
innerHTML={parseMarkdown(content()!, resolvedPath)}
/>
</Show>
</div>
);

View File

@ -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 级 */
function buildIconSrc(iconName: string, extension: string): string {
const levels = 10;
const urls: string[] = [];
for (let i = 0; i < levels; i++) {
const prefix = i === 0 ? "./" : "../".repeat(i);
urls.push(`url('${prefix}assets/${iconName}.${extension}')`);
let globalBasePath: string | undefined;
/** 设置当前 markdown 文件路径,用于解析 ./assets */
export function setIconBase(basePath: string | undefined) {
globalBasePath = basePath;
}
/** 生成多层 fallback 路径,从文件所在目录向上查找 */
function buildIconSrc(iconName: string, extension: string): string {
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 = 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(", ");
}

View File

@ -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;
}