Merge branch 'Chocobozzz:develop' into feature/Remember-user-table-pagination-in-admin
This commit is contained in:
commit
6cd05dc963
|
@ -1,7 +1,10 @@
|
|||
<ng-template #modal>
|
||||
<div class="modal-header">
|
||||
<h1 i18n class="modal-title">Contact the administrator(s)<p class="modal-subtitle">{{ instanceName }}</p></h1>
|
||||
<my-global-icon iconName="cross" aria-label="Close" tabindex="0" role="button" (click)="hide()" (keydown.enter)="hide()"></my-global-icon>
|
||||
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Follow</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
<ng-container *ngIf="isReject()">Reject {{ registration.username }} registration</ng-container>
|
||||
</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="processRegistration()">
|
||||
|
|
|
@ -112,11 +112,13 @@
|
|||
<li *ngFor="let file of video.files">
|
||||
<a target="_blank" rel="noopener noreferrer" [href]="file.fileUrl">{{ file.resolution.label }}</a>: {{ file.size | bytes: 1 }}
|
||||
|
||||
<my-global-icon
|
||||
*ngIf="canRemoveOneFile(video)"
|
||||
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
|
||||
<button
|
||||
*ngIf="canRemoveOneFile(video)" class="border-0 p-0"
|
||||
i18n-title title="Delete this file"
|
||||
(click)="removeVideoFile(video, file, 'web-videos')"
|
||||
></my-global-icon>
|
||||
>
|
||||
<my-global-icon iconName="delete"></my-global-icon>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -128,11 +130,13 @@
|
|||
<li *ngFor="let file of video.streamingPlaylists[0].files">
|
||||
<a target="_blank" rel="noopener noreferrer" [href]="file.fileUrl">{{ file.resolution.label }}</a>: {{ file.size | bytes: 1 }}
|
||||
|
||||
<my-global-icon
|
||||
*ngIf="canRemoveOneFile(video)"
|
||||
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
|
||||
<button
|
||||
*ngIf="canRemoveOneFile(video)" class="border-0 p-0"
|
||||
i18n-title title="Delete this file"
|
||||
(click)="removeVideoFile(video, file, 'hls')"
|
||||
></my-global-icon>
|
||||
>
|
||||
<my-global-icon iconName="delete"></my-global-icon>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<h1 i18n class="title-page-v2">
|
||||
<h1 class="title-page-v2">
|
||||
<strong class="underline-orange">{{ instanceName }}</strong>
|
||||
>
|
||||
Login
|
||||
|
||||
<ng-container i18n>Login</ng-container>
|
||||
</h1>
|
||||
|
||||
<div class="margin-content">
|
||||
|
@ -120,7 +120,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Forgot your password</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideForgotPasswordModal()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hideForgotPasswordModal()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-start">
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<div class="modal-header">
|
||||
<h1 i18n class="modal-title">Accept ownership</h1>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" [formGroup]="form">
|
||||
|
|
|
@ -2,14 +2,20 @@
|
|||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Change ownership</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<label i18n for="next-ownership-username">Select the next owner</label>
|
||||
<p-autoComplete formControlName="username" [suggestions]="usernamePropositions"
|
||||
(completeMethod)="search($event)" id="next-ownership-username"></p-autoComplete>
|
||||
|
||||
<p-autoComplete
|
||||
formControlName="username" [suggestions]="usernamePropositions"
|
||||
(completeMethod)="search($event)" id="next-ownership-username"
|
||||
></p-autoComplete>
|
||||
|
||||
<div *ngIf="formErrors.username" class="form-error" role="alert">
|
||||
{{ formErrors.username }}
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Add caption</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-container [formGroup]="form">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Edit caption</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
<my-actor-avatar [actor]="user?.account" [actorType]="getAvatarActorType()" size="25"></my-actor-avatar>
|
||||
|
||||
<div class="textarea-wrapper">
|
||||
<textarea i18n-placeholder placeholder="Add comment..." myAutoResize
|
||||
[readonly]="(user === null) ? true : false"
|
||||
(click)="openVisitorModal($event)"
|
||||
formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }"
|
||||
(keyup.control.enter)="onValidKey()" (keyup.meta.enter)="onValidKey()" #textarea>
|
||||
<textarea
|
||||
i18n-placeholder placeholder="Add comment..." myAutoResize
|
||||
[readonly]="(user === null) ? true : false"
|
||||
(click)="openVisitorModal($event)"
|
||||
formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }"
|
||||
(keyup.control.enter)="onValidKey()" (keyup.meta.enter)="onValidKey()" #textarea
|
||||
>
|
||||
</textarea>
|
||||
|
||||
<my-help
|
||||
|
@ -57,7 +59,10 @@
|
|||
<ng-template #visitorModal let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title" i18n>You are one step away from commenting</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideModals()"></my-global-icon>
|
||||
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hideModals()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
@ -81,8 +86,12 @@
|
|||
<ng-template #emojiModal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title" i18n>Markdown Emoji List</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideModals()"></my-global-icon>
|
||||
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hideModals()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="emoji-flex">
|
||||
<div class="emoji-flex-item" *ngFor="let emojiMarkup of getEmojiMarkupList()">
|
||||
|
|
|
@ -19,23 +19,21 @@
|
|||
</div>
|
||||
|
||||
<div class="playlist-controls">
|
||||
<my-global-icon
|
||||
iconName="videos"
|
||||
[class.active]="autoPlayNextVideoPlaylist"
|
||||
(click)="switchAutoPlayNextVideoPlaylist()"
|
||||
[ngbTooltip]="autoPlayNextVideoPlaylistSwitchText"
|
||||
placement="bottom auto"
|
||||
container="body"
|
||||
></my-global-icon>
|
||||
<button
|
||||
class="border-0 p-0 me-2" [ngClass]="{ active: autoPlayNextVideoPlaylist }" (click)="switchAutoPlayNextVideoPlaylist()"
|
||||
[ngbTooltip]="autoPlayNextVideoPlaylistSwitchText" [ariaLabel]="autoPlayNextVideoPlaylistSwitchText"
|
||||
placement="bottom auto" container="body"
|
||||
>
|
||||
<my-global-icon iconName="videos"></my-global-icon>
|
||||
</button>
|
||||
|
||||
<my-global-icon
|
||||
iconName="repeat"
|
||||
[class.active]="loopPlaylist"
|
||||
(click)="switchLoopPlaylist()"
|
||||
[ngbTooltip]="loopPlaylistSwitchText"
|
||||
placement="bottom auto"
|
||||
container="body"
|
||||
></my-global-icon>
|
||||
<button
|
||||
class="border-0 p-0" [ngClass]="{ active: loopPlaylist }" (click)="switchLoopPlaylist()"
|
||||
[ngbTooltip]="loopPlaylistSwitchText" [ariaLabel]="loopPlaylistSwitchText"
|
||||
placement="bottom auto" container="body"
|
||||
>
|
||||
<my-global-icon iconName="repeat"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -46,18 +46,8 @@
|
|||
display: flex;
|
||||
margin: 10px 0;
|
||||
|
||||
my-global-icon:not(:last-child) {
|
||||
@include margin-right(.5rem);
|
||||
}
|
||||
|
||||
my-global-icon {
|
||||
&:not(.active) {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
cursor: pointer;
|
||||
}
|
||||
button:not(.active) {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
<div [innerHTML]="broadcastMessage.message"></div>
|
||||
|
||||
<button
|
||||
*ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()" class="border-0" title="Close this message" i18n-title>
|
||||
*ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()"
|
||||
class="border-0" title="Close this message" i18n-title
|
||||
>
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Change the language</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Welcome to {{ instanceName }}, dear user!</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Welcome to PeerTube, dear administrator!</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ title }}</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" >
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{title}}</h4>
|
||||
<my-global-icon *ngIf="close" iconName="cross" aria-label="Close" role="button" (click)="onCloseClick()"></my-global-icon>
|
||||
|
||||
<button *ngIf="close" class="border-0 p-0" title="Close this modal" i18n-title (click)="onCloseClick()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" [innerHTML]="content"></div>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Configuration warning!</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">My settings</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
<ng-container i18n *ngIf="!isAdminView">Messages with the moderation team</ng-container>
|
||||
</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Moderation comment</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<button class="feed border-0 p-0" *ngIf="syndicationItems && syndicationItems.length !== 0">
|
||||
<my-global-icon
|
||||
role="button" aria-label="Open syndication dropdown" i18n-aria-label
|
||||
*ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto"
|
||||
class="icon-syndication" iconName="syndication"
|
||||
>
|
||||
</my-global-icon>
|
||||
<button
|
||||
*ngIf="syndicationItems && syndicationItems.length !== 0"
|
||||
[ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto"
|
||||
class="feed border-0 p-0"
|
||||
title="Open syndication dropdown" i18n-title
|
||||
>
|
||||
<my-global-icon iconName="syndication"></my-global-icon>
|
||||
|
||||
<ng-template #feedsList>
|
||||
<a *ngFor="let item of syndicationItems" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
|
||||
<a *ngFor="let item of syndicationItems" class="feed-link" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
|
||||
</ng-template>
|
||||
</button>
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
.feed {
|
||||
width: 100%;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: pvar(--mainForegroundColor);
|
||||
display: block;
|
||||
min-width: 100px;
|
||||
.feed-link {
|
||||
color: pvar(--mainForegroundColor);
|
||||
display: block;
|
||||
min-width: 100px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ action }}</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ modalTitle }}</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal>
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Report video "{{ video.name }}"</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">{{ getModalTitle() }}</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
<h4 i18n class="modal-title" *ngIf="getSingleVideo().isLive">Block live "{{ getSingleVideo().name }}"</h4>
|
||||
</ng-container>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Support {{ displayName }}</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" [innerHTML]="htmlSupport"></div>
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Live information</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" *ngIf="live">
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
</div>
|
||||
</h4>
|
||||
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||
<my-global-icon iconName="cross"></my-global-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="margin-content">
|
||||
<div class="videos-header">
|
||||
<h1 *ngIf="displayTitle" class="title" placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
||||
<div class="videos-header pt-4 mb-4">
|
||||
<h1 *ngIf="displayTitle" class="title mb-1" placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
|||
<my-feed [syndicationItems]="syndicationItems"></my-feed>
|
||||
</div>
|
||||
|
||||
<div class="action-block">
|
||||
<div *ngIf="headerActions.length !== 0" class="action-block mt-3">
|
||||
<ng-container *ngFor="let action of headerActions">
|
||||
<a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active">
|
||||
<ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container>
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
@use '_miniature' as *;
|
||||
|
||||
// Cannot set margin top to videos-header because of the main header fixed position
|
||||
$margin-top: 30px;
|
||||
$margin-top: 2rem;
|
||||
|
||||
.videos-header {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title,
|
||||
.title-subscription {
|
||||
|
@ -21,9 +20,6 @@ $margin-top: 30px;
|
|||
color: pvar(--mainForegroundColor);
|
||||
display: inline-block;
|
||||
font-weight: $font-semibold;
|
||||
|
||||
margin-top: $margin-top;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.title-subscription {
|
||||
|
@ -39,7 +35,6 @@ $margin-top: 30px;
|
|||
.action-block {
|
||||
grid-column: 3;
|
||||
grid-row: 1/3;
|
||||
margin-top: $margin-top;
|
||||
}
|
||||
|
||||
my-feed {
|
||||
|
@ -77,15 +72,15 @@ $margin-top: 30px;
|
|||
@include margin-right(pvar(--horizontalMarginContent));
|
||||
|
||||
.video-wrapper {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
.videos-header,
|
||||
my-video-filters-header {
|
||||
@include margin-left(15px);
|
||||
@include margin-right(15px);
|
||||
@include margin-left(1rem);
|
||||
@include margin-right(1rem);
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -95,9 +90,8 @@ $margin-top: 30px;
|
|||
}
|
||||
|
||||
.videos-header {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,13 @@
|
|||
|
||||
<my-edit-button *ngIf="owned && touchScreenEditButton" [ptRouterLink]="[ '/my-library', 'video-playlists', playlist.uuid ]"></my-edit-button>
|
||||
|
||||
<div *ngIf="owned" class="more dropdown-root" ngbDropdown #moreDropdown="ngbDropdown" placement="left auto"
|
||||
(openChange)="onDropdownOpenChange()" autoClose="outside" container="body"
|
||||
<div
|
||||
*ngIf="owned" class="more dropdown-root" ngbDropdown #moreDropdown="ngbDropdown" placement="left auto"
|
||||
(openChange)="onDropdownOpenChange()" autoClose="outside" container="body"
|
||||
>
|
||||
<my-global-icon iconName="more-vertical" ngbDropdownToggle role="button" class="icon-more" (click)="$event.preventDefault()"></my-global-icon>
|
||||
<button class="border-0 p-0 more-button" (click)="$event.preventDefault()" ngbDropdownToggle>
|
||||
<my-global-icon iconName="more-vertical"></my-global-icon>
|
||||
</button>
|
||||
|
||||
<div ngbDropdownMenu>
|
||||
<ng-container *ngIf="playlistElement.video">
|
||||
|
|
|
@ -29,24 +29,6 @@ my-video-thumbnail,
|
|||
padding: 10px;
|
||||
border-bottom: 1px solid $separator-border-color;
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
|
||||
.more {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media not all and (hover: hover) and (pointer: fine) {
|
||||
.more {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.playing {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
@ -87,20 +69,38 @@ my-video-thumbnail,
|
|||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.more-button {
|
||||
opacity: 0;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
&::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.icon-more {
|
||||
my-global-icon {
|
||||
@include apply-svg-color(pvar(--greyForegroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
display: flex;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: 0;
|
||||
}
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
.show {
|
||||
.more-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media not all and (hover: hover) and (pointer: fine) {
|
||||
.more-button {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ my-video-thumbnail,
|
|||
my-edit-button {
|
||||
display: none;
|
||||
|
||||
+ .more {
|
||||
+ .more-button {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ my-video-thumbnail,
|
|||
}
|
||||
}
|
||||
|
||||
my-edit-button + .more {
|
||||
my-edit-button + .more-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,16 +17,14 @@ class ChaptersPlugin extends Plugin {
|
|||
this.player.ready(() => {
|
||||
player.addClass('vjs-chapters')
|
||||
|
||||
this.player.one('durationchange', () => {
|
||||
for (const chapter of this.chapters) {
|
||||
if (chapter.timecode === 0) continue
|
||||
for (const chapter of this.chapters) {
|
||||
if (chapter.timecode === 0) continue
|
||||
|
||||
const marker = new ProgressBarMarkerComponent(player, { timecode: chapter.timecode })
|
||||
const marker = new ProgressBarMarkerComponent(player, { timecode: chapter.timecode })
|
||||
|
||||
this.markers.push(marker)
|
||||
this.getSeekBar().addChild(marker)
|
||||
}
|
||||
})
|
||||
this.markers.push(marker)
|
||||
this.getSeekBar().addChild(marker)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -34,6 +32,8 @@ class ChaptersPlugin extends Plugin {
|
|||
for (const marker of this.markers) {
|
||||
this.getSeekBar().removeChild(marker)
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
getChapter (timecode: number) {
|
||||
|
|
|
@ -9,16 +9,25 @@ export class ProgressBarMarkerComponent extends Component {
|
|||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||
constructor (player: videojs.Player, options?: ProgressBarMarkerComponentOptions & videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
|
||||
const updateMarker = () => {
|
||||
(this.el() as HTMLElement).style.setProperty('left', this.buildLeftStyle())
|
||||
}
|
||||
this.player().on('durationchange', updateMarker)
|
||||
|
||||
this.one('dispose', () => this.player().off('durationchange', updateMarker))
|
||||
}
|
||||
|
||||
createEl () {
|
||||
const left = (this.options_.timecode / this.player().duration()) * 100
|
||||
|
||||
return videojs.dom.createEl('span', {
|
||||
className: 'vjs-marker',
|
||||
style: `left: ${left}%`
|
||||
style: `left: ${this.buildLeftStyle()}`
|
||||
}) as HTMLButtonElement
|
||||
}
|
||||
|
||||
private buildLeftStyle () {
|
||||
return `${(this.options_.timecode / this.player().duration()) * 100}%`
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerComponent('ProgressBarMarkerComponent', ProgressBarMarkerComponent)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -115,7 +115,7 @@
|
|||
"bencode": "^4.0.0",
|
||||
"bittorrent-tracker": "^10.0.12",
|
||||
"bluebird": "^3.5.0",
|
||||
"bullmq": "^3.6.6",
|
||||
"bullmq": "^4.12.3",
|
||||
"bytes": "^3.0.0",
|
||||
"chokidar": "^3.4.2",
|
||||
"commander": "^11.0.0",
|
||||
|
|
|
@ -172,15 +172,21 @@ async function getVideoStream (path: string, existingProbe?: FfprobeData) {
|
|||
// Chapters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getChaptersFromContainer (path: string, existingProbe?: FfprobeData) {
|
||||
const metadata = existingProbe || await ffprobePromise(path)
|
||||
async function getChaptersFromContainer (options: {
|
||||
path: string
|
||||
maxTitleLength: number
|
||||
ffprobe?: FfprobeData
|
||||
}) {
|
||||
const { path, maxTitleLength, ffprobe } = options
|
||||
|
||||
const metadata = ffprobe || await ffprobePromise(path)
|
||||
|
||||
if (!Array.isArray(metadata?.chapters)) return []
|
||||
|
||||
return metadata.chapters
|
||||
.map(c => ({
|
||||
timecode: c.start_time,
|
||||
title: c['TAG:title']
|
||||
timecode: Math.round(c.start_time),
|
||||
title: (c['TAG:title'] || '').slice(0, maxTitleLength)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { VideoStateType } from '../videos/index.js'
|
|||
import { VideoStudioTaskCut } from '../videos/studio/index.js'
|
||||
import { SendEmailOptions } from './emailer.model.js'
|
||||
|
||||
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused' | 'waiting-children'
|
||||
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused' | 'waiting-children' | 'prioritized'
|
||||
|
||||
export type JobType =
|
||||
| 'activitypub-cleaner'
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('Test videos chapters API validator', function () {
|
|||
// ---------------------------------------------------------------
|
||||
|
||||
before(async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
server = await createSingleServer(1)
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ describe('Test video studio API validator', function () {
|
|||
})
|
||||
|
||||
it('Should fail with a video that is already waiting for edition', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(360000)
|
||||
|
||||
await command.createEditionTasks({
|
||||
videoId: videoUUID,
|
||||
|
@ -257,7 +257,7 @@ describe('Test video studio API validator', function () {
|
|||
})
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(360000)
|
||||
|
||||
await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
|
||||
|
||||
|
@ -291,7 +291,7 @@ describe('Test video studio API validator', function () {
|
|||
})
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(360000)
|
||||
|
||||
await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
|
||||
|
||||
|
@ -337,7 +337,7 @@ describe('Test video studio API validator', function () {
|
|||
})
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(360000)
|
||||
|
||||
await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
|
||||
await waitJobs([ server ])
|
||||
|
@ -347,7 +347,7 @@ describe('Test video studio API validator', function () {
|
|||
})
|
||||
|
||||
it('Should check total quota when creating the task', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(360000)
|
||||
|
||||
const user = await server.users.create({ username: 'user_quota_1' })
|
||||
const token = await server.login.getAccessToken('user_quota_1')
|
||||
|
|
|
@ -66,7 +66,7 @@ describe('Test admin notifications', function () {
|
|||
|
||||
joinPeerTubeServer.setLatestVersion('1.4.2')
|
||||
|
||||
await wait(3000)
|
||||
await wait(4500)
|
||||
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '1.4.2', checkType: 'absence' })
|
||||
})
|
||||
|
||||
|
@ -75,14 +75,14 @@ describe('Test admin notifications', function () {
|
|||
|
||||
joinPeerTubeServer.setLatestVersion('15.4.2')
|
||||
|
||||
await wait(3000)
|
||||
await wait(4500)
|
||||
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.2', checkType: 'presence' })
|
||||
})
|
||||
|
||||
it('Should not send the same notification to admins', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await wait(3000)
|
||||
await wait(4500)
|
||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(1)
|
||||
})
|
||||
|
||||
|
@ -97,7 +97,7 @@ describe('Test admin notifications', function () {
|
|||
|
||||
joinPeerTubeServer.setLatestVersion('15.4.3')
|
||||
|
||||
await wait(3000)
|
||||
await wait(4500)
|
||||
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.3', checkType: 'presence' })
|
||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
|
||||
})
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new comment notification after a comment on another video', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -51,7 +51,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new comment notification if I comment my own video', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -63,7 +63,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new comment notification if the account is muted', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
|
||||
|
||||
|
@ -79,7 +79,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a new comment notification after a local comment on my video', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -91,7 +91,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a new comment notification after a remote comment on my video', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -109,7 +109,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a new comment notification after a local reply on my video', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -122,7 +122,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a new comment notification after a remote reply on my video', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||
await waitJobs(servers)
|
||||
|
@ -148,7 +148,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should convert markdown in comment to html', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'cool video' } })
|
||||
|
||||
|
@ -178,7 +178,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new mention comment notification if I mention the video owner', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -189,7 +189,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new mention comment notification if I mention myself', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -200,7 +200,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new mention notification if the account is muted', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
|
||||
|
||||
|
@ -215,7 +215,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a new mention notification if the remote account mention a local account', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -229,7 +229,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a new mention notification after local comments', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -245,7 +245,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a new mention notification after remote comments', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||
|
||||
|
@ -277,7 +277,7 @@ describe('Test comments notifications', function () {
|
|||
})
|
||||
|
||||
it('Should convert markdown in comment to html', async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||
|
||||
|
|
|
@ -233,7 +233,7 @@ describe('Test user notifications', function () {
|
|||
})
|
||||
|
||||
it('Should not send a notification if the wait transcoding is false', async function () {
|
||||
this.timeout(100_000)
|
||||
this.timeout(240000)
|
||||
|
||||
await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: false })
|
||||
await waitJobs(servers)
|
||||
|
@ -245,7 +245,7 @@ describe('Test user notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
|
||||
this.timeout(100_000)
|
||||
this.timeout(240000)
|
||||
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
|
||||
await waitJobs(servers)
|
||||
|
@ -254,7 +254,7 @@ describe('Test user notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a notification with a transcoded video', async function () {
|
||||
this.timeout(100_000)
|
||||
this.timeout(240000)
|
||||
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
|
||||
await waitJobs(servers)
|
||||
|
@ -263,7 +263,7 @@ describe('Test user notifications', function () {
|
|||
})
|
||||
|
||||
it('Should send a notification when an imported video is transcoded', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(240000)
|
||||
|
||||
const name = 'video import ' + buildUUID()
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ describe('Test stats (excluding redundancy)', function () {
|
|||
})
|
||||
|
||||
it('Should have the correct AP stats', async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(240000)
|
||||
|
||||
await servers[0].config.disableTranscoding()
|
||||
|
||||
|
|
|
@ -489,7 +489,7 @@ describe('Test video transcoding', function () {
|
|||
})
|
||||
|
||||
it('Should downscale to the closest divisor standard framerate', async function () {
|
||||
this.timeout(200_000)
|
||||
this.timeout(360_000)
|
||||
|
||||
let tempFixturePath: string
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ describe('Test video playlists', function () {
|
|||
let commands: PlaylistsCommand[]
|
||||
|
||||
before(async function () {
|
||||
this.timeout(240000)
|
||||
this.timeout(360000)
|
||||
|
||||
servers = await createMultipleServers(3)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import { HttpStatusCode, VideoCreate, VideoPrivacy, VideoState } from '@peertube
|
|||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger.js'
|
||||
import { createReqFiles } from '../../../helpers/express-utils.js'
|
||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||
import { MIMETYPES } from '../../../initializers/constants.js'
|
||||
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants.js'
|
||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||
import { Hooks } from '../../../lib/plugins/hooks.js'
|
||||
import { generateLocalVideoMiniature } from '../../../lib/thumbnail.js'
|
||||
|
@ -145,7 +145,10 @@ async function addVideo (options: {
|
|||
const videoFile = await buildNewFile({ path: videoPhysicalFile.path, mode: 'web-video' })
|
||||
const originalFilename = videoPhysicalFile.originalname
|
||||
|
||||
const containerChapters = await getChaptersFromContainer(videoPhysicalFile.path)
|
||||
const containerChapters = await getChaptersFromContainer({
|
||||
path: videoPhysicalFile.path,
|
||||
maxTitleLength: CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max
|
||||
})
|
||||
logger.debug(`Got ${containerChapters.length} chapters from video "${video.name}" container`, { containerChapters, ...lTags(video.uuid) })
|
||||
|
||||
// Move physical file
|
||||
|
|
|
@ -2,10 +2,10 @@ import { JobState } from '@peertube/peertube-models'
|
|||
import { jobTypes } from '@server/lib/job-queue/job-queue.js'
|
||||
import { exists } from './misc.js'
|
||||
|
||||
const jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed', 'paused', 'waiting-children' ]
|
||||
const jobStates = new Set<JobState>([ 'active', 'completed', 'failed', 'waiting', 'delayed', 'paused', 'waiting-children', 'prioritized' ])
|
||||
|
||||
function isValidJobState (value: JobState) {
|
||||
return exists(value) && jobStates.includes(value)
|
||||
return exists(value) && jobStates.has(value)
|
||||
}
|
||||
|
||||
function isValidJobType (value: any) {
|
||||
|
|
|
@ -41,7 +41,7 @@ import {
|
|||
import { logger } from '../../../helpers/logger.js'
|
||||
import { getSecureTorrentName } from '../../../helpers/utils.js'
|
||||
import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent.js'
|
||||
import { JOB_TTL } from '../../../initializers/constants.js'
|
||||
import { CONSTRAINTS_FIELDS, JOB_TTL } from '../../../initializers/constants.js'
|
||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||
import { VideoFileModel } from '../../../models/video/video-file.js'
|
||||
import { VideoImportModel } from '../../../models/video/video-import.js'
|
||||
|
@ -143,16 +143,20 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
|||
throw new Error('The user video quota is exceeded with this video to import.')
|
||||
}
|
||||
|
||||
const probe = await ffprobePromise(tempVideoPath)
|
||||
const ffprobe = await ffprobePromise(tempVideoPath)
|
||||
|
||||
const { resolution } = await isAudioFile(tempVideoPath, probe)
|
||||
const { resolution } = await isAudioFile(tempVideoPath, ffprobe)
|
||||
? { resolution: VideoResolution.H_NOVIDEO }
|
||||
: await getVideoStreamDimensionsInfo(tempVideoPath, probe)
|
||||
: await getVideoStreamDimensionsInfo(tempVideoPath, ffprobe)
|
||||
|
||||
const fps = await getVideoStreamFPS(tempVideoPath, probe)
|
||||
const duration = await getVideoStreamDuration(tempVideoPath, probe)
|
||||
const fps = await getVideoStreamFPS(tempVideoPath, ffprobe)
|
||||
const duration = await getVideoStreamDuration(tempVideoPath, ffprobe)
|
||||
|
||||
const containerChapters = await getChaptersFromContainer(tempVideoPath, probe)
|
||||
const containerChapters = await getChaptersFromContainer({
|
||||
path: tempVideoPath,
|
||||
maxTitleLength: CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max,
|
||||
ffprobe
|
||||
})
|
||||
|
||||
// Prepare video file object for creation in database
|
||||
const fileExt = getLowercaseExtension(tempVideoPath)
|
||||
|
|
|
@ -257,6 +257,9 @@ class JobQueue {
|
|||
queue.on('error', err => { logger.error('Error in job queue %s.', handlerName, { err }) })
|
||||
|
||||
this.queues[handlerName] = queue
|
||||
|
||||
queue.removeDeprecatedPriorityKey()
|
||||
.catch(err => logger.error('Cannot remove bullmq deprecated priority keys of ' + handlerName, { err }))
|
||||
}
|
||||
|
||||
private buildQueueEvent (handlerName: JobType) {
|
||||
|
@ -455,12 +458,15 @@ class JobQueue {
|
|||
}
|
||||
|
||||
private buildStateFilter (state?: JobState) {
|
||||
if (!state) return jobStates
|
||||
if (!state) return Array.from(jobStates)
|
||||
|
||||
const states = [ state ]
|
||||
|
||||
// Include parent if filtering on waiting
|
||||
if (state === 'waiting') states.push('waiting-children')
|
||||
// Include parent and prioritized if filtering on waiting
|
||||
if (state === 'waiting') {
|
||||
states.push('waiting-children')
|
||||
states.push('prioritized')
|
||||
}
|
||||
|
||||
return states
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@peertube/peertube-core-utils'
|
||||
import { buildVideoEmbedPath, buildVideoWatchPath, pick, wait } from '@peertube/peertube-core-utils'
|
||||
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream } from '@peertube/peertube-ffmpeg'
|
||||
import {
|
||||
ResultList,
|
||||
|
@ -1925,7 +1925,20 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
|||
? getHLSRedundancyDirectory(this)
|
||||
: getHLSDirectory(this)
|
||||
|
||||
await remove(directoryPath)
|
||||
try {
|
||||
await remove(directoryPath)
|
||||
} catch (err) {
|
||||
// If it's a live, ffmpeg may have added another file while fs-extra is removing the directory
|
||||
// So wait a little bit and retry
|
||||
if (err.code === 'ENOTEMPTY') {
|
||||
await wait(1000)
|
||||
await remove(directoryPath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
if (isRedundancy !== true) {
|
||||
const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo
|
||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -3572,17 +3572,18 @@ builtins@^5.0.1:
|
|||
dependencies:
|
||||
semver "^7.0.0"
|
||||
|
||||
bullmq@^3.6.6:
|
||||
version "3.15.8"
|
||||
resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-3.15.8.tgz#e8ec5b46b0b7d7ce57e509280d03745109411e05"
|
||||
integrity sha512-k3uimHGhl5svqD7SEak+iI6c5DxeLOaOXzCufI9Ic0ST3nJr69v71TGR4cXCTXdgCff3tLec5HgoBnfyWjgn5A==
|
||||
bullmq@^4.12.3:
|
||||
version "4.12.3"
|
||||
resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-4.12.3.tgz#0c649b9a5e48227519c526ee9edd96b982eee22d"
|
||||
integrity sha512-4uPp4NQTALFF+eFK7g8VJM+rt0aiduQdzBomgiEO1OK4OE+TdgC6cjGXooKI/asuB8iDhSZ+pSnGYy5Xyr6qRA==
|
||||
dependencies:
|
||||
cron-parser "^4.6.0"
|
||||
glob "^8.0.3"
|
||||
ioredis "^5.3.2"
|
||||
lodash "^4.17.21"
|
||||
msgpackr "^1.6.2"
|
||||
semver "^7.3.7"
|
||||
node-abort-controller "^3.1.1"
|
||||
semver "^7.5.4"
|
||||
tslib "^2.0.0"
|
||||
uuid "^9.0.0"
|
||||
|
||||
|
@ -7458,6 +7459,11 @@ nice-try@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
node-abort-controller@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
|
||||
|
||||
node-addon-api@^3.0.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||
|
@ -8911,7 +8917,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
|
||||
semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
|
|
Loading…
Reference in New Issue
Block a user