Skip to content

Commit 299a877

Browse files
authored
fix: allow all heading depths in readme and transform them (#1306)
1 parent d61b472 commit 299a877

File tree

1 file changed

+35
-12
lines changed

1 file changed

+35
-12
lines changed

server/utils/readme.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
102102
const 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+
275286
export 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

Comments
 (0)