nanka iroiro

This commit is contained in:
syuilo 2020-12-27 17:04:41 +09:00
parent 03667e1fe6
commit 3fc427b699
9 changed files with 110 additions and 158 deletions

View File

@ -1,100 +1,11 @@
import { Directive } from 'vue'; import { Directive } from 'vue';
import keyCode from '../scripts/keycode'; import { makeHotkey } from '../scripts/hotkey';
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
);
}
export default { export default {
mounted(el, binding) { mounted(el, binding) {
el._hotkey_global = binding.modifiers.global === true; el._hotkey_global = binding.modifiers.global === true;
const actions = getKeyMap(binding.value); el._keyHandler = makeHotkey(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;
}
}
};
if (el._hotkey_global) { if (el._hotkey_global) {
document.addEventListener('keydown', el._keyHandler); document.addEventListener('keydown', el._keyHandler);

View File

@ -45,15 +45,17 @@ import { router } from '@/router';
import { applyTheme } from '@/scripts/theme'; import { applyTheme } from '@/scripts/theme';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { stream, isMobile, dialog } from '@/os'; import { stream, isMobile, dialog, post } from '@/os';
import * as sound from '@/scripts/sound'; import * as sound from '@/scripts/sound';
import { $i, refreshAccount, login, updateAccount, signout } from '@/account'; import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
import { defaultStore, ColdDeviceStorage } from '@/store'; import { defaultStore, ColdDeviceStorage } from '@/store';
import { fetchInstance, instance } from '@/instance'; import { fetchInstance, instance } from '@/instance';
import { makeHotkey } from './scripts/hotkey';
import { search } from './scripts/search';
console.info(`Misskey v${version}`); console.info(`Misskey v${version}`);
window.clearTimeout(window.mkBootTimer); window.clearTimeout((window as any).mkBootTimer);
if (_DEV_) { if (_DEV_) {
console.warn('Development mode!!!'); console.warn('Development mode!!!');
@ -214,6 +216,16 @@ window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
}); });
//#endregion //#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 => { watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true }); }, { immediate: true });

View File

@ -99,7 +99,7 @@ export default defineComponent({
const lightThemes = computed(() => themes.value.filter(t => t.base == 'light' || t.kind == 'light')); const lightThemes = computed(() => themes.value.filter(t => t.base == 'light' || t.kind == 'light'));
const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); const darkMode = defaultStore.reactiveState.darkMode;
const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
const wallpaper = ref(localStorage.getItem('wallpaper')); const wallpaper = ref(localStorage.getItem('wallpaper'));

View File

@ -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;
}
}
};
};

View File

@ -17,7 +17,7 @@
import { defineAsyncComponent, defineComponent } from 'vue'; import { defineAsyncComponent, defineComponent } from 'vue';
import { stream, popup, popups, uploads, pendingApiRequestsCount } from '@/os'; import { stream, popup, popups, uploads, pendingApiRequestsCount } from '@/os';
import * as sound from '@/scripts/sound'; import * as sound from '@/scripts/sound';
import { $i, $i } from '@/account'; import { $i } from '@/account';
export default defineComponent({ export default defineComponent({
components: { components: {

View File

@ -1,5 +1,5 @@
<template> <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' }" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }"
> >
<XSidebar ref="nav"/> <XSidebar ref="nav"/>
@ -35,7 +35,6 @@ import { faPlus, faPencilAlt, faChevronLeft, faBars, faCircle } from '@fortaweso
import { } from '@fortawesome/free-regular-svg-icons'; import { } from '@fortawesome/free-regular-svg-icons';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { host } from '@/config'; import { host } from '@/config';
import { search } from '@/scripts/search';
import DeckColumnCore from '@/ui/deck/column-core.vue'; import DeckColumnCore from '@/ui/deck/column-core.vue';
import XSidebar from '@/components/sidebar.vue'; import XSidebar from '@/components/sidebar.vue';
import { getScrollContainer } from '@/scripts/scroll'; import { getScrollContainer } from '@/scripts/scroll';
@ -75,14 +74,6 @@ export default defineComponent({
} }
return false; return false;
}, },
keymap(): any {
return {
'p': this.post,
'n': this.post,
's': this.search,
'h|/': this.help
};
},
}, },
created() { created() {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="mk-app" v-hotkey.global="keymap" :class="{ wallpaper }"> <div class="mk-app" :class="{ wallpaper }">
<XSidebar ref="nav" class="sidebar"/> <XSidebar ref="nav" class="sidebar"/>
<div class="contents" ref="contents"> <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 { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { faBell } from '@fortawesome/free-regular-svg-icons'; import { faBell } from '@fortawesome/free-regular-svg-icons';
import { host } from '@/config'; import { host } from '@/config';
import { search } from '@/scripts/search';
import { StickySidebar } from '@/scripts/sticky-sidebar'; import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from '@/components/sidebar.vue'; import XSidebar from '@/components/sidebar.vue';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
@ -65,7 +64,6 @@ import XHeader from './_common_/header.vue';
import XSide from './default.side.vue'; import XSide from './default.side.vue';
import * as os from '@/os'; import * as os from '@/os';
import { sidebarDef } from '@/sidebar'; import { sidebarDef } from '@/sidebar';
import { ColdDeviceStorage } from '@/store';
const DESKTOP_THRESHOLD = 1100; const DESKTOP_THRESHOLD = 1100;
@ -101,19 +99,6 @@ export default defineComponent({
}, },
computed: { 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 { navIndicated(): boolean {
for (const def in this.menuDef) { for (const def in this.menuDef) {
if (def === 'notifications') continue; // if (def === 'notifications') continue; //
@ -199,10 +184,6 @@ export default defineComponent({
window.scroll({ top: 0, behavior: 'smooth' }); window.scroll({ top: 0, behavior: 'smooth' });
}, },
help() {
this.$router.push('/docs/keyboard-shortcut');
},
onTransition() { onTransition() {
if (window._scroll) window._scroll(); if (window._scroll) window._scroll();
}, },

View File

@ -1,5 +1,5 @@
<template> <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"/> <XSidebar ref="nav" class="sidebar"/>
<XCommon/> <XCommon/>
@ -31,19 +31,6 @@ export default defineComponent({
}, },
computed: { 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[] { menu(): string[] {
return this.$store.state.menu; return this.$store.state.menu;
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="mk-app" v-hotkey.global="keymap"> <div class="mk-app">
<div class="contents"> <div class="contents">
<header class="header"> <header class="header">
<XHeader :info="pageInfo"/> <XHeader :info="pageInfo"/>
@ -26,11 +26,8 @@ import { defineComponent, defineAsyncComponent } from 'vue';
import { faLayerGroup, faBars, faHome, faCircle } from '@fortawesome/free-solid-svg-icons'; import { faLayerGroup, faBars, faHome, faCircle } from '@fortawesome/free-solid-svg-icons';
import { faBell } from '@fortawesome/free-regular-svg-icons'; import { faBell } from '@fortawesome/free-regular-svg-icons';
import { host } from '@/config'; import { host } from '@/config';
import { search } from '@/scripts/search';
import XHeader from './_common_/header.vue'; import XHeader from './_common_/header.vue';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
export default defineComponent({ export default defineComponent({
components: { 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: { watch: {
$route(to, from) { $route(to, from) {
this.pageKey++; this.pageKey++;