misskey/src/client/scripts/hotkey.ts

117 lines
2.8 KiB
TypeScript
Raw Normal View History

import keyCode from './keycode';
import { concat } from '../../prelude/array';
2018-09-18 03:34:41 +00:00
type pattern = {
which: string[];
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
};
type action = {
patterns: pattern[];
callback: Function;
allowRepeat: boolean;
2018-09-18 03:34:41 +00:00
};
const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
const result = {
patterns: [],
callback: callback,
allowRepeat: true
2018-09-18 03:34:41 +00:00
} as action;
if (patterns.match(/^\(.*\)$/) !== null) {
result.allowRepeat = false;
patterns = patterns.slice(1, -1);
}
2018-09-18 03:34:41 +00:00
result.patterns = patterns.split('|').map(part => {
const pattern = {
which: [],
ctrl: false,
alt: false,
shift: false
2018-09-18 03:34:41 +00:00
} as pattern;
const keys = part.trim().split('+').map(x => x.trim().toLowerCase());
for (const key of keys) {
2018-09-18 03:34:41 +00:00
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());
2018-09-18 00:11:52 +00:00
}
}
2018-09-18 03:34:41 +00:00
return pattern;
});
return result;
});
const ignoreElemens = ['input', 'textarea'];
2018-10-19 16:45:31 +00:00
function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
const key = e.code.toLowerCase();
return patterns.some(pattern => pattern.which.includes(key) &&
2020-04-03 23:46:54 +00:00
pattern.ctrl === e.ctrlKey &&
pattern.shift === e.shiftKey &&
pattern.alt === e.altKey &&
!e.metaKey
2018-10-19 16:45:31 +00:00
);
}
export default {
install(Vue) {
Vue.directive('hotkey', {
bind(el, binding) {
el._hotkey_global = binding.modifiers.global === true;
2018-09-18 03:34:41 +00:00
const actions = getKeyMap(binding.value);
2018-09-18 03:42:56 +00:00
// flatten
2018-10-19 16:45:31 +00:00
const reservedKeys = concat(actions.map(a => a.patterns));
2018-09-18 03:34:41 +00:00
2018-10-19 21:42:19 +00:00
el._misskey_reservedKeys = reservedKeys;
2018-09-24 11:57:32 +00:00
el._keyHandler = (e: KeyboardEvent) => {
2018-10-19 21:42:19 +00:00
const targetReservedKeys = document.activeElement ? ((document.activeElement as any)._misskey_reservedKeys || []) : [];
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
if (document.activeElement && document.activeElement.attributes['contenteditable']) return;
2018-09-18 03:34:41 +00:00
for (const action of actions) {
2018-10-19 16:45:31 +00:00
const matched = match(e, action.patterns);
2018-09-18 03:34:41 +00:00
if (matched) {
if (!action.allowRepeat && e.repeat) return;
if (el._hotkey_global && match(e, targetReservedKeys)) return;
2018-10-19 16:45:31 +00:00
e.preventDefault();
e.stopPropagation();
action.callback(e);
2018-09-18 03:34:41 +00:00
break;
}
}
};
if (el._hotkey_global) {
document.addEventListener('keydown', el._keyHandler);
} else {
el.addEventListener('keydown', el._keyHandler);
}
},
unbind(el) {
if (el._hotkey_global) {
document.removeEventListener('keydown', el._keyHandler);
} else {
el.removeEventListener('keydown', el._keyHandler);
}
}
});
}
};