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 { 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 remarkGfm from "remark-gfm";
|
||||||
import { parseProjectMentionHref } from "@paperclipai/shared";
|
import { parseProjectMentionHref } from "@paperclipai/shared";
|
||||||
import { cn } from "../lib/utils";
|
import { cn } from "../lib/utils";
|
||||||
@@ -116,6 +116,42 @@ function MermaidDiagramBlock({ source, darkMode }: { source: string; darkMode: b
|
|||||||
|
|
||||||
export function MarkdownBody({ children, className, resolveImageSrc }: MarkdownBodyProps) {
|
export function MarkdownBody({ children, className, resolveImageSrc }: MarkdownBodyProps) {
|
||||||
const { theme } = useTheme();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -124,44 +160,7 @@ export function MarkdownBody({ children, className, resolveImageSrc }: MarkdownB
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Markdown
|
<Markdown remarkPlugins={[remarkGfm]} components={components}>
|
||||||
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,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user