135 lines
4.9 KiB
TypeScript
135 lines
4.9 KiB
TypeScript
import type { AppTranslationKey } from '@/lib/i18n';
|
|
|
|
/*
|
|
Every keyboard shortcut the shell owns, as data.
|
|
|
|
── Why a registry ──
|
|
|
|
Ported in shape from the owner's `vimazing` (`vim-maze/src/gameInfo.ts`), where the controls are declared
|
|
once and the help screen renders them. The alternative is what this replaced: chords spelled out inside
|
|
three separate key handlers, and a help text nobody updates because it lives somewhere else.
|
|
|
|
Two things follow from having it. `Ctrl+?` can render an accurate list without anybody maintaining prose.
|
|
And when shortcuts become configurable — the next big piece — this is the table a binding UI edits, rather
|
|
than a grep across handlers.
|
|
|
|
── What it is NOT, yet ──
|
|
|
|
Declarative but not authoritative: the handlers still contain their own conditions, so a chord changed here
|
|
does not move. Making this the single source is part of the configurable-shortcuts work, and doing it now
|
|
would mean a rewrite of three handlers for no visible gain. Where a chord appears in both places the file
|
|
that owns it is named in `implementedIn`, so the pair can be found in one search.
|
|
*/
|
|
|
|
export type Shortcut = {
|
|
/** As a person reads it — `Ctrl+Shift+Space`, not a KeyboardEvent spelling. */
|
|
keys: string;
|
|
/** A catalog key. This is data with no component around it, so ShortcutsModal resolves it. */
|
|
descriptionKey: AppTranslationKey;
|
|
/** The file whose handler actually implements it, until this registry is authoritative. */
|
|
implementedIn: string;
|
|
};
|
|
|
|
export type ShortcutSection = {
|
|
titleKey: AppTranslationKey;
|
|
/** Shown under the title when the section needs a caveat that a per-row description cannot carry. */
|
|
noteKey?: AppTranslationKey;
|
|
shortcuts: Shortcut[];
|
|
};
|
|
|
|
export const SHORTCUT_SECTIONS: ShortcutSection[] = [
|
|
{
|
|
titleKey: 'shortcuts.navigation',
|
|
shortcuts: [
|
|
{
|
|
keys: 'Ctrl+Space',
|
|
descriptionKey: 'shortcuts.spotlight',
|
|
implementedIn: 'Layout/Spotlight/Spotlight.tsx',
|
|
},
|
|
{ keys: 'Ctrl+?', descriptionKey: 'shortcuts.thisList', implementedIn: 'Layout/ShortcutsModal.tsx' },
|
|
],
|
|
},
|
|
{
|
|
titleKey: 'shortcuts.themes',
|
|
noteKey: 'shortcuts.themesNote',
|
|
shortcuts: [
|
|
{
|
|
keys: 'Ctrl+Shift+↑ / ↓',
|
|
descriptionKey: 'shortcuts.openBrowser',
|
|
implementedIn: 'theme/useThemeKeys.ts',
|
|
},
|
|
{
|
|
keys: 'Ctrl+Shift+← / →',
|
|
descriptionKey: 'shortcuts.prevNextTheme',
|
|
implementedIn: 'theme/useThemeKeys.ts',
|
|
},
|
|
{
|
|
keys: 'Ctrl+Shift+F',
|
|
descriptionKey: 'shortcuts.starCurrent',
|
|
implementedIn: 'theme/useThemeKeys.ts',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
titleKey: 'shortcuts.inBrowser',
|
|
shortcuts: [
|
|
{ keys: 'j / k ↑ / ↓', descriptionKey: 'shortcuts.moveList', implementedIn: 'theme/useThemeKeys.ts' },
|
|
{
|
|
keys: 'l →',
|
|
descriptionKey: 'shortcuts.applyKeepBrowsing',
|
|
implementedIn: 'theme/useThemeKeys.ts',
|
|
},
|
|
{ keys: 'h ←', descriptionKey: 'shortcuts.restoreStart', implementedIn: 'theme/useThemeKeys.ts' },
|
|
{ keys: 'f', descriptionKey: 'shortcuts.starFocused', implementedIn: 'theme/useThemeKeys.ts' },
|
|
{ keys: 'Enter', descriptionKey: 'shortcuts.keepLooking', implementedIn: 'theme/useThemeKeys.ts' },
|
|
{ keys: 'Esc', descriptionKey: 'shortcuts.cancelRestore', implementedIn: 'theme/useThemeKeys.ts' },
|
|
],
|
|
},
|
|
{
|
|
titleKey: 'shortcuts.inSpotlight',
|
|
shortcuts: [
|
|
{ keys: '↑ / ↓', descriptionKey: 'shortcuts.moveResults', implementedIn: 'Layout/Spotlight/Spotlight.tsx' },
|
|
{ keys: 'Enter', descriptionKey: 'shortcuts.goThere', implementedIn: 'Layout/Spotlight/Spotlight.tsx' },
|
|
{ keys: 'Esc', descriptionKey: 'shortcuts.close', implementedIn: 'Layout/Spotlight/Spotlight.tsx' },
|
|
],
|
|
},
|
|
{
|
|
titleKey: 'shortcuts.chat',
|
|
noteKey: 'shortcuts.chatNote',
|
|
shortcuts: [
|
|
{
|
|
keys: '↑ / ↓',
|
|
descriptionKey: 'shortcuts.prevNextPrompt',
|
|
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
|
|
},
|
|
{
|
|
keys: 'Enter',
|
|
descriptionKey: 'shortcuts.send',
|
|
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
|
|
},
|
|
{
|
|
keys: 'Shift+Enter',
|
|
descriptionKey: 'shortcuts.newline',
|
|
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
|
|
},
|
|
{
|
|
keys: 'Esc',
|
|
descriptionKey: 'shortcuts.stopTurn',
|
|
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Is this the `Ctrl+?` chord?
|
|
*
|
|
* `?` is `Shift+/` on most layouts and its own key on some, so both spellings are accepted rather than
|
|
* guessing which keyboard is attached. `code === 'Slash'` is the physical key, which is stable where `key`
|
|
* is not.
|
|
*/
|
|
export function isShortcutsChord(ev: KeyboardEvent): boolean {
|
|
if (!ev.ctrlKey) return false;
|
|
return ev.key === '?' || (ev.shiftKey && ev.code === 'Slash');
|
|
}
|