Skip to content

Instantly share code, notes, and snippets.

@CarsonSlovoka
Last active May 16, 2024 14:14
Show Gist options
  • Save CarsonSlovoka/bf7cd5a172c24e89fbe31915776b73e5 to your computer and use it in GitHub Desktop.
Save CarsonSlovoka/bf7cd5a172c24e89fbe31915776b73e5 to your computer and use it in GitHub Desktop.
Create the TOC (table of content) by javascript.
<style>
/* CSS is not necessary. That is for look better and easy to test. */
/* Longer pages, so you can test to see if you can actually get to the specified location after clicking. */
body {
min-height: 160rem
}
/* similar to the bootstrap */
.fixed-top {
position: fixed;
top: 0;
right: 50vw;
z-index: 1000;
}
</style>
<div id="target">
<h1 id="my-app">App1</h1>
<h2>Video</h2>
<h3>mp4</h3>
<h3>webm</h3>
<h2>Audio</h2>
<h3>Mp3</h3>
<h3>m4a</h3>
<h1>App2</h1>
<h2>Overview</h2>
</div>
<script>
class TOC {
/**
* @param {[HTMLHeadingElement]} headingSet
* */
static parse(headingSet) {
const tocData = []
let curLevel = 0
let preTocItem = undefined
headingSet.forEach(heading => {
const hLevel = heading.outerHTML.match(/<h([\d]).*>/)[1]
const titleText = heading.innerText
switch (hLevel >= curLevel) {
case true:
if (preTocItem === undefined) {
preTocItem = new TocItem(titleText, hLevel)
tocData.push(preTocItem)
} else {
const curTocItem = new TocItem(titleText, hLevel)
const parent = curTocItem.level > preTocItem.level ? preTocItem : preTocItem.parent
curTocItem.parent = parent
parent.children.push(curTocItem)
preTocItem = curTocItem
}
break
case false:
// We need to find the appropriate parent node from the preTocItem
const curTocItem = new TocItem(titleText, hLevel)
while (1) {
if (preTocItem.level < curTocItem.level) {
preTocItem.children.push(curTocItem)
preTocItem = curTocItem
break
}
preTocItem = preTocItem.parent
if (preTocItem === undefined) {
tocData.push(curTocItem)
preTocItem = curTocItem
break
}
}
break
}
curLevel = hLevel
if (heading.id === "") {
heading.id = titleText.replace(/ /g, "-").toLowerCase()
}
preTocItem.id = heading.id
})
return tocData
}
/**
* @param {[TocItem]} tocData
* @return {string}
* */
static build(tocData) {
let result = "<ul>"
tocData.forEach(toc => {
result += `<li><a href=#${toc.id}>${toc.text}</a></li>`
if (toc.children.length) {
result += `${TOC.build(toc.children)}`
}
})
return result + "</ul>"
}
}
/**
* @param {string} text
* @param {int} level
* @param {TocItem} parent
* */
function TocItem(text, level, parent = undefined) {
this.text = text
this.level = level
this.id = undefined
this.parent = parent
this.children = []
}
window.onload = () => {
const headingSet = document.querySelectorAll("h1, h2, h3, h4, h5, h6") // You can also select only the titles you are interested in.
const tocData = TOC.parse(headingSet)
console.log(tocData)
const tocHTMLContent = TOC.build(tocData)
const frag = document.createRange().createContextualFragment(`<fieldset class="fixed-top"><legend>TOC</legend>${tocHTMLContent}</fieldset>`)
document.querySelector(`body`).insertBefore(frag, document.querySelector(`body`).firstChild)
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment