Last active
May 13, 2026 12:20
-
-
Save oirodolfo/47fc011dfc822b8347f96834c5ad8993 to your computer and use it in GitHub Desktop.
ZSH userscript.user.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Zsh Manual Mobile Reader - v7 | |
| // @namespace https://migos.club/userscripts | |
| // @version 1.0.0 | |
| // @description Mobile-first Zsh manual reader with resilient parsing, full-width wrapped Highlight.js code blocks, cleaner anchors, and compact repeated module links. | |
| // @match https://zsh.sourceforge.io/Doc/**/* | |
| // @run-at document-end | |
| // @grant none | |
| // ==/UserScript== | |
| (() => { | |
| "use strict"; | |
| const IDS = { | |
| root: "migos-zsh-root", | |
| style: "migos-zsh-style", | |
| toc: "migos-zsh-toc", | |
| hljs: "migos-zsh-hljs", | |
| }; | |
| const STORAGE = { | |
| accent: "migos:zsh:accent", | |
| scale: "migos:zsh:scale", | |
| }; | |
| const DEFAULT_ACCENT = "#7b73d8"; | |
| const HLJS_URL = "https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/lib/common.min.js"; | |
| function clamp(value, min, max) { | |
| return Math.min(Math.max(value, min), max); | |
| } | |
| function getScale() { | |
| return clamp(Number(localStorage.getItem(STORAGE.scale) || "1"), 0.88, 1.12); | |
| } | |
| function getAccent() { | |
| const value = localStorage.getItem(STORAGE.accent) || DEFAULT_ACCENT; | |
| return value === "#000000" || value === "#ffffff" ? DEFAULT_ACCENT : value; | |
| } | |
| function installStyles() { | |
| document.getElementById(IDS.style)?.remove(); | |
| const style = document.createElement("style"); | |
| style.id = IDS.style; | |
| style.textContent = ` | |
| :root { | |
| color-scheme: dark; | |
| --zsh-accent: ${getAccent()}; | |
| --zsh-bg: #0f1220; | |
| --zsh-page: #121626; | |
| --zsh-code: #070a12; | |
| --zsh-text: #eef0f7; | |
| --zsh-muted: #aeb4c6; | |
| --zsh-faint: #747d94; | |
| --zsh-border: rgba(255,255,255,.08); | |
| --zsh-scale: ${getScale()}; | |
| --zsh-top-offset: max(5.7rem, env(safe-area-inset-top)); | |
| } | |
| html, | |
| body { | |
| margin: 0 !important; | |
| padding: 0 !important; | |
| background: var(--zsh-bg) !important; | |
| color: var(--zsh-text) !important; | |
| font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important; | |
| font-size: calc(16px * var(--zsh-scale)) !important; | |
| line-height: 1.56 !important; | |
| -webkit-text-size-adjust: 100% !important; | |
| overflow-x: hidden !important; | |
| } | |
| body > *:not(#${IDS.root}):not(#${IDS.toc}) { | |
| display: none !important; | |
| } | |
| #${IDS.root} { | |
| box-sizing: border-box; | |
| width: 100%; | |
| padding: var(--zsh-top-offset) .82rem 6.5rem; | |
| background: radial-gradient(circle at top, rgba(123,115,216,.08), transparent 28rem), var(--zsh-bg); | |
| } | |
| .zsh-shell { | |
| max-width: 46rem; | |
| margin: 0 auto; | |
| } | |
| .zsh-toolbar { | |
| position: sticky; | |
| top: max(.65rem, env(safe-area-inset-top)); | |
| z-index: 40; | |
| display: grid; | |
| grid-template-columns: 1fr auto auto auto; | |
| gap: .38rem; | |
| align-items: center; | |
| padding: .48rem; | |
| margin-bottom: 1rem; | |
| border: 1px solid var(--zsh-border); | |
| border-radius: 1rem; | |
| background: rgba(18,22,38,.88); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| } | |
| .zsh-search, | |
| .zsh-btn, | |
| .zsh-color { | |
| height: 2.3rem; | |
| border: 1px solid var(--zsh-border); | |
| border-radius: .78rem; | |
| background: rgba(255,255,255,.035); | |
| color: var(--zsh-text); | |
| font: 700 .82rem ui-sans-serif, system-ui; | |
| } | |
| .zsh-search { | |
| min-width: 0; | |
| padding: 0 .72rem; | |
| outline: none; | |
| } | |
| .zsh-btn { | |
| padding: 0 .66rem; | |
| } | |
| .zsh-color { | |
| width: 2.3rem; | |
| padding: .18rem; | |
| } | |
| .zsh-hero { | |
| margin-bottom: 1rem; | |
| padding-bottom: .85rem; | |
| border-bottom: 1px solid var(--zsh-border); | |
| } | |
| .zsh-kicker { | |
| color: var(--zsh-faint); | |
| font-size: .66rem; | |
| font-weight: 850; | |
| letter-spacing: .15em; | |
| text-transform: uppercase; | |
| } | |
| .zsh-title { | |
| margin: .25rem 0 0; | |
| color: var(--zsh-text); | |
| font-size: clamp(1.85rem, 9.5vw, 3.05rem); | |
| line-height: 1; | |
| letter-spacing: -.055em; | |
| font-weight: 900; | |
| } | |
| .zsh-content h1, | |
| .zsh-content h2, | |
| .zsh-content h3, | |
| .zsh-content h4 { | |
| margin: 1.45rem 0 .65rem !important; | |
| color: var(--zsh-text) !important; | |
| font-weight: 820 !important; | |
| line-height: 1.14 !important; | |
| letter-spacing: -.03em !important; | |
| scroll-margin-top: calc(var(--zsh-top-offset) + 1rem) !important; | |
| } | |
| .zsh-content h1 { font-size: 1.58rem !important; } | |
| .zsh-content h2 { font-size: 1.34rem !important; } | |
| .zsh-content h3 { font-size: 1.12rem !important; } | |
| .zsh-content h4 { font-size: 1rem !important; } | |
| .zsh-content p, | |
| .zsh-content li, | |
| .zsh-content dd, | |
| .zsh-content dt { | |
| margin: .72rem 0 !important; | |
| color: var(--zsh-text) !important; | |
| font-weight: 420 !important; | |
| line-height: 1.56 !important; | |
| } | |
| .zsh-content ul, | |
| .zsh-content ol { | |
| padding-left: 1.05rem !important; | |
| margin: .65rem 0 !important; | |
| } | |
| .zsh-content dt { | |
| color: color-mix(in srgb, var(--zsh-accent) 52%, var(--zsh-text)) !important; | |
| font-weight: 720 !important; | |
| } | |
| .zsh-content dd { | |
| margin-left: 0 !important; | |
| padding-left: .72rem !important; | |
| border-left: 1px solid var(--zsh-border); | |
| color: var(--zsh-muted) !important; | |
| } | |
| .zsh-content a { | |
| color: color-mix(in srgb, var(--zsh-accent) 58%, var(--zsh-text)) !important; | |
| text-decoration: none !important; | |
| border-bottom: 1px solid color-mix(in srgb, var(--zsh-accent) 24%, transparent); | |
| font-weight: 570 !important; | |
| } | |
| .zsh-content code, | |
| .zsh-content tt, | |
| .zsh-content kbd { | |
| padding: .07rem .28rem !important; | |
| border: 1px solid color-mix(in srgb, var(--zsh-accent) 14%, transparent) !important; | |
| border-radius: .34rem !important; | |
| background: color-mix(in srgb, var(--zsh-accent) 8%, transparent) !important; | |
| color: color-mix(in srgb, var(--zsh-accent) 44%, var(--zsh-text)) !important; | |
| font-family: "SF Mono", ui-monospace, Menlo, Monaco, Consolas, monospace !important; | |
| font-size: .82em !important; | |
| font-weight: 640 !important; | |
| } | |
| .zsh-codebox { | |
| width: calc(100vw - 1.64rem) !important; | |
| max-width: calc(100vw - 1.64rem) !important; | |
| margin: 1rem 50% !important; | |
| transform: translateX(-50%); | |
| box-sizing: border-box !important; | |
| border: 1px solid rgba(255,255,255,.075); | |
| border-radius: .9rem; | |
| background: var(--zsh-code); | |
| overflow: hidden; | |
| } | |
| .zsh-codebar { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: .7rem; | |
| height: 2.15rem; | |
| padding: 0 .78rem; | |
| border-bottom: 1px solid rgba(255,255,255,.06); | |
| background: rgba(255,255,255,.035); | |
| color: var(--zsh-faint); | |
| font: 850 .68rem ui-sans-serif, system-ui; | |
| letter-spacing: .12em; | |
| text-transform: uppercase; | |
| } | |
| .zsh-copy { | |
| border: 0; | |
| background: transparent; | |
| color: var(--zsh-muted); | |
| font: inherit; | |
| } | |
| .zsh-content pre { | |
| width: 100% !important; | |
| max-width: 100% !important; | |
| box-sizing: border-box !important; | |
| overflow-x: hidden !important; | |
| margin: 0 !important; | |
| padding: .78rem !important; | |
| border: 0 !important; | |
| border-radius: 0 !important; | |
| background: transparent !important; | |
| box-shadow: none !important; | |
| white-space: pre-wrap !important; | |
| word-break: break-word !important; | |
| overflow-wrap: anywhere !important; | |
| } | |
| .zsh-content pre code { | |
| display: block !important; | |
| padding: 0 !important; | |
| border: 0 !important; | |
| background: transparent !important; | |
| color: #edf0fb !important; | |
| font-family: "SF Mono", ui-monospace, Menlo, Monaco, Consolas, monospace !important; | |
| font-size: .76rem !important; | |
| line-height: 1.48 !important; | |
| font-weight: 500 !important; | |
| white-space: pre-wrap !important; | |
| word-break: break-word !important; | |
| overflow-wrap: anywhere !important; | |
| tab-size: 2 !important; | |
| } | |
| .hljs-keyword, | |
| .hljs-built_in, | |
| .hljs-name { | |
| color: #d29bf0 !important; | |
| font-weight: 720; | |
| } | |
| .hljs-string { | |
| color: #f0b7cf !important; | |
| } | |
| .hljs-number, | |
| .hljs-literal { | |
| color: #9ddfbd !important; | |
| } | |
| .hljs-comment { | |
| color: #68728a !important; | |
| font-style: italic; | |
| } | |
| .hljs-variable, | |
| .hljs-template-variable { | |
| color: #b6a6f6 !important; | |
| } | |
| .hljs-operator, | |
| .hljs-punctuation { | |
| color: #e5e7f2 !important; | |
| } | |
| .zsh-content table { | |
| display: block !important; | |
| width: 100% !important; | |
| max-width: 100% !important; | |
| overflow-x: auto !important; | |
| border-collapse: collapse !important; | |
| font-size: .9rem !important; | |
| } | |
| .zsh-content td, | |
| .zsh-content th { | |
| padding: .42rem .48rem !important; | |
| border-bottom: 1px solid var(--zsh-border) !important; | |
| } | |
| .zsh-nav-noise, | |
| .zsh-empty-link-grid { | |
| display: none !important; | |
| } | |
| #${IDS.toc} { | |
| position: fixed; | |
| inset: 0 auto 0 0; | |
| z-index: 60; | |
| width: min(21rem, calc(100vw - 3rem)); | |
| box-sizing: border-box; | |
| padding: max(1rem, env(safe-area-inset-top)) .8rem max(1rem, env(safe-area-inset-bottom)); | |
| background: rgba(15,18,31,.98); | |
| border-right: 1px solid var(--zsh-border); | |
| transform: translateX(-105%); | |
| transition: transform .2s ease; | |
| overflow-y: auto; | |
| } | |
| html.zsh-toc-open #${IDS.toc} { | |
| transform: translateX(0); | |
| } | |
| .zsh-toc-head { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: .65rem; | |
| color: var(--zsh-text); | |
| font-weight: 820; | |
| } | |
| .zsh-toc-search { | |
| width: 100%; | |
| box-sizing: border-box; | |
| height: 2.3rem; | |
| margin-bottom: .65rem; | |
| border: 1px solid var(--zsh-border); | |
| border-radius: .75rem; | |
| background: rgba(255,255,255,.04); | |
| color: var(--zsh-text); | |
| padding: 0 .7rem; | |
| font: 650 .86rem ui-sans-serif, system-ui; | |
| } | |
| .zsh-toc-list { | |
| list-style: none; | |
| padding: 0; | |
| margin: 0; | |
| display: grid; | |
| gap: .08rem; | |
| } | |
| .zsh-toc-link { | |
| display: block; | |
| padding: .34rem .46rem; | |
| border-radius: .58rem; | |
| color: var(--zsh-muted) !important; | |
| border: 0 !important; | |
| text-decoration: none !important; | |
| font-size: .84rem; | |
| line-height: 1.28; | |
| font-weight: 560 !important; | |
| } | |
| .zsh-toc-link:hover, | |
| .zsh-toc-link.is-active { | |
| background: rgba(255,255,255,.045); | |
| color: var(--zsh-text) !important; | |
| } | |
| .zsh-l1 { padding-left: .1rem; font-weight: 760 !important; } | |
| .zsh-l2 { padding-left: .45rem; } | |
| .zsh-l3 { padding-left: .75rem; font-size: .8rem; } | |
| .zsh-l4 { padding-left: 1rem; font-size: .76rem; color: var(--zsh-faint) !important; } | |
| .zsh-fab { | |
| position: fixed; | |
| right: max(.85rem, env(safe-area-inset-right)); | |
| bottom: max(5.6rem, env(safe-area-inset-bottom)); | |
| z-index: 45; | |
| width: 3.1rem; | |
| height: 3.1rem; | |
| border: 1px solid var(--zsh-border); | |
| border-radius: 999px; | |
| background: rgba(21,25,41,.88); | |
| color: var(--zsh-text); | |
| font-size: 1.15rem; | |
| font-weight: 850; | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| } | |
| .zsh-menu { | |
| right: max(4.35rem, env(safe-area-inset-right)); | |
| } | |
| .zsh-hidden { | |
| display: none !important; | |
| } | |
| @media (max-width: 430px) { | |
| #${IDS.root} { | |
| padding-inline: .72rem; | |
| } | |
| .zsh-toolbar { | |
| grid-template-columns: 1fr auto auto; | |
| } | |
| .zsh-color { | |
| display: none; | |
| } | |
| .zsh-codebox { | |
| width: 100vw !important; | |
| max-width: 100vw !important; | |
| border-radius: 0; | |
| border-left: 0; | |
| border-right: 0; | |
| } | |
| .zsh-content pre { | |
| padding: .72rem !important; | |
| } | |
| .zsh-content pre code { | |
| font-size: .73rem !important; | |
| } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| function wrapPage() { | |
| if (document.getElementById(IDS.root)) return; | |
| const nodes = [...document.body.childNodes]; | |
| const root = document.createElement("main"); | |
| const shell = document.createElement("div"); | |
| const content = document.createElement("article"); | |
| root.id = IDS.root; | |
| shell.className = "zsh-shell"; | |
| content.className = "zsh-content"; | |
| for (const node of nodes) { | |
| content.appendChild(node); | |
| } | |
| shell.append(createToolbar(), createHero(), content); | |
| root.append(shell); | |
| document.body.append(root); | |
| } | |
| function createToolbar() { | |
| const toolbar = document.createElement("div"); | |
| const search = document.createElement("input"); | |
| const color = document.createElement("input"); | |
| const minus = document.createElement("button"); | |
| const plus = document.createElement("button"); | |
| toolbar.className = "zsh-toolbar"; | |
| search.className = "zsh-search"; | |
| search.type = "search"; | |
| search.placeholder = "Search page..."; | |
| color.className = "zsh-color"; | |
| color.type = "color"; | |
| color.value = getAccent(); | |
| minus.className = "zsh-btn"; | |
| minus.textContent = "A−"; | |
| plus.className = "zsh-btn"; | |
| plus.textContent = "A+"; | |
| search.addEventListener("input", () => filterPage(search.value)); | |
| color.addEventListener("input", () => { | |
| localStorage.setItem(STORAGE.accent, color.value); | |
| installStyles(); | |
| }); | |
| minus.addEventListener("click", () => { | |
| localStorage.setItem(STORAGE.scale, String(clamp(getScale() - 0.04, 0.88, 1.12))); | |
| installStyles(); | |
| }); | |
| plus.addEventListener("click", () => { | |
| localStorage.setItem(STORAGE.scale, String(clamp(getScale() + 0.04, 0.88, 1.12))); | |
| installStyles(); | |
| }); | |
| toolbar.append(search, color, minus, plus); | |
| return toolbar; | |
| } | |
| function createHero() { | |
| const hero = document.createElement("header"); | |
| const title = document.querySelector("h1,h2")?.textContent?.trim() || document.title || "Zsh Manual"; | |
| hero.className = "zsh-hero"; | |
| hero.innerHTML = ` | |
| <div class="zsh-kicker">Zsh Reference Manual</div> | |
| <h1 class="zsh-title">${escapeHtml(title)}</h1> | |
| `; | |
| return hero; | |
| } | |
| function installHljs() { | |
| return new Promise((resolve) => { | |
| if (window.hljs) { | |
| resolve(window.hljs); | |
| return; | |
| } | |
| if (document.getElementById(IDS.hljs)) { | |
| document.getElementById(IDS.hljs).addEventListener("load", () => resolve(window.hljs)); | |
| return; | |
| } | |
| const script = document.createElement("script"); | |
| script.id = IDS.hljs; | |
| script.src = HLJS_URL; | |
| script.onload = () => resolve(window.hljs); | |
| script.onerror = () => resolve(null); | |
| document.head.appendChild(script); | |
| }); | |
| } | |
| function normalizeCodeBlocks() { | |
| const candidates = [ | |
| ...document.querySelectorAll(".zsh-content pre"), | |
| ...document.querySelectorAll(".zsh-content table pre"), | |
| ]; | |
| for (const pre of candidates) { | |
| if (pre.closest(".zsh-codebox")) continue; | |
| const raw = pre.textContent || ""; | |
| const cleaned = dedent(raw) | |
| .replace(/\n{3,}/g, "\n\n") | |
| .trim(); | |
| const box = document.createElement("figure"); | |
| const bar = document.createElement("figcaption"); | |
| const copy = document.createElement("button"); | |
| const nextPre = document.createElement("pre"); | |
| const code = document.createElement("code"); | |
| box.className = "zsh-codebox"; | |
| bar.className = "zsh-codebar"; | |
| copy.className = "zsh-copy"; | |
| copy.type = "button"; | |
| copy.textContent = "Copy"; | |
| code.className = "language-bash"; | |
| code.textContent = cleaned; | |
| copy.addEventListener("click", async () => { | |
| await navigator.clipboard?.writeText(cleaned); | |
| copy.textContent = "Copied"; | |
| setTimeout(() => { | |
| copy.textContent = "Copy"; | |
| }, 1200); | |
| }); | |
| bar.append(document.createTextNode("zsh"), copy); | |
| nextPre.append(code); | |
| box.append(bar, nextPre); | |
| pre.replaceWith(box); | |
| } | |
| } | |
| function fixDefinitionNoise() { | |
| const content = document.querySelector(".zsh-content"); | |
| if (!content) return; | |
| const walkers = document.createTreeWalker(content, NodeFilter.SHOW_TEXT); | |
| for (let node = walkers.nextNode(); node; node = walkers.nextNode()) { | |
| node.nodeValue = node.nodeValue | |
| .replace(/\bsee\s+The\s+zsh\/([^.\s]+)\s+Module\b/gi, "see $1 module") | |
| .replace(/\bSee\s+The\s+zsh\/([^.\s]+)\s+Module\b/g, "See $1 module") | |
| .replace(/\bThe\s+zsh\/([^.\s]+)\s+Module\b/g, "$1 module"); | |
| } | |
| document.querySelectorAll(".zsh-content a").forEach((link) => { | |
| const text = link.textContent?.trim() || ""; | |
| link.textContent = text | |
| .replace(/^The zsh\/(.+) Module$/i, "$1") | |
| .replace(/^zsh\/(.+) Module$/i, "$1") | |
| .replace(/^See The zsh\/(.+) Module$/i, "see $1"); | |
| }); | |
| } | |
| function removeBrokenNavNoise() { | |
| document.querySelectorAll(".zsh-content p, .zsh-content div, .zsh-content table").forEach((node) => { | |
| const text = node.textContent?.replace(/\s+/g, " ").trim() || ""; | |
| if ( | |
| text.includes("<<< Up >>>") || | |
| text === "[ [ [ [ ] ] ] ]" || | |
| /^\[\s*\[\s*\[\s*\[/.test(text) | |
| ) { | |
| node.classList.add("zsh-nav-noise"); | |
| } | |
| }); | |
| } | |
| function fixAnchors() { | |
| document.querySelectorAll(".zsh-content [id], .zsh-content a[name]").forEach((node, index) => { | |
| const id = node.id || node.getAttribute("name"); | |
| if (!id) { | |
| node.id = `zsh-anchor-${index + 1}`; | |
| return; | |
| } | |
| node.id = safeId(id); | |
| }); | |
| document.querySelectorAll("a[href^='#']").forEach((link) => { | |
| const raw = decodeURIComponent(link.getAttribute("href") || "").slice(1); | |
| const id = safeId(raw); | |
| link.setAttribute("href", `#${id}`); | |
| link.addEventListener("click", (event) => { | |
| const target = document.getElementById(id); | |
| if (!target) return; | |
| event.preventDefault(); | |
| document.documentElement.classList.remove("zsh-toc-open"); | |
| const top = target.getBoundingClientRect().top + window.scrollY - 110; | |
| window.scrollTo({ top: Math.max(0, top), behavior: "smooth" }); | |
| history.replaceState(null, "", `#${id}`); | |
| }); | |
| }); | |
| } | |
| function buildToc() { | |
| document.getElementById(IDS.toc)?.remove(); | |
| const toc = document.createElement("aside"); | |
| const search = document.createElement("input"); | |
| const list = document.createElement("ol"); | |
| const headings = [...document.querySelectorAll(".zsh-content h1, .zsh-content h2, .zsh-content h3, .zsh-content h4")] | |
| .filter((heading) => heading.textContent?.trim()); | |
| toc.id = IDS.toc; | |
| toc.innerHTML = ` | |
| <div class="zsh-toc-head"> | |
| <span>Sections</span> | |
| <button class="zsh-btn" type="button">Close</button> | |
| </div> | |
| `; | |
| toc.querySelector("button")?.addEventListener("click", () => { | |
| document.documentElement.classList.remove("zsh-toc-open"); | |
| }); | |
| search.className = "zsh-toc-search"; | |
| search.type = "search"; | |
| search.placeholder = "Filter sections..."; | |
| list.className = "zsh-toc-list"; | |
| headings.forEach((heading, index) => { | |
| if (!heading.id) heading.id = `zsh-heading-${index + 1}`; | |
| const level = heading.tagName.slice(1); | |
| const item = document.createElement("li"); | |
| const link = document.createElement("a"); | |
| link.href = `#${heading.id}`; | |
| link.className = `zsh-toc-link zsh-l${level}`; | |
| link.textContent = cleanText(heading.textContent || ""); | |
| link.addEventListener("click", (event) => { | |
| event.preventDefault(); | |
| document.documentElement.classList.remove("zsh-toc-open"); | |
| const top = heading.getBoundingClientRect().top + window.scrollY - 110; | |
| window.scrollTo({ top: Math.max(0, top), behavior: "smooth" }); | |
| history.replaceState(null, "", `#${heading.id}`); | |
| }); | |
| item.append(link); | |
| list.append(item); | |
| }); | |
| search.addEventListener("input", () => { | |
| const query = search.value.trim().toLowerCase(); | |
| [...list.children].forEach((item) => { | |
| item.classList.toggle("zsh-hidden", query && !item.textContent.toLowerCase().includes(query)); | |
| }); | |
| }); | |
| toc.append(search, list); | |
| document.body.append(toc); | |
| } | |
| function createButtons() { | |
| if (document.querySelector(".zsh-fab")) return; | |
| const menu = document.createElement("button"); | |
| const top = document.createElement("button"); | |
| menu.className = "zsh-fab zsh-menu"; | |
| menu.textContent = "☰"; | |
| menu.addEventListener("click", () => { | |
| document.documentElement.classList.toggle("zsh-toc-open"); | |
| }); | |
| top.className = "zsh-fab"; | |
| top.textContent = "↑"; | |
| top.addEventListener("click", () => { | |
| window.scrollTo({ top: 0, behavior: "smooth" }); | |
| }); | |
| document.body.append(menu, top); | |
| } | |
| function highlightCodeBlocks() { | |
| if (!window.hljs) return; | |
| document.querySelectorAll(".zsh-codebox code").forEach((code) => { | |
| try { | |
| window.hljs.highlightElement(code); | |
| } catch { | |
| // Keep plain code if Highlight.js cannot classify the block. | |
| } | |
| }); | |
| } | |
| function filterPage(query) { | |
| const normalized = query.trim().toLowerCase(); | |
| const content = document.querySelector(".zsh-content"); | |
| if (!content) return; | |
| [...content.children].forEach((block) => { | |
| const text = block.textContent?.toLowerCase() || ""; | |
| block.classList.toggle("zsh-hidden", normalized && !text.includes(normalized)); | |
| }); | |
| } | |
| function dedent(value) { | |
| const lines = value.replace(/\t/g, " ").split("\n"); | |
| const nonEmpty = lines.filter((line) => line.trim().length > 0); | |
| if (nonEmpty.length === 0) return ""; | |
| const minIndent = Math.min(...nonEmpty.map((line) => line.match(/^ */)?.[0].length || 0)); | |
| return lines.map((line) => line.slice(minIndent)).join("\n"); | |
| } | |
| function safeId(value) { | |
| return value | |
| .trim() | |
| .replace(/^#/, "") | |
| .replace(/[^\w\-:.]/g, "-") | |
| .replace(/-+/g, "-"); | |
| } | |
| function cleanText(value) { | |
| return value.replace(/\s+/g, " ").trim(); | |
| } | |
| function escapeHtml(value) { | |
| return value | |
| .replaceAll("&", "&") | |
| .replaceAll("<", "<") | |
| .replaceAll(">", ">") | |
| .replaceAll('"', """) | |
| .replaceAll("'", "'"); | |
| } | |
| async function boot() { | |
| installStyles(); | |
| wrapPage(); | |
| fixDefinitionNoise(); | |
| removeBrokenNavNoise(); | |
| normalizeCodeBlocks(); | |
| fixAnchors(); | |
| buildToc(); | |
| createButtons(); | |
| await installHljs(); | |
| highlightCodeBlocks(); | |
| if (location.hash) { | |
| const target = document.getElementById(safeId(decodeURIComponent(location.hash.slice(1)))); | |
| if (target) { | |
| setTimeout(() => { | |
| const top = target.getBoundingClientRect().top + window.scrollY - 110; | |
| window.scrollTo({ top: Math.max(0, top), behavior: "smooth" }); | |
| }, 120); | |
| } | |
| } | |
| } | |
| boot(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment