The shared MarkdownText only renders absolute http(s) images (relative links are disabled for chat security), so a local image referenced by a previewed markdown file - , an absolute /cwd/img.png, or a reference definition - showed only its alt text. Rewrite such destinations into absolute /sidebar/file media URLs (with the GUI origin prefix) so MarkdownText accepts them and the host media route serves the bytes (still restricted to the session cwd). The rewriter is a dependency-free pure helper (markdown-images.ts) with a focused unit spec; TextEditor passes the rewritten preview text to both the plain MarkdownText and the mermaid-lazy preview.
74 lines
3.6 KiB
TypeScript
74 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { rewriteLocalImageUrls } from '../src/client/markdown-images.ts'
|
|
import type { SessionScope } from '../src/client/api.ts'
|
|
|
|
const ORIGIN = 'http://127.0.0.1:3080'
|
|
const scope: SessionScope = { sessionId: 'abc', cwd: '/repo' }
|
|
|
|
describe('rewriteLocalImageUrls', () => {
|
|
it('resolves relative destinations against the opened file directory', () => {
|
|
const md = '\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/readme.md', ORIGIN)
|
|
expect(out).toContain(``)
|
|
expect(out).toContain(``)
|
|
})
|
|
|
|
it('expects the rewritten destination to be an absolute http URL MarkdownText accepts', () => {
|
|
const out = rewriteLocalImageUrls('', scope, '/repo/readme.md', ORIGIN)
|
|
const url = out.match(/\]\((http[^)]+)\)/)?.[1]
|
|
expect(url).toBeDefined()
|
|
expect(new URL(url as string).protocol).toBe('http:')
|
|
})
|
|
|
|
it('passes absolute local paths through unchanged', () => {
|
|
const md = ''
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/readme.md', ORIGIN)
|
|
expect(out).toContain(``)
|
|
})
|
|
|
|
it('keeps remote http(s)/data/mailto destinations untouched', () => {
|
|
const md = '\n\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain('')
|
|
expect(out).toContain('')
|
|
expect(out).toContain('')
|
|
})
|
|
|
|
it('leaves in-page anchors untouched', () => {
|
|
const md = '\n\n[link](#top)'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain('')
|
|
})
|
|
|
|
it('rewrites reference-style image definitions', () => {
|
|
const md = '![logo][logo]\n\n[logo]: ./logo.svg'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain(`[logo]: ${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Flogo.svg&cwd=%2Frepo`)
|
|
// The inline reference itself is left for MarkdownText to combine.
|
|
expect(out).toContain('![logo][logo]')
|
|
})
|
|
|
|
it('normalizes `.`/`..` segments in relative destinations', () => {
|
|
const md = '\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/sub/readme.md', ORIGIN)
|
|
expect(out).toContain(``)
|
|
expect(out).toContain(``)
|
|
expect(out).not.toContain('/.')
|
|
})
|
|
|
|
it('drops the cwd param when the scope has no cwd', () => {
|
|
const out = rewriteLocalImageUrls('', { sessionId: 'abc' }, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain(`sessionId=abc&path=%2Frepo%2Fimg.png`)
|
|
expect(out).not.toContain('cwd=')
|
|
})
|
|
|
|
it('handles windows absolute paths as local (not remote)', () => {
|
|
const md = ''
|
|
const out = rewriteLocalImageUrls(md, scope, 'C:\\repo\\docs\\readme.md', ORIGIN)
|
|
// A Windows drive path is absolute (not a remote scheme) → rewritten.
|
|
const url = out.match(/\]\(([^)]+)\)/)?.[1] as string
|
|
expect(url).toContain('/sidebar/file?')
|
|
expect(decodeURIComponent(url)).toContain('C:\\repo\\img.png')
|
|
})
|
|
})
|