@@ -18,6 +18,7 @@ type Token =
1818 | { type : 'blockquote' , children : Token [ ] }
1919 | { type : 'codeblock' , language ?: string , content : string }
2020 | { type : 'hr' }
21+ | { type : 'table' , header : Token [ ] [ ] , rows : Token [ ] [ ] [ ] }
2122
2223function parseMarkdown ( text : string ) : Token [ ] {
2324 const tokens : Token [ ] = [ ]
@@ -106,6 +107,31 @@ function parseMarkdown(text: string): Token[] {
106107 continue
107108 }
108109
110+ // Table (requires header line | separator line)
111+ if ( line . includes ( '|' ) && i + 1 < lines . length ) {
112+ const sepLine = lines [ i + 1 ] ?? ''
113+ // Check if the next line is a valid table separator
114+ // Extended markdown alignment syntax: |:--|, |:--:|, |--:|
115+ if ( / ^ \s * \| ? ( \s * : ? - + : ? \s * \| ) + \s * : ? - + : ? \s * \| ? \s * $ / . test ( sepLine ) ) {
116+ // collect header cells
117+ const headerCells = splitTableRow ( line )
118+ i += 2
119+
120+ const rows : Token [ ] [ ] [ ] = [ ]
121+ while ( i < lines . length && lines [ i ] ?. includes ( '|' ) ) {
122+ rows . push ( splitTableRow ( lines [ i ] ?? '' ) . map ( c => parseInline ( c ) ) )
123+ i ++
124+ }
125+
126+ tokens . push ( {
127+ type : 'table' ,
128+ header : headerCells . map ( c => parseInline ( c ) ) ,
129+ rows,
130+ } )
131+ continue
132+ }
133+ }
134+
109135 // Paragraph
110136 const paraLines : string [ ] = [ ]
111137 while ( i < lines . length ) {
@@ -416,6 +442,16 @@ function parseInlineRecursive(text: string, stop?: string): [Token[], number] {
416442 return [ tokens , i ]
417443}
418444
445+ /** Split a table row, trimming outer pipes and whitespace */
446+ function splitTableRow ( row : string ) : string [ ] {
447+ return row
448+ . trim ( )
449+ . replace ( / ^ \| / , '' )
450+ . replace ( / \| $ / , '' )
451+ . split ( '|' )
452+ . map ( cell => cell . trim ( ) )
453+ }
454+
419455function isOpeningUnderscore ( text : string , pos : number ) : boolean {
420456 const prev = text [ pos - 1 ] ?? '\n'
421457 const next = text [ pos + 1 ] ?? '\n'
@@ -510,6 +546,43 @@ function renderTokens(tokens: Token[], keyPrefix = ''): ReactNode[] {
510546 )
511547 case 'hr' :
512548 return createElement ( 'hr' , { key } )
549+ case 'table' : {
550+ const thead = createElement (
551+ 'thead' ,
552+ null ,
553+ createElement (
554+ 'tr' ,
555+ null ,
556+ token . header . map ( ( cell , c ) =>
557+ createElement (
558+ 'th' ,
559+ { key : `${ key } -h${ c } ` } ,
560+ renderTokens ( cell , `${ key } -h${ c } -` )
561+ )
562+ )
563+ )
564+ )
565+
566+ const tbody = createElement (
567+ 'tbody' ,
568+ null ,
569+ token . rows . map ( ( row , r ) =>
570+ createElement (
571+ 'tr' ,
572+ { key : `${ key } -r${ r } ` } ,
573+ row . map ( ( cell , c ) =>
574+ createElement (
575+ 'td' ,
576+ { key : `${ key } -r${ r } c${ c } ` } ,
577+ renderTokens ( cell , `${ key } -r${ r } c${ c } -` )
578+ )
579+ )
580+ )
581+ )
582+ )
583+
584+ return createElement ( 'table' , { key } , [ thead , tbody ] )
585+ }
513586 default :
514587 return null
515588 }
0 commit comments