enhance: お知らせの既読をリセットできるように
This commit is contained in:
parent
04b221409c
commit
001fc04c80
|
@ -5222,6 +5222,14 @@ export interface Locale extends ILocale {
|
||||||
* 注意事項を理解した上でオンにします。
|
* 注意事項を理解した上でオンにします。
|
||||||
*/
|
*/
|
||||||
"acknowledgeNotesAndEnable": string;
|
"acknowledgeNotesAndEnable": string;
|
||||||
|
/**
|
||||||
|
* 既読をリセット
|
||||||
|
*/
|
||||||
|
"resetReads": string;
|
||||||
|
/**
|
||||||
|
* 「{x}」の既読をリセットしますか?
|
||||||
|
*/
|
||||||
|
"resetReadsAreYouSure": ParameterizedString<"x">;
|
||||||
"_accountSettings": {
|
"_accountSettings": {
|
||||||
/**
|
/**
|
||||||
* コンテンツの表示にログインを必須にする
|
* コンテンツの表示にログインを必須にする
|
||||||
|
@ -9910,6 +9918,14 @@ export interface Locale extends ILocale {
|
||||||
* ユーザーのお知らせを削除
|
* ユーザーのお知らせを削除
|
||||||
*/
|
*/
|
||||||
"deleteUserAnnouncement": string;
|
"deleteUserAnnouncement": string;
|
||||||
|
/**
|
||||||
|
* 全体のお知らせの既読をリセット
|
||||||
|
*/
|
||||||
|
"resetReadsForGlobalAnnouncement": string;
|
||||||
|
/**
|
||||||
|
* ユーザーのお知らせの既読をリセット
|
||||||
|
*/
|
||||||
|
"resetReadsForUserAnnouncement": string;
|
||||||
/**
|
/**
|
||||||
* パスワードをリセット
|
* パスワードをリセット
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1301,6 +1301,8 @@ lockdown: "ロックダウン"
|
||||||
pleaseSelectAccount: "アカウントを選択してください"
|
pleaseSelectAccount: "アカウントを選択してください"
|
||||||
availableRoles: "利用可能なロール"
|
availableRoles: "利用可能なロール"
|
||||||
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
|
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
|
||||||
|
resetReads: "既読をリセット"
|
||||||
|
resetReadsAreYouSure: "「{x}」の既読をリセットしますか?"
|
||||||
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||||
|
@ -2627,6 +2629,8 @@ _moderationLogTypes:
|
||||||
updateUserAnnouncement: "ユーザーのお知らせを更新"
|
updateUserAnnouncement: "ユーザーのお知らせを更新"
|
||||||
deleteGlobalAnnouncement: "全体のお知らせを削除"
|
deleteGlobalAnnouncement: "全体のお知らせを削除"
|
||||||
deleteUserAnnouncement: "ユーザーのお知らせを削除"
|
deleteUserAnnouncement: "ユーザーのお知らせを削除"
|
||||||
|
resetReadsForGlobalAnnouncement: "全体のお知らせの既読をリセット"
|
||||||
|
resetReadsForUserAnnouncement: "ユーザーのお知らせの既読をリセット"
|
||||||
resetPassword: "パスワードをリセット"
|
resetPassword: "パスワードをリセット"
|
||||||
suspendRemoteInstance: "リモートサーバーを停止"
|
suspendRemoteInstance: "リモートサーバーを停止"
|
||||||
unsuspendRemoteInstance: "リモートサーバーを再開"
|
unsuspendRemoteInstance: "リモートサーバーを再開"
|
||||||
|
|
|
@ -179,6 +179,32 @@ export class AnnouncementService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async resetReads(announcementId: MiAnnouncement['id'], moderator?: MiUser): Promise<void> {
|
||||||
|
await this.announcementReadsRepository.delete({
|
||||||
|
announcementId: announcementId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (moderator) {
|
||||||
|
const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });
|
||||||
|
if (announcement.userId) {
|
||||||
|
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
|
||||||
|
this.moderationLogService.log(moderator, 'resetReadsForUserAnnouncement', {
|
||||||
|
announcementId: announcement.id,
|
||||||
|
announcement: announcement,
|
||||||
|
userId: announcement.userId,
|
||||||
|
userUsername: user.username,
|
||||||
|
userHost: user.host,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.moderationLogService.log(moderator, 'resetReadsForGlobalAnnouncement', {
|
||||||
|
announcementId: announcement.id,
|
||||||
|
announcement: announcement,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getAnnouncement(announcementId: MiAnnouncement['id'], me: MiUser | null): Promise<Packed<'Announcement'>> {
|
public async getAnnouncement(announcementId: MiAnnouncement['id'], me: MiUser | null): Promise<Packed<'Announcement'>> {
|
||||||
const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });
|
const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });
|
||||||
|
|
|
@ -23,6 +23,7 @@ import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
|
||||||
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
|
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
|
||||||
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
||||||
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
||||||
|
import * as ep___admin_announcements_resetReads from './endpoints/admin/announcements/reset-reads.js';
|
||||||
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
||||||
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
||||||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||||
|
@ -411,6 +412,7 @@ const $admin_ad_update: Provider = { provide: 'ep:admin/ad/update', useClass: ep
|
||||||
const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default };
|
const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default };
|
||||||
const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
|
const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
|
||||||
const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
|
const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
|
||||||
|
const $admin_announcements_resetReads: Provider = { provide: 'ep:admin/announcements/reset-reads', useClass: ep___admin_announcements_resetReads.default };
|
||||||
const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
|
const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
|
||||||
const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default };
|
const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default };
|
||||||
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
|
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
|
||||||
|
@ -803,6 +805,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_announcements_create,
|
$admin_announcements_create,
|
||||||
$admin_announcements_delete,
|
$admin_announcements_delete,
|
||||||
$admin_announcements_list,
|
$admin_announcements_list,
|
||||||
|
$admin_announcements_resetReads,
|
||||||
$admin_announcements_update,
|
$admin_announcements_update,
|
||||||
$admin_avatarDecorations_create,
|
$admin_avatarDecorations_create,
|
||||||
$admin_avatarDecorations_delete,
|
$admin_avatarDecorations_delete,
|
||||||
|
@ -1189,6 +1192,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_announcements_create,
|
$admin_announcements_create,
|
||||||
$admin_announcements_delete,
|
$admin_announcements_delete,
|
||||||
$admin_announcements_list,
|
$admin_announcements_list,
|
||||||
|
$admin_announcements_resetReads,
|
||||||
$admin_announcements_update,
|
$admin_announcements_update,
|
||||||
$admin_avatarDecorations_create,
|
$admin_avatarDecorations_create,
|
||||||
$admin_avatarDecorations_delete,
|
$admin_avatarDecorations_delete,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
|
||||||
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
|
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
|
||||||
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
||||||
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
||||||
|
import * as ep___admin_announcements_resetReads from './endpoints/admin/announcements/reset-reads.js';
|
||||||
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
||||||
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
||||||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||||
|
@ -415,6 +416,7 @@ const eps = [
|
||||||
['admin/announcements/create', ep___admin_announcements_create],
|
['admin/announcements/create', ep___admin_announcements_create],
|
||||||
['admin/announcements/delete', ep___admin_announcements_delete],
|
['admin/announcements/delete', ep___admin_announcements_delete],
|
||||||
['admin/announcements/list', ep___admin_announcements_list],
|
['admin/announcements/list', ep___admin_announcements_list],
|
||||||
|
['admin/announcements/reset-reads', ep___admin_announcements_resetReads],
|
||||||
['admin/announcements/update', ep___admin_announcements_update],
|
['admin/announcements/update', ep___admin_announcements_update],
|
||||||
['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
|
['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
|
||||||
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
||||||
|
|
|
@ -53,6 +53,37 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
enum: ['info', 'warning', 'error', 'success'],
|
||||||
|
},
|
||||||
|
display: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
enum: ['normal', 'banner', 'dialog'],
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
forExistingUsers: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
silence: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
needConfirmationToRead: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
reads: {
|
reads: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -125,7 +156,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
silence: announcement.silence,
|
silence: announcement.silence,
|
||||||
needConfirmationToRead: announcement.needConfirmationToRead,
|
needConfirmationToRead: announcement.needConfirmationToRead,
|
||||||
userId: announcement.userId,
|
userId: announcement.userId,
|
||||||
reads: reads.get(announcement)!,
|
reads: reads.get(announcement) ?? 0,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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 { AnnouncementsRepository } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:announcements',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchAnnouncement: {
|
||||||
|
message: 'No such announcement.',
|
||||||
|
code: 'NO_SUCH_ANNOUNCEMENT',
|
||||||
|
id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
announcementId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['announcementId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.announcementsRepository)
|
||||||
|
private announcementsRepository: AnnouncementsRepository,
|
||||||
|
|
||||||
|
private announcementService: AnnouncementService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId });
|
||||||
|
|
||||||
|
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
|
||||||
|
|
||||||
|
await this.announcementService.resetReads(announcement.id, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,6 +92,8 @@ export const moderationLogTypes = [
|
||||||
'updateUserAnnouncement',
|
'updateUserAnnouncement',
|
||||||
'deleteGlobalAnnouncement',
|
'deleteGlobalAnnouncement',
|
||||||
'deleteUserAnnouncement',
|
'deleteUserAnnouncement',
|
||||||
|
'resetReadsForGlobalAnnouncement',
|
||||||
|
'resetReadsForUserAnnouncement',
|
||||||
'resetPassword',
|
'resetPassword',
|
||||||
'suspendRemoteInstance',
|
'suspendRemoteInstance',
|
||||||
'unsuspendRemoteInstance',
|
'unsuspendRemoteInstance',
|
||||||
|
@ -235,6 +237,17 @@ export type ModerationLogPayloads = {
|
||||||
userUsername: string;
|
userUsername: string;
|
||||||
userHost: string | null;
|
userHost: string | null;
|
||||||
};
|
};
|
||||||
|
resetReadsForGlobalAnnouncement: {
|
||||||
|
announcementId: string;
|
||||||
|
announcement: any;
|
||||||
|
};
|
||||||
|
resetReadsForUserAnnouncement: {
|
||||||
|
announcementId: string;
|
||||||
|
announcement: any;
|
||||||
|
userId: string;
|
||||||
|
userUsername: string;
|
||||||
|
userHost: string | null;
|
||||||
|
};
|
||||||
resetPassword: {
|
resetPassword: {
|
||||||
userId: string;
|
userId: string;
|
||||||
userUsername: string;
|
userUsername: string;
|
||||||
|
|
|
@ -39,7 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ i18n.ts._announcement.needConfirmationToRead }}
|
{{ i18n.ts._announcement.needConfirmationToRead }}
|
||||||
<template #caption>{{ i18n.ts._announcement.needConfirmationToReadDescription }}</template>
|
<template #caption>{{ i18n.ts._announcement.needConfirmationToReadDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
<MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<div class="_buttons">
|
||||||
|
<MkButton v-if="announcement" @click="resetReads()">{{ i18n.ts.resetReads }}</MkButton>
|
||||||
|
<MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
|
@ -117,6 +120,20 @@ async function done() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resetReads() {
|
||||||
|
if (!props.announcement) return;
|
||||||
|
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.tsx.resetReadsAreYouSure({ x: props.announcement.title }),
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
await os.apiWithDialog('admin/announcements/reset-reads', {
|
||||||
|
announcementId: props.announcement.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
|
@ -151,7 +151,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'announcements'" class="_gaps">
|
<div v-else-if="tab === 'announcements'" class="_gaps">
|
||||||
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
|
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
|
||||||
<MkSelect v-model="announcementsStatus">
|
<MkSelect v-model="announcementsStatus">
|
||||||
<template #label>{{ i18n.ts.filter }}</template>
|
<template #label>{{ i18n.ts.filter }}</template>
|
||||||
|
|
|
@ -34,6 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton rounded primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton rounded primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
<MkButton v-if="announcement.id != null && announcement.isActive" rounded @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
|
<MkButton v-if="announcement.id != null && announcement.isActive" rounded @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
|
||||||
<MkButton v-if="announcement.id != null && !announcement.isActive" rounded @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton>
|
<MkButton v-if="announcement.id != null && !announcement.isActive" rounded @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton>
|
||||||
|
<MkButton v-if="announcement.id != null && announcement.isActive && announcement.reads > 0" rounded @click="resetReads(announcement)">{{ i18n.ts.resetReads }}</MkButton>
|
||||||
<MkButton v-if="announcement.id != null" rounded danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<MkButton v-if="announcement.id != null" rounded danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -71,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
|
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
|
||||||
{{ i18n.ts._announcement.needConfirmationToRead }}
|
{{ i18n.ts._announcement.needConfirmationToRead }}
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
|
<p v-if="announcement.reads > 0">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkLoading v-if="loadingMore"/>
|
<MkLoading v-if="loadingMore"/>
|
||||||
|
@ -85,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import XHeader from './_header_.vue';
|
import XHeader from './_header_.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -143,6 +145,19 @@ function del(announcement) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resetReads(announcement) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.tsx.resetReadsAreYouSure({ x: announcement.title }),
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
await os.apiWithDialog('admin/announcements/reset-reads', {
|
||||||
|
announcementId: announcement.id,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
async function archive(announcement) {
|
async function archive(announcement) {
|
||||||
await os.apiWithDialog('admin/announcements/update', {
|
await os.apiWithDialog('admin/announcements/update', {
|
||||||
...announcement,
|
...announcement,
|
||||||
|
|
|
@ -62,9 +62,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
||||||
<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
|
<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
|
||||||
<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
||||||
|
<span v-else-if="log.type === 'resetReadsForGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
||||||
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||||
|
<span v-else-if="log.type === 'resetReadsForUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
|
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
|
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'createAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>
|
<span v-else-if="log.type === 'createAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>
|
||||||
|
|
|
@ -115,6 +115,9 @@ type AdminAnnouncementsListRequest = operations['admin___announcements___list'][
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
|
type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminAnnouncementsResetReadsRequest = operations['admin___announcements___reset-reads']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
|
type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -1254,6 +1257,7 @@ declare namespace entities {
|
||||||
AdminAnnouncementsDeleteRequest,
|
AdminAnnouncementsDeleteRequest,
|
||||||
AdminAnnouncementsListRequest,
|
AdminAnnouncementsListRequest,
|
||||||
AdminAnnouncementsListResponse,
|
AdminAnnouncementsListResponse,
|
||||||
|
AdminAnnouncementsResetReadsRequest,
|
||||||
AdminAnnouncementsUpdateRequest,
|
AdminAnnouncementsUpdateRequest,
|
||||||
AdminAvatarDecorationsCreateRequest,
|
AdminAvatarDecorationsCreateRequest,
|
||||||
AdminAvatarDecorationsCreateResponse,
|
AdminAvatarDecorationsCreateResponse,
|
||||||
|
@ -2525,6 +2529,12 @@ type ModerationLog = {
|
||||||
} | {
|
} | {
|
||||||
type: 'deleteUserAnnouncement';
|
type: 'deleteUserAnnouncement';
|
||||||
info: ModerationLogPayloads['deleteUserAnnouncement'];
|
info: ModerationLogPayloads['deleteUserAnnouncement'];
|
||||||
|
} | {
|
||||||
|
type: 'resetReadsForGlobalAnnouncement';
|
||||||
|
info: ModerationLogPayloads['resetReadsForGlobalAnnouncement'];
|
||||||
|
} | {
|
||||||
|
type: 'resetReadsForUserAnnouncement';
|
||||||
|
info: ModerationLogPayloads['resetReadsForUserAnnouncement'];
|
||||||
} | {
|
} | {
|
||||||
type: 'resetPassword';
|
type: 'resetPassword';
|
||||||
info: ModerationLogPayloads['resetPassword'];
|
info: ModerationLogPayloads['resetPassword'];
|
||||||
|
|
|
@ -195,6 +195,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:admin:announcements*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/announcements/reset-reads', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -29,6 +29,7 @@ import type {
|
||||||
AdminAnnouncementsDeleteRequest,
|
AdminAnnouncementsDeleteRequest,
|
||||||
AdminAnnouncementsListRequest,
|
AdminAnnouncementsListRequest,
|
||||||
AdminAnnouncementsListResponse,
|
AdminAnnouncementsListResponse,
|
||||||
|
AdminAnnouncementsResetReadsRequest,
|
||||||
AdminAnnouncementsUpdateRequest,
|
AdminAnnouncementsUpdateRequest,
|
||||||
AdminAvatarDecorationsCreateRequest,
|
AdminAvatarDecorationsCreateRequest,
|
||||||
AdminAvatarDecorationsCreateResponse,
|
AdminAvatarDecorationsCreateResponse,
|
||||||
|
@ -599,6 +600,7 @@ export type Endpoints = {
|
||||||
'admin/announcements/create': { req: AdminAnnouncementsCreateRequest; res: AdminAnnouncementsCreateResponse };
|
'admin/announcements/create': { req: AdminAnnouncementsCreateRequest; res: AdminAnnouncementsCreateResponse };
|
||||||
'admin/announcements/delete': { req: AdminAnnouncementsDeleteRequest; res: EmptyResponse };
|
'admin/announcements/delete': { req: AdminAnnouncementsDeleteRequest; res: EmptyResponse };
|
||||||
'admin/announcements/list': { req: AdminAnnouncementsListRequest; res: AdminAnnouncementsListResponse };
|
'admin/announcements/list': { req: AdminAnnouncementsListRequest; res: AdminAnnouncementsListResponse };
|
||||||
|
'admin/announcements/reset-reads': { req: AdminAnnouncementsResetReadsRequest; res: EmptyResponse };
|
||||||
'admin/announcements/update': { req: AdminAnnouncementsUpdateRequest; res: EmptyResponse };
|
'admin/announcements/update': { req: AdminAnnouncementsUpdateRequest; res: EmptyResponse };
|
||||||
'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: AdminAvatarDecorationsCreateResponse };
|
'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: AdminAvatarDecorationsCreateResponse };
|
||||||
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
|
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -32,6 +32,7 @@ export type AdminAnnouncementsCreateResponse = operations['admin___announcements
|
||||||
export type AdminAnnouncementsDeleteRequest = operations['admin___announcements___delete']['requestBody']['content']['application/json'];
|
export type AdminAnnouncementsDeleteRequest = operations['admin___announcements___delete']['requestBody']['content']['application/json'];
|
||||||
export type AdminAnnouncementsListRequest = operations['admin___announcements___list']['requestBody']['content']['application/json'];
|
export type AdminAnnouncementsListRequest = operations['admin___announcements___list']['requestBody']['content']['application/json'];
|
||||||
export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
|
export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
|
||||||
|
export type AdminAnnouncementsResetReadsRequest = operations['admin___announcements___reset-reads']['requestBody']['content']['application/json'];
|
||||||
export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
|
export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
|
export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
|
||||||
|
|
|
@ -170,6 +170,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin___announcements___list'];
|
post: operations['admin___announcements___list'];
|
||||||
};
|
};
|
||||||
|
'/admin/announcements/reset-reads': {
|
||||||
|
/**
|
||||||
|
* admin/announcements/reset-reads
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
|
||||||
|
*/
|
||||||
|
post: operations['admin___announcements___reset-reads'];
|
||||||
|
};
|
||||||
'/admin/announcements/update': {
|
'/admin/announcements/update': {
|
||||||
/**
|
/**
|
||||||
* admin/announcements/update
|
* admin/announcements/update
|
||||||
|
@ -6216,6 +6225,16 @@ export type operations = {
|
||||||
text: string;
|
text: string;
|
||||||
title: string;
|
title: string;
|
||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
|
/** @enum {string} */
|
||||||
|
icon: 'info' | 'warning' | 'error' | 'success';
|
||||||
|
/** @enum {string} */
|
||||||
|
display: 'normal' | 'banner' | 'dialog';
|
||||||
|
isActive: boolean;
|
||||||
|
forExistingUsers: boolean;
|
||||||
|
silence: boolean;
|
||||||
|
needConfirmationToRead: boolean;
|
||||||
|
/** Format: id */
|
||||||
|
userId: string | null;
|
||||||
reads: number;
|
reads: number;
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
|
@ -6252,6 +6271,58 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/announcements/reset-reads
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
|
||||||
|
*/
|
||||||
|
'admin___announcements___reset-reads': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
announcementId?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
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'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* admin/announcements/update
|
* admin/announcements/update
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
|
|
@ -298,6 +298,17 @@ export type ModerationLogPayloads = {
|
||||||
userUsername: string;
|
userUsername: string;
|
||||||
userHost: string | null;
|
userHost: string | null;
|
||||||
};
|
};
|
||||||
|
resetReadsForGlobalAnnouncement: {
|
||||||
|
announcementId: string;
|
||||||
|
announcement: Announcement;
|
||||||
|
};
|
||||||
|
resetReadsForUserAnnouncement: {
|
||||||
|
announcementId: string;
|
||||||
|
announcement: Announcement;
|
||||||
|
userId: string;
|
||||||
|
userUsername: string;
|
||||||
|
userHost: string | null;
|
||||||
|
};
|
||||||
resetPassword: {
|
resetPassword: {
|
||||||
userId: string;
|
userId: string;
|
||||||
userUsername: string;
|
userUsername: string;
|
||||||
|
|
|
@ -111,6 +111,12 @@ export type ModerationLog = {
|
||||||
} | {
|
} | {
|
||||||
type: 'deleteUserAnnouncement';
|
type: 'deleteUserAnnouncement';
|
||||||
info: ModerationLogPayloads['deleteUserAnnouncement'];
|
info: ModerationLogPayloads['deleteUserAnnouncement'];
|
||||||
|
} | {
|
||||||
|
type: 'resetReadsForGlobalAnnouncement';
|
||||||
|
info: ModerationLogPayloads['resetReadsForGlobalAnnouncement'];
|
||||||
|
} | {
|
||||||
|
type: 'resetReadsForUserAnnouncement';
|
||||||
|
info: ModerationLogPayloads['resetReadsForUserAnnouncement'];
|
||||||
} | {
|
} | {
|
||||||
type: 'resetPassword';
|
type: 'resetPassword';
|
||||||
info: ModerationLogPayloads['resetPassword'];
|
info: ModerationLogPayloads['resetPassword'];
|
||||||
|
|
Loading…
Reference in New Issue