fix(lightbox): slideshow navigation writes browser history
goToSlide() was replaceState()-ing, so paging through the viewer left a single history entry — browser Back skipped every plate you'd looked at. It now pushState()s on each user-driven slide (guarded to fire only when the index actually changes), popstate slides the viewer back/forward without stacking entries, and the first entry on a directly-loaded plate page is tagged with its slug so Back to it is handled in-place. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DZPmxGywFnAhmYJB1eh9fm
This commit is contained in:
+18
-7
@@ -289,9 +289,14 @@
|
|||||||
preloadNeighbors(lbIdx);
|
preloadNeighbors(lbIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToSlide(idx, smooth = true) {
|
// smooth — animate the track (false = jump, used when first opening)
|
||||||
|
// record — push a history entry + sync <title>/meta (default follows `smooth`;
|
||||||
|
// popstate passes false so back/forward doesn't stack entries)
|
||||||
|
function goToSlide(idx, smooth = true, record = smooth) {
|
||||||
if (!lbTrack) return;
|
if (!lbTrack) return;
|
||||||
lbIdx = Math.max(0, Math.min(idx, lbList.length - 1));
|
const next = Math.max(0, Math.min(idx, lbList.length - 1));
|
||||||
|
const changed = next !== lbIdx;
|
||||||
|
lbIdx = next;
|
||||||
lbTrack.style.transition = smooth ? '' : 'none';
|
lbTrack.style.transition = smooth ? '' : 'none';
|
||||||
lbTrack.style.transform = `translateX(-${lbIdx * 100}%)`;
|
lbTrack.style.transform = `translateX(-${lbIdx * 100}%)`;
|
||||||
if (!smooth) lbTrack.getBoundingClientRect(); // force reflow
|
if (!smooth) lbTrack.getBoundingClientRect(); // force reflow
|
||||||
@@ -302,17 +307,17 @@
|
|||||||
lbBuildMeta(p);
|
lbBuildMeta(p);
|
||||||
syncThumbs();
|
syncThumbs();
|
||||||
preloadNeighbors(lbIdx);
|
preloadNeighbors(lbIdx);
|
||||||
if (p && smooth) {
|
if (p) {
|
||||||
const postTitle = p.title + ' — Roux';
|
const postTitle = p.title + ' — Roux';
|
||||||
const postUrl = new URL(p.url, location.origin).href;
|
const postUrl = new URL(p.url, location.origin).href;
|
||||||
document.title = postTitle;
|
document.title = postTitle;
|
||||||
history.replaceState({ slug: p.slug }, '', p.url);
|
|
||||||
setMeta('name', 'description', p.description || null);
|
setMeta('name', 'description', p.description || null);
|
||||||
setMeta('property', 'og:title', postTitle);
|
setMeta('property', 'og:title', postTitle);
|
||||||
setMeta('property', 'og:description', p.description || null);
|
setMeta('property', 'og:description', p.description || null);
|
||||||
setMeta('property', 'og:url', postUrl);
|
setMeta('property', 'og:url', postUrl);
|
||||||
const canon = document.querySelector('link[rel="canonical"]');
|
const canon = document.querySelector('link[rel="canonical"]');
|
||||||
if (canon) canon.href = postUrl;
|
if (canon) canon.href = postUrl;
|
||||||
|
if (record && changed) history.pushState({ slug: p.slug }, '', p.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,15 +445,19 @@
|
|||||||
// Handle browser back/forward.
|
// Handle browser back/forward.
|
||||||
window.addEventListener('popstate', e => {
|
window.addEventListener('popstate', e => {
|
||||||
const slug = e.state && e.state.slug;
|
const slug = e.state && e.state.slug;
|
||||||
// Entry points at a plate we already know about → show it in the lightbox now,
|
|
||||||
// no page fetch needed (covers sliding + card-opened plates).
|
|
||||||
if (slug && POSTS.some(p => p.slug === slug)) {
|
if (slug && POSTS.some(p => p.slug === slug)) {
|
||||||
|
// Already in the viewer with this plate in the list → just slide to it.
|
||||||
|
if (lb && lb.dataset.open === 'true') {
|
||||||
|
const i = lbList.findIndex(p => p.slug === slug);
|
||||||
|
if (i !== -1) { goToSlide(i, true, false); return; }
|
||||||
|
}
|
||||||
|
// Otherwise (re)open the viewer at this plate — no page fetch needed.
|
||||||
const opened = POSTS.find(p => p.slug === slug);
|
const opened = POSTS.find(p => p.slug === slug);
|
||||||
const scoped = POSTS.filter(p => p.issue === opened.issue);
|
const scoped = POSTS.filter(p => p.issue === opened.issue);
|
||||||
lbOpen(slug, scoped.length ? scoped : POSTS);
|
lbOpen(slug, scoped.length ? scoped : POSTS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Otherwise re-render #content for the URL we landed on (also closes the lightbox).
|
// Not a plate → re-render #content for the URL we landed on (also closes the viewer).
|
||||||
navigate(location.href, { push: false });
|
navigate(location.href, { push: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -562,6 +571,8 @@
|
|||||||
const opened = POSTS.find(p => p.slug === window.__ROUX_OPEN_SLUG);
|
const opened = POSTS.find(p => p.slug === window.__ROUX_OPEN_SLUG);
|
||||||
const issueId = opened ? opened.issue : null;
|
const issueId = opened ? opened.issue : null;
|
||||||
const scoped = issueId ? POSTS.filter(p => p.issue === issueId) : POSTS;
|
const scoped = issueId ? POSTS.filter(p => p.issue === issueId) : POSTS;
|
||||||
|
// Tag this first history entry so back-navigation to it is handled by the viewer.
|
||||||
|
if (opened) history.replaceState({ slug: opened.slug }, '', location.href);
|
||||||
lbOpen(window.__ROUX_OPEN_SLUG, scoped.length ? scoped : POSTS);
|
lbOpen(window.__ROUX_OPEN_SLUG, scoped.length ? scoped : POSTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user