Skip to content

Commit

Permalink
feat(frontend/reactions): リアクションミュート機能を追加しました (MisskeyIO#758)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattyatea committed Oct 22, 2024
1 parent caf51cf commit ae28938
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 5 deletions.
8 changes: 8 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,14 @@ export interface Locale extends ILocale {
* リノートのミュートを解除
*/
"renoteUnmute": string;
/**
* リアクションのミュート
*/
"mutedReactions": string;
/**
* リモートの絵文字をミュート
*/
"remoteCustomEmojiMuted": string;
/**
* ブロック
*/
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ mute: "ミュート"
unmute: "ミュート解除"
renoteMute: "リノートをミュート"
renoteUnmute: "リノートのミュートを解除"
mutedReactions: "リアクションのミュート"
remoteCustomEmojiMuted: "リモートの絵文字をミュート"
block: "ブロック"
unblock: "ブロック解除"
suspend: "凍結"
Expand Down
35 changes: 33 additions & 2 deletions packages/frontend/src/components/MkEmojiPickerDialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-projectSPDX-License-Identifier: AGPL-3.0-only
SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
Expand Down Expand Up @@ -31,15 +32,29 @@ SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-projectSPDX-License
@chosen="chosen"
@esc="modal?.close()"
/>
<div v-if="manualReactionInput" :class="$style.remoteReactionInputWrapper">
<span>{{ i18n.ts.remoteCustomEmojiMuted }}</span>
<MkInput v-model="remoteReactionName" placeholder=":emojiname@host:" autocapitalize="off"/>
<MkButton :disabled="!(remoteReactionName && remoteReactionName[0] === ':')" @click="chosen(remoteReactionName)">
{{ i18n.ts.add }}
</MkButton>
<div :class="$style.emojiContainer">
<MkCustomEmoji v-if="remoteReactionName && remoteReactionName[0] === ':' " :class="$style.emoji" :name="remoteReactionName" :normal="true"/>
</div>
</div>
</MkModal>
</template>

<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue';
import { shallowRef, ref } from 'vue';
import { i18n } from '@/i18n.js';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
import { defaultStore } from '@/store.js';
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';

const props = withDefaults(defineProps<{
manualShowing?: boolean | null;
Expand All @@ -49,12 +64,14 @@ const props = withDefaults(defineProps<{
asReactionPicker?: boolean;
targetNote?: Misskey.entities.Note;
choseAndClose?: boolean;
manualReactionInput?: boolean;
}>(), {
manualShowing: null,
showPinned: true,
pinnedEmojis: undefined,
asReactionPicker: false,
choseAndClose: true,
manualReactionInput: false,
});

const emit = defineEmits<{
Expand All @@ -66,6 +83,8 @@ const emit = defineEmits<{
const modal = shallowRef<InstanceType<typeof MkModal>>();
const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();

const remoteReactionName = ref('');

function chosen(emoji: string) {
emit('done', emoji);
if (props.choseAndClose) {
Expand All @@ -90,4 +109,16 @@ function opening() {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}

.remoteReactionInputWrapper {
margin-top: var(--margin);
padding: 16px;
border-radius: var(--radius);
background: var(--popup);
}

.emojiContainer {
height: 48px;
width: 48px;
}
</style>
11 changes: 10 additions & 1 deletion packages/frontend/src/components/MkReactionsViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as Misskey from 'misskey-js';
import { inject, watch, ref } from 'vue';
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
import { defaultStore } from '@/store.js';
import { $i } from '@/account.js';

const props = withDefaults(defineProps<{
note: Misskey.entities.Note & {
Expand All @@ -46,6 +47,13 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m
reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction];
}

function shouldDisplayReaction([reaction]: [string, number]): boolean {
if (!$i) return true; // 非ログイン状態なら全部のリアクションを見れるように
if (reaction === props.note.myReaction) return true; // 自分がつけたリアクションなら表示する
if (!defaultStore.state.mutedReactions.includes(reaction.replace('@.', ''))) return true; // ローカルの絵文字には @. というsuffixがつくのでそれを消してから比較してあげる
return false;
}

function onMockToggleReaction(emoji: string, count: number) {
if (!mock) return;

Expand Down Expand Up @@ -80,7 +88,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]);
}

reactions.value = newReactions;
reactions.value = newReactions.filter(shouldDisplayReaction);
}, { immediate: true, deep: true });
</script>

Expand All @@ -104,6 +112,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
flex-wrap: wrap;
align-items: center;
margin: 4px -2px 0 -2px;
max-width: 100%;

&:empty {
display: none;
Expand Down
58 changes: 57 additions & 1 deletion packages/frontend/src/pages/settings/mute-block.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>

<MkFolder>
<template #icon><i class="ti ti-message-off"></i></template>
<template #label>{{ i18n.ts.mutedReactions }}</template>

<div class="_gaps">
<div v-panel style="border-radius: var(--radius); padding: var(--margin);">
<button v-for="emoji in mutedReactions" class="_button" :class="$style.emojisItem" @click="removeReaction(emoji, $event)">
<MkCustomEmoji v-if="emoji && emoji[0] === ':'" :name="emoji"/>
<MkEmoji v-else :emoji="emoji ? emoji : 'null'"/>
</button>
<button class="_button" @click="chooseReaction">
<i class="ti ti-plus"></i>
</button>
</div>
</div>
</MkFolder>

<MkFolder>
<template #icon><i class="ti ti-planet-off"></i></template>
<template #label>{{ i18n.ts.instanceMute }}</template>

Expand Down Expand Up @@ -132,7 +149,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue';
import { ref, computed, watch, Ref } from 'vue';
import * as Misskey from 'misskey-js';
import XInstanceMute from './mute-block.instance-mute.vue';
import XUserWordMute from './mute-block.user-word-mute.vue';
Expand All @@ -147,6 +164,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { infoImageUrl } from '@/instance.js';
import { signinRequired } from '@/account.js';
import MkFolder from '@/components/MkFolder.vue';
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
import MkEmoji from '@/components/global/MkEmoji.vue';
import { defaultStore } from '@/store.js';

const $i = signinRequired();
Expand All @@ -170,6 +189,38 @@ const expandedRenoteMuteItems = ref([]);
const expandedMuteItems = ref([]);
const expandedBlockItems = ref([]);

const mutedReactions = ref<string[]>(defaultStore.state.mutedReactions);

watch(mutedReactions, () => {
defaultStore.set('mutedReactions', mutedReactions.value);
}, {
deep: true,
});

const chooseReaction = (ev: MouseEvent) => pickEmoji(mutedReactions, ev);
const removeReaction = (reaction: string, ev: MouseEvent) => remove(mutedReactions, reaction, ev);

function remove(itemsRef: Ref<string[]>, reaction: string, ev: MouseEvent) {
os.popupMenu([{
text: i18n.ts.remove,
action: () => {
itemsRef.value = itemsRef.value.filter(x => x !== reaction);
},
}], ev.currentTarget ?? ev.target);
}

async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) {
os.pickEmoji(ev.currentTarget ?? ev.target, {
showPinned: false,
manualReactionInput: true,
}).then(it => {
const emoji = it;
if (!itemsRef.value.includes(emoji)) {
itemsRef.value.push(emoji);
}
});
}

async function unrenoteMute(user, ev) {
os.popupMenu([{
text: i18n.ts.renoteUnmute,
Expand Down Expand Up @@ -287,4 +338,9 @@ definePageMetadata(() => ({
transform: rotateX(180deg);
}
}

.emojisItem{
display: inline-block;
padding: 8px;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'sound_note',
'sound_noteMy',
'sound_notification',
'mutedReactions',
];
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
'lightTheme',
Expand Down
6 changes: 5 additions & 1 deletion packages/frontend/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,11 @@ export const defaultStore = markRaw(
where: 'device',
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
},
}),
mutedReactions: {
where: 'account',
default: [] as string[],
},
}),
);

// TODO: 他のタブと永続化されたstateを同期
Expand Down

0 comments on commit ae28938

Please sign in to comment.