Adds GitHub-flavored markdown rendering to message bodies in both MailboxView and MailboxModal via a shared MailboxMessageContent component (ReactMarkdown + remark-gfm). Plain-text messages render unchanged. Raw HTML is not executed. - New MailboxMessageContent component with mailbox-scoped pre/table/link overrides; links open in a new tab with noopener noreferrer. - Wired into the full message body and conversation thread bodies in both Mailbox surfaces (parity preserved). - CSS for .mailbox-markdown, .mailbox-markdown-pre, .mailbox-markdown-table with horizontal scroll for code blocks/tables; removed white-space: pre-wrap on bodies since markdown owns its own whitespace. - 10 new unit tests cover headings, lists, bold/italic, inline code, fenced code blocks, link target/rel, GFM tables, plain-text passthrough, raw-HTML safety, and testId forwarding. Existing 108 mailbox tests continue to pass.
61 lines
1.8 KiB
TypeScript
61 lines
1.8 KiB
TypeScript
import { memo } from "react";
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
import type { Components } from "react-markdown";
|
|
|
|
const mailboxMarkdownComponents: Components = {
|
|
pre: ({ children, ...props }) => (
|
|
<pre {...props} className="mailbox-markdown-pre">
|
|
{children}
|
|
</pre>
|
|
),
|
|
table: ({ children, ...props }) => (
|
|
<table {...props} className="mailbox-markdown-table">
|
|
{children}
|
|
</table>
|
|
),
|
|
// Open links in a new tab. ReactMarkdown does not allow raw HTML by default,
|
|
// so the rendered output here is safe.
|
|
a: ({ children, ...props }) => (
|
|
<a {...props} target="_blank" rel="noopener noreferrer">
|
|
{children}
|
|
</a>
|
|
),
|
|
};
|
|
|
|
interface MailboxMessageContentProps {
|
|
/** Raw message body. Rendered as GitHub-flavored markdown. */
|
|
content: string;
|
|
/** Optional extra class for the wrapper. */
|
|
className?: string;
|
|
/** Optional data-testid for test selectors. */
|
|
testId?: string;
|
|
}
|
|
|
|
/**
|
|
* Renders a mailbox message body as GitHub-flavored markdown.
|
|
*
|
|
* Uses ReactMarkdown defaults (no raw HTML) so untrusted message content is
|
|
* safe. Plain-text messages render unchanged (markdown is a strict superset
|
|
* for the formatting we care about — bold, lists, code, links, tables).
|
|
*
|
|
* Memoized because mailbox detail panes can re-render on selection / SSE
|
|
* updates while the underlying message body is unchanged.
|
|
*/
|
|
export const MailboxMessageContent = memo(function MailboxMessageContent({
|
|
content,
|
|
className,
|
|
testId,
|
|
}: MailboxMessageContentProps) {
|
|
const wrapperClass = className
|
|
? `mailbox-markdown ${className}`
|
|
: "mailbox-markdown";
|
|
return (
|
|
<div className={wrapperClass} data-testid={testId}>
|
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={mailboxMarkdownComponents}>
|
|
{content}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
});
|