-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
table-of-contents.webc
104 lines (98 loc) · 2.29 KB
/
table-of-contents.webc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!---
table-of-contents finds the anchor links in the content to create a table of contents
--->
<script webc:setup>
function getLevel(el) {
return parseInt(el.tagName.slice(1), 10);
}
function generateHtml(headings) {
let html = [];
let previousLevel = 2;
for(let el of headings) {
let level = getLevel(el);
if(level === previousLevel) {
html.push("</li>");
} else if(level < previousLevel) {
html.push("</ul>")
} else if(level > previousLevel) {
html.push("<ul>")
}
let text = Array.from(el.childNodes).map(node => {
if(node.nodeType === 3) {
return node.nodeValue.trim();
}
return "";
}).join("");
html.push(`<li><a href="#${el.getAttribute("id")}">${text}</a>`);
previousLevel = level;
}
return `<details open>
<summary>On this page</summary>
<ul>${html.join("")}</ul>
</details>`;
}
</script>
<style webc:scoped>
:host {
display: block;
margin: 1em 0;
}
:host ul {
list-style: none;
padding: 0;
margin: .25em 0;
}
:host ul ul {
margin-left: 1em;
}
:host li {
margin: .75em 0;
}
:host a {
display: block;
}
:host .active {
font-weight: bold;
}
</style>
<script webc:type="js">
// selectFromHtml via linkedom in eleventy.config.cjs
let headings = selectFromHtml(content, ":is(h2, h3, h4, h5, h6)[id]" );
generateHtml(headings);
</script>
<script>
class TableOfContents extends HTMLElement {
static ACTIVE = "active";
observe(targetsSelector) {
let observer = new IntersectionObserver((entries) => {
for (let entry of entries) {
if(entry.isIntersecting) {
let id = entry.target.getAttribute("id");
let tocHeading = this.querySelector(`a[href="#${id}"]`);
if(tocHeading) {
let previousHeading = this.querySelector(`.${TableOfContents.ACTIVE}`);
if(previousHeading) {
previousHeading.classList.remove(TableOfContents.ACTIVE);
}
tocHeading.classList.add(TableOfContents.ACTIVE);
}
break;
}
}
}, {
rootMargin: "0px 0px -90% 0px",
threshold: 1,
});
let headings = document.querySelectorAll(targetsSelector);
for(let heading of headings) {
observer.observe(heading);
}
}
connectedCallback() {
this.observe(":is(h2, h3, h4, h5, h6)[id]")
}
}
if(("customElements" in window)) {
window.customElements.define("table-of-contents", TableOfContents);
}
</script>