fix(editor): rewrite local images in markdown runs of the split renderer

Every .md preview renders through MarkdownDocument (htmlInfo is ungated),
but its markdown segments passed the raw source into MarkdownText, whose
chat-security stance drops non-http(s) image destinations — so local
markdown-syntax images (![alt](./icon.svg), reference-style definitions)
always fell back to alt text. Only HTML <img src> tags were rewritten
(postProcessSanitized).

Apply rewriteLocalImageUrls to each markdown run (segment + appended
reference definitions) inside MarkdownDocument's prepared memo, through
the same media trust fence the sanitized HTML leaves already use.
Idempotent for already-absolute media URLs; remote destinations
untouched. Also correct the stale "non-null only for HTML documents"
comment on TextEditor's htmlInfo.
This commit is contained in:
Menghuan1918
2026-09-06 21:53:49 +08:00
parent bb21657eae
commit 2a99968fa3
3 changed files with 64 additions and 8 deletions
+48
View File
@@ -175,3 +175,51 @@ describe('MarkdownDocument (inline pass)', () => {
await unmount(root)
})
})
describe('MarkdownDocument (local markdown images)', () => {
it('renders markdown-syntax local images through the /sidebar/file route', async () => {
const { container, root } = await renderDocument([
'# Title',
'',
'![icon](./assets/icon.svg)',
'',
'![logo][logo]',
'',
'[logo]: ./logo.png',
].join('\n'))
const imgs = [...container.querySelectorAll('img')]
expect(imgs.length, 'both local images must render as <img>, not alt fallback').toBe(2)
expect(imgs[0]?.getAttribute('src')).toBe('http://gui.origin/sidebar/file?sessionId=s1&path=%2Fws%2Fdocs%2Fassets%2Ficon.svg&cwd=%2Fws')
expect(imgs[0]?.getAttribute('alt')).toBe('icon')
expect(imgs[1]?.getAttribute('src')).toBe('http://gui.origin/sidebar/file?sessionId=s1&path=%2Fws%2Fdocs%2Flogo.png&cwd=%2Fws')
expect(imgs[1]?.getAttribute('alt')).toBe('logo')
await unmount(root)
})
it('renders local markdown images in HTML-mixed documents (the production path)', async () => {
const { container, root } = await renderDocument([
'<div align="center">badges</div>',
'',
'![shot](./img.png)',
].join('\n'))
const img = container.querySelector('img')
expect(img, 'the markdown-syntax image after an HTML run must render').not.toBeNull()
expect(img?.getAttribute('src')).toBe('http://gui.origin/sidebar/file?sessionId=s1&path=%2Fws%2Fdocs%2Fimg.png&cwd=%2Fws')
await unmount(root)
})
it('keeps remote image destinations untouched', async () => {
const { container, root } = await renderDocument('![r](https://example.com/x.png)')
const img = container.querySelector('img')
expect(img?.getAttribute('src')).toBe('https://example.com/x.png')
await unmount(root)
})
it('is idempotent for already-rewritten media URLs', async () => {
const url = 'http://gui.origin/sidebar/file?sessionId=s1&path=%2Fws%2Fdocs%2Fimg.png&cwd=%2Fws'
const { container, root } = await renderDocument(`![a](${url})`)
const img = container.querySelector('img')
expect(img?.getAttribute('src')).toBe(url)
await unmount(root)
})
})