From c33765102066621b5d27853415140e4ec54f97b4 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sun, 20 Sep 2026 00:23:31 +0000 Subject: [PATCH] Keep editor open across in-site navigation, retargeting to the new page load() closed the editor on every navigation, and the close raced the navigation: the shell's close-time loadPlain re-render resolved last, swapping the old page back in and replaceStating its URL. Navigation now leaves the panel open and the existing pagerite:switch-editor retarget fires while editing; the exception is /_a, where closeEditor({ navigating: true }) closes the panel without the re-swap or title restore. --- frontend/src/EditorShell.vue | 6 ++++-- frontend/src/main.js | 9 +++++++-- frontend/src/pagerite.js | 21 ++++++++++++++------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/frontend/src/EditorShell.vue b/frontend/src/EditorShell.vue index 59ee0e8..8cb8ba6 100644 --- a/frontend/src/EditorShell.vue +++ b/frontend/src/EditorShell.vue @@ -44,11 +44,13 @@ function openShell() { editorLang.value = session pinPreviewLang() } -function unpinPreviewLang() { +function unpinPreviewLang(ev) { if (!pinned) return pinned = false setLangOverride(null) - loadPlain(currentPath.value) + // A close caused by navigation (to /_a) must not re-render the page the + // editor was on: the navigation itself is swapping in the target page. + if (!ev?.detail?.navigating) loadPlain(currentPath.value) } watch(editorLang, () => { if (pinned) pinPreviewLang() }) // The page's primary may be (re)learned while pinned on it (doc accept, diff --git a/frontend/src/main.js b/frontend/src/main.js index 7170dc5..185aa76 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -125,7 +125,11 @@ function showEditor() { dispatchEvent(new CustomEvent('pagerite:editor-shown')) } -export function closeEditor() { +// navigating: the close is part of a fetch-navigation (to /_a) — the shell +// must not re-swap the page it was previewing back in, and the navigation +// itself sets the new title, so both the unpin re-render and the title +// restore are skipped. +export function closeEditor({ navigating = false } = {}) { if (!visible) return visible = false stopTrackingPanel() @@ -136,7 +140,8 @@ export function closeEditor() { host.firstElementChild?.classList.add('closing') const h = host setTimeout(() => { h.style.display = 'none' }, 250) - dispatchEvent(new CustomEvent('pagerite:editor-hidden')) + dispatchEvent(new CustomEvent('pagerite:editor-hidden', { detail: { navigating } })) + if (navigating) return // The editor may have dropped the prefetch cache; warm it again for the // now-final page so navigation stays instant. dispatchEvent(new CustomEvent('pagerite:preload-pages')) diff --git a/frontend/src/pagerite.js b/frontend/src/pagerite.js index d5c8974..ae6cf86 100644 --- a/frontend/src/pagerite.js +++ b/frontend/src/pagerite.js @@ -843,13 +843,16 @@ import { reconnectPolicy, socketSlot, watchConnecting } from "./reconnect"; // --- Fetch navigation ------------------------------------------------ async function load(url, push = true, back = false) { - // Navigating with the editor open closes it; unsaved edits are lost - // (the region swap discards the previewed changes anyway). Cache must be - // bypassed for this navigation because the editor may have invalidated - // the prefetched copies of other pages. + // Navigating with the editor open keeps it open: the shell retargets to + // the new page once the swap lands (below). The exception is /_a + // (analytics), where the panel does not apply — close it there, with + // navigating: true so the close does not re-swap/re-title the page it + // was previewing (this navigation is already swapping). The cache is + // bypassed while editing because the editor may have invalidated the + // prefetched copies of other pages. const editing = document.body.classList.contains("editing"); - if (editing) { - editorModule?.then((m) => m.closeEditor()); + if (editing && new URL(url, location.href).pathname === "/_a") { + await editorModule?.then((m) => m.closeEditor({ navigating: true })); } teardownAnalytics(); let doc; @@ -936,7 +939,11 @@ import { reconnectPolicy, socketSlot, watchConnecting } from "./reconnect"; // own lang="en" dir="ltr", so it is unaffected). document.documentElement.lang = doc.documentElement.lang; document.documentElement.dir = doc.documentElement.dir; - document.title = doc.title; + // The editor keeps its own title while open (the retargeted tab + // re-applies it); only inherit the server title when not editing. + if (!document.body.classList.contains("editing")) { + document.title = doc.title; + } // Banners may contain scripts (canvas etc.), content pages may too. runScripts(document.getElementById("page-banner")); runScripts(document.getElementById("main"));