@@ -219,6 +219,14 @@ function slugify(text: string): string {
219219 . replace ( / ^ - | - $ / g, '' ) // Trim leading/trailing hyphens
220220}
221221
222+ function getHeadingPlainText ( text : string ) : string {
223+ return decodeHtmlEntities ( stripHtmlTags ( text ) . trim ( ) )
224+ }
225+
226+ function getHeadingSlugSource ( text : string ) : string {
227+ return stripHtmlTags ( text ) . trim ( )
228+ }
229+
222230/**
223231 * Lazy ATX heading extension for marked: allows headings without a space after `#`.
224232 *
@@ -457,11 +465,17 @@ export async function renderReadmeHtml(
457465 let lastSemanticLevel = 2 // Start after h2 (the "Readme" section heading)
458466
459467 // Shared heading processing for both markdown and HTML headings
460- function processHeading ( depth : number , plainText : string , preservedAttrs = '' ) {
468+ function processHeading (
469+ depth : number ,
470+ displayHtml : string ,
471+ plainText : string ,
472+ slugSource : string ,
473+ preservedAttrs = '' ,
474+ ) {
461475 const semanticLevel = calculateSemanticDepth ( depth , lastSemanticLevel )
462476 lastSemanticLevel = semanticLevel
463477
464- let slug = slugify ( plainText )
478+ let slug = slugify ( slugSource )
465479 if ( ! slug ) slug = 'heading'
466480
467481 const count = usedSlugs . get ( slug ) ?? 0
@@ -473,13 +487,14 @@ export async function renderReadmeHtml(
473487 toc . push ( { text : plainText , id, depth } )
474488 }
475489
476- return `<h${ semanticLevel } id="${ id } " data-level="${ depth } "${ preservedAttrs } ><a href="#${ id } ">${ plainText } </a></h${ semanticLevel } >\n`
490+ return `<h${ semanticLevel } id="${ id } " data-level="${ depth } "${ preservedAttrs } ><a href="#${ id } ">${ displayHtml } </a></h${ semanticLevel } >\n`
477491 }
478492
479493 renderer . heading = function ( { tokens, depth } : Tokens . Heading ) {
480- const text = this . parser . parseInline ( tokens )
481- const plainText = decodeHtmlEntities ( stripHtmlTags ( text ) . trim ( ) )
482- return processHeading ( depth , plainText )
494+ const displayHtml = this . parser . parseInline ( tokens )
495+ const plainText = getHeadingPlainText ( displayHtml )
496+ const slugSource = getHeadingSlugSource ( displayHtml )
497+ return processHeading ( depth , displayHtml , plainText , slugSource )
483498 }
484499
485500 // Intercept HTML headings so they get id, TOC entry, and correct semantic level.
@@ -489,10 +504,11 @@ export async function renderReadmeHtml(
489504 renderer . html = function ( { text } : Tokens . HTML ) {
490505 let result = text . replace ( htmlHeadingRe , ( _ , level , attrs = '' , inner ) => {
491506 const depth = parseInt ( level )
492- const plainText = decodeHtmlEntities ( stripHtmlTags ( inner ) . trim ( ) )
507+ const plainText = getHeadingPlainText ( inner )
508+ const slugSource = getHeadingSlugSource ( inner )
493509 const align = / \b a l i g n = ( [ " ' ] ) ( .* ?) \1/ i. exec ( attrs ) ?. [ 2 ]
494510 const preservedAttrs = align ? ` align="${ align } "` : ''
495- return processHeading ( depth , plainText , preservedAttrs ) . trimEnd ( )
511+ return processHeading ( depth , inner , plainText , slugSource , preservedAttrs ) . trimEnd ( )
496512 } )
497513 // Process raw HTML <a> tags for playground link collection and URL resolution
498514 result = result . replace ( htmlAnchorRe , ( _full , beforeHref , _quote , href , afterHref , inner ) => {
0 commit comments