11"use client" ;
22
3- import Link from "next/link " ;
3+ import { sharedMdxComponents } from "@/components/mdx-shared-components " ;
44import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock" ;
55import type {
66 Blockquote ,
77 Code ,
88 Content ,
99 Emphasis ,
1010 Heading ,
11- HTML ,
1211 InlineCode ,
1312 Link as MdLink ,
1413 List ,
@@ -20,69 +19,70 @@ import type {
2019 TableCell ,
2120 TableRow ,
2221 Text ,
23- ThematicBreak ,
2422} from "mdast" ;
25- import { useMemo , type ReactNode } from "react" ;
23+ import { createElement , Fragment , useMemo , type ElementType , type ReactNode } from "react" ;
2624import remarkGfm from "remark-gfm" ;
2725import remarkParse from "remark-parse" ;
2826import { unified } from "unified" ;
2927
3028const markdownProcessor = unified ( ) . use ( remarkParse ) . use ( remarkGfm ) ;
3129
32- function toInternalSeedHref ( url : string ) : string | null {
33- try {
34- const parsed = new URL ( url ) ;
35- const isSeedDomain =
36- parsed . hostname === "seed-design.io" || parsed . hostname === "www.seed-design.io" ;
37- if ( ! isSeedDomain ) return null ;
38-
39- return `${ parsed . pathname } ${ parsed . search } ${ parsed . hash } ` ;
40- } catch {
41- return null ;
42- }
30+ type HtmlTag =
31+ | "h1"
32+ | "h2"
33+ | "h3"
34+ | "h4"
35+ | "h5"
36+ | "h6"
37+ | "p"
38+ | "strong"
39+ | "em"
40+ | "code"
41+ | "a"
42+ | "ol"
43+ | "ul"
44+ | "li"
45+ | "blockquote"
46+ | "hr"
47+ | "td" ;
48+
49+ function renderTag (
50+ tag : HtmlTag ,
51+ props : Record < string , unknown > ,
52+ key : string ,
53+ children ?: ReactNode ,
54+ ) {
55+ const component = ( sharedMdxComponents as Record < string , ElementType | undefined > ) [ tag ] ?? tag ;
56+ return createElement ( component , { ...props , key } , children ) ;
4357}
4458
4559function renderInlineNode ( node : Content , key : string ) : ReactNode {
4660 switch ( node . type ) {
4761 case "text" :
48- return < span key = { key } > { ( node as Text ) . value } </ span > ;
62+ return ( node as Text ) . value ;
4963 case "strong" :
50- return (
51- < strong key = { key } >
52- { ( node as Strong ) . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) }
53- </ strong >
64+ return renderTag (
65+ "strong" ,
66+ { } ,
67+ key ,
68+ ( node as Strong ) . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ,
5469 ) ;
5570 case "emphasis" :
56- return (
57- < em key = { key } >
58- { ( node as Emphasis ) . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) }
59- </ em >
71+ return renderTag (
72+ "em" ,
73+ { } ,
74+ key ,
75+ ( node as Emphasis ) . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ,
6076 ) ;
6177 case "inlineCode" :
62- return < code key = { key } > { ( node as InlineCode ) . value } </ code > ;
78+ return renderTag ( " code" , { } , key , ( node as InlineCode ) . value ) ;
6379 case "link" : {
6480 const linkNode = node as MdLink ;
65- const internalHref = toInternalSeedHref ( linkNode . url ) ;
66- const children = linkNode . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ;
67-
68- if ( internalHref ) {
69- return (
70- < Link key = { key } href = { internalHref } className = "text-fd-primary hover:underline break-all" >
71- { children }
72- </ Link >
73- ) ;
74- }
75-
76- return (
77- < a
78- key = { key }
79- href = { linkNode . url }
80- target = "_blank"
81- rel = "noreferrer"
82- className = "text-fd-primary hover:underline break-all"
83- >
84- { children }
85- </ a >
81+ return renderTag (
82+ "a" ,
83+ { href : linkNode . url } ,
84+ key ,
85+ linkNode . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ,
8686 ) ;
8787 }
8888 case "break" :
@@ -96,67 +96,65 @@ function renderTableCell(cell: TableCell, key: string, align?: "left" | "right"
9696 const className =
9797 align === "right" ? "text-right" : align === "center" ? "text-center" : "text-left" ;
9898
99- return (
100- < td key = { key } className = { `px-2 py-1 align-top ${ className } ` } >
101- { cell . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) }
102- </ td >
99+ return renderTag (
100+ "td" ,
101+ { className : `px-2 py-1 align-top ${ className } ` } ,
102+ key ,
103+ cell . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ,
103104 ) ;
104105}
105106
106107function renderBlockNode ( node : Content , key : string ) : ReactNode {
107108 switch ( node . type ) {
108109 case "heading" : {
109110 const headingNode = node as Heading ;
110- const content = headingNode . children . map ( ( child , i ) =>
111- renderInlineNode ( child , `${ key } -${ i } ` ) ,
111+ const tagName = `h${ Math . min ( headingNode . depth , 6 ) } ` as HtmlTag ;
112+ return renderTag (
113+ tagName ,
114+ { } ,
115+ key ,
116+ headingNode . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ,
112117 ) ;
113-
114- if ( headingNode . depth <= 2 ) return < h2 key = { key } > { content } </ h2 > ;
115- if ( headingNode . depth === 3 ) return < h3 key = { key } > { content } </ h3 > ;
116- if ( headingNode . depth === 4 ) return < h4 key = { key } > { content } </ h4 > ;
117- return < h5 key = { key } > { content } </ h5 > ;
118118 }
119-
120119 case "paragraph" : {
121120 const paragraphNode = node as Paragraph ;
122- return (
123- < p key = { key } >
124- { paragraphNode . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) }
125- </ p >
121+ return renderTag (
122+ "p" ,
123+ { } ,
124+ key ,
125+ paragraphNode . children . map ( ( child , i ) => renderInlineNode ( child , `${ key } -${ i } ` ) ) ,
126126 ) ;
127127 }
128-
129128 case "list" : {
130129 const listNode = node as List ;
131- const Element = listNode . ordered ? "ol" : "ul" ;
132- return (
133- < Element key = { key } >
134- { listNode . children . map ( ( child , i ) => renderBlockNode ( child , `${ key } -${ i } ` ) ) }
135- </ Element >
130+ const tagName = listNode . ordered ? "ol" : "ul" ;
131+ return renderTag (
132+ tagName ,
133+ { } ,
134+ key ,
135+ listNode . children . map ( ( child , i ) => renderBlockNode ( child , `${ key } -${ i } ` ) ) ,
136136 ) ;
137137 }
138-
139138 case "listItem" : {
140139 const listItem = node as ListItem ;
141- return (
142- < li key = { key } >
143- { listItem . children . map ( ( child , i ) => renderBlockNode ( child , `${ key } -${ i } ` ) ) }
144- </ li >
140+ return renderTag (
141+ "li" ,
142+ { } ,
143+ key ,
144+ listItem . children . map ( ( child , i ) => renderBlockNode ( child , `${ key } -${ i } ` ) ) ,
145145 ) ;
146146 }
147-
148147 case "blockquote" : {
149148 const blockquote = node as Blockquote ;
150- return (
151- < blockquote key = { key } >
152- { blockquote . children . map ( ( child , i ) => renderBlockNode ( child , `${ key } -${ i } ` ) ) }
153- </ blockquote >
149+ return renderTag (
150+ "blockquote" ,
151+ { } ,
152+ key ,
153+ blockquote . children . map ( ( child , i ) => renderBlockNode ( child , `${ key } -${ i } ` ) ) ,
154154 ) ;
155155 }
156-
157156 case "thematicBreak" :
158- return < hr key = { key } /> ;
159-
157+ return renderTag ( "hr" , { } , key ) ;
160158 case "code" : {
161159 const codeNode = node as Code ;
162160 return (
@@ -165,10 +163,10 @@ function renderBlockNode(node: Content, key: string): ReactNode {
165163 </ div >
166164 ) ;
167165 }
168-
169166 case "table" : {
170167 const tableNode = node as Table ;
171168 const [ head , ...body ] = tableNode . children as TableRow [ ] ;
169+
172170 return (
173171 < div key = { key } className = "my-2 overflow-x-auto" >
174172 < table className = "w-full border-collapse text-sm" >
@@ -211,10 +209,6 @@ function renderBlockNode(node: Content, key: string): ReactNode {
211209 </ div >
212210 ) ;
213211 }
214-
215- case "html" :
216- return < p key = { key } > { ( node as HTML ) . value } </ p > ;
217-
218212 default :
219213 return null ;
220214 }
@@ -237,7 +231,9 @@ export function ChatMarkdown({ markdown }: { markdown: string }) {
237231
238232 return (
239233 < div className = "prose prose-sm dark:prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0" >
240- { root . children . map ( ( node , i ) => renderBlockNode ( node , `chat-md-${ i } ` ) ) }
234+ { root . children . map ( ( node , i ) => (
235+ < Fragment key = { `chat-md-${ i } ` } > { renderBlockNode ( node , `chat-md-${ i } ` ) } </ Fragment >
236+ ) ) }
241237 </ div >
242238 ) ;
243239}
0 commit comments