diff --git a/src/client/components/notification.vue b/src/client/components/notification.vue index de233d14a..9e4806f05 100644 --- a/src/client/components/notification.vue +++ b/src/client/components/notification.vue @@ -90,9 +90,36 @@ export default Vue.extend({ getNoteSummary: (text: string) => noteSummary(text, this.$root.i18n.messages[this.$root.i18n.locale]), followRequestDone: false, groupInviteDone: false, + connection: null, + readObserver: null, faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck, faPollH }; }, + + mounted() { + if (!this.notification.isRead) { + this.readObserver = new IntersectionObserver((entries, observer) => { + if (!entries.some(entry => entry.isIntersecting)) return; + this.$root.stream.send('readNotification', { + id: this.notification.id + }); + entries.map(({ target }) => observer.unobserve(target)); + }); + + this.readObserver.observe(this.$el); + + this.connection = this.$root.stream.useSharedConnection('main'); + this.connection.on('readAllNotifications', () => this.readObserver.unobserve(this.$el)); + } + }, + + beforeDestroy() { + if (!this.notification.isRead) { + this.readObserver.unobserve(this.$el); + this.connection.dispose(); + } + }, + methods: { acceptFollowRequest() { this.followRequestDone = true; diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index 36464a309..5c5b5fb81 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -71,10 +71,13 @@ export default Vue.extend({ methods: { onNotification(notification) { - // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない - this.$root.stream.send('readNotification', { - id: notification.id - }); + if (document.visibilityState === 'visible') { + this.$root.stream.send('readNotification', { + id: notification.id + }); + + notification.isRead = true; + } this.prepend(notification); }, diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 07f6f0b5d..444bf229e 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -67,6 +67,7 @@ import extractMentions from '../../misc/extract-mentions'; import getAcct from '../../misc/acct/render'; import { formatTimeString } from '../../misc/format-time-string'; import { selectDriveFile } from '../scripts/select-drive-file'; +import { noteVisibilities } from '../../types'; export default Vue.extend({ components: { @@ -407,7 +408,7 @@ export default Vue.extend({ }, applyVisibility(v: string) { - this.visibility = ['public', 'home', 'followers', 'specified'].includes(v) ? v : 'public'; // v11互換性のため + this.visibility = (noteVisibilities as unknown as string[]).includes(v) ? v : 'public'; // v11互換性のため }, addVisibleUser() { diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 79b6b5ab7..196be1e35 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -2,6 +2,8 @@ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typ import { User } from './user'; import { DriveFile } from './drive-file'; import { id } from '../id'; +import { noteVisibilities } from '../../types'; + @Entity() @Index('IDX_NOTE_TAGS', { synchronize: false }) @@ -102,8 +104,8 @@ export class Note { * followers ... フォロワーのみ * specified ... visibleUserIds で指定したユーザーのみ */ - @Column('enum', { enum: ['public', 'home', 'followers', 'specified'] }) - public visibility: 'public' | 'home' | 'followers' | 'specified'; + @Column('enum', { enum: noteVisibilities }) + public visibility: typeof noteVisibilities[number]; @Index({ unique: true }) @Column('varchar', { diff --git a/src/models/entities/notification.ts b/src/models/entities/notification.ts index 565645a5d..988fdb341 100644 --- a/src/models/entities/notification.ts +++ b/src/models/entities/notification.ts @@ -5,6 +5,7 @@ import { Note } from './note'; import { FollowRequest } from './follow-request'; import { UserGroupInvitation } from './user-group-invitation'; import { AccessToken } from './access-token'; +import { notificationTypes } from '../../types'; @Entity() export class Notification { @@ -66,10 +67,10 @@ export class Notification { */ @Index() @Column('enum', { - enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'], + enum: notificationTypes, comment: 'The type of the Notification.' }) - public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'app'; + public type: typeof notificationTypes[number]; /** * 通知が読まれたかどうか diff --git a/src/models/entities/poll.ts b/src/models/entities/poll.ts index 6bb67163a..e3bbb1c3f 100644 --- a/src/models/entities/poll.ts +++ b/src/models/entities/poll.ts @@ -2,6 +2,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'type import { id } from '../id'; import { Note } from './note'; import { User } from './user'; +import { noteVisibilities } from '../../types'; @Entity() export class Poll { @@ -34,10 +35,10 @@ export class Poll { //#region Denormalized fields @Column('enum', { - enum: ['public', 'home', 'followers', 'specified'], + enum: noteVisibilities, comment: '[Denormalized]' }) - public noteVisibility: 'public' | 'home' | 'followers' | 'specified'; + public noteVisibility: typeof noteVisibilities[number]; @Index() @Column({ diff --git a/src/models/repositories/notification.ts b/src/models/repositories/notification.ts index b484c43c5..40f43d6c1 100644 --- a/src/models/repositories/notification.ts +++ b/src/models/repositories/notification.ts @@ -19,6 +19,7 @@ export class NotificationRepository extends Repository { id: notification.id, createdAt: notification.createdAt.toISOString(), type: notification.type, + isRead: notification.isRead, userId: notification.notifierId, user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null, ...(notification.type === 'mention' ? { diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 9a2e17a71..db6772beb 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -4,6 +4,7 @@ import { readNotification } from '../../common/read-notification'; import define from '../../define'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Notifications, Followings, Mutings, Users } from '../../../../models'; +import { notificationTypes } from '../../../../types'; export const meta = { desc: { @@ -42,12 +43,12 @@ export const meta = { }, includeTypes: { - validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted'])), + validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])), default: [] as string[] }, excludeTypes: { - validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted'])), + validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])), default: [] as string[] } }, diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index cccf138ad..5076dad82 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -11,6 +11,7 @@ import { Users, DriveFiles, Notes } from '../../../../models'; import { DriveFile } from '../../../../models/entities/drive-file'; import { Note } from '../../../../models/entities/note'; import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits'; +import { noteVisibilities } from '../../../../types'; let maxNoteTextLength = 500; @@ -38,7 +39,7 @@ export const meta = { params: { visibility: { - validator: $.optional.str.or(['public', 'home', 'followers', 'specified']), + validator: $.optional.str.or(noteVisibilities as unknown as string[]), default: 'public', desc: { 'ja-JP': '投稿の公開範囲' diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..30a62412a --- /dev/null +++ b/src/types.ts @@ -0,0 +1,3 @@ +export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const; + +export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;