bug: Fix page scroll detection and add browser history support
- Use getBoundingClientRect() in the scroll listener so the active-page threshold is measured relative to the content container rather than the document body, fixing off-by-one page detection. - Replace history.replaceState with pushState for user-initiated navigation (sidebar, prev/next, keyboard) and add a popstate listener so the browser back/forward buttons work correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
34
build.py
34
build.py
@@ -20,7 +20,7 @@ def build_index(title, DIR):
|
||||
sections = [(n, read_page(n)) for n in page_nums]
|
||||
|
||||
sidebar_items = '\n '.join(
|
||||
f'<li data-page="{n}"><button onclick="goTo({n})">'
|
||||
f'<li data-page="{n}"><button onclick="goTo({n},false,true)">'
|
||||
f'<span class="page-num">{n}</span><span>Page {n}</span></button></li>'
|
||||
for n in page_nums
|
||||
)
|
||||
@@ -250,7 +250,7 @@ def build_index(title, DIR):
|
||||
const indicator = document.getElementById('page-indicator');
|
||||
const titleEl = document.getElementById('page-title');
|
||||
|
||||
function updateNav(idx) {{
|
||||
function updateNav(idx, push) {{
|
||||
if (idx === currentIdx && indicator.textContent) return;
|
||||
currentIdx = idx;
|
||||
const n = PAGES[idx];
|
||||
@@ -263,28 +263,29 @@ def build_index(title, DIR):
|
||||
}});
|
||||
document.querySelector('#page-list li[data-page="' + n + '"]')
|
||||
?.scrollIntoView({{ block: 'nearest' }});
|
||||
history.replaceState(null, '', '#page-' + n);
|
||||
if (push) history.pushState(null, '', '#page-' + n);
|
||||
else history.replaceState(null, '', '#page-' + n);
|
||||
}}
|
||||
|
||||
function goTo(n, smooth) {{
|
||||
function goTo(n, smooth, push) {{
|
||||
const idx = PAGES.indexOf(n);
|
||||
if (idx === -1) return;
|
||||
const sec = document.getElementById('page-' + n);
|
||||
if (!sec) return;
|
||||
sec.scrollIntoView({{ behavior: smooth ? 'smooth' : 'instant', block: 'start' }});
|
||||
updateNav(idx);
|
||||
updateNav(idx, push);
|
||||
}}
|
||||
|
||||
btnPrev.addEventListener('click', () => {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); }});
|
||||
btnNext.addEventListener('click', () => {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); }});
|
||||
btnPrev.addEventListener('click', () => {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }});
|
||||
btnNext.addEventListener('click', () => {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }});
|
||||
|
||||
document.addEventListener('keydown', e => {{
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); }}
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); }}
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }}
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }}
|
||||
}});
|
||||
|
||||
// Track current page by scroll position
|
||||
// Track current page by scroll position (replaceState — no new history entry)
|
||||
content.addEventListener('scroll', () => {{
|
||||
const containerTop = content.getBoundingClientRect().top;
|
||||
let found = 0;
|
||||
@@ -293,13 +294,22 @@ def build_index(title, DIR):
|
||||
if (sec && sec.getBoundingClientRect().top - containerTop <= 40) found = i;
|
||||
else break;
|
||||
}}
|
||||
if (found !== currentIdx) updateNav(found);
|
||||
if (found !== currentIdx) updateNav(found, false);
|
||||
}}, {{ passive: true }});
|
||||
|
||||
// Browser back/forward
|
||||
window.addEventListener('popstate', () => {{
|
||||
const pm = location.hash.match(/^#page-(\\d+)$/);
|
||||
if (pm) {{
|
||||
const n = parseInt(pm[1], 10);
|
||||
if (PAGES.includes(n)) goTo(n, false, false);
|
||||
}}
|
||||
}});
|
||||
|
||||
// Initial navigation from URL hash
|
||||
const m = location.hash.match(/^#page-(\\d+)$/);
|
||||
const startPage = m ? parseInt(m[1], 10) : PAGES[0];
|
||||
goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false);
|
||||
goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false, false);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user