Links and GET forms to wiki pages swap the next page into .shell instead of loading a new document: the server answers boosted requests and history restores with a partial (title, page actions out-of-band, shell content). The header stays, browsers without cross-document view transitions (Firefox) no longer flash between pages, and the swap runs as a same-document view transition. Files, the editor, internal endpoints and POST forms still navigate normally. Back/Forward refetch pages (no snapshots that could predate a move or delete); the tree rail keeps its scroll position and the content column returns to where it was. Mutation redirects swap their result in the same way. Page scripts that assumed a fresh document now also run after a swap (toc, contents, companion, scroll-sync, search actions) or register once (fitness). The e2e suite now also runs in Firefox. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3.1 KiB
JavaScript
81 lines
3.1 KiB
JavaScript
// Fitness dashboard range dropdowns and "absolute" checkboxes: changing one
|
|
// reloads the page with that chart's query parameter updated. Plain GET
|
|
// navigation — each view is distinct and bookmarkable, so no
|
|
// postReplace/history handling is needed.
|
|
// Boosted navigation re-runs this script on every visit; the listeners
|
|
// are delegated to document, so register them once.
|
|
if (!window.fitnessWired) {
|
|
window.fitnessWired = true;
|
|
|
|
document.addEventListener('change', function (e) {
|
|
var url = new URL(window.location.href);
|
|
var sel = e.target.closest('[data-fitness-range]');
|
|
var abs = e.target.closest('[data-fitness-absolute]');
|
|
if (sel) {
|
|
url.searchParams.set(sel.dataset.fitnessRange, sel.value);
|
|
} else if (abs) {
|
|
if (abs.checked) url.searchParams.set(abs.dataset.fitnessAbsolute, '1');
|
|
else url.searchParams.delete(abs.dataset.fitnessAbsolute);
|
|
} else {
|
|
return;
|
|
}
|
|
window.location.href = url.toString();
|
|
});
|
|
|
|
// Hover tooltip: the pointer snaps to the data point nearest in time. Dots
|
|
// are sorted by x, so a binary search finds it; the tooltip sits above the
|
|
// point and is clamped to the chart panel.
|
|
function fitnessNearest(dots, x) {
|
|
var lo = 0, hi = dots.length - 1;
|
|
while (lo < hi) {
|
|
var mid = (lo + hi) >> 1;
|
|
if (dots[mid].cx.baseVal.value < x) lo = mid + 1; else hi = mid;
|
|
}
|
|
if (lo > 0 && x - dots[lo - 1].cx.baseVal.value < dots[lo].cx.baseVal.value - x) lo--;
|
|
return dots[lo];
|
|
}
|
|
|
|
function fitnessHide(svg) {
|
|
var chart = svg.closest('.fitness-chart');
|
|
svg.classList.remove('is-hover');
|
|
chart.querySelector('.fitness-tip').hidden = true;
|
|
}
|
|
|
|
document.addEventListener('pointermove', function (e) {
|
|
var svg = e.target.closest && e.target.closest('.fitness-svg');
|
|
document.querySelectorAll('.fitness-svg').forEach(function (other) {
|
|
if (other !== svg) fitnessHide(other);
|
|
});
|
|
if (!svg) return;
|
|
var dots = svg.querySelectorAll('.chart-dot');
|
|
if (!dots.length) return;
|
|
var ctm = svg.getScreenCTM();
|
|
if (!ctm) return;
|
|
var pt = new DOMPoint(e.clientX, e.clientY).matrixTransform(ctm.inverse());
|
|
var dot = fitnessNearest(dots, pt.x);
|
|
var cx = dot.cx.baseVal.value, cy = dot.cy.baseVal.value;
|
|
|
|
var cursor = svg.querySelector('.chart-cursor');
|
|
cursor.setAttribute('x1', cx);
|
|
cursor.setAttribute('x2', cx);
|
|
var focus = svg.querySelector('.chart-focus');
|
|
focus.setAttribute('cx', cx);
|
|
focus.setAttribute('cy', cy);
|
|
svg.classList.add('is-hover');
|
|
|
|
var chart = svg.closest('.fitness-chart');
|
|
var tip = chart.querySelector('.fitness-tip');
|
|
tip.firstElementChild.textContent = dot.dataset.date;
|
|
tip.lastElementChild.textContent = dot.dataset.value;
|
|
tip.hidden = false;
|
|
var box = chart.getBoundingClientRect();
|
|
var at = new DOMPoint(cx, cy).matrixTransform(ctm);
|
|
var left = at.x - box.left - tip.offsetWidth / 2;
|
|
left = Math.max(0, Math.min(left, box.width - tip.offsetWidth));
|
|
var top = at.y - box.top - tip.offsetHeight - 10;
|
|
if (top < 0) top = at.y - box.top + 10;
|
|
tip.style.left = left + 'px';
|
|
tip.style.top = top + 'px';
|
|
});
|
|
}
|