let AutoToc = (element, scope, options) => { let tableOfContents = ""; let opts = Object.assign(AutoToc.defaultOptions, options); let rootElement = document.getElementById(element); let listStyle = rootElement.tagName.toLowerCase(); let currentDepth = opts.startLevel; let headings = getAllHeadingsInScope(); addAnchorsToHeadings(); buildContentList(); appendTableOfContentsToPlaceholder(); function appendTableOfContentsToPlaceholder() { rootElement.innerHTML = tableOfContents; } function formatLink(element) { const link = document.createElement("a"); link.href = "#" + element.id; link.innerText = element.innerText; return link.outerHTML; } function buildContentList() { headings.forEach( (heading = (heading, i) => { const depth = Number(heading.nodeName.toLowerCase().substr(1, 1)); if (i > 0 || (i == 0 && depth != currentDepth)) { changeDepth(depth); } tableOfContents += formatLink(heading); }) ); changeDepth(opts.startLevel, true); if (tieredList()) { tableOfContents = "
  • \n" + tableOfContents + "
  • \n"; } if (opts.showTopLinks) appendTopLinkToHeadings(); } function tieredList() { return listStyle == "ul" || listStyle == "ol"; } function changeDepth(newDepth, last) { if (last !== true) last = false; if (!tieredList()) { currentDepth = newDepth; return true; } // If nested if (newDepth > currentDepth) { // Add enough opening tags to step into the heading // as it is possible that a poorly built document // steps from h1 to h3 without an h2 let openingTags = []; for (var i = currentDepth; i < newDepth; i++) { openingTags.push("<" + listStyle + ">" + "\n"); } var li = "
  • \n"; // Add the code to our TOC and an opening LI tableOfContents += openingTags.join(li) + li; } else if (newDepth < currentDepth) { // Close all the loops var closingTags = []; for (var i = currentDepth; i > newDepth; i--) { closingTags.push("" + "\n"); } // Add closing LI and any additional closing tags tableOfContents += "
  • \n" + closingTags.join("" + "\n"); // Open next block if (!last) { tableOfContents += "\n
  • \n"; } } else { // Depth has not changed if (!last) { tableOfContents += "
  • \n
  • \n"; } } // Store the new depth currentDepth = newDepth; } function addAnchorsToHeadings() { headings.forEach((element) => { if (!element.getAttribute("id")) { let slug = createIdSlug(element.innerText); element.setAttribute("id", slug); } }); } function appendTopLinkToHeadings() { let topLink = rootElement.id; if(opts.topLinkToParentToc == false){ addTopAnchorToBody(); topLink = opts.topBodyId; } headings.forEach((element) => { element.append(appendTopLinkToHeading(topLink)); }); } function addTopAnchorToBody() { let currentBodyId = document.body.getAttribute("id"); if (currentBodyId) { opts.topBodyId = currentBodyId; } else { document.body.setAttribute("id", opts.topBodyId); } } function appendTopLinkToHeading(topLink) { let link = document.createElement("a"); link.href = "#" + topLink; link.setAttribute("class", opts.topLinkClass); link.innerHTML = "↑"; return link; } function createIdSlug(text) { text = text .toLowerCase() .replace(/[^a-z0-9 -]/gi, "") .replace("-", "") .replace(/ /gi, "-"); text = text.substr(0, 50); return rootElement.id + "-" + text; } function getAllHeadingsInScope() { let elementsCollection = []; if (typeof scope == "undefined" || scope == null) { elementsCollection = document.getElementsByTagName("*"); } else { elementsCollection = document .getElementById(scope) .getElementsByTagName("*"); } let headingCollection = []; // Add all h* matching elements to collection for (var i = 0, n = elementsCollection.length; i < n; i++) { if (/^h\d{1}$/gi.test(elementsCollection[i].nodeName)) { const depth = Number(elementsCollection[i].nodeName.toLowerCase().substr(1, 1)); if (depth >= opts.startLevel && depth <= opts.depth) { headingCollection.push(elementsCollection[i]); } } } return headingCollection; } }; // Expose defaults AutoToc.defaultOptions = { startLevel: 1, depth: 3, showTopLinks: false, topLinkToParentToc: false, topLinkClass: "autoTocTopLink", topBodyId: "top", };