c46f9a48f7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
4.2 KiB
JavaScript
102 lines
4.2 KiB
JavaScript
/* pjax — client-side navigation. swaps the page content instead of a full
|
|
reload, so the cursor, shell and theme stay alive (no flashing, no re-fetch
|
|
of css/js). pure progressive enhancement: if fetch/history/DOMParser are
|
|
missing, or anything goes wrong, links fall back to normal navigation. */
|
|
(function () {
|
|
"use strict";
|
|
if (!window.history || !window.fetch || !window.DOMParser) return;
|
|
var MAIN = "#main-content";
|
|
if (!document.querySelector(MAIN)) return;
|
|
|
|
function internal(a) {
|
|
return a && a.href && a.origin === location.origin &&
|
|
a.getAttribute("href").charAt(0) !== "#" &&
|
|
!a.hasAttribute("download") &&
|
|
(a.getAttribute("target") || "") !== "_blank";
|
|
}
|
|
|
|
function swap(html, url) {
|
|
var doc = new DOMParser().parseFromString(html, "text/html");
|
|
var nm = doc.querySelector(MAIN), om = document.querySelector(MAIN);
|
|
if (!nm || !om) { location.href = url; return; }
|
|
|
|
om.innerHTML = nm.innerHTML;
|
|
if (doc.title) document.title = doc.title;
|
|
document.body.className = doc.body.className;
|
|
|
|
// breadcrumb (in the persistent header) — swap its inner links
|
|
var np = doc.querySelector(".prompt__path"), op = document.querySelector(".prompt__path");
|
|
if (np && op) op.innerHTML = np.innerHTML;
|
|
|
|
// per-section typeable routes live in .topbar — replace them
|
|
var topbar = document.querySelector(".topbar");
|
|
|
|
// language switch (persistent header) — repoint it at THIS page's
|
|
// translation, otherwise it keeps the target of the first-loaded page
|
|
var nl = doc.querySelector(".lang-switch"), ol = document.querySelector(".lang-switch");
|
|
if (ol && ol.parentNode) ol.parentNode.removeChild(ol);
|
|
if (nl && topbar) topbar.insertBefore(document.importNode(nl, true), topbar.firstChild);
|
|
var oldR = document.querySelector(".routes");
|
|
if (oldR && oldR.parentNode) oldR.parentNode.removeChild(oldR);
|
|
var newR = doc.querySelector(".routes");
|
|
if (newR && topbar) topbar.appendChild(document.importNode(newR, true));
|
|
|
|
window.scrollTo(0, 0);
|
|
document.dispatchEvent(new CustomEvent("pjax:load"));
|
|
}
|
|
|
|
// prefetch cache: hovering a link fetches its HTML so the click is instant
|
|
var cache = {};
|
|
function fetchHTML(url) {
|
|
return fetch(url, { credentials: "same-origin" })
|
|
.then(function (r) { if (!r.ok) throw new Error(r.status); return r.text(); });
|
|
}
|
|
function prefetch(url) {
|
|
if (!cache[url]) cache[url] = fetchHTML(url).catch(function () { delete cache[url]; return null; });
|
|
}
|
|
|
|
// which language a URL belongs to (de lives under /de/, en is the default)
|
|
function langOf(u) {
|
|
var p;
|
|
try { p = new URL(u, location.origin).pathname; } catch (e) { p = u; }
|
|
return /^\/de(\/|$)/.test(p) ? "de" : "en";
|
|
}
|
|
|
|
var busy = false;
|
|
function go(url, push) {
|
|
if (busy) return;
|
|
// a language change rebuilds the whole chrome (menu, html lang, switch),
|
|
// so do a real reload instead of a partial content swap
|
|
if (langOf(url) !== (document.documentElement.lang || "en")) { location.href = url; return; }
|
|
busy = true;
|
|
(cache[url] || fetchHTML(url))
|
|
.then(function (html) {
|
|
busy = false;
|
|
if (!html) { location.href = url; return; } // prefetch had failed
|
|
if (push) history.pushState({ pjax: true }, "", url);
|
|
swap(html, url);
|
|
})
|
|
.catch(function () { busy = false; location.href = url; });
|
|
}
|
|
|
|
document.addEventListener("mouseover", function (e) {
|
|
var a = e.target.closest ? e.target.closest("a[href]") : null;
|
|
if (internal(a) && a.href.split("#")[0] !== location.href.split("#")[0]) prefetch(a.href);
|
|
}, { passive: true });
|
|
|
|
document.addEventListener("click", function (e) {
|
|
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
var a = e.target.closest ? e.target.closest("a[href]") : null;
|
|
if (!internal(a)) return;
|
|
if (a.href.split("#")[0] === location.href.split("#")[0]) return; // same document
|
|
e.preventDefault();
|
|
go(a.href, true);
|
|
});
|
|
|
|
window.addEventListener("popstate", function () { go(location.href, false); });
|
|
history.replaceState({ pjax: true }, "", location.href);
|
|
|
|
// bridge for programmatic navigation (shell commands, filebrowser Enter)
|
|
window.pjax = function (url) { go(url, true); };
|
|
})();
|