Let's explore how to create a custom React Markdown component that supports mathematical equations, syntax highlighting, and other cool features. We'll be using react-markdown, remark, and rehype libraries to make this happen.
Introduction
The code provided is a laid-back React component named MathMarkdown. This component is designed to render markdown content with support for mathematical equations, syntax highlighting, and more. It uses several libraries and plugins to achieve this functionality, and it is also styled with Material-UI components.
Main Features
Here are the main features of the MathMarkdown component:
- Mathematical equations support
- Syntax highlighting
- Custom components for markdown elements
- Support for oEmbed media
- Code copy button
Let's explore each of these features in more detail with some examples.
For reference, here is the full MathMarkdown component:
import ReactMarkdown from "react-markdown"import remarkMath, { Options } from "remark-math"import rehypeMathjaxSvg from "rehype-mathjax/svg"; import rehypePrism from "rehype-prism-plus"import remarkDirective from "remark-directive"import remarkOembed from "../lib/remark-oembed"import { Box, Table, TableBody, TableCell, TableHead, TableRow,} from "@mui/material"import MLink from "./MlLink"import MTypography from "./MTypography"import MBlockquote from "./MBlockquote"import CustomImage from "./CustomImage"import Iframely from "./Iframely" import "./MathMarkdown.css"import remarkF2Z from "../lib/remark-f2z"import { useStoreState } from "../state/persist"import CodeCopyButton from "./CodeCopyButton";import { ReactNode } from "react";import React from "react"; interface MathMarkdownProps { content: string} const prismErrorHandler = (e: any) => { if (e.language && e.language !== 'none') { console.warn(`Unknown language '${e.language}' detected in code block. The code will be rendered as plain text.`); } return e.code;}; function extractTextFromChildren(children: ReactNode): string { if (typeof children === 'string') { return children; } if (Array.isArray(children)) { return children.reduce((text, child) => text + extractTextFromChildren(child), ''); } if (React.isValidElement(children) && children.props.children) { return extractTextFromChildren(children.props.children); } return '';} const remarkMathOptions: Options = { singleDollarTextMath: false,} const rehypePrismOptions = { ignoreMissing: true, transform: prismErrorHandler,}; export default function MathMarkdown(props: MathMarkdownProps) { const darkMode = useStoreState("darkmode") return ( <ReactMarkdown className="math-markdown" children={props.content} remarkPlugins={[ remarkDirective, remarkOembed, // remarkParse, // remarkGfm, // remarkRehype, [remarkMath, remarkMathOptions], remarkF2Z, ]} rehypePlugins={[ rehypeMathjaxSvg, [rehypePrism, rehypePrismOptions], ]} // https://github.com/hajhosein/mui-markdown/blob/main/src/defaultOverrides.ts components={{ pre: ({ children, className, ...props }) => { if (className?.startsWith('language-')) { return ( <Box component="div" display="flex" flexDirection="column" position="relative"> <CodeCopyButton code={extractTextFromChildren(children)} /> <pre className={className} {...props}> {children} </pre> </Box> ) } else { return ( <pre className={className} {...props}> {children} </pre> ) } }, table: Table, thead: (props) => { return <TableHead {...props} sx={{ backgroundColor: "inherit", }} /> }, tr: (props) => { const { children } = props const isOdd = Number(props.node.position?.start.line) % 2 === 0 return ( <TableRow sx={{ backgroundColor: (theme) => { let gray = "#f5f5f5"; if (darkMode) { gray = "#000000"; } return isOdd ? "inherit" : gray; }, }} {...props} > {children} </TableRow> ); }, tbody: TableBody, th: (props) => ( <TableCell {...props} align="left" sx={{ padding: "12px 16px", borderBottom: "1px solid rgba(224, 224, 224, 1)", // whiteSpace: "nowrap", // backgroundColor: (theme) => theme.palette.background.paper, backgroundColor: darkMode ? "#000000" : "#f5f5f5", wordBreak: "keep-all", }} /> ), td: (props) => ( <TableCell {...props} align="left" sx={{ padding: "12px 16px", borderBottom: "1px solid rgba(224, 224, 224, 1)", // whiteSpace: "nowrap", wordBreak: "keep-all", }} /> ), a: MLink, blockquote: MBlockquote, img: CustomImage, p: MTypography("body1"), code({ node, inline, className, children, ...props }) { // console.log(node, inline, className, children, props) const match = className === "oembed-display" const matchi = className === "iframe-display" if ((match || matchi) && children && children[0]) { let url = children[0].toString() if (url.startsWith('/')) { url = `https://${window.location.host}${url}` } return <Iframely url={url} /> } else { return ( // @ts-ignore <code className={className} {...props}> {children} </code> ) } }, }} /> )}
1. Mathematical Equations Support
The MathMarkdown component supports rendering mathematical equations by using the remark-math plugin for parsing equations in the markdown, and the rehype-mathjax/svg plugin for rendering the equations as SVG images.
Example:
import remarkMath from "remark-math";import rehypeMathjaxSvg from "rehype-mathjax/svg"; // ...remarkPlugins={[ // ... [remarkMath, remarkMathOptions], // ...],rehypePlugins={[ rehypeMathjaxSvg, // ...]},// ...
In this code snippet, we're importing the required plugins and adding them to the remarkPlugins and rehypePlugins arrays. The remarkMathOptions object is used to configure the remark-math plugin, disabling single dollar text math.
2. Syntax Highlighting
Syntax highlighting is achieved using the rehype-prism-plus plugin, which enhances code blocks with syntax highlighting using the Prism.js library.
Example:
import rehypePrism from "rehype-prism-plus"; // ...rehypePlugins={[ // ... [rehypePrism, rehypePrismOptions],]},// ...
In this code snippet, we're importing the rehype-prism-plus plugin and adding it to the rehypePlugins array. The rehypePrismOptions object is used to configure the plugin, ignoring missing languages and using a custom error handler (prismErrorHandler).
3. Custom Components with Material-UI
The MathMarkdown component utilizes Material-UI components to style the rendered markdown elements. This is done using the components prop of the ReactMarkdown component, which accepts an object containing custom components for each markdown element.
Example:
import { Box, TableCell, TableRow } from "@mui/material"; // ...components={{ // ... tr: (props) => { const { children } = props; const isOdd = Number(props.node.position?.start.line) % 2 === 0; return ( <TableRow // ... {...props} > {children} </TableRow> ); }, // ...}},// ...
In this code snippet, we're replacing the tr element with a custom TableRow component from Material-UI, adding custom styling for odd and even rows.
4. Support for oEmbed Media
The remark-oembed plugin is used to support embedding media from various providers, such as YouTube and Twitter, in the markdown content. The Iframely component is used to render the embedded content.
Example:
import remarkOembed from "../lib/remark-oembed"; // ...remarkPlugins={[ // ... remarkOembed, // ...]},// ...components={{ // ... code({ node, inline, className, children, ...props }) { const match = className === "oembed-display"; if (match && children && children[0]) { let url = children[0].toString(); if (url.startsWith('/')) { url = `https://${window.location.host}${url}`; } return <Iframely url={url} />; } else { // ... } }, // ...}},// ...
In this code snippet, we're importing the remark-oembed plugin and adding it to the remarkPlugins array. We then create a custom code component to check if the className is set to "oembed-display". If it is, we use the Iframely component to render the embedded content.
5. Code Copy Button
A copy button is added to code blocks using the CodeCopyButton component. This button allows users to easily copy code snippets to their clipboard.
Example:
import CodeCopyButton from "./CodeCopyButton"; // ...components={{ // ... pre: ({ children, className, ...props }) => { if (className?.startsWith('language-')) { return ( <Box component="div" display="flex" flexDirection="column" position="relative"> <CodeCopyButton code={extractTextFromChildren(children)} /> <pre className={className} {...props}> {children} </pre> </Box> ) } else { // ... } }, // ...}},// ...
In this code snippet, we're importing the CodeCopyButton component and creating a custom pre component. If the className starts with "language-", we render the CodeCopyButton component and pass the extracted code text to it. This allows users to copy the code by clicking the button.
Conclusion
The MathMarkdown component is a chill way to render markdown content with support for mathematical equations, syntax highlighting, and other nifty features. By combining react-markdown, remark, and rehype libraries with Material-UI components, this custom React component offers a flexible and customizable way to enhance markdown rendering in your projects. Kick back, relax, and enjoy the enhanced markdown experience!
Let me know if you want to see any of the other upstream code. You can already retrieve all of your content easily via the API. Maybe someone would like to make a different renderer from free2z-flavored markdown or even start another instance of free2z to federate.

