72 lines
2.4 KiB
React
72 lines
2.4 KiB
React
// MessageContent.jsx
|
|
import React, { useCallback } from 'react';
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkMath from 'remark-math';
|
|
import remarkGfm from 'remark-gfm';
|
|
import rehypeKatex from 'rehype-katex';
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
import './MessageContent.css';
|
|
|
|
export default function MessageContent({ content }) {
|
|
const handleCopy = useCallback(async (text) => {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
} catch {
|
|
/* you could add a toast here if desired */
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkMath, remarkGfm]}
|
|
rehypePlugins={[rehypeKatex]}
|
|
components={{
|
|
table: (props) => <table className="table table-bordered table-sm" {...props} />,
|
|
thead: (props) => <thead className="table-light" {...props} />,
|
|
code({ inline, className, children, ...props }) {
|
|
const codeText = String(children).replace(/\n$/, '');
|
|
const match = /language-(\w+)/.exec(className || '');
|
|
const isMultiLine = codeText.includes('\n');
|
|
|
|
if (inline || !isMultiLine) {
|
|
// Inline or single-line — render plainly
|
|
return (
|
|
<code className={className} {...props}>
|
|
{children}
|
|
</code>
|
|
);
|
|
}
|
|
|
|
// Multi-line fenced code block → highlight + copy button
|
|
return (
|
|
<div className="position-relative md-pre-wrapper">
|
|
<SyntaxHighlighter
|
|
style={oneDark}
|
|
language={match ? match[1] : null}
|
|
PreTag="div"
|
|
customStyle={{ margin: 0, borderRadius: '0.25rem', minHeight: '5rem' }}
|
|
>
|
|
{codeText || ' '} {/* keep at least a space to hold height */}
|
|
</SyntaxHighlighter>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm btn-outline-secondary position-absolute top-0 end-0 m-1 d-flex align-items-center gap-1"
|
|
onClick={() => handleCopy(codeText)}
|
|
>
|
|
<span role="img" aria-label="Copy">
|
|
📋
|
|
</span>
|
|
Copy
|
|
</button>
|
|
</div>
|
|
);
|
|
},
|
|
}}
|
|
>
|
|
{content}
|
|
</ReactMarkdown>
|
|
);
|
|
}
|
|
|