summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/directives/hotkey.ts93
-rw-r--r--src/client/init.ts16
-rw-r--r--src/client/pages/settings/theme.vue2
-rw-r--r--src/client/scripts/hotkey.ts88
-rw-r--r--src/client/ui/_common_/common.vue2
-rw-r--r--src/client/ui/deck.vue11
-rw-r--r--src/client/ui/default.vue21
-rw-r--r--src/client/ui/desktop.vue15
-rw-r--r--src/client/ui/zen.vue20
9 files changed, 110 insertions, 158 deletions
diff --git a/src/client/directives/hotkey.ts b/src/client/directives/hotkey.ts
index a1c49f0074..d813a95074 100644
--- a/src/client/directives/hotkey.ts
+++ b/src/client/directives/hotkey.ts
@@ -1,100 +1,11 @@
import { Directive } from 'vue';
-import keyCode from '../scripts/keycode';
-import { concat } from '../../prelude/array';
-
-type pattern = {
- which: string[];
- ctrl?: boolean;
- shift?: boolean;
- alt?: boolean;
-};
-
-type action = {
- patterns: pattern[];
-
- callback: Function;
-
- allowRepeat: boolean;
-};
-
-const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
- const result = {
- patterns: [],
- callback: callback,
- allowRepeat: true
- } as action;
-
- if (patterns.match(/^\(.*\)$/) !== null) {
- result.allowRepeat = false;
- patterns = patterns.slice(1, -1);
- }
-
- result.patterns = patterns.split('|').map(part => {
- const pattern = {
- which: [],
- ctrl: false,
- alt: false,
- shift: false
- } as pattern;
-
- const keys = part.trim().split('+').map(x => x.trim().toLowerCase());
- for (const key of keys) {
- 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());
- }
- }
-
- return pattern;
- });
-
- return result;
-});
-
-const ignoreElemens = ['input', 'textarea'];
-
-function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
- const key = e.code.toLowerCase();
- return patterns.some(pattern => pattern.which.includes(key) &&
- pattern.ctrl === e.ctrlKey &&
- pattern.shift === e.shiftKey &&
- pattern.alt === e.altKey &&
- !e.metaKey
- );
-}
+import { makeHotkey } from '../scripts/hotkey';
export default {
mounted(el, binding) {
el._hotkey_global = binding.modifiers.global === true;
- const actions = getKeyMap(binding.value);
-
- // flatten
- const reservedKeys = concat(actions.map(a => a.patterns));
-
- el._misskey_reservedKeys = reservedKeys;
-
- el._keyHandler = (e: KeyboardEvent) => {
- 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;
-
- for (const action of actions) {
- const matched = match(e, action.patterns);
-
- if (matched) {
- if (!action.allowRepeat && e.repeat) return;
- if (el._hotkey_global && match(e, targetReservedKeys)) return;
-
- e.preventDefault();
- e.stopPropagation();
- action.callback(e);
- break;
- }
- }
- };
+ el._keyHandler = makeHotkey(binding.value);
if (el._hotkey_global) {
document.addEventListener('keydown', el._keyHandler);
diff --git a/src/client/init.ts b/src/client/init.ts
index 4af6f25780..05fbec3a34 100644
--- a/src/client/init.ts
+++ b/src/client/init.ts
@@ -45,15 +45,17 @@ import { router } from '@/router';
import { applyTheme } from '@/scripts/theme';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
import { i18n } from '@/i18n';
-import { stream, isMobile, dialog } from '@/os';
+import { stream, isMobile, dialog, post } from '@/os';
import * as sound from '@/scripts/sound';
import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
import { defaultStore, ColdDeviceStorage } from '@/store';
import { fetchInstance, instance } from '@/instance';
+import { makeHotkey } from './scripts/hotkey';
+import { search } from './scripts/search';
console.info(`Misskey v${version}`);
-window.clearTimeout(window.mkBootTimer);
+window.clearTimeout((window as any).mkBootTimer);
if (_DEV_) {
console.warn('Development mode!!!');
@@ -214,6 +216,16 @@ window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
});
//#endregion
+// shortcut
+document.addEventListener('keydown', makeHotkey({
+ 'd': () => {
+ defaultStore.set('darkMode', !defaultStore.state.darkMode);
+ },
+ 'p|n': post,
+ 's': search,
+ //TODO: 'h|/': help
+}));
+
watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true });
diff --git a/src/client/pages/settings/theme.vue b/src/client/pages/settings/theme.vue
index 720874fd54..d83e243081 100644
--- a/src/client/pages/settings/theme.vue
+++ b/src/client/pages/settings/theme.vue
@@ -99,7 +99,7 @@ export default defineComponent({
const lightThemes = computed(() => themes.value.filter(t => t.base == 'light' || t.kind == 'light'));
const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
- const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
+ const darkMode = defaultStore.reactiveState.darkMode;
const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
const wallpaper = ref(localStorage.getItem('wallpaper'));
diff --git a/src/client/scripts/hotkey.ts b/src/client/scripts/hotkey.ts
new file mode 100644
index 0000000000..2b3f491fd8
--- /dev/null
+++ b/src/client/scripts/hotkey.ts
@@ -0,0 +1,88 @@
+import keyCode from './keycode';
+
+type Keymap = Record<string, Function>;
+
+type Pattern = {
+ which: string[];
+ ctrl?: boolean;
+ shift?: boolean;
+ alt?: boolean;
+};
+
+type Action = {
+ patterns: Pattern[];
+ callback: Function;
+ allowRepeat: boolean;
+};
+
+const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, callback]): Action => {
+ const result = {
+ patterns: [],
+ callback: callback,
+ allowRepeat: true
+ } as Action;
+
+ if (patterns.match(/^\(.*\)$/) !== null) {
+ result.allowRepeat = false;
+ patterns = patterns.slice(1, -1);
+ }
+
+ result.patterns = patterns.split('|').map(part => {
+ const pattern = {
+ which: [],
+ ctrl: false,
+ alt: false,
+ shift: false
+ } as Pattern;
+
+ const keys = part.trim().split('+').map(x => x.trim().toLowerCase());
+ for (const key of keys) {
+ 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());
+ }
+ }
+
+ return pattern;
+ });
+
+ return result;
+});
+
+const ignoreElemens = ['input', 'textarea'];
+
+function match(e: KeyboardEvent, patterns: Action['patterns']): boolean {
+ const key = e.code.toLowerCase();
+ return patterns.some(pattern => pattern.which.includes(key) &&
+ pattern.ctrl === e.ctrlKey &&
+ pattern.shift === e.shiftKey &&
+ pattern.alt === e.altKey &&
+ !e.metaKey
+ );
+}
+
+export const makeHotkey = (keymap: Keymap) => {
+ const actions = parseKeymap(keymap);
+
+ return (e: KeyboardEvent) => {
+ if (document.activeElement) {
+ if (ignoreElemens.some(el => document.activeElement!.matches(el))) return;
+ if (document.activeElement.attributes['contenteditable']) return;
+ }
+
+ for (const action of actions) {
+ const matched = match(e, action.patterns);
+
+ if (matched) {
+ if (!action.allowRepeat && e.repeat) return;
+
+ e.preventDefault();
+ e.stopPropagation();
+ action.callback(e);
+ break;
+ }
+ }
+ };
+};
diff --git a/src/client/ui/_common_/common.vue b/src/client/ui/_common_/common.vue
index a4d1661f46..e5cdaca235 100644
--- a/src/client/ui/_common_/common.vue
+++ b/src/client/ui/_common_/common.vue
@@ -17,7 +17,7 @@
import { defineAsyncComponent, defineComponent } from 'vue';
import { stream, popup, popups, uploads, pendingApiRequestsCount } from '@/os';
import * as sound from '@/scripts/sound';
-import { $i, $i } from '@/account';
+import { $i } from '@/account';
export default defineComponent({
components: {
diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue
index 0cbfd7a48b..6cdf56f3c3 100644
--- a/src/client/ui/deck.vue
+++ b/src/client/ui/deck.vue
@@ -1,5 +1,5 @@
<template>
-<div class="mk-deck" :class="`${deckStore.reactiveState.columnAlign.value}`" v-hotkey.global="keymap" @contextmenu.self.prevent="onContextmenu"
+<div class="mk-deck" :class="`${deckStore.reactiveState.columnAlign.value}`" @contextmenu.self.prevent="onContextmenu"
:style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }"
>
<XSidebar ref="nav"/>
@@ -35,7 +35,6 @@ import { faPlus, faPencilAlt, faChevronLeft, faBars, faCircle } from '@fortaweso
import { } from '@fortawesome/free-regular-svg-icons';
import { v4 as uuid } from 'uuid';
import { host } from '@/config';
-import { search } from '@/scripts/search';
import DeckColumnCore from '@/ui/deck/column-core.vue';
import XSidebar from '@/components/sidebar.vue';
import { getScrollContainer } from '@/scripts/scroll';
@@ -75,14 +74,6 @@ export default defineComponent({
}
return false;
},
- keymap(): any {
- return {
- 'p': this.post,
- 'n': this.post,
- 's': this.search,
- 'h|/': this.help
- };
- },
},
created() {
diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue
index 066b9dd354..1a3c336f39 100644
--- a/src/client/ui/default.vue
+++ b/src/client/ui/default.vue
@@ -1,5 +1,5 @@
<template>
-<div class="mk-app" v-hotkey.global="keymap" :class="{ wallpaper }">
+<div class="mk-app" :class="{ wallpaper }">
<XSidebar ref="nav" class="sidebar"/>
<div class="contents" ref="contents">
@@ -57,7 +57,6 @@ import { defineComponent, defineAsyncComponent, markRaw } from 'vue';
import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { faBell } from '@fortawesome/free-regular-svg-icons';
import { host } from '@/config';
-import { search } from '@/scripts/search';
import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from '@/components/sidebar.vue';
import XCommon from './_common_/common.vue';
@@ -65,7 +64,6 @@ import XHeader from './_common_/header.vue';
import XSide from './default.side.vue';
import * as os from '@/os';
import { sidebarDef } from '@/sidebar';
-import { ColdDeviceStorage } from '@/store';
const DESKTOP_THRESHOLD = 1100;
@@ -101,19 +99,6 @@ export default defineComponent({
},
computed: {
- keymap(): any {
- return {
- 'd': () => {
- if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
- this.$store.set('darkMode', !this.$store.state.darkMode);
- },
- 'p': os.post,
- 'n': os.post,
- 's': () => search(),
- 'h|/': this.help
- };
- },
-
navIndicated(): boolean {
for (const def in this.menuDef) {
if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
@@ -199,10 +184,6 @@ export default defineComponent({
window.scroll({ top: 0, behavior: 'smooth' });
},
- help() {
- this.$router.push('/docs/keyboard-shortcut');
- },
-
onTransition() {
if (window._scroll) window._scroll();
},
diff --git a/src/client/ui/desktop.vue b/src/client/ui/desktop.vue
index 7c5824c4c0..199c0fdaaf 100644
--- a/src/client/ui/desktop.vue
+++ b/src/client/ui/desktop.vue
@@ -1,5 +1,5 @@
<template>
-<div class="mk-app" v-hotkey.global="keymap" :class="{ wallpaper }" @contextmenu.prevent="() => {}">
+<div class="mk-app" :class="{ wallpaper }" @contextmenu.prevent="() => {}">
<XSidebar ref="nav" class="sidebar"/>
<XCommon/>
@@ -31,19 +31,6 @@ export default defineComponent({
},
computed: {
- keymap(): any {
- return {
- 'd': () => {
- if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
- this.$store.set('darkMode', !this.$store.state.darkMode);
- },
- 'p': os.post,
- 'n': os.post,
- 's': () => search(),
- 'h|/': this.help
- };
- },
-
menu(): string[] {
return this.$store.state.menu;
},
diff --git a/src/client/ui/zen.vue b/src/client/ui/zen.vue
index 1c1334bece..c2a307da73 100644
--- a/src/client/ui/zen.vue
+++ b/src/client/ui/zen.vue
@@ -1,5 +1,5 @@
<template>
-<div class="mk-app" v-hotkey.global="keymap">
+<div class="mk-app">
<div class="contents">
<header class="header">
<XHeader :info="pageInfo"/>
@@ -26,11 +26,8 @@ import { defineComponent, defineAsyncComponent } from 'vue';
import { faLayerGroup, faBars, faHome, faCircle } from '@fortawesome/free-solid-svg-icons';
import { faBell } from '@fortawesome/free-regular-svg-icons';
import { host } from '@/config';
-import { search } from '@/scripts/search';
import XHeader from './_common_/header.vue';
import XCommon from './_common_/common.vue';
-import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
export default defineComponent({
components: {
@@ -47,21 +44,6 @@ export default defineComponent({
};
},
- computed: {
- keymap(): any {
- return {
- 'd': () => {
- if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
- this.$store.set('darkMode', !this.$store.state.darkMode);
- },
- 'p': os.post,
- 'n': os.post,
- 's': search,
- 'h|/': this.help
- };
- },
- },
-
watch: {
$route(to, from) {
this.pageKey++;