From d23d209522c0805c5d6d6e949433768dc0b40416 Mon Sep 17 00:00:00 2001 From: thzxx Date: Wed, 27 May 2026 21:13:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=A2=84=E8=A7=88=E6=97=B6=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E7=89=87=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: 重构后丢失了原始版本的图片路径转换逻辑, 相对路径图片(如 ![](./images/logo.png))无法显示。 修复: - 新增 rehypeFixImages 自定义 rehype 插件 - 在 sanitize 之后、highlight 之前执行 - 将相对路径 src 转换为 file:// 绝对路径 - 基于当前文件路径计算目录,拼接图片路径 - renderMarkdown 新增 filePath 可选参数 - Preview 组件传入 activeTab.filePath 示例: Markdown: ![](./images/logo.png) 文件路径: /project/docs/README.md 转换后: file:///project/docs/images/logo.png --- src/renderer/components/Preview/Preview.tsx | 2 +- src/renderer/lib/markdown.ts | 64 ++++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/renderer/components/Preview/Preview.tsx b/src/renderer/components/Preview/Preview.tsx index d637406..70ebd20 100644 --- a/src/renderer/components/Preview/Preview.tsx +++ b/src/renderer/components/Preview/Preview.tsx @@ -19,7 +19,7 @@ export function Preview() { } const requestId = ++requestIdRef.current - renderMarkdown(activeTab.content).then(result => { + renderMarkdown(activeTab.content, activeTab.filePath).then(result => { // M-07: 只有当 requestId 仍为最新时才更新 if (requestId === requestIdRef.current) { setHtml(result) diff --git a/src/renderer/lib/markdown.ts b/src/renderer/lib/markdown.ts index 9c584b5..f80ec09 100644 --- a/src/renderer/lib/markdown.ts +++ b/src/renderer/lib/markdown.ts @@ -1,4 +1,4 @@ -import { unified } from 'unified' +import { unified, type Plugin } from 'unified' import remarkParse from 'remark-parse' import remarkGfm from 'remark-gfm' import remarkRehype from 'remark-rehype' @@ -6,24 +6,58 @@ import rehypeRaw from 'rehype-raw' import rehypeSanitize, { defaultSchema } from 'rehype-sanitize' import rehypeStringify from 'rehype-stringify' import rehypeHighlight from 'rehype-highlight' +import type { Element, Root } from 'hast' -const processor = unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkRehype, { allowDangerousHtml: true }) - .use(rehypeRaw) - .use(rehypeSanitize, { - ...defaultSchema, - attributes: { - ...defaultSchema.attributes, - img: [...(defaultSchema.attributes?.img ?? []), ['src']] +// 自定义 rehype 插件:将相对路径图片转为 file:// 绝对路径 +function rehypeFixImages(filePath: string | null): Plugin<[], Root> { + return () => (tree: Root) => { + if (!filePath) return + + // 获取文件所在目录 + const dir = filePath.replace(/[/\\][^/\\]+$/, '') + const sep = dir.includes('\\') ? '\\' : '/' + + function visit(node: Element | Root): void { + if (!node.children) return + for (const child of node.children) { + if (child.type === 'element' && child.tagName === 'img') { + const src = child.properties?.src as string | undefined + if (src && !src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('data:') && !src.startsWith('file://')) { + // 相对路径转绝对路径 + child.properties = { + ...child.properties, + src: 'file://' + (dir + sep + src).replace(/\\/g, '/') + } + } + } + if (child.type === 'element') { + visit(child as Element) + } + } } - }) - .use(rehypeHighlight) - .use(rehypeStringify) -export async function renderMarkdown(content: string): Promise { + visit(tree) + } +} + +export async function renderMarkdown(content: string, filePath?: string | null): Promise { try { + const processor = unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeRaw) + .use(rehypeSanitize, { + ...defaultSchema, + attributes: { + ...defaultSchema.attributes, + img: [...(defaultSchema.attributes?.img ?? []), ['src']] + } + }) + .use(rehypeFixImages(filePath ?? null)) + .use(rehypeHighlight) + .use(rehypeStringify) + const result = await processor.process(content) return String(result) } catch (e) {