anki/ts/lib/keys.ts
Henrik Giesel 8f8f3bd465
Insert symbols overlay (#2051)
* Add flag for enabling insert symbols feature

* Add symbols overlay directory

* Detect if :xy is inserted into editable

* Allow naive updating of overlay, and special handling of ':'

* First step towards better Virtual Element support

* Update floating to reference range on insert text

* Position SymbolsOverlay always on top or bottom

* Add a data-provider to emulate API

* Show correct suggestions in symbols overlay

* Rename to replacementLength

* Allow replacing via clicking in menu

* Optionally remove inline padding of Popover

* Hide Symbols overlay on blur of content editable

* Add specialKey to inputHandler and generalize how arrow movement is detected

- This way macOS users can use Ctrl-N to mean down, etc.

* Detect special key from within SymbolsOverlay

* Implement full backwards search while typing

* Allow navigating symbol menu and accepting with enter

* Add some entries to data-provider

* Satisfy eslint

* Generate symbolsTable from sources

* Use other github source, allow multiple names

In return, symbol must be unique

* Automatically scroll in symbols dropdown

* Use from npm packages rather than downloading from URL

* Remove console.log

* Remove print

* Add pointerDown event to input-handler

- so that SymbolsOverlay can reset on field click

* Make tab do the same as enter

* Make font a bit smaller but increase relative icon size

* Satisfy type requirement of handlerlist

* Revert changing default size of DropdownItems

* Remove some now unused code for bootstrap dropdowns
2022-09-10 18:46:59 +10:00

126 lines
3.7 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "./ftl";
import { isApplePlatform } from "./platform";
// those are the modifiers that Anki works with
export type Modifier = "Control" | "Alt" | "Shift" | "Meta";
const allModifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"];
const platformModifiers: string[] = isApplePlatform()
? ["Meta", "Alt", "Shift", "Control"]
: ["Control", "Alt", "Shift", "OS"];
function translateModifierToPlatform(modifier: Modifier): string {
return platformModifiers[allModifiers.indexOf(modifier)];
}
export function checkIfModifierKey(event: KeyboardEvent): boolean {
// At least the web view on Desktop Anki gives out the wrong values for
// `event.location`, which is why we do it like this.
let isInputKey = false;
for (const modifier of allModifiers) {
isInputKey ||= event.code.startsWith(modifier);
}
return isInputKey;
}
export function keyboardEventIsPrintableKey(event: KeyboardEvent): boolean {
return event.key.length === 1;
}
export const checkModifiers =
(required: Modifier[], optional: Modifier[] = []) =>
(event: KeyboardEvent): boolean => {
return allModifiers.reduce(
(
matches: boolean,
currentModifier: Modifier,
currentIndex: number,
): boolean =>
matches &&
(optional.includes(currentModifier as Modifier) ||
event.getModifierState(platformModifiers[currentIndex]) ===
required.includes(currentModifier)),
true,
);
};
const modifierPressed =
(modifier: Modifier) =>
(event: MouseEvent | KeyboardEvent): boolean => {
const translated = translateModifierToPlatform(modifier);
const state = event.getModifierState(translated);
return event.type === "keyup"
? state && (event as KeyboardEvent).key !== translated
: state;
};
export const controlPressed = modifierPressed("Control");
export const shiftPressed = modifierPressed("Shift");
export const altPressed = modifierPressed("Alt");
export const metaPressed = modifierPressed("Meta");
export function modifiersToPlatformString(modifiers: string[]): string {
const displayModifiers = isApplePlatform()
? ["^", "⌥", "⇧", "⌘"]
: [`${tr.keyboardCtrl()}+`, "Alt+", `${tr.keyboardShift()}+`, "Win+"];
let result = "";
for (const modifier of modifiers) {
result += displayModifiers[platformModifiers.indexOf(modifier)];
}
return result;
}
export function keyToPlatformString(key: string): string {
switch (key) {
case "Backspace":
return "⌫";
case "Delete":
return "⌦";
case "Escape":
return "⎋";
default:
return key;
}
}
export function isArrowLeft(event: KeyboardEvent): boolean {
if (event.code === "ArrowLeft") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyB";
}
export function isArrowRight(event: KeyboardEvent): boolean {
if (event.code === "ArrowRight") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyF";
}
export function isArrowUp(event: KeyboardEvent): boolean {
if (event.code === "ArrowUp") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyP";
}
export function isArrowDown(event: KeyboardEvent): boolean {
if (event.code === "ArrowDown") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyN";
}