Merge pull request #23 from team-shahu/feat/muted-reaction

feat: リアクションした人一覧がブロック・ミュートを考慮するようにする設定
This commit is contained in:
mai 2024-09-11 19:32:27 +09:00 committed by hijiki
parent 759109e7a1
commit a313fc6366
8 changed files with 54 additions and 6 deletions

View File

@ -2824,3 +2824,6 @@ _contextMenu:
app: "Application" app: "Application"
appWithShift: "Application with shift key" appWithShift: "Application with shift key"
native: "Native" native: "Native"
_reactionChecksMuting:
title: "Check mutings when get reactions"
caption: "Check mutings when get reactions, but cache does not work and may increase traffic"

10
locales/index.d.ts vendored
View File

@ -10895,6 +10895,16 @@ export interface Locale extends ILocale {
*/ */
"native": string; "native": string;
}; };
"_reactionChecksMuting": {
/**
*
*/
"title": string;
/**
*
*/
"caption": string;
};
} }
declare const locales: { declare const locales: {
[lang: string]: Locale; [lang: string]: Locale;

View File

@ -2899,3 +2899,7 @@ _contextMenu:
app: "アプリケーション" app: "アプリケーション"
appWithShift: "Shiftキーでアプリケーション" appWithShift: "Shiftキーでアプリケーション"
native: "ブラウザのUI" native: "ブラウザのUI"
_reactionChecksMuting:
title: "リアクションでミュートを考慮する"
caption: "リアクションがミュートを考慮しますが、キャッシュが効かず通信量が増えることがあります。"

View File

@ -4,13 +4,12 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Brackets, type FindOptionsWhere } from 'typeorm';
import type { NoteReactionsRepository } from '@/models/_.js'; import type { NoteReactionsRepository } from '@/models/_.js';
import type { MiNoteReaction } from '@/models/NoteReaction.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { QueryService } from '@/core/QueryService.js'; import { QueryService } from '@/core/QueryService.js';
import { CacheService } from '@/core/CacheService.js';
export const meta = { export const meta = {
tags: ['notes', 'reactions'], tags: ['notes', 'reactions'],
@ -59,6 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteReactionEntityService: NoteReactionEntityService, private noteReactionEntityService: NoteReactionEntityService,
private queryService: QueryService, private queryService: QueryService,
private cacheService: CacheService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId)
@ -66,6 +66,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('reaction.user', 'user') .leftJoinAndSelect('reaction.user', 'user')
.leftJoinAndSelect('reaction.note', 'note'); .leftJoinAndSelect('reaction.note', 'note');
if (me != null) {
const [userIdsWhoMeMuting, userIdsWhoBlockingMe] = await Promise.all([
this.cacheService.userMutingsCache.get(me.id),
this.cacheService.userBlockedCache.get(me.id),
]);
query.andWhere('reaction.userId NOT IN (:...userIds)', { userIds: Array.from(userIdsWhoMeMuting ?? []).concat(Array.from(userIdsWhoBlockingMe ?? [])) });
}
if (ps.type) { if (ps.type) {
// ローカルリアクションはホスト名が . とされているが // ローカルリアクションはホスト名が . とされているが
// DB 上ではそうではないので、必要に応じて変換 // DB 上ではそうではないので、必要に応じて変換

View File

@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAvatar :class="$style.avatar" :user="u"/> <MkAvatar :class="$style.avatar" :user="u"/>
<MkUserName :user="u" :nowrap="true"/> <MkUserName :user="u" :nowrap="true"/>
</div> </div>
<div v-if="count <= 0" :class="$style.user"> {{ i18n.ts.noUsers }} </div>
<div v-if="count > 10" :class="$style.more">+{{ count - 10 }}</div> <div v-if="count > 10" :class="$style.more">+{{ count - 10 }}</div>
</div> </div>
</div> </div>
@ -26,6 +27,7 @@ import { } from 'vue';
import MkTooltip from './MkTooltip.vue'; import MkTooltip from './MkTooltip.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue';
import { getEmojiName } from '@/scripts/emojilist.js'; import { getEmojiName } from '@/scripts/emojilist.js';
import { i18n } from '@/i18n';
defineProps<{ defineProps<{
showing: boolean; showing: boolean;

View File

@ -36,6 +36,8 @@ import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.j
import { customEmojisMap } from '@/custom-emojis.js'; import { customEmojisMap } from '@/custom-emojis.js';
import { getUnicodeEmoji } from '@/scripts/emojilist.js'; import { getUnicodeEmoji } from '@/scripts/emojilist.js';
const reactionChecksMuting = computed(defaultStore.makeGetterSetter('reactionChecksMuting'));
const props = defineProps<{ const props = defineProps<{
reaction: string; reaction: string;
count: number; count: number;
@ -146,7 +148,9 @@ onMounted(() => {
if (!mock) { if (!mock) {
useTooltip(buttonEl, async (showing) => { useTooltip(buttonEl, async (showing) => {
const reactions = await misskeyApiGet('notes/reactions', { const useGet = !reactionChecksMuting.value;
const apiCall = useGet ? misskeyApiGet : misskeyApi;
const reactions = await apiCall('notes/reactions', {
noteId: props.note.id, noteId: props.note.id,
type: props.reaction, type: props.reaction,
limit: 10, limit: 10,
@ -154,12 +158,13 @@ if (!mock) {
}); });
const users = reactions.map(x => x.user); const users = reactions.map(x => x.user);
const count = users.length;
const { dispose } = os.popup(XDetails, { const { dispose } = os.popup(XDetails, {
showing, showing,
reaction: props.reaction, reaction: props.reaction,
users, users,
count: props.count, count,
targetElement: buttonEl.value, targetElement: buttonEl.value,
}, { }, {
closed: () => dispose(), closed: () => dispose(),

View File

@ -235,6 +235,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch> <MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch> <MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch> <MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
<MkSwitch v-model="reactionChecksMuting">
{{ i18n.ts._reactionChecksMuting.title }}<span class="_beta">{{ i18n.ts.originalFeature }}</span>
<template #caption>{{ i18n.ts._reactionChecksMuting.caption }}</template>
</MkSwitch>
</div> </div>
<MkSelect v-model="serverDisconnectedBehavior"> <MkSelect v-model="serverDisconnectedBehavior">
<template #label>{{ i18n.ts.whenServerDisconnected }}</template> <template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@ -435,6 +440,8 @@ const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('u
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow')); const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia')); const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu')); const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
const searchEngine = computed(defaultStore.makeGetterSetter('searchEngine'));
const reactionChecksMuting = computed(defaultStore.makeGetterSetter('reactionChecksMuting'));
watch(lang, () => { watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string); miLocalStorage.setItem('lang', lang.value as string);

View File

@ -532,10 +532,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
contextMenu: { contextMenu: {
where: 'device', where: 'device',
default: 'app' as 'app' | 'appWithShift' | 'native', default: 'app' as 'app' | 'appWithShift' | 'native',
}, },
sound_masterVolume: { sound_masterVolume: {
where: 'device', where: 'device',
@ -565,6 +565,14 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore, default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
}, },
searchEngine: {
where: 'device',
default: 'https://google.com/search?q=',
},
reactionChecksMuting: {
where: 'device',
default: true,
},
})); }));
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期