From c89c42dc37bd40269e9112c4e8934e30f33ef8ac Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 21 May 2021 22:45:55 +0200 Subject: [PATCH] Base shortcuts with letters no event.key, with symbols/numbers on event.code --- ts/components/WithShortcut.svelte | 6 +- ts/deckoptions/DeckOptionsPage.svelte | 8 ++- ts/editor/ClozeButton.svelte | 3 +- ts/editor/FormatInlineButtons.svelte | 6 +- ts/editor/inputHandlers.ts | 4 +- ts/lib/shortcuts.ts | 90 +++++++++++++++++++-------- 6 files changed, 79 insertions(+), 38 deletions(-) diff --git a/ts/components/WithShortcut.svelte b/ts/components/WithShortcut.svelte index 3f753fa91..7632761c6 100644 --- a/ts/components/WithShortcut.svelte +++ b/ts/components/WithShortcut.svelte @@ -3,13 +3,11 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> diff --git a/ts/editor/ClozeButton.svelte b/ts/editor/ClozeButton.svelte index e8a4d2add..d7f27761a 100644 --- a/ts/editor/ClozeButton.svelte +++ b/ts/editor/ClozeButton.svelte @@ -42,8 +42,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html document.addEventListener("focusin", updateFocus, { once: true }), - [["Tab"]], - ["Shift"] + [["Shift?", "Tab"]], + true ); export function onKeyUp(evt: KeyboardEvent): void { diff --git a/ts/lib/shortcuts.ts b/ts/lib/shortcuts.ts index 24a75202e..9d74f3702 100644 --- a/ts/lib/shortcuts.ts +++ b/ts/lib/shortcuts.ts @@ -4,8 +4,6 @@ import * as tr from "./i18n"; export type Modifier = "Control" | "Alt" | "Shift" | "Meta"; -const modifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"]; - function isApplePlatform(): boolean { return ( window.navigator.platform.startsWith("Mac") || @@ -64,10 +62,15 @@ function capitalize(key: string): string { return key[0].toLocaleUpperCase() + key.slice(1); } +function isRequiredModifier(modifier: string): boolean { + return !modifier.endsWith("?"); +} + function toPlatformString(modifiersAndKey: string[]): string { return ( - modifiersToPlatformString(modifiersAndKey.slice(0, -1)) + - capitalize(keyToPlatformString(modifiersAndKey[modifiersAndKey.length - 1])) + modifiersToPlatformString( + modifiersAndKey.slice(0, -1).filter(isRequiredModifier) + ) + capitalize(keyToPlatformString(modifiersAndKey[modifiersAndKey.length - 1])) ); } @@ -75,33 +78,58 @@ export function getPlatformString(keyCombination: string[][]): string { return keyCombination.map(toPlatformString).join(", "); } -function checkKey(event: KeyboardEvent, key: string): boolean { - return event.key === key; +function checkKey( + getProperty: (event: KeyboardEvent) => string, + event: KeyboardEvent, + key: string +): boolean { + return getProperty(event) === key; } -function checkModifiers( - event: KeyboardEvent, - optionalModifiers: Modifier[], - activeModifiers: string[] -): boolean { - return modifiers.reduce( - (matches: boolean, modifier: string, currentIndex: number): boolean => +const allModifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"]; + +function partition(predicate: (t: T) => boolean, items: T[]): [T[], T[]] { + const trueItems: T[] = []; + const falseItems: T[] = []; + + items.forEach((t) => { + const target = predicate(t) ? trueItems : falseItems; + target.push(t); + }); + + return [trueItems, falseItems]; +} + +function removeTrailing(modifier: string): string { + return modifier.substring(0, modifier.length - 1); +} + +function checkModifiers(event: KeyboardEvent, modifiers: string[]): boolean { + const [requiredModifiers, otherModifiers] = partition( + isRequiredModifier, + modifiers + ); + + const optionalModifiers = otherModifiers.map(removeTrailing); + + return allModifiers.reduce( + (matches: boolean, currentModifier: string, currentIndex: number): boolean => matches && - (optionalModifiers.includes(modifier as Modifier) || + (optionalModifiers.includes(currentModifier as Modifier) || event.getModifierState(platformModifiers[currentIndex]) === - activeModifiers.includes(modifier)), + requiredModifiers.includes(currentModifier)), true ); } function check( + getProperty: (event: KeyboardEvent) => string, event: KeyboardEvent, - optionalModifiers: Modifier[], modifiersAndKey: string[] ): boolean { return ( - checkKey(event, modifiersAndKey[modifiersAndKey.length - 1]) && - checkModifiers(event, optionalModifiers, modifiersAndKey.slice(0, -1)) + checkModifiers(event, modifiersAndKey.slice(0, -1)) && + checkKey(getProperty, event, modifiersAndKey[modifiersAndKey.length - 1]) ); } @@ -111,7 +139,7 @@ const NUMPAD_KEY = 3; function innerShortcut( lastEvent: KeyboardEvent, callback: (event: KeyboardEvent) => void, - optionalModifiers: Modifier[], + getProperty: (event: KeyboardEvent) => string, ...keyCombination: string[][] ): void { let interval: number; @@ -122,10 +150,13 @@ function innerShortcut( const [nextKey, ...restKeys] = keyCombination; const handler = (event: KeyboardEvent): void => { - if (check(event, optionalModifiers, nextKey)) { - innerShortcut(event, callback, optionalModifiers, ...restKeys); + if (check(getProperty, event, nextKey)) { + innerShortcut(event, callback, getProperty, ...restKeys); clearTimeout(interval); - } else if (event.location === GENERAL_KEY || event.location === NUMPAD_KEY) { + } else if ( + event.location === GENERAL_KEY || + event.location === NUMPAD_KEY + ) { // Any non-modifier key will cancel the shortcut sequence document.removeEventListener("keydown", handler); } @@ -135,16 +166,25 @@ function innerShortcut( } } +function byKey(event: KeyboardEvent): string { + return event.key; +} + +function byCode(event: KeyboardEvent): string { + return event.code; +} + export function registerShortcut( callback: (event: KeyboardEvent) => void, keyCombination: string[][], - optionalModifiers: Modifier[] = [] + useCode = false ): () => void { const [firstKey, ...restKeys] = keyCombination; + const getProperty = useCode ? byCode : byKey; const handler = (event: KeyboardEvent): void => { - if (check(event, optionalModifiers, firstKey)) { - innerShortcut(event, callback, optionalModifiers, ...restKeys); + if (check(getProperty, event, firstKey)) { + innerShortcut(event, callback, getProperty, ...restKeys); } };