summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts/hotkey.ts
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-03-09 14:28:01 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2025-03-09 14:28:01 +0900
commitbe7e3b9a0cb81b78a744993fef2fa2fd2833fa9c (patch)
treec82e18ce93ec0a24c57d7e36eb54a09266b3a25b /packages/frontend/src/scripts/hotkey.ts
parentenhnace(frontend): 文字列比較のためのローマナイズを強化(... (diff)
downloadsharkey-be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c.tar.gz
sharkey-be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c.tar.bz2
sharkey-be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c.zip
refactor(frontend): scripts -> utility
Diffstat (limited to 'packages/frontend/src/scripts/hotkey.ts')
-rw-r--r--packages/frontend/src/scripts/hotkey.ts172
1 files changed, 0 insertions, 172 deletions
diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts
deleted file mode 100644
index 04fb235694..0000000000
--- a/packages/frontend/src/scripts/hotkey.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import { getHTMLElementOrNull } from "@/scripts/get-dom-node-or-null.js";
-
-//#region types
-export type Keymap = Record<string, CallbackFunction | CallbackObject>;
-
-type CallbackFunction = (ev: KeyboardEvent) => unknown;
-
-type CallbackObject = {
- callback: CallbackFunction;
- allowRepeat?: boolean;
-};
-
-type Pattern = {
- which: string[];
- ctrl: boolean;
- alt: boolean;
- shift: boolean;
-};
-
-type Action = {
- patterns: Pattern[];
- callback: CallbackFunction;
- options: Required<Omit<CallbackObject, 'callback'>>;
-};
-//#endregion
-
-//#region consts
-const KEY_ALIASES = {
- 'esc': 'Escape',
- 'enter': 'Enter',
- 'space': ' ',
- 'up': 'ArrowUp',
- 'down': 'ArrowDown',
- 'left': 'ArrowLeft',
- 'right': 'ArrowRight',
- 'plus': ['+', ';'],
-};
-
-const MODIFIER_KEYS = ['ctrl', 'alt', 'shift'];
-
-const IGNORE_ELEMENTS = ['input', 'textarea'];
-//#endregion
-
-//#region store
-let latestHotkey: Pattern & { callback: CallbackFunction } | null = null;
-//#endregion
-
-//#region impl
-export const makeHotkey = (keymap: Keymap) => {
- const actions = parseKeymap(keymap);
- return (ev: KeyboardEvent) => {
- if ('pswp' in window && window.pswp != null) return;
- if (document.activeElement != null) {
- if (IGNORE_ELEMENTS.includes(document.activeElement.tagName.toLowerCase())) return;
- if (getHTMLElementOrNull(document.activeElement)?.isContentEditable) return;
- }
- for (const action of actions) {
- if (matchPatterns(ev, action)) {
- ev.preventDefault();
- ev.stopPropagation();
- action.callback(ev);
- storePattern(ev, action.callback);
- }
- }
- };
-};
-
-const parseKeymap = (keymap: Keymap) => {
- return Object.entries(keymap).map(([rawPatterns, rawCallback]) => {
- const patterns = parsePatterns(rawPatterns);
- const callback = parseCallback(rawCallback);
- const options = parseOptions(rawCallback);
- return { patterns, callback, options } as const satisfies Action;
- });
-};
-
-const parsePatterns = (rawPatterns: keyof Keymap) => {
- return rawPatterns.split('|').map(part => {
- const keys = part.split('+').map(trimLower);
- const which = parseKeyCode(keys.findLast(x => !MODIFIER_KEYS.includes(x)));
- const ctrl = keys.includes('ctrl');
- const alt = keys.includes('alt');
- const shift = keys.includes('shift');
- return { which, ctrl, alt, shift } as const satisfies Pattern;
- });
-};
-
-const parseCallback = (rawCallback: Keymap[keyof Keymap]) => {
- if (typeof rawCallback === 'object') {
- return rawCallback.callback;
- }
- return rawCallback;
-};
-
-const parseOptions = (rawCallback: Keymap[keyof Keymap]) => {
- const defaultOptions = {
- allowRepeat: false,
- } as const satisfies Action['options'];
- if (typeof rawCallback === 'object') {
- const { callback, ...rawOptions } = rawCallback;
- const options = { ...defaultOptions, ...rawOptions };
- return { ...options } as const satisfies Action['options'];
- }
- return { ...defaultOptions } as const satisfies Action['options'];
-};
-
-const matchPatterns = (ev: KeyboardEvent, action: Action) => {
- const { patterns, options, callback } = action;
- if (ev.repeat && !options.allowRepeat) return false;
- const key = ev.key.toLowerCase();
- return patterns.some(({ which, ctrl, shift, alt }) => {
- if (
- options.allowRepeat === false &&
- latestHotkey != null &&
- latestHotkey.which.includes(key) &&
- latestHotkey.ctrl === ctrl &&
- latestHotkey.alt === alt &&
- latestHotkey.shift === shift &&
- latestHotkey.callback === callback
- ) {
- return false;
- }
- if (!which.includes(key)) return false;
- if (ctrl !== (ev.ctrlKey || ev.metaKey)) return false;
- if (alt !== ev.altKey) return false;
- if (shift !== ev.shiftKey) return false;
- return true;
- });
-};
-
-let lastHotKeyStoreTimer: number | null = null;
-
-const storePattern = (ev: KeyboardEvent, callback: CallbackFunction) => {
- if (lastHotKeyStoreTimer != null) {
- clearTimeout(lastHotKeyStoreTimer);
- }
-
- latestHotkey = {
- which: [ev.key.toLowerCase()],
- ctrl: ev.ctrlKey || ev.metaKey,
- alt: ev.altKey,
- shift: ev.shiftKey,
- callback,
- };
-
- lastHotKeyStoreTimer = window.setTimeout(() => {
- latestHotkey = null;
- }, 500);
-};
-
-const parseKeyCode = (input?: string | null) => {
- if (input == null) return [];
- const raw = getValueByKey(KEY_ALIASES, input);
- if (raw == null) return [input];
- if (typeof raw === 'string') return [trimLower(raw)];
- return raw.map(trimLower);
-};
-
-const getValueByKey = <
- T extends Record<keyof any, unknown>,
- K extends keyof T | keyof any,
- R extends K extends keyof T ? T[K] : T[keyof T] | undefined,
->(obj: T, key: K) => {
- return obj[key] as R;
-};
-
-const trimLower = (str: string) => str.trim().toLowerCase();
-//#endregion