From 764a158cd7e112186cbf54cb77599bcd22ea7d69 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Sun, 28 Feb 2021 01:09:59 +0900 Subject: [PATCH] Resolve #7270 --- src/client/components/emoji-picker-dialog.vue | 9 ++++- src/client/components/emoji-picker.vue | 35 ++++++++---------- src/client/components/note-detailed.vue | 22 ++++------- src/client/components/note.vue | 22 ++++------- src/client/components/ui/modal.vue | 26 +++++++++---- src/client/os.ts | 37 +++++++++++++++++++ src/client/ui/chat/note.vue | 25 ++++--------- 7 files changed, 101 insertions(+), 75 deletions(-) diff --git a/src/client/components/emoji-picker-dialog.vue b/src/client/components/emoji-picker-dialog.vue index 177b5db44..3450d219c 100644 --- a/src/client/components/emoji-picker-dialog.vue +++ b/src/client/components/emoji-picker-dialog.vue @@ -1,6 +1,6 @@ <template> -<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> - <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> +<MkModal ref="modal" :manual-showing="manualShowing" :src="src" @click="$refs.modal.close()" @opening="$refs.picker.focus()" @close="$emit('close')" @closed="$emit('closed')"> + <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/> </MkModal> </template> @@ -16,6 +16,11 @@ export default defineComponent({ }, props: { + manualShowing: { + type: Boolean, + required: false, + default: null, + }, src: { required: false }, diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue index b11f0a62f..41e667dd9 100644 --- a/src/client/components/emoji-picker.vue +++ b/src/client/components/emoji-picker.vue @@ -54,23 +54,17 @@ </div> </section> </div> - <div v-appear="() => showingCustomEmojis = true"> + <div> <header class="_acrylic">{{ $ts.customEmojis }}</header> - <template v-if="showingCustomEmojis"> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection> - </template> + <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection> </div> - <div v-appear="() => showingEmojis = true"> + <div> <header class="_acrylic">{{ $ts.emoji }}</header> - <template v-if="showingEmojis"> - <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection> - </template> + <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection> </div> - <div v-appear="() => showingTags = true"> + <div> <header class="_acrylic">{{ $ts.tags }}</header> - <template v-if="showingTags"> - <XSection v-for="tag in emojiTags" :emojis="customEmojis.filter(e => e.aliases.includes(tag)).map(e => ':' + e.name + ':')">{{ tag }}</XSection> - </template> + <XSection v-for="tag in emojiTags" :emojis="customEmojis.filter(e => e.aliases.includes(tag)).map(e => ':' + e.name + ':')">{{ tag }}</XSection> </div> </div> <div class="tabs"> @@ -127,9 +121,6 @@ export default defineComponent({ searchResultCustom: [], searchResultUnicode: [], tab: 'index', - showingCustomEmojis: false, - showingEmojis: false, - showingTags: false, categories: ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'], faGlobe, faClock, faChevronDown, faAsterisk, faLaugh, faUtensils, faLeaf, faShapes, faBicycle, faHashtag, }; @@ -279,14 +270,18 @@ export default defineComponent({ }, mounted() { - if (!isMobile && !isDeviceTouch) { - this.$refs.search.focus({ - preventScroll: true - }); - } + this.focus(); }, methods: { + focus() { + if (!isMobile && !isDeviceTouch) { + this.$refs.search.focus({ + preventScroll: true + }); + } + }, + getKey(emoji: any) { return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); }, diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue index e1927133a..7df87c6b0 100644 --- a/src/client/components/note-detailed.vue +++ b/src/client/components/note-detailed.vue @@ -523,20 +523,14 @@ export default defineComponent({ react(viaKeyboard = false) { pleaseLogin(); this.blur(); - os.popup(import('@/components/emoji-picker-dialog.vue'), { - src: this.$refs.reactButton, - asReactionPicker: true - }, { - done: reaction => { - if (reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - } - this.focus(); - }, - }, 'closed'); + os.pickReaction(this.$refs.reactButton, reaction => { + os.api('notes/reactions/create', { + noteId: this.appearNote.id, + reaction: reaction + }); + }, () => { + this.focus(); + }); }, reactDirectly(reaction) { diff --git a/src/client/components/note.vue b/src/client/components/note.vue index 6af0668e2..dab764376 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -498,20 +498,14 @@ export default defineComponent({ react(viaKeyboard = false) { pleaseLogin(); this.blur(); - os.popup(import('@/components/emoji-picker-dialog.vue'), { - src: this.$refs.reactButton, - asReactionPicker: true - }, { - done: reaction => { - if (reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - } - this.focus(); - }, - }, 'closed'); + os.pickReaction(this.$refs.reactButton, reaction => { + os.api('notes/reactions/create', { + noteId: this.appearNote.id, + reaction: reaction + }); + }, () => { + this.focus(); + }); }, reactDirectly(reaction) { diff --git a/src/client/components/ui/modal.vue b/src/client/components/ui/modal.vue index 405fa4aaa..1c8ae6390 100644 --- a/src/client/components/ui/modal.vue +++ b/src/client/components/ui/modal.vue @@ -1,11 +1,13 @@ <template> -<div class="mk-modal" v-hotkey.global="keymap" :style="{ pointerEvents: showing ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> +<div class="mk-modal" v-hotkey.global="keymap" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <transition :name="$store.state.animation ? 'modal-bg' : ''" appear> - <div class="bg _modalBg" v-if="showing" @click="onBgClick"></div> + <div class="bg _modalBg" v-if="manualShowing != null ? manualShowing : showing" @click="onBgClick"></div> </transition> <div class="content" :class="{ popup, fixed, top: position === 'top' }" @click.self="onBgClick" ref="content"> - <transition :name="$store.state.animation ? popup ? 'modal-popup-content' : 'modal-content' : ''" appear @after-leave="$emit('closed')" @after-enter="childRendered"> - <slot v-if="showing"></slot> + <transition :name="$store.state.animation ? popup ? 'modal-popup-content' : 'modal-content' : ''" appear @after-leave="$emit('closed')" @enter="$emit('opening')" @after-enter="childRendered"> + <div v-show="manualShowing != null ? manualShowing : showing"> + <slot></slot> + </div> </transition> </div> </div> @@ -29,6 +31,11 @@ export default defineComponent({ modal: true }, props: { + manualShowing: { + type: Boolean, + required: false, + default: null, + }, srcCenter: { type: Boolean, required: false @@ -40,7 +47,7 @@ export default defineComponent({ required: false } }, - emits: ['click', 'esc', 'closed'], + emits: ['opening', 'click', 'esc', 'close', 'closed'], data() { return { showing: true, @@ -60,15 +67,17 @@ export default defineComponent({ } }, mounted() { - this.fixed = getFixedContainer(this.src) != null; + this.$watch('src', () => { + this.fixed = getFixedContainer(this.src) != null; + }, { immediate: true }); this.$nextTick(() => { - if (!this.popup) return; - const popover = this.$refs.content as any; // TODO: ResizeObserver無くしたい new ResizeObserver((entries, observer) => { + if (!this.popup) return; + const rect = this.src.getBoundingClientRect(); const width = popover.offsetWidth; @@ -141,6 +150,7 @@ export default defineComponent({ close() { this.showing = false; + this.$emit('close'); }, onBgClick() { diff --git a/src/client/os.ts b/src/client/os.ts index a971eebd4..9fafb6db4 100644 --- a/src/client/os.ts +++ b/src/client/os.ts @@ -357,6 +357,43 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: }); } +let reactionPicker = null; +export async function pickReaction(src: HTMLElement, chosen, closed) { + if (reactionPicker) { + if (reactionPicker.opening) return; + + reactionPicker.opening = true; + reactionPicker.src.value = src; + reactionPicker.manualShowing.value = true; + reactionPicker.chosen = chosen; + reactionPicker.closed = closed; + } else { + reactionPicker = { + opening: true, + src: ref(src), + manualShowing: ref(true), + chosen, closed + }; + popup(import('@/components/emoji-picker-dialog.vue'), { + src: reactionPicker.src, + asReactionPicker: true, + manualShowing: reactionPicker.manualShowing + }, { + done: reaction => { + reactionPicker.chosen(reaction); + }, + close: () => { + reactionPicker.manualShowing.value = false; + }, + closed: () => { + reactionPicker.src.value = null; + reactionPicker.closed(); + reactionPicker.opening = false; + } + }); + } +} + export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { return new Promise((resolve, reject) => { let dispose; diff --git a/src/client/ui/chat/note.vue b/src/client/ui/chat/note.vue index d80978e18..75b92a32f 100644 --- a/src/client/ui/chat/note.vue +++ b/src/client/ui/chat/note.vue @@ -504,23 +504,14 @@ export default defineComponent({ pleaseLogin(); this.operating = true; this.blur(); - const { dispose } = await os.popup(import('@/components/emoji-picker-dialog.vue'), { - src: this.$refs.reactButton, - asReactionPicker: true - }, { - done: reaction => { - if (reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - } - }, - closed: () => { - this.operating = false; - this.focus(); - dispose(); - } + os.pickReaction(this.$refs.reactButton, reaction => { + os.api('notes/reactions/create', { + noteId: this.appearNote.id, + reaction: reaction + }); + }, () => { + this.operating = false; + this.focus(); }); },