Created
February 28, 2024 13:00
-
-
Save pax/38cafb525544991b0d365096a70edd1c to your computer and use it in GitHub Desktop.
Creates a Table of Contents with relative links to H* elements found inside target element or the whole document
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
.hx-0{margin-top:1.25ex;border-bottom:1pxdotted#999;font-size:1.2em;} | |
.hx-1{padding-left:.25ex;margin-left:.25ex;margin-top:1ex;font-size:1.1em;border-bottom:1pxdotted#DDD;} | |
.hx-2{padding-left:.75ex;margin-left:.5ex;margin-top:.5ex;border-left:1pxsolid#EEE;} | |
.hx-3{padding-left:1ex;margin-left:1ex;margin-top:.25ex;border-left:1pxsolid#EEE;} | |
.hx-0+.hx-0{margin-top:.25ex;} | |
.hx-2+.hx-2{margin-top:.5ex;} | |
.hx-1+.hx-1{margin-top:1ex;} | |
.hx-1:before {content: '\2022\00A0 '; color: #DCC;} | |
.hx-2:before {content: '\00A0\203A\00A0\00A0 '; color: #DCC;} | |
.hx-3:before {content: '– '; color: #DCC;} | |
.hx-4:before {content: '--- '; color: #DCC;} | |
@media screen and (min-width: 900px) { | |
.toc-element-auto-generated {position: sticky; top: 1em; background-color: rgba(255,255,255,.75);} | |
} | |
.toc-title, .toc-element-auto-generated .toc-title{border-bottom: 1px dashed #DDD; padding-bottom: 1ex;} | |
.no-headers-found {display: none;} | |
.toc-element-auto-generated {position: sticky; right: 1em; top: 1em; background-color: rgba(255,255,255,.75); padding: .5em 1em; display: inline-block;} |
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
/* | |
- creates a Table of Contents with relative links to H* elements found inside `searchBlockSelector` | |
- `tocSelector` and `searchBlockSelector` accept CSS paths, but are optional | |
- if paths not found or not given it looks for any of the ['#primary', '#main', 'main', '.site-main', 'article:first-of-type', '.entry-content:first-of-type'] | |
you need to add hx-* classes to your css - replace * and ... with actual values | |
.hx-*{margin-top: ...;margin-left: ...;font-size: ...;}.hx-1+.hx-1{margin-top: ...;}.hx-*+.hx-*{margin-top: ...;} | |
#toc.no-headers-found {display: none;} .toc-element-auto-generated {position: sticky; left: 1em; top: 1em; background-color: rgba(255,255,255,.75); padding: .5em 1em;} | |
# TODO | |
- [x] create TOC placeholder if it does not exist and add error message - or have some fall-back options | |
- [ ] add a hide control - maybe a checkbox so it only needs CSS? | |
- [ ] style active element, also on scroll | |
- [ ] smooth scrolling | |
- [ ] bookmarklet | |
*/ | |
function htmlTableOfContents(tocSelector, searchBlockSelector, hlevel = 'h1, h2, h3, h4') { | |
var documentRef = document; | |
var toc = documentRef.querySelector(tocSelector); | |
var searchBlock = documentRef.querySelector(searchBlockSelector); | |
var fallbackSelectors = ['#primary', '#main', 'main', '.site-main', 'article:first-of-type', '.entry-content:first-of-type']; | |
// Function to find first available fallback element | |
function findFallbackElement(selectors) { | |
for (var i = 0; i < selectors.length; i++) { | |
var foundElement = documentRef.querySelector(selectors[i]); | |
if (foundElement) return foundElement; | |
} | |
return null; | |
} | |
// Create TOC container if not found or not specified, and add "Table of Contents" header | |
if (!toc || !tocSelector) { | |
var fallbackElement = findFallbackElement(fallbackSelectors); | |
if (fallbackElement) { | |
toc = documentRef.createElement('div'); | |
toc.setAttribute('class', 'toc-container'); | |
toc.classList.add('toc-element-auto-generated'); | |
var tocHeader = documentRef.createElement('h2'); | |
tocHeader.textContent = 'Table of Contents'; | |
toc.appendChild(tocHeader); | |
fallbackElement.insertBefore(toc, fallbackElement.firstChild); | |
} else { | |
console.error('No suitable fallback element for TOC found.'); | |
return; | |
} | |
} | |
// Use the document as search block if not found or not specified | |
if (!searchBlock) { | |
searchBlock = findFallbackElement(fallbackSelectors) || documentRef.body; | |
} | |
var headings = [].slice.call(searchBlock.querySelectorAll(hlevel)); | |
// Determine the smallest header level found | |
var smallestHeaderLevel = Math.min(...headings.map(heading => parseInt(heading.tagName.substring(1), 10))); | |
if (headings.length === 0) { | |
toc.classList.add('no-headers-found'); | |
} else { | |
headings.forEach(function(heading, index) { | |
if (heading.textContent.trim() && !heading.classList.contains('hideToc')) { | |
var anchor = documentRef.createElement('a'); | |
anchor.setAttribute('name', 'toc' + index); | |
anchor.setAttribute('id', 'toc' + index); | |
var link = documentRef.createElement('a'); | |
link.setAttribute('href', '#toc' + index); | |
// Remove trailing ' #' from heading text if present | |
var headingText = heading.textContent.trim().endsWith(' #') ? heading.textContent.trim().slice(0, -2) : heading.textContent.trim(); | |
link.textContent = headingText; | |
var headerLevel = parseInt(heading.tagName.substring(1), 10) - smallestHeaderLevel; | |
var div = documentRef.createElement('div'); | |
div.setAttribute('class', 'hx-' + headerLevel); // Adjust based on smallest header level | |
div.appendChild(link); | |
toc.appendChild(div); | |
heading.parentNode.insertBefore(anchor, heading); | |
} | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment