fix: prevent inline code from rendering diagrams (#773)
* fix: prevent inline code from rendering diagrams * fix: address markdown renderer review feedback * fix: remove redundant markdown block detection
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import type { ReactNode } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { markdownComponents, remarkPlugins } from "./markdown-components";
|
||||
|
||||
vi.mock("@/components/shared/mermaid-block", () => ({
|
||||
MermaidBlock: ({ code }: { code: string }) => <div data-kind="mermaid">{code}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/task/chat/messages/code-block", () => ({
|
||||
CodeBlock: ({ children, className }: { children: ReactNode; className?: string }) => (
|
||||
<pre data-kind="block" className={className}>
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/task/chat/messages/inline-code", () => ({
|
||||
InlineCode: ({ children }: { children: ReactNode }) => <code data-kind="inline">{children}</code>,
|
||||
}));
|
||||
|
||||
function renderMarkdown(source: string): string {
|
||||
return renderToStaticMarkup(
|
||||
<ReactMarkdown remarkPlugins={remarkPlugins} components={markdownComponents}>
|
||||
{source}
|
||||
</ReactMarkdown>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("markdownComponents", () => {
|
||||
it("keeps mermaid keywords in inline code as inline code", () => {
|
||||
const html = renderMarkdown("Metadata comes from `kanban`, `kanbanMulti`, repositories.");
|
||||
|
||||
expect(html).toContain('data-kind="inline"');
|
||||
expect(html).toContain("kanban");
|
||||
expect(html).not.toContain('data-kind="mermaid"');
|
||||
});
|
||||
|
||||
it("renders fenced mermaid code as a mermaid block", () => {
|
||||
const html = renderMarkdown("```mermaid\ngraph LR\nA-->B\n```");
|
||||
|
||||
expect(html).toContain('data-kind="mermaid"');
|
||||
expect(html).toContain("graph LR");
|
||||
});
|
||||
|
||||
it("renders non-mermaid fenced code as a code block", () => {
|
||||
const html = renderMarkdown("```ts\nconst source = 'kanban';\n```");
|
||||
|
||||
expect(html).toContain('data-kind="block"');
|
||||
expect(html).toContain("language-ts");
|
||||
expect(html).toContain("const source");
|
||||
expect(html).not.toContain('data-kind="mermaid"');
|
||||
});
|
||||
});
|
||||
@@ -39,6 +39,15 @@ export function getTextContent(children: ReactNode): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
type MarkdownCodeProps = {
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
function isBlockCode(rawContent: string, hasLanguage: boolean): boolean {
|
||||
return hasLanguage || rawContent.includes("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared markdown component overrides for ReactMarkdown.
|
||||
* Element styles (headings, lists, inline code, etc.) are handled by
|
||||
@@ -46,15 +55,16 @@ export function getTextContent(children: ReactNode): string {
|
||||
* (code routing, link target, table overflow wrapper) remain here.
|
||||
*/
|
||||
export const markdownComponents = {
|
||||
code: ({ className, children }: { className?: string; children?: ReactNode }) => {
|
||||
const content = getTextContent(children).replace(/\n$/, "");
|
||||
code: ({ className, children }: MarkdownCodeProps) => {
|
||||
const rawContent = getTextContent(children);
|
||||
const content = rawContent.replace(/\n$/, "");
|
||||
const lang = className?.replace("language-", "") ?? null;
|
||||
if (isMermaidContent(lang, content)) {
|
||||
const hasLanguage = className?.startsWith("language-") ?? false;
|
||||
const isBlock = isBlockCode(rawContent, hasLanguage);
|
||||
if (isBlock && isMermaidContent(lang, content)) {
|
||||
return <MermaidBlock code={content} />;
|
||||
}
|
||||
const hasLanguage = className?.startsWith("language-");
|
||||
const hasNewlines = content.includes("\n");
|
||||
if (hasLanguage || hasNewlines) {
|
||||
if (isBlock) {
|
||||
return <CodeBlock className={className}>{content}</CodeBlock>;
|
||||
}
|
||||
return <InlineCode>{content}</InlineCode>;
|
||||
|
||||
Reference in New Issue
Block a user