From da30bb8fc3ddf8a42797f59ac2ac93c38ace9abd Mon Sep 17 00:00:00 2001 From: luxick Date: Fri, 24 Jul 2026 10:55:29 +0200 Subject: [PATCH] Fix back-to-cancel behavior of editor --- CLAUDE.md | 21 +++++++++++++-------- assets/global-shortcuts.js | 5 +++-- assets/history-nav.js | 28 ++++++++++++++++------------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6aa129a..3c93377 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,14 +75,19 @@ Prefer separate, human-readable `.html` files over inlined HTML strings in Go. E - Do not inline JS in templates or merge unrelated features into one file - `ALT+SHIFT` is the modifier for all keyboard shortcuts — do not introduce others - Editor toolbar buttons use `data-action` + `data-key`; adding `data-key` auto-registers the shortcut -- The editor is a *mode* of a page, not a destination. `history-nav.js` turns any - same-path `?edit` link (and the editor's CANCEL link back out) into - `location.replace`, and SAVE POSTs via fetch and then rewrites the entry with - the saved page. Net effect: an edit session never occupies a history entry of - its own. Links to a *different* page's editor (new page / new child) still push. - The save POST answers `204` + `X-Target` when the request carries - `X-Save-Mode: replace`, because the target may hold a `#section` anchor only - the server can compute and fetch drops fragments from followed redirects. +- The editor is a *mode* of a page. Opening it pushes a normal history entry so + the browser/Android Back button cancels the edit and returns to the page — this + "back to cancel" gesture takes priority. `history-nav.js` only rewrites the way + *out*: leaving the editor via the same-page CANCEL link uses `location.replace` + so the editor entry is collapsed rather than sandwiched between two page entries + (which would let Back walk back into the editor). SAVE does the equivalent — it + POSTs via fetch and then `replaceState`s the editor entry with the saved page, + so the editor never lingers in history once you leave it (the pre-save page + snapshot one step back is the accepted cost). Links to a *different* page's + editor (new page / new child) push normally. The save POST answers `204` + + `X-Target` when the request carries `X-Save-Mode: replace`, because the target + may hold a `#section` anchor only the server can compute and fetch drops + fragments from followed redirects. - For mutating modals (anything that POSTs and then navigates), call `closeModal()` and then `postReplace(action, body, target)` from `page/actions.js`. Do NOT use `
.submit()`. Two reasons: 1. The modal must be removed from the DOM before navigation, or the browser's bfcache snapshots it open and back-nav restores the modal. 2. `postReplace` uses `window.location.replace` so the action + result occupy a single history entry. A naive POST → 303 → GET creates two entries, and back-nav lands on a stale pre-mutation snapshot of the same page. diff --git a/assets/global-shortcuts.js b/assets/global-shortcuts.js index c5f4c4e..d2518a6 100644 --- a/assets/global-shortcuts.js +++ b/assets/global-shortcuts.js @@ -4,8 +4,9 @@ switch (e.key) { case 'E': e.preventDefault(); - // replace, not assign — same reasoning as history-nav.js. - window.location.replace(window.location.pathname + '?edit'); + // assign, not replace — opening the editor pushes a history + // entry so Back cancels the edit (see history-nav.js). + window.location.href = window.location.pathname + '?edit'; break; case 'N': e.preventDefault(); diff --git a/assets/history-nav.js b/assets/history-nav.js index 46aaf7e..4b87484 100644 --- a/assets/history-nav.js +++ b/assets/history-nav.js @@ -1,14 +1,14 @@ -// Keeps the page editor out of the browser history. +// Keeps the editor from lingering in history once you leave it — while still +// letting the browser/Android Back button CANCEL an edit session. // -// Opening the editor for the page you are already on is a mode switch, not a -// new destination, so it replaces the current history entry instead of pushing -// one; CANCEL replaces it right back, and SAVE does the same via postSave in -// editor/main.js. Without this, page -> edit -> save leaves -// [prev, page, editor, page'] behind and Back walks through the editor and a -// stale pre-save snapshot of the page before reaching prev. +// Opening the editor pushes a normal history entry, so Back exits the editor +// and returns to the page (the primary "back to cancel" gesture on mobile). // -// Links to a *different* page's editor (new page, new child) still push — the -// page you started from has to stay in history. +// Leaving the editor by CANCEL is the one transition we rewrite: the CANCEL +// link points back at the same page, so we replace the editor entry instead of +// pushing a second page entry on top of it. Without this, page -> edit -> CANCEL +// would leave [page, editor, page] and Back would walk straight back into the +// editor. SAVE does the equivalent from editor/main.js (replaceState + reload). (function () { function isEdit(loc) { return new URLSearchParams(loc.search).has('edit'); @@ -20,12 +20,16 @@ var a = e.target.closest ? e.target.closest('a[href]') : null; if (!a || a.target || a.hasAttribute('download')) return; + // Only act while inside the editor. Entering the editor stays a normal + // push so Back can cancel it. + if (!isEdit(window.location)) return; + var url = new URL(a.href, window.location.href); if (url.origin !== window.location.origin) return; if (url.pathname !== window.location.pathname) return; - // Same page: only editor entry/exit is a mode switch. Plain anchor - // links share the pathname too and must keep their normal behaviour. - if (!isEdit(url) && !isEdit(window.location)) return; + // Leaving the editor to another page (e.g. a wikilink) keeps its normal + // push; only the same-page exit (CANCEL) is collapsed. + if (isEdit(url)) return; e.preventDefault(); window.location.replace(url.href);