Files
ai-agent-book/extras/nav-collapse.js
T
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

177 lines
6.9 KiB
JavaScript

/**
* Desktop navigation controls for Material's primary sidebar.
*
* Background: with `navigation.sections` + `navigation.indexes`, each
* chapter renders as
*
* <li class="md-nav__item--section md-nav__item--nested">
* <input class="md-toggle" id="__nav_N">
* <div class="md-nav__link md-nav__container">
* <a href="…/chapterN/">chapter title</a> ← navigates
* <label for="__nav_N">chevron</label> ← toggles
* </div>
* <nav class="md-nav">…配套实验…</nav>
* </li>
*
* Material only honours the checkbox on mobile; on desktop the section
* subtree is always visible and the chevron is hidden. We inject CSS so
* that on desktop, too, the subtree follows the checkbox and the chevron
* is visible/clickable. Material checks the active chapter's checkbox at
* render time, so the default state (active chapter open, rest closed)
* comes for free.
*
* One case needs JS: on pages Material doesn't consider "active" — the
* translated editions (sidebar links are rewritten client-side by
* lang-switcher.js) and the per-experiment pages, which aren't in the
* nav — no checkbox is checked. There we match the current URL against
* each section's chapter number and open the matching section.
*
* The header also contains a desktop-only button for hiding the entire
* primary sidebar. Its state is persisted locally and restored across full
* reloads as well as Material's instant page swaps.
*
* Re-runs on every Material page swap (navigation.instant) via document$.
*/
(function () {
"use strict";
var SIDEBAR_STORAGE_KEY = "ai-agent-book.primary-sidebar-collapsed";
function ensureStyle() {
if (document.getElementById("nav-collapse-style")) return;
var s = document.createElement("style");
s.id = "nav-collapse-style";
s.textContent = [
"@media screen and (min-width: 76.25em) {",
// Collapse the subtree when the checkbox is unchecked (Material
// keeps section subtrees always-visible on desktop by default).
" .md-sidebar--primary .md-nav__item--nested > .md-toggle:not(:checked) ~ .md-nav {",
" display: none !important;",
" }",
// Material hides the section chevron on desktop; bring it back and
// make it clickable.
" .md-sidebar--primary .md-nav__item--nested > .md-nav__container > label.md-nav__link {",
" display: flex;",
" align-items: center;",
" cursor: pointer;",
" pointer-events: auto !important;",
" margin: 0;",
// 4px top padding centres the 1.2rem icon on the title's FIRST line
// (6px link padding + ~1.3 line-height): the container aligns
// flex-start (book-theme.css) so wrapped two-line titles don't pull
// the chevron down between the lines.
" padding: 4px 0.2rem 0 0.4rem;",
" }",
// Material's own stylesheet already rotates the chevron's ::after
// by 90° when the checkbox is checked — no extra transform here.
" .md-sidebar--primary .md-nav__item--nested > .md-nav__container > label.md-nav__link .md-nav__icon {",
" display: block;",
" }",
"}",
].join("\n");
document.head.appendChild(s);
}
function applyDefaultState() {
var sidebar = document.querySelector(".md-sidebar--primary");
if (!sidebar) return;
// Material already checked a section's checkbox? Then its render-time
// active detection worked — nothing to fix up.
if (sidebar.querySelector(".md-nav__item--nested > .md-toggle:checked")) return;
// Otherwise (translated edition or a page outside the nav), derive the
// chapter from the URL and open the matching section.
var m = location.pathname.match(/chapter(\d+)/);
if (!m) return;
var wanted = m[1];
var sections = sidebar.querySelectorAll(".md-nav__item--nested");
for (var i = 0; i < sections.length; i++) {
var link = sections[i].querySelector(":scope > .md-nav__container > a.md-nav__link");
var checkbox = sections[i].querySelector(":scope > .md-toggle");
if (!link || !checkbox) continue;
var lm = (link.getAttribute("href") || "").match(/chapter(\d+)/);
if (lm && lm[1] === wanted) {
checkbox.checked = true;
break;
}
}
}
function readSidebarPreference() {
try {
var value = window.localStorage.getItem(SIDEBAR_STORAGE_KEY);
return value === null ? null : value === "true";
} catch (_) {
// Storage may be disabled by the browser. The control still works for
// the current page, and the root class survives Material page swaps.
return null;
}
}
function writeSidebarPreference(collapsed) {
try {
window.localStorage.setItem(SIDEBAR_STORAGE_KEY, String(collapsed));
} catch (_) {
// A blocked localStorage must not prevent readers from using the
// collapse control for the current page.
}
}
function setSidebarCollapsed(collapsed, remember) {
document.documentElement.classList.toggle("sidebar-nav-collapsed", collapsed);
var button = document.querySelector("[data-sidebar-toggle]");
if (button) {
var catalog = window.SITE_I18N;
var code = window.siteCurrentLanguage || (catalog && catalog.default) || "zh";
var strings = catalog && catalog.languages && catalog.languages[code];
var label = strings && strings.sidebar
? strings.sidebar[collapsed ? "show" : "hide"]
: (collapsed ? "展开侧边栏" : "隐藏侧边栏");
button.setAttribute("aria-expanded", String(!collapsed));
button.setAttribute("aria-label", label);
button.setAttribute("title", label);
}
if (remember) writeSidebarPreference(collapsed);
}
function initSidebarToggle() {
var button = document.querySelector("[data-sidebar-toggle]");
var sidebar = document.querySelector(".md-sidebar--primary");
if (!button || !sidebar || sidebar.hidden) {
if (button) button.hidden = true;
return;
}
// Connect aria-controls to the sidebar generated by Material. The id is
// re-applied because navigation.instant replaces page content in place.
sidebar.id = "primary-navigation";
button.hidden = false;
var preference = readSidebarPreference();
var collapsed = preference === null
? document.documentElement.classList.contains("sidebar-nav-collapsed")
: preference;
setSidebarCollapsed(collapsed, false);
if (button.getAttribute("data-sidebar-toggle-bound") !== "true") {
button.setAttribute("data-sidebar-toggle-bound", "true");
button.addEventListener("click", function () {
var next = !document.documentElement.classList.contains("sidebar-nav-collapsed");
setSidebarCollapsed(next, true);
});
}
}
function init() {
ensureStyle();
applyDefaultState();
initSidebarToggle();
}
document.addEventListener("DOMContentLoaded", init);
if (window.document$) window.document$.subscribe(init);
})();