Fix markdown image rendering without resolver
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
31
ui/src/components/MarkdownBody.test.tsx
Normal file
31
ui/src/components/MarkdownBody.test.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { ThemeProvider } from "../context/ThemeContext";
|
||||
import { MarkdownBody } from "./MarkdownBody";
|
||||
|
||||
describe("MarkdownBody", () => {
|
||||
it("renders markdown images without a resolver", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ThemeProvider>
|
||||
<MarkdownBody>{""}</MarkdownBody>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(html).toContain('<img src="/api/attachments/test/content" alt=""/>');
|
||||
});
|
||||
|
||||
it("resolves relative image paths when a resolver is provided", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ThemeProvider>
|
||||
<MarkdownBody resolveImageSrc={(src) => `/resolved/${src}`}>
|
||||
{""}
|
||||
</MarkdownBody>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(html).toContain('src="/resolved/images/org-chart.png"');
|
||||
expect(html).toContain('alt="Org chart"');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isValidElement, useEffect, useId, useState, type CSSProperties, type ReactNode } from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import Markdown, { type Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { parseProjectMentionHref } from "@paperclipai/shared";
|
||||
import { cn } from "../lib/utils";
|
||||
@@ -116,6 +116,42 @@ function MermaidDiagramBlock({ source, darkMode }: { source: string; darkMode: b
|
||||
|
||||
export function MarkdownBody({ children, className, resolveImageSrc }: MarkdownBodyProps) {
|
||||
const { theme } = useTheme();
|
||||
const components: Components = {
|
||||
pre: ({ node: _node, children: preChildren, ...preProps }) => {
|
||||
const mermaidSource = extractMermaidSource(preChildren);
|
||||
if (mermaidSource) {
|
||||
return <MermaidDiagramBlock source={mermaidSource} darkMode={theme === "dark"} />;
|
||||
}
|
||||
return <pre {...preProps}>{preChildren}</pre>;
|
||||
},
|
||||
a: ({ href, children: linkChildren }) => {
|
||||
const parsed = href ? parseProjectMentionHref(href) : null;
|
||||
if (parsed) {
|
||||
const label = linkChildren;
|
||||
return (
|
||||
<a
|
||||
href={`/projects/${parsed.projectId}`}
|
||||
className="paperclip-project-mention-chip"
|
||||
style={mentionChipStyle(parsed.color)}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a href={href} rel="noreferrer">
|
||||
{linkChildren}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
};
|
||||
if (resolveImageSrc) {
|
||||
components.img = ({ node: _node, src, alt, ...imgProps }) => {
|
||||
const resolved = src ? resolveImageSrc(src) : null;
|
||||
return <img {...imgProps} src={resolved ?? src} alt={alt ?? ""} />;
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -124,44 +160,7 @@ export function MarkdownBody({ children, className, resolveImageSrc }: MarkdownB
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
pre: ({ node: _node, children: preChildren, ...preProps }) => {
|
||||
const mermaidSource = extractMermaidSource(preChildren);
|
||||
if (mermaidSource) {
|
||||
return <MermaidDiagramBlock source={mermaidSource} darkMode={theme === "dark"} />;
|
||||
}
|
||||
return <pre {...preProps}>{preChildren}</pre>;
|
||||
},
|
||||
a: ({ href, children: linkChildren }) => {
|
||||
const parsed = href ? parseProjectMentionHref(href) : null;
|
||||
if (parsed) {
|
||||
const label = linkChildren;
|
||||
return (
|
||||
<a
|
||||
href={`/projects/${parsed.projectId}`}
|
||||
className="paperclip-project-mention-chip"
|
||||
style={mentionChipStyle(parsed.color)}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a href={href} rel="noreferrer">
|
||||
{linkChildren}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
img: resolveImageSrc
|
||||
? ({ src, alt, ...imgProps }) => {
|
||||
const resolved = src ? resolveImageSrc(src) : null;
|
||||
return <img {...imgProps} src={resolved ?? src} alt={alt ?? ""} />;
|
||||
}
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<Markdown remarkPlugins={[remarkGfm]} components={components}>
|
||||
{children}
|
||||
</Markdown>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user