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
-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",