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") }) {
|
Ul({ classes("list-unstyled", "mb-0") }) {
|
||||||
toc.forEach { item ->
|
toc.forEach { item ->
|
||||||
Li({ classes("mb-1") }) {
|
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 routeNoFrag = route.substringBefore('#')
|
||||||
val tocHref = "#/$routeNoFrag#${item.id}"
|
val tocHref = "#/$routeNoFrag#${item.id}"
|
||||||
A(attrs = {
|
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> {
|
fun buildToc(root: HTMLElement): List<TocItem> {
|
||||||
val out = mutableListOf<TocItem>()
|
|
||||||
val used = hashSetOf<String>()
|
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) {
|
for (i in 0 until hs.length) {
|
||||||
val h = hs.item(i) as? HTMLHeadingElement ?: continue
|
val h = hs.item(i) as? HTMLHeadingElement ?: continue
|
||||||
val level = when (h.tagName.uppercase()) {
|
val level = h.tagName.removePrefix("H").toIntOrNull() ?: continue
|
||||||
"H1" -> 1
|
|
||||||
"H2" -> 2
|
|
||||||
else -> 3
|
|
||||||
}
|
|
||||||
var id = h.id.ifBlank { slugify(h.textContent ?: "") }
|
var id = h.id.ifBlank { slugify(h.textContent ?: "") }
|
||||||
if (id.isBlank()) id = "section-${i + 1}"
|
if (id.isBlank()) id = "section-${i + 1}"
|
||||||
var unique = id
|
var unique = id
|
||||||
@ -1036,8 +1040,19 @@ fun buildToc(root: HTMLElement): List<TocItem> {
|
|||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
h.id = unique
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -71,5 +71,37 @@ class RouteAndDomRewriteTest {
|
|||||||
val el = root.ownerDocument?.getElementById(id) ?: root.querySelector("#${id}")
|
val el = root.ownerDocument?.getElementById(id) ?: root.querySelector("#${id}")
|
||||||
assertNotNull(el, "Heading with id $id should be present in DOM")
|
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