diff --git a/snippets/components/integrators/embeds/DataEmbed.jsx b/snippets/components/integrators/embeds/DataEmbed.jsx index a5beed083..0b443315b 100644 --- a/snippets/components/integrators/embeds/DataEmbed.jsx +++ b/snippets/components/integrators/embeds/DataEmbed.jsx @@ -93,6 +93,22 @@ export const MarkdownEmbed = ({ url, className = '', style = {}, ...rest }) => { const [html, setHtml] = useState('') useEffect(() => { + // Derive base URL so relative image paths in the remote file can be resolved + const lastSlash = url.lastIndexOf('/') + const baseUrl = lastSlash >= 0 ? url.substring(0, lastSlash + 1) : '' + const origin = baseUrl.match(/^(https?:\/\/[^/]+)/)?.[1] ?? '' + + const resolveUrl = (src) => { + // Pass through absolute URLs, protocol-relative, data URIs, and fragment links + if (/^https?:\/\//i.test(src) || src.startsWith('//') || src.startsWith('data:') || src.startsWith('#')) return src + // Root-relative paths anchor to the origin, not the directory base + if (src.startsWith('/')) return origin + src + return baseUrl + src + } + + const escapeAttr = (str) => + String(str).replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>') + fetch(url) .then((res) => res.text()) .then((md) => { @@ -102,10 +118,20 @@ export const MarkdownEmbed = ({ url, className = '', style = {}, ...rest }) => { .replace(/```(\w*)\n([\s\S]*?)```/g, '
$2
') // Inline code .replace(/`([^`]+)`/g, '$1') - // Images + // Markdown images — resolve relative src to absolute .replace( /!\[([^\]]*)\]\(([^)]+)\)/g, - '$1' + (_, alt, src) => `${escapeAttr(alt)}` + ) + // Raw HTML img tags with double-quoted src — resolve relative src to absolute + .replace( + /]*)src="([^"]*)"([^>]*)>/gi, + (_, before, src, after) => `` + ) + // Raw HTML img tags with single-quoted src — resolve relative src to absolute + .replace( + /]*)src='([^']*)'([^>]*)>/gi, + (_, before, src, after) => `` ) // Links .replace(