Last active
May 12, 2024 20:11
-
-
Save vlavrynovych/4111095383229db74e71aedf72652fc9 to your computer and use it in GitHub Desktop.
Script for the Ghost blog which builds a Table of Contents where it is placed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- <toc> element is used to inject a TOC to it --> | |
<toc></toc> | |
<style> | |
toc #toc-container { width: 100%; } | |
toc .toc-empty-item { display: block } | |
toc .toc-caption { text-align: center; margin-bottom: 15px } | |
</style> | |
<script> | |
class TOC { | |
caption = { | |
enabled: true, | |
text: 'Table of contents' | |
} | |
constructor() { | |
document.addEventListener('DOMContentLoaded', () => this.onLoad()); | |
} | |
onLoad() { | |
const article = document.querySelector('article'); | |
// find all <h2> and <h3> elements within the <article> | |
this.headers = Array.from(article.querySelectorAll('h2,h3')); | |
// defines if the structure is complex or not | |
this.isMultilevel = this.headers | |
.map(el => el.tagName) | |
.filter(isUnique) | |
.length > 1; | |
// create container | |
const container = this.el("div", 'toc-container'); | |
this.addCaption(container); | |
// create navigation element | |
const nav = this.el('nav', 'table-of-contents'); | |
nav.setAttribute('role', 'navigation'); | |
nav.appendChild(this.buildList()); | |
container.appendChild(nav); | |
article.querySelector('toc').appendChild(container); | |
} | |
/** | |
* Creates one item | |
* @param el - defines H2 or H3 element | |
* @returns {HTMLLIElement} <li> element with link inside | |
*/ | |
createItem(el) { | |
if(!el) return this.el('li', 'toc-empty-item'); | |
const item = this.el('li'), | |
link = this.el('a'); | |
link.setAttribute('href', `#${el.id}`); | |
link.textContent = el.textContent; | |
item.appendChild(link); | |
return item; | |
} | |
/** | |
* Builds a table of contents as a list of elements | |
* @returns {HTMLUListElement} list of items | |
*/ | |
buildList() { | |
const list = this.el('ul'); | |
this.prepareStructure().forEach(item => { | |
const li = this.createItem(item.el); | |
if (item.list.length) { | |
const ul = this.el('ul'); | |
item.list.forEach(el => ul.appendChild(this.createItem(el))) | |
li.appendChild(ul) | |
} | |
list.appendChild(li) | |
}) | |
return list; | |
} | |
prepareStructure() { | |
const list = []; | |
this.headers.forEach(el => { | |
if(el.tagName === 'H2' || !this.isMultilevel) { | |
list.push(new ListItem(el)); | |
} else { | |
let item = list[list.length - 1]; | |
if(item == null) { | |
item = new ListItem(); | |
list.push(item); | |
} | |
item.list.push(el) | |
} | |
}) | |
return list; | |
} | |
/** | |
* Add a caption if enabled | |
* @param container - defines a container element | |
*/ | |
addCaption(container) { | |
if(!this.caption.enabled) return; | |
const caption = this.el('H2', 'toc-caption'); | |
caption.textContent = this.caption.text; | |
container.appendChild(caption); | |
} | |
/** | |
* Create new element of provided tag name with class if specified | |
* @param tagName - defines the tag name of element | |
* @param clazz - defines what will be set to class attribute | |
* @returns {*} | |
*/ | |
el(tagName, clazz) { | |
const el = document.createElement(tagName); | |
if(clazz) el.setAttribute('class', clazz); | |
return el; | |
} | |
} | |
// --- Utils | |
function isUnique(value, index, array) { | |
return array.indexOf(value) === index; | |
} | |
class ListItem { | |
list = []; | |
constructor(el) { | |
this.el = el; | |
} | |
} | |
new TOC(); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment