diff --git a/locales/index.d.ts b/locales/index.d.ts
index e564b47270..de157a91d2 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -5417,6 +5417,18 @@ export interface Locale extends ILocale {
* スクロールして閉じる
*/
"scrollToClose": string;
+ /**
+ * 絵文字ミュート
+ */
+ "emojiMute": string;
+ /**
+ * {x}をミュート
+ */
+ "muteX": ParameterizedString<"x">;
+ /**
+ * {x}のミュートを解除
+ */
+ "unmuteX": ParameterizedString<"x">;
"_chat": {
/**
* まだメッセージはありません
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 7d2edf7194..2edd825229 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1349,6 +1349,9 @@ goToDeck: "デッキへ戻る"
federationJobs: "連合ジョブ"
driveAboutTip: "ドライブでは、過去にアップロードしたファイルの一覧が表示されます。
\nノートに添付する際に再利用したり、あとで投稿するファイルを予めアップロードしておくこともできます。
\nファイルを削除すると、今までそのファイルを使用した全ての場所(ノート、ページ、アバター、バナー等)からも見えなくなるので注意してください。
\nフォルダを作って整理することもできます。"
scrollToClose: "スクロールして閉じる"
+emojiMute: "絵文字ミュート"
+muteX: "{x}をミュート"
+unmuteX: "{x}のミュートを解除"
_chat:
noMessagesYet: "まだメッセージはありません"
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index dda45ceaa2..64cdb118e5 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-:{{ customEmojiName }}:
+:{{ customEmojiName }}:
();
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 = props.name.startsWith(':') ? props.name : `:${props.name}${props.host ? `@${props.host}` : ''}:`;
+const isMuted = computed(() => prefer.r.mutingEmojis.value.includes(emojiCodeToMute));
+const shouldMute = computed(() => !props.ignoreMuted && isMuted.value);
const rawUrl = computed(() => {
if (props.url) {
@@ -95,14 +99,18 @@ function onClick(ev: MouseEvent) {
menuItems.push({
type: 'label',
text: `:${props.name}:`,
- }, {
- text: i18n.ts.copy,
- icon: 'ti ti-copy',
- action: () => {
- copyToClipboard(`:${props.name}:`);
- },
});
+ if (isLocal.value) {
+ menuItems.push({
+ text: i18n.ts.copy,
+ icon: 'ti ti-copy',
+ action: () => {
+ copyToClipboard(`:${props.name}:`);
+ },
+ });
+ }
+
if (props.menuReaction && react) {
menuItems.push({
text: i18n.ts.doReaction,
@@ -113,21 +121,33 @@ function onClick(ev: MouseEvent) {
});
}
+ if (isLocal.value) {
+ menuItems.push({
+ type: 'divider',
+ }, {
+ text: i18n.ts.info,
+ icon: 'ti ti-info-circle',
+ action: async () => {
+ const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
+ emoji: await misskeyApiGet('emoji', {
+ name: customEmojiName.value,
+ }),
+ }, {
+ closed: () => dispose(),
+ });
+ },
+ });
+ }
+
menuItems.push({
- text: i18n.ts.info,
- icon: 'ti ti-info-circle',
+ text: i18n.ts.mute,
+ icon: 'ti ti-mood-off',
action: async () => {
- const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
- emoji: await misskeyApiGet('emoji', {
- name: customEmojiName.value,
- }),
- }, {
- closed: () => dispose(),
- });
+ await mute();
},
});
- if ($i?.isModerator ?? $i?.isAdmin) {
+ if (($i?.isModerator ?? $i?.isAdmin) && isLocal.value) {
menuItems.push({
text: i18n.ts.edit,
icon: 'ti ti-pencil',
@@ -152,6 +172,21 @@ async function edit(name: string) {
});
}
+function mute() {
+ os.confirm({
+ type: 'question',
+ title: i18n.tsx.muteX({ x: emojiCodeToMute }),
+ }).then(({ canceled }) => {
+ if (canceled) {
+ return;
+ }
+ const mutedEmojis = prefer.r.mutingEmojis.value;
+ if (!mutedEmojis.includes(emojiCodeToMute)) {
+ prefer.commit('mutingEmojis', [...mutedEmojis, emojiCodeToMute]);
+ }
+ });
+}
+
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index fc9cd8f892..3a7718ee3e 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -49,6 +49,20 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+ {{ i18n.ts.emojiMute }}
+
+
+
+
+
+
+
import { ref, computed, watch } from 'vue';
+import XEmojiMute from './mute-block.emoji-mute.vue';
import XInstanceMute from './mute-block.instance-mute.vue';
import XWordMute from './mute-block.word-mute.vue';
import MkPagination from '@/components/MkPagination.vue';
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index 96f43bb2f6..56fa09ae27 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -342,6 +342,9 @@ export const PREF_DEF = {
plugins: {
default: [] as Plugin[],
},
+ mutingEmojis: {
+ default: [] as string[],
+ },
'sound.masterVolume': {
default: 0.3,