Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Last active November 28, 2023 21:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cferdinandi/3a7dfd171551e71335e6a434edea181d to your computer and use it in GitHub Desktop.
Save cferdinandi/3a7dfd171551e71335e6a434edea181d to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>Table of Contents</title>
<style type="text/css">
body {
margin: 1em auto;
max-width: 30em;
width: 88%;
}
</style>
</head>
<body>
<h1>Table of Contents</h1>
<table-of-contents levels="h2, h3" heading="On This Page..."></table-of-contents>
<h2>Cat O'Nine Tails</h2>
<p>Cat o'nine tails Pieces of Eight swab carouser tackle. Pink hornswaggle gabion Sea Legs Davy Jones' Locker.</p>
<p>Hang the jib Nelsons folly trysail ahoy prow. Transom strike colors scallywag aft league.</p>
<h3 id="the-brig">The Brig</h3>
<p>Dead men tell no tales topmast Sail ho Davy Jones' Locker chantey. Wherry fluke pillage rope's end brig.</p>
<h4>Privateer</h4>
<p>Tack topgallant draft line flogging. Maroon overhaul grog blossom Privateer main sheet.</p>
<p>Provost me cackle fruit Corsair Cat o'nine tails. Hempen halter Davy Jones' Locker clipper bring a spring upon her cable run a shot across the bow.</p>
<h2>Ahoy</h2>
<p>Booty squiffy wench overhaul ahoy. Parrel Pirate Round long clothes long boat come about.</p>
<p>Squiffy jack crow's nest bilged on her anchor barkadeer. Snow bucko mizzen six pounders tack.</p>
<h3 id="man-of-war">Man-of-War</h3>
<p>Lee lad nipperkin avast pressgang. Man-of-war prow ho Sail ho landlubber or just lubber.</p>
<p>Ho no prey, no pay fire ship salmagundi capstan. Hail-shot doubloon wherry loaded to the gunwalls cutlass.</p>
<h2 id="corsair">Corsair</h2>
<p>Corsair chantey hardtack ahoy snow. Maroon cog galleon topmast tender.</p>
<h3 id="shiver-me-timbers">Shiver Me Timbers</h3>
<p>Galleon nipper Shiver me timbers lugger Nelsons folly. Wench ballast scurvy man-of-war hearties.</p>
<p>Poop deck clap of thunder Corsair grog hornswaggle. Dead men tell no tales brigantine long clothes black spot sutler.</p>
<h4 id="scurvy-dog">Scurvy Dog</h4>
<p>Jury mast Letter of Marque boatswain scurvy sheet. Jolly boat plunder jack starboard Pirate Round.</p>
<p>Holystone bring a spring upon her cable grog blossom deadlights league. Lanyard gabion reef sails booty gaff.</p>
<h4>Sea Legs</h4>
<p>Sea Legs to go on account skysail Yellow Jack heave down. Spanker heave down yawl starboard barque.</p>
<p>To go on account hulk swing the lead heave to tack. Fore fire in the hole prow run a rig Jack Ketch.</p>
<h2 id="quarterdeck">On the Quarterdeck</h2>
<p>Tack chase red ensign league pinnace. Holystone quarterdeck me boatswain rope's end.</p>
<p>Sink me lanyard Pieces of Eight starboard black spot. Blimey heave down crimp mutiny matey.</p>
<h3 id="jolly-roger">Jolly Roger</h3>
<p>Belay piracy come about jolly boat transom. Heave to gally snow Arr wherry.</p>
<p>Sutler Davy Jones' Locker ahoy walk the plank lugger. Jolly Roger matey hornswaggle Privateer marooned.</p>
<h2>Davy Jones' Locker</h2>
<p>Davy Jones' Locker jib trysail bowsprit heave down. Transom square-rigged clipper Jack Ketch chandler.</p>
<p>Square-rigged yawl execution dock sloop American Main. Six pounders red ensign lugger heave to dead men tell no tales.</p>
<h3 id="sloop">Sloop</h3>
<p>Spanker rutters Arr sloop pinnace. List crimp swab Blimey grog.</p>
<p>Shiver me timbers hulk black jack Pirate Round clap of thunder. Scuppers six pounders carouser spirits jib.</p>
<h4 id="swab">Swab</h4>
<p>Spanker rutters Arr sloop pinnace. List crimp swab Blimey grog.</p>
<p>Shiver me timbers hulk black jack Pirate Round clap of thunder. Scuppers six pounders carouser spirits jib.</p>
<h5>Grog</h5>
<p>Spanker rutters Arr sloop pinnace. List crimp swab Blimey grog.</p>
<p>Shiver me timbers hulk black jack Pirate Round clap of thunder. Scuppers six pounders carouser spirits jib.</p>
<script>
customElements.define('table-of-contents', class extends HTMLElement {
/**
* Instantiate the Web Component
*/
constructor () {
// Get parent class properties
super();
// Define properties
this.content = this.getAttribute('content') || document;
this.levels = this.getAttribute('levels') || 'h2, h3, h4, h5, h6';
this.heading = this.getAttribute('heading') || 'Table of Contents';
this.headingLevel = this.getAttribute('heading-level') || 'h2';
this.listType = this.getAttribute('list-type') || 'ul';
// Get the headings
// If none are found, don't render a list
this.headings = this.content.querySelectorAll(this.levels);
if (!this.headings.length) return;
// Render the list
this.render();
}
/**
* Inject the table of contents into the DOM
*/
render () {
// Track the current heading level
let level = this.headings[0].tagName.slice(1);
let startingLevel = level;
// Cache the number of headings
let len = this.headings.length - 1;
// Inject the HTML into the DOM
this.innerHTML =
`<${this.headingLevel}>${this.heading}</${this.headingLevel}>
<${this.listType}>
${Array.from(this.headings).map((heading, index) => {
// Add an ID if one is missing
this.createID(heading);
// Check the heading level vs. the current list
let currentLevel = heading.tagName.slice(1);
let levelDifference = currentLevel - level;
level = currentLevel;
let html = this.getStartingHTML(levelDifference, index);
// Generate the HTML
html +=
`<li>
<a href="#${heading.id}">${heading.innerHTML.trim()}</a>`;
// If the last item, close it all out
if (index === len) {
html += this.getOutdent(Math.abs(startingLevel - currentLevel));
}
return html;
}).join('')}
</${this.listType}>`;
}
/**
* Create an ID for a heading if one does not exist
* @param {Node} heading The heading element
*/
createID (heading) {
if (heading.id.length) return;
heading.id = `toc_${crypto.randomUUID()}`;
}
/**
* Get the HTML to indent a list a specific number of levels
* @param {Integer} count The number of times to indent the list
* @return {String} The HTML
*/
getIndent (count) {
let html = '';
for (let i = 0; i < count; i++) {
html += `<${this.listType}>`;
}
return html;
}
/**
* Get the HTML to close an indented list a specific number of levels
* @param {Integer} count The number of times to "outdent" the list
* @return {String} The HTML
*/
getOutdent (count) {
let html = '';
for (let i = 0; i < count; i++) {
html += `</${this.listType}></li>`;
}
return html;
}
/**
* Get the HTML string to start a new list of headings
* @param {Integer} diff The number of levels in or out from the current level the list is
* @param {Integer} index The index of the heading in the "headings" NodeList
* @return {String} The HTML
*/
getStartingHTML (diff, index) {
// If indenting
if (diff > 0) {
return this.getIndent(diff);
}
// If outdenting
if (diff < 0) {
return this.getOutdent(Math.abs(diff));
}
// If it's not the first item and there's no difference
if (index && !diff) {
return '</li>';
}
return '';
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment