iceshrimp/packages/client/src/scripts/hotkey.ts
ThatOneCalculator 2aab2de38d refactor: 🎨 rome
2023-01-12 20:40:33 -08:00

105 lines
2.1 KiB
TypeScript

import keyCode from "./keycode";
type Callback = (ev: KeyboardEvent) => void;
type Keymap = Record<string, Callback>;
type Pattern = {
which: string[];
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
};
type Action = {
patterns: Pattern[];
callback: Callback;
allowRepeat: boolean;
};
const parseKeymap = (keymap: Keymap) =>
Object.entries(keymap).map(([patterns, callback]): Action => {
const result = {
patterns: [],
callback,
allowRepeat: true,
} as Action;
if (patterns.match(/^\(.*\)$/) !== null) {
result.allowRepeat = false;
patterns = patterns.slice(1, -1);
}
result.patterns = patterns.split("|").map((part) => {
const pattern = {
which: [],
ctrl: false,
alt: false,
shift: false,
} as Pattern;
const keys = part
.trim()
.split("+")
.map((x) => x.trim().toLowerCase());
for (const key of keys) {
switch (key) {
case "ctrl":
pattern.ctrl = true;
break;
case "alt":
pattern.alt = true;
break;
case "shift":
pattern.shift = true;
break;
default:
pattern.which = keyCode(key).map((k) => k.toLowerCase());
}
}
return pattern;
});
return result;
});
const ignoreElemens = ["input", "textarea"];
function match(ev: KeyboardEvent, patterns: Action["patterns"]): boolean {
const key = ev.code.toLowerCase();
return patterns.some(
(pattern) =>
pattern.which.includes(key) &&
pattern.ctrl === ev.ctrlKey &&
pattern.shift === ev.shiftKey &&
pattern.alt === ev.altKey &&
!ev.metaKey,
);
}
export const makeHotkey = (keymap: Keymap) => {
const actions = parseKeymap(keymap);
return (ev: KeyboardEvent) => {
if (document.activeElement) {
if (ignoreElemens.some((el) => document.activeElement!.matches(el)))
return;
if (document.activeElement.attributes["contenteditable"]) return;
}
for (const action of actions) {
const matched = match(ev, action.patterns);
if (matched) {
if (!action.allowRepeat && ev.repeat) return;
ev.preventDefault();
ev.stopPropagation();
action.callback(ev);
break;
}
}
};
};