summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-09-18 05:35:06 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-09-18 05:35:06 +0900
commit31ce3aa31296a1809cabc02f1ed6c92b328f5b3e (patch)
treeffb15463ddcaad74c4e17076640cd9bf9082ab2d /src/client
parentMerge branch 'develop' of https://github.com/syuilo/misskey into develop (diff)
downloadsharkey-31ce3aa31296a1809cabc02f1ed6c92b328f5b3e.tar.gz
sharkey-31ce3aa31296a1809cabc02f1ed6c92b328f5b3e.tar.bz2
sharkey-31ce3aa31296a1809cabc02f1ed6c92b328f5b3e.zip
キーボードショートカットを強化するなど
Diffstat (limited to 'src/client')
-rw-r--r--src/client/app/common/hotkey.ts79
-rw-r--r--src/client/app/common/keycode.ts139
-rw-r--r--src/client/app/common/views/components/reaction-picker.vue35
-rw-r--r--src/client/app/desktop/views/components/choose-file-from-drive-window.vue2
-rw-r--r--src/client/app/desktop/views/components/choose-folder-from-drive-window.vue2
-rw-r--r--src/client/app/desktop/views/components/drive-window.vue2
-rw-r--r--src/client/app/desktop/views/components/followers-window.vue2
-rw-r--r--src/client/app/desktop/views/components/following-window.vue2
-rw-r--r--src/client/app/desktop/views/components/game-window.vue2
-rw-r--r--src/client/app/desktop/views/components/home.vue4
-rw-r--r--src/client/app/desktop/views/components/input-dialog.vue2
-rw-r--r--src/client/app/desktop/views/components/messaging-room-window.vue2
-rw-r--r--src/client/app/desktop/views/components/messaging-window.vue2
-rw-r--r--src/client/app/desktop/views/components/notes.note.vue63
-rw-r--r--src/client/app/desktop/views/components/notes.vue4
-rw-r--r--src/client/app/desktop/views/components/post-form-window.vue6
-rw-r--r--src/client/app/desktop/views/components/progress-dialog.vue2
-rw-r--r--src/client/app/desktop/views/components/received-follow-requests-window.vue2
-rw-r--r--src/client/app/desktop/views/components/renote-form-window.vue33
-rw-r--r--src/client/app/desktop/views/components/settings-window.vue2
-rw-r--r--src/client/app/desktop/views/components/timeline.core.vue11
-rw-r--r--src/client/app/desktop/views/components/timeline.vue4
-rw-r--r--src/client/app/desktop/views/components/ui.vue35
-rw-r--r--src/client/app/desktop/views/components/user-lists-window.vue2
-rw-r--r--src/client/app/desktop/views/components/window.vue2
-rw-r--r--src/client/app/desktop/views/pages/home.vue12
-rw-r--r--src/client/app/init.ts2
27 files changed, 355 insertions, 100 deletions
diff --git a/src/client/app/common/hotkey.ts b/src/client/app/common/hotkey.ts
new file mode 100644
index 0000000000..10cbeea543
--- /dev/null
+++ b/src/client/app/common/hotkey.ts
@@ -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);
+ }
+ }
+ });
+ }
+};
diff --git a/src/client/app/common/keycode.ts b/src/client/app/common/keycode.ts
new file mode 100644
index 0000000000..c5ea6cb484
--- /dev/null
+++ b/src/client/app/common/keycode.ts
@@ -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;
+}
diff --git a/src/client/app/common/views/components/reaction-picker.vue b/src/client/app/common/views/components/reaction-picker.vue
index a4828c987b..58985658c6 100644
--- a/src/client/app/common/views/components/reaction-picker.vue
+++ b/src/client/app/common/views/components/reaction-picker.vue
@@ -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();
+ }
});
}
}
diff --git a/src/client/app/desktop/views/components/choose-file-from-drive-window.vue b/src/client/app/desktop/views/components/choose-file-from-drive-window.vue
index b894f0e109..933d31f299 100644
--- a/src/client/app/desktop/views/components/choose-file-from-drive-window.vue
+++ b/src/client/app/desktop/views/components/choose-file-from-drive-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
+<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
<span slot="header">
<span v-html="title" :class="$style.title"></span>
<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
diff --git a/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue b/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue
index 0c4643fdcb..03d6fd1636 100644
--- a/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue
+++ b/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
+<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
<span slot="header">
<span v-html="title" :class="$style.title"></span>
</span>
diff --git a/src/client/app/desktop/views/components/drive-window.vue b/src/client/app/desktop/views/components/drive-window.vue
index 1f45b64324..191579538d 100644
--- a/src/client/app/desktop/views/components/drive-window.vue
+++ b/src/client/app/desktop/views/components/drive-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" @closed="$destroy" width="800px" height="500px" :popout-url="popout">
+<mk-window ref="window" @closed="destroyDom" width="800px" height="500px" :popout-url="popout">
<template slot="header">
<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> %i18n:@used%</p>
<span :class="$style.title">%fa:cloud%%i18n:@drive%</span>
diff --git a/src/client/app/desktop/views/components/followers-window.vue b/src/client/app/desktop/views/components/followers-window.vue
index fdab7bc1ce..d5214adb2f 100644
--- a/src/client/app/desktop/views/components/followers-window.vue
+++ b/src/client/app/desktop/views/components/followers-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window width="400px" height="550px" @closed="$destroy">
+<mk-window width="400px" height="550px" @closed="destroyDom">
<span slot="header" :class="$style.header">
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
</span>
diff --git a/src/client/app/desktop/views/components/following-window.vue b/src/client/app/desktop/views/components/following-window.vue
index 7cca833a82..aa9f2bde7b 100644
--- a/src/client/app/desktop/views/components/following-window.vue
+++ b/src/client/app/desktop/views/components/following-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window width="400px" height="550px" @closed="$destroy">
+<mk-window width="400px" height="550px" @closed="destroyDom">
<span slot="header" :class="$style.header">
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
</span>
diff --git a/src/client/app/desktop/views/components/game-window.vue b/src/client/app/desktop/views/components/game-window.vue
index 7c6cb9cd40..594eae58f8 100644
--- a/src/client/app/desktop/views/components/game-window.vue
+++ b/src/client/app/desktop/views/components/game-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
+<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
<mk-reversi :class="$style.content" @gamed="g => game = g"/>
</mk-window>
diff --git a/src/client/app/desktop/views/components/home.vue b/src/client/app/desktop/views/components/home.vue
index d45cc82e13..79c9a9a517 100644
--- a/src/client/app/desktop/views/components/home.vue
+++ b/src/client/app/desktop/views/components/home.vue
@@ -237,6 +237,10 @@ export default Vue.extend({
warp(date) {
(this.$refs.tl as any).warp(date);
+ },
+
+ focus() {
+ (this.$refs.tl as any).focus();
}
}
});
diff --git a/src/client/app/desktop/views/components/input-dialog.vue b/src/client/app/desktop/views/components/input-dialog.vue
index e2cf4e48fd..cf7c09ea56 100644
--- a/src/client/app/desktop/views/components/input-dialog.vue
+++ b/src/client/app/desktop/views/components/input-dialog.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" is-modal width="500px" @before-close="beforeClose" @closed="$destroy">
+<mk-window ref="window" is-modal width="500px" @before-close="beforeClose" @closed="destroyDom">
<span slot="header" :class="$style.header">
%fa:i-cursor%{{ title }}
</span>
diff --git a/src/client/app/desktop/views/components/messaging-room-window.vue b/src/client/app/desktop/views/components/messaging-room-window.vue
index 41b421b0e7..3706377607 100644
--- a/src/client/app/desktop/views/components/messaging-room-window.vue
+++ b/src/client/app/desktop/views/components/messaging-room-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
+<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title% {{ user | userName }}</span>
<mk-messaging-room :user="user" :class="$style.content"/>
</mk-window>
diff --git a/src/client/app/desktop/views/components/messaging-window.vue b/src/client/app/desktop/views/components/messaging-window.vue
index 9580c5061d..a8f0fc68b9 100644
--- a/src/client/app/desktop/views/components/messaging-window.vue
+++ b/src/client/app/desktop/views/components/messaging-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" width="500px" height="560px" @closed="$destroy">
+<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title%</span>
<mk-messaging :class="$style.content" @navigate="navigate"/>
</mk-window>
diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue
index 46a866f9a7..fadf47e628 100644
--- a/src/client/app/desktop/views/components/notes.note.vue
+++ b/src/client/app/desktop/views/components/notes.note.vue
@@ -1,5 +1,5 @@
<template>
-<div class="note" tabindex="-1" :title="title" @keydown="onKeydown">
+<div class="note" tabindex="-1" v-hotkey="keymap" :title="title">
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
<x-sub :note="p.reply"/>
</div>
@@ -111,6 +111,18 @@ export default Vue.extend({
},
computed: {
+ keymap(): any {
+ return {
+ 'r': this.reply,
+ 'a': this.react,
+ 'n': this.renote,
+ 'up': this.focusBefore,
+ 'shift+tab': this.focusBefore,
+ 'down': this.focusAfter,
+ 'tab': this.focusAfter,
+ };
+ },
+
isRenote(): boolean {
return (this.note.renote &&
this.note.text == null &&
@@ -223,64 +235,39 @@ export default Vue.extend({
reply() {
(this as any).os.new(MkPostFormWindow, {
reply: this.p
- });
+ }).$once('closed', this.focus);
},
renote() {
(this as any).os.new(MkRenoteFormWindow, {
note: this.p
- });
+ }).$once('closed', this.focus);
},
react() {
(this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton,
note: this.p
- });
+ }).$once('closed', this.focus);
},
menu() {
(this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton,
note: this.p
- });
+ }).$once('closed', this.focus);
},
- onKeydown(e) {
- let shouldBeCancel = true;
-
- switch (true) {
- case e.which == 38: // [↑]
- case e.which == 74: // [j]
- case e.which == 9 && e.shiftKey: // [Shift] + [Tab]
- focus(this.$el, e => e.previousElementSibling);
- break;
-
- case e.which == 40: // [↓]
- case e.which == 75: // [k]
- case e.which == 9: // [Tab]
- focus(this.$el, e => e.nextElementSibling);
- break;
-
- case e.which == 81: // [q]
- case e.which == 69: // [e]
- this.renote();
- break;
-
- case e.which == 70: // [f]
- case e.which == 76: // [l]
- //this.like();
- break;
-
- case e.which == 82: // [r]
- this.reply();
- break;
+ focus() {
+ this.$el.focus();
+ },
- default:
- shouldBeCancel = false;
- }
+ focusBefore() {
+ focus(this.$el, e => e.previousElementSibling);
+ },
- if (shouldBeCancel) e.preventDefault();
+ focusAfter() {
+ focus(this.$el, e => e.nextElementSibling);
}
}
});
diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue
index ec9aa285d0..469f62c080 100644
--- a/src/client/app/desktop/views/components/notes.vue
+++ b/src/client/app/desktop/views/components/notes.vue
@@ -12,7 +12,7 @@
<!-- トランジションを有効にするとなぜかメモリリークする -->
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
<template v-for="(note, i) in _notes">
- <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
+ <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" ref="note"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
<span>%fa:angle-up%{{ note._datetext }}</span>
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
@@ -89,7 +89,7 @@ export default Vue.extend({
},
focus() {
- (this.$el as any).children[0].focus();
+ (this.$refs.note as any)[0].focus();
},
onNoteUpdated(i, note) {
diff --git a/src/client/app/desktop/views/components/post-form-window.vue b/src/client/app/desktop/views/components/post-form-window.vue
index a88c96d1bf..ade84f6bb9 100644
--- a/src/client/app/desktop/views/components/post-form-window.vue
+++ b/src/client/app/desktop/views/components/post-form-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window class="mk-post-form-window" ref="window" is-modal @closed="$destroy">
+<mk-window class="mk-post-form-window" ref="window" is-modal @closed="onWindowClosed">
<span slot="header" class="mk-post-form-window--header">
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
<span v-if="!reply">%i18n:@note%</span>
@@ -53,6 +53,10 @@ export default Vue.extend({
},
onPosted() {
(this.$refs.window as any).close();
+ },
+ onWindowClosed() {
+ this.$emit('closed');
+ this.destroyDom();
}
}
});
diff --git a/src/client/app/desktop/views/components/progress-dialog.vue b/src/client/app/desktop/views/components/progress-dialog.vue
index 2f59733d99..cc25ba8e30 100644
--- a/src/client/app/desktop/views/components/progress-dialog.vue
+++ b/src/client/app/desktop/views/components/progress-dialog.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="$destroy">
+<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="destroyDom">
<span slot="header">{{ title }}<mk-ellipsis/></span>
<div :class="$style.body">
<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
diff --git a/src/client/app/desktop/views/components/received-follow-requests-window.vue b/src/client/app/desktop/views/components/received-follow-requests-window.vue
index 26b7ec2590..d8a94f6cbe 100644
--- a/src/client/app/desktop/views/components/received-follow-requests-window.vue
+++ b/src/client/app/desktop/views/components/received-follow-requests-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
+<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
<span slot="header">%fa:envelope R% %i18n:@title%</span>
<div class="slpqaxdoxhvglersgjukmvizkqbmbokc" :data-darkmode="$store.state.device.darkmode">
diff --git a/src/client/app/desktop/views/components/renote-form-window.vue b/src/client/app/desktop/views/components/renote-form-window.vue
index df9d2f7fc7..6c9cb59d4a 100644
--- a/src/client/app/desktop/views/components/renote-form-window.vue
+++ b/src/client/app/desktop/views/components/renote-form-window.vue
@@ -1,7 +1,7 @@
<template>
-<mk-window ref="window" is-modal @closed="$destroy">
+<mk-window ref="window" is-modal @closed="onWindowClosed">
<span slot="header" :class="$style.header">%fa:retweet%%i18n:@title%</span>
- <mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/>
+ <mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled" v-hotkey.global="keymap"/>
</mk-window>
</template>
@@ -10,25 +10,32 @@ import Vue from 'vue';
export default Vue.extend({
props: ['note'],
- mounted() {
- document.addEventListener('keydown', this.onDocumentKeydown);
- },
- beforeDestroy() {
- document.removeEventListener('keydown', this.onDocumentKeydown);
+
+ computed: {
+ keymap(): any {
+ return {
+ 'esc': this.close,
+ 'ctrl+enter': this.post
+ };
+ }
},
+
methods: {
- onDocumentKeydown(e) {
- if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
- if (e.which == 27) { // Esc
- (this.$refs.window as any).close();
- }
- }
+ post() {
+ (this.$refs.form as any).ok();
+ },
+ close() {
+ (this.$refs.window as any).close();
},
onPosted() {
(this.$refs.window as any).close();
},
onCanceled() {
(this.$refs.window as any).close();
+ },
+ onWindowClosed() {
+ this.$emit('closed');
+ this.destroyDom();
}
}
});
diff --git a/src/client/app/desktop/views/components/settings-window.vue b/src/client/app/desktop/views/components/settings-window.vue
index b4cc570282..4247717748 100644
--- a/src/client/app/desktop/views/components/settings-window.vue
+++ b/src/client/app/desktop/views/components/settings-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" is-modal width="700px" height="550px" @closed="$destroy">
+<mk-window ref="window" is-modal width="700px" height="550px" @closed="destroyDom">
<span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span>
<mk-settings :initial-page="initialPage" @done="close"/>
</mk-window>
diff --git a/src/client/app/desktop/views/components/timeline.core.vue b/src/client/app/desktop/views/components/timeline.core.vue
index c8aa36f171..ff73bde95c 100644
--- a/src/client/app/desktop/views/components/timeline.core.vue
+++ b/src/client/app/desktop/views/components/timeline.core.vue
@@ -152,14 +152,11 @@ export default Vue.extend({
});
}
- document.addEventListener('keydown', this.onKeydown);
-
this.fetch();
},
beforeDestroy() {
this.$emit('beforeDestroy');
- document.removeEventListener('keydown', this.onKeydown);
},
methods: {
@@ -212,14 +209,6 @@ export default Vue.extend({
warp(date) {
this.date = date;
this.fetch();
- },
-
- onKeydown(e) {
- if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
- if (e.which == 84) { // t
- this.focus();
- }
- }
}
}
});
diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue
index ccc35f95ff..9f421a68ed 100644
--- a/src/client/app/desktop/views/components/timeline.vue
+++ b/src/client/app/desktop/views/components/timeline.vue
@@ -92,6 +92,10 @@ export default Vue.extend({
});
},
+ focus() {
+ (this.$refs.tl as any).focus();
+ },
+
warp(date) {
(this.$refs.tl as any).warp(date);
},
diff --git a/src/client/app/desktop/views/components/ui.vue b/src/client/app/desktop/views/components/ui.vue
index d410c3d980..a28cb3029e 100644
--- a/src/client/app/desktop/views/components/ui.vue
+++ b/src/client/app/desktop/views/components/ui.vue
@@ -1,5 +1,5 @@
<template>
-<div class="mk-ui" :style="style">
+<div class="mk-ui" :style="style" v-hotkey.global="keymap">
<x-header class="header" v-show="!zenMode"/>
<div class="content">
<slot></slot>
@@ -16,11 +16,13 @@ export default Vue.extend({
components: {
XHeader
},
+
data() {
return {
zenMode: false
};
},
+
computed: {
style(): any {
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
@@ -28,27 +30,24 @@ export default Vue.extend({
backgroundColor: this.$store.state.i.wallpaperColor && this.$store.state.i.wallpaperColor.length == 3 ? `rgb(${ this.$store.state.i.wallpaperColor.join(',') })` : null,
backgroundImage: `url(${ this.$store.state.i.wallpaperUrl })`
};
+ },
+
+ keymap(): any {
+ return {
+ 'p': this.post,
+ 'n': this.post,
+ 'z': this.toggleZenMode
+ };
}
},
- mounted() {
- document.addEventListener('keydown', this.onKeydown);
- },
- beforeDestroy() {
- document.removeEventListener('keydown', this.onKeydown);
- },
- methods: {
- onKeydown(e) {
- if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA') return;
- if (e.which == 80 || e.which == 78) { // p or n
- e.preventDefault();
- (this as any).apis.post();
- }
+ methods: {
+ post() {
+ (this as any).apis.post();
+ },
- if (e.which == 90) { // z
- e.preventDefault();
- this.zenMode = !this.zenMode;
- }
+ toggleZenMode() {
+ this.zenMode = !this.zenMode;
}
}
});
diff --git a/src/client/app/desktop/views/components/user-lists-window.vue b/src/client/app/desktop/views/components/user-lists-window.vue
index 72ae9cf4e4..75253e0788 100644
--- a/src/client/app/desktop/views/components/user-lists-window.vue
+++ b/src/client/app/desktop/views/components/user-lists-window.vue
@@ -1,5 +1,5 @@
<template>
-<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
+<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
<span slot="header">%fa:list% %i18n:@title%</span>
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp" :data-darkmode="$store.state.device.darkmode">
diff --git a/src/client/app/desktop/views/components/window.vue b/src/client/app/desktop/views/components/window.vue
index 30f0ec558f..e6886956eb 100644
--- a/src/client/app/desktop/views/components/window.vue
+++ b/src/client/app/desktop/views/components/window.vue
@@ -190,8 +190,8 @@ export default Vue.extend({
});
setTimeout(() => {
- this.destroyDom();
this.$emit('closed');
+ this.destroyDom();
}, 300);
},
diff --git a/src/client/app/desktop/views/pages/home.vue b/src/client/app/desktop/views/pages/home.vue
index c7ff0904e0..e595ef4c36 100644
--- a/src/client/app/desktop/views/pages/home.vue
+++ b/src/client/app/desktop/views/pages/home.vue
@@ -1,6 +1,6 @@
<template>
<mk-ui>
- <mk-home :mode="mode" @loaded="loaded"/>
+ <mk-home :mode="mode" @loaded="loaded" ref="home" v-hotkey.global="keymap"/>
</mk-ui>
</template>
@@ -15,6 +15,13 @@ export default Vue.extend({
default: 'timeline'
}
},
+ computed: {
+ keymap(): any {
+ return {
+ 't': this.focus
+ };
+ }
+ },
mounted() {
document.title = (this as any).os.instanceName;
@@ -23,6 +30,9 @@ export default Vue.extend({
methods: {
loaded() {
Progress.done();
+ },
+ focus() {
+ this.$refs.home.focus();
}
}
});
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index db3852da60..3a03f8492e 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
import * as TreeView from 'vue-json-tree-view';
import VAnimateCss from 'v-animate-css';
import VModal from 'vue-js-modal';
+import VueHotkey from './common/hotkey';
import App from './app.vue';
import checkForUpdate from './common/scripts/check-for-update';
@@ -19,6 +20,7 @@ Vue.use(VueRouter);
Vue.use(TreeView);
Vue.use(VAnimateCss);
Vue.use(VModal);
+Vue.use(VueHotkey);
// Register global directives
require('./common/views/directives');