|
1 | | -import { onlyText } from "react-children-utilities"; |
2 | | -import ReactMarkdown from "react-markdown"; |
3 | | -import remarkGfm from "remark-gfm"; |
4 | | -import { CodeClient } from "@/components/ui/code/code.client"; |
5 | | -import { PlainTextCodeBlock } from "@/components/ui/code/plaintext-code"; |
6 | | -import { InlineCode } from "@/components/ui/inline-code"; |
7 | | -import { |
8 | | - Table, |
9 | | - TableBody, |
10 | | - TableCell, |
11 | | - TableContainer, |
12 | | - TableHead, |
13 | | - TableHeader, |
14 | | - TableRow, |
15 | | -} from "@/components/ui/table"; |
16 | | -import { UnderlineLink } from "@/components/ui/UnderlineLink"; |
17 | | -import { cn } from "@/lib/utils"; |
18 | | - |
19 | | -export const MarkdownRenderer: React.FC<{ |
20 | | - markdownText: string; |
21 | | - className?: string; |
22 | | - code?: { |
23 | | - disableCodeHighlight?: boolean; |
24 | | - ignoreFormattingErrors?: boolean; |
25 | | - className?: string; |
26 | | - }; |
27 | | - inlineCode?: { |
28 | | - className?: string; |
29 | | - }; |
30 | | - p?: { |
31 | | - className?: string; |
32 | | - }; |
33 | | - li?: { |
34 | | - className?: string; |
35 | | - }; |
36 | | - skipHtml?: boolean; |
37 | | -}> = (markdownProps) => { |
38 | | - const { markdownText, className, code } = markdownProps; |
39 | | - const commonHeadingClassName = "mb-2 leading-5 font-semibold tracking-tight"; |
40 | | - |
41 | | - return ( |
42 | | - <div className={className}> |
43 | | - <ReactMarkdown |
44 | | - components={{ |
45 | | - a: (props) => ( |
46 | | - <UnderlineLink |
47 | | - href={props.href ?? "#"} |
48 | | - rel="noopener noreferrer" |
49 | | - target="_blank" |
50 | | - {...cleanedProps(props)} |
51 | | - className="mt-4" |
52 | | - /> |
53 | | - ), |
54 | | - |
55 | | - code: ({ ...props }) => { |
56 | | - const codeStr = onlyText(props.children); |
57 | | - |
58 | | - if (props?.className || codeStr.length > 100) { |
59 | | - if (code?.disableCodeHighlight || !props.className) { |
60 | | - return ( |
61 | | - <div className="my-4"> |
62 | | - {/* @ts-expect-error - TODO: fix this */} |
63 | | - <PlainTextCodeBlock |
64 | | - {...cleanedProps(props)} |
65 | | - className={markdownProps.code?.className} |
66 | | - code={onlyText(props.children).trim()} |
67 | | - /> |
68 | | - </div> |
69 | | - ); |
70 | | - } |
71 | | - const language = props.className.replace("language-", ""); |
72 | | - return ( |
73 | | - <div className="my-4"> |
74 | | - <CodeClient |
75 | | - // @ts-expect-error - TODO: fix this |
76 | | - lang={language} |
77 | | - {...cleanedProps(props)} |
78 | | - className={markdownProps.code?.className} |
79 | | - code={onlyText(props.children).trim()} |
80 | | - ignoreFormattingErrors={code?.ignoreFormattingErrors} |
81 | | - /> |
82 | | - </div> |
83 | | - ); |
84 | | - } |
85 | | - |
86 | | - return ( |
87 | | - <InlineCode |
88 | | - className={markdownProps.inlineCode?.className} |
89 | | - code={onlyText(props.children).trim()} |
90 | | - /> |
91 | | - ); |
92 | | - }, |
93 | | - h1: (props) => ( |
94 | | - <h2 |
95 | | - className={cn( |
96 | | - commonHeadingClassName, |
97 | | - "mb-3 border-dashed border-b pb-2 text-xl md:text-2xl", |
98 | | - )} |
99 | | - {...cleanedProps(props)} |
100 | | - /> |
101 | | - ), |
102 | | - |
103 | | - h2: (props) => ( |
104 | | - <h3 |
105 | | - {...cleanedProps(props)} |
106 | | - className={cn( |
107 | | - commonHeadingClassName, |
108 | | - "mt-8 mb-3 border-dashed border-b pb-2 text-lg md:text-xl", |
109 | | - )} |
110 | | - /> |
111 | | - ), |
112 | | - |
113 | | - h3: (props) => ( |
114 | | - <h4 |
115 | | - {...cleanedProps(props)} |
116 | | - className={cn( |
117 | | - commonHeadingClassName, |
118 | | - "mt-4 text-base md:text-lg", |
119 | | - )} |
120 | | - /> |
121 | | - ), |
122 | | - |
123 | | - h4: (props) => ( |
124 | | - <h5 |
125 | | - {...cleanedProps(props)} |
126 | | - className={cn(commonHeadingClassName, "mt-4 text-lg")} |
127 | | - /> |
128 | | - ), |
129 | | - |
130 | | - h5: (props) => ( |
131 | | - <h6 |
132 | | - {...cleanedProps(props)} |
133 | | - className={cn(commonHeadingClassName, "mt-4 text-lg")} |
134 | | - /> |
135 | | - ), |
136 | | - |
137 | | - h6: (props) => ( |
138 | | - <p |
139 | | - {...cleanedProps(props)} |
140 | | - className={cn(commonHeadingClassName, "mt-4 text-lg")} |
141 | | - /> |
142 | | - ), |
143 | | - |
144 | | - hr: (props) => ( |
145 | | - <hr {...cleanedProps(props)} className="my-5 bg-border" /> |
146 | | - ), |
147 | | - li: ({ children: c, ...props }) => ( |
148 | | - <li |
149 | | - className={cn( |
150 | | - "text-muted-foreground leading-relaxed [&>p]:m-0", |
151 | | - markdownProps.li?.className, |
152 | | - )} |
153 | | - {...cleanedProps(props)} |
154 | | - > |
155 | | - {c} |
156 | | - </li> |
157 | | - ), |
158 | | - ol: (props) => ( |
159 | | - <ol |
160 | | - className="mb-4 list-outside list-decimal pl-4 space-y-2 [&>li]:first:mt-2" |
161 | | - {...cleanedProps(props)} |
162 | | - /> |
163 | | - ), |
164 | | - |
165 | | - p: (props) => ( |
166 | | - <p |
167 | | - className={cn( |
168 | | - "mb-3 text-muted-foreground leading-7", |
169 | | - markdownProps.p?.className, |
170 | | - )} |
171 | | - {...cleanedProps(props)} |
172 | | - /> |
173 | | - ), |
174 | | - strong(props) { |
175 | | - return <strong className="font-medium" {...cleanedProps(props)} />; |
176 | | - }, |
177 | | - |
178 | | - table: (props) => ( |
179 | | - <div className="mb-6"> |
180 | | - <TableContainer> |
181 | | - <Table {...cleanedProps(props)} /> |
182 | | - </TableContainer> |
183 | | - </div> |
184 | | - ), |
185 | | - tbody: (props) => <TableBody {...cleanedProps(props)} />, |
186 | | - |
187 | | - td: (props) => ( |
188 | | - <TableCell {...cleanedProps(props)} className="text-left" /> |
189 | | - ), |
190 | | - |
191 | | - th: ({ children: c, ...props }) => ( |
192 | | - <TableHead |
193 | | - {...cleanedProps(props)} |
194 | | - className="text-left text-muted-foreground" |
195 | | - > |
196 | | - {c} |
197 | | - </TableHead> |
198 | | - ), |
199 | | - thead: (props) => <TableHeader {...cleanedProps(props)} />, |
200 | | - tr: (props) => <TableRow {...cleanedProps(props)} />, |
201 | | - ul: (props) => { |
202 | | - return ( |
203 | | - <ul |
204 | | - className="mb-4 list-outside list-disc pl-4 space-y-2 [&>li]:first:mt-2" |
205 | | - {...cleanedProps(props)} |
206 | | - /> |
207 | | - ); |
208 | | - }, |
209 | | - }} |
210 | | - remarkPlugins={[remarkGfm]} |
211 | | - skipHtml={markdownProps.skipHtml} |
212 | | - > |
213 | | - {markdownText} |
214 | | - </ReactMarkdown> |
215 | | - </div> |
216 | | - ); |
217 | | -}; |
218 | | - |
219 | | -function cleanedProps<T extends object & { node?: unknown }>( |
220 | | - props: T, |
221 | | -): Omit<T, "node"> { |
222 | | - // biome-ignore lint/correctness/noUnusedVariables: EXPECTED |
223 | | - const { node, ...rest } = props; |
224 | | - return rest; |
225 | | -} |
| 1 | +export { MarkdownRenderer } from "@workspace/ui/components/markdown-renderer"; |
0 commit comments