Markdown.tsx
Language TypeScript3.2 KB111 lines
import { memo, useState } from 'react'import ReactMarkdown from 'react-markdown'import rehypeHighlight from 'rehype-highlight'import remarkGfm from 'remark-gfm'import bash from 'highlight.js/lib/languages/bash'import css from 'highlight.js/lib/languages/css'import diff from 'highlight.js/lib/languages/diff'import go from 'highlight.js/lib/languages/go'import javascript from 'highlight.js/lib/languages/javascript'import json from 'highlight.js/lib/languages/json'import markdown from 'highlight.js/lib/languages/markdown'import python from 'highlight.js/lib/languages/python'import rust from 'highlight.js/lib/languages/rust'import sql from 'highlight.js/lib/languages/sql'import typescript from 'highlight.js/lib/languages/typescript'import xml from 'highlight.js/lib/languages/xml'import yaml from 'highlight.js/lib/languages/yaml'import { useStore } from '../state/store'const languages = {bash,sh: bash,shell: bash,css,diff,go,javascript,js: javascript,json,markdown,md: markdown,python,py: python,rust,rs: rust,sql,typescript,ts: typescript,tsx: typescript,xml,html: xml,yaml,yml: yaml,}function textOf(node: unknown): string {if (typeof node === 'string') return nodeif (Array.isArray(node)) return node.map(textOf).join('')if (node && typeof node === 'object' && 'props' in node)return textOf((node as { props: { children?: unknown } }).props.children)return ''}function CodeBlock({ children }: { children?: React.ReactNode }) {const [copied, setCopied] = useState(false)const code = textOf(children).replace(/\n$/, '')const lang = /language-(\w+)/.exec(((children as { props?: { className?: string } })?.props?.className ?? '') as string,)?.[1]return (<div className="codeblock"><div className="codeblock-bar"><span>{lang ?? 'text'}</span><buttonclassName="ghost small"onClick={() => {void navigator.clipboard?.writeText(code)setCopied(true)setTimeout(() => setCopied(false), 1400)}}>{copied ? 'Copied' : 'Copy'}</button></div><pre>{children}</pre></div>)}/** Assistant text. No raw HTML; links open in the browser, https only. */export const Markdown = memo(function Markdown({ text }: { text: string }) {const client = useStore((s) => s.client)return (<div className="md selectable"><ReactMarkdownremarkPlugins={[remarkGfm]}rehypePlugins={[[rehypeHighlight, { languages, detect: false, ignoreMissing: true }]]}skipHtmlcomponents={{pre: ({ children }) => <CodeBlock>{children}</CodeBlock>,a: ({ href, children }) => (<ahref={href}onClick={(e) => {e.preventDefault()if (href && /^https:\/\//.test(href)) void client?.openUrl(href)}}title={href}>{children}</a>),img: ({ alt }) => <span className="md-img">[image: {alt || 'untitled'}]</span>,}}>{text}</ReactMarkdown></div>)})