import clsx from "clsx"; import type { ReactNode } from "react"; import React, { useCallback, useState } from "react"; import { FiClipboard } from "react-icons/fi"; import ReactMarkdown from "react-markdown"; import rehypeHighlight from "rehype-highlight"; import remarkGfm from "remark-gfm"; import "highlight.js/styles/default.css"; interface MarkdownRendererProps { children: string; className?: string; } const MarkdownRenderer = ({ children, className }: MarkdownRendererProps) => { return ( rehypeHighlight({ ignoreMissing: true })]} components={{ pre: CustomPre, code: CustomCodeBlock, h1: (props) =>

{props.children}

, h2: (props) =>

{props.children}

, a: (props) => CustomLink({ children: props.children, href: props.href }), p: (props) =>

{props.children}

, ul: (props) => ( ), ol: (props) => (
    {props.children}
), li: (props) =>
  • {props.children}
  • , }} > {children}
    ); }; const CustomPre = ({ children }: { children: ReactNode }) => { const [isCopied, setIsCopied] = useState(false); const code = React.Children.toArray(children).find(isValidCustomCodeBlock); const language: string = code && code.props.className ? extractLanguageName(code.props.className.replace("hljs ", "")) : ""; const handleCopyClick = useCallback(() => { if (code && React.isValidElement(code)) { const codeString = extractTextFromNode(code.props.children); void navigator.clipboard.writeText(codeString); setIsCopied(true); setTimeout(() => { setIsCopied(false); }, 2000); } }, [code]); return (
    {language.charAt(0).toUpperCase() + language.slice(1)}
    {children}
    ); }; interface CustomCodeBlockProps { inline?: boolean; className?: string; children: ReactNode; } const CustomCodeBlock = ({ inline, className, children }: CustomCodeBlockProps) => { // Inline code blocks will be placed directly within a paragraph if (inline) { return {children}; } const language = className ? className.replace("language-", "") : "plaintext"; return {children}; }; const CustomLink = ({ children, href }) => { return ( {children} ); }; const isValidCustomCodeBlock = ( element: ReactNode ): element is React.ReactElement => React.isValidElement(element) && element.type === CustomCodeBlock; const extractLanguageName = (languageString: string): string => { // The provided language will be "language-{PROGRAMMING_LANGUAGE}" const parts = languageString.split("-"); if (parts.length > 1) { return parts[1] || ""; } return ""; }; const extractTextFromNode = (node: React.ReactNode): string => { if (typeof node === "string") { return node; } if (Array.isArray(node)) { return node.map(extractTextFromNode).join(""); } if (React.isValidElement(node)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access return extractTextFromNode(node.props.children); } return ""; }; export default MarkdownRenderer;