(function () { const notice = document.querySelector("[data-version-notice]"); const reload = document.querySelector("[data-version-reload]"); const offlineNotice = document.querySelector("[data-offline-notice]"); const documentShell = document.querySelector("[data-document-path][data-document-hash]"); const browser = document.querySelector(".miller-browser"); // Auto-scroll miller browser to show the rightmost (active) column function scrollBrowserToRight() { if (!browser) return; requestAnimationFrame(function () { // Only scroll if the rightmost column is not already visible var tolerance = 50; var maxScroll = browser.scrollWidth - browser.clientWidth; if (maxScroll <= 0) return; // everything fits, no scroll needed if (browser.scrollLeft >= maxScroll - tolerance) return; // already at right edge browser.scrollTo({ left: browser.scrollWidth, behavior: "smooth", }); }); } scrollBrowserToRight(); function updateOfflineUI() { if (!offlineNotice) return; if (navigator.onLine) { offlineNotice.hidden = true; } else { offlineNotice.hidden = false; } } window.addEventListener("online", function () { updateOfflineUI(); fetchAndCacheDocuments(); }); window.addEventListener("offline", function () { updateOfflineUI(); restoreBrowserFromCache(); }); updateOfflineUI(); // Cache current page content if (documentShell && window.MDHubCache) { const path = documentShell.getAttribute("data-document-path"); const title = document.title; const html = documentShell.querySelector(".markdown-body")?.innerHTML; if (path && html) { window.MDHubCache.cacheContent(path, html, title).catch(function () {}); } } // Fetch and cache documents function fetchAndCacheDocuments() { if (!window.MDHubCache) return; fetch("/api/documents") .then(function (res) { if (!res.ok) throw new Error("fetch failed"); return res.json(); }) .then(function (data) { if (data.documents) { window.MDHubCache.cacheDocuments(data.documents).catch(function () {}); } }) .catch(function () {}); } fetchAndCacheDocuments(); // Restore browser from cache when offline function restoreBrowserFromCache() { if (!browser || !window.MDHubCache) return; window.MDHubCache.getCachedDocuments().then(function (docs) { if (!docs || docs.length === 0) return; // The browser is already rendered server-side; we just keep it. // If we wanted to rebuild it from cache, we'd need the full logic. // For now, the existing browser HTML is likely cached by the browser itself. }).catch(function () {}); } // Intercept browser navigation when offline if (browser) { browser.addEventListener("click", function (event) { const link = event.target.closest("a"); if (!link) return; if (navigator.onLine) return; const href = link.getAttribute("href"); if (!href || href.startsWith("http") || href.startsWith("//")) return; // Try to serve from cache for document pages if (href.startsWith("/docs/")) { event.preventDefault(); const path = href.replace("/docs/", ""); window.MDHubCache.getCachedContent(path).then(function (cached) { if (cached && cached.html) { loadCachedDocument(path, cached.title, cached.html); } else { window.location.href = href; } }).catch(function () { window.location.href = href; }); } }); } function loadCachedDocument(path, title, html) { if (!documentShell) { window.location.href = "/docs/" + path; return; } // Update URL without reloading history.pushState({ cachedPath: path }, title, "/docs/" + path); document.title = title; // Update document shell documentShell.setAttribute("data-document-path", path); documentShell.setAttribute("data-document-hash", ""); const titleEl = documentShell.querySelector(".document-meta h1"); if (titleEl) titleEl.textContent = title; const pathEl = documentShell.querySelector(".meta-grid code"); if (pathEl) pathEl.textContent = path; const bodyEl = documentShell.querySelector(".markdown-body"); if (bodyEl) bodyEl.innerHTML = html; // Re-render math and mermaid if (typeof renderMath === "function") renderMath(); if (typeof renderMermaid === "function") renderMermaid(); // Update active state in browser updateBrowserActiveState(path); } function updateBrowserActiveState(activePath) { if (!browser) return; browser.querySelectorAll("a.is-active").forEach(function (el) { el.classList.remove("is-active"); }); browser.querySelectorAll("a").forEach(function (link) { const href = link.getAttribute("href"); if (href === "/docs/" + activePath || href === "/?folder=" + activePath) { link.classList.add("is-active"); } }); } // WebSocket realtime updates if (!window.WebSocket) { return; } const currentPath = documentShell ? documentShell.getAttribute("data-document-path") : null; const currentHash = documentShell ? documentShell.getAttribute("data-document-hash") : null; const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; let reconnectTimer = null; function connect() { const socket = new WebSocket(protocol + "//" + window.location.host + "/ws"); socket.addEventListener("open", function () { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } }); socket.addEventListener("message", function (event) { let payload; try { payload = JSON.parse(event.data); } catch { return; } if (payload.type !== "document_version" || !payload.data) { return; } const change = payload.data; if (currentPath && change.path === currentPath && change.hash !== currentHash) { if (notice) notice.hidden = false; return; } if (browser && !documentShell) { if (notice) notice.hidden = false; } }); socket.addEventListener("close", function () { reconnectTimer = setTimeout(connect, 3000); }); socket.addEventListener("error", function () { socket.close(); }); } connect(); if (reload) { reload.addEventListener("click", function () { window.location.reload(); }); } // Handle back/forward buttons for cached documents window.addEventListener("popstate", function (event) { if (event.state && event.state.cachedPath && window.MDHubCache) { window.MDHubCache.getCachedContent(event.state.cachedPath).then(function (cached) { if (cached && cached.html) { loadCachedDocument(event.state.cachedPath, cached.title, cached.html); } }).catch(function () {}); } }); })();