Skip to content

Instantly share code, notes, and snippets.

@Sebas-h
Created October 28, 2021 07:44
Show Gist options
  • Save Sebas-h/66ba95ba016b22335936c9bcb419622f to your computer and use it in GitHub Desktop.
Save Sebas-h/66ba95ba016b22335936c9bcb419622f to your computer and use it in GitHub Desktop.
Userscript - mdBook Page ToC
function populatePageToc() {
// Source: https://github.com/JorelAli/mdBook-pagetoc
// Modified:
// Un-active everything when you click it
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
el.addEventHandler("click", function () {
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
el.classList.remove("active");
});
el.classList.add("active");
});
});
var updateFunction = function () {
var id;
var elements = document.getElementsByClassName("header");
Array.prototype.forEach.call(elements, function (el) {
if (window.pageYOffset >= el.offsetTop) {
id = el;
}
});
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
el.classList.remove("active");
});
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
if (id.href.localeCompare(el.href) == 0) {
el.classList.add("active");
}
});
};
// Populate sidebar on load
function populate() {
var pagetoc = document.getElementsByClassName("pagetoc")[0];
var elements = document.getElementsByClassName("header");
Array.prototype.forEach.call(elements, function (el) {
var link = document.createElement("a");
// Indent shows hierarchy
var indent = "";
switch (el.parentElement.tagName) {
case "H2":
indent = "20px";
break;
case "H3":
indent = "40px";
break;
case "H4":
indent = "60px";
break;
case "H5":
indent = "80px";
break;
default:
break;
}
link.appendChild(document.createTextNode(el.text));
link.style.paddingLeft = indent;
link.href = el.href;
pagetoc.appendChild(link);
});
updateFunction.call();
}
// window.addEventListener('load', populate);
populate(); // For Firefox
// Handle active elements on scroll
window.addEventListener("scroll", updateFunction);
}
// Source: https://github.com/JorelAli/mdBook-pagetoc
// Modified:
const css = `
@media only screen and (max-width:1110px) {
.sidetoc {
display: none;
}
}
@media only screen and (min-width:440px) {
main {
position: relative;
}
.sidetoc {
margin-left: auto;
margin-right: auto;
left: calc(100% + (var(--content-max-width))/4 - 140px);
position: absolute;
}
.pagetoc {
position: fixed;
width: 300px;
height: calc(100vh - var(--menu-bar-height) - 0.67em * 6);
overflow: auto;
}
.pagetoc a {
border-left: 1px solid var(--sidebar-bg);
color: var(--fg) !important;
display: block;
padding-bottom: 5px;
padding-top: 5px;
padding-left: 10px;
text-align: left;
text-decoration: none;
}
.pagetoc a:hover,
.pagetoc a.active {
background: var(--sidebar-bg);
color: var(--sidebar-fg) !important;
}
.pagetoc .active {
background: var(--sidebar-bg);
color: var(--sidebar-fg);
}
}
.custom-btn {
text-decoration: none;
display: inline-block;
padding-left: 8px;
padding-right: 8px;
border-radius: 12px;
cursor: pointer;
}
.custom-btn:hover {
background-color:gray;
}
`;
(function () {
'use strict';
// Add style sheet to page
let head = document.head || document.getElementsByTagName('head')[0];
let style = document.createElement('style');
head.appendChild(style);
style.appendChild(document.createTextNode(css));
// Add nested divs to be populated with headers
let pageToc = document.createElement("nav");
pageToc.className = "pagetoc";
let sideNav = document.createElement("div");
sideNav.className = "sidetoc";
sideNav.appendChild(pageToc)
let mainEl = document.getElementsByTagName("main")[0];
mainEl.insertBefore(sideNav, mainEl.firstChild);
// Run code to extract page headers, populate the page toc and activate
// event listener to highlight correct page toc item.
populatePageToc()
// Disable big nav buttons
// (i.e. <nav class="nav-wide-wrapper" aria-label="Page navigation">)
let bigNavElement = document.querySelectorAll('nav.nav-wide-wrapper')[0];
bigNavElement.style.display = "none";
// Extract previous and next href links:
const prevHref = document.querySelectorAll('a.nav-chapters.previous')[0].href;
const nextHref = document.querySelectorAll('a.nav-chapters.next')[0].href;
// Remove left side big margin
mainEl.style.marginLeft = 0;
// Create 'Previous' Chapter Button
let prevEl = document.createElement("a");
prevEl.className = "custom-btn";
prevEl.innerText = "<";
prevEl.setAttribute("href", prevHref || "")
// Create 'Next' Chapter Button
let nextEl = document.createElement("a");
nextEl.className = "custom-btn";
nextEl.innerText = ">";
nextEl.setAttribute("href", nextHref || "")
// Add Prev and Next to title bar
let titleEl = document.getElementsByClassName("menu-title")[0];
titleEl.insertBefore(prevEl, titleEl.firstChild);
titleEl.appendChild(nextEl);
})();
@Sebas-h
Copy link
Author

Sebas-h commented Oct 28, 2021

Screenshot

Screen Shot 2021-10-28 at 00 15 08

Source

ToC CSS and JS to populate it comes from this mdBook plugin:
https://github.com/JorelAli/mdBook-pagetoc

Used with Tampermokey

Chromium-based browsers

Screen Shot 2021-10-28 at 09 34 44

Enable this option in the extension's settings:
Screen Shot 2021-10-28 at 09 48 16

Firefox

I believe it is not possible to open a local file like this in Firefox so @require won't work.
Remove the @require comment/tag in from the userscript in Tampermokey and simply copy and paste the js code into the Tampermokey script directly.

When does Tampermokey run the script

By default The script will be injected after the DOMContentLoaded event was dispatched.
See: https://www.tampermonkey.net/documentation.php#_run_at

Tweaks

  • You might want to the change the CSS media query's max-width and min-width values to match your display properties.
  • Likewise I haven't spent much time perfecting the width, margins, padding and other CSS properties as it worked good enough in my case.
  • I did however move the navigation (previous/next page) to the top bar and removed the large left side margin on the content. You can change or removed the relevant code if this does not suit your use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment