From 30c2b0fe8232cc748ab2bded6ab4d76febe32425 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 3 May 2026 19:17:04 +0300 Subject: fix(admin): fix rescan goroutine lifecycle and race on shared ScanProgress - Protect adminService scan state (cancel func + progress pointer) with sync.Mutex. - Allocate fresh ScanProgress per trigger and pass it to the scanner, eliminating races on the previously shared progress struct. - Cancel previous scan context before starting a new one. - Add tests for cancellation, fresh progress per scan, concurrent triggers, and empty progress when never started. - Fix race-prone tests by polling Running==true before waiting for completion. --- web/js/keyboard.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) (limited to 'web/js/keyboard.js') diff --git a/web/js/keyboard.js b/web/js/keyboard.js index 8623a10..951e573 100644 --- a/web/js/keyboard.js +++ b/web/js/keyboard.js @@ -3,6 +3,42 @@ export function initKeyboard(handlers) { const tag = e.target.tagName; const editing = tag === 'INPUT' || tag === 'TEXTAREA' || e.target.isContentEditable; + // Lightbox keyboard navigation (overrides global keys while open) + if (handlers.isLightboxOpen?.()) { + if (e.shiftKey && e.code === 'KeyS') { + e.preventDefault(); + handlers.toggleSlideshow?.(e); + return; + } + switch (e.key) { + case 'ArrowLeft': + case 'h': + e.preventDefault(); + handlers.lightboxPrev?.(e); + return; + case 'ArrowRight': + case 'l': + e.preventDefault(); + handlers.lightboxNext?.(e); + return; + case 'Escape': + e.preventDefault(); + handlers.closeLightbox?.(e); + return; + case '+': + case '=': + e.preventDefault(); + handlers.zoomIn?.(e); + return; + case '-': + e.preventDefault(); + handlers.zoomOut?.(e); + return; + } + // Allow only Escape/Arrows/h/l/+/−/Shift+S inside the lightbox; ignore everything else + return; + } + // Shares modal keyboard navigation (overrides global keys while open) if (handlers.isSharesOpen?.()) { switch (e.key) { @@ -101,8 +137,15 @@ export function initKeyboard(handlers) { } if (e.code === 'KeyS') { e.preventDefault(); - if (e.shiftKey) handlers.share?.(e); - else handlers.sidebar?.(e); + if (e.shiftKey) { + if (handlers.isImageMode?.()) { + handlers.toggleSlideshow?.(e); + } else { + handlers.share?.(e); + } + } else { + handlers.sidebar?.(e); + } return; } @@ -174,8 +217,7 @@ export function initKeyboard(handlers) { case 'Escape': handlers.escape?.(e); break; case 'Backspace': handlers.backspace?.(e); break; case 'r': handlers.shuffle?.(e); break; - case 's': handlers.sidebar?.(e); break; - case 'S': handlers.share?.(e); break; + // Note: 's' / 'S' are handled above by the e.code === 'KeyS' block case '/': e.preventDefault(); handlers.search?.(e); -- cgit v1.2.3