Better live notification label

This commit is contained in:
Chocobozzz 2025-01-16 10:47:45 +01:00
parent d5c4cc2b44
commit 82246a0c8d
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
13 changed files with 73 additions and 35 deletions

View File

@ -66,6 +66,7 @@
</div>
<my-user-notifications
#userNotifications
[ignoreLoadingBar]="true" [infiniteScroll]="false" [itemsPerPage]="10"
[markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()"
></my-user-notifications>

View File

@ -35,6 +35,7 @@ export class NotificationDropdownComponent implements OnInit, OnDestroy {
opened = false
markAllAsReadSubject = new Subject<boolean>()
userNotificationReload = new Subject<boolean>()
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
}

View File

@ -63,11 +63,15 @@
<ng-container *ngSwitchCase="15"> <!-- UserNotificationType.ABUSE_STATE_CHANGE -->
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
<a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been
<ng-container *ngIf="isAccepted(notification)">accepted</ng-container>
<ng-container *ngIf="!isAccepted(notification)">rejected</ng-container>
</div>
@if (isAbuseAccepted(notification)) {
<div class="message" i18n>
<a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been accepted
</div>
} @else {
<div class="message" i18n>
<a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been accepted
</div>
}
</ng-container>
<ng-container *ngSwitchCase="16"> <!-- UserNotificationType.ABUSE_NEW_MESSAGE -->
@ -227,9 +231,15 @@
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
</a>
<div class="message" i18n>
{{ notification.video.channel.displayName }} is live streaming in <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
</div>
@if (isVideoPublished(notification)) {
<div class="message" i18n>
{{ notification.video.channel.displayName }} is live streaming: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
</div>
} @else {
<div class="message" i18n>
{{ notification.video.channel.displayName }} went live: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
</div>
}
} @else {
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>

View File

@ -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<boolean>
@Input() userNotificationReload: Subject<boolean>
@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
}
}

View File

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

View File

@ -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')
}

View File

@ -78,7 +78,7 @@ export class NewVideoOrLiveForSubscribers extends AbstractNotification <MVideoAc
return {
to,
subject: channelName + ' is live streaming',
text: `Your subscription ${channelName} is live streaming in "${this.payload.name}".`,
text: `Your subscription ${channelName} is live streaming: "${this.payload.name}".`,
locals: {
title: 'New content ',
action: {

View File

@ -80,6 +80,7 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery {
"Video"."id" AS "Video.id",
"Video"."uuid" AS "Video.uuid",
"Video"."name" AS "Video.name",
"Video"."state" AS "Video.state",
"Video->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 () {

View File

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

View File

@ -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<VideoCaptionModel> {
static loadByVideoIdAndLanguage (videoId: string | number, language: string, transaction?: Transaction): Promise<MVideoCaptionVideo> {
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<VideoCaptionModel> {
include: [
{
model: VideoModel.unscoped(),
attributes: [ 'id', 'remote', 'uuid' ]
attributes: videoAttributes
}
]
}
@ -186,7 +184,7 @@ export class VideoCaptionModel extends SequelizeModel<VideoCaptionModel> {
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<VideoCaptionModel> {
transaction
}
const captions = await VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll<MVideoCaptionVideo>(query)
const captions = await VideoCaptionModel.scope(ScopeNames.CAPTION_WITH_VIDEO).findAll<MVideoCaptionVideo>(query)
const result: { [ id: number ]: MVideoCaptionVideo[] } = {}
for (const id of videoIds) {

View File

@ -25,7 +25,7 @@ type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationMo
export module UserNotificationIncludes {
export type ActorImageInclude = Pick<ActorImageModel, 'createdAt' | 'filename' | 'getStaticPath' | 'width' | 'updatedAt'>
export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'>
export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name' | 'state'>
export type VideoIncludeChannel =
VideoInclude &
PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor>
@ -58,7 +58,7 @@ export module UserNotificationIncludes {
Pick<VideoCommentAbuseModel, 'id'> &
PickWith<VideoCommentAbuseModel, 'VideoComment',
Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid'>>>
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid' | 'state'>>>
export type AbuseInclude =
Pick<AbuseModel, 'id' | 'state'> &

View File

@ -17,7 +17,7 @@ export type MVideoCaptionLanguageUrl =
export type MVideoCaptionVideo =
MVideoCaption &
Use<'Video', Pick<MVideo, 'id' | 'name' | 'remote' | 'uuid' | 'url' | 'getWatchStaticPath'>>
Use<'Video', Pick<MVideo, 'id' | 'name' | 'remote' | 'uuid' | 'url' | 'state' | 'getWatchStaticPath'>>
// ############################################################################

View File

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