@@ -54,9 +54,8 @@ const description = post.data.subtitle;
54
54
55
55
{ post .data .toc ?
56
56
<TwoCols >
57
- <div class = " mt-28" > aaa </div >
58
57
<Fragment slot = " content" >
59
- <Headline id = " contents" as = " h4" title = " Contents " />
58
+ <Headline id = " contents" as = " h4" title = " Table of contents " />
60
59
<div id = " toc" class = " mb-6 last:mb-0 rounded-lg shadow-md bg-white p-6" ></div >
61
60
</Fragment >
62
61
<Fragment slot = " sidebar" >
@@ -107,26 +106,112 @@ document.addEventListener("DOMContentLoaded", () => {
107
106
const tocContainer = document.getElementById("toc");
108
107
if (!tocContainer) return;
109
108
110
- const headings = document.querySelectorAll("article h2, article h3" );
109
+ const headings = Array.from( document.querySelectorAll("article h2, article h3, article h4") );
111
110
if (!headings.length) return;
112
111
113
- const ul = document.createElement("ul");
112
+ const rootUl = document.createElement("ul");
113
+ let currentUl = rootUl;
114
+ let lastLevel = 2;
115
+ const parents = [rootUl];
114
116
115
117
headings.forEach((heading, index) => {
116
118
if (!heading.id) {
117
119
heading.id = `heading-${index}`;
118
120
}
119
121
122
+ const level = parseInt(heading.tagName[1], 10);
120
123
const li = document.createElement("li");
121
- li.style.marginLeft = heading.tagName === "H3" ? "1em" : "0";
122
124
const a = document.createElement("a");
123
125
a.href = `#${heading.id}`;
124
126
a.textContent = heading.textContent;
125
-
126
127
li.appendChild(a);
127
- ul.appendChild(li);
128
+
129
+ if (level > lastLevel) {
130
+ const newUl = document.createElement("ul");
131
+ parents[parents.length - 1].lastElementChild?.appendChild(newUl);
132
+ parents.push(newUl);
133
+ } else if (level < lastLevel) {
134
+ parents.splice(-(lastLevel - level));
135
+ }
136
+
137
+ currentUl = parents[parents.length - 1];
138
+ currentUl.appendChild(li);
139
+ lastLevel = level;
128
140
});
129
141
130
- tocContainer.appendChild(ul);
142
+ tocContainer.appendChild(rootUl);
143
+
144
+ // Scroll spy: highlight active link
145
+ const observer = new IntersectionObserver(
146
+ entries => {
147
+ entries.forEach(entry => {
148
+ const id = entry.target.id;
149
+ const tocLink = tocContainer.querySelector(`a[href="#${id}"]`);
150
+ if (tocLink) {
151
+ if (entry.isIntersecting) {
152
+ tocContainer.querySelectorAll("a").forEach(a => a.classList.remove("active"));
153
+ tocLink.classList.add("active");
154
+ }
155
+ }
156
+ });
157
+ },
158
+ {
159
+ rootMargin: "-30% 0px -60% 0px", // adjust when to highlight
160
+ threshold: 0,
161
+ }
162
+ );
163
+
164
+ headings.forEach(heading => observer.observe(heading));
131
165
});
132
166
</script >
167
+
168
+ <style is:global >
169
+
170
+ #toc {
171
+ font-size: 0.95rem;
172
+ line-height: 1.5;
173
+ max-width: 300px;
174
+ padding: 1rem;
175
+ border-left: 1px solid #e0e0e0;
176
+ position: sticky;
177
+ top: 1rem;
178
+ }
179
+
180
+ #toc ul {
181
+ list-style: none;
182
+ padding-left: 0;
183
+ margin: 0;
184
+ }
185
+
186
+ #toc li {
187
+ margin: 0.25em 0;
188
+ }
189
+
190
+ #toc li ul {
191
+ margin-left: 1em;
192
+ border-left: 1px dashed #ddd;
193
+ padding-left: 0.75em;
194
+ }
195
+
196
+ #toc a {
197
+ text-decoration: none;
198
+ color: #333;
199
+ display: block;
200
+ padding: 0.25em 0.5em;
201
+ border-radius: 4px;
202
+ transition: background 0.2s ease, color 0.2s ease;
203
+ }
204
+
205
+ #toc a:hover {
206
+ background: #f0f0f0;
207
+ color: #007acc;
208
+ }
209
+
210
+ #toc a.active {
211
+ font-weight: 600;
212
+ color: #007acc;
213
+ border-left: 3px solid #007acc;
214
+ background: rgba(0, 122, 204, 0.1);
215
+ padding-left: 0.5em;
216
+ }
217
+ </style >
0 commit comments