@@ -97,9 +97,11 @@ function matchPlaygroundProvider(url: string): PlaygroundProvider | null {
9797 return null
9898}
9999
100- // only allow h3 -h6 since we shift README headings down by 2 levels
100+ // allow h1 -h6, but replace h1-h2 later since we shift README headings down by 2 levels
101101// (page h1 = package name, h2 = "Readme" section, so README h1 → h3)
102102const ALLOWED_TAGS = [
103+ 'h1' ,
104+ 'h2' ,
103105 'h3' ,
104106 'h4' ,
105107 'h5' ,
@@ -272,6 +274,15 @@ function prefixId(tagName: string, attribs: sanitizeHtml.Attributes) {
272274 return { tagName, attribs }
273275}
274276
277+ // README h1 always becomes h3
278+ // For deeper levels, ensure sequential order
279+ // Don't allow jumping more than 1 level deeper than previous
280+ function calculateSemanticDepth ( depth : number , lastSemanticLevel : number ) {
281+ if ( depth === 1 ) return 3
282+ const maxAllowed = Math . min ( lastSemanticLevel + 1 , 6 )
283+ return Math . min ( depth + 2 , maxAllowed )
284+ }
285+
275286export async function renderReadmeHtml (
276287 content : string ,
277288 packageName : string ,
@@ -301,17 +312,7 @@ export async function renderReadmeHtml(
301312 // Calculate the target semantic level based on document structure
302313 // Start at h3 (since page h1 + section h2 already exist)
303314 // But ensure we never skip levels - can only go down by 1 or stay same/go up
304- let semanticLevel : number
305- if ( depth === 1 ) {
306- // README h1 always becomes h3
307- semanticLevel = 3
308- } else {
309- // For deeper levels, ensure sequential order
310- // Don't allow jumping more than 1 level deeper than previous
311- const maxAllowed = Math . min ( lastSemanticLevel + 1 , 6 )
312- semanticLevel = Math . min ( depth + 2 , maxAllowed )
313- }
314-
315+ const semanticLevel = calculateSemanticDepth ( depth , lastSemanticLevel )
315316 lastSemanticLevel = semanticLevel
316317 const text = this . parser . parseInline ( tokens )
317318
@@ -414,6 +415,28 @@ ${html}
414415 allowedSchemes : [ 'http' , 'https' , 'mailto' ] ,
415416 // Transform img src URLs (GitHub blob → raw, relative → GitHub raw)
416417 transformTags : {
418+ h1 : ( _ , attribs ) => {
419+ return { tagName : 'h3' , attribs : { ...attribs , 'data-level' : '1' } }
420+ } ,
421+ h2 : ( _ , attribs ) => {
422+ return { tagName : 'h4' , attribs : { ...attribs , 'data-level' : '2' } }
423+ } ,
424+ h3 : ( _ , attribs ) => {
425+ if ( attribs [ 'data-level' ] ) return { tagName : 'h3' , attribs : attribs }
426+ return { tagName : 'h5' , attribs : { ...attribs , 'data-level' : '3' } }
427+ } ,
428+ h4 : ( _ , attribs ) => {
429+ if ( attribs [ 'data-level' ] ) return { tagName : 'h4' , attribs : attribs }
430+ return { tagName : 'h6' , attribs : { ...attribs , 'data-level' : '4' } }
431+ } ,
432+ h5 : ( _ , attribs ) => {
433+ if ( attribs [ 'data-level' ] ) return { tagName : 'h5' , attribs : attribs }
434+ return { tagName : 'h6' , attribs : { ...attribs , 'data-level' : '5' } }
435+ } ,
436+ h6 : ( _ , attribs ) => {
437+ if ( attribs [ 'data-level' ] ) return { tagName : 'h6' , attribs : attribs }
438+ return { tagName : 'h6' , attribs : { ...attribs , 'data-level' : '6' } }
439+ } ,
417440 img : ( tagName , attribs ) => {
418441 if ( attribs . src ) {
419442 attribs . src = resolveImageUrl ( attribs . src , packageName , repoInfo )
0 commit comments