fix: 预览时本地图片不显示
问题: 重构后丢失了原始版本的图片路径转换逻辑,
相对路径图片(如 )无法显示。
修复:
- 新增 rehypeFixImages 自定义 rehype 插件
- 在 sanitize 之后、highlight 之前执行
- 将相对路径 src 转换为 file:// 绝对路径
- 基于当前文件路径计算目录,拼接图片路径
- renderMarkdown 新增 filePath 可选参数
- Preview 组件传入 activeTab.filePath
示例:
Markdown: 
文件路径: /project/docs/README.md
转换后: file:///project/docs/images/logo.png
This commit is contained in:
@@ -19,7 +19,7 @@ export function Preview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestId = ++requestIdRef.current
|
const requestId = ++requestIdRef.current
|
||||||
renderMarkdown(activeTab.content).then(result => {
|
renderMarkdown(activeTab.content, activeTab.filePath).then(result => {
|
||||||
// M-07: 只有当 requestId 仍为最新时才更新
|
// M-07: 只有当 requestId 仍为最新时才更新
|
||||||
if (requestId === requestIdRef.current) {
|
if (requestId === requestIdRef.current) {
|
||||||
setHtml(result)
|
setHtml(result)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { unified } from 'unified'
|
import { unified, type Plugin } from 'unified'
|
||||||
import remarkParse from 'remark-parse'
|
import remarkParse from 'remark-parse'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
import remarkRehype from 'remark-rehype'
|
import remarkRehype from 'remark-rehype'
|
||||||
@@ -6,24 +6,58 @@ import rehypeRaw from 'rehype-raw'
|
|||||||
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'
|
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'
|
||||||
import rehypeStringify from 'rehype-stringify'
|
import rehypeStringify from 'rehype-stringify'
|
||||||
import rehypeHighlight from 'rehype-highlight'
|
import rehypeHighlight from 'rehype-highlight'
|
||||||
|
import type { Element, Root } from 'hast'
|
||||||
|
|
||||||
const processor = unified()
|
// 自定义 rehype 插件:将相对路径图片转为 file:// 绝对路径
|
||||||
.use(remarkParse)
|
function rehypeFixImages(filePath: string | null): Plugin<[], Root> {
|
||||||
.use(remarkGfm)
|
return () => (tree: Root) => {
|
||||||
.use(remarkRehype, { allowDangerousHtml: true })
|
if (!filePath) return
|
||||||
.use(rehypeRaw)
|
|
||||||
.use(rehypeSanitize, {
|
// 获取文件所在目录
|
||||||
...defaultSchema,
|
const dir = filePath.replace(/[/\\][^/\\]+$/, '')
|
||||||
attributes: {
|
const sep = dir.includes('\\') ? '\\' : '/'
|
||||||
...defaultSchema.attributes,
|
|
||||||
img: [...(defaultSchema.attributes?.img ?? []), ['src']]
|
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<string> {
|
visit(tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function renderMarkdown(content: string, filePath?: string | null): Promise<string> {
|
||||||
try {
|
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)
|
const result = await processor.process(content)
|
||||||
return String(result)
|
return String(result)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user