feat: notification muting
This commit is contained in:
parent
ab84c9afa0
commit
03c64c296b
@ -1331,6 +1331,9 @@ normalize: "Normalize"
|
|||||||
normalizeConfirm: "After normalization, the account will be irreversible. Are you sure you want to do this?"
|
normalizeConfirm: "After normalization, the account will be irreversible. Are you sure you want to do this?"
|
||||||
normalizeDescription: "Normalization is a feature for bulk data wiping and account suspension of users, which has a similar effect to deleting an account and closes all reports after it is run. Please note that this is an irreversible action."
|
normalizeDescription: "Normalization is a feature for bulk data wiping and account suspension of users, which has a similar effect to deleting an account and closes all reports after it is run. Please note that this is an irreversible action."
|
||||||
useNormalization: "Show the Normalize menu"
|
useNormalization: "Show the Normalize menu"
|
||||||
|
alsoMuteNotification: "Also mute notifications from this user"
|
||||||
|
muteNotification: "Mute notifications"
|
||||||
|
unmuteNotification: "Unmute notifications"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "How to play"
|
howToPlay: "How to play"
|
||||||
hold: "Hold"
|
hold: "Hold"
|
||||||
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
@ -5496,6 +5496,18 @@ export interface Locale extends ILocale {
|
|||||||
* 選択した項目のみ許可
|
* 選択した項目のみ許可
|
||||||
*/
|
*/
|
||||||
"consentSelected": string;
|
"consentSelected": string;
|
||||||
|
/**
|
||||||
|
* このユーザーが送信する通知も一緒にミュートする
|
||||||
|
*/
|
||||||
|
"alsoMuteNotification": string;
|
||||||
|
/**
|
||||||
|
* 通知をミュートする
|
||||||
|
*/
|
||||||
|
"muteNotification": string;
|
||||||
|
/**
|
||||||
|
* 通知をミュート解除する
|
||||||
|
*/
|
||||||
|
"unmuteNotification": string;
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
/**
|
/**
|
||||||
* 遊び方
|
* 遊び方
|
||||||
|
@ -1367,6 +1367,9 @@ pleaseConsentToTracking: "{host}は[プライバシーポリシー]({privacyPoli
|
|||||||
consentEssential: "必須項目のみ許可"
|
consentEssential: "必須項目のみ許可"
|
||||||
consentAll: "全て許可"
|
consentAll: "全て許可"
|
||||||
consentSelected: "選択した項目のみ許可"
|
consentSelected: "選択した項目のみ許可"
|
||||||
|
alsoMuteNotification: "このユーザーが送信する通知も一緒にミュートする"
|
||||||
|
muteNotification: "通知をミュートする"
|
||||||
|
unmuteNotification: "通知をミュート解除する"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
|
@ -1356,6 +1356,9 @@ pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUr
|
|||||||
consentEssential: "필수 항목만 허용"
|
consentEssential: "필수 항목만 허용"
|
||||||
consentAll: "모두 허용"
|
consentAll: "모두 허용"
|
||||||
consentSelected: "선택한 항목만 허용"
|
consentSelected: "선택한 항목만 허용"
|
||||||
|
alsoMuteNotification: "이 유저가 보내는 알림도 같이 뮤트하기"
|
||||||
|
muteNotification: "알림을 뮤트하기"
|
||||||
|
unmuteNotification: "알림 뮤트를 해제하기"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "설명"
|
howToPlay: "설명"
|
||||||
|
@ -21,6 +21,8 @@ export class CacheService implements OnApplicationShutdown {
|
|||||||
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
||||||
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
||||||
public userProfileCache: RedisKVCache<MiUserProfile>;
|
public userProfileCache: RedisKVCache<MiUserProfile>;
|
||||||
|
public userMutingsWithNotificationCache: RedisKVCache<Set<string>>;
|
||||||
|
public userMutingsWithoutNotificationCache: RedisKVCache<Set<string>>;
|
||||||
public userMutingsCache: RedisKVCache<Set<string>>;
|
public userMutingsCache: RedisKVCache<Set<string>>;
|
||||||
public userBlockingCache: RedisKVCache<Set<string>>;
|
public userBlockingCache: RedisKVCache<Set<string>>;
|
||||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||||
@ -77,6 +79,22 @@ export class CacheService implements OnApplicationShutdown {
|
|||||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.userMutingsWithoutNotificationCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key, withNotification: false }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||||
|
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||||
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.userMutingsWithNotificationCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key, withNotification: true }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||||
|
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||||
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
|
||||||
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
|
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
|
||||||
lifetime: 1000 * 60 * 30, // 30m
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
memoryCacheLifetime: 1000 * 60, // 1m
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
@ -188,6 +206,8 @@ export class CacheService implements OnApplicationShutdown {
|
|||||||
this.uriPersonCache.dispose();
|
this.uriPersonCache.dispose();
|
||||||
this.userProfileCache.dispose();
|
this.userProfileCache.dispose();
|
||||||
this.userMutingsCache.dispose();
|
this.userMutingsCache.dispose();
|
||||||
|
this.userMutingsWithoutNotificationCache.dispose();
|
||||||
|
this.userMutingsWithNotificationCache.dispose();
|
||||||
this.userBlockingCache.dispose();
|
this.userBlockingCache.dispose();
|
||||||
this.userBlockedCache.dispose();
|
this.userBlockedCache.dispose();
|
||||||
this.renoteMutingsCache.dispose();
|
this.renoteMutingsCache.dispose();
|
||||||
|
@ -106,7 +106,7 @@ export class NotificationService implements OnApplicationShutdown {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
|
const mutings = await this.cacheService.userMutingsWithNotificationCache.fetch(notifieeId);
|
||||||
if (mutings.has(notifierId)) {
|
if (mutings.has(notifierId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,18 @@ export class UserMutingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null, withNotification = true): Promise<void> {
|
||||||
await this.mutingsRepository.insert({
|
await this.mutingsRepository.insert({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
expiresAt: expiresAt ?? null,
|
expiresAt: expiresAt ?? null,
|
||||||
muterId: user.id,
|
muterId: user.id,
|
||||||
muteeId: target.id,
|
muteeId: target.id,
|
||||||
|
withNotification: withNotification,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cacheService.userMutingsCache.refresh(user.id);
|
this.cacheService.userMutingsCache.refresh(user.id);
|
||||||
|
this.cacheService.userMutingsWithNotificationCache.refresh(user.id);
|
||||||
|
this.cacheService.userMutingsWithoutNotificationCache.refresh(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -46,6 +49,17 @@ export class UserMutingService {
|
|||||||
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
||||||
for (const muterId of muterIds) {
|
for (const muterId of muterIds) {
|
||||||
this.cacheService.userMutingsCache.refresh(muterId);
|
this.cacheService.userMutingsCache.refresh(muterId);
|
||||||
|
this.cacheService.userMutingsWithNotificationCache.refresh(muterId);
|
||||||
|
this.cacheService.userMutingsWithoutNotificationCache.refresh(muterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async editMute(muting: MiMuting, withNotification: boolean): Promise<void> {
|
||||||
|
await this.mutingsRepository.update(muting.id, { withNotification: withNotification });
|
||||||
|
|
||||||
|
this.cacheService.userMutingsCache.refresh(muting.muterId);
|
||||||
|
this.cacheService.userMutingsWithNotificationCache.refresh(muting.muterId);
|
||||||
|
this.cacheService.userMutingsWithoutNotificationCache.refresh(muting.muterId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,12 +269,12 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
*/
|
*/
|
||||||
#validateNotifier <T extends MiNotification | MiGroupedNotification> (
|
#validateNotifier <T extends MiNotification | MiGroupedNotification> (
|
||||||
notification: T,
|
notification: T,
|
||||||
userIdsWhoMeMuting: Set<MiUser['id']>,
|
userIdsWhoMeMutingWithNotification: Set<MiUser['id']>,
|
||||||
userMutedInstances: Set<string>,
|
userMutedInstances: Set<string>,
|
||||||
notifiers: MiUser[],
|
notifiers: MiUser[],
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!('notifierId' in notification)) return true;
|
if (!('notifierId' in notification)) return true;
|
||||||
if (userIdsWhoMeMuting.has(notification.notifierId)) return false;
|
if (userIdsWhoMeMutingWithNotification.has(notification.notifierId)) return false;
|
||||||
|
|
||||||
const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
|
const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
|
||||||
|
|
||||||
@ -303,10 +303,10 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
meId: MiUser['id'],
|
meId: MiUser['id'],
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const [
|
const [
|
||||||
userIdsWhoMeMuting,
|
userIdsWhoMeMutingWithNotification,
|
||||||
userMutedInstances,
|
userMutedInstances,
|
||||||
] = (await Promise.allSettled([
|
] = (await Promise.allSettled([
|
||||||
this.cacheService.userMutingsCache.fetch(meId),
|
this.cacheService.userMutingsWithNotificationCache.fetch(meId),
|
||||||
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
||||||
])).map(result => result.status === 'fulfilled' ? result.value : new Set<string>());
|
])).map(result => result.status === 'fulfilled' ? result.value : new Set<string>());
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
}) : [];
|
}) : [];
|
||||||
|
|
||||||
return ((await Promise.allSettled(notifications.map(async (notification) => {
|
return ((await Promise.allSettled(notifications.map(async (notification) => {
|
||||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
|
const isValid = this.#validateNotifier(notification, userIdsWhoMeMutingWithNotification, userMutedInstances, notifiers);
|
||||||
return isValid ? notification : null;
|
return isValid ? notification : null;
|
||||||
}))).filter(result => result.status === 'fulfilled' && isNotNull(result.value))
|
}))).filter(result => result.status === 'fulfilled' && isNotNull(result.value))
|
||||||
.map(result => (result as PromiseFulfilledResult<T>).value));
|
.map(result => (result as PromiseFulfilledResult<T>).value));
|
||||||
|
@ -281,6 +281,7 @@ import * as ep___emoji from './endpoints/emoji.js';
|
|||||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||||
|
import * as ep___mute_edit from './endpoints/mute/edit.js';
|
||||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||||
@ -680,6 +681,7 @@ const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
|
|||||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||||
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||||
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
|
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
|
||||||
|
const $mute_edit: Provider = { provide: 'ep:mute/edit', useClass: ep___mute_edit.default };
|
||||||
const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
|
const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
|
||||||
const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
|
const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
|
||||||
const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
|
const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
|
||||||
@ -1083,6 +1085,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$miauth_genToken,
|
$miauth_genToken,
|
||||||
$mute_create,
|
$mute_create,
|
||||||
$mute_delete,
|
$mute_delete,
|
||||||
|
$mute_edit,
|
||||||
$mute_list,
|
$mute_list,
|
||||||
$renoteMute_create,
|
$renoteMute_create,
|
||||||
$renoteMute_delete,
|
$renoteMute_delete,
|
||||||
@ -1480,6 +1483,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$miauth_genToken,
|
$miauth_genToken,
|
||||||
$mute_create,
|
$mute_create,
|
||||||
$mute_delete,
|
$mute_delete,
|
||||||
|
$mute_edit,
|
||||||
$mute_list,
|
$mute_list,
|
||||||
$renoteMute_create,
|
$renoteMute_create,
|
||||||
$renoteMute_delete,
|
$renoteMute_delete,
|
||||||
|
@ -281,6 +281,7 @@ import * as ep___emoji from './endpoints/emoji.js';
|
|||||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||||
|
import * as ep___mute_edit from './endpoints/mute/edit.js';
|
||||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||||
@ -678,6 +679,7 @@ const eps = [
|
|||||||
['miauth/gen-token', ep___miauth_genToken],
|
['miauth/gen-token', ep___miauth_genToken],
|
||||||
['mute/create', ep___mute_create],
|
['mute/create', ep___mute_create],
|
||||||
['mute/delete', ep___mute_delete],
|
['mute/delete', ep___mute_delete],
|
||||||
|
['mute/edit', ep___mute_edit],
|
||||||
['mute/list', ep___mute_list],
|
['mute/list', ep___mute_list],
|
||||||
['renote-mute/create', ep___renoteMute_create],
|
['renote-mute/create', ep___renoteMute_create],
|
||||||
['renote-mute/delete', ep___renoteMute_delete],
|
['renote-mute/delete', ep___renoteMute_delete],
|
||||||
|
88
packages/backend/src/server/api/endpoints/mute/edit.ts
Normal file
88
packages/backend/src/server/api/endpoints/mute/edit.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import type { MutingsRepository } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['account'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canUpdateContent',
|
||||||
|
|
||||||
|
kind: 'write:mutes',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: 'b851d00b-8ab1-4a56-8b1b-e24187cb48ef',
|
||||||
|
},
|
||||||
|
|
||||||
|
muteeIsYourself: {
|
||||||
|
message: 'Mutee is yourself.',
|
||||||
|
code: 'MUTEE_IS_YOURSELF',
|
||||||
|
id: 'f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9',
|
||||||
|
},
|
||||||
|
|
||||||
|
notMuting: {
|
||||||
|
message: 'You are not muting that user.',
|
||||||
|
code: 'NOT_MUTING',
|
||||||
|
id: '5467d020-daa9-4553-81e1-135c0c35a96d',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
withNotification: { type: 'boolean', nullable: false },
|
||||||
|
},
|
||||||
|
required: ['userId', 'withNotification'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.mutingsRepository)
|
||||||
|
private mutingsRepository: MutingsRepository,
|
||||||
|
|
||||||
|
private userMutingService: UserMutingService,
|
||||||
|
private getterService: GetterService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const muter = me;
|
||||||
|
|
||||||
|
// Check if the mutee is yourself
|
||||||
|
if (me.id === ps.userId) {
|
||||||
|
throw new ApiError(meta.errors.muteeIsYourself);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutee
|
||||||
|
const mutee = await this.getterService.getUser(ps.userId).catch(err => {
|
||||||
|
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check not muting
|
||||||
|
const exist = await this.mutingsRepository.findOneBy({
|
||||||
|
muterId: muter.id,
|
||||||
|
muteeId: mutee.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist == null) {
|
||||||
|
throw new ApiError(meta.errors.notMuting);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userMutingService.editMute(exist, ps.withNotification);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkUserCardMini :user="item.mutee"/>
|
<MkUserCardMini :user="item.mutee"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||||
<button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
<button class="_button" :class="$style.remove" @click="editMute(item, $event)"><i class="ti ti-dots"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||||
@ -216,8 +216,20 @@ async function unrenoteMute(user, ev) {
|
|||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unmute(user, ev) {
|
async function editMute(muting, ev) {
|
||||||
os.popupMenu([{
|
os.popupMenu([...(muting.withNotification === false ? [{
|
||||||
|
icon: 'ti ti-bell',
|
||||||
|
text: i18n.ts.muteNotification,
|
||||||
|
action: async () => {
|
||||||
|
await os.apiWithDialog('mute/edit', { userId: muting.mutee.id, withNotification: true });
|
||||||
|
},
|
||||||
|
}] : [{
|
||||||
|
icon: 'ti ti-bell-off',
|
||||||
|
text: i18n.ts.unmuteNotification,
|
||||||
|
action: async () => {
|
||||||
|
await os.apiWithDialog('mute/edit', { userId: muting.mutee.id, withNotification: false });
|
||||||
|
},
|
||||||
|
}]), {
|
||||||
text: i18n.ts.unmute,
|
text: i18n.ts.unmute,
|
||||||
icon: 'ti ti-x',
|
icon: 'ti ti-x',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
|
@ -1623,6 +1623,7 @@ declare namespace entities {
|
|||||||
MiauthGenTokenResponse,
|
MiauthGenTokenResponse,
|
||||||
MuteCreateRequest,
|
MuteCreateRequest,
|
||||||
MuteDeleteRequest,
|
MuteDeleteRequest,
|
||||||
|
MuteEditRequest,
|
||||||
MuteListRequest,
|
MuteListRequest,
|
||||||
MuteListResponse,
|
MuteListResponse,
|
||||||
RenoteMuteCreateRequest,
|
RenoteMuteCreateRequest,
|
||||||
@ -2565,6 +2566,9 @@ type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type MuteEditRequest = operations['mute___edit']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -3067,6 +3067,17 @@ declare module '../api.js' {
|
|||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||||
|
*/
|
||||||
|
request<E extends 'mute/edit', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -403,6 +403,7 @@ import type {
|
|||||||
MiauthGenTokenResponse,
|
MiauthGenTokenResponse,
|
||||||
MuteCreateRequest,
|
MuteCreateRequest,
|
||||||
MuteDeleteRequest,
|
MuteDeleteRequest,
|
||||||
|
MuteEditRequest,
|
||||||
MuteListRequest,
|
MuteListRequest,
|
||||||
MuteListResponse,
|
MuteListResponse,
|
||||||
RenoteMuteCreateRequest,
|
RenoteMuteCreateRequest,
|
||||||
@ -868,6 +869,7 @@ export type Endpoints = {
|
|||||||
'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse };
|
'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse };
|
||||||
'mute/create': { req: MuteCreateRequest; res: EmptyResponse };
|
'mute/create': { req: MuteCreateRequest; res: EmptyResponse };
|
||||||
'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse };
|
'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse };
|
||||||
|
'mute/edit': { req: MuteEditRequest; res: EmptyResponse };
|
||||||
'mute/list': { req: MuteListRequest; res: MuteListResponse };
|
'mute/list': { req: MuteListRequest; res: MuteListResponse };
|
||||||
'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse };
|
'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse };
|
||||||
'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse };
|
'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse };
|
||||||
|
@ -406,6 +406,7 @@ export type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBod
|
|||||||
export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json'];
|
export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json'];
|
||||||
export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
||||||
export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json'];
|
export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json'];
|
||||||
|
export type MuteEditRequest = operations['mute___edit']['requestBody']['content']['application/json'];
|
||||||
export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
||||||
export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json'];
|
export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json'];
|
||||||
export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
|
export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
|
||||||
|
@ -2652,6 +2652,15 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations['mute___delete'];
|
post: operations['mute___delete'];
|
||||||
};
|
};
|
||||||
|
'/mute/edit': {
|
||||||
|
/**
|
||||||
|
* mute/edit
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||||
|
*/
|
||||||
|
post: operations['mute___edit'];
|
||||||
|
};
|
||||||
'/mute/list': {
|
'/mute/list': {
|
||||||
/**
|
/**
|
||||||
* mute/list
|
* mute/list
|
||||||
@ -22915,6 +22924,8 @@ export type operations = {
|
|||||||
userId: string;
|
userId: string;
|
||||||
/** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */
|
/** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */
|
||||||
expiresAt?: number | null;
|
expiresAt?: number | null;
|
||||||
|
/** @default true */
|
||||||
|
withNotification?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -23013,6 +23024,59 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* mute/edit
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||||
|
*/
|
||||||
|
mute___edit: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
userId: string;
|
||||||
|
withNotification: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (without any results) */
|
||||||
|
204: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* mute/list
|
* mute/list
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
Loading…
Reference in New Issue
Block a user