enhance: 通知の履歴をリセットできるように (#13335)
* enhance: 通知の履歴をリセットできるように * Update Changelog * 通知欄も連動して更新するように * revert some changes * Update CHANGELOG.md * Remove unused part * fix
This commit is contained in:
parent
ec18991328
commit
39d6af135f
|
@ -18,6 +18,7 @@
|
||||||
- Enhance: サーバーごとにモデレーションノートを残せるように
|
- Enhance: サーバーごとにモデレーションノートを残せるように
|
||||||
- Enhance: コンディショナルロールの条件に「マニュアルロールへのアサイン」を追加
|
- Enhance: コンディショナルロールの条件に「マニュアルロールへのアサイン」を追加
|
||||||
- Enhance: 通知の受信設定に「フォロー中またはフォロワー」を追加
|
- Enhance: 通知の受信設定に「フォロー中またはフォロワー」を追加
|
||||||
|
- Enhance: 通知の履歴をリセットできるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整
|
- Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整
|
||||||
|
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
|
@ -8913,6 +8913,10 @@ export interface Locale extends ILocale {
|
||||||
* {n}人にフォローされました
|
* {n}人にフォローされました
|
||||||
*/
|
*/
|
||||||
"followedBySomeUsers": ParameterizedString<"n">;
|
"followedBySomeUsers": ParameterizedString<"n">;
|
||||||
|
/**
|
||||||
|
* 通知の履歴をリセットする
|
||||||
|
*/
|
||||||
|
"flushNotification": string;
|
||||||
"_types": {
|
"_types": {
|
||||||
/**
|
/**
|
||||||
* すべて
|
* すべて
|
||||||
|
|
|
@ -2356,6 +2356,7 @@ _notification:
|
||||||
reactedBySomeUsers: "{n}人がリアクションしました"
|
reactedBySomeUsers: "{n}人がリアクションしました"
|
||||||
renotedBySomeUsers: "{n}人がリノートしました"
|
renotedBySomeUsers: "{n}人がリノートしました"
|
||||||
followedBySomeUsers: "{n}人にフォローされました"
|
followedBySomeUsers: "{n}人にフォローされました"
|
||||||
|
flushNotification: "通知の履歴をリセットする"
|
||||||
|
|
||||||
_types:
|
_types:
|
||||||
all: "すべて"
|
all: "すべて"
|
||||||
|
|
|
@ -69,6 +69,7 @@ export interface MainEventTypes {
|
||||||
file: Packed<'DriveFile'>;
|
file: Packed<'DriveFile'>;
|
||||||
};
|
};
|
||||||
readAllNotifications: undefined;
|
readAllNotifications: undefined;
|
||||||
|
notificationFlushed: undefined;
|
||||||
unreadNotification: Packed<'Notification'>;
|
unreadNotification: Packed<'Notification'>;
|
||||||
unreadMention: MiNote['id'];
|
unreadMention: MiNote['id'];
|
||||||
readAllUnreadMentions: undefined;
|
readAllUnreadMentions: undefined;
|
||||||
|
|
|
@ -214,6 +214,15 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async flushAllNotifications(userId: MiUser['id']) {
|
||||||
|
await Promise.all([
|
||||||
|
this.redisClient.del(`notificationTimeline:${userId}`),
|
||||||
|
this.redisClient.del(`latestReadNotification:${userId}`),
|
||||||
|
]);
|
||||||
|
this.globalEventService.publishMainStream(userId, 'notificationFlushed');
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.#shutdownController.abort();
|
this.#shutdownController.abort();
|
||||||
|
|
|
@ -293,6 +293,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
|
||||||
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
||||||
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
||||||
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
||||||
|
import * as ep___notifications_flush from './endpoints/notifications/flush.js';
|
||||||
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
|
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
|
||||||
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
|
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
|
||||||
import * as ep___pagePush from './endpoints/page-push.js';
|
import * as ep___pagePush from './endpoints/page-push.js';
|
||||||
|
@ -664,6 +665,7 @@ const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep
|
||||||
const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
|
const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
|
||||||
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
|
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
|
||||||
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
|
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
|
||||||
|
const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default };
|
||||||
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
|
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
|
||||||
const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
|
const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
|
||||||
const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
|
const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
|
||||||
|
@ -1039,6 +1041,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$notes_unrenote,
|
$notes_unrenote,
|
||||||
$notes_userListTimeline,
|
$notes_userListTimeline,
|
||||||
$notifications_create,
|
$notifications_create,
|
||||||
|
$notifications_flush,
|
||||||
$notifications_markAllAsRead,
|
$notifications_markAllAsRead,
|
||||||
$notifications_testNotification,
|
$notifications_testNotification,
|
||||||
$pagePush,
|
$pagePush,
|
||||||
|
@ -1408,7 +1411,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$notes_unrenote,
|
$notes_unrenote,
|
||||||
$notes_userListTimeline,
|
$notes_userListTimeline,
|
||||||
$notifications_create,
|
$notifications_create,
|
||||||
|
$notifications_flush,
|
||||||
$notifications_markAllAsRead,
|
$notifications_markAllAsRead,
|
||||||
|
$notifications_testNotification,
|
||||||
$pagePush,
|
$pagePush,
|
||||||
$pages_create,
|
$pages_create,
|
||||||
$pages_delete,
|
$pages_delete,
|
||||||
|
|
|
@ -293,6 +293,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
|
||||||
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
||||||
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
||||||
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
||||||
|
import * as ep___notifications_flush from './endpoints/notifications/flush.js';
|
||||||
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
|
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
|
||||||
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
|
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
|
||||||
import * as ep___pagePush from './endpoints/page-push.js';
|
import * as ep___pagePush from './endpoints/page-push.js';
|
||||||
|
@ -662,6 +663,7 @@ const eps = [
|
||||||
['notes/unrenote', ep___notes_unrenote],
|
['notes/unrenote', ep___notes_unrenote],
|
||||||
['notes/user-list-timeline', ep___notes_userListTimeline],
|
['notes/user-list-timeline', ep___notes_userListTimeline],
|
||||||
['notifications/create', ep___notifications_create],
|
['notifications/create', ep___notifications_create],
|
||||||
|
['notifications/flush', ep___notifications_flush],
|
||||||
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
|
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
|
||||||
['notifications/test-notification', ep___notifications_testNotification],
|
['notifications/test-notification', ep___notifications_testNotification],
|
||||||
['page-push', ep___pagePush],
|
['page-push', ep___pagePush],
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['notifications', 'account'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'write:notifications',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
this.notificationService.flushAllNotifications(me.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import { notificationTypes } from '@/const.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
excludeTypes?: typeof notificationTypes[number][];
|
excludeTypes?: typeof notificationTypes[number][];
|
||||||
|
@ -75,17 +76,19 @@ function reload() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection;
|
let connection: Misskey.ChannelConnection<Misskey.Channels['main']>;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
connection = useStream().useChannel('main');
|
connection = useStream().useChannel('main');
|
||||||
connection.on('notification', onNotification);
|
connection.on('notification', onNotification);
|
||||||
|
connection.on('notificationFlushed', reload);
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
pagingComponent.value?.reload();
|
pagingComponent.value?.reload();
|
||||||
connection = useStream().useChannel('main');
|
connection = useStream().useChannel('main');
|
||||||
connection.on('notification', onNotification);
|
connection.on('notification', onNotification);
|
||||||
|
connection.on('notificationFlushed', reload);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
|
@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
|
<FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
|
||||||
|
<FormLink @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</FormLink>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormSection>
|
<FormSection>
|
||||||
|
@ -114,6 +115,17 @@ function testNotification(): void {
|
||||||
misskeyApi('notifications/test-notification');
|
misskeyApi('notifications/test-notification');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function flushNotification() {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.ts.resetAreYouSure,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
os.apiWithDialog('notifications/flush');
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
|
@ -530,6 +530,7 @@ export type Channels = {
|
||||||
unreadNotification: (payload: Notification_2) => void;
|
unreadNotification: (payload: Notification_2) => void;
|
||||||
unreadMention: (payload: Note['id']) => void;
|
unreadMention: (payload: Note['id']) => void;
|
||||||
readAllUnreadMentions: () => void;
|
readAllUnreadMentions: () => void;
|
||||||
|
notificationFlushed: () => void;
|
||||||
unreadSpecifiedNote: (payload: Note['id']) => void;
|
unreadSpecifiedNote: (payload: Note['id']) => void;
|
||||||
readAllUnreadSpecifiedNotes: () => void;
|
readAllUnreadSpecifiedNotes: () => void;
|
||||||
readAllAntennas: () => void;
|
readAllAntennas: () => void;
|
||||||
|
|
|
@ -3195,6 +3195,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:notifications*
|
||||||
|
*/
|
||||||
|
request<E extends 'notifications/flush', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -841,6 +841,7 @@ export type Endpoints = {
|
||||||
'notes/unrenote': { req: NotesUnrenoteRequest; res: EmptyResponse };
|
'notes/unrenote': { req: NotesUnrenoteRequest; res: EmptyResponse };
|
||||||
'notes/user-list-timeline': { req: NotesUserListTimelineRequest; res: NotesUserListTimelineResponse };
|
'notes/user-list-timeline': { req: NotesUserListTimelineRequest; res: NotesUserListTimelineResponse };
|
||||||
'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse };
|
'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse };
|
||||||
|
'notifications/flush': { req: EmptyRequest; res: EmptyResponse };
|
||||||
'notifications/mark-all-as-read': { req: EmptyRequest; res: EmptyResponse };
|
'notifications/mark-all-as-read': { req: EmptyRequest; res: EmptyResponse };
|
||||||
'notifications/test-notification': { req: EmptyRequest; res: EmptyResponse };
|
'notifications/test-notification': { req: EmptyRequest; res: EmptyResponse };
|
||||||
'page-push': { req: PagePushRequest; res: EmptyResponse };
|
'page-push': { req: PagePushRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -2770,6 +2770,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['notifications/create'];
|
post: operations['notifications/create'];
|
||||||
};
|
};
|
||||||
|
'/notifications/flush': {
|
||||||
|
/**
|
||||||
|
* notifications/flush
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:notifications*
|
||||||
|
*/
|
||||||
|
post: operations['notifications/flush'];
|
||||||
|
};
|
||||||
'/notifications/mark-all-as-read': {
|
'/notifications/mark-all-as-read': {
|
||||||
/**
|
/**
|
||||||
* notifications/mark-all-as-read
|
* notifications/mark-all-as-read
|
||||||
|
@ -22056,6 +22065,50 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* notifications/flush
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:notifications*
|
||||||
|
*/
|
||||||
|
'notifications/flush': {
|
||||||
|
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'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* notifications/mark-all-as-read
|
* notifications/mark-all-as-read
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
|
|
@ -40,6 +40,7 @@ export type Channels = {
|
||||||
unreadNotification: (payload: Notification) => void;
|
unreadNotification: (payload: Notification) => void;
|
||||||
unreadMention: (payload: Note['id']) => void;
|
unreadMention: (payload: Note['id']) => void;
|
||||||
readAllUnreadMentions: () => void;
|
readAllUnreadMentions: () => void;
|
||||||
|
notificationFlushed: () => void;
|
||||||
unreadSpecifiedNote: (payload: Note['id']) => void;
|
unreadSpecifiedNote: (payload: Note['id']) => void;
|
||||||
readAllUnreadSpecifiedNotes: () => void;
|
readAllUnreadSpecifiedNotes: () => void;
|
||||||
readAllAntennas: () => void;
|
readAllAntennas: () => void;
|
||||||
|
|
Loading…
Reference in a new issue