@@ -54,9 +54,8 @@ const description = post.data.subtitle;
5454
5555 { post .data .toc ?
5656 <TwoCols >
57- <div class = " mt-28" > aaa </div >
5857 <Fragment slot = " content" >
59- <Headline id = " contents" as = " h4" title = " Contents " />
58+ <Headline id = " contents" as = " h4" title = " Table of contents " />
6059 <div id = " toc" class = " mb-6 last:mb-0 rounded-lg shadow-md bg-white p-6" ></div >
6160 </Fragment >
6261 <Fragment slot = " sidebar" >
@@ -107,26 +106,112 @@ document.addEventListener("DOMContentLoaded", () => {
107106 const tocContainer = document.getElementById("toc");
108107 if (!tocContainer) return;
109108
110- const headings = document.querySelectorAll("article h2, article h3" );
109+ const headings = Array.from( document.querySelectorAll("article h2, article h3, article h4") );
111110 if (!headings.length) return;
112111
113- const ul = document.createElement("ul");
112+ const rootUl = document.createElement("ul");
113+ let currentUl = rootUl;
114+ let lastLevel = 2;
115+ const parents = [rootUl];
114116
115117 headings.forEach((heading, index) => {
116118 if (!heading.id) {
117119 heading.id = `heading-${index}`;
118120 }
119121
122+ const level = parseInt(heading.tagName[1], 10);
120123 const li = document.createElement("li");
121- li.style.marginLeft = heading.tagName === "H3" ? "1em" : "0";
122124 const a = document.createElement("a");
123125 a.href = `#${heading.id}`;
124126 a.textContent = heading.textContent;
125-
126127 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;
128140 });
129141
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));
131165});
132166</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