From 22f99b42f6e34154baff568b831099f84cb9901f Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 25 Mar 2025 18:30:28 +0900 Subject: [PATCH] enhance(frontend): refactor and improve ux --- packages/frontend/src/components/MkNote.vue | 3 +- .../src/components/MkNoteDetailed.vue | 4 ++- .../src/components/global/MkCustomEmoji.vue | 5 ++- .../src/components/global/MkEmoji.vue | 5 ++- packages/frontend/src/di.ts | 1 + packages/frontend/src/pages/chat/XMessage.vue | 31 +++++++++++++++++-- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 73ff85b150..07da1bd4d9 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -379,7 +379,8 @@ const keymap = { }, } as const satisfies Keymap; -provide('react', (reaction: string) => { +provide(DI.mfmEmojiReactCallback, (reaction) => { + sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { noteId: appearNote.value.id, reaction: reaction, diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 4f74432041..a26eb808e4 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -256,6 +256,7 @@ import { isEnabledUrlPreview } from '@/instance.js'; import { getAppearNote } from '@/utility/get-appear-note.js'; import { prefer } from '@/preferences.js'; import { getPluginHandlers } from '@/plugin.js'; +import { DI } from '@/di.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -337,7 +338,8 @@ const keymap = { }, } as const satisfies Keymap; -provide('react', (reaction: string) => { +provide(DI.mfmEmojiReactCallback, (reaction) => { + sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { noteId: appearNote.value.id, reaction: reaction, diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index af8f1d035e..dda45ceaa2 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -35,11 +35,11 @@ import { customEmojisMap } from '@/custom-emojis.js'; import * as os from '@/os.js'; import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; -import * as sound from '@/utility/sound.js'; import { i18n } from '@/i18n.js'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; +import { DI } from '@/di.js'; const props = defineProps<{ name: string; @@ -53,7 +53,7 @@ const props = defineProps<{ fallbackToImage?: boolean; }>(); -const react = inject<((name: string) => void) | null>('react', null); +const react = inject(DI.mfmEmojiReactCallback); const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', '')); const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@'))); @@ -109,7 +109,6 @@ function onClick(ev: MouseEvent) { icon: 'ti ti-plus', action: () => { react(`:${props.name}:`); - sound.playMisskeySfx('reaction'); }, }); } diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index ca67a28b70..198c0d8ace 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -15,9 +15,9 @@ import { char2fluentEmojiFilePath, char2twemojiFilePath } from '@@/js/emoji-base import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; -import * as sound from '@/utility/sound.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; +import { DI } from '@/di.js'; const props = defineProps<{ emoji: string; @@ -25,7 +25,7 @@ const props = defineProps<{ menuReaction?: boolean; }>(); -const react = inject<((name: string) => void) | null>('react', null); +const react = inject(DI.mfmEmojiReactCallback); const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; @@ -59,7 +59,6 @@ function onClick(ev: MouseEvent) { icon: 'ti ti-plus', action: () => { react(props.emoji); - sound.playMisskeySfx('reaction'); }, }); } diff --git a/packages/frontend/src/di.ts b/packages/frontend/src/di.ts index f9fc282315..b58c8c9659 100644 --- a/packages/frontend/src/di.ts +++ b/packages/frontend/src/di.ts @@ -14,4 +14,5 @@ export const DI = { viewId: Symbol() as InjectionKey<string>, currentStickyTop: Symbol() as InjectionKey<Ref<number>>, currentStickyBottom: Symbol() as InjectionKey<Ref<number>>, + mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>, }; diff --git a/packages/frontend/src/pages/chat/XMessage.vue b/packages/frontend/src/pages/chat/XMessage.vue index 7ee30e7e8d..cbb817de05 100644 --- a/packages/frontend/src/pages/chat/XMessage.vue +++ b/packages/frontend/src/pages/chat/XMessage.vue @@ -10,7 +10,16 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="!isMe && prefer.s['chat.showSenderName']" :class="$style.header"><MkUserName :user="message.fromUser"/></div> <MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :accented="isMe"> <div v-if="!message.isDeleted" :class="$style.content"> - <Mfm v-if="message.text" ref="text" class="_selectable" :text="message.text" :i="$i"/> + <Mfm + v-if="message.text" + ref="text" + class="_selectable" + :text="message.text" + :i="$i" + :nyaize="'respect'" + :enableEmojiMenu="true" + :enableEmojiMenuReaction="true" + /> <MkMediaList v-if="message.file" :mediaList="[message.file]" :class="$style.file"/> </div> <div v-else :class="$style.content"> @@ -47,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, defineAsyncComponent } from 'vue'; +import { computed, defineAsyncComponent, provide } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { url } from '@@/js/config.js'; @@ -65,6 +74,7 @@ import { reactionPicker } from '@/utility/reaction-picker.js'; import * as sound from '@/utility/sound.js'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import { prefer } from '@/preferences.js'; +import { DI } from '@/di.js'; const $i = ensureSignin(); @@ -76,10 +86,17 @@ const props = defineProps<{ const isMe = computed(() => props.message.fromUserId === $i.id); const urls = computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []); +provide(DI.mfmEmojiReactCallback, (reaction) => { + sound.playMisskeySfx('reaction'); + misskeyApi('chat/messages/react', { + messageId: props.message.id, + reaction: reaction, + }); +}); + function react(ev: MouseEvent) { reactionPicker.show(ev.currentTarget ?? ev.target, null, async (reaction) => { sound.playMisskeySfx('reaction'); - misskeyApi('chat/messages/react', { messageId: props.message.id, reaction: reaction, @@ -93,6 +110,14 @@ function onReactionClick(record: Misskey.entities.ChatMessage['reactions'][0]) { messageId: props.message.id, reaction: record.reaction, }); + } else { + if (!props.message.reactions.some(r => r.user.id === $i.id && r.reaction === record.reaction)) { + sound.playMisskeySfx('reaction'); + misskeyApi('chat/messages/react', { + messageId: props.message.id, + reaction: record.reaction, + }); + } } }