Compare commits

..

7 Commits

Author SHA1 Message Date
bec48319ec 8.50.0 2018-09-18 12:43:24 +09:00
71a93b2b43 Refactor & Usability improvements 2018-09-18 12:42:56 +09:00
6ed3f9e414 リファクタリングなど 2018-09-18 12:34:41 +09:00
dc8f592c1f 8.49.0 2018-09-18 09:21:02 +09:00
f66c31c771 Improve usability & refactoring 2018-09-18 09:20:06 +09:00
55e2ae1408 Improve usability 2018-09-18 09:11:52 +09:00
19c72627fc Improve keyboard shortcut 2018-09-18 08:19:45 +09:00
5 changed files with 166 additions and 174 deletions

View File

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "8.48.0", "version": "8.50.0",
"clientVersion": "1.0.9876", "clientVersion": "1.0.9883",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,

View File

@ -1,27 +1,42 @@
import keyCode from './keycode'; import keyCode from './keycode';
import { concat } from '../../../prelude/array';
const getKeyMap = keymap => Object.keys(keymap).map(input => { type pattern = {
const result = {} as any; which: string[];
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
};
const { keyup, keydown } = keymap[input]; type action = {
patterns: pattern[];
input.split('+').forEach(keyName => { callback: Function;
switch (keyName.toLowerCase()) { };
case 'ctrl':
case 'alt': const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
case 'shift': const result = {
case 'meta': patterns: [],
result[keyName] = true; callback: callback
break; } as action;
default:
result.keyCode = keyCode(keyName); result.patterns = patterns.split('|').map(part => {
const pattern = {
which: []
} as pattern;
part.trim().split('+').forEach(key => {
key = key.trim().toLowerCase();
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());
} }
}); });
result.callback = { return pattern;
keydown: keydown || keymap[input], });
keyup
};
return result; return result;
}); });
@ -34,28 +49,40 @@ export default {
bind(el, binding) { bind(el, binding) {
el._hotkey_global = binding.modifiers.global === true; el._hotkey_global = binding.modifiers.global === true;
el._keymap = getKeyMap(binding.value); const actions = getKeyMap(binding.value);
el.dataset.reservedKeyCodes = el._keymap.map(key => `'${key.keyCode}'`).join(' '); // flatten
const reservedKeys = concat(concat(actions.map(a => a.patterns.map(p => p.which))));
el.dataset.reservedKeys = reservedKeys.map(key => `'${key}'`).join(' ');
el._keyHandler = e => { el._keyHandler = e => {
const reservedKeyCodes = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeyCodes || '' : ''; const key = e.code.toLowerCase();
const targetReservedKeys = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeys || '' : '';
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return; if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
for (const hotkey of el._keymap) { for (const action of actions) {
if (el._hotkey_global && reservedKeyCodes.includes(`'${e.keyCode}'`)) break; if (el._hotkey_global && targetReservedKeys.includes(`'${key}'`)) break;
const callback = hotkey.keyCode === e.keyCode && const matched = action.patterns.some(pattern => {
!!hotkey.ctrl === e.ctrlKey && let matched = pattern.which.includes(key);
!!hotkey.alt === e.altKey && if (pattern.ctrl && !e.ctrlKey) matched = false;
!!hotkey.shift === e.shiftKey && if (pattern.shift && !e.shiftKey) matched = false;
!!hotkey.meta === e.metaKey && if (pattern.alt && !e.altKey) matched = false;
hotkey.callback[e.type];
if (callback) { if (matched) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
callback(e); action.callback(e);
return true;
} else {
return false;
}
});
if (matched) {
break;
} }
} }
}; };

View File

@ -1,117 +1,20 @@
export default searchInput => { export default (input: string): string[] => {
// Keyboard Events if (Object.keys(aliases).some(a => a.toLowerCase() == input.toLowerCase())) {
if (searchInput && typeof searchInput === 'object') { const codes = aliases[input];
const hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; return Array.isArray(codes) ? codes : [codes];
if (hasKeyCode) { } else {
searchInput = hasKeyCode; return [input];
} }
}
// 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 = { export const aliases = {
'windows': 91, 'esc': 'Escape',
'': 16, 'enter': ['Enter', 'NumpadEnter'],
'': 18, 'up': 'ArrowUp',
'': 17, 'down': 'ArrowDown',
'': 91, 'left': 'ArrowLeft',
'ctl': 17, 'right': 'ArrowRight',
'control': 17, 'plus': ['NumpadAdd', 'Semicolon'],
'option': 18,
'pause': 19,
'break': 19,
'caps': 20,
'return': 13,
'escape': 27,
'spc': 32,
'pgup': 33,
'pgdn': 34,
'ins': 45,
'del': 46,
'cmd': 91
}; };
/*! /*!
@ -120,20 +23,11 @@ export const aliases = {
// lower case chars // lower case chars
for (let i = 97; i < 123; i++) { for (let i = 97; i < 123; i++) {
codes[String.fromCharCode(i)] = i - 32; const char = String.fromCharCode(i);
aliases[char] = `Key${char.toUpperCase()}`;
} }
// numbers // 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++) { for (let i = 0; i < 10; i++) {
codes['numpad ' + i] = i + 96; aliases[i] = [`Numpad${i}`, `Digit${i}`];
} }

View File

@ -3,7 +3,7 @@
<div class="backdrop" ref="backdrop" @click="close"></div> <div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ compact, big }" ref="popover"> <div class="popover" :class="{ compact, big }" ref="popover">
<p v-if="!compact">{{ title }}</p> <p v-if="!compact">{{ title }}</p>
<div> <div ref="buttons" :class="{ showFocus }">
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button> <button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button> <button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button>
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button> <button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button>
@ -50,18 +50,37 @@ export default Vue.extend({
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false
},
showFocus: {
type: Boolean,
required: false,
default: false
},
animation: {
type: Boolean,
required: false,
default: true
} }
}, },
data() { data() {
return { return {
title: placeholder title: placeholder,
focus: null
}; };
}, },
computed: { computed: {
keymap(): any { keymap(): any {
return { return {
'esc': this.close,
'enter|space|plus': this.choose,
'up': this.focusUp,
'right': this.focusRight,
'down': this.focusDown,
'left': this.focusLeft,
'1': () => this.react('like'), '1': () => this.react('like'),
'2': () => this.react('love'), '2': () => this.react('love'),
'3': () => this.react('laugh'), '3': () => this.react('laugh'),
@ -76,8 +95,20 @@ export default Vue.extend({
} }
}, },
watch: {
focus(i) {
this.$refs.buttons.childNodes[i].focus();
if (this.showFocus) {
this.title = this.$refs.buttons.childNodes[i].title;
}
}
},
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
this.focus = 0;
const popover = this.$refs.popover as any; const popover = this.$refs.popover as any;
const rect = this.source.getBoundingClientRect(); const rect = this.source.getBoundingClientRect();
@ -99,7 +130,7 @@ export default Vue.extend({
anime({ anime({
targets: this.$refs.backdrop, targets: this.$refs.backdrop,
opacity: 1, opacity: 1,
duration: 100, duration: this.animation ? 100 : 0,
easing: 'linear' easing: 'linear'
}); });
@ -107,7 +138,7 @@ export default Vue.extend({
targets: this.$refs.popover, targets: this.$refs.popover,
opacity: 1, opacity: 1,
scale: [0.5, 1], scale: [0.5, 1],
duration: 500 duration: this.animation ? 500 : 0
}); });
}); });
}, },
@ -137,7 +168,7 @@ export default Vue.extend({
anime({ anime({
targets: this.$refs.backdrop, targets: this.$refs.backdrop,
opacity: 0, opacity: 0,
duration: 200, duration: this.animation ? 200 : 0,
easing: 'linear' easing: 'linear'
}); });
@ -146,13 +177,33 @@ export default Vue.extend({
targets: this.$refs.popover, targets: this.$refs.popover,
opacity: 0, opacity: 0,
scale: 0.5, scale: 0.5,
duration: 200, duration: this.animation ? 200 : 0,
easing: 'easeInBack', easing: 'easeInBack',
complete: () => { complete: () => {
this.$emit('closed'); this.$emit('closed');
this.destroyDom(); this.destroyDom();
} }
}); });
},
focusUp() {
this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5);
},
focusDown() {
this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5);
},
focusRight() {
this.focus = this.focus == 9 ? 0 : (this.focus + 1);
},
focusLeft() {
this.focus = this.focus == 0 ? 9 : (this.focus - 1);
},
choose() {
this.$refs.buttons.childNodes[this.focus].click();
} }
} }
}); });
@ -238,6 +289,21 @@ root(isDark)
width 240px width 240px
text-align center text-align center
&.showFocus
> button:focus
z-index 1
&:after
content ""
pointer-events none
position absolute
top 0
right 0
bottom 0
left 0
border 2px solid rgba($theme-color, 0.3)
border-radius 4px
> button > button
padding 0 padding 0
width 40px width 40px

View File

@ -48,7 +48,7 @@
<button class="renoteButton" @click="renote" title="%i18n:@renote%"> <button class="renoteButton" @click="renote" title="%i18n:@renote%">
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
</button> </button>
<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:@add-reaction%"> <button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
</button> </button>
<button @click="menu" ref="menuButton"> <button @click="menu" ref="menuButton">
@ -113,13 +113,11 @@ export default Vue.extend({
computed: { computed: {
keymap(): any { keymap(): any {
return { return {
'r': this.reply, 'r|left': this.reply,
'a': this.react, 'a|plus': () => this.react(true),
'n': this.renote, 'n|right': this.renote,
'up': this.focusBefore, 'up|shift+tab': this.focusBefore,
'shift+tab': this.focusBefore, 'down|tab': this.focusAfter,
'down': this.focusAfter,
'tab': this.focusAfter,
}; };
}, },
@ -244,10 +242,13 @@ export default Vue.extend({
}).$once('closed', this.focus); }).$once('closed', this.focus);
}, },
react() { react(viaKeyboard = false) {
this.blur();
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
note: this.p note: this.p,
showFocus: viaKeyboard,
animation: !viaKeyboard
}).$once('closed', this.focus); }).$once('closed', this.focus);
}, },
@ -262,6 +263,10 @@ export default Vue.extend({
this.$el.focus(); this.$el.focus();
}, },
blur() {
this.$el.blur();
},
focusBefore() { focusBefore() {
focus(this.$el, e => e.previousElementSibling); focus(this.$el, e => e.previousElementSibling);
}, },