diff --git a/client/src/app/header/notification-dropdown.component.html b/client/src/app/header/notification-dropdown.component.html index 2bf61f902..53f04cbdf 100644 --- a/client/src/app/header/notification-dropdown.component.html +++ b/client/src/app/header/notification-dropdown.component.html @@ -66,6 +66,7 @@ diff --git a/client/src/app/header/notification-dropdown.component.ts b/client/src/app/header/notification-dropdown.component.ts index 60d9c7236..c9eed52e3 100644 --- a/client/src/app/header/notification-dropdown.component.ts +++ b/client/src/app/header/notification-dropdown.component.ts @@ -35,6 +35,7 @@ export class NotificationDropdownComponent implements OnInit, OnDestroy { opened = false markAllAsReadSubject = new Subject() + userNotificationReload = new Subject() private notificationSub: Subscription private routeSub: Subscription @@ -78,6 +79,8 @@ export class NotificationDropdownComponent implements OnInit, OnDestroy { } onDropdownShown () { + if (this.loaded) this.userNotificationReload.next(true) + this.opened = true } diff --git a/client/src/app/shared/standalone-notifications/user-notifications.component.html b/client/src/app/shared/standalone-notifications/user-notifications.component.html index c0c824221..4b05d38d3 100644 --- a/client/src/app/shared/standalone-notifications/user-notifications.component.html +++ b/client/src/app/shared/standalone-notifications/user-notifications.component.html @@ -63,11 +63,15 @@ -
- Your abuse {{ notification.abuse.id }} has been - accepted - rejected -
+ @if (isAbuseAccepted(notification)) { + + } @else { + + }
@@ -227,9 +231,15 @@ -
- {{ notification.video.channel.displayName }} is live streaming in {{ notification.video.name }} -
+ @if (isVideoPublished(notification)) { +
+ {{ notification.video.channel.displayName }} is live streaming: {{ notification.video.name }} +
+ } @else { +
+ {{ notification.video.channel.displayName }} went live: {{ notification.video.name }} +
+ } } @else { diff --git a/client/src/app/shared/standalone-notifications/user-notifications.component.ts b/client/src/app/shared/standalone-notifications/user-notifications.component.ts index a3f25fcc0..37cf4848a 100644 --- a/client/src/app/shared/standalone-notifications/user-notifications.component.ts +++ b/client/src/app/shared/standalone-notifications/user-notifications.component.ts @@ -1,7 +1,7 @@ import { Subject } from 'rxjs' import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { ComponentPagination, hasMoreItems, Notifier } from '@app/core' -import { AbuseState } from '@peertube/peertube-models' +import { AbuseState, VideoState } from '@peertube/peertube-models' import { CommonModule } from '@angular/common' import { GlobalIconComponent } from '../shared-icons/global-icon.component' import { RouterLink } from '@angular/router' @@ -22,6 +22,7 @@ export class UserNotificationsComponent implements OnInit { @Input() infiniteScroll = true @Input() itemsPerPage = 20 @Input() markAllAsReadSubject: Subject + @Input() userNotificationReload: Subject @Output() notificationsLoaded = new EventEmitter() @@ -49,9 +50,15 @@ export class UserNotificationsComponent implements OnInit { if (this.markAllAsReadSubject) { this.markAllAsReadSubject.subscribe(() => this.markAllAsRead()) } + + if (this.userNotificationReload) { + this.userNotificationReload.subscribe(() => this.loadNotifications(true)) + } } loadNotifications (reset?: boolean) { + if (reset) this.componentPagination.currentPage = 1 + const options = { pagination: this.componentPagination, ignoreLoadingBar: this.ignoreLoadingBar, @@ -123,7 +130,11 @@ export class UserNotificationsComponent implements OnInit { this.loadNotifications(true) } - isAccepted (notification: UserNotification) { + isAbuseAccepted (notification: UserNotification) { return notification.abuse.state === AbuseState.ACCEPTED } + + isVideoPublished (notification: UserNotification) { + return notification.video.state.id === VideoState.PUBLISHED + } } diff --git a/packages/models/src/users/user-notification.model.ts b/packages/models/src/users/user-notification.model.ts index 362c080ec..678262589 100644 --- a/packages/models/src/users/user-notification.model.ts +++ b/packages/models/src/users/user-notification.model.ts @@ -2,6 +2,7 @@ import { FollowState } from '../actors/index.js' import { AbuseStateType } from '../moderation/index.js' import { PluginType_Type } from '../plugins/index.js' import { VideoConstant } from '../videos/video-constant.model.js' +import { VideoStateType } from '../videos/video-state.enum.js' export const UserNotificationType = { NEW_VIDEO_FROM_SUBSCRIPTION: 1, @@ -49,6 +50,10 @@ export interface VideoInfo { uuid: string shortUUID: string name: string + state: { + id: VideoStateType + label: string + } } export interface AvatarInfo { diff --git a/packages/tests/src/shared/notifications.ts b/packages/tests/src/shared/notifications.ts index 569d03fd4..72abc9b9b 100644 --- a/packages/tests/src/shared/notifications.ts +++ b/packages/tests/src/shared/notifications.ts @@ -966,6 +966,7 @@ function checkVideo (video: any, videoName?: string, shortUUID?: string) { expect(video.shortUUID).to.equal(shortUUID) } + expect(video.state).to.exist expect(video.id).to.be.a('number') } diff --git a/server/core/lib/notifier/shared/video-publication/new-video-or-live-for-subscribers.ts b/server/core/lib/notifier/shared/video-publication/new-video-or-live-for-subscribers.ts index 882295a0e..1940a55d6 100644 --- a/server/core/lib/notifier/shared/video-publication/new-video-or-live-for-subscribers.ts +++ b/server/core/lib/notifier/shared/video-publication/new-video-or-live-for-subscribers.ts @@ -78,7 +78,7 @@ export class NewVideoOrLiveForSubscribers extends AbstractNotification VideoChannel"."id" AS "Video.VideoChannel.id", "Video->VideoChannel"."name" AS "Video.VideoChannel.name", "Video->VideoChannel->Actor"."id" AS "Video.VideoChannel.Actor.id", @@ -106,18 +107,21 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { "VideoComment->Video"."id" AS "VideoComment.Video.id", "VideoComment->Video"."uuid" AS "VideoComment.Video.uuid", "VideoComment->Video"."name" AS "VideoComment.Video.name", + "VideoComment->Video"."state" AS "VideoComment.Video.state", "Abuse"."id" AS "Abuse.id", "Abuse"."state" AS "Abuse.state", "Abuse->VideoAbuse"."id" AS "Abuse.VideoAbuse.id", "Abuse->VideoAbuse->Video"."id" AS "Abuse.VideoAbuse.Video.id", "Abuse->VideoAbuse->Video"."uuid" AS "Abuse.VideoAbuse.Video.uuid", "Abuse->VideoAbuse->Video"."name" AS "Abuse.VideoAbuse.Video.name", + "Abuse->VideoAbuse->Video"."state" AS "Abuse.VideoAbuse.Video.state", "Abuse->VideoCommentAbuse"."id" AS "Abuse.VideoCommentAbuse.id", "Abuse->VideoCommentAbuse->VideoComment"."id" AS "Abuse.VideoCommentAbuse.VideoComment.id", "Abuse->VideoCommentAbuse->VideoComment"."originCommentId" AS "Abuse.VideoCommentAbuse.VideoComment.originCommentId", "Abuse->VideoCommentAbuse->VideoComment->Video"."id" AS "Abuse.VideoCommentAbuse.VideoComment.Video.id", "Abuse->VideoCommentAbuse->VideoComment->Video"."name" AS "Abuse.VideoCommentAbuse.VideoComment.Video.name", "Abuse->VideoCommentAbuse->VideoComment->Video"."uuid" AS "Abuse.VideoCommentAbuse.VideoComment.Video.uuid", + "Abuse->VideoCommentAbuse->VideoComment->Video"."state" AS "Abuse.VideoCommentAbuse.VideoComment.Video.state", "Abuse->FlaggedAccount"."id" AS "Abuse.FlaggedAccount.id", "Abuse->FlaggedAccount"."name" AS "Abuse.FlaggedAccount.name", "Abuse->FlaggedAccount"."description" AS "Abuse.FlaggedAccount.description", @@ -138,6 +142,7 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { "VideoBlacklist->Video"."id" AS "VideoBlacklist.Video.id", "VideoBlacklist->Video"."uuid" AS "VideoBlacklist.Video.uuid", "VideoBlacklist->Video"."name" AS "VideoBlacklist.Video.name", + "VideoBlacklist->Video"."state" AS "VideoBlacklist.Video.name", "VideoImport"."id" AS "VideoImport.id", "VideoImport"."magnetUri" AS "VideoImport.magnetUri", "VideoImport"."targetUrl" AS "VideoImport.targetUrl", @@ -145,6 +150,7 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { "VideoImport->Video"."id" AS "VideoImport.Video.id", "VideoImport->Video"."uuid" AS "VideoImport.Video.uuid", "VideoImport->Video"."name" AS "VideoImport.Video.name", + "VideoImport->Video"."state" AS "VideoImport.Video.name", "Plugin"."id" AS "Plugin.id", "Plugin"."name" AS "Plugin.name", "Plugin"."type" AS "Plugin.type", @@ -188,7 +194,8 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { "VideoCaption"."language" AS "VideoCaption.language", "VideoCaption->Video"."id" AS "VideoCaption.Video.id", "VideoCaption->Video"."uuid" AS "VideoCaption.Video.uuid", - "VideoCaption->Video"."name" AS "VideoCaption.Video.name"` + "VideoCaption->Video"."name" AS "VideoCaption.Video.name", + "VideoCaption->Video"."state" AS "VideoCaption.Video.state"` } private getJoins () { diff --git a/server/core/models/user/user-notification.ts b/server/core/models/user/user-notification.ts index d1cd9c123..2f742a2b1 100644 --- a/server/core/models/user/user-notification.ts +++ b/server/core/models/user/user-notification.ts @@ -12,6 +12,7 @@ import { ActorFollowModel } from '../actor/actor-follow.js' import { ApplicationModel } from '../application/application.js' import { PluginModel } from '../server/plugin.js' import { SequelizeModel, throwIfNotValid } from '../shared/index.js' +import { getStateLabel } from '../video/formatter/video-api-format.js' import { VideoBlacklistModel } from '../video/video-blacklist.js' import { VideoCaptionModel } from '../video/video-caption.js' import { VideoCommentModel } from '../video/video-comment.js' @@ -490,7 +491,11 @@ export class UserNotificationModel extends SequelizeModel id: video.id, uuid: video.uuid, shortUUID: uuidToShort(video.uuid), - name: video.name + name: video.name, + state: { + id: video.state, + label: getStateLabel(video.state) + } } } @@ -500,12 +505,7 @@ export class UserNotificationModel extends SequelizeModel threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), video: abuse.VideoCommentAbuse.VideoComment.Video - ? { - id: abuse.VideoCommentAbuse.VideoComment.Video.id, - name: abuse.VideoCommentAbuse.VideoComment.Video.name, - shortUUID: uuidToShort(abuse.VideoCommentAbuse.VideoComment.Video.uuid), - uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid - } + ? this.formatVideo(abuse.VideoCommentAbuse.VideoComment.Video) : undefined } : undefined diff --git a/server/core/models/video/video-caption.ts b/server/core/models/video/video-caption.ts index 2be742e63..1e6971845 100644 --- a/server/core/models/video/video-caption.ts +++ b/server/core/models/video/video-caption.ts @@ -30,14 +30,16 @@ import { SequelizeModel, buildWhereIdOrUUID, throwIfNotValid } from '../shared/i import { VideoModel } from './video.js' export enum ScopeNames { - WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' + CAPTION_WITH_VIDEO = 'CAPTION_WITH_VIDEO' } +const videoAttributes = [ 'id', 'name', 'remote', 'uuid', 'url', 'state' ] + @Scopes(() => ({ - [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { + [ScopeNames.CAPTION_WITH_VIDEO]: { include: [ { - attributes: [ 'id', 'uuid', 'remote' ], + attributes: videoAttributes, model: VideoModel.unscoped(), required: true } @@ -130,17 +132,13 @@ export class VideoCaptionModel extends SequelizeModel { static loadByVideoIdAndLanguage (videoId: string | number, language: string, transaction?: Transaction): Promise { const videoInclude = { model: VideoModel.unscoped(), - attributes: [ 'id', 'name', 'remote', 'uuid', 'url' ], + attributes: videoAttributes, where: buildWhereIdOrUUID(videoId) } const query = { - where: { - language - }, - include: [ - videoInclude - ], + where: { language }, + include: [ videoInclude ], transaction } @@ -155,7 +153,7 @@ export class VideoCaptionModel extends SequelizeModel { include: [ { model: VideoModel.unscoped(), - attributes: [ 'id', 'remote', 'uuid' ] + attributes: videoAttributes } ] } @@ -186,7 +184,7 @@ export class VideoCaptionModel extends SequelizeModel { transaction } - return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query) + return VideoCaptionModel.scope(ScopeNames.CAPTION_WITH_VIDEO).findAll(query) } static async listCaptionsOfMultipleVideos (videoIds: number[], transaction?: Transaction) { @@ -200,7 +198,7 @@ export class VideoCaptionModel extends SequelizeModel { transaction } - const captions = await VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query) + const captions = await VideoCaptionModel.scope(ScopeNames.CAPTION_WITH_VIDEO).findAll(query) const result: { [ id: number ]: MVideoCaptionVideo[] } = {} for (const id of videoIds) { diff --git a/server/core/types/models/user/user-notification.ts b/server/core/types/models/user/user-notification.ts index 83411d3fd..0e59c4754 100644 --- a/server/core/types/models/user/user-notification.ts +++ b/server/core/types/models/user/user-notification.ts @@ -25,7 +25,7 @@ type Use = PickWith - export type VideoInclude = Pick + export type VideoInclude = Pick export type VideoIncludeChannel = VideoInclude & PickWith @@ -58,7 +58,7 @@ export module UserNotificationIncludes { Pick & PickWith & - PickWith>> + PickWith>> export type AbuseInclude = Pick & diff --git a/server/core/types/models/video/video-caption.ts b/server/core/types/models/video/video-caption.ts index 3dca15104..ebba1edb5 100644 --- a/server/core/types/models/video/video-caption.ts +++ b/server/core/types/models/video/video-caption.ts @@ -17,7 +17,7 @@ export type MVideoCaptionLanguageUrl = export type MVideoCaptionVideo = MVideoCaption & - Use<'Video', Pick> + Use<'Video', Pick> // ############################################################################ diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index e91b71f41..2ce57dd8f 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -8252,6 +8252,8 @@ components: $ref: '#/components/schemas/Video/properties/uuid' name: $ref: '#/components/schemas/Video/properties/name' + state: + $ref: '#/components/schemas/Video/properties/state' Video: properties: id: