Normalize site TOC heading hierarchy
This commit is contained in:
parent
35f4c968a4
commit
1bababa058
@ -176,7 +176,7 @@ private fun TocNav(
|
||||
Ul({ classes("list-unstyled", "mb-0") }) {
|
||||
toc.forEach { item ->
|
||||
Li({ classes("mb-1") }) {
|
||||
val pad = when (item.level) { 1 -> "0"; 2 -> "0.75rem"; else -> "1.5rem" }
|
||||
val pad = "${(item.level - 1) * 0.75}rem"
|
||||
val routeNoFrag = route.substringBefore('#')
|
||||
val tocHref = "#/$routeNoFrag#${item.id}"
|
||||
A(attrs = {
|
||||
|
||||
@ -1016,17 +1016,21 @@ fun rewriteAnchors(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TocTreeNode(
|
||||
val headingLevel: Int,
|
||||
val id: String,
|
||||
val title: String,
|
||||
val children: MutableList<TocTreeNode> = mutableListOf(),
|
||||
)
|
||||
|
||||
fun buildToc(root: HTMLElement): List<TocItem> {
|
||||
val out = mutableListOf<TocItem>()
|
||||
val used = hashSetOf<String>()
|
||||
val hs = root.querySelectorAll("h1, h2, h3")
|
||||
val tocRoot = TocTreeNode(headingLevel = 0, id = "", title = "")
|
||||
val stack = mutableListOf(tocRoot)
|
||||
val hs = root.querySelectorAll("h1, h2, h3, h4, h5, h6")
|
||||
for (i in 0 until hs.length) {
|
||||
val h = hs.item(i) as? HTMLHeadingElement ?: continue
|
||||
val level = when (h.tagName.uppercase()) {
|
||||
"H1" -> 1
|
||||
"H2" -> 2
|
||||
else -> 3
|
||||
}
|
||||
val level = h.tagName.removePrefix("H").toIntOrNull() ?: continue
|
||||
var id = h.id.ifBlank { slugify(h.textContent ?: "") }
|
||||
if (id.isBlank()) id = "section-${i + 1}"
|
||||
var unique = id
|
||||
@ -1036,8 +1040,19 @@ fun buildToc(root: HTMLElement): List<TocItem> {
|
||||
n++
|
||||
}
|
||||
h.id = unique
|
||||
out += TocItem(level, unique, h.textContent ?: "")
|
||||
|
||||
while (stack.last().headingLevel >= level) stack.removeAt(stack.lastIndex)
|
||||
val node = TocTreeNode(level, unique, h.textContent ?: "")
|
||||
stack.last().children += node
|
||||
stack += node
|
||||
}
|
||||
|
||||
val out = mutableListOf<TocItem>()
|
||||
fun visit(node: TocTreeNode, normalizedLevel: Int) {
|
||||
if (node.id.isNotEmpty()) out += TocItem(normalizedLevel, node.id, node.title)
|
||||
node.children.forEach { child -> visit(child, normalizedLevel + 1) }
|
||||
}
|
||||
tocRoot.children.forEach { child -> visit(child, 1) }
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@ -71,5 +71,37 @@ class RouteAndDomRewriteTest {
|
||||
val el = root.ownerDocument?.getElementById(id) ?: root.querySelector("#${id}")
|
||||
assertNotNull(el, "Heading with id $id should be present in DOM")
|
||||
}
|
||||
|
||||
assertEquals(listOf(1, 2, 2, 3), toc.map { it.level })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildToc_normalizesStartingLevelAndGaps() {
|
||||
val root = document.createElement("div") as HTMLElement
|
||||
root.innerHTML = """
|
||||
<h3>Top</h3>
|
||||
<h5>Deep child</h5>
|
||||
<h4>Middle sibling</h4>
|
||||
<h6>Nested</h6>
|
||||
""".trimIndent()
|
||||
|
||||
val toc = buildToc(root)
|
||||
|
||||
assertEquals(listOf("Top", "Deep child", "Middle sibling", "Nested"), toc.map { it.title })
|
||||
assertEquals(listOf(1, 2, 2, 3), toc.map { it.level })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildToc_supportsAllHeadingLevels() {
|
||||
val root = document.createElement("div") as HTMLElement
|
||||
root.innerHTML = """
|
||||
<h4>Start</h4>
|
||||
<h5>Child</h5>
|
||||
<h6>Grandchild</h6>
|
||||
""".trimIndent()
|
||||
|
||||
val toc = buildToc(root)
|
||||
|
||||
assertEquals(listOf(1, 2, 3), toc.map { it.level })
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user