Keyboard shortcuts for the web UI

Four shortcuts: `/` focuses the filter input, `r` triggers a library
rescan, `Esc` closes the topmost modal, `?` opens a help dialog
listing the bindings. All disabled while the focus is inside a text
input / textarea / contentEditable element so typing a video title
doesn't accidentally fire actions — except Esc, which always closes
modals (even from an input).

A new `?` button next to the existing rescan / maintenance / settings
icons makes the shortcuts discoverable; the help dialog is plain HTML
table with monospace key chips.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-05-25 22:15:54 -07:00
parent c86e9b90a5
commit 171553c137

View file

@ -134,6 +134,7 @@
<button onclick="rescan()" title="Rescan library"></button>
<button onclick="openStats()" title="Library statistics">📊</button>
<button onclick="openMaintenance()" title="Library health">🩺</button>
<button onclick="showShortcutsHelp()" title="Keyboard shortcuts (?)">?</button>
<button onclick="openSettings()"></button>
<span id="status"></span>
</header>
@ -1445,6 +1446,52 @@ function schedulePoll(ms){
}
schedulePoll(600);
/* ── Keyboard shortcuts ─────────────────────────────────────────── */
// Listed in the ? help dialog below. Skip when the user is typing in a
// text input — otherwise pressing `r` while typing a filename would
// trigger a rescan.
document.addEventListener('keydown',(e)=>{
const tag=(document.activeElement?.tagName||'').toLowerCase();
const inField=tag==='input'||tag==='textarea'||document.activeElement?.isContentEditable;
// Esc always closes the topmost modal — even from within an input.
if(e.key==='Escape'){
const modals=document.querySelectorAll('.modal-bg');
if(modals.length){modals[modals.length-1].remove();e.preventDefault();return}
}
if(inField)return;
switch(e.key){
case '/':
document.getElementById('search')?.focus();
e.preventDefault();
break;
case 'r':
rescan();
e.preventDefault();
break;
case '?':
showShortcutsHelp();
e.preventDefault();
break;
}
});
function showShortcutsHelp(){
if(document.getElementById('shortcuts-help'))return;
const bg=document.createElement('div');bg.className='modal-bg';bg.id='shortcuts-help';
bg.onclick=e=>{if(e.target===bg)bg.remove()};
const row=(k,d)=>`<tr><td style="padding:4px 12px 4px 0"><kbd style="background:var(--card);border:1px solid var(--border);border-radius:3px;padding:1px 6px;font-family:monospace;font-size:11px">${k}</kbd></td><td style="padding:4px 0;font-size:13px">${d}</td></tr>`;
bg.innerHTML=`<div class="modal" style="max-width:380px">
<div class="modal-hdr"><h2>Keyboard shortcuts</h2><button onclick="this.closest('.modal-bg').remove()"></button></div>
<table><tbody>
${row('/','Focus search')}
${row('r','Rescan library')}
${row('Esc','Close modal / cancel')}
${row('?','This help')}
</tbody></table>
<div class="settings-hint" style="margin-top:10px">Shortcuts are ignored while typing in text inputs.</div>
</div>`;
document.body.appendChild(bg);
}
/* ── Utilities ──────────────────────────────────────────────────── */
function fmtDur(s){s=Math.floor(s);const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),sec=s%60;return h?`${h}:${p(m)}:${p(sec)}`:`${m}:${p(sec)}`}
function fmtSize(b){if(b>=1073741824)return(b/1073741824).toFixed(1)+' GB';if(b>=1048576)return Math.round(b/1048576)+' MB';return Math.round(b/1024)+' KB'}