fix(markdown): preview local/relative images in .md files

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 - ![alt](./img.png), 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.
This commit is contained in:
Lin
2026-08-21 13:40:41 +08:00
parent 50a888845f
commit bc6f1e129d
5 changed files with 174 additions and 2 deletions
+10 -2
View File
@@ -22,6 +22,7 @@ import { EditorView as CodeMirrorView, keymap, lineNumbers } from '@codemirror/v
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
import { IconCheckOutline16, MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'
import { api, htmlUrl } from './api.ts'
import { rewriteLocalImageUrls } from './markdown-images.ts'
import { languageForPath } from './lang.ts'
import { cmSurfaceTheme, CmThemeCompartment } from './cm-themes.ts'
import { isDarkScheme, subscribeColorScheme } from './theme.ts'
@@ -252,6 +253,13 @@ export function TextEditor(props: FileViewerProps) {
const html = viewerId === 'html'
/** The markdown source the preview renders (draft wins over saved content). */
const mdText = draft ?? content ?? ''
/** The preview source: `mdText` with local image destinations rewritten to
* absolute media URLs (see {@link rewriteLocalImageUrls}); the raw
* `mdText` stays untouched for selection/line lookup and for mermaid-block
* detection, which are unaffected by image syntax. */
const previewText = markdown
? rewriteLocalImageUrls(mdText, scope, path, window.location.origin)
: mdText
/** md/mermaid block split for the preview (mermaid fences lift out). Split
* only in preview mode: edit-mode keystrokes must not re-scan the source. */
const mdBlocks = useMemo(
@@ -392,8 +400,8 @@ export function TextEditor(props: FileViewerProps) {
parse; cross-fence references/footnotes stay intact); files
without one render exactly as before. */}
{hasMermaid
? <LazyMermaidMarkdown text={mdText} codeLabels={codeLabels} />
: <MarkdownText text={mdText} codeLabels={codeLabels} />}
? <LazyMermaidMarkdown text={previewText} codeLabels={codeLabels} />
: <MarkdownText text={previewText} codeLabels={codeLabels} />}
</div>
)}
{html && mode === 'preview' && (