I used Github Copilot to generate this using the below prompt where I pasted the previous code along with some instructions on what to do. You could use this as an example on how to get code from ChatGPT in the future.
Prompt
Assuming a html structure like below:
<div class="container">
<div class="sidebar">
<ul id="sidebar-list">
<!-- Sidebar will be populated by running populateSideBar() -->
</ul>
</div>
<div class="content-container">
<!-- Arbitrary content that contains h1, h2, h3 etc. headers at places -->
</div>
</div>
</div>
And populateSideBar
being run once the html is loaded like this:
<script>
// Populate the sidebar with <li> elements
function populateSidebar() {
const sidebarList = document.getElementById("sidebar-list");
const headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
// Duplicate the header structure in the sidebar as a nested <ul> list
let currentList = sidebarList;
let previousLevel = 1;
headers.forEach((header) => {
const level = parseInt(header.tagName[1]);
const listItem = document.createElement("li");
const link = document.createElement("a");
link.textContent = header.textContent;
// If the header doesn't have an id, assign it the text content
if (!header.id) {
header.id = header.textContent;
}
link.href = "#" + header.id;
listItem.appendChild(link);
if (level > previousLevel) {
const newList = document.createElement("ul");
currentList.appendChild(newList);
currentList = newList;
} else if (level < previousLevel) {
for (let i = 0; i < previousLevel - level; i++) {
currentList = currentList.parentElement.parentElement;
}
}
currentList.appendChild(listItem);
previousLevel = level;
});
}
populateSidebar();
</script>
Write a function highlightHeaderInView
used in window.addEventListener("scroll", highlightHeaderInView)
that will
- identify the header element that is closest to the viewport top (the current “active” header)
- add the css class
active-header
to the <li>
corresponding to that header
- remove the
active-header
class from any other <li>
element
Include comments in the code explaining each step so that a javascript beginner could understand how it works.
JS to update sidebar on scrolling
The event listener part at the end was edited by me though. I figured it’d be hard to explain the weirdness of the desktop Anki JS environment that requires this addition to avoid buggy behaviour.
<script>
// Function to highlight the header in view on the sidebar
function highlightHeaderInView() {
// Select all header elements in the content container
const headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
// Select all links in the sidebar list
const sidebarLinks = document.querySelectorAll("#sidebar-list a");
// Initialize variables to keep track of the closest header and its distance from the top
let closestHeader = null;
let closestHeaderDistance = Infinity;
// Loop through each header to find the one closest to the top of the viewport
headers.forEach(header => {
// Calculate the distance from the top of the viewport to the header
const distance = header.getBoundingClientRect().top;
// If this distance is the smallest positive value so far, update closestHeader and closestHeaderDistance
if (distance < closestHeaderDistance && distance >= 0) {
closestHeader = header;
closestHeaderDistance = distance;
}
});
// If a closest header is found, proceed to highlight the corresponding sidebar link
if (closestHeader) {
// Loop through each link in the sidebar
sidebarLinks.forEach(link => {
// Get the parent <li> element of the link
const listItem = link.parentElement;
// Check if the link's href matches the id of the closest header
if (link.getAttribute("href") === "#" + closestHeader.id) {
// If it matches, add the 'active-header' class to highlight it
listItem.classList.add("active-header");
} else {
// If it doesn't match, remove the 'active-header' class
listItem.classList.remove("active-header");
}
});
}
}
// Attach the scroll event listener, prevent adding it multiple times on desktop
var addEventListenerAdded;
if (!addEventListenerAdded) {
window.addEventListener("scroll", highlightHeaderInView);
addEventListenerAdded = true;
}
Additional css to the style the active-header
/* Styling for the active header */
.sidebar-list .active-header {
font-weight: bold;
}
Debugging
I didn’t test this code, so please try use inspect to debug it (you should be seeing class="active-header"
getting added to a <li>
. To debug the code itself, you can add console.log(...)
calls for variables to see what’s up. For example, you could do
const headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
console.log(headers)
To check whether it’s correctly getting the headers in the card.