pr_01m47d15m3e54sn21z27rpy5n9/apps/web/app/components/markdown.tsx

43 lines1,326 bytesCodeBlame
1import type { ReactNode } from "react";
2import ReactMarkdown from "react-markdown";
3import { Link } from "react-router";
4import remarkGfm from "remark-gfm";
5
6/** An anchor id for a heading, so sections can be linked to. */
7function slug(children: ReactNode): string {
8 const text = Array.isArray(children) ? children.join("") : String(children ?? "");
9 return text
10 .toLowerCase()
11 .replace(/[^a-z0-9]+/g, "-")
12 .replace(/^-|-$/g, "");
13}
14
15/**
16 * Renders markdown from repositories, briefs and docs. Raw HTML in the
17 * source is not rendered, so untrusted content is safe to pass in.
18 */
19export function Markdown({ source }: { source: string }) {
20 return (
21 <div className="prose">
22 <ReactMarkdown
23 remarkPlugins={[remarkGfm]}
24 components={{
25 h2: ({ children }) => <h2 id={slug(children)}>{children}</h2>,
26 h3: ({ children }) => <h3 id={slug(children)}>{children}</h3>,
27 a({ href, children }) {
28 // In-site links navigate on the client.
29 return href?.startsWith("/") ? (
30 <Link to={href}>{children}</Link>
31 ) : (
32 <a href={href} rel="noreferrer nofollow">
33 {children}
34 </a>
35 );
36 },
37 }}
38 >
39 {source}
40 </ReactMarkdown>
41 </div>
42 );
43}