preserve scroll position when entering page editor

This commit is contained in:
2026-07-24 15:24:40 +02:00
parent 3abcc94eca
commit e37dde1e40
2 changed files with 62 additions and 0 deletions
+1
View File
@@ -11,6 +11,7 @@
<script src="/_/modal.js"></script>
<script src="/_/global-shortcuts.js"></script>
<script src="/_/history-nav.js"></script>
<script src="/_/scroll-sync.js" defer></script>
<script src="/_/search-suggest.js" defer></script>
<script src="/_/tree-picker.js"></script>
<script src="/_/companion.js" defer></script>
+61
View File
@@ -0,0 +1,61 @@
// Roughly preserve reading position when entering the editor.
//
// While reading a page we record how far the center column is scrolled (as a
// fraction of its scrollable height, keyed by path). When the same path opens
// in the editor we restore that fraction, so you land near where you were
// reading instead of at the top. It is fraction-based — the rendered page and
// the raw-markdown editor have different heights — so the match is deliberately
// approximate. This only moves the viewport; the caret default (end of doc) is
// unchanged, so a click still decides where you actually edit.
(function () {
var center = document.querySelector('.center');
if (!center) return;
var key = 'scrollsync:' + location.pathname;
// Only restore a freshly-recorded position: a stale entry from earlier in
// the tab session would jump a direct ?edit open to the wrong place.
var MAX_AGE_MS = 5 * 60 * 1000;
var params = new URLSearchParams(location.search);
var inEditor = params.has('edit');
// Section / insert edits load only a slice of the document into the editor,
// so a whole-page reading fraction does not map onto them — restore only for
// a full-page ?edit.
var fullPageEdit = inEditor && !params.has('section') && !params.has('insert_before');
function maxScroll() { return Math.max(0, center.scrollHeight - center.clientHeight); }
if (inEditor) {
if (!fullPageEdit) return;
var raw = sessionStorage.getItem(key);
if (!raw) return;
var data;
try { data = JSON.parse(raw); } catch (e) { return; }
if (!data || typeof data.f !== 'number' || Date.now() - (data.t || 0) > MAX_AGE_MS) return;
// Wait for CodeMirror to mount and the column to reach full height, then
// map the fraction onto it. A double rAF after load lets CM's initial
// layout and focus scroll settle so ours wins.
window.addEventListener('load', function () {
requestAnimationFrame(function () {
requestAnimationFrame(function () {
center.scrollTop = data.f * maxScroll();
});
});
});
return;
}
// Reading mode: keep the latest scroll fraction recorded (throttled to one
// write per frame) so it is ready the moment the editor opens.
var pending = false;
center.addEventListener('scroll', function () {
if (pending) return;
pending = true;
requestAnimationFrame(function () {
pending = false;
var m = maxScroll();
try {
sessionStorage.setItem(key, JSON.stringify({ f: m > 0 ? center.scrollTop / m : 0, t: Date.now() }));
} catch (e) {}
});
}, { passive: true });
})();