Files
pl_mail/assets/history_layer.js
Paul LütznerandClaude Opus 5.5 0277f72b29 What's new: a screenshot grows to fill the dialog instead of opening a tab (#181)
Clicking a highlight's screenshot opened the PNG in a new tab. It now grows
out of its thumbnail until it fills the What's new dialog, with an X; the X,
Escape or Back shrink it back, and What's new stays open.

Back goes through a history entry of its own (assets/history_layer.js),
imported first in app.js so its popstate listener runs before Turbo's, which
would otherwise restore the page and close the dialog.

Closes #179

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:02:06 +02:00

73 lines
2.4 KiB
JavaScript

/*
* history_layer.js — Back closes something on the page instead of leaving it.
*
* On a phone, Back is how anything that has been opened is put away. For a
* layer that is not a page of its own — the zoomed screenshot in What's new is
* the first (#179) — that means a history entry of its own: openLayer() pushes
* one, and Back pops it and calls the layer's onBack instead of going anywhere.
*
* ## Why this file is imported before everything else in app.js
*
* The popstate that Back fires must reach nobody but the layer. Turbo answers
* any popstate onto an entry carrying its state with a restoration visit — the
* whole page drawn again, What's new and all — and the mail pane treats one
* without its own state as "back to the list". Both listen on window, and on
* window listeners run in the order they were added, capture or not (tried in
* Chromium: a capture listener added later still runs after the others), so
* stopImmediatePropagation() only keeps them out if this listener came first.
* Being the first module app.js evaluates is what makes it first: Turbo is
* started from stimulus_bootstrap.js, which comes after.
*
* ## Closing without Back
*
* The X, Escape, or the dialog around the layer closing: closeLayer() goes
* back through the entry so the stack is as it was found, and swallows the
* popstate that follows, for the same reason as above.
*
* One layer at a time. Nothing stacks today, and a stack would need to know
* which entry belongs to which layer when they are closed out of order.
*/
let onBack = null
let swallow = 0
window.addEventListener("popstate", (event) => {
if (swallow > 0) {
swallow--
event.stopImmediatePropagation()
return
}
if (null !== onBack) {
const back = onBack
onBack = null
event.stopImmediatePropagation()
back()
}
})
/**
* @param {() => void} back called when Back is pressed while the layer is open
*/
export function openLayer(back) {
closeLayer()
history.pushState({ ...history.state, historyLayer: true }, "")
onBack = back
}
/**
* Takes the layer's entry back off the stack, if it is still the current one —
* after a Turbo visit it is not, and going back then would be a navigation.
*/
export function closeLayer() {
onBack = null
if (true === history.state?.historyLayer) {
swallow++
history.back()
}
}