diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 951447f15a..204a011d6e 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -22,6 +22,7 @@ import { computed, inject, onMounted, useTemplateRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { getUnicodeEmoji } from '@@/js/emojilist.js'; import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue'; +import type { MenuItem } from '@/types/menu'; import XDetails from '@/components/MkReactionsViewer.details.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import * as os from '@/os.js'; @@ -36,6 +37,7 @@ import { checkReactionPermissions } from '@/utility/check-reaction-permissions.j import { customEmojisMap } from '@/custom-emojis.js'; import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; +import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as isEmojiMuted } from '@/utility/emoji-mute.js'; const props = defineProps<{ reaction: string; @@ -59,6 +61,7 @@ const canToggle = computed(() => { return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value); }); const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); +const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.'); async function toggleReaction() { if (!canToggle.value) return; @@ -118,21 +121,55 @@ async function toggleReaction() { } async function menu(ev) { - if (!canGetInfo.value) return; + let menuItems: MenuItem[] = []; - os.popupMenu([{ - text: i18n.ts.info, - icon: 'ti ti-info-circle', - action: async () => { - const { dispose } = os.popup(MkCustomEmojiDetailedDialog, { - emoji: await misskeyApiGet('emoji', { - name: props.reaction.replace(/:/g, '').replace(/@\./, ''), - }), - }, { - closed: () => dispose(), - }); - }, - }], ev.currentTarget ?? ev.target); + if (canGetInfo.value) { + menuItems.push({ + text: i18n.ts.info, + icon: 'ti ti-info-circle', + action: async () => { + const { dispose } = os.popup(MkCustomEmojiDetailedDialog, { + emoji: await misskeyApiGet('emoji', { + name: props.reaction.replace(/:/g, '').replace(/@\./, ''), + }), + }, { + closed: () => dispose(), + }); + }, + }); + } + + if (isEmojiMuted(props.reaction).value) { + menuItems.push({ + text: i18n.ts.unmute, + icon: 'ti ti-mood-smile', + action: () => { + os.confirm({ + type: 'question', + title: i18n.tsx.unmuteX({ x: isLocalCustomEmoji ? `:${emojiName.value}:` : props.reaction }), + }).then(({ canceled }) => { + if (canceled) return; + unmuteEmoji(props.reaction); + }); + }, + }); + } else { + menuItems.push({ + text: i18n.ts.mute, + icon: 'ti ti-mood-off', + action: () => { + os.confirm({ + type: 'question', + title: i18n.tsx.muteX({ x: isLocalCustomEmoji ? `:${emojiName.value}:` : props.reaction }), + }).then(({ canceled }) => { + if (canceled) return; + muteEmoji(props.reaction); + }); + }, + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } function anime() { diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index ee84f96224..8f03d194b1 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -60,7 +60,7 @@ 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('@'))); const emojiCodeToMute = makeEmojiMuteKey(props); -const isMuted = computed(() => checkEmojiMuted(emojiCodeToMute)); +const isMuted = checkEmojiMuted(emojiCodeToMute); const shouldMute = computed(() => !props.ignoreMuted && isMuted.value); const rawUrl = computed(() => { @@ -174,9 +174,12 @@ async function edit(name: string) { } function mute() { + const titleEmojiName = isLocal.value + ? `:${customEmojiName.value}:` + : emojiCodeToMute; os.confirm({ type: 'question', - title: i18n.tsx.muteX({ x: emojiCodeToMute }), + title: i18n.tsx.muteX({ x: titleEmojiName }), }).then(({ canceled }) => { if (canceled) { return; diff --git a/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue b/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue index e269cda0c5..e4df442c22 100644 --- a/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue +++ b/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue @@ -34,24 +34,15 @@ import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; -import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as isMuted } from '@/utility/emoji-mute.js'; +import { + mute as muteEmoji, + unmute as unmuteEmoji, + extractCustomEmojiName as customEmojiName, + extractCustomEmojiHost as customEmojiHost, +} from '@/utility/emoji-mute.js'; const emojis = prefer.model('mutingEmojis'); -function customEmojiName (name:string) { - return (name[0] === ':' ? name.substring(1, name.length - 1) : name).replace('@.', '').split('@')[0]; -} - -function customEmojiHost (name:string) { - // nameは:emojiName@host:の形式 - // 取り出したい部分はhostなので、@以降を取り出す - const index = name.indexOf('@'); - if (index === -1) { - return null; - } - return name.substring(index + 1, name.length - 1); -} - function getHTMLElement(ev: MouseEvent): HTMLElement { const target = ev.currentTarget ?? ev.target; return target as HTMLElement; diff --git a/packages/frontend/src/utility/emoji-mute.ts b/packages/frontend/src/utility/emoji-mute.ts index 759515f166..0693c5d473 100644 --- a/packages/frontend/src/utility/emoji-mute.ts +++ b/packages/frontend/src/utility/emoji-mute.ts @@ -11,22 +11,50 @@ export function makeEmojiMuteKey(props: { name: string; host?: string | null }) return props.name.startsWith(':') ? props.name : `:${props.name}${props.host ? `@${props.host}` : ''}:`; } +export function extractCustomEmojiName (name:string) { + return (name[0] === ':' ? name.substring(1, name.length - 1) : name).replace('@.', '').split('@')[0]; +} + +export function extractCustomEmojiHost (name:string) { + // nameは:emojiName@host:の形式 + // 取り出したい部分はhostなので、@以降を取り出す + const index = name.indexOf('@'); + if (index === -1) { + return null; + } + const host = name.substring(index + 1, name.length - 1); + if (host === '' || host === '.') { + return null; + } + return host; +} + export function mute(emoji: string) { + const isCustomEmoji = emoji.startsWith(':') && emoji.endsWith(':'); + const emojiMuteKey = isCustomEmoji ? + makeEmojiMuteKey({ name: extractCustomEmojiName(emoji), host: extractCustomEmojiHost(emoji) }) : + emoji; const mutedEmojis = prefer.r.mutingEmojis.value; if (!mutedEmojis.includes(emoji)) { - prefer.commit('mutingEmojis', [...mutedEmojis, emoji]); + prefer.commit('mutingEmojis', [...mutedEmojis, emojiMuteKey]); } } export function unmute(emoji:string) { + const isCustomEmoji = emoji.startsWith(':') && emoji.endsWith(':'); + const emojiMuteKey = isCustomEmoji ? + makeEmojiMuteKey({ name: extractCustomEmojiName(emoji), host: extractCustomEmojiHost(emoji) }) : + emoji; const mutedEmojis = prefer.r.mutingEmojis.value; - const index = mutedEmojis.indexOf(emoji); - if (index !== -1) { - mutedEmojis.splice(index, 1); - prefer.commit('mutingEmojis', mutedEmojis); - } + console.log('unmute', emoji, emojiMuteKey); + console.log('mutedEmojis', mutedEmojis); + prefer.commit('mutingEmojis', mutedEmojis.filter((e) => e !== emojiMuteKey)); } export function checkMuted(emoji: string) { - return computed(() => prefer.r.mutingEmojis.value.includes(emoji)); + const isCustomEmoji = emoji.startsWith(':') && emoji.endsWith(':'); + const emojiMuteKey = isCustomEmoji ? + makeEmojiMuteKey({ name: extractCustomEmojiName(emoji), host: extractCustomEmojiHost(emoji) }) : + emoji; + return computed(() => prefer.r.mutingEmojis.value.includes(emojiMuteKey)); }