Add mdBook documentation with Ghostty-style sidebar
This commit is contained in:
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
// 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";
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user