@@ -16,9 +16,8 @@ const parseInlineMarkdown = (line: string): React.ReactNode[] => {
1616 { regex : / \* \* ( .* ?) \* \* / , tag : 'strong' } ,
1717 { regex : / \* ( .* ?) \* / , tag : 'em' } ,
1818 { regex : / ` ( [ ^ ` ] + ) ` / , tag : 'code' } ,
19- { regex : / \[ ( [ ^ \] ] + ) \ ]\( ( [ ^ ) ] + ) \) / , tag : 'a' } ,
19+ { regex : / \[ ( [ ^ \] ] + ) ] \( ( [ ^ ) ] + ) \) / , tag : 'a' } ,
2020 { regex : / ( h t t p s ? : \/ \/ [ ^ \s ) ] + [ ^ \s ) . , ; : ' " \] \s ] ) / , tag : 'auto-link' } ,
21- { regex : / - - - / , tag : 'hr' } ,
2221 ] ;
2322
2423 while ( line . length ) {
@@ -53,9 +52,10 @@ const parseInlineMarkdown = (line: string): React.ReactNode[] => {
5352 </ code >
5453 ) ;
5554 } else if ( pattern . tag === 'a' ) {
55+ const isUrl = / ^ h t t p s ? : \/ \/ / . test ( content ) ;
5656 parts . push (
5757 < a key = { parts . length } href = { match [ 2 ] } target = "_blank" rel = "noreferrer" >
58- { content }
58+ { isUrl ? match [ 2 ] : content }
5959 </ a >
6060 ) ;
6161 } else if ( pattern . tag === 'auto-link' ) {
@@ -64,8 +64,6 @@ const parseInlineMarkdown = (line: string): React.ReactNode[] => {
6464 { match [ 1 ] }
6565 </ a >
6666 ) ;
67- } else if ( pattern . tag === 'hr' ) {
68- parts . push ( < hr key = { parts . length } style = { { border : '1px solid #ccc' , margin : '16px 0' } } /> ) ;
6967 }
7068
7169 line = remaining ;
@@ -101,19 +99,65 @@ const parseMarkdown = (raw: string) => {
10199 }
102100 } ;
103101
104- lines . forEach ( ( line , index ) => {
102+ let i = 0 ;
103+ while ( i < lines . length ) {
104+ const line = lines [ i ] ;
105+
106+ if ( / ^ ( \s * ) ( [ - * _ ] ) ( \s * \2) { 2 , } \s * $ / . test ( line ) ) {
107+ flushList ( i ) ;
108+ elements . push (
109+ < hr key = { `hr-${ i } ` } style = { { margin : '16px 0' , border : 'none' , borderTop : '1px solid #ccc' } } />
110+ ) ;
111+ i ++ ;
112+ continue ;
113+ }
114+
115+ if ( / ^ \| ( .+ ) \| $ / . test ( line ) && i + 1 < lines . length && / ^ \| [ - \s | ] + ?\| $ / . test ( lines [ i + 1 ] ) ) {
116+ flushList ( i ) ;
117+ const headers = line . split ( '|' ) . slice ( 1 , - 1 ) . map ( h => h . trim ( ) ) ;
118+ const rows : string [ ] [ ] = [ ] ;
119+ i += 2 ;
120+
121+ while ( i < lines . length && / ^ \| ( .+ ) \| $ / . test ( lines [ i ] ) ) {
122+ const cells = lines [ i ] . split ( '|' ) . slice ( 1 , - 1 ) . map ( c => c . trim ( ) ) ;
123+ rows . push ( cells ) ;
124+ i ++ ;
125+ }
126+
127+ elements . push (
128+ < table key = { `table-${ i } ` } style = { { borderCollapse : 'collapse' , margin : '12px 0' } } >
129+ < thead >
130+ < tr >
131+ { headers . map ( ( h , j ) => (
132+ < th key = { `th-${ j } ` } style = { { border : '1px solid #ccc' , padding : 6 , background : '#f7f7f7' } } >
133+ { parseInlineMarkdown ( h ) }
134+ </ th >
135+ ) ) }
136+ </ tr >
137+ </ thead >
138+ < tbody >
139+ { rows . map ( ( row , rowIndex ) => (
140+ < tr key = { `tr-${ rowIndex } ` } >
141+ { row . map ( ( cell , colIndex ) => (
142+ < td key = { `td-${ rowIndex } -${ colIndex } ` } style = { { border : '1px solid #ccc' , padding : 6 } } >
143+ { parseInlineMarkdown ( cell ) }
144+ </ td >
145+ ) ) }
146+ </ tr >
147+ ) ) }
148+ </ tbody >
149+ </ table >
150+ ) ;
151+ continue ;
152+ }
153+
105154 if ( line . trim ( ) . startsWith ( '```' ) ) {
106- flushList ( index ) ;
155+ flushList ( i ) ;
107156 if ( inCodeBlock ) {
108157 elements . push (
109158 < pre
110- key = { `code-${ index } ` }
111- style = { {
112- background : '#1e1e1e' ,
113- color : '#fff' ,
114- padding : 12 ,
115- overflowX : 'auto' ,
116- } }
159+ key = { `code-${ i } ` }
160+ style = { { background : '#1e1e1e' , color : '#fff' , padding : 12 , overflowX : 'auto' } }
117161 >
118162 < code > { codeBuffer . join ( '\n' ) } </ code >
119163 </ pre >
@@ -123,64 +167,60 @@ const parseMarkdown = (raw: string) => {
123167 } else {
124168 inCodeBlock = true ;
125169 }
126- return ;
170+ i ++ ;
171+ continue ;
127172 }
128173
129174 if ( inCodeBlock ) {
130175 codeBuffer . push ( line ) ;
131- return ;
176+ i ++ ;
177+ continue ;
132178 }
133179
134- // blockquote
135180 if ( line . startsWith ( '>' ) ) {
136- flushList ( index ) ;
181+ flushList ( i ) ;
137182 elements . push (
138- < blockquote
139- key = { `quote-${ index } ` }
140- style = { {
141- borderLeft : '4px solid #ccc' ,
142- paddingLeft : 12 ,
143- margin : '12px 0' ,
144- } }
145- >
183+ < blockquote key = { `quote-${ i } ` } style = { { borderLeft : '4px solid #ccc' , paddingLeft : 12 , margin : '12px 0' } } >
146184 { parseInlineMarkdown ( line . slice ( 1 ) . trim ( ) ) }
147185 </ blockquote >
148186 ) ;
149- return ;
187+ i ++ ;
188+ continue ;
150189 }
151190
152- // heading
153191 const headingMatch = line . match ( / ^ ( # { 1 , 6 } ) \s + ( .* ) / ) ;
154192 if ( headingMatch ) {
155- flushList ( index ) ;
193+ flushList ( i ) ;
156194 const level = headingMatch [ 1 ] . length ;
157195 const HeadingTag = `h${ level } ` as keyof JSX . IntrinsicElements ;
158196 elements . push (
159- < HeadingTag key = { `h-${ index } ` } style = { { margin : '12px 0' } } >
197+ < HeadingTag key = { `h-${ i } ` } style = { { margin : '12px 0' } } >
160198 { parseInlineMarkdown ( headingMatch [ 2 ] ) }
161199 </ HeadingTag >
162200 ) ;
163- return ;
201+ i ++ ;
202+ continue ;
164203 }
165204
166- // list
167205 if ( / ^ \s * [ - * + ] \s + / . test ( line ) ) {
168206 const content = line . replace ( / ^ [ - * + ] \s + / , '' ) ;
169- currentListItems . push ( < li key = { `li-${ index } ` } > { parseInlineMarkdown ( content ) } </ li > ) ;
170- return ;
207+ currentListItems . push ( < li key = { `li-${ i } ` } > { parseInlineMarkdown ( content ) } </ li > ) ;
208+ i ++ ;
209+ continue ;
171210 } else {
172- flushList ( index ) ;
211+ flushList ( i ) ;
173212 }
174213
175214 const trimmed = line . trim ( ) ;
176215 if ( trimmed . length > 0 ) {
177216 elements . push (
178- < p key = { `p-${ index } ` } > { parseInlineMarkdown ( trimmed ) } </ p >
217+ < p key = { `p-${ i } ` } > { parseInlineMarkdown ( trimmed ) } </ p >
179218 ) ;
180219 }
181- } ) ;
182220
183- // Flush any remaining list at end
221+ i ++ ;
222+ }
223+
184224 flushList ( lines . length ) ;
185225
186226 return elements ;
@@ -190,4 +230,4 @@ const MarkdownToHTMLRenderer: React.FC<MarkdownToHTMLRendererProps> = ({ markdow
190230 return < div style = { { fontFamily : 'system-ui' , lineHeight : 1.6 } } > { parseMarkdown ( markdown ) } </ div > ;
191231} ;
192232
193- export default MarkdownToHTMLRenderer ;
233+ export default MarkdownToHTMLRenderer ;
0 commit comments