mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 15:46:29 +08:00
3219b82827
This PR fixes #14755 bug <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed keyboard shortcut handling to properly distinguish between locale input characters and actual shortcuts when using Alt+Meta key combinations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
140 lines
4.0 KiB
TypeScript
140 lines
4.0 KiB
TypeScript
import { IS_MAC } from '@blocksuite/global/env';
|
||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||
import { base, keyName } from 'w3c-keyname';
|
||
|
||
import type { UIEventHandler } from './base.js';
|
||
|
||
function normalizeKeyName(name: string) {
|
||
const parts = name.split(/-(?!$)/);
|
||
let result = parts.at(-1);
|
||
if (result === 'Space') {
|
||
result = ' ';
|
||
}
|
||
let alt, ctrl, shift, meta;
|
||
parts.slice(0, -1).forEach(mod => {
|
||
if (/^(cmd|meta|m)$/i.test(mod)) {
|
||
meta = true;
|
||
return;
|
||
}
|
||
if (/^a(lt)?$/i.test(mod)) {
|
||
alt = true;
|
||
return;
|
||
}
|
||
if (/^(c|ctrl|control)$/i.test(mod)) {
|
||
ctrl = true;
|
||
return;
|
||
}
|
||
if (/^s(hift)?$/i.test(mod)) {
|
||
shift = true;
|
||
return;
|
||
}
|
||
if (/^mod$/i.test(mod)) {
|
||
if (IS_MAC) {
|
||
meta = true;
|
||
} else {
|
||
ctrl = true;
|
||
}
|
||
return;
|
||
}
|
||
|
||
throw new BlockSuiteError(
|
||
ErrorCode.EventDispatcherError,
|
||
'Unrecognized modifier name: ' + mod
|
||
);
|
||
});
|
||
if (alt) result = 'Alt-' + result;
|
||
if (ctrl) result = 'Ctrl-' + result;
|
||
if (meta) result = 'Meta-' + result;
|
||
if (shift) result = 'Shift-' + result;
|
||
return result as string;
|
||
}
|
||
|
||
function modifiers(name: string, event: KeyboardEvent, shift = true) {
|
||
if (event.altKey) name = 'Alt-' + name;
|
||
if (event.ctrlKey) name = 'Ctrl-' + name;
|
||
if (event.metaKey) name = 'Meta-' + name;
|
||
if (shift && event.shiftKey) name = 'Shift-' + name;
|
||
return name;
|
||
}
|
||
|
||
function normalize(map: Record<string, UIEventHandler>) {
|
||
const copy: Record<string, UIEventHandler> = Object.create(null);
|
||
for (const prop in map) copy[normalizeKeyName(prop)] = map[prop];
|
||
return copy;
|
||
}
|
||
|
||
export function bindKeymap(
|
||
bindings: Record<string, UIEventHandler>
|
||
): UIEventHandler {
|
||
const map = normalize(bindings);
|
||
return ctx => {
|
||
const state = ctx.get('keyboardState');
|
||
const event = state.raw;
|
||
const name = keyName(event);
|
||
const direct = map[modifiers(name, event)];
|
||
if (direct && direct(ctx)) {
|
||
return true;
|
||
}
|
||
if (name.length !== 1 || name === ' ') {
|
||
return false;
|
||
}
|
||
|
||
if (event.shiftKey) {
|
||
const noShift = map[modifiers(name, event, false)];
|
||
if (noShift && noShift(ctx)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// For non-standard keyboards, fallback to keyCode only when modifier keys are pressed.
|
||
// Do NOT fallback when the key produces a non-ASCII character (e.g., Cyrillic 'х' on Russian keyboard),
|
||
// because the user intends to type that character, not trigger a shortcut bound to the physical key.
|
||
// See: https://github.com/toeverything/AFFiNE/issues/14059
|
||
const hasModifier =
|
||
event.shiftKey || event.altKey || event.ctrlKey || event.metaKey;
|
||
const baseName = base[event.keyCode];
|
||
const isSingleAscii = name.length === 1 && name.charCodeAt(0) <= 0x7e;
|
||
const isAltInputChar =
|
||
event.altKey && !event.ctrlKey && !event.metaKey && !isSingleAscii;
|
||
// Keep supporting existing Alt+digit shortcuts (e.g. Alt-0/1/2 in edgeless)
|
||
// while preventing Alt-based locale input characters from triggering letter shortcuts.
|
||
const isDigitBaseKey =
|
||
baseName != null && baseName.length === 1 && /[0-9]/.test(baseName);
|
||
if (
|
||
hasModifier &&
|
||
baseName &&
|
||
baseName !== name &&
|
||
!(isAltInputChar && !isDigitBaseKey)
|
||
) {
|
||
const fromCode = map[modifiers(baseName, event)];
|
||
if (fromCode && fromCode(ctx)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
};
|
||
}
|
||
|
||
// In some IME of Android like, the keypress event dose not contain
|
||
// the information about what key is pressed. See
|
||
// https://stackoverflow.com/a/68188679
|
||
// https://stackoverflow.com/a/66724830
|
||
export function androidBindKeymapPatch(
|
||
bindings: Record<string, UIEventHandler>
|
||
): UIEventHandler {
|
||
return ctx => {
|
||
const event = ctx.get('defaultState').event;
|
||
if (!(event instanceof InputEvent)) return;
|
||
|
||
if (
|
||
event.inputType === 'deleteContentBackward' &&
|
||
'Backspace' in bindings
|
||
) {
|
||
return bindings['Backspace'](ctx);
|
||
}
|
||
|
||
return false;
|
||
};
|
||
}
|