nanka iroiro
This commit is contained in:
		
							parent
							
								
									03667e1fe6
								
							
						
					
					
						commit
						3fc427b699
					
				
					 9 changed files with 110 additions and 158 deletions
				
			
		|  | @ -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); | ||||
|  |  | |||
|  | @ -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 }); | ||||
|  |  | |||
|  | @ -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')); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										88
									
								
								src/client/scripts/hotkey.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/client/scripts/hotkey.ts
									
										
									
									
									
										Normal 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; | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
| }; | ||||
|  | @ -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: { | ||||
|  |  | |||
|  | @ -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() { | ||||
|  |  | |||
|  | @ -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(); | ||||
| 		}, | ||||
|  |  | |||
|  | @ -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; | ||||
| 		}, | ||||
|  |  | |||
|  | @ -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++; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue