Prefer event.which over event.{key,code}
It works better with alternative Latin-based keyboard layouts
This commit is contained in:
parent
c89c42dc37
commit
3a18dce03f
@ -6,8 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
import { onDestroy } from "svelte";
|
||||
import { registerShortcut, getPlatformString } from "lib/shortcuts";
|
||||
|
||||
export let shortcut: string[][];
|
||||
export let useCode = false;
|
||||
export let shortcut: string;
|
||||
|
||||
const shortcutLabel = getPlatformString(shortcut);
|
||||
|
||||
@ -15,14 +14,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
function createShortcut({ detail }: CustomEvent): void {
|
||||
const mounted: HTMLButtonElement = detail.button;
|
||||
deregister = registerShortcut(
|
||||
(event: KeyboardEvent) => {
|
||||
mounted.dispatchEvent(new MouseEvent("click", event));
|
||||
event.preventDefault();
|
||||
},
|
||||
shortcut,
|
||||
useCode
|
||||
);
|
||||
deregister = registerShortcut((event: KeyboardEvent) => {
|
||||
mounted.dispatchEvent(new MouseEvent("click", event));
|
||||
event.preventDefault();
|
||||
}, shortcut);
|
||||
}
|
||||
|
||||
onDestroy(() => deregister());
|
||||
|
@ -36,11 +36,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
let registerCleanup: () => void;
|
||||
onMount(() => {
|
||||
registerCleanup = registerShortcut(
|
||||
() => state.save(false),
|
||||
[["Control", "Enter"]],
|
||||
true
|
||||
);
|
||||
registerCleanup = registerShortcut(() => state.save(false), "Control+Enter");
|
||||
});
|
||||
onDestroy(() => registerCleanup?.());
|
||||
</script>
|
||||
|
@ -41,10 +41,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
}
|
||||
</script>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'Alt?', 'Shift', 'C']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+Alt?+Shift+C'} let:createShortcut let:shortcutLabel>
|
||||
<IconButton
|
||||
tooltip={`${tr.editingClozeDeletion()} (${shortcutLabel})`}
|
||||
on:click={onCloze}
|
||||
|
@ -36,7 +36,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
<ButtonGroup {api}>
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut shortcut={[['F7']]} let:createShortcut let:shortcutLabel>
|
||||
<WithShortcut shortcut={'F7'} let:createShortcut let:shortcutLabel>
|
||||
<IconButton
|
||||
class="forecolor"
|
||||
tooltip={appendInParentheses(tr.editingSetForegroundColor(), shortcutLabel)}
|
||||
@ -48,7 +48,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut shortcut={[['F8']]} let:createShortcut let:shortcutLabel>
|
||||
<WithShortcut shortcut={'F8'} let:createShortcut let:shortcutLabel>
|
||||
<ColorPicker
|
||||
tooltip={appendInParentheses(tr.editingChangeColor(), shortcutLabel)}
|
||||
on:change={setWithCurrentColor}
|
||||
|
@ -26,10 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
<ButtonGroup {api}>
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'b']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+B'} let:createShortcut let:shortcutLabel>
|
||||
<WithState
|
||||
key="bold"
|
||||
update={() => document.queryCommandState('bold')}
|
||||
@ -50,10 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'i']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+I'} let:createShortcut let:shortcutLabel>
|
||||
<WithState
|
||||
key="italic"
|
||||
update={() => document.queryCommandState('italic')}
|
||||
@ -74,10 +68,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'u']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+U'} let:createShortcut let:shortcutLabel>
|
||||
<WithState
|
||||
key="underline"
|
||||
update={() => document.queryCommandState('underline')}
|
||||
@ -98,11 +89,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'Equal']]}
|
||||
useCode={true}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+='} let:createShortcut let:shortcutLabel>
|
||||
<WithState
|
||||
key="superscript"
|
||||
update={() => document.queryCommandState('superscript')}
|
||||
@ -123,11 +110,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'Shift', 'Equal']]}
|
||||
useCode={true}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+Shift+='} let:createShortcut let:shortcutLabel>
|
||||
<WithState
|
||||
key="subscript"
|
||||
update={() => document.queryCommandState('subscript')}
|
||||
@ -148,10 +131,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'r']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+R'} let:createShortcut let:shortcutLabel>
|
||||
<IconButton
|
||||
tooltip={appendInParentheses(tr.editingRemoveFormatting(), shortcutLabel)}
|
||||
on:click={() => {
|
||||
|
@ -25,10 +25,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'l']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+L'} let:createShortcut let:shortcutLabel>
|
||||
<LabelButton
|
||||
disables={false}
|
||||
tooltip={`${tr.editingCustomizeCardTemplates()} (${shortcutLabel})`}
|
||||
|
@ -10,10 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
import LabelButton from "components/LabelButton.svelte";
|
||||
</script>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'Shift', 'P']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+Shift+P'} let:createShortcut let:shortcutLabel>
|
||||
<LabelButton
|
||||
tooltip={tr.browsingPreviewSelectedCard({ val: shortcutLabel })}
|
||||
disables={false}
|
||||
|
@ -36,7 +36,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
<ButtonGroup {api}>
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut shortcut={[['F3']]} let:createShortcut let:shortcutLabel>
|
||||
<WithShortcut shortcut={'F3'} let:createShortcut let:shortcutLabel>
|
||||
<IconButton
|
||||
tooltip={appendInParentheses(tr.editingAttachPicturesaudiovideo(), shortcutLabel)}
|
||||
on:click={onAttachment}
|
||||
@ -47,7 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut shortcut={[['F5']]} let:createShortcut let:shortcutLabel>
|
||||
<WithShortcut shortcut={'F5'} let:createShortcut let:shortcutLabel>
|
||||
<IconButton
|
||||
tooltip={appendInParentheses(tr.editingRecordAudio(), shortcutLabel)}
|
||||
on:click={onRecord}
|
||||
@ -69,7 +69,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
<DropdownMenu id={menuId}>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'm'], ['m']]}
|
||||
shortcut={'Control+M, M'}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<DropdownItem
|
||||
@ -81,7 +81,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</WithShortcut>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'm'], ['e']]}
|
||||
shortcut={'Control+M, E'}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<DropdownItem
|
||||
@ -93,7 +93,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</WithShortcut>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'm'], ['c']]}
|
||||
shortcut={'Control+M, C'}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<DropdownItem
|
||||
@ -105,7 +105,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</WithShortcut>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 't'], ['t']]}
|
||||
shortcut={'Control+T, T'}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<DropdownItem
|
||||
@ -117,7 +117,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</WithShortcut>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 't'], ['e']]}
|
||||
shortcut={'Control+T, E'}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<DropdownItem
|
||||
@ -129,7 +129,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</WithShortcut>
|
||||
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 't'], ['m']]}
|
||||
shortcut={'Control+T, M'}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<DropdownItem
|
||||
@ -144,10 +144,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
</ButtonGroupItem>
|
||||
|
||||
<ButtonGroupItem>
|
||||
<WithShortcut
|
||||
shortcut={[['Control', 'Shift', 'X']]}
|
||||
let:createShortcut
|
||||
let:shortcutLabel>
|
||||
<WithShortcut shortcut={'Control+Shift+X'} let:createShortcut let:shortcutLabel>
|
||||
<IconButton
|
||||
tooltip={appendInParentheses(tr.editingHtmlEditor(), shortcutLabel)}
|
||||
on:click={onHtmlEdit}
|
||||
|
@ -66,8 +66,7 @@ function updateFocus(evt: FocusEvent) {
|
||||
|
||||
registerShortcut(
|
||||
() => document.addEventListener("focusin", updateFocus, { once: true }),
|
||||
[["Shift?", "Tab"]],
|
||||
true
|
||||
"Shift?+Tab"
|
||||
);
|
||||
|
||||
export function onKeyUp(evt: KeyboardEvent): void {
|
||||
|
@ -30,60 +30,60 @@ function modifiersToPlatformString(modifiers: string[]): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
const alphabeticPrefix = "Key";
|
||||
const numericPrefix = "Digit";
|
||||
const keyToCharacterMap = {
|
||||
Backslash: "\\",
|
||||
Backquote: "`",
|
||||
BracketLeft: "[",
|
||||
BrackerRight: "]",
|
||||
Quote: "'",
|
||||
Semicolon: ";",
|
||||
Minus: "-",
|
||||
Equal: "=",
|
||||
Comma: ",",
|
||||
Period: ".",
|
||||
Slash: "/",
|
||||
const keyCodeLookup = {
|
||||
Backspace: 8,
|
||||
Delete: 46,
|
||||
Tab: 9,
|
||||
Enter: 13,
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
F11: 122,
|
||||
F12: 123,
|
||||
"=": 187,
|
||||
"-": 189,
|
||||
"[": 219,
|
||||
"]": 221,
|
||||
"\\": 220,
|
||||
";": 186,
|
||||
"'": 222,
|
||||
",": 188,
|
||||
".": 190,
|
||||
"/": 191,
|
||||
"`": 192,
|
||||
};
|
||||
|
||||
function keyToPlatformString(key: string): string {
|
||||
if (key.startsWith(alphabeticPrefix)) {
|
||||
return key.slice(alphabeticPrefix.length);
|
||||
} else if (key.startsWith(numericPrefix)) {
|
||||
return key.slice(numericPrefix.length);
|
||||
} else if (Object.prototype.hasOwnProperty.call(keyToCharacterMap, key)) {
|
||||
return keyToCharacterMap[key];
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
function splitKeyCombinationString(keyCombinationString: string): string[][] {
|
||||
return keyCombinationString.split(", ").map((segment) => segment.split("+"));
|
||||
}
|
||||
|
||||
function toPlatformString(keyCombination: string[]): string {
|
||||
return (
|
||||
modifiersToPlatformString(
|
||||
modifiersAndKey.slice(0, -1).filter(isRequiredModifier)
|
||||
) + capitalize(keyToPlatformString(modifiersAndKey[modifiersAndKey.length - 1]))
|
||||
keyCombination.slice(0, -1).filter(isRequiredModifier)
|
||||
) + keyCombination[keyCombination.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
export function getPlatformString(keyCombination: string[][]): string {
|
||||
return keyCombination.map(toPlatformString).join(", ");
|
||||
export function getPlatformString(keyCombinationString: string): string {
|
||||
return splitKeyCombinationString(keyCombinationString)
|
||||
.map(toPlatformString)
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
function checkKey(
|
||||
getProperty: (event: KeyboardEvent) => string,
|
||||
event: KeyboardEvent,
|
||||
key: string
|
||||
): boolean {
|
||||
return getProperty(event) === key;
|
||||
function checkKey(event: KeyboardEvent, key: number): boolean {
|
||||
return event.which === key;
|
||||
}
|
||||
|
||||
const allModifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"];
|
||||
@ -122,15 +122,23 @@ function checkModifiers(event: KeyboardEvent, modifiers: string[]): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function check(
|
||||
getProperty: (event: KeyboardEvent) => string,
|
||||
event: KeyboardEvent,
|
||||
modifiersAndKey: string[]
|
||||
): boolean {
|
||||
return (
|
||||
checkModifiers(event, modifiersAndKey.slice(0, -1)) &&
|
||||
checkKey(getProperty, event, modifiersAndKey[modifiersAndKey.length - 1])
|
||||
);
|
||||
const check = (keyCode: number, modifiers: string[]) => (
|
||||
event: KeyboardEvent
|
||||
): boolean => {
|
||||
return checkKey(event, keyCode) && checkModifiers(event, modifiers);
|
||||
};
|
||||
|
||||
function keyToCode(key: string): number {
|
||||
return keyCodeLookup[key] || key.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
|
||||
function keyCombinationToCheck(
|
||||
keyCombination: string[]
|
||||
): (event: KeyboardEvent) => boolean {
|
||||
const keyCode = keyToCode(keyCombination[keyCombination.length - 1]);
|
||||
const modifiers = keyCombination.slice(0, -1);
|
||||
|
||||
return check(keyCode, modifiers);
|
||||
}
|
||||
|
||||
const GENERAL_KEY = 0;
|
||||
@ -139,19 +147,17 @@ const NUMPAD_KEY = 3;
|
||||
function innerShortcut(
|
||||
lastEvent: KeyboardEvent,
|
||||
callback: (event: KeyboardEvent) => void,
|
||||
getProperty: (event: KeyboardEvent) => string,
|
||||
...keyCombination: string[][]
|
||||
...checks: ((event: KeyboardEvent) => boolean)[]
|
||||
): void {
|
||||
let interval: number;
|
||||
|
||||
if (keyCombination.length === 0) {
|
||||
if (checks.length === 0) {
|
||||
callback(lastEvent);
|
||||
} else {
|
||||
const [nextKey, ...restKeys] = keyCombination;
|
||||
|
||||
const [nextCheck, ...restChecks] = checks;
|
||||
const handler = (event: KeyboardEvent): void => {
|
||||
if (check(getProperty, event, nextKey)) {
|
||||
innerShortcut(event, callback, getProperty, ...restKeys);
|
||||
if (nextCheck(event)) {
|
||||
innerShortcut(event, callback, ...restChecks);
|
||||
clearTimeout(interval);
|
||||
} else if (
|
||||
event.location === GENERAL_KEY ||
|
||||
@ -166,25 +172,17 @@ 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[][],
|
||||
useCode = false
|
||||
keyCombinationString: string
|
||||
): () => void {
|
||||
const [firstKey, ...restKeys] = keyCombination;
|
||||
const getProperty = useCode ? byCode : byKey;
|
||||
const [check, ...restChecks] = splitKeyCombinationString(keyCombinationString).map(
|
||||
keyCombinationToCheck
|
||||
);
|
||||
|
||||
const handler = (event: KeyboardEvent): void => {
|
||||
if (check(getProperty, event, firstKey)) {
|
||||
innerShortcut(event, callback, getProperty, ...restKeys);
|
||||
if (check(event)) {
|
||||
innerShortcut(event, callback, ...restChecks);
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user