react-markdown code 的样式渲染
2023-05-10
该代码段展示了如何使用`react-markdown`及其插件来渲染和自定义Markdown中的代码块样式,包括处理特殊字符、应用不同的CSS类以及使用自定义组件来增强代码块的功能,如复制和下载代码。
<MemoizedReactMarkdown className="flex-1 h-full p-5 bg-black" remarkPlugins={[remarkGfm, remarkMath]} rehypePlugins={[rehypeMathjax]} components={{ code({node, inline, className, children, ...props}) { if (children.length) { if (children[0] == '▍') { return <span className="animate-pulse cursor-default mt-1">▍</span> } children[0] = (children[0] as string).replace("`▍`", "▍") } const match = /language-(\w+)/.exec(className || ''); return !inline ? ( <CodeBlock key={Math.random()} language={(match && match[1]) || ''} value={String(children).replace(/\n$/, '')} {...props} /> ) : ( <code className={className} {...props}> {children} </code> ); }, table({children}) { return ( <table className="border-collapse border border-black px-3 py-1 dark:border-white"> {children} </table> ); }, th({children}) { return ( <th className="break-words border border-black bg-gray-500 px-3 py-1 text-white dark:border-white"> {children} </th> ); }, td({children}) { return ( <td className="break-words border border-black px-3 py-1 dark:border-white"> {children} </td> ); }, ol({children}) { return ( <ol className="list-decimal list-inside"> {children} </ol> ) }, ul({children}) { return ( <ul className="list-disc list-inside"> {children} </ul> ) } }} > {`${markdown}`} </MemoizedReactMarkdown>
提供的修改类型包括
export type SpecialComponents = { code: CodeComponent | ReactMarkdownNames h1: HeadingComponent | ReactMarkdownNames h2: HeadingComponent | ReactMarkdownNames h3: HeadingComponent | ReactMarkdownNames h4: HeadingComponent | ReactMarkdownNames h5: HeadingComponent | ReactMarkdownNames h6: HeadingComponent | ReactMarkdownNames li: LiComponent | ReactMarkdownNames ol: OrderedListComponent | ReactMarkdownNames td: TableDataCellComponent | ReactMarkdownNames th: TableHeaderCellComponent | ReactMarkdownNames tr: TableRowComponent | ReactMarkdownNames ul: UnorderedListComponent | ReactMarkdownNames }
CodeBlock
interface Props { language: string; value: string; } export const CodeBlock: FC<Props> = memo(({ language, value }) => { const { t } = useTranslation('markdown'); const [isCopied, setIsCopied] = useState<Boolean>(false); const copyToClipboard = () => { if (!navigator.clipboard || !navigator.clipboard.writeText) { return; } navigator.clipboard.writeText(value).then(() => { setIsCopied(true); setTimeout(() => { setIsCopied(false); }, 2000); }); }; const downloadAsFile = () => { const fileExtension = programmingLanguages[language] || '.file'; const suggestedFileName = `file-${generateRandomString( 3, true, )}${fileExtension}`; const fileName = window.prompt( t('Enter file name') || '', suggestedFileName, ); if (!fileName) { // user pressed cancel on prompt return; } const blob = new Blob([value], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = fileName; link.href = url; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; return ( <div className="codeblock relative font-sans text-[16px]"> <div className="flex items-center justify-between py-1.5 px-4"> <span className="text-xs lowercase text-white">{language}</span> <div className="flex items-center"> <button className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-white" onClick={copyToClipboard} > {isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />} {isCopied ? t('Copied!') : t('Copy code')} </button> <button className="flex items-center rounded bg-none p-1 text-xs text-white" onClick={downloadAsFile} > <IconDownload size={18} /> </button> </div> </div> <SyntaxHighlighter language={language} style={oneDark} customStyle={{ margin: 0 }} > {value} </SyntaxHighlighter> </div> ); }); CodeBlock.displayName = 'CodeBlock';