(add) backend, frontend impl

This commit is contained in:
kakkokari-gtyih 2023-09-10 18:42:46 +09:00
parent 06fefa8ab2
commit 41a6f6271a
14 changed files with 95 additions and 25 deletions

3
locales/index.d.ts vendored
View File

@ -2133,7 +2133,8 @@ export interface Locale {
"emptyPushNotificationMessage": string;
"achievementEarned": string;
"testNotification": string;
"doTestNotification": string;
"checkNotificationBehavior": string;
"sendTestNotification": string;
"notificationWillBeDisplayedLikeThis": string;
"_types": {
"all": string;

View File

@ -2048,7 +2048,8 @@ _notification:
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
achievementEarned: "実績を獲得"
testNotification: "通知テスト"
doTestNotification: "通知をテストする"
checkNotificationBehavior: "通知の表示を確かめる"
sendTestNotification: "テスト通知を送信する"
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
_types:

View File

@ -33,6 +33,7 @@ export type MiNotification = {
* followRequestAccepted -
* achievementEarned -
* app -
* test -
*/
type: typeof notificationTypes[number];

View File

@ -283,6 +283,7 @@ 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___notifications_create from './endpoints/notifications/create.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___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
@ -629,6 +630,7 @@ const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep__
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_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 $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
@ -979,6 +981,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_userListTimeline,
$notifications_create,
$notifications_markAllAsRead,
$notifications_testNotification,
$pagePush,
$pages_create,
$pages_delete,

View File

@ -283,6 +283,7 @@ 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___notifications_create from './endpoints/notifications/create.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___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
@ -627,6 +628,7 @@ const eps = [
['notes/user-list-timeline', ep___notes_userListTimeline],
['notifications/create', ep___notifications_create],
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
['notifications/test-notification', ep___notifications_testNotification],
['page-push', ep___pagePush],
['pages/create', ep___pages_create],
['pages/delete', ep___pages_delete],

View File

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* 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'],
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, user) => {
this.notificationService.createNotification(user.id, 'test', {});
});
}
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const;
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const;
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;

View File

@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.head">
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
<div
@ -47,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<header :class="$style.header">
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
<span v-else>{{ notification.header }}</span>
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
@ -91,6 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
</div>
</template>
<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
<span v-else-if="notification.type === 'app'" :class="$style.text">
<Mfm :text="notification.body" :nowrap="false"/>
</span>
@ -113,6 +116,7 @@ import { i18n } from '@/i18n';
import * as os from '@/os';
import { useTooltip } from '@/scripts/use-tooltip';
import { $i } from '@/account';
import { infoImageUrl } from '@/instance';
const props = withDefaults(defineProps<{
notification: Misskey.entities.Notification;

View File

@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
</MkRadios>
<MkButton @click="testNotification()">{{ i18n.ts._notification.doTestNotification }}</MkButton>
<MkButton @click="testNotification('client')">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
</div>
</FormSection>
@ -192,7 +192,7 @@ import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { infoImageUrl } from '@/instance';
import { testNotification } from '@/scripts/test-notification';
const lang = ref(miLocalStorage.getItem('lang'));
const fontSize = ref(miLocalStorage.getItem('fontSize'));
@ -305,14 +305,6 @@ function removeEmojiIndex(lang: string) {
os.promiseDialog(main());
}
function testNotification(): void {
os.api('notifications/create', {
header: i18n.ts._notification.testNotification,
body: i18n.ts._notification.notificationWillBeDisplayedLikeThis,
icon: infoImageUrl.value,
});
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);

View File

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormSection>
<FormSection>
<div class="_gaps_m">
<FormLink @click="testNotification">{{ i18n.ts._notification.doTestNotification }}</FormLink>
<FormLink @click="testNotification('server')">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
</div>
</FormSection>
<FormSection>
@ -46,7 +46,7 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
import { notificationTypes } from '@/const';
import { infoImageUrl } from '@/instance';
import { testNotification } from '@/scripts/test-notification';
let allowButton = $shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer);
@ -89,14 +89,6 @@ function onChangeSendReadMessage(v: boolean) {
});
}
function testNotification(): void {
os.api('notifications/create', {
header: i18n.ts._notification.testNotification,
body: i18n.ts._notification.notificationWillBeDisplayedLikeThis,
icon: infoImageUrl.value,
});
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);

View File

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
import * as os from '@/os';
import { globalEvents } from '@/events';
/**
*
*
* - `client`
* - `server`
*
* @param type
*/
export function testNotification(type: 'client' | 'server'): void {
const notification: Misskey.entities.Notification = {
id: Math.random().toString(),
createdAt: new Date().toUTCString(),
isRead: false,
type: 'test',
};
switch (type) {
case 'server':
os.api('notifications/test-notification');
break;
case 'client':
globalEvents.emit('notification', notification);
break;
}
}

View File

@ -56,6 +56,7 @@ import { $i } from '@/account';
import { useStream } from '@/stream';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { globalEvents } from '@/events';
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
@ -64,11 +65,13 @@ const dev = _DEV_;
let notifications = $ref<Misskey.entities.Notification[]>([]);
function onNotification(notification) {
function onNotification(notification: Misskey.entities.Notification, isClient: boolean = false) {
if ($i.mutingNotificationTypes.includes(notification.type)) return;
if (document.visibilityState === 'visible') {
useStream().send('readNotification');
if (!isClient) {
useStream().send('readNotification');
}
notifications.unshift(notification);
window.setTimeout(() => {
@ -86,6 +89,7 @@ function onNotification(notification) {
if ($i) {
const connection = useStream().useChannel('main', null, 'UI');
connection.on('notification', onNotification);
globalEvents.on('notification', notification => onNotification(notification, true));
//#region Listen message from SW
if ('serviceWorker' in navigator) {

View File

@ -534,6 +534,7 @@ export type Endpoints = {
// notifications
'notifications/create': { req: { body: string; header?: string | null; icon?: string | null; }; res: null; };
'notifications/test-notification': { req: NoParams; res: null; };
'notifications/mark-all-as-read': { req: NoParams; res: null; };
// page-push

View File

@ -257,6 +257,8 @@ export type Notification = {
header?: string | null;
body: string;
icon?: string | null;
} | {
type: 'test';
});
export type MessagingMessage = {