Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Table of Contents Component for api pages #1775

Open
wants to merge 2 commits into
base: gh-pages
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion _includes/api/en/4x/menu.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<ul id="menu">

<li><em>On this page</em>
<li id="express-api"><a href="#express">express()</a>
<ul id="express-menu">
<li><em>Methods</em></li>
Expand Down Expand Up @@ -204,4 +204,6 @@
</li>
</ul>
</li>
</li>
</ul>
<button id="menu-toggle" title="show table of content">Table of content &#x25BC</button>
8 changes: 8 additions & 0 deletions css/dark-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,11 @@ html.dark-mode #blog-side-menu-container h3 a{
html.dark-mode #blog-side-menu > li > a{
color: var(--dark_inner_text);
}

@media all and (max-width: 1440px) {
html.dark-mode #menu,
#tags-side-menu,
#blog-side-menu-container {
background-color: var(--second_dark_bg);
}
}
95 changes: 86 additions & 9 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -705,12 +705,12 @@ footer {
#blog-side-menu-container {
position: fixed;
margin: 0;
padding: 0 10px 0 0;
padding: 0;
top: 153px;
left: 30px;
right: 0;
height: 500px;
text-align: left;
font-size: 13px;
font-size: 1rem;
overflow-y: auto;
}

Expand Down Expand Up @@ -840,9 +840,82 @@ h2 a {
}
}

/* table of content btn */
#menu-toggle {
cursor: pointer;
display: none;
margin-block: 1.5rem;
font-size: 1rem;
padding: 0.5rem;
opacity: 0;
}

/* responsive */

@media all and (max-width: 1440px) {
#menu,
#tags-side-menu,
#blog-side-menu-container {
position: sticky;
max-height: 0;
top: 57px;
left: 0;
background-color: #eee;
z-index: 100;
}

#menu.open {
max-height: 50vh;
padding: 1rem;
}

#menu-toggle.show{
display: block;
opacity: 1;
}

#menu-toggle.position-fixed {
position: fixed;
right: 1rem;
top: 57px;
margin-block: 1rem;
border-radius: 50%;
background-color: #eeeeee56;
padding: 1rem;
border: 1px solid rgb(202, 199, 199);
display: flex;
justify-content: center;
align-items: center;
}

.content {
margin: 57px 3% 7%;
}

#menu li {
cursor: pointer;
border-bottom: 1px solid #cdcdcd;
}

#overlay.blurs{
display: block;
}
}

/* responsive */

@media all and (max-width: 1110px) {
#menu,
#tags-side-menu,
#blog-side-menu-container {
position: fixed;
border-bottom: 1px solid #ddd;
}

.content {
margin: 80px 3% 7%;
}

#boxes {
grid-template-columns: 1fr 1fr;
}
Expand Down Expand Up @@ -908,12 +981,6 @@ h2 a {
font-weight: normal;
}

#menu,
#tags-side-menu,
#blog-side-menu-container {
display: none;
}

.content {
padding-left: 0;
}
Expand Down Expand Up @@ -1020,6 +1087,16 @@ h2 a {
#boxes {
grid-template-columns: 1fr;
}

#menu-toggle.position-fixed {
left: 0;
top: 57px;
margin-block: 0rem;
width: 100vw;
padding: 0.5rem;
border-radius: 0;
background-color: #eeeeeec9;
}
}

@media all and (max-width: 420px) {
Expand Down
97 changes: 96 additions & 1 deletion js/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const menu = document.querySelector("#navmenu");
const overlay = document.querySelector("#overlay");
const navButton = document.querySelector("#nav-button");
const languagePickerButton = document.querySelector("#language-picker-button");
const toggleBtn = document.getElementById("menu-toggle");
const menuList = document.getElementById("menu");

for (const el of linkItemsMenu) {
el.addEventListener("click", (e) => {
Expand Down Expand Up @@ -83,6 +85,7 @@ navButton?.addEventListener("click", () => {
languagePickerMenu?.classList.remove("opens");
menu?.classList.toggle("opens");
} else {
updateTocVisibility();
menu?.classList.toggle("opens");
overlay?.classList.toggle("blurs");
document.body.classList.toggle("no-scroll");
Expand All @@ -96,6 +99,7 @@ languagePickerButton?.addEventListener("click", () => {
menu?.classList.remove("opens");
languagePickerMenu?.classList.toggle("opens");
} else {
updateTocVisibility();
languagePickerMenu?.classList.toggle("opens");
overlay?.classList.toggle("blurs");
document.body.classList.toggle("no-scroll");
Expand All @@ -109,10 +113,101 @@ overlay?.addEventListener("click", () => {
if (languagePickerMenu?.classList.contains("opens")) {
languagePickerMenu.classList.remove("opens");
}
if(menuList?.classList.contains("open")) {
toggleBtn?.classList.add("show");
menuList?.classList.remove("open");
menu.disabled = "false";
}
overlay.classList.remove("blurs");
document.body.classList.remove("no-scroll");
});

document
.querySelector(`.submenu-content a[href="${document.location.pathname}"]`)
?.classList.add("current");
?.classList.add("current");

// TOC
const tocScreen = window.matchMedia("(max-width: 1440px)");
let isTocScreen = tocScreen.matches;
// ! important note add scroll observer element common to all pages that include TOC component 👇🏻 remove id to "scroll-observer"
const firstHeader = document.getElementById("express");
let observer;


// Scroll observer (perform better than window scroll event listener)
function createScrollObserver() {
if (observer) observer.disconnect();

let options = {
root: null, // Observe relative to viewport
rootMargin: "-57px 0px 0px 0px", // Slight offset to ensure intersection triggers correctly
threshold: 0, // Trigger when the element is fully out of view (i.e. behind header)
};

observer = new IntersectionObserver(handleIntersect, options);
// observe intersection of TOC btn with header bar
observer.observe(firstHeader);
}
Comment on lines +138 to +150
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to have an observer? I don't see why we should use it—the less JavaScript, the better. If we can use the menu logic instead, that would be better since this behaves like a menu

We could even use the <details> element for this case. Of course, I'll leave it up to you, but the less JavaScript, the better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Adapt or reusing the menu logic should theoretically get you pretty far, and save you time writing new JS.


// Update button visibility based on screen size
function updateTocVisibility() {
if (isTocScreen) {
overlay.classList.remove("blurs")
menuList.classList.remove("open");
toggleBtn?.classList.add("show");
createScrollObserver();
} else {
toggleBtn?.classList.remove("show");
if (observer) observer.disconnect();
}
}

function handleIntersect(entries) {
const [entry] = entries
const clientWidth = entry.boundingClientRect.width
// first header in invisible then show floating TOC btn
if(!entry.isIntersecting) {
if(toggleBtn) {
if(clientWidth >= 540) {
toggleBtn.innerHTML = "&#x25BC";
} else {
toggleBtn.innerHTML = "Table of content &#x25BC";
};
}
toggleBtn?.classList.add("position-fixed");
};
// first header is visible then show static TOC btn
if(entry.isIntersecting) {
if(toggleBtn) toggleBtn.innerHTML = "Table of content &#x25BC";
toggleBtn?.classList.remove("position-fixed");
};
};

// Show button on page load
updateTocVisibility();

// Listen for changes in screen size
tocScreen.addEventListener("change", (event) => {
isTocScreen = event.matches;
updateTocVisibility();
});

// Toggle menu on button click
toggleBtn?.addEventListener("click", () => {
menuList?.classList.toggle("open");
overlay?.classList.toggle("blurs");
document.body.classList.toggle("no-scroll");
toggleBtn?.classList.remove("show");
});

// Close menu on link click
document.querySelectorAll("#menu a").forEach((link) => {
link.addEventListener("click", function () {
if(isTocScreen) {
menuList?.classList.remove("open");
overlay?.classList.remove("blurs");
document.body.classList.remove("no-scroll");
toggleBtn?.classList.add("show");
}
});
});