キーボードショートカットを強化するなど
This commit is contained in:
79
src/client/app/common/hotkey.ts
Normal file
79
src/client/app/common/hotkey.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import keyCode from './keycode';
|
||||
|
||||
const getKeyMap = keymap => Object.keys(keymap).map(input => {
|
||||
const result = {} as any;
|
||||
|
||||
const { keyup, keydown } = keymap[input];
|
||||
|
||||
input.split('+').forEach(keyName => {
|
||||
switch (keyName.toLowerCase()) {
|
||||
case 'ctrl':
|
||||
case 'alt':
|
||||
case 'shift':
|
||||
case 'meta':
|
||||
result[keyName] = true;
|
||||
break;
|
||||
default:
|
||||
result.keyCode = keyCode(keyName);
|
||||
}
|
||||
});
|
||||
|
||||
result.callback = {
|
||||
keydown: keydown || keymap[input],
|
||||
keyup
|
||||
};
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const ignoreElemens = ['input', 'textarea'];
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
Vue.directive('hotkey', {
|
||||
bind(el, binding) {
|
||||
el._hotkey_global = binding.modifiers.global === true;
|
||||
|
||||
el._keymap = getKeyMap(binding.value);
|
||||
|
||||
el.dataset.reservedKeyCodes = el._keymap.map(key => `'${key.keyCode}'`).join(' ');
|
||||
|
||||
el._keyHandler = e => {
|
||||
const reservedKeyCodes = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeyCodes || '' : '';
|
||||
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
|
||||
|
||||
for (const hotkey of el._keymap) {
|
||||
if (el._hotkey_global && reservedKeyCodes.includes(`'${e.keyCode}'`)) break;
|
||||
|
||||
const callback = hotkey.keyCode === e.keyCode &&
|
||||
!!hotkey.ctrl === e.ctrlKey &&
|
||||
!!hotkey.alt === e.altKey &&
|
||||
!!hotkey.shift === e.shiftKey &&
|
||||
!!hotkey.meta === e.metaKey &&
|
||||
hotkey.callback[e.type];
|
||||
|
||||
if (callback) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
callback(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
139
src/client/app/common/keycode.ts
Normal file
139
src/client/app/common/keycode.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
export default searchInput => {
|
||||
// Keyboard Events
|
||||
if (searchInput && typeof searchInput === 'object') {
|
||||
const hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
|
||||
if (hasKeyCode) {
|
||||
searchInput = hasKeyCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Numbers
|
||||
// if (typeof searchInput === 'number') {
|
||||
// return names[searchInput]
|
||||
// }
|
||||
|
||||
// Everything else (cast to string)
|
||||
const search = String(searchInput);
|
||||
|
||||
// check codes
|
||||
const foundNamedKeyCodes = codes[search.toLowerCase()];
|
||||
if (foundNamedKeyCodes) {
|
||||
return foundNamedKeyCodes;
|
||||
}
|
||||
|
||||
// check aliases
|
||||
const foundNamedKeyAliases = aliases[search.toLowerCase()];
|
||||
if (foundNamedKeyAliases) {
|
||||
return foundNamedKeyAliases;
|
||||
}
|
||||
|
||||
// weird character?
|
||||
if (search.length === 1) {
|
||||
return search.charCodeAt(0);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get by name
|
||||
*
|
||||
* exports.code['enter'] // => 13
|
||||
*/
|
||||
|
||||
export const codes = {
|
||||
'backspace': 8,
|
||||
'tab': 9,
|
||||
'enter': 13,
|
||||
'shift': 16,
|
||||
'ctrl': 17,
|
||||
'alt': 18,
|
||||
'pause/break': 19,
|
||||
'caps lock': 20,
|
||||
'esc': 27,
|
||||
'space': 32,
|
||||
'page up': 33,
|
||||
'page down': 34,
|
||||
'end': 35,
|
||||
'home': 36,
|
||||
'left': 37,
|
||||
'up': 38,
|
||||
'right': 39,
|
||||
'down': 40,
|
||||
// 'add': 43,
|
||||
'insert': 45,
|
||||
'delete': 46,
|
||||
'command': 91,
|
||||
'left command': 91,
|
||||
'right command': 93,
|
||||
'numpad *': 106,
|
||||
// 'numpad +': 107,
|
||||
'numpad +': 43,
|
||||
'numpad add': 43, // as a trick
|
||||
'numpad -': 109,
|
||||
'numpad .': 110,
|
||||
'numpad /': 111,
|
||||
'num lock': 144,
|
||||
'scroll lock': 145,
|
||||
'my computer': 182,
|
||||
'my calculator': 183,
|
||||
';': 186,
|
||||
'=': 187,
|
||||
',': 188,
|
||||
'-': 189,
|
||||
'.': 190,
|
||||
'/': 191,
|
||||
'`': 192,
|
||||
'[': 219,
|
||||
'\\': 220,
|
||||
']': 221,
|
||||
"'": 222
|
||||
};
|
||||
|
||||
// Helper aliases
|
||||
|
||||
export const aliases = {
|
||||
'windows': 91,
|
||||
'⇧': 16,
|
||||
'⌥': 18,
|
||||
'⌃': 17,
|
||||
'⌘': 91,
|
||||
'ctl': 17,
|
||||
'control': 17,
|
||||
'option': 18,
|
||||
'pause': 19,
|
||||
'break': 19,
|
||||
'caps': 20,
|
||||
'return': 13,
|
||||
'escape': 27,
|
||||
'spc': 32,
|
||||
'pgup': 33,
|
||||
'pgdn': 34,
|
||||
'ins': 45,
|
||||
'del': 46,
|
||||
'cmd': 91
|
||||
};
|
||||
|
||||
/*!
|
||||
* Programatically add the following
|
||||
*/
|
||||
|
||||
// lower case chars
|
||||
for (let i = 97; i < 123; i++) {
|
||||
codes[String.fromCharCode(i)] = i - 32;
|
||||
}
|
||||
|
||||
// numbers
|
||||
for (let i = 48; i < 58; i++) {
|
||||
codes[i - 48] = i;
|
||||
}
|
||||
|
||||
// function keys
|
||||
for (let i = 1; i < 13; i++) {
|
||||
codes['f' + i] = i + 111;
|
||||
}
|
||||
|
||||
// numpad keys
|
||||
for (let i = 0; i < 10; i++) {
|
||||
codes['numpad ' + i] = i + 96;
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-reaction-picker">
|
||||
<div class="mk-reaction-picker" v-hotkey.global="keymap">
|
||||
<div class="backdrop" ref="backdrop" @click="close"></div>
|
||||
<div class="popover" :class="{ compact, big }" ref="popover">
|
||||
<p v-if="!compact">{{ title }}</p>
|
||||
@@ -31,28 +31,51 @@ export default Vue.extend({
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
|
||||
source: {
|
||||
required: true
|
||||
},
|
||||
|
||||
compact: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
cb: {
|
||||
required: false
|
||||
},
|
||||
|
||||
big: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
title: placeholder
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'1': () => this.react('like'),
|
||||
'2': () => this.react('love'),
|
||||
'3': () => this.react('laugh'),
|
||||
'4': () => this.react('hmm'),
|
||||
'5': () => this.react('surprise'),
|
||||
'6': () => this.react('congrats'),
|
||||
'7': () => this.react('angry'),
|
||||
'8': () => this.react('confused'),
|
||||
'9': () => this.react('rip'),
|
||||
'0': () => this.react('pudding'),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const popover = this.$refs.popover as any;
|
||||
@@ -88,6 +111,7 @@ export default Vue.extend({
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
react(reaction) {
|
||||
(this as any).api('notes/reactions/create', {
|
||||
@@ -95,15 +119,19 @@ export default Vue.extend({
|
||||
reaction: reaction
|
||||
}).then(() => {
|
||||
if (this.cb) this.cb();
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
onMouseover(e) {
|
||||
this.title = e.target.title;
|
||||
},
|
||||
|
||||
onMouseout(e) {
|
||||
this.title = placeholder;
|
||||
},
|
||||
|
||||
close() {
|
||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||
anime({
|
||||
@@ -120,7 +148,10 @@ export default Vue.extend({
|
||||
scale: 0.5,
|
||||
duration: 200,
|
||||
easing: 'easeInBack',
|
||||
complete: () => this.destroyDom()
|
||||
complete: () => {
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user