This commit is contained in:
syuilo 2023-08-09 18:43:34 +09:00
parent 8bae53fb3f
commit 00b68de761
7 changed files with 50 additions and 26 deletions

View File

@ -1096,6 +1096,11 @@ doYouAgree: "同意しますか?"
beSureToReadThisAsItIsImportant: "重要ですので必ずお読みください。"
iHaveReadXCarefullyAndAgree: "「{x}」の内容をよく読み、同意します。"
_announcement:
forExistingUsers: "既存ユーザーのみ"
forExistingUsersDescription: "有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。"
deactivate: "お知らせを終了"
_initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!"
letsStartAccountSetup: "アカウントの初期設定を行いましょう。"

View File

@ -46,7 +46,10 @@ export class AnnouncementService {
qb.orWhere('announcement.userId = :userId', { userId: user.id });
qb.orWhere('announcement.userId IS NULL');
}))
.andWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt })
.andWhere(new Brackets(qb => {
qb.orWhere('announcement.forExistingUsers = false');
qb.orWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt });
}))
.andWhere(`announcement.id NOT IN (${ readsQuery.getQuery() })`);
q.setParameters(readsQuery.getParameters());

View File

@ -54,6 +54,12 @@ export class Announcement {
})
public isActive: boolean;
@Index()
@Column('boolean', {
default: false,
})
public forExistingUsers: boolean;
@Index()
@Column({
...id(),

View File

@ -58,6 +58,7 @@ export const paramDef = {
text: { type: 'string', minLength: 1 },
imageUrl: { type: 'string', nullable: true, minLength: 1 },
display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' },
forExistingUsers: { type: 'boolean', default: false },
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
},
required: ['title', 'text', 'imageUrl'],
@ -81,6 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
text: ps.text,
imageUrl: ps.imageUrl,
display: ps.display,
forExistingUsers: ps.forExistingUsers,
userId: ps.userId,
}).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0]));

View File

@ -66,6 +66,7 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id' },
},
required: [],
} as const;
@ -84,6 +85,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
if (ps.userId) {
query.andWhere('announcement.userId = :userId', { userId: ps.userId });
} else {
query.andWhere('announcement.userId IS NULL');
}
const announcements = await query.limit(ps.limit).getMany();
@ -102,6 +108,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
title: announcement.title,
text: announcement.text,
imageUrl: announcement.imageUrl,
display: announcement.display,
isActive: announcement.isActive,
forExistingUsers: announcement.forExistingUsers,
reads: reads.get(announcement)!,
}));
});

View File

@ -31,8 +31,11 @@ export const paramDef = {
title: { type: 'string', minLength: 1 },
text: { type: 'string', minLength: 1 },
imageUrl: { type: 'string', nullable: true, minLength: 0 },
display: { type: 'string', enum: ['normal', 'banner', 'dialog'] },
forExistingUsers: { type: 'boolean' },
isActive: { type: 'boolean' },
},
required: ['id', 'title', 'text', 'imageUrl'],
required: ['id', 'title', 'text', 'imageUrl', 'display', 'forExistingUsers', 'isActive'],
} as const;
// eslint-disable-next-line import/no-default-export
@ -53,6 +56,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
text: ps.text,
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
imageUrl: ps.imageUrl || null,
display: ps.display,
forExistingUsers: ps.forExistingUsers,
isActive: ps.isActive,
});
});
}

View File

@ -19,9 +19,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-model="announcement.imageUrl">
<template #label>{{ i18n.ts.imageUrl }}</template>
</MkInput>
<MkSwitch v-model="announcement.forExistingUsers">
{{ i18n.ts._announcement.forExistingUsers }}
<template #caption>{{ i18n.ts._announcement.forExistingUsersDescription }}</template>
</MkSwitch>
<p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p>
<div class="buttons _buttons">
<MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
<MkButton class="button" inline @click="deactivate(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts._announcement.deactivate }}</MkButton>
<MkButton class="button" inline danger @click="remove(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
</div>
</div>
@ -67,32 +72,20 @@ function remove(announcement) {
});
}
function save(announcement) {
async function deactivate(announcement) {
await os.apiWithDialog('admin/announcements/update', {
...announcement,
isActive: false,
});
refresh();
}
async function save(announcement) {
if (announcement.id == null) {
os.api('admin/announcements/create', announcement).then(() => {
os.alert({
type: 'success',
text: i18n.ts.saved,
});
refresh();
}).catch(err => {
os.alert({
type: 'error',
text: err,
});
});
await os.apiWithDialog('admin/announcements/create', announcement);
refresh();
} else {
os.api('admin/announcements/update', announcement).then(() => {
os.alert({
type: 'success',
text: i18n.ts.saved,
});
}).catch(err => {
os.alert({
type: 'error',
text: err,
});
});
os.apiWithDialog('admin/announcements/update', announcement);
}
}