docs(ui): cookie-rendered sidebar state, data-collapsed CSS, working overview links + link-checker gate
Three problems, one root cause each: 1. Sidebar jump: the server rendered its own default (current section open, rest collapsed) and restoreDocsNav corrected it after paint — open-then-close. Fix: persistence moved server-side. toggleCollapsible now writes a docs_nav cookie (strictly-necessary UI state, no identifiers, SameSite=Lax); ApplyNavOverrides merges it into the nav before render, so first paint IS the visitor's state. The post-paint restore is deleted. 2. Arrows out of sync / double-click: two visibility mechanisms disagreed. There was NO data-collapsed CSS at all — collapsed sections were only ever hidden by JS after paint, and the arrow rotation came from a different (server) state. Now: style.css hides on [data-collapsed=true] (single source of truth, server-rendered), applyCollapsed sets only the attribute, arrows rotate from the same merged value. Live check: 37/37 collapsibles arrow-consistent. 3. api-reference.md links: all 20+ quick links were directory-style (authentication/ etc.) that 404 on the docs site, plus stale targets (books/, ../collections-api.md, websocket 'coming soon'). Rewritten to real per-file links — all 9 unique targets return 200 live. Same rot fixed in admin/hash-conflicts.md, development.md, android-app.md, create_media_item.md, koreader-protocol.md; docs/index.md redirected from the deleted collections-api monolith (stale 530-line duplicate of the gated api/collections docs — deleted, per the WO2 doctrine). New gate: TestDocsRelativeLinks walks docs/developer/** and fails make test on any relative link that is directory-style or unresolvable — overview links can never silently rot again.
This commit is contained in:
1 parent
f1a6de7cd7
commit
a8f8c4def9
17 files changed
+241
-674
No files matched your search
+21
-39
@@ -16,39 +16,38 @@ function toggleSidebar(): void {
|
||||
}
|
||||
|
||||
// Docs sidebar collapse persistence. Rendering ownership: the server
|
||||
// renders what is expanded on FIRST paint (navigation.go derives it from
|
||||
// the current path); after that, open/closed is client-local state and
|
||||
// survives navigation via localStorage. Keys are the data-key attributes
|
||||
// rendered by DocsSidebar ("Section" / "Section / Group").
|
||||
// renders what is expanded (navigation defaults + docs_nav cookie
|
||||
// overrides merged in internal/docs), so first paint is already correct —
|
||||
// no post-paint correction, no jumping. This module only records intent:
|
||||
// every toggle rewrites the cookie with the full current state.
|
||||
|
||||
const DOCS_NAV_STORAGE_KEY = "docs-nav-state";
|
||||
const DOCS_NAV_COOKIE = "docs_nav";
|
||||
|
||||
type DocsNavState = Record<string, boolean>;
|
||||
|
||||
function readDocsNavState(): DocsNavState {
|
||||
try {
|
||||
const raw = localStorage.getItem(DOCS_NAV_STORAGE_KEY);
|
||||
if (!raw) return {};
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as DocsNavState;
|
||||
}
|
||||
} catch {
|
||||
// Corrupt state falls back to the server-rendered defaults.
|
||||
}
|
||||
return {};
|
||||
// collectDocsNavState reads the current collapsed flag of every
|
||||
// collapsible in the sidebar straight from the DOM.
|
||||
function collectDocsNavState(): DocsNavState {
|
||||
const state: DocsNavState = {};
|
||||
document.querySelectorAll<HTMLElement>("[data-key][data-collapsed]").forEach((el) => {
|
||||
const key = el.getAttribute("data-key");
|
||||
if (key) state[key] = el.getAttribute("data-collapsed") === "true";
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
function writeDocsNavState(state: DocsNavState): void {
|
||||
function persistDocsNavCookie(state: DocsNavState): void {
|
||||
try {
|
||||
localStorage.setItem(DOCS_NAV_STORAGE_KEY, JSON.stringify(state));
|
||||
// Strictly-necessary UI state: first-party, no identifiers, one year.
|
||||
document.cookie = `${DOCS_NAV_COOKIE}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=31536000; SameSite=Lax`;
|
||||
} catch {
|
||||
// Storage full/blocked: persistence is best-effort.
|
||||
// Storage blocked: the sidebar still works, state just doesn't persist.
|
||||
}
|
||||
}
|
||||
|
||||
function applyCollapsed(items: HTMLElement, collapsed: boolean): void {
|
||||
items.classList.toggle("hidden", collapsed);
|
||||
// The data-collapsed attribute is the single source of truth — the
|
||||
// stylesheet hides on it, and the arrow is rotated relative to it.
|
||||
items.setAttribute("data-collapsed", collapsed ? "true" : "false");
|
||||
const arrow = items.previousElementSibling?.querySelector<HTMLElement>(".section-arrow");
|
||||
if (arrow) {
|
||||
@@ -56,16 +55,6 @@ function applyCollapsed(items: HTMLElement, collapsed: boolean): void {
|
||||
}
|
||||
}
|
||||
|
||||
function restoreDocsNav(): void {
|
||||
const state = readDocsNavState();
|
||||
document.querySelectorAll<HTMLElement>("[data-key][data-collapsed]").forEach((items) => {
|
||||
const key = items.getAttribute("data-key");
|
||||
if (key && key in state) {
|
||||
applyCollapsed(items, state[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleCollapsible(header: HTMLElement): void {
|
||||
const items = header.nextElementSibling as HTMLElement | null;
|
||||
if (!items) return;
|
||||
@@ -73,13 +62,7 @@ function toggleCollapsible(header: HTMLElement): void {
|
||||
const isCollapsed = items.getAttribute("data-collapsed") === "true";
|
||||
|
||||
applyCollapsed(items, !isCollapsed);
|
||||
|
||||
const key = items.getAttribute("data-key");
|
||||
if (key) {
|
||||
const state = readDocsNavState();
|
||||
state[key] = !isCollapsed;
|
||||
writeDocsNavState(state);
|
||||
}
|
||||
persistDocsNavCookie(collectDocsNavState());
|
||||
}
|
||||
|
||||
let docsSearchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -169,7 +152,6 @@ function initializeCodeCopyButtons(): void {
|
||||
Alpine.data("docs", () => ({
|
||||
toggleSidebar,
|
||||
toggleCollapsible,
|
||||
restoreDocsNav,
|
||||
initializeSearch: initializeDocsSearch,
|
||||
initializeCodeCopyButtons,
|
||||
}));
|
||||
Reference in new issue
Block a user