@@ -497,6 +497,17 @@ export async function renderReadmeHtml(
497497 return processHeading ( depth , displayHtml , plainText , slugSource )
498498 }
499499
500+ // Extract and preserve allowed attributes from HTML heading tags
501+ function extractHeadingAttrs ( attrsString : string ) : string {
502+ if ( ! attrsString ) return ''
503+ const preserved : string [ ] = [ ]
504+ const alignMatch = / \b a l i g n = ( [ " ' ] ? ) ( [ ^ " ' \s > ] + ) \1/ i. exec ( attrsString )
505+ if ( alignMatch ?. [ 2 ] ) {
506+ preserved . push ( `align="${ alignMatch [ 2 ] } "` )
507+ }
508+ return preserved . length > 0 ? ` ${ preserved . join ( ' ' ) } ` : ''
509+ }
510+
500511 // Intercept HTML headings so they get id, TOC entry, and correct semantic level.
501512 // Also intercept raw HTML <a> tags so playground links are collected in the same pass.
502513 const htmlHeadingRe = / < h ( [ 1 - 6 ] ) ( \s [ ^ > ] * ) ? > ( [ \s \S ] * ?) < \/ h \1> / gi
@@ -506,8 +517,7 @@ export async function renderReadmeHtml(
506517 const depth = parseInt ( level )
507518 const plainText = getHeadingPlainText ( inner )
508519 const slugSource = getHeadingSlugSource ( inner )
509- const align = / \b a l i g n = ( [ " ' ] ) ( .* ?) \1/ i. exec ( attrs ) ?. [ 2 ]
510- const preservedAttrs = align ? ` align="${ align } "` : ''
520+ const preservedAttrs = extractHeadingAttrs ( attrs )
511521 return processHeading ( depth , inner , plainText , slugSource , preservedAttrs ) . trimEnd ( )
512522 } )
513523 // Process raw HTML <a> tags for playground link collection and URL resolution
0 commit comments