feat(e10): put every keyboard shortcut in one table

Six surfaces each owned a private keydown listener with its own key
literals and its own idea of "am I typing?", and they had drifted:
PlexPlayer matched `case "a"` without checking modifiers, so Ctrl+A
(select all) cycled the audio track, and its typing guard missed
<select> and contentEditable where the other two surfaces covered both.

lib/keymap.ts is a TABLE, not a dispatcher. Each surface keeps its own
listener and closures - PlexPlayer's refs are not worth re-plumbing -
but the chords, the modifier rules and the typing guard live in one
place, and dispatch() binds a scope's rows to handlers through a mapped
type, so a row without a handler (or a handler without a row) is a tsc
error rather than a comment someone forgets.

Matching is now exact on modifiers, which is what kills the Ctrl+A bug
and lets Shift+arrow be its own row beside the bare arrow. Escape opts
out of the typing guard (allowInInput) so it stays the way out of a
player's own subtitle-offset field.

The player's shortcut sheet renders helpLines() instead of a hand-copied
array beside the handler - the two used to be edited separately, which
is how a sheet ends up documenting a key that no longer exists. Labels
move to the new `keys` i18n namespace (EN + HU), and a test asserts
every row is named in both.
This commit is contained in:
2026-09-05 20:58:17 +02:00
parent 928eac4c89
commit 12822a9e04
10 changed files with 663 additions and 226 deletions
+23
View File
@@ -0,0 +1,23 @@
{
"title": "Keyboard shortcuts",
"contextMenu": "Open the context menu",
"undo": "Undo",
"redo": "Redo",
"playPause": "Play / pause",
"fullscreen": "Fullscreen",
"seek": "Seek 10 seconds",
"seek5": "Seek 5 seconds",
"fineSeek": "Fine seek",
"episode": "Previous / next episode",
"video": "Previous / next video",
"volume": "Volume up / down",
"mute": "Mute",
"audio": "Cycle audio track",
"subtitle": "Cycle subtitle",
"help": "Show this help",
"info": "Media info",
"stats": "Stats for nerds",
"back": "Stop & back to feed",
"browseBack": "Back one level",
"closePlayer": "Close the player"
}
-15
View File
@@ -186,21 +186,6 @@
"stop": "Stop",
"help": "Keyboard shortcuts",
"infoBtn": "Media info",
"keys": {
"playPause": "Play / pause",
"seek": "Seek 10 seconds",
"episode": "Previous / next episode",
"volume": "Volume up / down",
"mute": "Mute",
"fullscreen": "Fullscreen",
"info": "Media info",
"back": "Stop & back to feed",
"help": "Show this help",
"audio": "Cycle audio track",
"subtitle": "Cycle subtitle",
"fineSeek": "Fine seek",
"stats": "Stats for nerds"
},
"upNext": {
"title": "Up next",
"playNow": "Play now",
+23
View File
@@ -0,0 +1,23 @@
{
"title": "Billentyűparancsok",
"contextMenu": "Helyi menü megnyitása",
"undo": "Visszavonás",
"redo": "Újra",
"playPause": "Lejátszás / szünet",
"fullscreen": "Teljes képernyő",
"seek": "Tekerés 10 másodperc",
"seek5": "Tekerés 5 másodperc",
"fineSeek": "Finom léptetés",
"episode": "Előző / következő rész",
"video": "Előző / következő videó",
"volume": "Hangerő fel / le",
"mute": "Némítás",
"audio": "Hangsáv váltása",
"subtitle": "Felirat váltása",
"help": "Súgó megjelenítése",
"info": "Média infó",
"stats": "Statisztika (nerdöknek)",
"back": "Leállítás és vissza a feedre",
"browseBack": "Vissza egy szintet",
"closePlayer": "Lejátszó bezárása"
}
-15
View File
@@ -186,21 +186,6 @@
"stop": "Leállítás",
"help": "Billentyűparancsok",
"infoBtn": "Média infó",
"keys": {
"playPause": "Lejátszás / szünet",
"seek": "Tekerés 10 másodperc",
"episode": "Előző / következő rész",
"volume": "Hangerő fel / le",
"mute": "Némítás",
"fullscreen": "Teljes képernyő",
"info": "Média infó",
"back": "Leállítás és vissza a feedre",
"help": "Súgó megjelenítése",
"audio": "Hangsáv váltása",
"subtitle": "Felirat váltása",
"fineSeek": "Finom léptetés",
"stats": "Statisztika (nerdöknek)"
},
"upNext": {
"title": "Következik",
"playNow": "Lejátszás most",