227 lines
6.8 KiB
JavaScript
227 lines
6.8 KiB
JavaScript
|
|
// Replace default mdBook themes with Catppuccin themes
|
|||
|
|
(function () {
|
|||
|
|
"use strict";
|
|||
|
|
|
|||
|
|
// Wait for DOM to be ready
|
|||
|
|
if (document.readyState === "loading") {
|
|||
|
|
document.addEventListener("DOMContentLoaded", init);
|
|||
|
|
} else {
|
|||
|
|
init();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function init() {
|
|||
|
|
addSidebarLogo();
|
|||
|
|
replaceThemeList();
|
|||
|
|
setupGhosttyStyleSidebar();
|
|||
|
|
|
|||
|
|
// Watch for sidebar changes and re-run setup
|
|||
|
|
const sidebarScrollbox = document.querySelector(".sidebar-scrollbox");
|
|||
|
|
if (sidebarScrollbox) {
|
|||
|
|
const observer = new MutationObserver(() => {
|
|||
|
|
// Wait a bit for mdBook to finish updating
|
|||
|
|
setTimeout(setupGhosttyStyleSidebar, 50);
|
|||
|
|
});
|
|||
|
|
observer.observe(sidebarScrollbox, {
|
|||
|
|
childList: true,
|
|||
|
|
subtree: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Also re-run on page navigation
|
|||
|
|
window.addEventListener("hashchange", () => {
|
|||
|
|
setTimeout(setupGhosttyStyleSidebar, 100);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addSidebarLogo() {
|
|||
|
|
const scrollbox = document.querySelector(".sidebar-scrollbox");
|
|||
|
|
if (!scrollbox) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Check if logo already exists
|
|||
|
|
if (document.querySelector(".sidebar-logo")) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Create logo container
|
|||
|
|
const logoContainer = document.createElement("div");
|
|||
|
|
logoContainer.className = "sidebar-logo";
|
|||
|
|
|
|||
|
|
// Create clickable link wrapper
|
|||
|
|
const logoLink = document.createElement("a");
|
|||
|
|
logoLink.href = "https://socktop.io";
|
|||
|
|
logoLink.style.display = "block";
|
|||
|
|
logoLink.style.textAlign = "center";
|
|||
|
|
|
|||
|
|
// Create logo image
|
|||
|
|
const logoImg = document.createElement("img");
|
|||
|
|
// Use root-relative path that works from any page depth
|
|||
|
|
logoImg.src = window.location.pathname.includes("/assets/docs/")
|
|||
|
|
? "/assets/docs/logo.png"
|
|||
|
|
: "logo.png";
|
|||
|
|
logoImg.alt = "socktop";
|
|||
|
|
logoImg.style.display = "inline-block";
|
|||
|
|
logoImg.style.maxWidth = "80%";
|
|||
|
|
|
|||
|
|
logoLink.appendChild(logoImg);
|
|||
|
|
logoContainer.appendChild(logoLink);
|
|||
|
|
|
|||
|
|
// Insert as the very first child inside the scrollbox
|
|||
|
|
scrollbox.insertBefore(logoContainer, scrollbox.firstChild);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function replaceThemeList() {
|
|||
|
|
const themeList = document.getElementById("mdbook-theme-list");
|
|||
|
|
if (!themeList) {
|
|||
|
|
console.warn("Theme list not found");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Clear existing themes
|
|||
|
|
themeList.innerHTML = "";
|
|||
|
|
|
|||
|
|
// Catppuccin themes
|
|||
|
|
const catppuccinThemes = [
|
|||
|
|
{ id: "latte", name: "Latte" },
|
|||
|
|
{ id: "frappe", name: "Frappé" },
|
|||
|
|
{ id: "macchiato", name: "Macchiato" },
|
|||
|
|
{ id: "mocha", name: "Mocha" },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// Add Catppuccin themes
|
|||
|
|
catppuccinThemes.forEach((theme) => {
|
|||
|
|
const li = document.createElement("li");
|
|||
|
|
li.setAttribute("role", "none");
|
|||
|
|
|
|||
|
|
const button = document.createElement("button");
|
|||
|
|
button.setAttribute("role", "menuitem");
|
|||
|
|
button.className = "theme";
|
|||
|
|
button.id = "mdbook-theme-" + theme.id;
|
|||
|
|
button.textContent = theme.name;
|
|||
|
|
|
|||
|
|
li.appendChild(button);
|
|||
|
|
themeList.appendChild(li);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function setupGhosttyStyleSidebar() {
|
|||
|
|
// Hide mdBook's default fold toggles
|
|||
|
|
const defaultToggles = document.querySelectorAll(".chapter-fold-toggle");
|
|||
|
|
defaultToggles.forEach((toggle) => {
|
|||
|
|
toggle.style.display = "none";
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Get current page path to determine active item
|
|||
|
|
const currentPath = window.location.pathname;
|
|||
|
|
|
|||
|
|
// Find all chapter items
|
|||
|
|
const allChapterItems = document.querySelectorAll(
|
|||
|
|
"ol.chapter > li.chapter-item",
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
allChapterItems.forEach((li) => {
|
|||
|
|
// Check if this item has a nested section list
|
|||
|
|
const nestedList = li.querySelector("ol.section");
|
|||
|
|
|
|||
|
|
// Skip if no nested list (like Introduction)
|
|||
|
|
if (!nestedList) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const linkWrapper = li.querySelector("span.chapter-link-wrapper");
|
|||
|
|
const link = linkWrapper ? linkWrapper.querySelector("a") : null;
|
|||
|
|
|
|||
|
|
if (!linkWrapper) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Check if any child link matches current page
|
|||
|
|
let hasActivePage = false;
|
|||
|
|
const childLinks = nestedList.querySelectorAll("a");
|
|||
|
|
childLinks.forEach((childLink) => {
|
|||
|
|
const href = childLink.getAttribute("href");
|
|||
|
|
if (
|
|||
|
|
href &&
|
|||
|
|
currentPath.includes(href.replace("../", "").replace("./", ""))
|
|||
|
|
) {
|
|||
|
|
childLink.closest("li.chapter-item").classList.add("active");
|
|||
|
|
hasActivePage = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Skip if we already added a chevron - just update state
|
|||
|
|
const existingChevron = linkWrapper.querySelector(".chapter-chevron");
|
|||
|
|
if (existingChevron) {
|
|||
|
|
if (hasActivePage) {
|
|||
|
|
nestedList.style.display = "block";
|
|||
|
|
li.classList.add("expanded");
|
|||
|
|
li.classList.remove("collapsed");
|
|||
|
|
existingChevron.style.transform = "rotate(90deg)";
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Create custom chevron
|
|||
|
|
const chevron = document.createElement("span");
|
|||
|
|
chevron.className = "chapter-chevron";
|
|||
|
|
chevron.textContent = "›";
|
|||
|
|
chevron.style.cssText =
|
|||
|
|
"float: right; transition: transform 0.2s ease; display: inline-block; opacity: 0.6; font-size: 1.2em; line-height: 1; user-select: none; cursor: pointer;";
|
|||
|
|
|
|||
|
|
// Insert chevron into the link wrapper
|
|||
|
|
linkWrapper.appendChild(chevron);
|
|||
|
|
|
|||
|
|
// Start expanded if it contains the active page, collapsed otherwise
|
|||
|
|
if (hasActivePage) {
|
|||
|
|
nestedList.style.display = "block";
|
|||
|
|
li.classList.add("expanded");
|
|||
|
|
li.classList.remove("collapsed");
|
|||
|
|
chevron.style.transform = "rotate(90deg)";
|
|||
|
|
} else {
|
|||
|
|
nestedList.style.display = "none";
|
|||
|
|
li.classList.add("collapsed");
|
|||
|
|
li.classList.remove("expanded");
|
|||
|
|
chevron.style.transform = "rotate(0deg)";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Add click handler to toggle
|
|||
|
|
const toggleSection = function (e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
|
|||
|
|
const isCollapsed = li.classList.contains("collapsed");
|
|||
|
|
|
|||
|
|
if (isCollapsed) {
|
|||
|
|
// Expand
|
|||
|
|
nestedList.style.display = "block";
|
|||
|
|
li.classList.remove("collapsed");
|
|||
|
|
li.classList.add("expanded");
|
|||
|
|
chevron.style.transform = "rotate(90deg)";
|
|||
|
|
} else {
|
|||
|
|
// Collapse
|
|||
|
|
nestedList.style.display = "none";
|
|||
|
|
li.classList.add("collapsed");
|
|||
|
|
li.classList.remove("expanded");
|
|||
|
|
chevron.style.transform = "rotate(0deg)";
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Click on chevron toggles
|
|||
|
|
chevron.addEventListener("click", toggleSection);
|
|||
|
|
|
|||
|
|
// Click on parent link also toggles if it's a dummy link
|
|||
|
|
if (link) {
|
|||
|
|
const href = link.getAttribute("href");
|
|||
|
|
if (!href || href === "" || href === "#") {
|
|||
|
|
link.addEventListener("click", toggleSection);
|
|||
|
|
link.style.cursor = "pointer";
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
linkWrapper.addEventListener("click", toggleSection);
|
|||
|
|
linkWrapper.style.cursor = "pointer";
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
})();
|