diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ef3a36ab..694f330120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ ### Client - Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加 - Feat: データセーバーでコードハイライトの読み込みを削減できるように +- Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336 - Enhance: 絵文字のオートコンプリート機能強化 #12364 - Enhance: ユーザーのRawデータを表示するページが復活 - Enhance: リアクション選択時に音を鳴らせるように diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 71236e4c53..88e2f83895 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -19,6 +19,7 @@ import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js import { mainRouter } from '@/router.js'; import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; +import { emojiPicker } from '@/scripts/emoji-picker.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( @@ -30,6 +31,7 @@ export async function mainBoot() { )); reactionPicker.init(); + emojiPicker.init(); if (isClientUpdated && $i) { popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed'); diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index ecff2b5ace..b5e5a0260c 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only </section> <div v-if="tab === 'index'" class="group index"> - <section v-if="showPinned"> + <section v-if="showPinned && pinned.length > 0"> <div class="body"> <button v-for="emoji in pinned" @@ -137,7 +137,7 @@ const searchEl = shallowRef<HTMLInputElement>(); const emojisEl = shallowRef<HTMLDivElement>(); const { - reactions: pinned, + reactions: pinnedReactions, reactionPickerSize, reactionPickerWidth, reactionPickerHeight, @@ -145,6 +145,7 @@ const { recentlyUsedEmojis, } = defaultStore.reactiveState; +const pinned = computed(() => props.asReactionPicker ? pinnedReactions.value : []); // TODO: 非リアクションの絵文字ピッカー用のpinned絵文字を設定可能にする? const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1); const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3); const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2); diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 9d3132c540..05b137e335 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -31,20 +31,21 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { shallowRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; import { defaultStore } from '@/store.js'; -withDefaults(defineProps<{ +const props = withDefaults(defineProps<{ manualShowing?: boolean | null; src?: HTMLElement; showPinned?: boolean; asReactionPicker?: boolean; + choseAndClose?: boolean; }>(), { manualShowing: null, showPinned: true, asReactionPicker: false, + choseAndClose: true, }); const emit = defineEmits<{ @@ -53,21 +54,23 @@ const emit = defineEmits<{ (ev: 'closed'): void; }>(); -const modal = shallowRef<InstanceType<typeof MkModal>>(); -const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>(); +const modal = $shallowRef<InstanceType<typeof MkModal>>(); +const picker = $shallowRef<InstanceType<typeof MkEmojiPicker>>(); function chosen(emoji: any) { emit('done', emoji); - modal.value?.close(); + if (props.choseAndClose) { + modal?.close(); + } } function opening() { - picker.value?.reset(); - picker.value?.focus(); + picker?.reset(); + picker?.focus(); // 何故かちょっと待たないとフォーカスされない setTimeout(() => { - picker.value?.focus(); + picker?.focus(); }, 10); } </script> diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 3244f743ac..0445536ae5 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -124,6 +124,7 @@ import { deepClone } from '@/scripts/clone.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { miLocalStorage } from '@/local-storage.js'; import { claimAchievement } from '@/scripts/achievements.js'; +import { emojiPicker } from '@/scripts/emoji-picker.js'; const modal = inject('modal'); @@ -845,7 +846,15 @@ function insertMention() { } async function insertEmoji(ev: MouseEvent) { - os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl); + emojiPicker.show( + ev.currentTarget ?? ev.target, + emoji => { + insertTextAtCursor(textareaEl, emoji); + }, + () => { + focus(); + }, + ); } function showActions(ev) { diff --git a/packages/frontend/src/scripts/emoji-picker.ts b/packages/frontend/src/scripts/emoji-picker.ts new file mode 100644 index 0000000000..d6d6bf1245 --- /dev/null +++ b/packages/frontend/src/scripts/emoji-picker.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { defineAsyncComponent, Ref, ref } from 'vue'; +import { popup } from '@/os.js'; + +/** + * 絵文字ピッカーを表示する。 + * 類似の機能として{@link ReactionPicker}が存在しているが、この機能とは動きが異なる。 + * 投稿フォームなどで絵文字を選択する時など、絵文字ピックアップ後でもダイアログが消えずに残り、 + * 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。 + */ +class EmojiPicker { + private src: Ref<HTMLElement | null> = ref(null); + private manualShowing = ref(false); + private onChosen?: (emoji: string) => void; + private onClosed?: () => void; + + constructor() { + // nop + } + + public async init() { + await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { + src: this.src, + asReactionPicker: false, + manualShowing: this.manualShowing, + choseAndClose: false, + }, { + done: emoji => { + if (this.onChosen) this.onChosen(emoji); + }, + close: () => { + this.manualShowing.value = false; + }, + closed: () => { + this.src.value = null; + if (this.onClosed) this.onClosed(); + }, + }); + } + + public show( + src: HTMLElement, + onChosen: EmojiPicker['onChosen'], + onClosed: EmojiPicker['onClosed'], + ) { + this.src.value = src; + this.manualShowing.value = true; + this.onChosen = onChosen; + this.onClosed = onClosed; + } +} + +export const emojiPicker = new EmojiPicker();