fix(cmdk): bypass Alpine entirely for close + cache-bust static assets
Two layered fixes for the palette getting stuck open even after the
1.4.3 ESC fix:
1) Static asset cache busting
- /static/css/app.css and /static/js/app.js now include ?v={app_version}
in their <script>/<link> URLs. Container Manager Rebuild was pulling
the new image but browsers were still using the cached JS/CSS from
a previous version — making any fix to app.js invisible until the
user hit Cmd+Shift+R. The version-suffixed URL forces a refetch on
every release. _app_version is now passed in the index() context.
2) Direct DOM bypass for the palette close path
- New cmdkForceClose() does the close via plain DOM (display:none + set
Alpine.$data().open=false if Alpine is alive). Independent of Alpine
reactivity timing.
- Document-level keydown listener with capture:true installed at
DOMContentLoaded. Runs before any focused element captures Esc /
Cmd+K, including the palette's own input. Calls cmdkForceClose()
directly — does not rely on the Alpine event going through.
- X button and outer overlay onclick now invoke cmdkForceClose() via
onclick=, not Alpine @click. The Alpine @click handlers are kept
side-by-side as belt-and-suspenders.
- Input keeps a literal onkeydown="" Escape handler too — three
independent ways to close, so something always works.
Bump 1.5.0 -> 1.5.1 (patch on top of the pending 1.5.0 PR; release
will publish a single 1.5.1 tag covering both the relations editor
and the palette hardening).
Verified locally: HTML response carries ?v= on static URLs,
cmdkForceClose function is present, onkeydown handler on input is in.
151 tests + lint + types + format still green.
This commit is contained in:
parent
e69d6128e6
commit
d983944850
4 changed files with 45 additions and 24 deletions
|
|
@ -7,7 +7,7 @@ packages = ["src"]
|
|||
|
||||
[project]
|
||||
name = "reddit-media-collector"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
description = "Self-hosted media collector for Reddit with Immich integration"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ async def index(request: Request):
|
|||
"users": users,
|
||||
"blacklist": blacklist,
|
||||
"pin_enabled": pin_required(),
|
||||
"app_version": _app_version,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1729,29 +1729,47 @@ window.removeTagRelation = removeTagRelation;
|
|||
|
||||
// ---- Cmd+K palette (Alpine component) ----
|
||||
|
||||
// Hard close: removes overlay from layout via plain DOM, bypassing Alpine.
|
||||
// Used by document-level listeners and the X button so a stuck Alpine state
|
||||
// can never trap the user.
|
||||
function cmdkForceClose() {
|
||||
const el = document.getElementById('cmdk-overlay');
|
||||
if (!el) return;
|
||||
el.style.display = 'none';
|
||||
const data = window.Alpine && Alpine.$data ? Alpine.$data(el) : null;
|
||||
if (data) data.open = false;
|
||||
}
|
||||
window.cmdkForceClose = cmdkForceClose;
|
||||
|
||||
// Document-level listener installed once at DOMContentLoaded. Uses
|
||||
// `capture: true` so it runs before any focused input swallows the key.
|
||||
// Independent of Alpine, so it works even if the component never mounted.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
const el = document.getElementById('cmdk-overlay');
|
||||
if (el && getComputedStyle(el).display !== 'none') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
cmdkForceClose();
|
||||
}
|
||||
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
const el = document.getElementById('cmdk-overlay');
|
||||
const data = window.Alpine && Alpine.$data && el ? Alpine.$data(el) : null;
|
||||
if (data) data.toggle();
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
function cmdkPalette() {
|
||||
return {
|
||||
open: false,
|
||||
query: '',
|
||||
active: 0,
|
||||
results: {authors: [], subreddits: [], tags: []},
|
||||
init() {
|
||||
// Window-level listener — survives x-show toggling display:none
|
||||
// and runs even when the focused element is the palette's input
|
||||
// (Alpine's @keydown.escape.window can race with input focus).
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.open) {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
this.toggle();
|
||||
}
|
||||
});
|
||||
},
|
||||
toggle() { this.open = !this.open; if (this.open) { this.query=''; this.results={authors:[],subreddits:[],tags:[]}; this.active=0; this.$nextTick(()=>this.$el.querySelector('input')?.focus()); } },
|
||||
close() { this.open = false; },
|
||||
close() { this.open = false; cmdkForceClose(); },
|
||||
async search() {
|
||||
const q = this.query.trim();
|
||||
if (!q) { this.results = {authors: [], subreddits: [], tags: []}; return; }
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Reddit Media Collector</title>
|
||||
<link rel="stylesheet" href="/static/css/app.css">
|
||||
<link rel="stylesheet" href="/static/css/app.css?v={{ app_version }}">
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4" defer></script>
|
||||
<script src="https://unpkg.com/htmx-ext-json-enc@2.0.2" defer></script>
|
||||
<script defer src="https://unpkg.com/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
<script src="/static/js/api.js"></script>
|
||||
<script src="/static/js/app.js" defer></script>
|
||||
<script src="/static/js/api.js?v={{ app_version }}"></script>
|
||||
<script src="/static/js/app.js?v={{ app_version }}" defer></script>
|
||||
</head>
|
||||
<body x-data x-init="$store.prefs.init()">
|
||||
<div class="header">
|
||||
|
|
@ -60,12 +60,14 @@
|
|||
{% include 'partials/_modal_tag.html' %}
|
||||
|
||||
<div id="cmdk-overlay" class="cmdk-overlay" x-data="cmdkPalette()" x-show="open" x-cloak
|
||||
@click.self="close()">
|
||||
<div class="cmdk-card" @click.stop>
|
||||
<button type="button" class="cmdk-close" @click="close()" title="Fechar (Esc)">×</button>
|
||||
@click.self="close()"
|
||||
onclick="if(event.target===this){cmdkForceClose();}">
|
||||
<div class="cmdk-card" @click.stop onclick="event.stopPropagation();">
|
||||
<button type="button" class="cmdk-close" onclick="cmdkForceClose();" title="Fechar (Esc)">×</button>
|
||||
<input type="text" x-model="query" @input.debounce.150ms="search()"
|
||||
@keydown.arrow-down.prevent="move(1)" @keydown.arrow-up.prevent="move(-1)"
|
||||
@keydown.enter.prevent="choose()" @keydown.escape.prevent.stop="close()"
|
||||
@keydown.enter.prevent="choose()"
|
||||
onkeydown="if(event.key==='Escape'){event.preventDefault();event.stopPropagation();cmdkForceClose();}"
|
||||
placeholder="Buscar autores, subreddits, tags..." autocomplete="off">
|
||||
<div class="cmdk-results" x-show="hasResults()">
|
||||
<template x-for="(group, gi) in groups()" :key="gi">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue