mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-24 01:03:08 +02:00
キーボードショートカットを強化するなど
This commit is contained in:
parent
e765be4205
commit
31ce3aa312
27 changed files with 355 additions and 100 deletions
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>
|
<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="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>
|
||||||
|
@ -31,28 +31,51 @@ export default Vue.extend({
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
source: {
|
source: {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
compact: {
|
compact: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
cb: {
|
cb: {
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
|
||||||
big: {
|
big: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
title: placeholder
|
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() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const popover = this.$refs.popover as any;
|
const popover = this.$refs.popover as any;
|
||||||
|
@ -88,6 +111,7 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
react(reaction) {
|
react(reaction) {
|
||||||
(this as any).api('notes/reactions/create', {
|
(this as any).api('notes/reactions/create', {
|
||||||
|
@ -95,15 +119,19 @@ export default Vue.extend({
|
||||||
reaction: reaction
|
reaction: reaction
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (this.cb) this.cb();
|
if (this.cb) this.cb();
|
||||||
|
this.$emit('closed');
|
||||||
this.destroyDom();
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseover(e) {
|
onMouseover(e) {
|
||||||
this.title = e.target.title;
|
this.title = e.target.title;
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseout(e) {
|
onMouseout(e) {
|
||||||
this.title = placeholder;
|
this.title = placeholder;
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||||
anime({
|
anime({
|
||||||
|
@ -120,7 +148,10 @@ export default Vue.extend({
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
duration: 200,
|
duration: 200,
|
||||||
easing: 'easeInBack',
|
easing: 'easeInBack',
|
||||||
complete: () => this.destroyDom()
|
complete: () => {
|
||||||
|
this.$emit('closed');
|
||||||
|
this.destroyDom();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 slot="header">
|
||||||
<span v-html="title" :class="$style.title"></span>
|
<span v-html="title" :class="$style.title"></span>
|
||||||
<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
|
<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 slot="header">
|
||||||
<span v-html="title" :class="$style.title"></span>
|
<span v-html="title" :class="$style.title"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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">
|
<template slot="header">
|
||||||
<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> %i18n:@used%</p>
|
<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> %i18n:@used%</p>
|
||||||
<span :class="$style.title">%fa:cloud%%i18n:@drive%</span>
|
<span :class="$style.title">%fa:cloud%%i18n:@drive%</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
<mk-window width="400px" height="550px" @closed="destroyDom">
|
||||||
<span slot="header" :class="$style.header">
|
<span slot="header" :class="$style.header">
|
||||||
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
|
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
<mk-window width="400px" height="550px" @closed="destroyDom">
|
||||||
<span slot="header" :class="$style.header">
|
<span slot="header" :class="$style.header">
|
||||||
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
|
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
|
||||||
<mk-reversi :class="$style.content" @gamed="g => game = g"/>
|
<mk-reversi :class="$style.content" @gamed="g => game = g"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -237,6 +237,10 @@ export default Vue.extend({
|
||||||
|
|
||||||
warp(date) {
|
warp(date) {
|
||||||
(this.$refs.tl as any).warp(date);
|
(this.$refs.tl as any).warp(date);
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
(this.$refs.tl as any).focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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">
|
<span slot="header" :class="$style.header">
|
||||||
%fa:i-cursor%{{ title }}
|
%fa:i-cursor%{{ title }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title% {{ user | userName }}</span>
|
||||||
<mk-messaging-room :user="user" :class="$style.content"/>
|
<mk-messaging-room :user="user" :class="$style.content"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title%</span>
|
||||||
<mk-messaging :class="$style.content" @navigate="navigate"/>
|
<mk-messaging :class="$style.content" @navigate="navigate"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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)">
|
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||||
<x-sub :note="p.reply"/>
|
<x-sub :note="p.reply"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,6 +111,18 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
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 {
|
isRenote(): boolean {
|
||||||
return (this.note.renote &&
|
return (this.note.renote &&
|
||||||
this.note.text == null &&
|
this.note.text == null &&
|
||||||
|
@ -223,64 +235,39 @@ export default Vue.extend({
|
||||||
reply() {
|
reply() {
|
||||||
(this as any).os.new(MkPostFormWindow, {
|
(this as any).os.new(MkPostFormWindow, {
|
||||||
reply: this.p
|
reply: this.p
|
||||||
});
|
}).$once('closed', this.focus);
|
||||||
},
|
},
|
||||||
|
|
||||||
renote() {
|
renote() {
|
||||||
(this as any).os.new(MkRenoteFormWindow, {
|
(this as any).os.new(MkRenoteFormWindow, {
|
||||||
note: this.p
|
note: this.p
|
||||||
});
|
}).$once('closed', this.focus);
|
||||||
},
|
},
|
||||||
|
|
||||||
react() {
|
react() {
|
||||||
(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
|
||||||
});
|
}).$once('closed', this.focus);
|
||||||
},
|
},
|
||||||
|
|
||||||
menu() {
|
menu() {
|
||||||
(this as any).os.new(MkNoteMenu, {
|
(this as any).os.new(MkNoteMenu, {
|
||||||
source: this.$refs.menuButton,
|
source: this.$refs.menuButton,
|
||||||
note: this.p
|
note: this.p
|
||||||
});
|
}).$once('closed', this.focus);
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeydown(e) {
|
focus() {
|
||||||
let shouldBeCancel = true;
|
this.$el.focus();
|
||||||
|
},
|
||||||
|
|
||||||
switch (true) {
|
focusBefore() {
|
||||||
case e.which == 38: // [↑]
|
focus(this.$el, e => e.previousElementSibling);
|
||||||
case e.which == 74: // [j]
|
},
|
||||||
case e.which == 9 && e.shiftKey: // [Shift] + [Tab]
|
|
||||||
focus(this.$el, e => e.previousElementSibling);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case e.which == 40: // [↓]
|
focusAfter() {
|
||||||
case e.which == 75: // [k]
|
focus(this.$el, e => e.nextElementSibling);
|
||||||
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;
|
|
||||||
|
|
||||||
default:
|
|
||||||
shouldBeCancel = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldBeCancel) e.preventDefault();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
|
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
|
||||||
<template v-for="(note, i) in _notes">
|
<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">
|
<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-up%{{ note._datetext }}</span>
|
||||||
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
|
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
|
||||||
|
@ -89,7 +89,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
(this.$el as any).children[0].focus();
|
(this.$refs.note as any)[0].focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
onNoteUpdated(i, note) {
|
onNoteUpdated(i, note) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 slot="header" class="mk-post-form-window--header">
|
||||||
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
|
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
|
||||||
<span v-if="!reply">%i18n:@note%</span>
|
<span v-if="!reply">%i18n:@note%</span>
|
||||||
|
@ -53,6 +53,10 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
onPosted() {
|
onPosted() {
|
||||||
(this.$refs.window as any).close();
|
(this.$refs.window as any).close();
|
||||||
|
},
|
||||||
|
onWindowClosed() {
|
||||||
|
this.$emit('closed');
|
||||||
|
this.destroyDom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header">{{ title }}<mk-ellipsis/></span>
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
|
<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header">%fa:envelope R% %i18n:@title%</span>
|
||||||
|
|
||||||
<div class="slpqaxdoxhvglersgjukmvizkqbmbokc" :data-darkmode="$store.state.device.darkmode">
|
<div class="slpqaxdoxhvglersgjukmvizkqbmbokc" :data-darkmode="$store.state.device.darkmode">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<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>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -10,25 +10,32 @@ import Vue from 'vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['note'],
|
props: ['note'],
|
||||||
mounted() {
|
|
||||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
computed: {
|
||||||
},
|
keymap(): any {
|
||||||
beforeDestroy() {
|
return {
|
||||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
'esc': this.close,
|
||||||
|
'ctrl+enter': this.post
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onDocumentKeydown(e) {
|
post() {
|
||||||
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
|
(this.$refs.form as any).ok();
|
||||||
if (e.which == 27) { // Esc
|
},
|
||||||
(this.$refs.window as any).close();
|
close() {
|
||||||
}
|
(this.$refs.window as any).close();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onPosted() {
|
onPosted() {
|
||||||
(this.$refs.window as any).close();
|
(this.$refs.window as any).close();
|
||||||
},
|
},
|
||||||
onCanceled() {
|
onCanceled() {
|
||||||
(this.$refs.window as any).close();
|
(this.$refs.window as any).close();
|
||||||
|
},
|
||||||
|
onWindowClosed() {
|
||||||
|
this.$emit('closed');
|
||||||
|
this.destroyDom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span>
|
||||||
<mk-settings :initial-page="initialPage" @done="close"/>
|
<mk-settings :initial-page="initialPage" @done="close"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -152,14 +152,11 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', this.onKeydown);
|
|
||||||
|
|
||||||
this.fetch();
|
this.fetch();
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$emit('beforeDestroy');
|
this.$emit('beforeDestroy');
|
||||||
document.removeEventListener('keydown', this.onKeydown);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -212,14 +209,6 @@ export default Vue.extend({
|
||||||
warp(date) {
|
warp(date) {
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.fetch();
|
this.fetch();
|
||||||
},
|
|
||||||
|
|
||||||
onKeydown(e) {
|
|
||||||
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
|
|
||||||
if (e.which == 84) { // t
|
|
||||||
this.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -92,6 +92,10 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
(this.$refs.tl as any).focus();
|
||||||
|
},
|
||||||
|
|
||||||
warp(date) {
|
warp(date) {
|
||||||
(this.$refs.tl as any).warp(date);
|
(this.$refs.tl as any).warp(date);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-ui" :style="style">
|
<div class="mk-ui" :style="style" v-hotkey.global="keymap">
|
||||||
<x-header class="header" v-show="!zenMode"/>
|
<x-header class="header" v-show="!zenMode"/>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -16,11 +16,13 @@ export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
XHeader
|
XHeader
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
zenMode: false
|
zenMode: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
style(): any {
|
style(): any {
|
||||||
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
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,
|
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 })`
|
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: {
|
methods: {
|
||||||
onKeydown(e) {
|
post() {
|
||||||
if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA') return;
|
(this as any).apis.post();
|
||||||
|
},
|
||||||
|
|
||||||
if (e.which == 80 || e.which == 78) { // p or n
|
toggleZenMode() {
|
||||||
e.preventDefault();
|
this.zenMode = !this.zenMode;
|
||||||
(this as any).apis.post();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.which == 90) { // z
|
|
||||||
e.preventDefault();
|
|
||||||
this.zenMode = !this.zenMode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<span slot="header">%fa:list% %i18n:@title%</span>
|
||||||
|
|
||||||
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp" :data-darkmode="$store.state.device.darkmode">
|
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp" :data-darkmode="$store.state.device.darkmode">
|
||||||
|
|
|
@ -190,8 +190,8 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.destroyDom();
|
|
||||||
this.$emit('closed');
|
this.$emit('closed');
|
||||||
|
this.destroyDom();
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<mk-home :mode="mode" @loaded="loaded"/>
|
<mk-home :mode="mode" @loaded="loaded" ref="home" v-hotkey.global="keymap"/>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -15,6 +15,13 @@ export default Vue.extend({
|
||||||
default: 'timeline'
|
default: 'timeline'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
't': this.focus
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = (this as any).os.instanceName;
|
document.title = (this as any).os.instanceName;
|
||||||
|
|
||||||
|
@ -23,6 +30,9 @@ export default Vue.extend({
|
||||||
methods: {
|
methods: {
|
||||||
loaded() {
|
loaded() {
|
||||||
Progress.done();
|
Progress.done();
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
this.$refs.home.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
|
||||||
import * as TreeView from 'vue-json-tree-view';
|
import * as TreeView from 'vue-json-tree-view';
|
||||||
import VAnimateCss from 'v-animate-css';
|
import VAnimateCss from 'v-animate-css';
|
||||||
import VModal from 'vue-js-modal';
|
import VModal from 'vue-js-modal';
|
||||||
|
import VueHotkey from './common/hotkey';
|
||||||
|
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import checkForUpdate from './common/scripts/check-for-update';
|
import checkForUpdate from './common/scripts/check-for-update';
|
||||||
|
@ -19,6 +20,7 @@ Vue.use(VueRouter);
|
||||||
Vue.use(TreeView);
|
Vue.use(TreeView);
|
||||||
Vue.use(VAnimateCss);
|
Vue.use(VAnimateCss);
|
||||||
Vue.use(VModal);
|
Vue.use(VModal);
|
||||||
|
Vue.use(VueHotkey);
|
||||||
|
|
||||||
// Register global directives
|
// Register global directives
|
||||||
require('./common/views/directives');
|
require('./common/views/directives');
|
||||||
|
|
Loading…
Reference in a new issue