You got some HTML content you want to make a book with, but it would takes too much time to set the ids and hyperlinks? We got you covered!
In this part of the doc, we’ll show you how to build a table of contents using a custom script and the css function target-counter()
for the content
property.
Build the table of contents from your html
In HTML, a table of contents is a <nav>
elements that contain a list of the reveleant titles of your document with a link to the unique identifier of each. This part can be done with your own tool/generator but here is an exemple of a script to generate a table of contents in vanilla javascript:
function createToc(config) {
const content = config.content;
const tocElement = config.tocElement;
const titleElements = config.titleElements;
let tocElementDiv = content.querySelector(tocElement);
let tocUl = document.createElement("ul");
tocUl.id = "list-toc-generated";
tocElementDiv.appendChild(tocUl);
// add class to all title elements
let tocElementNbr = 0;
for (var i = 0; i < titleElements.length; i++) {
let titleHierarchy = i + 1;
let titleElement = content.querySelectorAll(titleElements[i]);
titleElement.forEach(function (element) {
// add classes to the element
element.classList.add("title-element");
element.setAttribute("data-title-level", titleHierarchy);
// add id if doesn't exist
tocElementNbr++;
idElement = element.id;
if (idElement == "") {
element.id = "title-element-" + tocElementNbr;
}
let newIdElement = element.id;
});
}
// create toc list
let tocElements = content.querySelectorAll(".title-element");
for (var i = 0; i < tocElements.length; i++) {
let tocElement = tocElements[i];
let tocNewLi = document.createElement("li");
// Add class for the hierarcy of toc
tocNewLi.classList.add("toc-element");
tocNewLi.classList.add(
"toc-element-level-" + tocElement.dataset.titleLevel
);
// Keep class of title elements
let classTocElement = tocElement.classList;
for (var n = 0; n < classTocElement.length; n++) {
if (classTocElement[n] != "title-element") {
tocNewLi.classList.add(classTocElement[n]);
}
}
// Create the element
tocNewLi.innerHTML =
'<a href="#' + tocElement.id + '">' + tocElement.innerHTML + "</a>";
tocUl.appendChild(tocNewLi);
}
}
Copy this script to a .js
and link this file to your document.
The table of contents need to be generated before paged.js fragment the content into pages. Therefore, you need to register the handler beforeParsed()
and call the table of contents script inside.
Add this code in the head
of you html document after the call for paged.js script:
<script>
class handlers extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}
beforeParsed(content) {
createToc({
content: content,
tocElement: "#my-toc-content",
titleElements: [".mw-content-ltr h2", "h3"],
});
}
}
Paged.registerHandlers(handlers);
</script>
Configuring the script
tocElement
: define the id element where the toc list will be created
titleElements
: array of the title element you want in the your table of contents. You can add as many as you want and the elements can be selected like any css, for example: .title-1
or .my-content h1
Generate page numbers
Thanks to the previous script, your content is now structured using id
for the heading, and the table of contents we created use hyperlinks to those #id
:
<!-- the headings in the text-->
<h1 id="pre-digital_era" class="title-element" data-title-level="h1">
Pre-digital era
</h1>
<p>Content...</p>
<h1 id="digital_era" class="title-element" data-title-level="h1">
Digital era
</h1>
<p>Content...</p>
<!-- the table of contents-->
<ul id="toc">
<li><a href="#pre-digital_era">Pre-digital era</a></li>
<li><a href="#digital_era">Digital era</a></li>
</ul>
In the CSS, the target-counter
property is used within ::before
and ::after
pseudo-elements, using the content
property. It can be translated as: find the counter named page
that appears where you find the element we’re targetting with the attribute href
:
#toc li a::after {
content: target-counter(attr(href), page);
float: right;
}
Add styles to the table of contents
If you need to add counters or leaders to your table of contents generated above, here is an exemple of CSS you can use:
/* set the style for the list numbering to none */
#list-toc-generated {
list-style: none;
}
#list-toc-generated .toc-element {
break-inside: avoid;
}
#list-toc-generated .toc-element a::after {
content: " p. " target-counter(attr(href), page);
float: right;
}
#list-toc-generated .toc-element-level-1 {
margin-top: 25px;
font-weight: bold;
}
#list-toc-generated .toc-element-level-2 {
margin-left: 25px;
}
/* counters */
#list-toc-generated {
counter-reset: counterTocLevel1;
}
#list-toc-generated .toc-element-level-1 {
counter-increment: counterTocLevel1;
counter-reset: counterTocLevel2;
}
#list-toc-generated .toc-element-level-1::before {
content: counter(counterTocLevel1) ". ";
padding-right: 5px;
}
#list-toc-generated .toc-element-level-2 {
counter-increment: counterTocLevel2;
}
#list-toc-generated .toc-element-level-2::before {
content: counter(counterTocLevel1) ". " counter(counterTocLevel2) ". ";
padding-right: 5px;
}
/* hack for leaders */
#list-toc-generated {
overflow-x: hidden;
}
/* fake leading */
#list-toc-generated .toc-element::after {
content: ".............................................."
".............................................."
".............................................." "........";
float: left;
width: 0;
padding-left: 5px;
letter-spacing: 2px;
}
#list-toc-generated .toc-element {
display: flex;
}
#list-toc-generated .toc-element a::after {
position: absolute;
right: 0;
background-color: white;
padding-left: 6px;
}
#list-toc-generated .toc-element a {
right: 0;
}
And voilà! All the files (scripts, CSS, HTML exemples) to create a table of contents are available on gitlab: https://gitlab.coko.foundation/pagedjs/experiments/tree/master/table-of-content.