scroll spy
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Michael Zhang 2023-09-01 20:59:23 -05:00
parent e3ded78ba3
commit a670600ad7
4 changed files with 124 additions and 9 deletions

View file

@ -17,12 +17,16 @@ const minDepth = Math.min(...headings.map((heading) => heading.depth));
<div class="toc-wrapper">
<slot />
<div class="toc">
Table of contents
<h3 class="title">Table of contents</h3>
<ul>
{headings.map((heading) => {
return (
<li style={`padding-left: ${(heading.depth - minDepth) * 10}px;`}>
<a href={`#${heading.slug}`}>{heading.text}</a>
<li>
<a href={`#${heading.slug}`} id={`${heading.slug}-link`}>
<span style={`padding-left: ${(heading.depth - minDepth) * 10}px;`}>
{heading.text}
</span>
</a>
</li>
);
})}
@ -35,8 +39,86 @@ const minDepth = Math.min(...headings.map((heading) => heading.depth));
)
}
<script type="text/javascript">
document.addEventListener("scroll", (doc, evt) => {
console.log("SHIET");
<script define:vars={{ toc, headings }}>
if (toc) {
const headingTags = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
const headingsMap = new Map([...headings.map((heading) => [heading.slug, new Set()])]);
const reverseHeadingMap = new Map();
const linkMap = new Map();
document.addEventListener("DOMContentLoaded", function () {
const visibleElements = new Map();
// Register links
for (const heading of headings) {
const link = document.getElementById(`${heading.slug}-link`);
const el = document.getElementById(heading.slug);
if (link && el) {
linkMap.set(heading.slug, link);
link.addEventListener("click", (e) => {
el.scrollIntoView({ behavior: "smooth" });
e.preventDefault();
});
}
if (!visibleElements.has(heading.slug)) {
visibleElements.set(heading.slug, new Set());
}
}
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const target = entry.target;
const slug = reverseHeadingMap.get(target);
const link = linkMap.get(slug);
const associatedEls = visibleElements.get(slug);
if (entry.isIntersecting) {
// if it wasn't previously visible
// let's make the link active
if (associatedEls.size === 0) {
console.log("SHIET", link);
link.parentNode.classList.add("active");
}
associatedEls.add(target);
} else {
// if it was previously visible
// check if it's the last element
if (associatedEls.size > 0) {
if (associatedEls.size === 1) link.parentNode.classList.remove("active");
}
if (associatedEls.size > 0) {
associatedEls.delete(target);
}
}
}
});
const postContentEl = document.getElementById("post-content");
console.log("content", postContentEl);
let belongsTo;
for (const child of postContentEl.children) {
if (headingTags.has(child.tagName.toLowerCase())) {
belongsTo = child.id;
}
if (belongsTo) {
const headingSet = headingsMap.get(belongsTo);
headingSet.add(child);
reverseHeadingMap.set(child, belongsTo);
}
}
console.log("headings map", headingsMap);
console.log("reverse", reverseHeadingMap);
[...headingsMap.values()]
.flatMap((x) => [...x])
.forEach((x) => {
observer.observe(x);
});
});
}
</script>

View file

@ -80,7 +80,7 @@ const datestamp = post.data.date.toLocaleDateString(undefined, {
)
}
<div class="post-content">
<div class="post-content" id="post-content">
<Content />
</div>
</div>

View file

@ -3,6 +3,7 @@
.post-title {
font-size: 2rem;
font-weight: 600;
margin-top: 20px;
margin-bottom: 12px;
}

View file

@ -5,6 +5,10 @@ $tocBreakpoint: variables.$breakpoint + $tocWidth;
.toc-wrapper {
position: relative;
display: flex;
.title {
margin-top: 20px;
}
}
.toc {
@ -16,7 +20,35 @@ $tocBreakpoint: variables.$breakpoint + $tocWidth;
li {
font-size: 14px;
margin: 6px;
display: block;
&:hover {
// background-color: var(--link-hover-color);
}
a {
display: block;
padding: 10px;
color: var(--text-color);
border-left: 4px solid transparent;
border-radius: unset;
&:hover {
background-color: unset;
border-left-color: var(--link-color);
color: var(--link-color);
}
}
&.active {
&:first {
border: 1px solid orange;
}
a {
background-color: var(--link-hover-color);
}
}
}
}
}