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:
parent
c86e9b90a5
commit
171553c137
1 changed files with 47 additions and 0 deletions
|
|
@ -134,6 +134,7 @@
|
||||||
<button onclick="rescan()" title="Rescan library">⟳</button>
|
<button onclick="rescan()" title="Rescan library">⟳</button>
|
||||||
<button onclick="openStats()" title="Library statistics">📊</button>
|
<button onclick="openStats()" title="Library statistics">📊</button>
|
||||||
<button onclick="openMaintenance()" title="Library health">🩺</button>
|
<button onclick="openMaintenance()" title="Library health">🩺</button>
|
||||||
|
<button onclick="showShortcutsHelp()" title="Keyboard shortcuts (?)">?</button>
|
||||||
<button onclick="openSettings()">⚙</button>
|
<button onclick="openSettings()">⚙</button>
|
||||||
<span id="status"></span>
|
<span id="status"></span>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -1445,6 +1446,52 @@ function schedulePoll(ms){
|
||||||
}
|
}
|
||||||
schedulePoll(600);
|
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 ──────────────────────────────────────────────────── */
|
/* ── 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 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'}
|
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'}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue