152 lines
4.7 KiB
TypeScript
152 lines
4.7 KiB
TypeScript
'use client';
|
|
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkGfm from 'remark-gfm';
|
|
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
|
|
interface MarkdownRendererProps {
|
|
content: string;
|
|
}
|
|
|
|
export default function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|
return (
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
components={{
|
|
h1: ({ children }) => (
|
|
<h1 className="text-4xl font-bold mt-8 mb-4">{children}</h1>
|
|
),
|
|
h2: ({ children }) => (
|
|
<h2 className="text-3xl font-bold mt-6 mb-3">{children}</h2>
|
|
),
|
|
h3: ({ children }) => (
|
|
<h3 className="text-2xl font-bold mt-4 mb-2">{children}</h3>
|
|
),
|
|
h4: ({ children }) => (
|
|
<h4 className="text-xl font-bold mt-3 mb-2">{children}</h4>
|
|
),
|
|
h5: ({ children }) => (
|
|
<h5 className="text-lg font-bold mt-2 mb-1">{children}</h5>
|
|
),
|
|
h6: ({ children }) => (
|
|
<h6 className="text-base font-bold mt-2 mb-1">{children}</h6>
|
|
),
|
|
p: ({ children }) => (
|
|
<p className="my-4 leading-7">{children}</p>
|
|
),
|
|
ul: ({ children }) => (
|
|
<ul className="list-disc list-inside my-4 space-y-2">{children}</ul>
|
|
),
|
|
ol: ({ children }) => (
|
|
<ol className="list-decimal list-inside my-4 space-y-2">{children}</ol>
|
|
),
|
|
li: ({ children }) => (
|
|
<li className="ml-4">{children}</li>
|
|
),
|
|
blockquote: ({ children }) => (
|
|
<blockquote className="border-l-4 border-gray-300 pl-4 my-4 italic text-gray-700">
|
|
{children}
|
|
</blockquote>
|
|
),
|
|
code: ({ inline, className, children, ...props }: any) => {
|
|
const match = /language-(\w+)/.exec(className || '');
|
|
return !inline && match ? (
|
|
<SyntaxHighlighter
|
|
style={vscDarkPlus}
|
|
language={match[1]}
|
|
PreTag="div"
|
|
className="my-4 rounded-lg"
|
|
{...props}
|
|
>
|
|
{String(children).replace(/\n$/, '')}
|
|
</SyntaxHighlighter>
|
|
) : (
|
|
<code className="bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono" {...props}>
|
|
{children}
|
|
</code>
|
|
);
|
|
},
|
|
img: ({ src, alt }) => {
|
|
if (!src || typeof src !== 'string') return null;
|
|
const isExternal = src.startsWith('http://') || src.startsWith('https://');
|
|
|
|
if (isExternal) {
|
|
return (
|
|
<img
|
|
src={src}
|
|
alt={alt || ''}
|
|
className="my-4 rounded-lg max-w-full h-auto"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="my-4 relative w-full h-auto">
|
|
<Image
|
|
src={src}
|
|
alt={alt || ''}
|
|
width={800}
|
|
height={600}
|
|
className="rounded-lg"
|
|
style={{ width: '100%', height: 'auto' }}
|
|
/>
|
|
</div>
|
|
);
|
|
},
|
|
a: ({ href, children }) => {
|
|
if (!href) return <>{children}</>;
|
|
const isExternal = href.startsWith('http://') || href.startsWith('https://');
|
|
|
|
if (isExternal) {
|
|
return (
|
|
<a
|
|
href={href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-blue-600 hover:underline"
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Link href={href} className="text-blue-600 hover:underline">
|
|
{children}
|
|
</Link>
|
|
);
|
|
},
|
|
table: ({ children }) => (
|
|
<div className="overflow-x-auto my-4">
|
|
<table className="min-w-full border-collapse border border-gray-300">
|
|
{children}
|
|
</table>
|
|
</div>
|
|
),
|
|
thead: ({ children }) => (
|
|
<thead className="bg-gray-100">{children}</thead>
|
|
),
|
|
tbody: ({ children }) => (
|
|
<tbody>{children}</tbody>
|
|
),
|
|
tr: ({ children }) => (
|
|
<tr className="border-b border-gray-300">{children}</tr>
|
|
),
|
|
th: ({ children }) => (
|
|
<th className="border border-gray-300 px-4 py-2 text-left font-bold">
|
|
{children}
|
|
</th>
|
|
),
|
|
td: ({ children }) => (
|
|
<td className="border border-gray-300 px-4 py-2">{children}</td>
|
|
),
|
|
}}
|
|
>
|
|
{content}
|
|
</ReactMarkdown>
|
|
);
|
|
}
|