1+ import type { ReactNode } from "react"
2+ import ReactMarkdown from "react-markdown"
3+ import remarkGfm from "remark-gfm"
4+ import { Link } from "react-router-dom"
5+
6+ const USER_MENTION_RE = / ( ^ | [ ^ \w / ] ) @ ( [ A - Z a - z 0 - 9 _ . - ] { 2 , 32 } ) (? = $ | [ ^ \w . - ] ) / g
7+
8+ function linkifyUserMentions ( text : string ) : string {
9+ return String ( text || "" ) . replace ( USER_MENTION_RE , ( match , prefix , username ) => {
10+ if ( ! username ) return match
11+ return `${ prefix } [@${ username } ](/user/${ encodeURIComponent ( username ) } )`
12+ } )
13+ }
14+
15+ const markdownComponents = {
16+ h1 : ( { children } : { children ?: ReactNode } ) => (
17+ < h1 className = "mt-4 mb-2 text-xl font-black leading-tight first:mt-0 sm:text-2xl" > { children } </ h1 >
18+ ) ,
19+ h2 : ( { children } : { children ?: ReactNode } ) => (
20+ < h2 className = "mt-4 mb-2 text-lg font-bold leading-tight first:mt-0 sm:text-xl" > { children } </ h2 >
21+ ) ,
22+ h3 : ( { children } : { children ?: ReactNode } ) => (
23+ < h3 className = "mt-3 mb-1.5 text-base font-bold leading-tight sm:text-lg" > { children } </ h3 >
24+ ) ,
25+ p : ( { children } : { children ?: ReactNode } ) => (
26+ < p className = "my-1.5 leading-relaxed [overflow-wrap:anywhere]" > { children } </ p >
27+ ) ,
28+ ul : ( { children } : { children ?: ReactNode } ) => (
29+ < ul className = "my-2 ml-5 list-disc space-y-1 marker:text-primary" > { children } </ ul >
30+ ) ,
31+ ol : ( { children } : { children ?: ReactNode } ) => (
32+ < ol className = "my-2 ml-5 list-decimal space-y-1 marker:text-primary" > { children } </ ol >
33+ ) ,
34+ li : ( { children } : { children ?: ReactNode } ) => < li className = "pl-1" > { children } </ li > ,
35+ blockquote : ( { children } : { children ?: ReactNode } ) => (
36+ < blockquote className = "my-3 rounded-r-lg border-l-2 border-white/10 bg-white/[0.03] px-3 py-2 text-inherit/80" >
37+ { children }
38+ </ blockquote >
39+ ) ,
40+ hr : ( ) => < hr className = "my-3 border-white/10" /> ,
41+ code : ( { children } : { children ?: ReactNode } ) => (
42+ < code className = "rounded border border-white/10 bg-black/20 px-1.5 py-0.5 font-mono text-[0.8rem] text-primary/90" >
43+ { children }
44+ </ code >
45+ ) ,
46+ pre : ( { children } : { children ?: ReactNode } ) => (
47+ < pre className = "my-3 overflow-x-auto rounded-xl border border-white/10 bg-black/30 p-3 text-[0.85rem] text-zinc-100" >
48+ { children }
49+ </ pre >
50+ ) ,
51+ strong : ( { children } : { children ?: ReactNode } ) => < strong className = "font-semibold" > { children } </ strong > ,
52+ em : ( { children } : { children ?: ReactNode } ) => < em className = "italic" > { children } </ em > ,
53+ a : ( { href, children } : { href ?: string ; children ?: ReactNode } ) => {
54+ if ( typeof href === "string" && href . startsWith ( "/" ) ) {
55+ return (
56+ < Link to = { href } className = "font-semibold text-primary underline underline-offset-4 hover:text-primary/80" >
57+ { children }
58+ </ Link >
59+ )
60+ }
61+
62+ const isExternal = typeof href === "string" && / ^ h t t p s ? : \/ \/ / . test ( href )
63+ return (
64+ < a
65+ href = { href }
66+ className = "font-semibold text-primary underline underline-offset-4 hover:text-primary/80"
67+ target = { isExternal ? "_blank" : undefined }
68+ rel = { isExternal ? "noopener noreferrer" : undefined }
69+ >
70+ { children }
71+ </ a >
72+ )
73+ } ,
74+ }
75+
76+ export function CommentMarkdown ( { text, className = "" } : { text : string ; className ?: string } ) {
77+ return (
78+ < div className = { `w-full min-w-0 max-w-full text-sm leading-relaxed [overflow-wrap:anywhere] ${ className } ` . trim ( ) } >
79+ < ReactMarkdown remarkPlugins = { [ remarkGfm ] } components = { markdownComponents } >
80+ { linkifyUserMentions ( text ) }
81+ </ ReactMarkdown >
82+ </ div >
83+ )
84+ }
0 commit comments