キーボードショートカットを強化するなど
This commit is contained in:
		
							parent
							
								
									e765be4205
								
							
						
					
					
						commit
						31ce3aa312
					
				|  | @ -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); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
|  | @ -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> | ||||
| <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(); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -237,6 +237,10 @@ export default Vue.extend({ | |||
| 
 | ||||
| 		warp(date) { | ||||
| 			(this.$refs.tl as any).warp(date); | ||||
| 		}, | ||||
| 
 | ||||
| 		focus() { | ||||
| 			(this.$refs.tl as any).focus(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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; | ||||
| 		focus() { | ||||
| 			this.$el.focus(); | ||||
| 		}, | ||||
| 
 | ||||
| 			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; | ||||
| 		focusBefore() { | ||||
| 			focus(this.$el, e => e.previousElementSibling); | ||||
| 		}, | ||||
| 
 | ||||
| 				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; | ||||
| 
 | ||||
| 				default: | ||||
| 					shouldBeCancel = false; | ||||
| 			} | ||||
| 
 | ||||
| 			if (shouldBeCancel) e.preventDefault(); | ||||
| 		focusAfter() { | ||||
| 			focus(this.$el, e => e.nextElementSibling); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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"> | ||||
|  |  | |||
|  | @ -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(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -92,6 +92,10 @@ export default Vue.extend({ | |||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		focus() { | ||||
| 			(this.$refs.tl as any).focus(); | ||||
| 		}, | ||||
| 
 | ||||
| 		warp(date) { | ||||
| 			(this.$refs.tl as any).warp(date); | ||||
| 		}, | ||||
|  |  | |||
|  | @ -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; | ||||
| 		post() { | ||||
| 			(this as any).apis.post(); | ||||
| 		}, | ||||
| 
 | ||||
| 			if (e.which == 80 || e.which == 78) { // p or n | ||||
| 				e.preventDefault(); | ||||
| 				(this as any).apis.post(); | ||||
| 			} | ||||
| 
 | ||||
| 			if (e.which == 90) { // z | ||||
| 				e.preventDefault(); | ||||
| 				this.zenMode = !this.zenMode; | ||||
| 			} | ||||
| 		toggleZenMode() { | ||||
| 			this.zenMode = !this.zenMode; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -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"> | ||||
|  |  | |||
|  | @ -190,8 +190,8 @@ export default Vue.extend({ | |||
| 			}); | ||||
| 
 | ||||
| 			setTimeout(() => { | ||||
| 				this.destroyDom(); | ||||
| 				this.$emit('closed'); | ||||
| 				this.destroyDom(); | ||||
| 			}, 300); | ||||
| 		}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -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'); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue