Spaces:
Running
Running
| import { FC, memo } from 'react'; | |
| import ReactMarkdown, { Options } from 'react-markdown'; | |
| import Img from '../ui/Img'; | |
| import remarkGfm from 'remark-gfm'; | |
| import remarkMath from 'remark-math'; | |
| import rehypeRaw from 'rehype-raw'; | |
| import { CodeBlock } from '../ui/CodeBlock'; | |
| export const MemoizedReactMarkdown: FC<Options> = memo( | |
| ReactMarkdown, | |
| (prevProps, nextProps) => | |
| prevProps.children === nextProps.children && | |
| prevProps.className === nextProps.className, | |
| ); | |
| export const Markdown: React.FC<{ | |
| content: string; | |
| }> = ({ content }) => { | |
| return ( | |
| <> | |
| <MemoizedReactMarkdown | |
| className="break-words overflow-auto" | |
| remarkPlugins={[remarkGfm, remarkMath]} | |
| rehypePlugins={[rehypeRaw] as any} | |
| components={{ | |
| p({ children, ...props }) { | |
| if ( | |
| props.node.children.some( | |
| child => child.type === 'element' && child.tagName === 'img', | |
| ) | |
| ) { | |
| return ( | |
| <p className="flex flex-wrap gap-2 items-start">{children}</p> | |
| ); | |
| } | |
| return <p className="mb-2 whitespace-pre-line">{children}</p>; | |
| }, | |
| img(props) { | |
| if (props.src?.endsWith('.mp4')) { | |
| return ( | |
| <video src={props.src} controls width={500} height={500} /> | |
| ); | |
| } | |
| return ( | |
| <Img | |
| src={props.src ?? '/landing.png'} | |
| alt={props.alt ?? 'answer-image'} | |
| quality={100} | |
| sizes="(min-width: 66em) 15vw, | |
| (min-width: 44em) 20vw, | |
| 100vw" | |
| /> | |
| ); | |
| }, | |
| code({ node, inline, className, children, ...props }) { | |
| const match = /language-(\w+)/.exec(className || ''); | |
| return ( | |
| <CodeBlock | |
| key={Math.random()} | |
| language={(match && match[1]) || ''} | |
| value={String(children).replace(/\n$/, '')} | |
| {...props} | |
| /> | |
| ); | |
| }, | |
| }} | |
| > | |
| {content} | |
| </MemoizedReactMarkdown> | |
| </> | |
| ); | |
| }; | |