Put every shortcut on one shared keymap and make the app keyboard-reachable
The keymap was scattered over ShortcutsService if-chains, the file panel's key switch, macOS-only menu accelerators and hand-typed hints in 16 tooltips x 7 locales, and large parts of the app had no keyboard path at all. src/shared/keymap.ts is now the one table: scopes, per-platform defaults, exact matching, formatting with localized key names, conflicts, reserved combos and native-menu accelerators, proven by keymap.spec.ts. - ShortcutsService resolves against the effective keymap: claim() takes the key (preventDefault, held keys repeat only movement, never mid-IME), and from a text field only chords and F-keys fire. The file panel, App, editor, manager and Settings windows all dispatch through it. - FileActionsService.run is the one file-command dispatcher for the list keys, the row menu, the macOS menu bar and the toolbar. - New keys: the row menu (Shift+F10 / Menu / Ctrl+Return), the source picker (Mod+K), the path bar (Mod+L), transfers (Mod+J), sorting (Alt+1-7), Shift+movement selection, close other tabs, move and disconnect tabs, refresh and edit on Windows/Linux, permissions, copy path, reveal, trash, open in default app, editor find next/previous. - Right-click menus: soft shortcut hints, icons on the everyday verbs (the toolbar's glyphs), section headers, scrolling, item keys while open, a highlighted first item when opened from the keyboard. The panel source picker now renders through the same menu. - Dialogs: role=dialog, a Tab trap via cycleFocus (WKWebView skips buttons on Tab without Keyboard navigation), initial focus, Esc that stops propagating; the chmod dialog takes focus and applies on Enter. - Fixed: Ctrl+Tab also flipped the active panel; Esc in a manager dialog closed the manager; Ctrl-F/T fired Cmd shortcuts on macOS; shortcuts fired under dialogs; keys died after any button click on macOS; the editor's save prompt and find box never got focus; WebView2 reload keys; a window could save default settings before its first load. - Settings closes on its own close-window key (new settingsClose request). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,9 @@ import { NgIcon } from '@ng-icons/core';
|
||||
import { ContextMenuService } from '@core/context-menu.service';
|
||||
import { I18nService } from '@core/i18n.service';
|
||||
import { PanelsService, type Workspace } from '@core/panels.service';
|
||||
import { IS_MAC_CHROME, MOD_LABEL } from '@core/platform';
|
||||
import { IS_MAC_CHROME } from '@core/platform';
|
||||
import { RpcService } from '@core/rpc.service';
|
||||
import { ShortcutsService } from '@core/shortcuts.service';
|
||||
import { UpdaterService } from '@core/updater.service';
|
||||
|
||||
@Component({
|
||||
@@ -63,7 +64,7 @@ import { UpdaterService } from '@core/updater.service';
|
||||
(click)="close($event, ws.id)"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
[title]="i18n.t('tabBar.closeTab', { mod: modLabel })"
|
||||
[title]="i18n.t('tabBar.closeTab')"
|
||||
class="mr-1 grid size-4 shrink-0 place-items-center text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-border hover:text-foreground"
|
||||
>
|
||||
<ng-icon name="tdesignClose" size="0.7rem" />
|
||||
@@ -79,7 +80,7 @@ import { UpdaterService } from '@core/updater.service';
|
||||
</div>
|
||||
<button
|
||||
(click)="panels.newWorkspace()"
|
||||
[title]="i18n.t('tabBar.newTab', { mod: modLabel })"
|
||||
[title]="shortcuts.tip(i18n.t('tabBar.newTab'), 'new-tab')"
|
||||
class="ml-0.5 grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<ng-icon name="tdesignAdd" size=".8rem" />
|
||||
@@ -121,7 +122,7 @@ import { UpdaterService } from '@core/updater.service';
|
||||
<!-- connection manager (own native window) -->
|
||||
<button
|
||||
(click)="openManager()"
|
||||
[title]="i18n.t('tabBar.manageConnections')"
|
||||
[title]="shortcuts.tip(i18n.t('tabBar.manageConnections'), 'connection-manager')"
|
||||
class="grid h-6 px-2 py-1 place-items-center text-[0.6875rem] text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<!-- <ng-icon name="tdesignSetting" size="0.9rem" /> -->
|
||||
@@ -131,7 +132,7 @@ import { UpdaterService } from '@core/updater.service';
|
||||
<!-- settings (own native window) -->
|
||||
<button
|
||||
(click)="openSettings()"
|
||||
[title]="i18n.t('tabBar.options')"
|
||||
[title]="shortcuts.tip(i18n.t('tabBar.options'), 'open-settings')"
|
||||
class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<ng-icon name="tdesignSetting" size="0.9rem" />
|
||||
@@ -145,8 +146,8 @@ export class TabBar {
|
||||
readonly updater = inject(UpdaterService);
|
||||
private readonly contextMenu = inject(ContextMenuService);
|
||||
private readonly rpc = inject(RpcService);
|
||||
readonly shortcuts = inject(ShortcutsService);
|
||||
readonly isMacChrome = IS_MAC_CHROME;
|
||||
readonly modLabel = MOD_LABEL;
|
||||
|
||||
// drag & drop reordering
|
||||
readonly draggedId = signal<number | null>(null);
|
||||
@@ -213,9 +214,7 @@ export class TabBar {
|
||||
|
||||
/** Update pill: first click downloads, second click (when ready) applies + relaunches. */
|
||||
onUpdateClick(): void {
|
||||
const state = this.updater.state();
|
||||
if (state === 'available') void this.updater.download();
|
||||
else if (state === 'ready') this.updater.apply();
|
||||
this.updater.advance();
|
||||
}
|
||||
|
||||
/** Insertion line at the drop position (inset shadow: no layout shift). */
|
||||
|
||||
Reference in New Issue
Block a user