feat: Observe notification read and fix #6406 (#6407)

* Resolve https://github.com/syuilo/misskey/pull/6406#issuecomment-633203670

* Improve typing

* Observe notification read

* capture readAllNotifications

* fix

* fix

* Refactor

* Update src/client/components/notification.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* Update src/client/components/notification.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* missing ;

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
syuilo 2020-06-04 22:07:39 +09:00 committed by GitHub
commit 66de51c1ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 55 additions and 14 deletions

View file

@ -90,9 +90,36 @@ export default Vue.extend({
getNoteSummary: (text: string) => noteSummary(text, this.$root.i18n.messages[this.$root.i18n.locale]), getNoteSummary: (text: string) => noteSummary(text, this.$root.i18n.messages[this.$root.i18n.locale]),
followRequestDone: false, followRequestDone: false,
groupInviteDone: false, groupInviteDone: false,
connection: null,
readObserver: null,
faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck, faPollH 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: { methods: {
acceptFollowRequest() { acceptFollowRequest() {
this.followRequestDone = true; this.followRequestDone = true;

View file

@ -71,11 +71,14 @@ export default Vue.extend({
methods: { methods: {
onNotification(notification) { onNotification(notification) {
// TODO: () if (document.visibilityState === 'visible') {
this.$root.stream.send('readNotification', { this.$root.stream.send('readNotification', {
id: notification.id id: notification.id
}); });
notification.isRead = true;
}
this.prepend(notification); this.prepend(notification);
}, },
} }

View file

@ -67,6 +67,7 @@ import extractMentions from '../../misc/extract-mentions';
import getAcct from '../../misc/acct/render'; import getAcct from '../../misc/acct/render';
import { formatTimeString } from '../../misc/format-time-string'; import { formatTimeString } from '../../misc/format-time-string';
import { selectDriveFile } from '../scripts/select-drive-file'; import { selectDriveFile } from '../scripts/select-drive-file';
import { noteVisibilities } from '../../types';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -407,7 +408,7 @@ export default Vue.extend({
}, },
applyVisibility(v: string) { 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() { addVisibleUser() {

View file

@ -2,6 +2,8 @@ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typ
import { User } from './user'; import { User } from './user';
import { DriveFile } from './drive-file'; import { DriveFile } from './drive-file';
import { id } from '../id'; import { id } from '../id';
import { noteVisibilities } from '../../types';
@Entity() @Entity()
@Index('IDX_NOTE_TAGS', { synchronize: false }) @Index('IDX_NOTE_TAGS', { synchronize: false })
@ -102,8 +104,8 @@ export class Note {
* followers ... * followers ...
* specified ... visibleUserIds * specified ... visibleUserIds
*/ */
@Column('enum', { enum: ['public', 'home', 'followers', 'specified'] }) @Column('enum', { enum: noteVisibilities })
public visibility: 'public' | 'home' | 'followers' | 'specified'; public visibility: typeof noteVisibilities[number];
@Index({ unique: true }) @Index({ unique: true })
@Column('varchar', { @Column('varchar', {

View file

@ -5,6 +5,7 @@ import { Note } from './note';
import { FollowRequest } from './follow-request'; import { FollowRequest } from './follow-request';
import { UserGroupInvitation } from './user-group-invitation'; import { UserGroupInvitation } from './user-group-invitation';
import { AccessToken } from './access-token'; import { AccessToken } from './access-token';
import { notificationTypes } from '../../types';
@Entity() @Entity()
export class Notification { export class Notification {
@ -66,10 +67,10 @@ export class Notification {
*/ */
@Index() @Index()
@Column('enum', { @Column('enum', {
enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'], enum: notificationTypes,
comment: 'The type of the Notification.' comment: 'The type of the Notification.'
}) })
public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'app'; public type: typeof notificationTypes[number];
/** /**
* *

View file

@ -2,6 +2,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'type
import { id } from '../id'; import { id } from '../id';
import { Note } from './note'; import { Note } from './note';
import { User } from './user'; import { User } from './user';
import { noteVisibilities } from '../../types';
@Entity() @Entity()
export class Poll { export class Poll {
@ -34,10 +35,10 @@ export class Poll {
//#region Denormalized fields //#region Denormalized fields
@Column('enum', { @Column('enum', {
enum: ['public', 'home', 'followers', 'specified'], enum: noteVisibilities,
comment: '[Denormalized]' comment: '[Denormalized]'
}) })
public noteVisibility: 'public' | 'home' | 'followers' | 'specified'; public noteVisibility: typeof noteVisibilities[number];
@Index() @Index()
@Column({ @Column({

View file

@ -19,6 +19,7 @@ export class NotificationRepository extends Repository<Notification> {
id: notification.id, id: notification.id,
createdAt: notification.createdAt.toISOString(), createdAt: notification.createdAt.toISOString(),
type: notification.type, type: notification.type,
isRead: notification.isRead,
userId: notification.notifierId, userId: notification.notifierId,
user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null, user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
...(notification.type === 'mention' ? { ...(notification.type === 'mention' ? {

View file

@ -4,6 +4,7 @@ import { readNotification } from '../../common/read-notification';
import define from '../../define'; import define from '../../define';
import { makePaginationQuery } from '../../common/make-pagination-query'; import { makePaginationQuery } from '../../common/make-pagination-query';
import { Notifications, Followings, Mutings, Users } from '../../../../models'; import { Notifications, Followings, Mutings, Users } from '../../../../models';
import { notificationTypes } from '../../../../types';
export const meta = { export const meta = {
desc: { desc: {
@ -42,12 +43,12 @@ export const meta = {
}, },
includeTypes: { 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[] default: [] as string[]
}, },
excludeTypes: { 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[] default: [] as string[]
} }
}, },

View file

@ -11,6 +11,7 @@ import { Users, DriveFiles, Notes } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file'; import { DriveFile } from '../../../../models/entities/drive-file';
import { Note } from '../../../../models/entities/note'; import { Note } from '../../../../models/entities/note';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits'; import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
import { noteVisibilities } from '../../../../types';
let maxNoteTextLength = 500; let maxNoteTextLength = 500;
@ -38,7 +39,7 @@ export const meta = {
params: { params: {
visibility: { visibility: {
validator: $.optional.str.or(['public', 'home', 'followers', 'specified']), validator: $.optional.str.or(noteVisibilities as unknown as string[]),
default: 'public', default: 'public',
desc: { desc: {
'ja-JP': '投稿の公開範囲' 'ja-JP': '投稿の公開範囲'

3
src/types.ts Normal file
View file

@ -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;