aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+about/about-follows/about-follows.component.ts2
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.ts2
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.ts8
-rw-r--r--client/src/app/+about/about-routing.module.ts1
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.ts12
-rw-r--r--client/src/app/+accounts/account-videos/account-videos.component.ts2
-rw-r--r--client/src/app/+accounts/accounts.component.html6
-rw-r--r--client/src/app/+accounts/accounts.component.ts14
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts2
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts4
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-homepage.component.ts2
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts3
-rw-r--r--client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts2
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts4
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.html4
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts4
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts6
-rw-r--r--client/src/app/+admin/plugins/plugin-search/plugin-search.component.html2
-rw-r--r--client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts6
-rw-r--r--client/src/app/+admin/plugins/shared/plugin-api.service.ts4
-rw-r--r--client/src/app/+admin/system/jobs/job.service.ts8
-rw-r--r--client/src/app/+admin/system/logs/logs.service.ts6
-rw-r--r--client/src/app/+admin/users/user-edit/user-create.component.ts6
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.ts2
-rw-r--r--client/src/app/+admin/users/user-edit/user-password.component.ts6
-rw-r--r--client/src/app/+admin/users/user-edit/user-update.component.ts14
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts2
-rw-r--r--client/src/app/+login/login.component.html2
-rw-r--r--client/src/app/+login/login.component.ts2
-rw-r--r--client/src/app/+my-account/my-account-applications/my-account-applications.component.ts1
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts6
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts14
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts3
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.ts2
-rw-r--r--client/src/app/+my-account/my-account.component.ts2
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts4
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts8
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts8
-rw-r--r--client/src/app/+my-library/my-history/my-history.component.ts7
-rw-r--r--client/src/app/+my-library/my-library.component.ts2
-rw-r--r--client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts2
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts6
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts4
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts10
-rw-r--r--client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts6
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.ts4
-rw-r--r--client/src/app/+remote-interaction/remote-interaction.component.ts2
-rw-r--r--client/src/app/+reset-password/reset-password.component.ts2
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.ts6
-rw-r--r--client/src/app/+signup/+register/register.component.ts11
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts2
-rw-r--r--client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts2
-rw-r--r--client/src/app/+video-channels/video-channels.component.html2
-rw-r--r--client/src/app/+video-channels/video-channels.component.ts8
-rw-r--r--client/src/app/+videos/+video-edit/shared/i18n-primeng-calendar.service.ts2
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts8
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit-utils.ts2
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.ts10
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/drag-drop.directive.ts8
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts4
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-send.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts5
-rw-r--r--client/src/app/+videos/+video-edit/video-update.component.ts5
-rw-r--r--client/src/app/+videos/+video-edit/video-update.resolver.ts6
-rw-r--r--client/src/app/+videos/+video-watch/player-styles.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts6
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts8
-rw-r--r--client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts10
-rw-r--r--client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/shared/timestamp-route-transformer.directive.ts8
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts39
-rw-r--r--client/src/app/+videos/video-list/overview/overview.service.ts2
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending-header.component.ts2
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending.component.ts6
-rw-r--r--client/src/app/+videos/video-list/video-local.component.ts2
-rw-r--r--client/src/app/app.component.ts4
-rw-r--r--client/src/app/core/auth/auth.service.ts6
-rw-r--r--client/src/app/core/hotkeys/hotkeys.component.ts6
-rw-r--r--client/src/app/core/menu/menu.service.ts4
-rw-r--r--client/src/app/core/plugins/hooks.service.ts10
-rw-r--r--client/src/app/core/plugins/plugin.service.ts10
-rw-r--r--client/src/app/core/renderer/markdown.service.ts14
-rw-r--r--client/src/app/core/rest/rest-extractor.service.ts26
-rw-r--r--client/src/app/core/routing/custom-reuse-strategy.ts4
-rw-r--r--client/src/app/core/routing/menu-guard.service.ts18
-rw-r--r--client/src/app/core/routing/meta-guard.service.ts2
-rw-r--r--client/src/app/core/routing/preload-selected-modules-list.ts2
-rw-r--r--client/src/app/core/scoped-tokens/scoped-tokens.service.ts7
-rw-r--r--client/src/app/core/server/server.service.ts4
-rw-r--r--client/src/app/core/theme/theme.service.ts4
-rw-r--r--client/src/app/core/users/user.service.ts4
-rw-r--r--client/src/app/header/search-typeahead.component.ts2
-rw-r--r--client/src/app/helpers/locales/oc.ts114
-rw-r--r--client/src/app/helpers/utils.ts10
-rw-r--r--client/src/app/menu/language-chooser.component.ts2
-rw-r--r--client/src/app/menu/menu.component.ts2
-rw-r--r--client/src/app/modal/custom-modal.component.ts8
-rw-r--r--client/src/app/shared/form-validators/abuse-validators.ts24
-rw-r--r--client/src/app/shared/form-validators/custom-config-validators.ts88
-rw-r--r--client/src/app/shared/form-validators/form-validator.model.ts2
-rw-r--r--client/src/app/shared/form-validators/host-validators.ts20
-rw-r--r--client/src/app/shared/form-validators/instance-validators.ts24
-rw-r--r--client/src/app/shared/form-validators/login-validators.ts4
-rw-r--r--client/src/app/shared/form-validators/reset-password-validators.ts2
-rw-r--r--client/src/app/shared/form-validators/user-validators.ts64
-rw-r--r--client/src/app/shared/form-validators/video-block-validators.ts4
-rw-r--r--client/src/app/shared/form-validators/video-captions-validators.ts4
-rw-r--r--client/src/app/shared/form-validators/video-channel-validators.ts22
-rw-r--r--client/src/app/shared/form-validators/video-comment-validators.ts6
-rw-r--r--client/src/app/shared/form-validators/video-ownership-change-validators.ts8
-rw-r--r--client/src/app/shared/form-validators/video-playlist-validators.ts16
-rw-r--r--client/src/app/shared/form-validators/video-validators.ts32
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts10
-rw-r--r--client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts2
-rw-r--r--client/src/app/shared/shared-abuse-list/processed-abuse.model.ts2
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts2
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts4
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.ts36
-rw-r--r--client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts8
-rw-r--r--client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts4
-rw-r--r--client/src/app/shared/shared-forms/form-reactive.ts6
-rw-r--r--client/src/app/shared/shared-forms/form-validator.service.ts8
-rw-r--r--client/src/app/shared/shared-forms/reactive-file.component.ts2
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.ts106
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.ts2
-rw-r--r--client/src/app/shared/shared-instance/instance-follow.service.ts16
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.ts4
-rw-r--r--client/src/app/shared/shared-instance/instance.service.ts2
-rw-r--r--client/src/app/shared/shared-instance/shared-instance.module.ts1
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts2
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts2
-rw-r--r--client/src/app/shared/shared-main/angular/autofocus.directive.ts2
-rw-r--r--client/src/app/shared/shared-main/angular/link.component.ts2
-rw-r--r--client/src/app/shared/shared-main/angular/peertube-template.directive.ts1
-rw-r--r--client/src/app/shared/shared-main/feeds/syndication.model.ts4
-rw-r--r--client/src/app/shared/shared-main/misc/help.component.ts12
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.ts4
-rw-r--r--client/src/app/shared/shared-main/misc/simple-search-input.component.ts2
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts2
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.service.ts6
-rw-r--r--client/src/app/shared/shared-main/users/user-quota.component.ts2
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.model.ts5
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.service.ts12
-rw-r--r--client/src/app/shared/shared-main/video/redundancy.service.ts4
-rw-r--r--client/src/app/shared/shared-main/video/video-edit.model.ts12
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts2
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts18
-rw-r--r--client/src/app/shared/shared-moderation/abuse.service.ts13
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.ts7
-rw-r--r--client/src/app/shared/shared-moderation/blocklist.service.ts8
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.ts10
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.ts4
-rw-r--r--client/src/app/shared/shared-support-modal/support-modal.component.ts4
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.ts2
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts16
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.ts4
-rw-r--r--client/src/app/shared/shared-user-subscription/user-subscription.service.ts20
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.model.ts8
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.service.ts14
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.ts16
-rw-r--r--client/src/app/shared/shared-video-miniature/video-download.component.ts26
-rw-r--r--client/src/app/shared/shared-video-miniature/video-list-header.component.ts2
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.model.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.service.ts10
-rw-r--r--client/src/assets/player/p2p-media-loader/hls-plugin.ts94
-rw-r--r--client/src/assets/player/p2p-media-loader/segment-validator.ts5
-rw-r--r--client/src/assets/player/peertube-player-manager.ts42
-rw-r--r--client/src/assets/player/peertube-plugin.ts2
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts9
-rw-r--r--client/src/assets/player/stats/stats-card.ts9
-rw-r--r--client/src/assets/player/translations-manager.ts6
-rw-r--r--client/src/assets/player/upnext/end-card.ts14
-rw-r--r--client/src/assets/player/utils.ts2
-rw-r--r--client/src/assets/player/videojs-components/p2p-info-button.ts8
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-item.ts4
-rw-r--r--client/src/assets/player/videojs-components/settings-dialog.ts4
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-item.ts21
-rw-r--r--client/src/assets/player/videojs-components/settings-panel-child.ts4
-rw-r--r--client/src/assets/player/videojs-components/settings-panel.ts4
-rw-r--r--client/src/assets/player/webtorrent/peertube-chunk-store.ts8
-rw-r--r--client/src/assets/player/webtorrent/video-renderer.ts8
-rw-r--r--client/src/assets/player/webtorrent/webtorrent-plugin.ts24
-rw-r--r--client/src/polyfills.ts2
-rw-r--r--client/src/root-helpers/bytes.ts2
-rw-r--r--client/src/root-helpers/peertube-web-storage.ts4
-rw-r--r--client/src/root-helpers/plugins-manager.ts6
-rw-r--r--client/src/standalone/player/definitions.ts2
-rw-r--r--client/src/standalone/player/events.ts14
-rw-r--r--client/src/standalone/player/player.ts31
-rw-r--r--client/src/standalone/videos/embed-api.ts19
-rw-r--r--client/src/standalone/videos/embed.ts47
-rw-r--r--client/src/standalone/videos/test-embed.ts20
-rw-r--r--client/src/types/register-client-option.model.ts12
-rw-r--r--client/src/typings.d.ts1
206 files changed, 994 insertions, 917 deletions
diff --git a/client/src/app/+about/about-follows/about-follows.component.ts b/client/src/app/+about/about-follows/about-follows.component.ts
index 1dcb6396c..a35272681 100644
--- a/client/src/app/+about/about-follows/about-follows.component.ts
+++ b/client/src/app/+about/about-follows/about-follows.component.ts
@@ -90,7 +90,7 @@ export class AboutFollowsComponent implements OnInit {
90 private loadMoreFollowers (reset = false) { 90 private loadMoreFollowers (reset = false) {
91 const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination) 91 const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination)
92 92
93 this.followService.getFollowers({ pagination: pagination, sort: this.sort, state: 'accepted' }) 93 this.followService.getFollowers({ pagination, sort: this.sort, state: 'accepted' })
94 .subscribe({ 94 .subscribe({
95 next: resultList => { 95 next: resultList => {
96 if (reset) this.followers = [] 96 if (reset) this.followers = []
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts
index f86df5b67..0826bbc5a 100644
--- a/client/src/app/+about/about-instance/about-instance.component.ts
+++ b/client/src/app/+about/about-instance/about-instance.component.ts
@@ -95,6 +95,6 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked {
95 onClickCopyLink (anchor: HTMLAnchorElement) { 95 onClickCopyLink (anchor: HTMLAnchorElement) {
96 const link = anchor.href 96 const link = anchor.href
97 copyToClipboard(link) 97 copyToClipboard(link)
98 this.notifier.success(link, $localize `Link copied`) 98 this.notifier.success(link, $localize`Link copied`)
99 } 99 }
100} 100}
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
index cbc759881..fab9cfc4b 100644
--- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
@@ -77,10 +77,10 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit {
77 } 77 }
78 78
79 sendForm () { 79 sendForm () {
80 const fromName = this.form.value[ 'fromName' ] 80 const fromName = this.form.value['fromName']
81 const fromEmail = this.form.value[ 'fromEmail' ] 81 const fromEmail = this.form.value['fromEmail']
82 const subject = this.form.value[ 'subject' ] 82 const subject = this.form.value['subject']
83 const body = this.form.value[ 'body' ] 83 const body = this.form.value['body']
84 84
85 this.instanceService.contactAdministrator(fromEmail, fromName, subject, body) 85 this.instanceService.contactAdministrator(fromEmail, fromName, subject, body)
86 .subscribe({ 86 .subscribe({
diff --git a/client/src/app/+about/about-routing.module.ts b/client/src/app/+about/about-routing.module.ts
index 3974231e3..cf5570d31 100644
--- a/client/src/app/+about/about-routing.module.ts
+++ b/client/src/app/+about/about-routing.module.ts
@@ -3,7 +3,6 @@ import { RouterModule, Routes } from '@angular/router'
3import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component' 3import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component'
4import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' 4import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
5import { AboutInstanceResolver } from '@app/+about/about-instance/about-instance.resolver' 5import { AboutInstanceResolver } from '@app/+about/about-instance/about-instance.resolver'
6import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
7import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' 6import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
8import { AboutComponent } from './about.component' 7import { AboutComponent } from './about.component'
9 8
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
index e146a5cd2..f6df38857 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
@@ -1,10 +1,10 @@
1import { from, Subject, Subscription } from 'rxjs' 1import { from, Subject, Subscription } from 'rxjs'
2import { concatMap, map, switchMap, tap } from 'rxjs/operators' 2import { concatMap, map, switchMap, tap } from 'rxjs/operators'
3import { Component, OnDestroy, OnInit } from '@angular/core' 3import { Component, OnDestroy, OnInit } from '@angular/core'
4import { ComponentPagination, hasMoreItems, MarkdownService, ScreenService, User, UserService } from '@app/core' 4import { ComponentPagination, hasMoreItems, MarkdownService, User, UserService } from '@app/core'
5import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' 5import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
6import { NSFWPolicyType, VideoSortField } from '@shared/models'
7import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' 6import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
7import { NSFWPolicyType, VideoSortField } from '@shared/models'
8 8
9@Component({ 9@Component({
10 selector: 'my-account-video-channels', 10 selector: 'my-account-video-channels',
@@ -87,7 +87,9 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
87 87
88 this.videoChannelService.listAccountVideoChannels(options) 88 this.videoChannelService.listAccountVideoChannels(options)
89 .pipe( 89 .pipe(
90 tap(res => this.channelPagination.totalItems = res.total), 90 tap(res => {
91 this.channelPagination.totalItems = res.total
92 }),
91 switchMap(res => from(res.data)), 93 switchMap(res => from(res.data)),
92 concatMap(videoChannel => { 94 concatMap(videoChannel => {
93 const options = { 95 const options = {
@@ -113,14 +115,14 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
113 } 115 }
114 116
115 getVideosOf (videoChannel: VideoChannel) { 117 getVideosOf (videoChannel: VideoChannel) {
116 const obj = this.videos[ videoChannel.id ] 118 const obj = this.videos[videoChannel.id]
117 if (!obj) return [] 119 if (!obj) return []
118 120
119 return obj.videos 121 return obj.videos
120 } 122 }
121 123
122 getTotalVideosOf (videoChannel: VideoChannel) { 124 getTotalVideosOf (videoChannel: VideoChannel) {
123 const obj = this.videos[ videoChannel.id ] 125 const obj = this.videos[videoChannel.id]
124 if (!obj) return undefined 126 if (!obj) return undefined
125 127
126 return obj.total 128 return obj.total
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts
index 75af45e90..4ab6d2147 100644
--- a/client/src/app/+accounts/account-videos/account-videos.component.ts
+++ b/client/src/app/+accounts/account-videos/account-videos.component.ts
@@ -1,5 +1,5 @@
1import { forkJoin, Subscription } from 'rxjs' 1import { forkJoin, Subscription } from 'rxjs'
2import { first, tap } from 'rxjs/operators' 2import { first } from 'rxjs/operators'
3import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' 3import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' 5import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core'
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html
index cb3e79a18..0906992fe 100644
--- a/client/src/app/+accounts/accounts.component.html
+++ b/client/src/app/+accounts/accounts.component.html
@@ -71,14 +71,14 @@
71 <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> 71 <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
72 </ng-template> 72 </ng-template>
73 73
74 <list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></list-overflow> 74 <my-list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow>
75 75
76 <simple-search-input 76 <my-simple-search-input
77 [alwaysShow]="!isInSmallView()" (searchChanged)="searchChanged($event)" 77 [alwaysShow]="!isInSmallView()" (searchChanged)="searchChanged($event)"
78 (inputDisplayChanged)="onSearchInputDisplayChanged($event)" name="search-videos" 78 (inputDisplayChanged)="onSearchInputDisplayChanged($event)" name="search-videos"
79 i18n-iconTitle icon-title="Search account videos" 79 i18n-iconTitle icon-title="Search account videos"
80 i18n-placeholder placeholder="Search account videos" 80 i18n-placeholder placeholder="Search account videos"
81 ></simple-search-input> 81 ></my-simple-search-input>
82 </div> 82 </div>
83 83
84 <router-outlet (activate)="onOutletLoaded($event)"></router-outlet> 84 <router-outlet (activate)="onOutletLoaded($event)"></router-outlet>
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts
index 25eb13588..733cff8d5 100644
--- a/client/src/app/+accounts/accounts.component.ts
+++ b/client/src/app/+accounts/accounts.component.ts
@@ -61,7 +61,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
61 ngOnInit () { 61 ngOnInit () {
62 this.routeSub = this.route.params 62 this.routeSub = this.route.params
63 .pipe( 63 .pipe(
64 map(params => params[ 'accountId' ]), 64 map(params => params['accountId']),
65 distinctUntilChanged(), 65 distinctUntilChanged(),
66 switchMap(accountId => this.accountService.getAccount(accountId)), 66 switchMap(accountId => this.accountService.getAccount(accountId)),
67 tap(account => this.onAccount(account)), 67 tap(account => this.onAccount(account)),
@@ -72,7 +72,9 @@ export class AccountsComponent implements OnInit, OnDestroy {
72 ])) 72 ]))
73 ) 73 )
74 .subscribe({ 74 .subscribe({
75 next: videoChannels => this.videoChannels = videoChannels.data, 75 next: videoChannels => {
76 this.videoChannels = videoChannels.data
77 },
76 78
77 error: err => this.notifier.error(err.message) 79 error: err => this.notifier.error(err.message)
78 }) 80 })
@@ -176,7 +178,9 @@ export class AccountsComponent implements OnInit, OnDestroy {
176 if (user.hasRight(UserRight.MANAGE_USERS)) { 178 if (user.hasRight(UserRight.MANAGE_USERS)) {
177 this.userService.getUser(account.userId) 179 this.userService.getUser(account.userId)
178 .subscribe({ 180 .subscribe({
179 next: accountUser => this.accountUser = accountUser, 181 next: accountUser => {
182 this.accountUser = accountUser
183 },
180 184
181 error: err => this.notifier.error(err.message) 185 error: err => this.notifier.error(err.message)
182 }) 186 })
@@ -209,6 +213,8 @@ export class AccountsComponent implements OnInit, OnDestroy {
209 itemsPerPage: 0 213 itemsPerPage: 0
210 }, 214 },
211 sort: '-publishedAt' 215 sort: '-publishedAt'
212 }).subscribe(res => this.accountVideosCount = res.total) 216 }).subscribe(res => {
217 this.accountVideosCount = res.total
218 })
213 } 219 }
214} 220}
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts
index 671e734ac..7a8258820 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts
@@ -97,7 +97,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
97 .pipe(pairwise()) 97 .pipe(pairwise())
98 .subscribe(([ oldValue, newValue ]) => { 98 .subscribe(([ oldValue, newValue ]) => {
99 if (oldValue !== true && newValue === true) { 99 if (oldValue !== true && newValue === true) {
100 // tslint:disable:max-line-length 100 /* eslint-disable max-len */
101 this.signupAlertMessage = $localize`You enabled signup: we automatically enabled the "Block new videos automatically" checkbox of the "Videos" section just below.` 101 this.signupAlertMessage = $localize`You enabled signup: we automatically enabled the "Block new videos automatically" checkbox of the "Videos" section just below.`
102 102
103 this.form.patchValue({ 103 this.form.patchValue({
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index 538fa6f14..be1a99289 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -277,7 +277,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
277 277
278 // Reload general configuration 278 // Reload general configuration
279 this.serverService.resetConfig() 279 this.serverService.resetConfig()
280 .subscribe(config => this.serverConfig = config) 280 .subscribe(config => {
281 this.serverConfig = config
282 })
281 283
282 this.updateForm() 284 this.updateForm()
283 285
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.ts
index 1923ede39..dc834da14 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.ts
@@ -1,4 +1,4 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { FormGroup } from '@angular/forms' 2import { FormGroup } from '@angular/forms'
3import { CustomMarkupService } from '@app/shared/shared-custom-markup' 3import { CustomMarkupService } from '@app/shared/shared-custom-markup'
4 4
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index cf0225098..383f66ffd 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -2,7 +2,6 @@ import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' 3import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
4import { InstanceFollowService } from '@app/shared/shared-instance' 4import { InstanceFollowService } from '@app/shared/shared-instance'
5import { BatchDomainsModalComponent } from '@app/shared/shared-moderation'
6import { ActorFollow } from '@shared/models' 5import { ActorFollow } from '@shared/models'
7import { FollowModalComponent } from './follow-modal.component' 6import { FollowModalComponent } from './follow-modal.component'
8 7
@@ -22,7 +21,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
22 private notifier: Notifier, 21 private notifier: Notifier,
23 private confirmService: ConfirmService, 22 private confirmService: ConfirmService,
24 private followService: InstanceFollowService 23 private followService: InstanceFollowService
25 ) { 24 ) {
26 super() 25 super()
27 } 26 }
28 27
diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
index 47c402510..95f8473db 100644
--- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
+++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
@@ -14,7 +14,7 @@ export class RedundancyCheckboxComponent {
14 constructor ( 14 constructor (
15 private notifier: Notifier, 15 private notifier: Notifier,
16 private redundancyService: RedundancyService 16 private redundancyService: RedundancyService
17 ) { } 17 ) { }
18 18
19 updateRedundancyState () { 19 updateRedundancyState () {
20 this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed) 20 this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed)
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
index 4c691269a..7ffed83e8 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
@@ -21,7 +21,7 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
21 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 21 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
22 displayType: VideoRedundanciesTarget = 'my-videos' 22 displayType: VideoRedundanciesTarget = 'my-videos'
23 23
24 redundanciesGraphsData: { stats: VideosRedundancyStats, graphData: object, options: object }[] = [] 24 redundanciesGraphsData: { stats: VideosRedundancyStats, graphData: any, options: any }[] = []
25 25
26 noRedundancies = false 26 noRedundancies = false
27 27
@@ -32,7 +32,7 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
32 private confirmService: ConfirmService, 32 private confirmService: ConfirmService,
33 private redundancyService: RedundancyService, 33 private redundancyService: RedundancyService,
34 private serverService: ServerService 34 private serverService: ServerService
35 ) { 35 ) {
36 super() 36 super()
37 37
38 this.bytesPipe = new BytesPipe() 38 this.bytesPipe = new BytesPipe()
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
index d89c8f244..3a8df1f07 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
@@ -21,7 +21,7 @@
21 21
22 <ng-template pTemplate="header"> 22 <ng-template pTemplate="header">
23 <tr> 23 <tr>
24 <th style="width: 40px"></th> 24 <th style="width: 40px;"></th>
25 <th style="width: 150px;"></th> 25 <th style="width: 150px;"></th>
26 <th i18n pSortableColumn="name">Video <p-sortIcon field="name"></p-sortIcon></th> 26 <th i18n pSortableColumn="name">Video <p-sortIcon field="name"></p-sortIcon></th>
27 <th style="width: 100px;" i18n>Sensitive</th> 27 <th style="width: 100px;" i18n>Sensitive</th>
@@ -54,7 +54,7 @@
54 </div> 54 </div>
55 <div class="table-video-text"> 55 <div class="table-video-text">
56 <div> 56 <div>
57 <my-global-icon i18n-title title="The video was blocked due to automatic blocking of new videos" *ngIf="videoBlock.type == 2" iconName="robot"></my-global-icon> 57 <my-global-icon i18n-title title="The video was blocked due to automatic blocking of new videos" *ngIf="videoBlock.type === 2" iconName="robot"></my-global-icon>
58 {{ videoBlock.video.name }} 58 {{ videoBlock.video.name }}
59 </div> 59 </div>
60 <div class="text-muted">by {{ videoBlock.video.channel?.displayName }} on {{ videoBlock.video.channel?.host }} </div> 60 <div class="text-muted">by {{ videoBlock.video.channel?.displayName }} on {{ videoBlock.video.channel?.host }} </div>
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index adef16975..3edcb1c63 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -28,11 +28,11 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
28 28
29 inputFilters: AdvancedInputFilter[] = [ 29 inputFilters: AdvancedInputFilter[] = [
30 { 30 {
31 queryParams: { 'search': 'type:auto' }, 31 queryParams: { search: 'type:auto' },
32 label: $localize`Automatic blocks` 32 label: $localize`Automatic blocks`
33 }, 33 },
34 { 34 {
35 queryParams: { 'search': 'type:manual' }, 35 queryParams: { search: 'type:manual' },
36 label: $localize`Manual blocks` 36 label: $localize`Manual blocks`
37 } 37 }
38 ] 38 ]
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
index 4904bcc25..512ceffd9 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
@@ -44,11 +44,11 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
44 44
45 inputFilters: AdvancedInputFilter[] = [ 45 inputFilters: AdvancedInputFilter[] = [
46 { 46 {
47 queryParams: { 'search': 'local:true' }, 47 queryParams: { search: 'local:true' },
48 label: $localize`Local comments` 48 label: $localize`Local comments`
49 }, 49 },
50 { 50 {
51 queryParams: { 'search': 'local:false' }, 51 queryParams: { search: 'local:false' },
52 label: $localize`Remote comments` 52 label: $localize`Remote comments`
53 } 53 }
54 ] 54 ]
@@ -66,7 +66,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
66 private videoCommentService: VideoCommentService, 66 private videoCommentService: VideoCommentService,
67 private markdownRenderer: MarkdownService, 67 private markdownRenderer: MarkdownService,
68 private bulkService: BulkService 68 private bulkService: BulkService
69 ) { 69 ) {
70 super() 70 super()
71 71
72 this.videoCommentActions = [ 72 this.videoCommentActions = [
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
index 8d8f12c48..a41c7d700 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
@@ -3,7 +3,7 @@
3</div> 3</div>
4 4
5<div class="search-bar"> 5<div class="search-bar">
6 <input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..." autofocus /> 6 <input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..." myAutofocus />
7</div> 7</div>
8 8
9<div class="alert alert-info" i18n *ngIf="pluginInstalled"> 9<div class="alert alert-info" i18n *ngIf="pluginInstalled">
diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts
index 10fb52911..402bef1ea 100644
--- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts
@@ -103,8 +103,8 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
103 const settingsValues: any = {} 103 const settingsValues: any = {}
104 104
105 for (const setting of this.registeredSettings) { 105 for (const setting of this.registeredSettings) {
106 buildOptions[ setting.name ] = null 106 buildOptions[setting.name] = null
107 settingsValues[ setting.name ] = this.getSetting(setting.name) 107 settingsValues[setting.name] = this.getSetting(setting.name)
108 } 108 }
109 109
110 this.buildForm(buildOptions) 110 this.buildForm(buildOptions)
@@ -117,7 +117,7 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
117 private getSetting (name: string) { 117 private getSetting (name: string) {
118 const settings = this.plugin.settings 118 const settings = this.plugin.settings
119 119
120 if (settings && settings[name] !== undefined) return settings[name] 120 if (settings?.[name] !== undefined) return settings[name]
121 121
122 const registered = this.registeredSettings.find(r => r.name === name) 122 const registered = this.registeredSettings.find(r => r.name === name)
123 123
diff --git a/client/src/app/+admin/plugins/shared/plugin-api.service.ts b/client/src/app/+admin/plugins/shared/plugin-api.service.ts
index d91fccc09..c4f480cae 100644
--- a/client/src/app/+admin/plugins/shared/plugin-api.service.ts
+++ b/client/src/app/+admin/plugins/shared/plugin-api.service.ts
@@ -1,10 +1,8 @@
1import { Observable } from 'rxjs' 1import { catchError } from 'rxjs/operators'
2import { catchError, map, switchMap } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 2import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
5import { ComponentPagination, RestExtractor, RestService } from '@app/core' 4import { ComponentPagination, RestExtractor, RestService } from '@app/core'
6import { PluginService } from '@app/core/plugins/plugin.service' 5import { PluginService } from '@app/core/plugins/plugin.service'
7import { peertubeTranslate } from '@shared/core-utils/i18n'
8import { 6import {
9 InstallOrUpdatePlugin, 7 InstallOrUpdatePlugin,
10 ManagePlugin, 8 ManagePlugin,
diff --git a/client/src/app/+admin/system/jobs/job.service.ts b/client/src/app/+admin/system/jobs/job.service.ts
index 4b4a8914f..6c4a07469 100644
--- a/client/src/app/+admin/system/jobs/job.service.ts
+++ b/client/src/app/+admin/system/jobs/job.service.ts
@@ -20,9 +20,9 @@ export class JobService {
20 ) {} 20 ) {}
21 21
22 getJobs (options: { 22 getJobs (options: {
23 jobState?: JobStateClient, 23 jobState?: JobStateClient
24 jobType: JobTypeClient, 24 jobType: JobTypeClient
25 pagination: RestPagination, 25 pagination: RestPagination
26 sort: SortMeta 26 sort: SortMeta
27 }): Observable<ResultList<Job>> { 27 }): Observable<ResultList<Job>> {
28 const { jobState, jobType, pagination, sort } = options 28 const { jobState, jobType, pagination, sort } = options
@@ -32,7 +32,7 @@ export class JobService {
32 32
33 if (jobType !== 'all') params = params.append('jobType', jobType) 33 if (jobType !== 'all') params = params.append('jobType', jobType)
34 34
35 return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + `/${jobState ? jobState : ''}`, { params }) 35 return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + `/${jobState || ''}`, { params })
36 .pipe( 36 .pipe(
37 map(res => { 37 map(res => {
38 return this.restExtractor.convertResultListDateToHuman(res, [ 'createdAt', 'processedOn', 'finishedOn' ]) 38 return this.restExtractor.convertResultListDateToHuman(res, [ 'createdAt', 'processedOn', 'finishedOn' ])
diff --git a/client/src/app/+admin/system/logs/logs.service.ts b/client/src/app/+admin/system/logs/logs.service.ts
index 69439a179..0c222cad2 100644
--- a/client/src/app/+admin/system/logs/logs.service.ts
+++ b/client/src/app/+admin/system/logs/logs.service.ts
@@ -18,9 +18,9 @@ export class LogsService {
18 ) {} 18 ) {}
19 19
20 getLogs (options: { 20 getLogs (options: {
21 isAuditLog: boolean, 21 isAuditLog: boolean
22 startDate: string, 22 startDate: string
23 level?: LogLevel, 23 level?: LogLevel
24 endDate?: string 24 endDate?: string
25 }): Observable<any[]> { 25 }): Observable<any[]> {
26 const { isAuditLog, startDate } = options 26 const { isAuditLog, startDate } = options
diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts
index 8403db91a..b61b22fd0 100644
--- a/client/src/app/+admin/users/user-edit/user-create.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-create.component.ts
@@ -33,7 +33,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
33 private router: Router, 33 private router: Router,
34 private notifier: Notifier, 34 private notifier: Notifier,
35 private userService: UserService 35 private userService: UserService
36 ) { 36 ) {
37 super() 37 super()
38 38
39 this.buildQuotaOptions() 39 this.buildQuotaOptions()
@@ -78,7 +78,9 @@ export class UserCreateComponent extends UserEdit implements OnInit {
78 this.router.navigate([ '/admin/users/list' ]) 78 this.router.navigate([ '/admin/users/list' ])
79 }, 79 },
80 80
81 error: err => this.error = err.message 81 error: err => {
82 this.error = err.message
83 }
82 }) 84 })
83 } 85 }
84 86
diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts
index ae1f79ba0..af5e674a7 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.ts
+++ b/client/src/app/+admin/users/user-edit/user-edit.ts
@@ -7,7 +7,7 @@ import { HTMLServerConfig, UserAdminFlag, UserRole, VideoResolution } from '@sha
7import { SelectOptionsItem } from '../../../../types/select-options-item.model' 7import { SelectOptionsItem } from '../../../../types/select-options-item.model'
8 8
9@Directive() 9@Directive()
10// tslint:disable-next-line: directive-class-suffix 10// eslint-disable-next-line @angular-eslint/directive-class-suffix
11export abstract class UserEdit extends FormReactive implements OnInit { 11export abstract class UserEdit extends FormReactive implements OnInit {
12 videoQuotaOptions: SelectOptionsItem[] = [] 12 videoQuotaOptions: SelectOptionsItem[] = []
13 videoQuotaDailyOptions: SelectOptionsItem[] = [] 13 videoQuotaDailyOptions: SelectOptionsItem[] = []
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.ts b/client/src/app/+admin/users/user-edit/user-password.component.ts
index 7c42b9241..42bf20de1 100644
--- a/client/src/app/+admin/users/user-edit/user-password.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-password.component.ts
@@ -20,7 +20,7 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
20 protected formValidatorService: FormValidatorService, 20 protected formValidatorService: FormValidatorService,
21 private notifier: Notifier, 21 private notifier: Notifier,
22 private userService: UserService 22 private userService: UserService
23 ) { 23 ) {
24 super() 24 super()
25 } 25 }
26 26
@@ -39,7 +39,9 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
39 .subscribe({ 39 .subscribe({
40 next: () => this.notifier.success($localize`Password changed for user ${this.username}.`), 40 next: () => this.notifier.success($localize`Password changed for user ${this.username}.`),
41 41
42 error: err => this.error = err.message 42 error: err => {
43 this.error = err.message
44 }
43 }) 45 })
44 } 46 }
45 47
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts
index 2128ba4fd..42599a17e 100644
--- a/client/src/app/+admin/users/user-edit/user-update.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-update.component.ts
@@ -33,7 +33,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
33 private router: Router, 33 private router: Router,
34 private notifier: Notifier, 34 private notifier: Notifier,
35 private userService: UserService 35 private userService: UserService
36 ) { 36 ) {
37 super() 37 super()
38 38
39 this.buildQuotaOptions() 39 this.buildQuotaOptions()
@@ -63,7 +63,9 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
63 .subscribe({ 63 .subscribe({
64 next: user => this.onUserFetched(user), 64 next: user => this.onUserFetched(user),
65 65
66 error: err => this.error = err.message 66 error: err => {
67 this.error = err.message
68 }
67 }) 69 })
68 }) 70 })
69 } 71 }
@@ -91,7 +93,9 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
91 this.router.navigate([ '/admin/users/list' ]) 93 this.router.navigate([ '/admin/users/list' ])
92 }, 94 },
93 95
94 error: err => this.error = err.message 96 error: err => {
97 this.error = err.message
98 }
95 }) 99 })
96 } 100 }
97 101
@@ -114,7 +118,9 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
114 this.notifier.success($localize`An email asking for password reset has been sent to ${this.user.username}.`) 118 this.notifier.success($localize`An email asking for password reset has been sent to ${this.user.username}.`)
115 }, 119 },
116 120
117 error: err => this.error = err.message 121 error: err => {
122 this.error = err.message
123 }
118 }) 124 })
119 } 125 }
120 126
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index d4406549a..39caf5ed5 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -36,7 +36,7 @@ export class UserListComponent extends RestTable implements OnInit {
36 36
37 inputFilters: AdvancedInputFilter[] = [ 37 inputFilters: AdvancedInputFilter[] = [
38 { 38 {
39 queryParams: { 'search': 'banned:true' }, 39 queryParams: { search: 'banned:true' },
40 label: $localize`Banned users` 40 label: $localize`Banned users`
41 } 41 }
42 ] 42 ]
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html
index 27793ff0c..0be67368e 100644
--- a/client/src/app/+login/login.component.html
+++ b/client/src/app/+login/login.component.html
@@ -21,7 +21,7 @@
21 <label i18n for="username">User</label> 21 <label i18n for="username">User</label>
22 <input 22 <input
23 type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" 23 type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
24 formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" autofocus 24 formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" myAutofocus
25 > 25 >
26 </div> 26 </div>
27 27
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts
index 16876afd6..1fa4bd3b5 100644
--- a/client/src/app/+login/login.component.ts
+++ b/client/src/app/+login/login.component.ts
@@ -46,7 +46,7 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
46 private redirectService: RedirectService, 46 private redirectService: RedirectService,
47 private notifier: Notifier, 47 private notifier: Notifier,
48 private hooks: HooksService 48 private hooks: HooksService
49 ) { 49 ) {
50 super() 50 super()
51 } 51 }
52 52
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
index 6873c7d40..e88cdd228 100644
--- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
+++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
@@ -36,6 +36,7 @@ export class MyAccountApplicationsComponent implements OnInit {
36 36
37 async renewToken () { 37 async renewToken () {
38 const res = await this.confirmService.confirm( 38 const res = await this.confirmService.confirm(
39 // eslint-disable-next-line max-len
39 $localize`Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?`, 40 $localize`Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?`,
40 $localize`Renew token` 41 $localize`Renew token`
41 ) 42 )
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
index 08bc5b425..9b87daa40 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
@@ -28,7 +28,7 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni
28 ngOnInit () { 28 ngOnInit () {
29 this.buildForm({ 29 this.buildForm({
30 'new-email': USER_EMAIL_VALIDATOR, 30 'new-email': USER_EMAIL_VALIDATOR,
31 'password': USER_PASSWORD_VALIDATOR 31 password: USER_PASSWORD_VALIDATOR
32 }) 32 })
33 33
34 this.user = this.authService.getUser() 34 this.user = this.authService.getUser()
@@ -38,8 +38,8 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni
38 this.error = null 38 this.error = null
39 this.success = null 39 this.success = null
40 40
41 const password = this.form.value[ 'password' ] 41 const password = this.form.value['password']
42 const email = this.form.value[ 'new-email' ] 42 const email = this.form.value['new-email']
43 43
44 forkJoin([ 44 forkJoin([
45 this.serverService.getConfig(), 45 this.serverService.getConfig(),
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
index f91b2f37b..47e54dc23 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
@@ -1,7 +1,11 @@
1import { filter } from 'rxjs/operators' 1import { filter } from 'rxjs/operators'
2import { Component, OnInit } from '@angular/core' 2import { Component, OnInit } from '@angular/core'
3import { AuthService, Notifier, UserService } from '@app/core' 3import { AuthService, Notifier, UserService } from '@app/core'
4import { USER_CONFIRM_PASSWORD_VALIDATOR, USER_PASSWORD_VALIDATOR, USER_EXISTING_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' 4import {
5 USER_CONFIRM_PASSWORD_VALIDATOR,
6 USER_EXISTING_PASSWORD_VALIDATOR,
7 USER_PASSWORD_VALIDATOR
8} from '@app/shared/form-validators/user-validators'
5import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 9import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
6import { User } from '@shared/models' 10import { User } from '@shared/models'
7 11
@@ -19,7 +23,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
19 private notifier: Notifier, 23 private notifier: Notifier,
20 private authService: AuthService, 24 private authService: AuthService,
21 private userService: UserService 25 private userService: UserService
22 ) { 26 ) {
23 super() 27 super()
24 } 28 }
25 29
@@ -35,13 +39,13 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
35 const confirmPasswordControl = this.form.get('new-confirmed-password') 39 const confirmPasswordControl = this.form.get('new-confirmed-password')
36 40
37 confirmPasswordControl.valueChanges 41 confirmPasswordControl.valueChanges
38 .pipe(filter(v => v !== this.form.value[ 'new-password' ])) 42 .pipe(filter(v => v !== this.form.value['new-password']))
39 .subscribe(() => confirmPasswordControl.setErrors({ matchPassword: true })) 43 .subscribe(() => confirmPasswordControl.setErrors({ matchPassword: true }))
40 } 44 }
41 45
42 changePassword () { 46 changePassword () {
43 const currentPassword = this.form.value[ 'current-password' ] 47 const currentPassword = this.form.value['current-password']
44 const newPassword = this.form.value[ 'new-password' ] 48 const newPassword = this.form.value['new-password']
45 49
46 this.userService.changePassword(currentPassword, newPassword) 50 this.userService.changePassword(currentPassword, newPassword)
47 .subscribe({ 51 .subscribe({
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
index 5005cb630..4a46f1ad9 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
@@ -15,10 +15,11 @@ export class MyAccountDangerZoneComponent {
15 private userService: UserService, 15 private userService: UserService,
16 private confirmService: ConfirmService, 16 private confirmService: ConfirmService,
17 private redirectService: RedirectService 17 private redirectService: RedirectService
18 ) { } 18 ) { }
19 19
20 async deleteMe () { 20 async deleteMe () {
21 const res = await this.confirmService.confirmWithInput( 21 const res = await this.confirmService.confirmWithInput(
22 // eslint-disable-next-line max-len
22 $localize`Are you sure you want to delete your account? This will delete all your data, including channels, videos and comments. Content cached by other servers and other third-parties might make longer to be deleted.`, 23 $localize`Are you sure you want to delete your account? This will delete all your data, including channels, videos and comments. Content cached by other servers and other third-parties might make longer to be deleted.`,
23 $localize`Type your username to confirm`, 24 $localize`Type your username to confirm`,
24 this.user.username, 25 this.user.username,
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
index fc7635f38..a5bcb6496 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
@@ -19,7 +19,7 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
19 private userService: UserService, 19 private userService: UserService,
20 private authService: AuthService, 20 private authService: AuthService,
21 private notifier: Notifier 21 private notifier: Notifier
22 ) {} 22 ) {}
23 23
24 get userInformationLoaded () { 24 get userInformationLoaded () {
25 return this.authService.userInformationLoaded 25 return this.authService.userInformationLoaded
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts
index eaf8a72e9..450454ca2 100644
--- a/client/src/app/+my-account/my-account.component.ts
+++ b/client/src/app/+my-account/my-account.component.ts
@@ -13,7 +13,7 @@ export class MyAccountComponent implements OnInit {
13 13
14 constructor ( 14 constructor (
15 private screenService: ScreenService 15 private screenService: ScreenService
16 ) { } 16 ) { }
17 17
18 get isBroadcastMessageDisplayed () { 18 get isBroadcastMessageDisplayed () {
19 return this.screenService.isBroadcastMessageDisplayed 19 return this.screenService.isBroadcastMessageDisplayed
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
index d983aacd9..fd00720d8 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
@@ -31,7 +31,7 @@ export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements
31 private notifier: Notifier, 31 private notifier: Notifier,
32 private router: Router, 32 private router: Router,
33 private videoChannelService: VideoChannelService 33 private videoChannelService: VideoChannelService
34 ) { 34 ) {
35 super() 35 super()
36 } 36 }
37 37
@@ -64,7 +64,7 @@ export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements
64 this.authService.refreshUserInformation() 64 this.authService.refreshUserInformation()
65 65
66 this.notifier.success($localize`Video channel ${videoChannelCreate.displayName} created.`) 66 this.notifier.success($localize`Video channel ${videoChannelCreate.displayName} created.`)
67 this.router.navigate(['/my-library', 'video-channels']) 67 this.router.navigate([ '/my-library', 'video-channels' ])
68 }, 68 },
69 69
70 error: err => { 70 error: err => {
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
index e8bfbf9ce..f9521b8b5 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
@@ -66,7 +66,9 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
66 }) 66 })
67 }, 67 },
68 68
69 error: err => this.error = err.message 69 error: err => {
70 this.error = err.message
71 }
70 }) 72 })
71 }) 73 })
72 } 74 }
@@ -96,7 +98,9 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
96 this.router.navigate([ '/my-library', 'video-channels' ]) 98 this.router.navigate([ '/my-library', 'video-channels' ])
97 }, 99 },
98 100
99 error: err => this.error = err.message 101 error: err => {
102 this.error = err.message
103 }
100 }) 104 })
101 } 105 }
102 106
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
index 8b665fd58..303f783fd 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
@@ -121,16 +121,16 @@ channel with the same name (${videoChannel.name})!`,
121 display: false 121 display: false
122 }, 122 },
123 scales: { 123 scales: {
124 xAxes: [{ 124 xAxes: [ {
125 display: false 125 display: false
126 }], 126 } ],
127 yAxes: [{ 127 yAxes: [ {
128 display: false, 128 display: false,
129 ticks: { 129 ticks: {
130 min: Math.max(0, this.videoChannelsMinimumDailyViews - (3 * this.videoChannelsMaximumDailyViews / 100)), 130 min: Math.max(0, this.videoChannelsMinimumDailyViews - (3 * this.videoChannelsMaximumDailyViews / 100)),
131 max: Math.max(1, this.videoChannelsMaximumDailyViews) 131 max: Math.max(1, this.videoChannelsMaximumDailyViews)
132 } 132 }
133 }] 133 } ]
134 }, 134 },
135 layout: { 135 layout: {
136 padding: { 136 padding: {
diff --git a/client/src/app/+my-library/my-history/my-history.component.ts b/client/src/app/+my-library/my-history/my-history.component.ts
index fe6e86346..a72d22e1c 100644
--- a/client/src/app/+my-library/my-history/my-history.component.ts
+++ b/client/src/app/+my-library/my-history/my-history.component.ts
@@ -1,4 +1,3 @@
1import { Subject } from 'rxjs'
2import { tap } from 'rxjs/operators' 1import { tap } from 'rxjs/operators'
3import { Component, ComponentFactoryResolver, OnInit, ViewChild } from '@angular/core' 2import { Component, ComponentFactoryResolver, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
@@ -109,9 +108,9 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook {
109 this.userService.updateMyProfile({ videosHistoryEnabled: this.videosHistoryEnabled }) 108 this.userService.updateMyProfile({ videosHistoryEnabled: this.videosHistoryEnabled })
110 .subscribe({ 109 .subscribe({
111 next: () => { 110 next: () => {
112 const message = this.videosHistoryEnabled === true ? 111 const message = this.videosHistoryEnabled === true
113 $localize`Videos history is enabled` : 112 ? $localize`Videos history is enabled`
114 $localize`Videos history is disabled` 113 : $localize`Videos history is disabled`
115 114
116 this.notifier.success(message) 115 this.notifier.success(message)
117 116
diff --git a/client/src/app/+my-library/my-library.component.ts b/client/src/app/+my-library/my-library.component.ts
index 2ee3559a7..16a7f63e3 100644
--- a/client/src/app/+my-library/my-library.component.ts
+++ b/client/src/app/+my-library/my-library.component.ts
@@ -17,7 +17,7 @@ export class MyLibraryComponent implements OnInit {
17 private serverService: ServerService, 17 private serverService: ServerService,
18 private authService: AuthService, 18 private authService: AuthService,
19 private screenService: ScreenService 19 private screenService: ScreenService
20 ) { } 20 ) { }
21 21
22 get isBroadcastMessageDisplayed () { 22 get isBroadcastMessageDisplayed () {
23 return this.screenService.isBroadcastMessageDisplayed 23 return this.screenService.isBroadcastMessageDisplayed
diff --git a/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts b/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
index b92377bca..764da2369 100644
--- a/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
+++ b/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
@@ -29,7 +29,7 @@ export class MyAcceptOwnershipComponent extends FormReactive implements OnInit {
29 private notifier: Notifier, 29 private notifier: Notifier,
30 private authService: AuthService, 30 private authService: AuthService,
31 private modalService: NgbModal 31 private modalService: NgbModal
32 ) { 32 ) {
33 super() 33 super()
34 } 34 }
35 35
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
index 3e3c3c878..8bc78b2db 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
@@ -29,7 +29,7 @@ export class MyVideoPlaylistCreateComponent extends MyVideoPlaylistEdit implemen
29 private router: Router, 29 private router: Router,
30 private videoPlaylistService: VideoPlaylistService, 30 private videoPlaylistService: VideoPlaylistService,
31 private serverService: ServerService 31 private serverService: ServerService
32 ) { 32 ) {
33 super() 33 super()
34 } 34 }
35 35
@@ -78,7 +78,9 @@ export class MyVideoPlaylistCreateComponent extends MyVideoPlaylistEdit implemen
78 this.router.navigate([ '/my-library', 'video-playlists' ]) 78 this.router.navigate([ '/my-library', 'video-playlists' ])
79 }, 79 },
80 80
81 error: err => this.error = err.message 81 error: err => {
82 this.error = err.message
83 }
82 }) 84 })
83 } 85 }
84 86
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
index 6aff5dbd7..d6959a50e 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
@@ -57,7 +57,7 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
57 ] 57 ]
58 58
59 this.paramsSub = this.route.params.subscribe(routeParams => { 59 this.paramsSub = this.route.params.subscribe(routeParams => {
60 this.videoPlaylistId = routeParams[ 'videoPlaylistId' ] 60 this.videoPlaylistId = routeParams['videoPlaylistId']
61 this.loadElements() 61 this.loadElements()
62 62
63 this.loadPlaylistInfo() 63 this.loadPlaylistInfo()
@@ -145,8 +145,6 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
145 * we add a delay to prevent unwanted drag&drop. 145 * we add a delay to prevent unwanted drag&drop.
146 * 146 *
147 * @see {@link https://github.com/Chocobozzz/PeerTube/issues/2078} 147 * @see {@link https://github.com/Chocobozzz/PeerTube/issues/2078}
148 *
149 * @returns {null|number} Null for no delay, or a number in milliseconds.
150 */ 148 */
151 getDragStartDelay (): null | number { 149 getDragStartDelay (): null | number {
152 if (this.screenService.isInTouchScreen()) { 150 if (this.screenService.isInTouchScreen()) {
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
index a3f54279b..06ac3ad50 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
@@ -65,14 +65,16 @@ export class MyVideoPlaylistUpdateComponent extends MyVideoPlaylistEdit implemen
65 }) 65 })
66 ) 66 )
67 .subscribe({ 67 .subscribe({
68 next: ([ videoPlaylistToUpdate, videoPlaylistPrivacies]) => { 68 next: ([ videoPlaylistToUpdate, videoPlaylistPrivacies ]) => {
69 this.videoPlaylistToUpdate = videoPlaylistToUpdate 69 this.videoPlaylistToUpdate = videoPlaylistToUpdate
70 this.videoPlaylistPrivacies = videoPlaylistPrivacies 70 this.videoPlaylistPrivacies = videoPlaylistPrivacies
71 71
72 this.hydrateFormFromPlaylist() 72 this.hydrateFormFromPlaylist()
73 }, 73 },
74 74
75 error: err => this.error = err.message 75 error: err => {
76 this.error = err.message
77 }
76 }) 78 })
77 } 79 }
78 80
@@ -99,7 +101,9 @@ export class MyVideoPlaylistUpdateComponent extends MyVideoPlaylistEdit implemen
99 this.router.navigate([ '/my-library', 'video-playlists' ]) 101 this.router.navigate([ '/my-library', 'video-playlists' ])
100 }, 102 },
101 103
102 error: err => this.error = err.message 104 error: err => {
105 this.error = err.message
106 }
103 }) 107 })
104 } 108 }
105 109
diff --git a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts
index 8c1f94bf3..960c9a4f7 100644
--- a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts
+++ b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts
@@ -25,7 +25,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
25 private notifier: Notifier, 25 private notifier: Notifier,
26 private userService: UserService, 26 private userService: UserService,
27 private modalService: NgbModal 27 private modalService: NgbModal
28 ) { 28 ) {
29 super() 29 super()
30 } 30 }
31 31
@@ -49,7 +49,9 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
49 const query = event.query 49 const query = event.query
50 this.userService.autocomplete(query) 50 this.userService.autocomplete(query)
51 .subscribe({ 51 .subscribe({
52 next: usernames => this.usernamePropositions = usernames, 52 next: usernames => {
53 this.usernamePropositions = usernames
54 },
53 55
54 error: err => this.notifier.error(err.message) 56 error: err => this.notifier.error(err.message)
55 }) 57 })
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts
index 4f9b71cc6..edb9392dd 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.ts
+++ b/client/src/app/+my-library/my-videos/my-videos.component.ts
@@ -49,7 +49,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
49 49
50 inputFilters: AdvancedInputFilter[] = [ 50 inputFilters: AdvancedInputFilter[] = [
51 { 51 {
52 queryParams: { 'search': 'isLive:true' }, 52 queryParams: { search: 'isLive:true' },
53 label: $localize`Only live videos` 53 label: $localize`Only live videos`
54 } 54 }
55 ] 55 ]
@@ -107,7 +107,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
107 107
108 async deleteSelectedVideos () { 108 async deleteSelectedVideos () {
109 const toDeleteVideosIds = Object.keys(this.selection) 109 const toDeleteVideosIds = Object.keys(this.selection)
110 .filter(k => this.selection[ k ] === true) 110 .filter(k => this.selection[k] === true)
111 .map(k => parseInt(k, 10)) 111 .map(k => parseInt(k, 10))
112 112
113 const res = await this.confirmService.confirm( 113 const res = await this.confirmService.confirm(
diff --git a/client/src/app/+remote-interaction/remote-interaction.component.ts b/client/src/app/+remote-interaction/remote-interaction.component.ts
index 293f7edad..775cc580c 100644
--- a/client/src/app/+remote-interaction/remote-interaction.component.ts
+++ b/client/src/app/+remote-interaction/remote-interaction.component.ts
@@ -7,7 +7,7 @@ import { SearchService } from '@app/shared/shared-search'
7@Component({ 7@Component({
8 selector: 'my-remote-interaction', 8 selector: 'my-remote-interaction',
9 templateUrl: './remote-interaction.component.html', 9 templateUrl: './remote-interaction.component.html',
10 styleUrls: ['./remote-interaction.component.scss'] 10 styleUrls: [ './remote-interaction.component.scss' ]
11}) 11})
12export class RemoteInteractionComponent implements OnInit { 12export class RemoteInteractionComponent implements OnInit {
13 error = '' 13 error = ''
diff --git a/client/src/app/+reset-password/reset-password.component.ts b/client/src/app/+reset-password/reset-password.component.ts
index a1c04147b..11c5110fd 100644
--- a/client/src/app/+reset-password/reset-password.component.ts
+++ b/client/src/app/+reset-password/reset-password.component.ts
@@ -21,7 +21,7 @@ export class ResetPasswordComponent extends FormReactive implements OnInit {
21 private notifier: Notifier, 21 private notifier: Notifier,
22 private router: Router, 22 private router: Router,
23 private route: ActivatedRoute 23 private route: ActivatedRoute
24 ) { 24 ) {
25 super() 25 super()
26 } 26 }
27 27
diff --git a/client/src/app/+signup/+register/custom-stepper.component.ts b/client/src/app/+signup/+register/custom-stepper.component.ts
index 5a80895f9..3b7ba40e8 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.ts
+++ b/client/src/app/+signup/+register/custom-stepper.component.ts
@@ -1,5 +1,5 @@
1import { Component } from '@angular/core'
2import { CdkStep, CdkStepper } from '@angular/cdk/stepper' 1import { CdkStep, CdkStepper } from '@angular/cdk/stepper'
2import { Component } from '@angular/core'
3 3
4@Component({ 4@Component({
5 selector: 'my-custom-stepper', 5 selector: 'my-custom-stepper',
@@ -14,13 +14,13 @@ export class CustomStepperComponent extends CdkStepper {
14 } 14 }
15 15
16 isCompleted (step: CdkStep) { 16 isCompleted (step: CdkStep) {
17 return step.stepControl && step.stepControl.dirty && step.stepControl.valid 17 return step.stepControl?.dirty && step.stepControl.valid
18 } 18 }
19 19
20 isAccessible (index: number) { 20 isAccessible (index: number) {
21 const stepsCompletedMap = this.steps.map(step => this.isCompleted(step)) 21 const stepsCompletedMap = this.steps.map(step => this.isCompleted(step))
22 return index === 0 22 return index === 0
23 ? true 23 ? true
24 : stepsCompletedMap[ index - 1 ] 24 : stepsCompletedMap[index - 1]
25 } 25 }
26} 26}
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
index 056442107..d8ac39c7c 100644
--- a/client/src/app/+signup/+register/register.component.ts
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -49,8 +49,7 @@ export class RegisterComponent implements OnInit {
49 private authService: AuthService, 49 private authService: AuthService,
50 private userService: UserService, 50 private userService: UserService,
51 private hooks: HooksService 51 private hooks: HooksService
52 ) { 52 ) { }
53 }
54 53
55 get requiresEmailVerification () { 54 get requiresEmailVerification () {
56 return this.serverConfig.signup.requiresEmailVerification 55 return this.serverConfig.signup.requiresEmailVerification
@@ -138,11 +137,15 @@ export class RegisterComponent implements OnInit {
138 this.success = $localize`You are now logged in as ${body.username}!` 137 this.success = $localize`You are now logged in as ${body.username}!`
139 }, 138 },
140 139
141 error: err => this.error = err.message 140 error: err => {
141 this.error = err.message
142 }
142 }) 143 })
143 }, 144 },
144 145
145 error: err => this.error = err.message 146 error: err => {
147 this.error = err.message
148 }
146 }) 149 })
147 } 150 }
148} 151}
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
index 457a0abe0..827ec7652 100644
--- a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
@@ -20,7 +20,7 @@ export class VerifyAccountEmailComponent implements OnInit {
20 private authService: AuthService, 20 private authService: AuthService,
21 private notifier: Notifier, 21 private notifier: Notifier,
22 private route: ActivatedRoute 22 private route: ActivatedRoute
23 ) { 23 ) {
24 } 24 }
25 25
26 ngOnInit () { 26 ngOnInit () {
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
index d83fc1324..ef8fd79b9 100644
--- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
+++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
@@ -1,5 +1,5 @@
1import { forkJoin, Subscription } from 'rxjs' 1import { forkJoin, Subscription } from 'rxjs'
2import { first, tap } from 'rxjs/operators' 2import { first } from 'rxjs/operators'
3import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' 3import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' 5import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core'
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index b4d81fe39..064fbb6f5 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -114,7 +114,7 @@
114 <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> 114 <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
115 </ng-template> 115 </ng-template>
116 116
117 <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow> 117 <my-list-overflow [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow>
118 </div> 118 </div>
119 119
120 <router-outlet></router-outlet> 120 <router-outlet></router-outlet>
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts
index 6479644f1..272fc41d9 100644
--- a/client/src/app/+video-channels/video-channels.component.ts
+++ b/client/src/app/+video-channels/video-channels.component.ts
@@ -44,7 +44,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
44 ngOnInit () { 44 ngOnInit () {
45 this.routeSub = this.route.params 45 this.routeSub = this.route.params
46 .pipe( 46 .pipe(
47 map(params => params[ 'videoChannelName' ]), 47 map(params => params['videoChannelName']),
48 distinctUntilChanged(), 48 distinctUntilChanged(),
49 switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)), 49 switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)),
50 catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'other', [ 50 catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'other', [
@@ -64,9 +64,9 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
64 64
65 this.hotkeys = [ 65 this.hotkeys = [
66 new Hotkey('S', (event: KeyboardEvent): boolean => { 66 new Hotkey('S', (event: KeyboardEvent): boolean => {
67 this.subscribeButton.subscribed ? 67 if (this.subscribeButton.subscribed) this.subscribeButton.unsubscribe()
68 this.subscribeButton.unsubscribe() : 68 else this.subscribeButton.subscribe()
69 this.subscribeButton.subscribe() 69
70 return false 70 return false
71 }, undefined, $localize`Subscribe to the account`) 71 }, undefined, $localize`Subscribe to the account`)
72 ] 72 ]
diff --git a/client/src/app/+videos/+video-edit/shared/i18n-primeng-calendar.service.ts b/client/src/app/+videos/+video-edit/shared/i18n-primeng-calendar.service.ts
index 34848b036..4b201ac74 100644
--- a/client/src/app/+videos/+video-edit/shared/i18n-primeng-calendar.service.ts
+++ b/client/src/app/+videos/+video-edit/shared/i18n-primeng-calendar.service.ts
@@ -73,7 +73,7 @@ export class I18nPrimengCalendarService {
73 } 73 }
74 74
75 getTimezone () { 75 getTimezone () {
76 const gmt = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)[1] 76 const gmt = new Date().toString().match(/([A-Z]+[+-][0-9]+)/)[1]
77 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone 77 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
78 78
79 return `${timezone} - ${gmt}` 79 return `${timezone} - ${gmt}`
diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts
index 875911b91..98d66ff00 100644
--- a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts
+++ b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts
@@ -66,18 +66,18 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni
66 isReplacingExistingCaption () { 66 isReplacingExistingCaption () {
67 if (this.closingModal === true) return false 67 if (this.closingModal === true) return false
68 68
69 const languageId = this.form.value[ 'language' ] 69 const languageId = this.form.value['language']
70 70
71 return languageId && this.existingCaptions.indexOf(languageId) !== -1 71 return languageId && this.existingCaptions.includes(languageId)
72 } 72 }
73 73
74 async addCaption () { 74 async addCaption () {
75 const languageId = this.form.value[ 'language' ] 75 const languageId = this.form.value['language']
76 const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) 76 const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId)
77 77
78 this.captionAdded.emit({ 78 this.captionAdded.emit({
79 language: languageObject, 79 language: languageObject,
80 captionfile: this.form.value[ 'captionfile' ] 80 captionfile: this.form.value['captionfile']
81 }) 81 })
82 82
83 this.hide() 83 this.hide()
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts b/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts
index 3a7dbed36..db1ef8d73 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts
+++ b/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts
@@ -24,7 +24,7 @@ function hydrateFormFromVideo (formGroup: FormGroup, video: VideoEdit, thumbnail
24 .then(response => response.blob()) 24 .then(response => response.blob())
25 .then(data => { 25 .then(data => {
26 formGroup.patchValue({ 26 formGroup.patchValue({
27 [ obj.name ]: data 27 [obj.name]: data
28 }) 28 })
29 }) 29 })
30 } 30 }
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
index 90a0e8f52..366c93a79 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
@@ -233,7 +233,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
233 233
234 async deleteCaption (caption: VideoCaptionEdit) { 234 async deleteCaption (caption: VideoCaptionEdit) {
235 // Caption recovers his former state 235 // Caption recovers his former state
236 if (caption.action && this.initialVideoCaptions.indexOf(caption.language.id) !== -1) { 236 if (caption.action && this.initialVideoCaptions.includes(caption.language.id)) {
237 caption.action = undefined 237 caption.action = undefined
238 return 238 return
239 } 239 }
@@ -297,7 +297,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
297 297
298 private trackPrivacyChange () { 298 private trackPrivacyChange () {
299 // We will update the schedule input and the wait transcoding checkbox validators 299 // We will update the schedule input and the wait transcoding checkbox validators
300 this.form.controls[ 'privacy' ] 300 this.form.controls['privacy']
301 .valueChanges 301 .valueChanges
302 .pipe(map(res => parseInt(res.toString(), 10))) 302 .pipe(map(res => parseInt(res.toString(), 10)))
303 .subscribe( 303 .subscribe(
@@ -336,12 +336,12 @@ export class VideoEditComponent implements OnInit, OnDestroy {
336 336
337 private trackChannelChange () { 337 private trackChannelChange () {
338 // We will update the "support" field depending on the channel 338 // We will update the "support" field depending on the channel
339 this.form.controls[ 'channelId' ] 339 this.form.controls['channelId']
340 .valueChanges 340 .valueChanges
341 .pipe(map(res => parseInt(res.toString(), 10))) 341 .pipe(map(res => parseInt(res.toString(), 10)))
342 .subscribe( 342 .subscribe(
343 newChannelId => { 343 newChannelId => {
344 const oldChannelId = parseInt(this.form.value[ 'channelId' ], 10) 344 const oldChannelId = parseInt(this.form.value['channelId'], 10)
345 345
346 // Not initialized yet 346 // Not initialized yet
347 if (isNaN(newChannelId)) return 347 if (isNaN(newChannelId)) return
@@ -350,7 +350,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
350 350
351 // Wait support field update 351 // Wait support field update
352 setTimeout(() => { 352 setTimeout(() => {
353 const currentSupport = this.form.value[ 'support' ] 353 const currentSupport = this.form.value['support']
354 354
355 // First time we set the channel? 355 // First time we set the channel?
356 if (isNaN(oldChannelId)) { 356 if (isNaN(oldChannelId)) {
diff --git a/client/src/app/+videos/+video-edit/video-add-components/drag-drop.directive.ts b/client/src/app/+videos/+video-edit/video-add-components/drag-drop.directive.ts
index 7b1a38c62..7c35e6b84 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/drag-drop.directive.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/drag-drop.directive.ts
@@ -1,26 +1,26 @@
1import { Directive, Output, EventEmitter, HostBinding, HostListener } from '@angular/core' 1import { Directive, Output, EventEmitter, HostBinding, HostListener } from '@angular/core'
2 2
3@Directive({ 3@Directive({
4 selector: '[dragDrop]' 4 selector: '[myDragDrop]'
5}) 5})
6export class DragDropDirective { 6export class DragDropDirective {
7 @Output() fileDropped = new EventEmitter<FileList>() 7 @Output() fileDropped = new EventEmitter<FileList>()
8 8
9 @HostBinding('class.dragover') dragover = false 9 @HostBinding('class.dragover') dragover = false
10 10
11 @HostListener('dragover', ['$event']) onDragOver (e: Event) { 11 @HostListener('dragover', [ '$event' ]) onDragOver (e: Event) {
12 e.preventDefault() 12 e.preventDefault()
13 e.stopPropagation() 13 e.stopPropagation()
14 this.dragover = true 14 this.dragover = true
15 } 15 }
16 16
17 @HostListener('dragleave', ['$event']) public onDragLeave (e: Event) { 17 @HostListener('dragleave', [ '$event' ]) public onDragLeave (e: Event) {
18 e.preventDefault() 18 e.preventDefault()
19 e.stopPropagation() 19 e.stopPropagation()
20 this.dragover = false 20 this.dragover = false
21 } 21 }
22 22
23 @HostListener('drop', ['$event']) public ondrop (e: DragEvent) { 23 @HostListener('drop', [ '$event' ]) public ondrop (e: DragEvent) {
24 e.preventDefault() 24 e.preventDefault()
25 e.stopPropagation() 25 e.stopPropagation()
26 this.dragover = false 26 this.dragover = false
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
index 30c79594d..1b9447e03 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
@@ -41,7 +41,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
41 private liveVideoService: LiveVideoService, 41 private liveVideoService: LiveVideoService,
42 private router: Router, 42 private router: Router,
43 private hooks: HooksService 43 private hooks: HooksService
44 ) { 44 ) {
45 super() 45 super()
46 } 46 }
47 47
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html
index 20a7538db..0f1a94c84 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html
@@ -1,4 +1,4 @@
1<div *ngIf="!hasImportedVideo" class="upload-video-container" dragDrop (fileDropped)="setTorrentFile($event)"> 1<div *ngIf="!hasImportedVideo" class="upload-video-container" myDragDrop (fileDropped)="setTorrentFile($event)">
2 <div class="first-step-block"> 2 <div class="first-step-block">
3 <my-global-icon class="upload-icon" iconName="upload" aria-hidden="true"></my-global-icon> 3 <my-global-icon class="upload-icon" iconName="upload" aria-hidden="true"></my-global-icon>
4 4
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
index fef1f5d65..87e47683f 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
@@ -5,7 +5,7 @@ import { scrollToTop } from '@app/helpers'
5import { FormValidatorService } from '@app/shared/shared-forms' 5import { FormValidatorService } from '@app/shared/shared-forms'
6import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' 6import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
7import { LoadingBarService } from '@ngx-loading-bar/core' 7import { LoadingBarService } from '@ngx-loading-bar/core'
8import { PeerTubeProblemDocument, ServerErrorCode, VideoPrivacy, VideoUpdate } from '@shared/models' 8import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models'
9import { hydrateFormFromVideo } from '../shared/video-edit-utils' 9import { hydrateFormFromVideo } from '../shared/video-edit-utils'
10import { VideoSend } from './video-send' 10import { VideoSend } from './video-send'
11 11
@@ -43,7 +43,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
43 private router: Router, 43 private router: Router,
44 private videoImportService: VideoImportService, 44 private videoImportService: VideoImportService,
45 private hooks: HooksService 45 private hooks: HooksService
46 ) { 46 ) {
47 super() 47 super()
48 } 48 }
49 49
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
index e1893b28f..3487c1adf 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
@@ -59,7 +59,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
59 } 59 }
60 60
61 isTargetUrlValid () { 61 isTargetUrlValid () {
62 return this.targetUrl && this.targetUrl.match(/https?:\/\//) 62 return this.targetUrl?.match(/https?:\/\//)
63 } 63 }
64 64
65 importVideo () { 65 importVideo () {
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
index ce8de049d..efa8c85a3 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
@@ -9,7 +9,7 @@ import { LoadingBarService } from '@ngx-loading-bar/core'
9import { HTMLServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' 9import { HTMLServerConfig, VideoConstant, VideoPrivacy } from '@shared/models'
10 10
11@Directive() 11@Directive()
12// tslint:disable-next-line: directive-class-suffix 12// eslint-disable-next-line @angular-eslint/directive-class-suffix
13export abstract class VideoSend extends FormReactive implements OnInit { 13export abstract class VideoSend extends FormReactive implements OnInit {
14 userVideoChannels: SelectChannelItem[] = [] 14 userVideoChannels: SelectChannelItem[] = []
15 videoPrivacies: VideoConstant<VideoPrivacy>[] = [] 15 videoPrivacies: VideoConstant<VideoPrivacy>[] = []
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html
index 14cd06fcf..db494a02f 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html
@@ -1,4 +1,4 @@
1<div *ngIf="!isUploadingVideo" class="upload-video-container" dragDrop (fileDropped)="onFileDropped($event)"> 1<div *ngIf="!isUploadingVideo" class="upload-video-container" myDragDrop (fileDropped)="onFileDropped($event)">
2 <div class="first-step-block"> 2 <div class="first-step-block">
3 <my-global-icon class="upload-icon" iconName="upload" aria-hidden="true"></my-global-icon> 3 <my-global-icon class="upload-icon" iconName="upload" aria-hidden="true"></my-global-icon>
4 4
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
index b8cb4fa1e..dee2bb57a 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
@@ -128,7 +128,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
128 128
129 onUploadVideoOngoing (state: UploadState) { 129 onUploadVideoOngoing (state: UploadState) {
130 switch (state.status) { 130 switch (state.status) {
131 case 'error': 131 case 'error': {
132 const error = state.response?.error || 'Unknow error' 132 const error = state.response?.error || 'Unknow error'
133 133
134 this.handleUploadError({ 134 this.handleUploadError({
@@ -143,6 +143,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
143 url: state.url 143 url: state.url
144 }) 144 })
145 break 145 break
146 }
146 147
147 case 'cancelled': 148 case 'cancelled':
148 this.isUploadingVideo = false 149 this.isUploadingVideo = false
@@ -323,6 +324,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
323 const videoQuotaUsedBytes = bytePipes.transform(this.userVideoQuotaUsed, 0) 324 const videoQuotaUsedBytes = bytePipes.transform(this.userVideoQuotaUsed, 0)
324 const videoQuotaBytes = bytePipes.transform(videoQuota, 0) 325 const videoQuotaBytes = bytePipes.transform(videoQuota, 0)
325 326
327 // eslint-disable-next-line max-len
326 const msg = $localize`Your video quota is exceeded with this video (video size: ${videoSizeBytes}, used: ${videoQuotaUsedBytes}, quota: ${videoQuotaBytes})` 328 const msg = $localize`Your video quota is exceeded with this video (video size: ${videoSizeBytes}, used: ${videoQuotaUsedBytes}, quota: ${videoQuotaBytes})`
327 this.notifier.error(msg) 329 this.notifier.error(msg)
328 330
@@ -341,6 +343,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
341 const videoSizeBytes = bytePipes.transform(videofile.size, 0) 343 const videoSizeBytes = bytePipes.transform(videofile.size, 0)
342 const quotaUsedDailyBytes = bytePipes.transform(this.userVideoQuotaUsedDaily, 0) 344 const quotaUsedDailyBytes = bytePipes.transform(this.userVideoQuotaUsedDaily, 0)
343 const quotaDailyBytes = bytePipes.transform(videoQuotaDaily, 0) 345 const quotaDailyBytes = bytePipes.transform(videoQuotaDaily, 0)
346 // eslint-disable-next-line max-len
344 const msg = $localize`Your daily video quota is exceeded with this video (video size: ${videoSizeBytes}, used: ${quotaUsedDailyBytes}, quota: ${quotaDailyBytes})` 347 const msg = $localize`Your daily video quota is exceeded with this video (video size: ${videoSizeBytes}, used: ${quotaUsedDailyBytes}, quota: ${quotaDailyBytes})`
345 this.notifier.error(msg) 348 this.notifier.error(msg)
346 349
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts
index 95336dc75..9bef60133 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.ts
+++ b/client/src/app/+videos/+video-edit/video-update.component.ts
@@ -38,7 +38,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
38 private loadingBar: LoadingBarService, 38 private loadingBar: LoadingBarService,
39 private videoCaptionService: VideoCaptionService, 39 private videoCaptionService: VideoCaptionService,
40 private liveVideoService: LiveVideoService 40 private liveVideoService: LiveVideoService
41 ) { 41 ) {
42 super() 42 super()
43 } 43 }
44 44
@@ -119,8 +119,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
119 } 119 }
120 120
121 update () { 121 update () {
122 if (this.checkForm() === false 122 if (this.checkForm() === false || this.isUpdatingVideo === true) {
123 || this.isUpdatingVideo === true) {
124 return 123 return
125 } 124 }
126 125
diff --git a/client/src/app/+videos/+video-edit/video-update.resolver.ts b/client/src/app/+videos/+video-edit/video-update.resolver.ts
index 9172b78a8..91e76b7fe 100644
--- a/client/src/app/+videos/+video-edit/video-update.resolver.ts
+++ b/client/src/app/+videos/+video-edit/video-update.resolver.ts
@@ -18,7 +18,7 @@ export class VideoUpdateResolver implements Resolve<any> {
18 } 18 }
19 19
20 resolve (route: ActivatedRouteSnapshot) { 20 resolve (route: ActivatedRouteSnapshot) {
21 const uuid: string = route.params[ 'uuid' ] 21 const uuid: string = route.params['uuid']
22 22
23 return this.videoService.getVideo({ videoId: uuid }) 23 return this.videoService.getVideo({ videoId: uuid })
24 .pipe( 24 .pipe(
@@ -42,8 +42,8 @@ export class VideoUpdateResolver implements Resolve<any> {
42 ), 42 ),
43 43
44 video.isLive 44 video.isLive
45 ? this.liveVideoService.getVideoLive(video.id) 45 ? this.liveVideoService.getVideoLive(video.id)
46 : of(undefined) 46 : of(undefined)
47 ] 47 ]
48 } 48 }
49} 49}
diff --git a/client/src/app/+videos/+video-watch/player-styles.component.ts b/client/src/app/+videos/+video-watch/player-styles.component.ts
index 9b1672a8c..448a0ee96 100644
--- a/client/src/app/+videos/+video-watch/player-styles.component.ts
+++ b/client/src/app/+videos/+video-watch/player-styles.component.ts
@@ -8,7 +8,7 @@ import { Component, ViewEncapsulation } from '@angular/core'
8 selector: 'my-player-styles', 8 selector: 'my-player-styles',
9 template: '', 9 template: '',
10 styleUrls: [ './player-styles.component.scss' ], 10 styleUrls: [ './player-styles.component.scss' ],
11 // tslint:disable:use-component-view-encapsulation 11 /* eslint-disable @angular-eslint/use-component-view-encapsulation */
12 encapsulation: ViewEncapsulation.None 12 encapsulation: ViewEncapsulation.None
13}) 13})
14export class PlayerStylesComponent { 14export class PlayerStylesComponent {
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
index ac65f7260..71fb127f6 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
@@ -25,7 +25,7 @@ import { VideoCommentCreate } from '@shared/models'
25@Component({ 25@Component({
26 selector: 'my-video-comment-add', 26 selector: 'my-video-comment-add',
27 templateUrl: './video-comment-add.component.html', 27 templateUrl: './video-comment-add.component.html',
28 styleUrls: ['./video-comment-add.component.scss'] 28 styleUrls: [ './video-comment-add.component.scss' ]
29}) 29})
30export class VideoCommentAddComponent extends FormReactive implements OnChanges, OnInit { 30export class VideoCommentAddComponent extends FormReactive implements OnChanges, OnInit {
31 @Input() user: User 31 @Input() user: User
@@ -64,7 +64,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges,
64 for (const emojiMarkupName in emojiMarkupObjectList) { 64 for (const emojiMarkupName in emojiMarkupObjectList) {
65 if (emojiMarkupName) { 65 if (emojiMarkupName) {
66 const emoji = emojiMarkupObjectList[emojiMarkupName] 66 const emoji = emojiMarkupObjectList[emojiMarkupName]
67 emojiMarkupArrayList.push([emoji, emojiMarkupName]) 67 emojiMarkupArrayList.push([ emoji, emojiMarkupName ])
68 } 68 }
69 } 69 }
70 70
@@ -91,7 +91,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges,
91 // Not initialized yet 91 // Not initialized yet
92 if (!this.form) return 92 if (!this.form) return
93 93
94 if (changes.textValue && changes.textValue.currentValue && changes.textValue.currentValue !== changes.textValue.previousValue) { 94 if (changes.textValue?.currentValue && changes.textValue.currentValue !== changes.textValue.previousValue) {
95 this.patchTextValue(changes.textValue.currentValue, true) 95 this.patchTextValue(changes.textValue.currentValue, true)
96 } 96 }
97 } 97 }
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html
index d8b944b35..d0e9bcd29 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html
@@ -29,7 +29,7 @@
29 class="comment-html" 29 class="comment-html"
30 [innerHTML]="sanitizedCommentHTML" 30 [innerHTML]="sanitizedCommentHTML"
31 (timestampClicked)="handleTimestampClicked($event)" 31 (timestampClicked)="handleTimestampClicked($event)"
32 timestampRouteTransformer 32 myTimestampRouteTransformer
33 ></div> 33 ></div>
34 34
35 <div class="comment-actions"> 35 <div class="comment-actions">
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
index f858f4750..5bbcffe82 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
@@ -10,7 +10,7 @@ import { User, UserRight } from '@shared/models'
10@Component({ 10@Component({
11 selector: 'my-video-comment', 11 selector: 'my-video-comment',
12 templateUrl: './video-comment.component.html', 12 templateUrl: './video-comment.component.html',
13 styleUrls: ['./video-comment.component.scss'] 13 styleUrls: [ './video-comment.component.scss' ]
14}) 14})
15export class VideoCommentComponent implements OnInit, OnChanges { 15export class VideoCommentComponent implements OnInit, OnChanges {
16 @ViewChild('commentReportModal') commentReportModal: CommentReportComponent 16 @ViewChild('commentReportModal') commentReportModal: CommentReportComponent
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
index 72866b874..17e0af3bc 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
@@ -9,7 +9,7 @@ import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/
9@Component({ 9@Component({
10 selector: 'my-video-comments', 10 selector: 'my-video-comments',
11 templateUrl: './video-comments.component.html', 11 templateUrl: './video-comments.component.html',
12 styleUrls: ['./video-comments.component.scss'] 12 styleUrls: [ './video-comments.component.scss' ]
13}) 13})
14export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { 14export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
15 @ViewChild('commentHighlightBlock') commentHighlightBlock: ElementRef 15 @ViewChild('commentHighlightBlock') commentHighlightBlock: ElementRef
@@ -200,7 +200,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
200 } 200 }
201 201
202 async onWantedToRedraft (commentToRedraft: VideoComment) { 202 async onWantedToRedraft (commentToRedraft: VideoComment) {
203 const confirm = await this.onWantedToDelete(commentToRedraft, $localize`Delete and re-draft`, $localize`Do you really want to delete and re-draft this comment?`) 203 const confirm = await this.onWantedToDelete(
204 commentToRedraft,
205 $localize`Delete and re-draft`,
206 $localize`Do you really want to delete and re-draft this comment?`
207 )
204 208
205 if (confirm) { 209 if (confirm) {
206 this.inReplyToCommentId = commentToRedraft.inReplyToCommentId 210 this.inReplyToCommentId = commentToRedraft.inReplyToCommentId
diff --git a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts
index 0072492ac..257d463b4 100644
--- a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts
@@ -23,7 +23,7 @@ export class VideoAlertComponent {
23 } 23 }
24 24
25 hasVideoScheduledPublication () { 25 hasVideoScheduledPublication () {
26 return this.video && this.video.scheduledUpdate !== undefined 26 return this.video?.scheduledUpdate !== undefined
27 } 27 }
28 28
29 isWaitingForLive () { 29 isWaitingForLive () {
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html
index 57f682899..2cfaad8f6 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html
@@ -3,7 +3,7 @@
3 class="video-info-description-html" 3 class="video-info-description-html"
4 [innerHTML]="videoHTMLDescription" 4 [innerHTML]="videoHTMLDescription"
5 (timestampClicked)="onTimestampClicked($event)" 5 (timestampClicked)="onTimestampClicked($event)"
6 timestampRouteTransformer 6 myTimestampRouteTransformer
7 ></div> 7 ></div>
8 8
9 <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> 9 <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()">
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
index f0f7966b1..78b3af4a7 100644
--- a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
@@ -39,7 +39,7 @@ export class VideoWatchPlaylistComponent {
39 private notifier: Notifier, 39 private notifier: Notifier,
40 private videoPlaylist: VideoPlaylistService, 40 private videoPlaylist: VideoPlaylistService,
41 private localStorageService: LocalStorageService, 41 private localStorageService: LocalStorageService,
42 private sessionStorageService: SessionStorageService, 42 private sessionStorage: SessionStorageService,
43 private router: Router 43 private router: Router
44 ) { 44 ) {
45 // defaults to true 45 // defaults to true
@@ -50,7 +50,7 @@ export class VideoWatchPlaylistComponent {
50 this.setAutoPlayNextVideoPlaylistSwitchText() 50 this.setAutoPlayNextVideoPlaylistSwitchText()
51 51
52 // defaults to false 52 // defaults to false
53 this.loopPlaylist = this.sessionStorageService.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' 53 this.loopPlaylist = this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
54 this.setLoopPlaylistSwitchText() 54 this.setLoopPlaylistSwitchText()
55 } 55 }
56 56
@@ -145,7 +145,7 @@ export class VideoWatchPlaylistComponent {
145 145
146 const start = previous.startTimestamp 146 const start = previous.startTimestamp
147 const stop = previous.stopTimestamp 147 const stop = previous.stopTimestamp
148 this.router.navigate([],{ queryParams: { playlistPosition: previous.position, start, stop } }) 148 this.router.navigate([], { queryParams: { playlistPosition: previous.position, start, stop } })
149 } 149 }
150 150
151 findPlaylistVideo (position: number, type: 'previous' | 'next'): VideoPlaylistElement { 151 findPlaylistVideo (position: number, type: 'previous' | 'next'): VideoPlaylistElement {
@@ -163,7 +163,7 @@ export class VideoWatchPlaylistComponent {
163 } 163 }
164 164
165 const found = this.playlistElements.find(e => e.position === position) 165 const found = this.playlistElements.find(e => e.position === position)
166 if (found && found.video) return found 166 if (found?.video) return found
167 167
168 const newPosition = type === 'previous' 168 const newPosition = type === 'previous'
169 ? position - 1 169 ? position - 1
@@ -178,7 +178,7 @@ export class VideoWatchPlaylistComponent {
178 178
179 const start = next.startTimestamp 179 const start = next.startTimestamp
180 const stop = next.stopTimestamp 180 const stop = next.stopTimestamp
181 this.router.navigate([],{ queryParams: { playlistPosition: next.position, start, stop } }) 181 this.router.navigate([], { queryParams: { playlistPosition: next.position, start, stop } })
182 } 182 }
183 183
184 switchAutoPlayNextVideoPlaylist () { 184 switchAutoPlayNextVideoPlaylist () {
diff --git a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html
index e1040fead..bbfcab2ae 100644
--- a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html
+++ b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html
@@ -20,7 +20,7 @@
20 > 20 >
21 </my-video-miniature> 21 </my-video-miniature>
22 22
23 <hr *ngIf="!playlist && i == 0 && length > 1" /> 23 <hr *ngIf="!playlist && i === 0 && length > 1" />
24 </ng-container> 24 </ng-container>
25 </ng-container> 25 </ng-container>
26</div> 26</div>
diff --git a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
index 7f3703c08..dfc296d15 100644
--- a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
@@ -51,7 +51,7 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
51 } else { 51 } else {
52 this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' 52 this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
53 53
54 this.sessionStorageService.watch([UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO]).subscribe( 54 this.sessionStorageService.watch([ UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO ]).subscribe(
55 () => { 55 () => {
56 this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' 56 this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
57 } 57 }
diff --git a/client/src/app/+videos/+video-watch/shared/timestamp-route-transformer.directive.ts b/client/src/app/+videos/+video-watch/shared/timestamp-route-transformer.directive.ts
index 45e023695..91fe5bf5d 100644
--- a/client/src/app/+videos/+video-watch/shared/timestamp-route-transformer.directive.ts
+++ b/client/src/app/+videos/+video-watch/shared/timestamp-route-transformer.directive.ts
@@ -1,12 +1,12 @@
1import { Directive, EventEmitter, HostListener, Output } from '@angular/core' 1import { Directive, EventEmitter, HostListener, Output } from '@angular/core'
2 2
3@Directive({ 3@Directive({
4 selector: '[timestampRouteTransformer]' 4 selector: '[myTimestampRouteTransformer]'
5}) 5})
6export class TimestampRouteTransformerDirective { 6export class TimestampRouteTransformerDirective {
7 @Output() timestampClicked = new EventEmitter<number>() 7 @Output() timestampClicked = new EventEmitter<number>()
8 8
9 @HostListener('click', ['$event']) 9 @HostListener('click', [ '$event' ])
10 public onClick ($event: Event) { 10 public onClick ($event: Event) {
11 const target = $event.target as HTMLLinkElement 11 const target = $event.target as HTMLLinkElement
12 12
@@ -21,10 +21,10 @@ export class TimestampRouteTransformerDirective {
21 const ngxLinkParams = new URLSearchParams(ngxLink.search) 21 const ngxLinkParams = new URLSearchParams(ngxLink.search)
22 if (ngxLinkParams.has('start') !== true) return 22 if (ngxLinkParams.has('start') !== true) return
23 23
24 const separators = ['h', 'm', 's'] 24 const separators = [ 'h', 'm', 's' ]
25 const start = ngxLinkParams 25 const start = ngxLinkParams
26 .get('start') 26 .get('start')
27 .match(new RegExp('(\\d{1,9}[' + separators.join('') + '])','g')) // match digits before any given separator 27 .match(new RegExp('(\\d{1,9}[' + separators.join('') + '])', 'g')) // match digits before any given separator
28 .map(t => { 28 .map(t => {
29 if (t.includes('h')) return parseInt(t, 10) * 3600 29 if (t.includes('h')) return parseInt(t, 10) * 3600
30 if (t.includes('m')) return parseInt(t, 10) * 60 30 if (t.includes('m')) return parseInt(t, 10) * 60
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index 85100b653..2007bdecb 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -195,10 +195,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
195 195
196 private loadRouteParams () { 196 private loadRouteParams () {
197 this.paramsSub = this.route.params.subscribe(routeParams => { 197 this.paramsSub = this.route.params.subscribe(routeParams => {
198 const videoId = routeParams[ 'videoId' ] 198 const videoId = routeParams['videoId']
199 if (videoId) return this.loadVideo(videoId) 199 if (videoId) return this.loadVideo(videoId)
200 200
201 const playlistId = routeParams[ 'playlistId' ] 201 const playlistId = routeParams['playlistId']
202 if (playlistId) return this.loadPlaylist(playlistId) 202 if (playlistId) return this.loadPlaylist(playlistId)
203 }) 203 })
204 } 204 }
@@ -206,7 +206,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
206 private loadRouteQuery () { 206 private loadRouteQuery () {
207 this.queryParamsSub = this.route.queryParams.subscribe(queryParams => { 207 this.queryParamsSub = this.route.queryParams.subscribe(queryParams => {
208 // Handle the ?playlistPosition 208 // Handle the ?playlistPosition
209 const positionParam = queryParams[ 'playlistPosition' ] ?? 1 209 const positionParam = queryParams['playlistPosition'] ?? 1
210 210
211 this.playlistPosition = positionParam === 'last' 211 this.playlistPosition = positionParam === 'last'
212 ? -1 // Handle the "last" index 212 ? -1 // Handle the "last" index
@@ -219,7 +219,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
219 219
220 this.videoWatchPlaylist.updatePlaylistIndex(this.playlistPosition) 220 this.videoWatchPlaylist.updatePlaylistIndex(this.playlistPosition)
221 221
222 const start = queryParams[ 'start' ] 222 const start = queryParams['start']
223 if (this.player && start) this.player.currentTime(parseInt(start, 10)) 223 if (this.player && start) this.player.currentTime(parseInt(start, 10))
224 }) 224 })
225 } 225 }
@@ -237,7 +237,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
237 'filter:api.video-watch.video.get.result' 237 'filter:api.video-watch.video.get.result'
238 ) 238 )
239 239
240 forkJoin([ videoObs, this.videoCaptionService.listCaptions(videoId)]) 240 forkJoin([ videoObs, this.videoCaptionService.listCaptions(videoId) ])
241 .subscribe({ 241 .subscribe({
242 next: ([ video, captionsResult ]) => { 242 next: ([ video, captionsResult ]) => {
243 const queryParams = this.route.snapshot.queryParams 243 const queryParams = this.route.snapshot.queryParams
@@ -292,6 +292,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
292 const originUrl = errorBody.originUrl + (window.location.search ?? '') 292 const originUrl = errorBody.originUrl + (window.location.search ?? '')
293 293
294 const res = await this.confirmService.confirm( 294 const res = await this.confirmService.confirm(
295 // eslint-disable-next-line max-len
295 $localize`This video is not available on this instance. Do you want to be redirected on the origin instance: <a href="${originUrl}">${originUrl}</a>?`, 296 $localize`This video is not available on this instance. Do you want to be redirected on the origin instance: <a href="${originUrl}">${originUrl}</a>?`,
296 $localize`Redirection` 297 $localize`Redirection`
297 ) 298 )
@@ -312,7 +313,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
312 if (!errorMessage) return 313 if (!errorMessage) return
313 314
314 // Display a message in the video player instead of a notification 315 // Display a message in the video player instead of a notification
315 if (errorMessage.indexOf('from xs param') !== -1) { 316 if (errorMessage.includes('from xs param')) {
316 this.flushPlayer() 317 this.flushPlayer()
317 this.remoteServerDown = true 318 this.remoteServerDown = true
318 319
@@ -466,7 +467,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
466 467
467 if (this.nextVideoUUID) { 468 if (this.nextVideoUUID) {
468 this.router.navigate([ '/w', this.nextVideoUUID ]) 469 this.router.navigate([ '/w', this.nextVideoUUID ])
469 return
470 } 470 }
471 } 471 }
472 472
@@ -483,14 +483,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
483 483
484 private isAutoPlayNext () { 484 private isAutoPlayNext () {
485 return ( 485 return (
486 (this.user && this.user.autoPlayNextVideo) || 486 (this.user?.autoPlayNextVideo) ||
487 this.anonymousUser.autoPlayNextVideo 487 this.anonymousUser.autoPlayNextVideo
488 ) 488 )
489 } 489 }
490 490
491 private isPlaylistAutoPlayNext () { 491 private isPlaylistAutoPlayNext () {
492 return ( 492 return (
493 (this.user && this.user.autoPlayNextVideoPlaylist) || 493 (this.user?.autoPlayNextVideoPlaylist) ||
494 this.anonymousUser.autoPlayNextVideoPlaylist 494 this.anonymousUser.autoPlayNextVideoPlaylist
495 ) 495 )
496 } 496 }
@@ -508,9 +508,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
508 } 508 }
509 509
510 private buildPlayerManagerOptions (params: { 510 private buildPlayerManagerOptions (params: {
511 video: VideoDetails, 511 video: VideoDetails
512 videoCaptions: VideoCaption[], 512 videoCaptions: VideoCaption[]
513 urlOptions: CustomizationOptions & { playerMode: PlayerMode }, 513 urlOptions: CustomizationOptions & { playerMode: PlayerMode }
514 user?: AuthUser 514 user?: AuthUser
515 }) { 515 }) {
516 const { video, videoCaptions, urlOptions, user } = params 516 const { video, videoCaptions, urlOptions, user } = params
@@ -573,10 +573,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
573 573
574 language: this.localeId, 574 language: this.localeId,
575 575
576 userWatching: user && user.videosHistoryEnabled === true ? { 576 userWatching: user && user.videosHistoryEnabled === true
577 url: this.videoService.getUserWatchingVideoUrl(video.uuid), 577 ? {
578 authorizationHeader: this.authService.getRequestHeaderValue() 578 url: this.videoService.getUserWatchingVideoUrl(video.uuid),
579 } : undefined, 579 authorizationHeader: this.authService.getRequestHeaderValue()
580 }
581 : undefined,
580 582
581 serverUrl: environment.apiUrl, 583 serverUrl: environment.apiUrl,
582 584
@@ -704,9 +706,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
704 if (this.isUserLoggedIn()) { 706 if (this.isUserLoggedIn()) {
705 this.hotkeys = this.hotkeys.concat([ 707 this.hotkeys = this.hotkeys.concat([
706 new Hotkey('shift+s', () => { 708 new Hotkey('shift+s', () => {
707 this.subscribeButton.isSubscribedToAll() 709 if (this.subscribeButton.isSubscribedToAll()) this.subscribeButton.unsubscribe()
708 ? this.subscribeButton.unsubscribe() 710 else this.subscribeButton.subscribe()
709 : this.subscribeButton.subscribe()
710 711
711 return false 712 return false
712 }, undefined, $localize`Subscribe to the account`) 713 }, undefined, $localize`Subscribe to the account`)
diff --git a/client/src/app/+videos/video-list/overview/overview.service.ts b/client/src/app/+videos/video-list/overview/overview.service.ts
index 3aa64ebc8..12d2aa1cb 100644
--- a/client/src/app/+videos/video-list/overview/overview.service.ts
+++ b/client/src/app/+videos/video-list/overview/overview.service.ts
@@ -43,7 +43,7 @@ export class OverviewService {
43 43
44 // Build videos objects 44 // Build videos objects
45 for (const key of Object.keys(serverVideosOverview)) { 45 for (const key of Object.keys(serverVideosOverview)) {
46 for (const object of serverVideosOverview[ key ]) { 46 for (const object of serverVideosOverview[key]) {
47 observables.push( 47 observables.push(
48 of(object.videos) 48 of(object.videos)
49 .pipe( 49 .pipe(
diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
index 6c2b32a4f..c94655c74 100644
--- a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
+++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
@@ -15,7 +15,7 @@ interface VideoTrendingHeaderItem {
15} 15}
16 16
17@Component({ 17@Component({
18 selector: 'video-trending-title-page', 18 selector: 'my-video-trending-title-page',
19 styleUrls: [ './video-trending-header.component.scss' ], 19 styleUrls: [ './video-trending-header.component.scss' ],
20 templateUrl: './video-trending-header.component.html' 20 templateUrl: './video-trending-header.component.html'
21}) 21})
diff --git a/client/src/app/+videos/video-list/trending/video-trending.component.ts b/client/src/app/+videos/video-list/trending/video-trending.component.ts
index ebec672f3..085f29a8b 100644
--- a/client/src/app/+videos/video-list/trending/video-trending.component.ts
+++ b/client/src/app/+videos/video-list/trending/video-trending.component.ts
@@ -63,7 +63,7 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
63 63
64 if (oldSort !== this.sort) this.reloadVideos() 64 if (oldSort !== this.sort) this.reloadVideos()
65 } 65 }
66 ) 66 )
67 } 67 }
68 68
69 ngOnDestroy () { 69 ngOnDestroy () {
@@ -97,12 +97,12 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
97 97
98 getInjector () { 98 getInjector () {
99 return Injector.create({ 99 return Injector.create({
100 providers: [{ 100 providers: [ {
101 provide: 'data', 101 provide: 'data',
102 useValue: { 102 useValue: {
103 model: this.defaultSort 103 model: this.defaultSort
104 } 104 }
105 }] 105 } ]
106 }) 106 })
107 } 107 }
108 108
diff --git a/client/src/app/+videos/video-list/video-local.component.ts b/client/src/app/+videos/video-list/video-local.component.ts
index 4be8cd6b5..b576883d1 100644
--- a/client/src/app/+videos/video-list/video-local.component.ts
+++ b/client/src/app/+videos/video-list/video-local.component.ts
@@ -5,7 +5,7 @@ import { HooksService } from '@app/core/plugins/hooks.service'
5import { immutableAssign } from '@app/helpers' 5import { immutableAssign } from '@app/helpers'
6import { VideoService } from '@app/shared/shared-main' 6import { VideoService } from '@app/shared/shared-main'
7import { AbstractVideoList } from '@app/shared/shared-video-miniature' 7import { AbstractVideoList } from '@app/shared/shared-video-miniature'
8import { UserRight, VideoFilter, VideoSortField } from '@shared/models' 8import { VideoFilter, VideoSortField } from '@shared/models'
9 9
10@Component({ 10@Component({
11 selector: 'my-videos-local', 11 selector: 'my-videos-local',
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index c1e296182..ed5cc53d9 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -243,7 +243,7 @@ export class AppComponent implements OnInit, AfterViewInit {
243 // Inject JS 243 // Inject JS
244 if (this.serverConfig.instance.customizations.javascript) { 244 if (this.serverConfig.instance.customizations.javascript) {
245 try { 245 try {
246 // tslint:disable:no-eval 246 /* eslint-disable no-eval */
247 eval(this.serverConfig.instance.customizations.javascript) 247 eval(this.serverConfig.instance.customizations.javascript)
248 } catch (err) { 248 } catch (err) {
249 console.error('Cannot eval custom JavaScript.', err) 249 console.error('Cannot eval custom JavaScript.', err)
@@ -294,7 +294,7 @@ export class AppComponent implements OnInit, AfterViewInit {
294 294
295 private initHotkeys () { 295 private initHotkeys () {
296 this.hotkeysService.add([ 296 this.hotkeysService.add([
297 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { 297 new Hotkey([ '/', 's' ], (event: KeyboardEvent): boolean => {
298 document.getElementById('search-video').focus() 298 document.getElementById('search-video').focus()
299 return false 299 return false
300 }, undefined, $localize`Focus the search bar`), 300 }, undefined, $localize`Focus the search bar`),
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 5da66c981..79239a17a 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -48,7 +48,7 @@ export class AuthService {
48 private hotkeysService: HotkeysService, 48 private hotkeysService: HotkeysService,
49 private restExtractor: RestExtractor, 49 private restExtractor: RestExtractor,
50 private router: Router 50 private router: Router
51 ) { 51 ) {
52 this.loginChanged = new Subject<AuthStatus>() 52 this.loginChanged = new Subject<AuthStatus>()
53 this.loginChangedSource = this.loginChanged.asObservable() 53 this.loginChangedSource = this.loginChanged.asObservable()
54 54
@@ -206,7 +206,9 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
206 this.refreshingTokenObservable = this.http.post<UserRefreshToken>(AuthService.BASE_TOKEN_URL, body, { headers }) 206 this.refreshingTokenObservable = this.http.post<UserRefreshToken>(AuthService.BASE_TOKEN_URL, body, { headers })
207 .pipe( 207 .pipe(
208 map(res => this.handleRefreshToken(res)), 208 map(res => this.handleRefreshToken(res)),
209 tap(() => { this.refreshingTokenObservable = null }), 209 tap(() => {
210 this.refreshingTokenObservable = null
211 }),
210 catchError(err => { 212 catchError(err => {
211 this.refreshingTokenObservable = null 213 this.refreshingTokenObservable = null
212 214
diff --git a/client/src/app/core/hotkeys/hotkeys.component.ts b/client/src/app/core/hotkeys/hotkeys.component.ts
index 315e1a25e..60b7516aa 100644
--- a/client/src/app/core/hotkeys/hotkeys.component.ts
+++ b/client/src/app/core/hotkeys/hotkeys.component.ts
@@ -3,8 +3,8 @@ import { Subscription } from 'rxjs'
3import { Component, Input, OnDestroy, OnInit } from '@angular/core' 3import { Component, Input, OnDestroy, OnInit } from '@angular/core'
4 4
5@Component({ 5@Component({
6 selector : 'my-hotkeys-cheatsheet', 6 selector: 'my-hotkeys-cheatsheet',
7 templateUrl : './hotkeys.component.html', 7 templateUrl: './hotkeys.component.html',
8 styleUrls: [ './hotkeys.component.scss' ] 8 styleUrls: [ './hotkeys.component.scss' ]
9}) 9})
10export class CheatSheetComponent implements OnInit, OnDestroy { 10export class CheatSheetComponent implements OnInit, OnDestroy {
@@ -16,7 +16,7 @@ export class CheatSheetComponent implements OnInit, OnDestroy {
16 16
17 constructor ( 17 constructor (
18 private hotkeysService: HotkeysService 18 private hotkeysService: HotkeysService
19 ) {} 19 ) {}
20 20
21 public ngOnInit (): void { 21 public ngOnInit (): void {
22 this.subscription = this.hotkeysService.cheatSheetToggle.subscribe((isOpen) => { 22 this.subscription = this.hotkeysService.cheatSheetToggle.subscribe((isOpen) => {
diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts
index 0b8d0191e..f0dc4fcaa 100644
--- a/client/src/app/core/menu/menu.service.ts
+++ b/client/src/app/core/menu/menu.service.ts
@@ -25,7 +25,7 @@ export type MenuSection = {
25export class MenuService { 25export class MenuService {
26 isMenuDisplayed = true 26 isMenuDisplayed = true
27 isMenuChangedByUser = false 27 isMenuChangedByUser = false
28 menuWidth = 240 // should be kept equal to $menu-width 28 menuWidth = 240 // should be kept equal to $menu-width
29 29
30 constructor ( 30 constructor (
31 private screenService: ScreenService 31 private screenService: ScreenService
@@ -55,7 +55,7 @@ export class MenuService {
55 // On touch screens, lock body scroll and display content overlay when memu is opened 55 // On touch screens, lock body scroll and display content overlay when memu is opened
56 if (this.isMenuDisplayed) { 56 if (this.isMenuDisplayed) {
57 document.body.classList.add('menu-open') 57 document.body.classList.add('menu-open')
58 this.screenService.onFingerSwipe('left', () => { this.setMenuDisplay(false) }) 58 this.screenService.onFingerSwipe('left', () => this.setMenuDisplay(false))
59 return 59 return
60 } 60 }
61 61
diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts
index ddde198d2..062083fd1 100644
--- a/client/src/app/core/plugins/hooks.service.ts
+++ b/client/src/app/core/plugins/hooks.service.ts
@@ -27,9 +27,8 @@ export class HooksService {
27 }) 27 })
28 } 28 }
29 29
30 wrapObsFun 30 wrapObsFun <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName>
31 <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName> 31 (fun: ObservableFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
32 (fun: ObservableFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
33 return from(this.pluginService.ensurePluginsAreLoaded(scope)) 32 return from(this.pluginService.ensurePluginsAreLoaded(scope))
34 .pipe( 33 .pipe(
35 mergeMap(() => this.wrapObjectWithoutScopeLoad(params, hookParamName)), 34 mergeMap(() => this.wrapObjectWithoutScopeLoad(params, hookParamName)),
@@ -38,9 +37,8 @@ export class HooksService {
38 ) 37 )
39 } 38 }
40 39
41 async wrapFun 40 async wrapFun <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName>
42 <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName> 41 (fun: RawFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
43 (fun: RawFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
44 await this.pluginService.ensurePluginsAreLoaded(scope) 42 await this.pluginService.ensurePluginsAreLoaded(scope)
45 43
46 const newParams = await this.wrapObjectWithoutScopeLoad(params, hookParamName) 44 const newParams = await this.wrapObjectWithoutScopeLoad(params, hookParamName)
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts
index 774c03964..89391c2c5 100644
--- a/client/src/app/core/plugins/plugin.service.ts
+++ b/client/src/app/core/plugins/plugin.service.ts
@@ -188,7 +188,7 @@ export class PluginService implements ClientHook {
188 if (!this.authService.isLoggedIn()) return undefined 188 if (!this.authService.isLoggedIn()) return undefined
189 189
190 const value = this.authService.getRequestHeaderValue() 190 const value = this.authService.getRequestHeaderValue()
191 return { 'Authorization': value } 191 return { Authorization: value }
192 }, 192 },
193 193
194 notifier: { 194 notifier: {
@@ -198,10 +198,10 @@ export class PluginService implements ClientHook {
198 }, 198 },
199 199
200 showModal: (input: { 200 showModal: (input: {
201 title: string, 201 title: string
202 content: string, 202 content: string
203 close?: boolean, 203 close?: boolean
204 cancel?: { value: string, action?: () => void }, 204 cancel?: { value: string, action?: () => void }
205 confirm?: { value: string, action?: () => void } 205 confirm?: { value: string, action?: () => void }
206 }) => { 206 }) => {
207 this.zone.run(() => this.customModal.show(input)) 207 this.zone.run(() => this.customModal.show(input))
diff --git a/client/src/app/core/renderer/markdown.service.ts b/client/src/app/core/renderer/markdown.service.ts
index 36258ca98..a81d99534 100644
--- a/client/src/app/core/renderer/markdown.service.ts
+++ b/client/src/app/core/renderer/markdown.service.ts
@@ -103,20 +103,20 @@ export class MarkdownService {
103 const { name, markdown, withEmoji, additionalAllowedTags } = options 103 const { name, markdown, withEmoji, additionalAllowedTags } = options
104 if (!markdown) return '' 104 if (!markdown) return ''
105 105
106 const config = this.parsersConfig[ name ] 106 const config = this.parsersConfig[name]
107 if (!this.markdownParsers[ name ]) { 107 if (!this.markdownParsers[name]) {
108 this.markdownParsers[ name ] = await this.createMarkdownIt(config) 108 this.markdownParsers[name] = await this.createMarkdownIt(config)
109 109
110 if (withEmoji) { 110 if (withEmoji) {
111 if (!this.emojiModule) { 111 if (!this.emojiModule) {
112 this.emojiModule = (await import('markdown-it-emoji/light')).default 112 this.emojiModule = (await import('markdown-it-emoji/light')).default
113 } 113 }
114 114
115 this.markdownParsers[ name ].use(this.emojiModule) 115 this.markdownParsers[name].use(this.emojiModule)
116 } 116 }
117 } 117 }
118 118
119 let html = this.markdownParsers[ name ].render(markdown) 119 let html = this.markdownParsers[name].render(markdown)
120 html = this.avoidTruncatedTags(html) 120 html = this.avoidTruncatedTags(html)
121 121
122 if (config.escape) return this.htmlRenderer.toSafeHtml(html, additionalAllowedTags) 122 if (config.escape) return this.htmlRenderer.toSafeHtml(html, additionalAllowedTags)
@@ -156,7 +156,7 @@ export class MarkdownService {
156 if (relIndex < 0) token.attrPush([ 'rel', 'noopener noreferrer' ]) 156 if (relIndex < 0) token.attrPush([ 'rel', 'noopener noreferrer' ])
157 else token.attrs[relIndex][1] = 'noopener noreferrer' 157 else token.attrs[relIndex][1] = 'noopener noreferrer'
158 158
159 // pass token to default renderer. 159 // pass token to default renderer.*
160 return defaultRender(tokens, index, options, env, self) 160 return defaultRender(tokens, index, options, env, self)
161 } 161 }
162 } 162 }
@@ -164,7 +164,7 @@ export class MarkdownService {
164 private avoidTruncatedTags (html: string) { 164 private avoidTruncatedTags (html: string) {
165 return html.replace(/\*\*?([^*]+)$/, '$1') 165 return html.replace(/\*\*?([^*]+)$/, '$1')
166 .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...') 166 .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
167 .replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1') 167 .replace(/\[[^\]]+\]\(([^)]+)$/m, '$1')
168 .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>') 168 .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
169 } 169 }
170} 170}
diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts
index 29a56ba39..dd4a78de5 100644
--- a/client/src/app/core/rest/rest-extractor.service.ts
+++ b/client/src/app/core/rest/rest-extractor.service.ts
@@ -13,15 +13,16 @@ export class RestExtractor {
13 return true 13 return true
14 } 14 }
15 15
16 applyToResultListData <T> (result: ResultList<T>, fun: Function, additionalArgs?: any[]): ResultList<T> { 16 applyToResultListData <T, A, U> (
17 result: ResultList<T>,
18 fun: (data: T, ...args: A[]) => U,
19 additionalArgs: A[] = []
20 ): ResultList<U> {
17 const data: T[] = result.data 21 const data: T[] = result.data
18 const newData: T[] = []
19
20 data.forEach(d => newData.push(fun.apply(this, [ d ].concat(additionalArgs))))
21 22
22 return { 23 return {
23 total: result.total, 24 total: result.total,
24 data: newData 25 data: data.map(d => fun.apply(this, [ d, ...additionalArgs ]))
25 } 26 }
26 } 27 }
27 28
@@ -29,8 +30,10 @@ export class RestExtractor {
29 return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ]) 30 return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ])
30 } 31 }
31 32
32 convertDateToHuman (target: { [ id: string ]: string }, fieldsToConvert: string[]) { 33 convertDateToHuman (target: any, fieldsToConvert: string[]) {
33 fieldsToConvert.forEach(field => target[field] = dateToHuman(target[field])) 34 fieldsToConvert.forEach(field => {
35 target[field] = dateToHuman(target[field])
36 })
34 37
35 return target 38 return target
36 } 39 }
@@ -46,7 +49,7 @@ export class RestExtractor {
46 errorMessage = err.error 49 errorMessage = err.error
47 } else if (err.status !== undefined) { 50 } else if (err.status !== undefined) {
48 // A server-side error occurred. 51 // A server-side error occurred.
49 if (err.error && err.error.errors) { 52 if (err.error?.errors) {
50 const errors = err.error.errors 53 const errors = err.error.errors
51 const errorsArray: string[] = [] 54 const errorsArray: string[] = []
52 55
@@ -55,9 +58,10 @@ export class RestExtractor {
55 }) 58 })
56 59
57 errorMessage = errorsArray.join('. ') 60 errorMessage = errorsArray.join('. ')
58 } else if (err.error && err.error.error) { 61 } else if (err.error?.error) {
59 errorMessage = err.error.error 62 errorMessage = err.error.error
60 } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) { 63 } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) {
64 // eslint-disable-next-line max-len
61 errorMessage = $localize`Media is too large for the server. Please contact you administrator if you want to increase the limit size.` 65 errorMessage = $localize`Media is too large for the server. Please contact you administrator if you want to increase the limit size.`
62 } else if (err.status === HttpStatusCode.TOO_MANY_REQUESTS_429) { 66 } else if (err.status === HttpStatusCode.TOO_MANY_REQUESTS_429) {
63 const secondsLeft = err.headers.get('retry-after') 67 const secondsLeft = err.headers.get('retry-after')
@@ -71,7 +75,7 @@ export class RestExtractor {
71 errorMessage = $localize`Server error. Please retry later.` 75 errorMessage = $localize`Server error. Please retry later.`
72 } 76 }
73 77
74 errorMessage = errorMessage ? errorMessage : 'Unknown error.' 78 errorMessage = errorMessage || 'Unknown error.'
75 console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) 79 console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
76 } else { 80 } else {
77 console.error(err) 81 console.error(err)
@@ -93,7 +97,7 @@ export class RestExtractor {
93 } 97 }
94 98
95 redirectTo404IfNotFound (obj: { status: number }, type: 'video' | 'other', status = [ HttpStatusCode.NOT_FOUND_404 ]) { 99 redirectTo404IfNotFound (obj: { status: number }, type: 'video' | 'other', status = [ HttpStatusCode.NOT_FOUND_404 ]) {
96 if (obj && obj.status && status.indexOf(obj.status) !== -1) { 100 if (obj?.status && status.includes(obj.status)) {
97 // Do not use redirectService to avoid circular dependencies 101 // Do not use redirectService to avoid circular dependencies
98 this.router.navigate([ '/404' ], { state: { type, obj }, skipLocationChange: true }) 102 this.router.navigate([ '/404' ], { state: { type, obj }, skipLocationChange: true })
99 } 103 }
diff --git a/client/src/app/core/routing/custom-reuse-strategy.ts b/client/src/app/core/routing/custom-reuse-strategy.ts
index c0f9f04e0..c2510f1df 100644
--- a/client/src/app/core/routing/custom-reuse-strategy.ts
+++ b/client/src/app/core/routing/custom-reuse-strategy.ts
@@ -1,5 +1,5 @@
1import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
2import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
3 3
4@Injectable() 4@Injectable()
5export class CustomReuseStrategy implements RouteReuseStrategy { 5export class CustomReuseStrategy implements RouteReuseStrategy {
@@ -78,6 +78,6 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
78 } 78 }
79 79
80 private isReuseEnabled (route: ActivatedRouteSnapshot) { 80 private isReuseEnabled (route: ActivatedRouteSnapshot) {
81 return route.data.reuse && route.data.reuse.enabled && route.queryParams[ 'a-state' ] 81 return route.data.reuse?.enabled && route.queryParams['a-state']
82 } 82 }
83} 83}
diff --git a/client/src/app/core/routing/menu-guard.service.ts b/client/src/app/core/routing/menu-guard.service.ts
index c4e64d434..8c5bbfde9 100644
--- a/client/src/app/core/routing/menu-guard.service.ts
+++ b/client/src/app/core/routing/menu-guard.service.ts
@@ -17,33 +17,43 @@ abstract class MenuGuard implements CanActivate, CanDeactivate<any> {
17 if (!this.screen.isInMobileView() && this.screen.isInMediumView()) { 17 if (!this.screen.isInMobileView() && this.screen.isInMediumView()) {
18 this.menu.setMenuDisplay(this.display) 18 this.menu.setMenuDisplay(this.display)
19 } 19 }
20
20 return true 21 return true
21 } 22 }
22} 23}
23 24
24@Injectable() 25@Injectable()
25export class OpenMenuGuard extends MenuGuard { 26export class OpenMenuGuard extends MenuGuard {
26 constructor (menu: MenuService, screen: ScreenService) { super(menu, screen, true) } 27 constructor (menu: MenuService, screen: ScreenService) {
28 super(menu, screen, true)
29 }
27} 30}
28 31
29@Injectable() 32@Injectable()
30export class OpenMenuAlwaysGuard extends MenuGuard { 33export class OpenMenuAlwaysGuard extends MenuGuard {
31 constructor (menu: MenuService, screen: ScreenService) { super(menu, screen, true) } 34 constructor (menu: MenuService, screen: ScreenService) {
35 super(menu, screen, true)
36 }
32 37
33 canActivate (): boolean { 38 canActivate (): boolean {
34 this.menu.setMenuDisplay(this.display) 39 this.menu.setMenuDisplay(this.display)
40
35 return true 41 return true
36 } 42 }
37} 43}
38 44
39@Injectable() 45@Injectable()
40export class CloseMenuGuard extends MenuGuard { 46export class CloseMenuGuard extends MenuGuard {
41 constructor (menu: MenuService, screen: ScreenService) { super(menu, screen, false) } 47 constructor (menu: MenuService, screen: ScreenService) {
48 super(menu, screen, false)
49 }
42} 50}
43 51
44@Injectable() 52@Injectable()
45export class CloseMenuAlwaysGuard extends MenuGuard { 53export class CloseMenuAlwaysGuard extends MenuGuard {
46 constructor (menu: MenuService, screen: ScreenService) { super(menu, screen, false) } 54 constructor (menu: MenuService, screen: ScreenService) {
55 super(menu, screen, false)
56 }
47 57
48 canActivate (): boolean { 58 canActivate (): boolean {
49 this.menu.setMenuDisplay(this.display) 59 this.menu.setMenuDisplay(this.display)
diff --git a/client/src/app/core/routing/meta-guard.service.ts b/client/src/app/core/routing/meta-guard.service.ts
index bedb3450e..851404959 100644
--- a/client/src/app/core/routing/meta-guard.service.ts
+++ b/client/src/app/core/routing/meta-guard.service.ts
@@ -1,5 +1,5 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router' 2import { ActivatedRouteSnapshot, CanActivate, CanActivateChild } from '@angular/router'
3import { MetaService } from './meta.service' 3import { MetaService } from './meta.service'
4 4
5@Injectable() 5@Injectable()
diff --git a/client/src/app/core/routing/preload-selected-modules-list.ts b/client/src/app/core/routing/preload-selected-modules-list.ts
index b494a40bc..b5c3195b0 100644
--- a/client/src/app/core/routing/preload-selected-modules-list.ts
+++ b/client/src/app/core/routing/preload-selected-modules-list.ts
@@ -6,7 +6,7 @@ import { Injectable } from '@angular/core'
6@Injectable() 6@Injectable()
7export class PreloadSelectedModulesList implements PreloadingStrategy { 7export class PreloadSelectedModulesList implements PreloadingStrategy {
8 8
9 preload (route: Route, load: Function): Observable<any> { 9 preload (route: Route, load: () => Observable<any>): Observable<any> {
10 if (!route.data || !route.data.preload) return ofObservable(null) 10 if (!route.data || !route.data.preload) return ofObservable(null)
11 11
12 if (typeof route.data.preload === 'number') { 12 if (typeof route.data.preload === 'number') {
diff --git a/client/src/app/core/scoped-tokens/scoped-tokens.service.ts b/client/src/app/core/scoped-tokens/scoped-tokens.service.ts
index 8e3697c31..038e5031c 100644
--- a/client/src/app/core/scoped-tokens/scoped-tokens.service.ts
+++ b/client/src/app/core/scoped-tokens/scoped-tokens.service.ts
@@ -1,9 +1,8 @@
1import { Injectable } from '@angular/core' 1import { catchError } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http' 2import { HttpClient } from '@angular/common/http'
3import { environment } from '../../../environments/environment' 3import { Injectable } from '@angular/core'
4import { AuthService } from '../auth'
5import { ScopedToken } from '@shared/models/users/user-scoped-token' 4import { ScopedToken } from '@shared/models/users/user-scoped-token'
6import { catchError } from 'rxjs/operators' 5import { environment } from '../../../environments/environment'
7import { RestExtractor } from '../rest' 6import { RestExtractor } from '../rest'
8 7
9@Injectable() 8@Injectable()
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 8f041a147..d01942139 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -4,7 +4,7 @@ import { HttpClient } from '@angular/common/http'
4import { Inject, Injectable, LOCALE_ID } from '@angular/core' 4import { Inject, Injectable, LOCALE_ID } from '@angular/core'
5import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers' 5import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
6import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' 6import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
7import { HTMLServerConfig, SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models' 7import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
8import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
9 9
10@Injectable() 10@Injectable()
@@ -171,7 +171,7 @@ export class ServerService {
171 map(({ data, translations }) => { 171 map(({ data, translations }) => {
172 const hashToPopulate: VideoConstant<T>[] = Object.keys(data) 172 const hashToPopulate: VideoConstant<T>[] = Object.keys(data)
173 .map(dataKey => { 173 .map(dataKey => {
174 const label = data[ dataKey ] 174 const label = data[dataKey]
175 175
176 const id = attributeName === 'languages' 176 const id = attributeName === 'languages'
177 ? dataKey as T 177 ? dataKey as T
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts
index c35548798..c9e6fa700 100644
--- a/client/src/app/core/theme/theme.service.ts
+++ b/client/src/app/core/theme/theme.service.ts
@@ -95,8 +95,8 @@ export class ThemeService {
95 private loadTheme (name: string) { 95 private loadTheme (name: string) {
96 const links = document.getElementsByTagName('link') 96 const links = document.getElementsByTagName('link')
97 for (let i = 0; i < links.length; i++) { 97 for (let i = 0; i < links.length; i++) {
98 const link = links[ i ] 98 const link = links[i]
99 if (link.getAttribute('rel').indexOf('style') !== -1 && link.getAttribute('title')) { 99 if (link.getAttribute('rel').includes('style') && link.getAttribute('title')) {
100 link.disabled = link.getAttribute('title') !== name 100 link.disabled = link.getAttribute('title') !== name
101 } 101 }
102 } 102 }
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index 47db985e1..a8a774eca 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -35,7 +35,7 @@ export class UserService {
35 private restService: RestService, 35 private restService: RestService,
36 private localStorageService: LocalStorageService, 36 private localStorageService: LocalStorageService,
37 private sessionStorageService: SessionStorageService 37 private sessionStorageService: SessionStorageService
38 ) { } 38 ) { }
39 39
40 changePassword (currentPassword: string, newPassword: string) { 40 changePassword (currentPassword: string, newPassword: string) {
41 const url = UserService.BASE_USERS_URL + 'me' 41 const url = UserService.BASE_USERS_URL + 'me'
@@ -266,7 +266,7 @@ export class UserService {
266 266
267 getUserWithCache (userId: number) { 267 getUserWithCache (userId: number) {
268 if (!this.userCache[userId]) { 268 if (!this.userCache[userId]) {
269 this.userCache[ userId ] = this.getUser(userId).pipe(shareReplay()) 269 this.userCache[userId] = this.getUser(userId).pipe(shareReplay())
270 } 270 }
271 271
272 return this.userCache[userId] 272 return this.userCache[userId]
diff --git a/client/src/app/header/search-typeahead.component.ts b/client/src/app/header/search-typeahead.component.ts
index b5d76c70e..e10baea2b 100644
--- a/client/src/app/header/search-typeahead.component.ts
+++ b/client/src/app/header/search-typeahead.component.ts
@@ -199,7 +199,7 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDes
199 } 199 }
200 200
201 private loadUserLanguagesIfNeeded (queryParams: any) { 201 private loadUserLanguagesIfNeeded (queryParams: any) {
202 if (queryParams && queryParams.languageOneOf) return of(queryParams) 202 if (queryParams?.languageOneOf) return of(queryParams)
203 203
204 return this.authService.userInformationLoaded 204 return this.authService.userInformationLoaded
205 .pipe( 205 .pipe(
diff --git a/client/src/app/helpers/locales/oc.ts b/client/src/app/helpers/locales/oc.ts
index d3b2e8407..716b76df1 100644
--- a/client/src/app/helpers/locales/oc.ts
+++ b/client/src/app/helpers/locales/oc.ts
@@ -12,16 +12,16 @@ function plural (n: number): number {
12 12
13export default [ 13export default [
14 'oc', 14 'oc',
15 [['a. m.', 'p. m.'], u, u], 15 [ [ 'a. m.', 'p. m.' ], u, u ],
16 u, 16 u,
17 [ 17 [
18 ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'], 18 [ 'dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds' ], [ 'dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.' ],
19 ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], 19 [ 'dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte' ],
20 ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'] 20 [ 'dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.' ]
21 ], 21 ],
22 u, 22 u,
23 [ 23 [
24 ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'], 24 [ 'GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC' ],
25 [ 25 [
26 'de gen.', 'de febr.', 'de març', 'd’abr.', 'de mai', 'de junh', 'de jul.', 'd’ag.', 26 'de gen.', 'de febr.', 'de març', 'd’abr.', 'de mai', 'de junh', 'de jul.', 'd’ag.',
27 'de set.', 'd’oct.', 'de nov.', 'de dec.' 27 'de set.', 'd’oct.', 'de nov.', 'de dec.'
@@ -32,7 +32,7 @@ export default [
32 ] 32 ]
33 ], 33 ],
34 [ 34 [
35 ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'], 35 [ 'GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC' ],
36 [ 36 [
37 'gen.', 'febr.', 'març', 'abr.', 'mai', 'junh', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 37 'gen.', 'febr.', 'març', 'abr.', 'mai', 'junh', 'jul.', 'ag.', 'set.', 'oct.', 'nov.',
38 'dec.' 38 'dec.'
@@ -42,62 +42,62 @@ export default [
42 'novembre', 'decembre' 42 'novembre', 'decembre'
43 ] 43 ]
44 ], 44 ],
45 [['aC', 'dC'], u, ['abans Jèsus-Crist', 'aprèp Jèsus-Crist']], 45 [ [ 'aC', 'dC' ], u, [ 'abans Jèsus-Crist', 'aprèp Jèsus-Crist' ] ],
46 1, 46 1,
47 [6, 0], 47 [ 6, 0 ],
48 ['d/M/yy', 'd MMM y', 'd MMMM \'de\' y', 'EEEE, d MMMM \'de\' y'], 48 [ 'd/M/yy', 'd MMM y', 'd MMMM \'de\' y', 'EEEE, d MMMM \'de\' y' ],
49 ['H:mm', 'H:mm:ss', 'H:mm:ss z', 'H:mm:ss zzzz'], 49 [ 'H:mm', 'H:mm:ss', 'H:mm:ss z', 'H:mm:ss zzzz' ],
50 ['{1} {0}', '{1}, {0}', '{1} \'a\' \'les\' {0}', u], 50 [ '{1} {0}', '{1}, {0}', '{1} \'a\' \'les\' {0}', u ],
51 [',', '.', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'], 51 [ ',', '.', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':' ],
52 ['#,##0.###', '#,##0%', '#,##0.00 ¤', '#E0'], 52 [ '#,##0.###', '#,##0%', '#,##0.00 ¤', '#E0' ],
53 'EUR', 53 'EUR',
54 '€', 54 '€',
55 'euro', 55 'euro',
56 { 56 {
57 'ARS': ['$AR', '$'], 57 ARS: [ '$AR', '$' ],
58 'AUD': ['$AU', '$'], 58 AUD: [ '$AU', '$' ],
59 'BEF': ['FB'], 59 BEF: [ 'FB' ],
60 'BMD': ['$BM', '$'], 60 BMD: [ '$BM', '$' ],
61 'BND': ['$BN', '$'], 61 BND: [ '$BN', '$' ],
62 'BZD': ['$BZ', '$'], 62 BZD: [ '$BZ', '$' ],
63 'CAD': ['$CA', '$'], 63 CAD: [ '$CA', '$' ],
64 'CLP': ['$CL', '$'], 64 CLP: [ '$CL', '$' ],
65 'CNY': [u, 'Â¥'], 65 CNY: [ u, 'Â¥' ],
66 'COP': ['$CO', '$'], 66 COP: [ '$CO', '$' ],
67 'CYP': ['£CY'], 67 CYP: [ '£CY' ],
68 'EGP': [u, '£E'], 68 EGP: [ u, '£E' ],
69 'FJD': ['$FJ', '$'], 69 FJD: [ '$FJ', '$' ],
70 'FKP': ['£FK', '£'], 70 FKP: [ '£FK', '£' ],
71 'FRF': ['F'], 71 FRF: [ 'F' ],
72 'GBP': ['£GB', '£'], 72 GBP: [ '£GB', '£' ],
73 'GIP': ['£GI', '£'], 73 GIP: [ '£GI', '£' ],
74 'HKD': [u, '$'], 74 HKD: [ u, '$' ],
75 'IEP': ['£IE'], 75 IEP: [ '£IE' ],
76 'ILP': ['£IL'], 76 ILP: [ '£IL' ],
77 'ITL': ['₤IT'], 77 ITL: [ '₤IT' ],
78 'JPY': [u, 'Â¥'], 78 JPY: [ u, 'Â¥' ],
79 'KMF': [u, 'FC'], 79 KMF: [ u, 'FC' ],
80 'LBP': ['£LB', '£L'], 80 LBP: [ '£LB', '£L' ],
81 'MTP': ['£MT'], 81 MTP: [ '£MT' ],
82 'MXN': ['$MX', '$'], 82 MXN: [ '$MX', '$' ],
83 'NAD': ['$NA', '$'], 83 NAD: [ '$NA', '$' ],
84 'NIO': [u, '$C'], 84 NIO: [ u, '$C' ],
85 'NZD': ['$NZ', '$'], 85 NZD: [ '$NZ', '$' ],
86 'RHD': ['$RH'], 86 RHD: [ '$RH' ],
87 'RON': [u, 'L'], 87 RON: [ u, 'L' ],
88 'RWF': [u, 'FR'], 88 RWF: [ u, 'FR' ],
89 'SBD': ['$SB', '$'], 89 SBD: [ '$SB', '$' ],
90 'SGD': ['$SG', '$'], 90 SGD: [ '$SG', '$' ],
91 'SRD': ['$SR', '$'], 91 SRD: [ '$SR', '$' ],
92 'TOP': [u, '$T'], 92 TOP: [ u, '$T' ],
93 'TTD': ['$TT', '$'], 93 TTD: [ '$TT', '$' ],
94 'TWD': [u, 'NT$'], 94 TWD: [ u, 'NT$' ],
95 'USD': ['$US', '$'], 95 USD: [ '$US', '$' ],
96 'UYU': ['$UY', '$'], 96 UYU: [ '$UY', '$' ],
97 'WST': ['$WS'], 97 WST: [ '$WS' ],
98 'XCD': [u, '$'], 98 XCD: [ u, '$' ],
99 'XPF': ['FCFP'], 99 XPF: [ 'FCFP' ],
100 'ZMW': [u, 'Kw'] 100 ZMW: [ u, 'Kw' ]
101 }, 101 },
102 'ltr', 102 'ltr',
103 plural 103 plural
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts
index edcaf50e0..8636f3a55 100644
--- a/client/src/app/helpers/utils.ts
+++ b/client/src/app/helpers/utils.ts
@@ -10,7 +10,7 @@ import { AuthService } from '../core/auth'
10// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript 10// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
11function getParameterByName (name: string, url: string) { 11function getParameterByName (name: string, url: string) {
12 if (!url) url = window.location.href 12 if (!url) url = window.location.href
13 name = name.replace(/[\[\]]/g, '\\$&') 13 name = name.replace(/[[\]]/g, '\\$&')
14 14
15 const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)') 15 const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
16 const results = regex.exec(url) 16 const results = regex.exec(url)
@@ -110,10 +110,10 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) {
110 continue 110 continue
111 } 111 }
112 112
113 if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { 113 if (obj[key] !== null && typeof obj[key] === 'object' && !(obj[key] instanceof File)) {
114 objectToFormData(obj[ key ], fd, formKey) 114 objectToFormData(obj[key], fd, formKey)
115 } else { 115 } else {
116 fd.append(formKey, obj[ key ]) 116 fd.append(formKey, obj[key])
117 } 117 }
118 } 118 }
119 119
@@ -159,7 +159,7 @@ function scrollToTop (behavior: 'auto' | 'smooth' = 'auto') {
159function isInViewport (el: HTMLElement) { 159function isInViewport (el: HTMLElement) {
160 const bounding = el.getBoundingClientRect() 160 const bounding = el.getBoundingClientRect()
161 return ( 161 return (
162 bounding.top >= 0 && 162 bounding.top >= 0 &&
163 bounding.left >= 0 && 163 bounding.left >= 0 &&
164 bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 164 bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
165 bounding.right <= (window.innerWidth || document.documentElement.clientWidth) 165 bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
diff --git a/client/src/app/menu/language-chooser.component.ts b/client/src/app/menu/language-chooser.component.ts
index e15aeff20..b42e41855 100644
--- a/client/src/app/menu/language-chooser.component.ts
+++ b/client/src/app/menu/language-chooser.component.ts
@@ -18,7 +18,7 @@ export class LanguageChooserComponent {
18 @Inject(LOCALE_ID) private localeId: string 18 @Inject(LOCALE_ID) private localeId: string
19 ) { 19 ) {
20 const l = Object.keys(I18N_LOCALES) 20 const l = Object.keys(I18N_LOCALES)
21 .map(k => ({ id: k, label: I18N_LOCALES[k] , iso: getShortLocale(k) })) 21 .map(k => ({ id: k, label: I18N_LOCALES[k], iso: getShortLocale(k) }))
22 22
23 this.languages = sortBy(l, 'label') 23 this.languages = sortBy(l, 'label')
24 } 24 }
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index 627a8712f..97f07c956 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -28,7 +28,7 @@ const logger = debug('peertube:menu:MenuComponent')
28@Component({ 28@Component({
29 selector: 'my-menu', 29 selector: 'my-menu',
30 templateUrl: './menu.component.html', 30 templateUrl: './menu.component.html',
31 styleUrls: ['./menu.component.scss'] 31 styleUrls: [ './menu.component.scss' ]
32}) 32})
33export class MenuComponent implements OnInit { 33export class MenuComponent implements OnInit {
34 @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent 34 @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent
diff --git a/client/src/app/modal/custom-modal.component.ts b/client/src/app/modal/custom-modal.component.ts
index a98579085..559230e04 100644
--- a/client/src/app/modal/custom-modal.component.ts
+++ b/client/src/app/modal/custom-modal.component.ts
@@ -22,10 +22,10 @@ export class CustomModalComponent {
22 ) { } 22 ) { }
23 23
24 show (input: { 24 show (input: {
25 title: string, 25 title: string
26 content: string, 26 content: string
27 close?: boolean, 27 close?: boolean
28 cancel?: { value: string, action?: () => void }, 28 cancel?: { value: string, action?: () => void }
29 confirm?: { value: string, action?: () => void } 29 confirm?: { value: string, action?: () => void }
30 }) { 30 }) {
31 if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) { 31 if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) {
diff --git a/client/src/app/shared/form-validators/abuse-validators.ts b/client/src/app/shared/form-validators/abuse-validators.ts
index 75bfacf01..8d3c411b4 100644
--- a/client/src/app/shared/form-validators/abuse-validators.ts
+++ b/client/src/app/shared/form-validators/abuse-validators.ts
@@ -2,28 +2,28 @@ import { Validators } from '@angular/forms'
2import { BuildFormValidator } from './form-validator.model' 2import { BuildFormValidator } from './form-validator.model'
3 3
4export const ABUSE_REASON_VALIDATOR: BuildFormValidator = { 4export const ABUSE_REASON_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [Validators.required, Validators.minLength(2), Validators.maxLength(3000)], 5 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
6 MESSAGES: { 6 MESSAGES: {
7 'required': $localize`Report reason is required.`, 7 required: $localize`Report reason is required.`,
8 'minlength': $localize`Report reason must be at least 2 characters long.`, 8 minlength: $localize`Report reason must be at least 2 characters long.`,
9 'maxlength': $localize`Report reason cannot be more than 3000 characters long.` 9 maxlength: $localize`Report reason cannot be more than 3000 characters long.`
10 } 10 }
11} 11}
12 12
13export const ABUSE_MODERATION_COMMENT_VALIDATOR: BuildFormValidator = { 13export const ABUSE_MODERATION_COMMENT_VALIDATOR: BuildFormValidator = {
14 VALIDATORS: [Validators.required, Validators.minLength(2), Validators.maxLength(3000)], 14 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
15 MESSAGES: { 15 MESSAGES: {
16 'required': $localize`Moderation comment is required.`, 16 required: $localize`Moderation comment is required.`,
17 'minlength': $localize`Moderation comment must be at least 2 characters long.`, 17 minlength: $localize`Moderation comment must be at least 2 characters long.`,
18 'maxlength': $localize`Moderation comment cannot be more than 3000 characters long.` 18 maxlength: $localize`Moderation comment cannot be more than 3000 characters long.`
19 } 19 }
20} 20}
21 21
22export const ABUSE_MESSAGE_VALIDATOR: BuildFormValidator = { 22export const ABUSE_MESSAGE_VALIDATOR: BuildFormValidator = {
23 VALIDATORS: [Validators.required, Validators.minLength(2), Validators.maxLength(3000)], 23 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
24 MESSAGES: { 24 MESSAGES: {
25 'required': $localize`Abuse message is required.`, 25 required: $localize`Abuse message is required.`,
26 'minlength': $localize`Abuse message must be at least 2 characters long.`, 26 minlength: $localize`Abuse message must be at least 2 characters long.`,
27 'maxlength': $localize`Abuse message cannot be more than 3000 characters long.` 27 maxlength: $localize`Abuse message cannot be more than 3000 characters long.`
28 } 28 }
29} 29}
diff --git a/client/src/app/shared/form-validators/custom-config-validators.ts b/client/src/app/shared/form-validators/custom-config-validators.ts
index 1ed5700ff..fbf423d08 100644
--- a/client/src/app/shared/form-validators/custom-config-validators.ts
+++ b/client/src/app/shared/form-validators/custom-config-validators.ts
@@ -2,120 +2,120 @@ import { Validators } from '@angular/forms'
2import { BuildFormValidator } from './form-validator.model' 2import { BuildFormValidator } from './form-validator.model'
3 3
4export const INSTANCE_NAME_VALIDATOR: BuildFormValidator = { 4export const INSTANCE_NAME_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [Validators.required], 5 VALIDATORS: [ Validators.required ],
6 MESSAGES: { 6 MESSAGES: {
7 'required': $localize`Instance name is required.` 7 required: $localize`Instance name is required.`
8 } 8 }
9} 9}
10 10
11export const INSTANCE_SHORT_DESCRIPTION_VALIDATOR: BuildFormValidator = { 11export const INSTANCE_SHORT_DESCRIPTION_VALIDATOR: BuildFormValidator = {
12 VALIDATORS: [Validators.max(250)], 12 VALIDATORS: [ Validators.max(250) ],
13 MESSAGES: { 13 MESSAGES: {
14 'max': $localize`Short description should not be longer than 250 characters.` 14 max: $localize`Short description should not be longer than 250 characters.`
15 } 15 }
16} 16}
17 17
18export const SERVICES_TWITTER_USERNAME_VALIDATOR: BuildFormValidator = { 18export const SERVICES_TWITTER_USERNAME_VALIDATOR: BuildFormValidator = {
19 VALIDATORS: [Validators.required], 19 VALIDATORS: [ Validators.required ],
20 MESSAGES: { 20 MESSAGES: {
21 'required': $localize`Twitter username is required.` 21 required: $localize`Twitter username is required.`
22 } 22 }
23} 23}
24 24
25export const CACHE_PREVIEWS_SIZE_VALIDATOR: BuildFormValidator = { 25export const CACHE_PREVIEWS_SIZE_VALIDATOR: BuildFormValidator = {
26 VALIDATORS: [Validators.required, Validators.min(1), Validators.pattern('[0-9]+')], 26 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
27 MESSAGES: { 27 MESSAGES: {
28 'required': $localize`Previews cache size is required.`, 28 required: $localize`Previews cache size is required.`,
29 'min': $localize`Previews cache size must be greater than 1.`, 29 min: $localize`Previews cache size must be greater than 1.`,
30 'pattern': $localize`Previews cache size must be a number.` 30 pattern: $localize`Previews cache size must be a number.`
31 } 31 }
32} 32}
33 33
34export const CACHE_CAPTIONS_SIZE_VALIDATOR: BuildFormValidator = { 34export const CACHE_CAPTIONS_SIZE_VALIDATOR: BuildFormValidator = {
35 VALIDATORS: [Validators.required, Validators.min(1), Validators.pattern('[0-9]+')], 35 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
36 MESSAGES: { 36 MESSAGES: {
37 'required': $localize`Captions cache size is required.`, 37 required: $localize`Captions cache size is required.`,
38 'min': $localize`Captions cache size must be greater than 1.`, 38 min: $localize`Captions cache size must be greater than 1.`,
39 'pattern': $localize`Captions cache size must be a number.` 39 pattern: $localize`Captions cache size must be a number.`
40 } 40 }
41} 41}
42 42
43export const SIGNUP_LIMIT_VALIDATOR: BuildFormValidator = { 43export const SIGNUP_LIMIT_VALIDATOR: BuildFormValidator = {
44 VALIDATORS: [Validators.required, Validators.min(-1), Validators.pattern('-?[0-9]+')], 44 VALIDATORS: [ Validators.required, Validators.min(-1), Validators.pattern('-?[0-9]+') ],
45 MESSAGES: { 45 MESSAGES: {
46 'required': $localize`Signup limit is required.`, 46 required: $localize`Signup limit is required.`,
47 'min': $localize`Signup limit must be greater than 1. Use -1 to disable it.`, 47 min: $localize`Signup limit must be greater than 1. Use -1 to disable it.`,
48 'pattern': $localize`Signup limit must be a number.` 48 pattern: $localize`Signup limit must be a number.`
49 } 49 }
50} 50}
51 51
52export const SIGNUP_MINIMUM_AGE_VALIDATOR: BuildFormValidator = { 52export const SIGNUP_MINIMUM_AGE_VALIDATOR: BuildFormValidator = {
53 VALIDATORS: [Validators.required, Validators.min(1), Validators.pattern('[0-9]+')], 53 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
54 MESSAGES: { 54 MESSAGES: {
55 'required': $localize`Signup minimum age is required.`, 55 required: $localize`Signup minimum age is required.`,
56 'min': $localize`Signup minimum age must be greater than 1.`, 56 min: $localize`Signup minimum age must be greater than 1.`,
57 'pattern': $localize`Signup minimum age must be a number.` 57 pattern: $localize`Signup minimum age must be a number.`
58 } 58 }
59} 59}
60 60
61export const ADMIN_EMAIL_VALIDATOR: BuildFormValidator = { 61export const ADMIN_EMAIL_VALIDATOR: BuildFormValidator = {
62 VALIDATORS: [Validators.required, Validators.email], 62 VALIDATORS: [ Validators.required, Validators.email ],
63 MESSAGES: { 63 MESSAGES: {
64 'required': $localize`Admin email is required.`, 64 required: $localize`Admin email is required.`,
65 'email': $localize`Admin email must be valid.` 65 email: $localize`Admin email must be valid.`
66 } 66 }
67} 67}
68 68
69export const TRANSCODING_THREADS_VALIDATOR: BuildFormValidator = { 69export const TRANSCODING_THREADS_VALIDATOR: BuildFormValidator = {
70 VALIDATORS: [Validators.required, Validators.min(0)], 70 VALIDATORS: [ Validators.required, Validators.min(0) ],
71 MESSAGES: { 71 MESSAGES: {
72 'required': $localize`Transcoding threads is required.`, 72 required: $localize`Transcoding threads is required.`,
73 'min': $localize`Transcoding threads must be greater or equal to 0.` 73 min: $localize`Transcoding threads must be greater or equal to 0.`
74 } 74 }
75} 75}
76 76
77export const MAX_LIVE_DURATION_VALIDATOR: BuildFormValidator = { 77export const MAX_LIVE_DURATION_VALIDATOR: BuildFormValidator = {
78 VALIDATORS: [Validators.required, Validators.min(-1)], 78 VALIDATORS: [ Validators.required, Validators.min(-1) ],
79 MESSAGES: { 79 MESSAGES: {
80 'required': $localize`Max live duration is required.`, 80 required: $localize`Max live duration is required.`,
81 'min': $localize`Max live duration should be greater or equal to -1.` 81 min: $localize`Max live duration should be greater or equal to -1.`
82 } 82 }
83} 83}
84 84
85export const MAX_INSTANCE_LIVES_VALIDATOR: BuildFormValidator = { 85export const MAX_INSTANCE_LIVES_VALIDATOR: BuildFormValidator = {
86 VALIDATORS: [Validators.required, Validators.min(-1)], 86 VALIDATORS: [ Validators.required, Validators.min(-1) ],
87 MESSAGES: { 87 MESSAGES: {
88 'required': $localize`Max instance lives is required.`, 88 required: $localize`Max instance lives is required.`,
89 'min': $localize`Max instance lives should be greater or equal to -1.` 89 min: $localize`Max instance lives should be greater or equal to -1.`
90 } 90 }
91} 91}
92 92
93export const MAX_USER_LIVES_VALIDATOR: BuildFormValidator = { 93export const MAX_USER_LIVES_VALIDATOR: BuildFormValidator = {
94 VALIDATORS: [Validators.required, Validators.min(-1)], 94 VALIDATORS: [ Validators.required, Validators.min(-1) ],
95 MESSAGES: { 95 MESSAGES: {
96 'required': $localize`Max user lives is required.`, 96 required: $localize`Max user lives is required.`,
97 'min': $localize`Max user lives should be greater or equal to -1.` 97 min: $localize`Max user lives should be greater or equal to -1.`
98 } 98 }
99} 99}
100 100
101export const CONCURRENCY_VALIDATOR: BuildFormValidator = { 101export const CONCURRENCY_VALIDATOR: BuildFormValidator = {
102 VALIDATORS: [Validators.required, Validators.min(1)], 102 VALIDATORS: [ Validators.required, Validators.min(1) ],
103 MESSAGES: { 103 MESSAGES: {
104 'required': $localize`Concurrency is required.`, 104 required: $localize`Concurrency is required.`,
105 'min': $localize`Concurrency should be greater or equal to 1.` 105 min: $localize`Concurrency should be greater or equal to 1.`
106 } 106 }
107} 107}
108 108
109export const INDEX_URL_VALIDATOR: BuildFormValidator = { 109export const INDEX_URL_VALIDATOR: BuildFormValidator = {
110 VALIDATORS: [Validators.pattern(/^https:\/\//)], 110 VALIDATORS: [ Validators.pattern(/^https:\/\//) ],
111 MESSAGES: { 111 MESSAGES: {
112 'pattern': $localize`Index URL should be a URL` 112 pattern: $localize`Index URL should be a URL`
113 } 113 }
114} 114}
115 115
116export const SEARCH_INDEX_URL_VALIDATOR: BuildFormValidator = { 116export const SEARCH_INDEX_URL_VALIDATOR: BuildFormValidator = {
117 VALIDATORS: [Validators.pattern(/^https?:\/\//)], 117 VALIDATORS: [ Validators.pattern(/^https?:\/\//) ],
118 MESSAGES: { 118 MESSAGES: {
119 'pattern': $localize`Search index URL should be a URL` 119 pattern: $localize`Search index URL should be a URL`
120 } 120 }
121} 121}
diff --git a/client/src/app/shared/form-validators/form-validator.model.ts b/client/src/app/shared/form-validators/form-validator.model.ts
index 07b1ea075..6f2472ccd 100644
--- a/client/src/app/shared/form-validators/form-validator.model.ts
+++ b/client/src/app/shared/form-validators/form-validator.model.ts
@@ -1,7 +1,7 @@
1import { ValidatorFn } from '@angular/forms' 1import { ValidatorFn } from '@angular/forms'
2 2
3export type BuildFormValidator = { 3export type BuildFormValidator = {
4 VALIDATORS: ValidatorFn[], 4 VALIDATORS: ValidatorFn[]
5 MESSAGES: { [ name: string ]: string } 5 MESSAGES: { [ name: string ]: string }
6} 6}
7 7
diff --git a/client/src/app/shared/form-validators/host-validators.ts b/client/src/app/shared/form-validators/host-validators.ts
index 6f410a50a..3d9c476b5 100644
--- a/client/src/app/shared/form-validators/host-validators.ts
+++ b/client/src/app/shared/form-validators/host-validators.ts
@@ -4,7 +4,7 @@ import { BuildFormValidator } from './form-validator.model'
4export function validateHost (value: string) { 4export function validateHost (value: string) {
5 // Thanks to http://stackoverflow.com/a/106223 5 // Thanks to http://stackoverflow.com/a/106223
6 const HOST_REGEXP = new RegExp( 6 const HOST_REGEXP = new RegExp(
7 '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' 7 '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$'
8 ) 8 )
9 9
10 return HOST_REGEXP.test(value) 10 return HOST_REGEXP.test(value)
@@ -32,7 +32,7 @@ const validHosts: ValidatorFn = (control: AbstractControl) => {
32 if (errors.length === 0) return null 32 if (errors.length === 0) return null
33 33
34 return { 34 return {
35 'validHosts': { 35 validHosts: {
36 reason: 'invalid', 36 reason: 'invalid',
37 value: errors.join('. ') + '.' 37 value: errors.join('. ') + '.'
38 } 38 }
@@ -55,7 +55,7 @@ const validHostsOrHandles: ValidatorFn = (control: AbstractControl) => {
55 if (errors.length === 0) return null 55 if (errors.length === 0) return null
56 56
57 return { 57 return {
58 'validHostsOrHandles': { 58 validHostsOrHandles: {
59 reason: 'invalid', 59 reason: 'invalid',
60 value: errors.join('. ') + '.' 60 value: errors.join('. ') + '.'
61 } 61 }
@@ -80,7 +80,7 @@ export const unique: ValidatorFn = (control: AbstractControl) => {
80 } 80 }
81 81
82 return { 82 return {
83 'unique': { 83 unique: {
84 reason: 'invalid' 84 reason: 'invalid'
85 } 85 }
86 } 86 }
@@ -89,17 +89,17 @@ export const unique: ValidatorFn = (control: AbstractControl) => {
89export const UNIQUE_HOSTS_VALIDATOR: BuildFormValidator = { 89export const UNIQUE_HOSTS_VALIDATOR: BuildFormValidator = {
90 VALIDATORS: [ Validators.required, validHosts, unique ], 90 VALIDATORS: [ Validators.required, validHosts, unique ],
91 MESSAGES: { 91 MESSAGES: {
92 'required': $localize`Domain is required.`, 92 required: $localize`Domain is required.`,
93 'validHosts': $localize`Hosts entered are invalid.`, 93 validHosts: $localize`Hosts entered are invalid.`,
94 'unique': $localize`Hosts entered contain duplicates.` 94 unique: $localize`Hosts entered contain duplicates.`
95 } 95 }
96} 96}
97 97
98export const UNIQUE_HOSTS_OR_HANDLE_VALIDATOR: BuildFormValidator = { 98export const UNIQUE_HOSTS_OR_HANDLE_VALIDATOR: BuildFormValidator = {
99 VALIDATORS: [ Validators.required, validHostsOrHandles, unique ], 99 VALIDATORS: [ Validators.required, validHostsOrHandles, unique ],
100 MESSAGES: { 100 MESSAGES: {
101 'required': $localize`Domain is required.`, 101 required: $localize`Domain is required.`,
102 'validHostsOrHandles': $localize`Hosts or handles are invalid.`, 102 validHostsOrHandles: $localize`Hosts or handles are invalid.`,
103 'unique': $localize`Hosts or handles contain duplicates.` 103 unique: $localize`Hosts or handles contain duplicates.`
104 } 104 }
105} 105}
diff --git a/client/src/app/shared/form-validators/instance-validators.ts b/client/src/app/shared/form-validators/instance-validators.ts
index a72e28ba0..4b1f0d048 100644
--- a/client/src/app/shared/form-validators/instance-validators.ts
+++ b/client/src/app/shared/form-validators/instance-validators.ts
@@ -2,10 +2,10 @@ import { Validators } from '@angular/forms'
2import { BuildFormValidator } from './form-validator.model' 2import { BuildFormValidator } from './form-validator.model'
3 3
4export const FROM_EMAIL_VALIDATOR: BuildFormValidator = { 4export const FROM_EMAIL_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [Validators.required, Validators.email], 5 VALIDATORS: [ Validators.required, Validators.email ],
6 MESSAGES: { 6 MESSAGES: {
7 'required': $localize`Email is required.`, 7 required: $localize`Email is required.`,
8 'email': $localize`Email must be valid.` 8 email: $localize`Email must be valid.`
9 } 9 }
10} 10}
11 11
@@ -16,9 +16,9 @@ export const FROM_NAME_VALIDATOR: BuildFormValidator = {
16 Validators.maxLength(120) 16 Validators.maxLength(120)
17 ], 17 ],
18 MESSAGES: { 18 MESSAGES: {
19 'required': $localize`Your name is required.`, 19 required: $localize`Your name is required.`,
20 'minlength': $localize`Your name must be at least 1 character long.`, 20 minlength: $localize`Your name must be at least 1 character long.`,
21 'maxlength': $localize`Your name cannot be more than 120 characters long.` 21 maxlength: $localize`Your name cannot be more than 120 characters long.`
22 } 22 }
23} 23}
24 24
@@ -29,9 +29,9 @@ export const SUBJECT_VALIDATOR: BuildFormValidator = {
29 Validators.maxLength(120) 29 Validators.maxLength(120)
30 ], 30 ],
31 MESSAGES: { 31 MESSAGES: {
32 'required': $localize`A subject is required.`, 32 required: $localize`A subject is required.`,
33 'minlength': $localize`The subject must be at least 1 character long.`, 33 minlength: $localize`The subject must be at least 1 character long.`,
34 'maxlength': $localize`The subject cannot be more than 120 characters long.` 34 maxlength: $localize`The subject cannot be more than 120 characters long.`
35 } 35 }
36} 36}
37 37
@@ -42,8 +42,8 @@ export const BODY_VALIDATOR: BuildFormValidator = {
42 Validators.maxLength(5000) 42 Validators.maxLength(5000)
43 ], 43 ],
44 MESSAGES: { 44 MESSAGES: {
45 'required': $localize`A message is required.`, 45 required: $localize`A message is required.`,
46 'minlength': $localize`The message must be at least 3 characters long.`, 46 minlength: $localize`The message must be at least 3 characters long.`,
47 'maxlength': $localize`The message cannot be more than 5000 characters long.` 47 maxlength: $localize`The message cannot be more than 5000 characters long.`
48 } 48 }
49} 49}
diff --git a/client/src/app/shared/form-validators/login-validators.ts b/client/src/app/shared/form-validators/login-validators.ts
index 1ceae1be3..5b911ac47 100644
--- a/client/src/app/shared/form-validators/login-validators.ts
+++ b/client/src/app/shared/form-validators/login-validators.ts
@@ -6,7 +6,7 @@ export const LOGIN_USERNAME_VALIDATOR: BuildFormValidator = {
6 Validators.required 6 Validators.required
7 ], 7 ],
8 MESSAGES: { 8 MESSAGES: {
9 'required': $localize`Username is required.` 9 required: $localize`Username is required.`
10 } 10 }
11} 11}
12 12
@@ -15,6 +15,6 @@ export const LOGIN_PASSWORD_VALIDATOR: BuildFormValidator = {
15 Validators.required 15 Validators.required
16 ], 16 ],
17 MESSAGES: { 17 MESSAGES: {
18 'required': $localize`Password is required.` 18 required: $localize`Password is required.`
19 } 19 }
20} 20}
diff --git a/client/src/app/shared/form-validators/reset-password-validators.ts b/client/src/app/shared/form-validators/reset-password-validators.ts
index b87f2eab9..70617a562 100644
--- a/client/src/app/shared/form-validators/reset-password-validators.ts
+++ b/client/src/app/shared/form-validators/reset-password-validators.ts
@@ -6,6 +6,6 @@ export const RESET_PASSWORD_CONFIRM_VALIDATOR: BuildFormValidator = {
6 Validators.required 6 Validators.required
7 ], 7 ],
8 MESSAGES: { 8 MESSAGES: {
9 'required': $localize`Confirmation of the password is required.` 9 required: $localize`Confirmation of the password is required.`
10 } 10 }
11} 11}
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts
index 976c97b87..6d0dea64e 100644
--- a/client/src/app/shared/form-validators/user-validators.ts
+++ b/client/src/app/shared/form-validators/user-validators.ts
@@ -11,10 +11,10 @@ export const USER_USERNAME_VALIDATOR: BuildFormValidator = {
11 Validators.pattern(new RegExp(`^${USER_USERNAME_REGEX_CHARACTERS}*$`)) 11 Validators.pattern(new RegExp(`^${USER_USERNAME_REGEX_CHARACTERS}*$`))
12 ], 12 ],
13 MESSAGES: { 13 MESSAGES: {
14 'required': $localize`Username is required.`, 14 required: $localize`Username is required.`,
15 'minlength': $localize`Username must be at least 1 character long.`, 15 minlength: $localize`Username must be at least 1 character long.`,
16 'maxlength': $localize`Username cannot be more than 50 characters long.`, 16 maxlength: $localize`Username cannot be more than 50 characters long.`,
17 'pattern': $localize`Username should be lowercase alphanumeric; dots and underscores are allowed.` 17 pattern: $localize`Username should be lowercase alphanumeric; dots and underscores are allowed.`
18 } 18 }
19} 19}
20 20
@@ -26,18 +26,18 @@ export const USER_CHANNEL_NAME_VALIDATOR: BuildFormValidator = {
26 Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) 26 Validators.pattern(/^[a-z0-9][a-z0-9._]*$/)
27 ], 27 ],
28 MESSAGES: { 28 MESSAGES: {
29 'required': $localize`Channel name is required.`, 29 required: $localize`Channel name is required.`,
30 'minlength': $localize`Channel name must be at least 1 character long.`, 30 minlength: $localize`Channel name must be at least 1 character long.`,
31 'maxlength': $localize`Channel name cannot be more than 50 characters long.`, 31 maxlength: $localize`Channel name cannot be more than 50 characters long.`,
32 'pattern': $localize`Channel name should be lowercase, and can contain only alphanumeric characters, dots and underscores.` 32 pattern: $localize`Channel name should be lowercase, and can contain only alphanumeric characters, dots and underscores.`
33 } 33 }
34} 34}
35 35
36export const USER_EMAIL_VALIDATOR: BuildFormValidator = { 36export const USER_EMAIL_VALIDATOR: BuildFormValidator = {
37 VALIDATORS: [ Validators.required, Validators.email ], 37 VALIDATORS: [ Validators.required, Validators.email ],
38 MESSAGES: { 38 MESSAGES: {
39 'required': $localize`Email is required.`, 39 required: $localize`Email is required.`,
40 'email': $localize`Email must be valid.` 40 email: $localize`Email must be valid.`
41 } 41 }
42} 42}
43 43
@@ -47,8 +47,8 @@ export const USER_HANDLE_VALIDATOR: BuildFormValidator = {
47 Validators.pattern(/@.+/) 47 Validators.pattern(/@.+/)
48 ], 48 ],
49 MESSAGES: { 49 MESSAGES: {
50 'required': $localize`Handle is required.`, 50 required: $localize`Handle is required.`,
51 'pattern': $localize`Handle must be valid (eg. chocobozzz@example.com).` 51 pattern: $localize`Handle must be valid (eg. chocobozzz@example.com).`
52 } 52 }
53} 53}
54 54
@@ -57,7 +57,7 @@ export const USER_EXISTING_PASSWORD_VALIDATOR: BuildFormValidator = {
57 Validators.required 57 Validators.required
58 ], 58 ],
59 MESSAGES: { 59 MESSAGES: {
60 'required': $localize`Password is required.` 60 required: $localize`Password is required.`
61 } 61 }
62} 62}
63 63
@@ -68,9 +68,9 @@ export const USER_PASSWORD_VALIDATOR: BuildFormValidator = {
68 Validators.maxLength(255) 68 Validators.maxLength(255)
69 ], 69 ],
70 MESSAGES: { 70 MESSAGES: {
71 'required': $localize`Password is required.`, 71 required: $localize`Password is required.`,
72 'minlength': $localize`Password must be at least 6 characters long.`, 72 minlength: $localize`Password must be at least 6 characters long.`,
73 'maxlength': $localize`Password cannot be more than 255 characters long.` 73 maxlength: $localize`Password cannot be more than 255 characters long.`
74 } 74 }
75} 75}
76 76
@@ -80,37 +80,37 @@ export const USER_PASSWORD_OPTIONAL_VALIDATOR: BuildFormValidator = {
80 Validators.maxLength(255) 80 Validators.maxLength(255)
81 ], 81 ],
82 MESSAGES: { 82 MESSAGES: {
83 'minlength': $localize`Password must be at least 6 characters long.`, 83 minlength: $localize`Password must be at least 6 characters long.`,
84 'maxlength': $localize`Password cannot be more than 255 characters long.` 84 maxlength: $localize`Password cannot be more than 255 characters long.`
85 } 85 }
86} 86}
87 87
88export const USER_CONFIRM_PASSWORD_VALIDATOR: BuildFormValidator = { 88export const USER_CONFIRM_PASSWORD_VALIDATOR: BuildFormValidator = {
89 VALIDATORS: [], 89 VALIDATORS: [],
90 MESSAGES: { 90 MESSAGES: {
91 'matchPassword': $localize`The new password and the confirmed password do not correspond.` 91 matchPassword: $localize`The new password and the confirmed password do not correspond.`
92 } 92 }
93} 93}
94 94
95export const USER_VIDEO_QUOTA_VALIDATOR: BuildFormValidator = { 95export const USER_VIDEO_QUOTA_VALIDATOR: BuildFormValidator = {
96 VALIDATORS: [ Validators.required, Validators.min(-1) ], 96 VALIDATORS: [ Validators.required, Validators.min(-1) ],
97 MESSAGES: { 97 MESSAGES: {
98 'required': $localize`Video quota is required.`, 98 required: $localize`Video quota is required.`,
99 'min': $localize`Quota must be greater than -1.` 99 min: $localize`Quota must be greater than -1.`
100 } 100 }
101} 101}
102export const USER_VIDEO_QUOTA_DAILY_VALIDATOR: BuildFormValidator = { 102export const USER_VIDEO_QUOTA_DAILY_VALIDATOR: BuildFormValidator = {
103 VALIDATORS: [ Validators.required, Validators.min(-1) ], 103 VALIDATORS: [ Validators.required, Validators.min(-1) ],
104 MESSAGES: { 104 MESSAGES: {
105 'required': $localize`Daily upload limit is required.`, 105 required: $localize`Daily upload limit is required.`,
106 'min': $localize`Daily upload limit must be greater than -1.` 106 min: $localize`Daily upload limit must be greater than -1.`
107 } 107 }
108} 108}
109 109
110export const USER_ROLE_VALIDATOR: BuildFormValidator = { 110export const USER_ROLE_VALIDATOR: BuildFormValidator = {
111 VALIDATORS: [ Validators.required ], 111 VALIDATORS: [ Validators.required ],
112 MESSAGES: { 112 MESSAGES: {
113 'required': $localize`User role is required.` 113 required: $localize`User role is required.`
114 } 114 }
115} 115}
116 116
@@ -122,15 +122,15 @@ export const USER_DESCRIPTION_VALIDATOR: BuildFormValidator = {
122 Validators.maxLength(1000) 122 Validators.maxLength(1000)
123 ], 123 ],
124 MESSAGES: { 124 MESSAGES: {
125 'minlength': $localize`Description must be at least 3 characters long.`, 125 minlength: $localize`Description must be at least 3 characters long.`,
126 'maxlength': $localize`Description cannot be more than 1000 characters long.` 126 maxlength: $localize`Description cannot be more than 1000 characters long.`
127 } 127 }
128} 128}
129 129
130export const USER_TERMS_VALIDATOR: BuildFormValidator = { 130export const USER_TERMS_VALIDATOR: BuildFormValidator = {
131 VALIDATORS: [ Validators.requiredTrue ], 131 VALIDATORS: [ Validators.requiredTrue ],
132 MESSAGES: { 132 MESSAGES: {
133 'required': $localize`You must agree with the instance terms in order to register on it.` 133 required: $localize`You must agree with the instance terms in order to register on it.`
134 } 134 }
135} 135}
136 136
@@ -140,8 +140,8 @@ export const USER_BAN_REASON_VALIDATOR: BuildFormValidator = {
140 Validators.maxLength(250) 140 Validators.maxLength(250)
141 ], 141 ],
142 MESSAGES: { 142 MESSAGES: {
143 'minlength': $localize`Ban reason must be at least 3 characters long.`, 143 minlength: $localize`Ban reason must be at least 3 characters long.`,
144 'maxlength': $localize`Ban reason cannot be more than 250 characters long.` 144 maxlength: $localize`Ban reason cannot be more than 250 characters long.`
145 } 145 }
146} 146}
147 147
@@ -152,9 +152,9 @@ function buildDisplayNameValidator (required: boolean) {
152 Validators.maxLength(120) 152 Validators.maxLength(120)
153 ], 153 ],
154 MESSAGES: { 154 MESSAGES: {
155 'required': $localize`Display name is required.`, 155 required: $localize`Display name is required.`,
156 'minlength': $localize`Display name must be at least 1 character long.`, 156 minlength: $localize`Display name must be at least 1 character long.`,
157 'maxlength': $localize`Display name cannot be more than 50 characters long.` 157 maxlength: $localize`Display name cannot be more than 50 characters long.`
158 } 158 }
159 } 159 }
160 160
diff --git a/client/src/app/shared/form-validators/video-block-validators.ts b/client/src/app/shared/form-validators/video-block-validators.ts
index d3974aefe..cd2791b76 100644
--- a/client/src/app/shared/form-validators/video-block-validators.ts
+++ b/client/src/app/shared/form-validators/video-block-validators.ts
@@ -4,7 +4,7 @@ import { BuildFormValidator } from './form-validator.model'
4export const VIDEO_BLOCK_REASON_VALIDATOR: BuildFormValidator = { 4export const VIDEO_BLOCK_REASON_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ], 5 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ],
6 MESSAGES: { 6 MESSAGES: {
7 'minlength': $localize`Block reason must be at least 2 characters long.`, 7 minlength: $localize`Block reason must be at least 2 characters long.`,
8 'maxlength': $localize`Block reason cannot be more than 300 characters long.` 8 maxlength: $localize`Block reason cannot be more than 300 characters long.`
9 } 9 }
10} 10}
diff --git a/client/src/app/shared/form-validators/video-captions-validators.ts b/client/src/app/shared/form-validators/video-captions-validators.ts
index 9742d2925..a16216422 100644
--- a/client/src/app/shared/form-validators/video-captions-validators.ts
+++ b/client/src/app/shared/form-validators/video-captions-validators.ts
@@ -4,13 +4,13 @@ import { BuildFormValidator } from './form-validator.model'
4export const VIDEO_CAPTION_LANGUAGE_VALIDATOR: BuildFormValidator = { 4export const VIDEO_CAPTION_LANGUAGE_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [ Validators.required ], 5 VALIDATORS: [ Validators.required ],
6 MESSAGES: { 6 MESSAGES: {
7 'required': $localize`Video caption language is required.` 7 required: $localize`Video caption language is required.`
8 } 8 }
9} 9}
10 10
11export const VIDEO_CAPTION_FILE_VALIDATOR: BuildFormValidator = { 11export const VIDEO_CAPTION_FILE_VALIDATOR: BuildFormValidator = {
12 VALIDATORS: [ Validators.required ], 12 VALIDATORS: [ Validators.required ],
13 MESSAGES: { 13 MESSAGES: {
14 'required': $localize`Video caption file is required.` 14 required: $localize`Video caption file is required.`
15 } 15 }
16} 16}
diff --git a/client/src/app/shared/form-validators/video-channel-validators.ts b/client/src/app/shared/form-validators/video-channel-validators.ts
index ba502ed01..48f5b1a2c 100644
--- a/client/src/app/shared/form-validators/video-channel-validators.ts
+++ b/client/src/app/shared/form-validators/video-channel-validators.ts
@@ -7,10 +7,10 @@ export const VIDEO_CHANNEL_NAME_VALIDATOR: BuildFormValidator = {
7 VALIDATORS: USER_USERNAME_VALIDATOR.VALIDATORS, 7 VALIDATORS: USER_USERNAME_VALIDATOR.VALIDATORS,
8 8
9 MESSAGES: { 9 MESSAGES: {
10 'required': $localize`Name is required.`, 10 required: $localize`Name is required.`,
11 'minlength': $localize`Name must be at least 1 character long.`, 11 minlength: $localize`Name must be at least 1 character long.`,
12 'maxlength': $localize`Name cannot be more than 50 characters long.`, 12 maxlength: $localize`Name cannot be more than 50 characters long.`,
13 'pattern': $localize`Name should be lowercase alphanumeric; dots and underscores are allowed.` 13 pattern: $localize`Name should be lowercase alphanumeric; dots and underscores are allowed.`
14 } 14 }
15} 15}
16 16
@@ -21,9 +21,9 @@ export const VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR: BuildFormValidator = {
21 Validators.maxLength(50) 21 Validators.maxLength(50)
22 ], 22 ],
23 MESSAGES: { 23 MESSAGES: {
24 'required': $localize`Display name is required.`, 24 required: $localize`Display name is required.`,
25 'minlength': $localize`Display name must be at least 1 character long.`, 25 minlength: $localize`Display name must be at least 1 character long.`,
26 'maxlength': $localize`Display name cannot be more than 50 characters long.` 26 maxlength: $localize`Display name cannot be more than 50 characters long.`
27 } 27 }
28} 28}
29 29
@@ -33,8 +33,8 @@ export const VIDEO_CHANNEL_DESCRIPTION_VALIDATOR: BuildFormValidator = {
33 Validators.maxLength(1000) 33 Validators.maxLength(1000)
34 ], 34 ],
35 MESSAGES: { 35 MESSAGES: {
36 'minlength': $localize`Description must be at least 3 characters long.`, 36 minlength: $localize`Description must be at least 3 characters long.`,
37 'maxlength': $localize`Description cannot be more than 1000 characters long.` 37 maxlength: $localize`Description cannot be more than 1000 characters long.`
38 } 38 }
39} 39}
40 40
@@ -44,7 +44,7 @@ export const VIDEO_CHANNEL_SUPPORT_VALIDATOR: BuildFormValidator = {
44 Validators.maxLength(1000) 44 Validators.maxLength(1000)
45 ], 45 ],
46 MESSAGES: { 46 MESSAGES: {
47 'minlength': $localize`Support text must be at least 3 characters long.`, 47 minlength: $localize`Support text must be at least 3 characters long.`,
48 'maxlength': $localize`Support text cannot be more than 1000 characters long` 48 maxlength: $localize`Support text cannot be more than 1000 characters long`
49 } 49 }
50} 50}
diff --git a/client/src/app/shared/form-validators/video-comment-validators.ts b/client/src/app/shared/form-validators/video-comment-validators.ts
index c56564d34..9e8f95e7c 100644
--- a/client/src/app/shared/form-validators/video-comment-validators.ts
+++ b/client/src/app/shared/form-validators/video-comment-validators.ts
@@ -4,8 +4,8 @@ import { BuildFormValidator } from './form-validator.model'
4export const VIDEO_COMMENT_TEXT_VALIDATOR: BuildFormValidator = { 4export const VIDEO_COMMENT_TEXT_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [ Validators.required, Validators.minLength(1), Validators.maxLength(3000) ], 5 VALIDATORS: [ Validators.required, Validators.minLength(1), Validators.maxLength(3000) ],
6 MESSAGES: { 6 MESSAGES: {
7 'required': $localize`Comment is required.`, 7 required: $localize`Comment is required.`,
8 'minlength': $localize`Comment must be at least 2 characters long.`, 8 minlength: $localize`Comment must be at least 2 characters long.`,
9 'maxlength': $localize`Comment cannot be more than 3000 characters long.` 9 maxlength: $localize`Comment cannot be more than 3000 characters long.`
10 } 10 }
11} 11}
diff --git a/client/src/app/shared/form-validators/video-ownership-change-validators.ts b/client/src/app/shared/form-validators/video-ownership-change-validators.ts
index e1a2df8a6..3e7823c49 100644
--- a/client/src/app/shared/form-validators/video-ownership-change-validators.ts
+++ b/client/src/app/shared/form-validators/video-ownership-change-validators.ts
@@ -4,21 +4,21 @@ import { BuildFormValidator } from './form-validator.model'
4export const OWNERSHIP_CHANGE_CHANNEL_VALIDATOR: BuildFormValidator = { 4export const OWNERSHIP_CHANGE_CHANNEL_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [ Validators.required ], 5 VALIDATORS: [ Validators.required ],
6 MESSAGES: { 6 MESSAGES: {
7 'required': $localize`The channel is required.` 7 required: $localize`The channel is required.`
8 } 8 }
9} 9}
10 10
11export const OWNERSHIP_CHANGE_USERNAME_VALIDATOR: BuildFormValidator = { 11export const OWNERSHIP_CHANGE_USERNAME_VALIDATOR: BuildFormValidator = {
12 VALIDATORS: [ Validators.required, localAccountValidator ], 12 VALIDATORS: [ Validators.required, localAccountValidator ],
13 MESSAGES: { 13 MESSAGES: {
14 'required': $localize`The username is required.`, 14 required: $localize`The username is required.`,
15 'localAccountOnly': $localize`You can only transfer ownership to a local account` 15 localAccountOnly: $localize`You can only transfer ownership to a local account`
16 } 16 }
17} 17}
18 18
19function localAccountValidator (control: AbstractControl): ValidationErrors { 19function localAccountValidator (control: AbstractControl): ValidationErrors {
20 if (control.value.includes('@')) { 20 if (control.value.includes('@')) {
21 return { 'localAccountOnly': true } 21 return { localAccountOnly: true }
22 } 22 }
23 23
24 return null 24 return null
diff --git a/client/src/app/shared/form-validators/video-playlist-validators.ts b/client/src/app/shared/form-validators/video-playlist-validators.ts
index 7e3d29458..63af637a3 100644
--- a/client/src/app/shared/form-validators/video-playlist-validators.ts
+++ b/client/src/app/shared/form-validators/video-playlist-validators.ts
@@ -9,9 +9,9 @@ export const VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR: BuildFormValidator = {
9 Validators.maxLength(120) 9 Validators.maxLength(120)
10 ], 10 ],
11 MESSAGES: { 11 MESSAGES: {
12 'required': $localize`Display name is required.`, 12 required: $localize`Display name is required.`,
13 'minlength': $localize`Display name must be at least 1 character long.`, 13 minlength: $localize`Display name must be at least 1 character long.`,
14 'maxlength': $localize`Display name cannot be more than 120 characters long.` 14 maxlength: $localize`Display name cannot be more than 120 characters long.`
15 } 15 }
16} 16}
17 17
@@ -20,7 +20,7 @@ export const VIDEO_PLAYLIST_PRIVACY_VALIDATOR: BuildFormValidator = {
20 Validators.required 20 Validators.required
21 ], 21 ],
22 MESSAGES: { 22 MESSAGES: {
23 'required': $localize`Privacy is required.` 23 required: $localize`Privacy is required.`
24 } 24 }
25} 25}
26 26
@@ -30,21 +30,21 @@ export const VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR: BuildFormValidator = {
30 Validators.maxLength(1000) 30 Validators.maxLength(1000)
31 ], 31 ],
32 MESSAGES: { 32 MESSAGES: {
33 'minlength': $localize`Description must be at least 3 characters long.`, 33 minlength: $localize`Description must be at least 3 characters long.`,
34 'maxlength': $localize`Description cannot be more than 1000 characters long.` 34 maxlength: $localize`Description cannot be more than 1000 characters long.`
35 } 35 }
36} 36}
37 37
38export const VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR: BuildFormValidator = { 38export const VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR: BuildFormValidator = {
39 VALIDATORS: [], 39 VALIDATORS: [],
40 MESSAGES: { 40 MESSAGES: {
41 'required': $localize`The channel is required when the playlist is public.` 41 required: $localize`The channel is required when the playlist is public.`
42 } 42 }
43} 43}
44 44
45export function setPlaylistChannelValidator (channelControl: AbstractControl, privacy: VideoPlaylistPrivacy) { 45export function setPlaylistChannelValidator (channelControl: AbstractControl, privacy: VideoPlaylistPrivacy) {
46 if (privacy.toString() === VideoPlaylistPrivacy.PUBLIC.toString()) { 46 if (privacy.toString() === VideoPlaylistPrivacy.PUBLIC.toString()) {
47 channelControl.setValidators([Validators.required]) 47 channelControl.setValidators([ Validators.required ])
48 } else { 48 } else {
49 channelControl.setValidators(null) 49 channelControl.setValidators(null)
50 } 50 }
diff --git a/client/src/app/shared/form-validators/video-validators.ts b/client/src/app/shared/form-validators/video-validators.ts
index 1382a7747..f96189aa2 100644
--- a/client/src/app/shared/form-validators/video-validators.ts
+++ b/client/src/app/shared/form-validators/video-validators.ts
@@ -12,17 +12,17 @@ export const trimValidator: ValidatorFn = (control: FormControl) => {
12export const VIDEO_NAME_VALIDATOR: BuildFormValidator = { 12export const VIDEO_NAME_VALIDATOR: BuildFormValidator = {
13 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(120) ], 13 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(120) ],
14 MESSAGES: { 14 MESSAGES: {
15 'required': $localize`Video name is required.`, 15 required: $localize`Video name is required.`,
16 'minlength': $localize`Video name must be at least 3 characters long.`, 16 minlength: $localize`Video name must be at least 3 characters long.`,
17 'maxlength': $localize`Video name cannot be more than 120 characters long.`, 17 maxlength: $localize`Video name cannot be more than 120 characters long.`,
18 'spaces': $localize`Video name has leading or trailing whitespace.` 18 spaces: $localize`Video name has leading or trailing whitespace.`
19 } 19 }
20} 20}
21 21
22export const VIDEO_PRIVACY_VALIDATOR: BuildFormValidator = { 22export const VIDEO_PRIVACY_VALIDATOR: BuildFormValidator = {
23 VALIDATORS: [ Validators.required ], 23 VALIDATORS: [ Validators.required ],
24 MESSAGES: { 24 MESSAGES: {
25 'required': $localize`Video privacy is required.` 25 required: $localize`Video privacy is required.`
26 } 26 }
27} 27}
28 28
@@ -49,46 +49,46 @@ export const VIDEO_IMAGE_VALIDATOR: BuildFormValidator = {
49export const VIDEO_CHANNEL_VALIDATOR: BuildFormValidator = { 49export const VIDEO_CHANNEL_VALIDATOR: BuildFormValidator = {
50 VALIDATORS: [ Validators.required ], 50 VALIDATORS: [ Validators.required ],
51 MESSAGES: { 51 MESSAGES: {
52 'required': $localize`Video channel is required.` 52 required: $localize`Video channel is required.`
53 } 53 }
54} 54}
55 55
56export const VIDEO_DESCRIPTION_VALIDATOR: BuildFormValidator = { 56export const VIDEO_DESCRIPTION_VALIDATOR: BuildFormValidator = {
57 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ], 57 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ],
58 MESSAGES: { 58 MESSAGES: {
59 'minlength': $localize`Video description must be at least 3 characters long.`, 59 minlength: $localize`Video description must be at least 3 characters long.`,
60 'maxlength': $localize`Video description cannot be more than 10000 characters long.` 60 maxlength: $localize`Video description cannot be more than 10000 characters long.`
61 } 61 }
62} 62}
63 63
64export const VIDEO_TAG_VALIDATOR: BuildFormValidator = { 64export const VIDEO_TAG_VALIDATOR: BuildFormValidator = {
65 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(30) ], 65 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(30) ],
66 MESSAGES: { 66 MESSAGES: {
67 'minlength': $localize`A tag should be more than 2 characters long.`, 67 minlength: $localize`A tag should be more than 2 characters long.`,
68 'maxlength': $localize`A tag should be less than 30 characters long.` 68 maxlength: $localize`A tag should be less than 30 characters long.`
69 } 69 }
70} 70}
71 71
72export const VIDEO_TAGS_ARRAY_VALIDATOR: BuildFormValidator = { 72export const VIDEO_TAGS_ARRAY_VALIDATOR: BuildFormValidator = {
73 VALIDATORS: [ Validators.maxLength(5), arrayTagLengthValidator() ], 73 VALIDATORS: [ Validators.maxLength(5), arrayTagLengthValidator() ],
74 MESSAGES: { 74 MESSAGES: {
75 'maxlength': $localize`A maximum of 5 tags can be used on a video.`, 75 maxlength: $localize`A maximum of 5 tags can be used on a video.`,
76 'arrayTagLength': $localize`A tag should be more than 1 and less than 30 characters long.` 76 arrayTagLength: $localize`A tag should be more than 1 and less than 30 characters long.`
77 } 77 }
78} 78}
79 79
80export const VIDEO_SUPPORT_VALIDATOR: BuildFormValidator = { 80export const VIDEO_SUPPORT_VALIDATOR: BuildFormValidator = {
81 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(1000) ], 81 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(1000) ],
82 MESSAGES: { 82 MESSAGES: {
83 'minlength': $localize`Video support must be at least 3 characters long.`, 83 minlength: $localize`Video support must be at least 3 characters long.`,
84 'maxlength': $localize`Video support cannot be more than 1000 characters long.` 84 maxlength: $localize`Video support cannot be more than 1000 characters long.`
85 } 85 }
86} 86}
87 87
88export const VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR: BuildFormValidator = { 88export const VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR: BuildFormValidator = {
89 VALIDATORS: [ ], 89 VALIDATORS: [ ],
90 MESSAGES: { 90 MESSAGES: {
91 'required': $localize`A date is required to schedule video update.` 91 required: $localize`A date is required to schedule video update.`
92 } 92 }
93} 93}
94 94
@@ -105,6 +105,6 @@ function arrayTagLengthValidator (min = 2, max = 30): ValidatorFn {
105 return null 105 return null
106 } 106 }
107 107
108 return { 'arrayTagLength': true } 108 return { arrayTagLength: true }
109 } 109 }
110} 110}
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index e3bf9e1fb..33e9fd8de 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -39,23 +39,23 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
39 39
40 inputFilters: AdvancedInputFilter[] = [ 40 inputFilters: AdvancedInputFilter[] = [
41 { 41 {
42 queryParams: { 'search': 'state:pending' }, 42 queryParams: { search: 'state:pending' },
43 label: $localize`Unsolved reports` 43 label: $localize`Unsolved reports`
44 }, 44 },
45 { 45 {
46 queryParams: { 'search': 'state:accepted' }, 46 queryParams: { search: 'state:accepted' },
47 label: $localize`Accepted reports` 47 label: $localize`Accepted reports`
48 }, 48 },
49 { 49 {
50 queryParams: { 'search': 'state:rejected' }, 50 queryParams: { search: 'state:rejected' },
51 label: $localize`Refused reports` 51 label: $localize`Refused reports`
52 }, 52 },
53 { 53 {
54 queryParams: { 'search': 'videoIs:blacklisted' }, 54 queryParams: { search: 'videoIs:blacklisted' },
55 label: $localize`Reports with blocked videos` 55 label: $localize`Reports with blocked videos`
56 }, 56 },
57 { 57 {
58 queryParams: { 'search': 'videoIs:deleted' }, 58 queryParams: { search: 'videoIs:deleted' },
59 label: $localize`Reports with deleted videos` 59 label: $localize`Reports with deleted videos`
60 } 60 }
61 ] 61 ]
diff --git a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
index 06f1555ea..ccb0c5262 100644
--- a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
+++ b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
@@ -50,7 +50,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
50 } 50 }
51 51
52 async banUser () { 52 async banUser () {
53 const moderationComment: string = this.form.value[ 'moderationComment' ] 53 const moderationComment: string = this.form.value['moderationComment']
54 54
55 this.abuseService.updateAbuse(this.abuseToComment, { moderationComment }) 55 this.abuseService.updateAbuse(this.abuseToComment, { moderationComment })
56 .subscribe({ 56 .subscribe({
diff --git a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
index fce1a8db3..194d52a33 100644
--- a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
+++ b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
@@ -5,7 +5,7 @@ import { Account } from '@app/shared/shared-main'
5// Don't use an abuse model because we need external services to compute some properties 5// Don't use an abuse model because we need external services to compute some properties
6// And this model is only used in this component 6// And this model is only used in this component
7export type ProcessedAbuse = AdminAbuse & { 7export type ProcessedAbuse = AdminAbuse & {
8 moderationCommentHtml?: string, 8 moderationCommentHtml?: string
9 reasonHtml?: string 9 reasonHtml?: string
10 embedHtml?: SafeHtml 10 embedHtml?: SafeHtml
11 updatedAt?: Date 11 updatedAt?: Date
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
index dc9b72ddb..2c0e45e20 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
@@ -51,7 +51,7 @@ export class ActorAvatarEditComponent implements OnInit {
51 onAvatarChange (input: HTMLInputElement) { 51 onAvatarChange (input: HTMLInputElement) {
52 this.avatarfileInput = new ElementRef(input) 52 this.avatarfileInput = new ElementRef(input)
53 53
54 const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] 54 const avatarfile = this.avatarfileInput.nativeElement.files[0]
55 if (avatarfile.size > this.maxAvatarSize) { 55 if (avatarfile.size > this.maxAvatarSize) {
56 this.notifier.error('Error', $localize`This image is too large.`) 56 this.notifier.error('Error', $localize`This image is too large.`)
57 return 57 return
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts
index c3f10c055..cba2c5db3 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts
+++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts
@@ -40,14 +40,14 @@ export class ActorBannerEditComponent implements OnInit {
40 this.maxBannerSize = config.banner.file.size.max 40 this.maxBannerSize = config.banner.file.size.max
41 this.bannerExtensions = config.banner.file.extensions.join(', ') 41 this.bannerExtensions = config.banner.file.extensions.join(', ')
42 42
43 // tslint:disable:max-line-length 43 /* eslint-disable max-len */
44 this.bannerFormat = $localize`ratio 6/1, recommended size: 1920x317, max size: ${getBytes(this.maxBannerSize)}, extensions: ${this.bannerExtensions}` 44 this.bannerFormat = $localize`ratio 6/1, recommended size: 1920x317, max size: ${getBytes(this.maxBannerSize)}, extensions: ${this.bannerExtensions}`
45 } 45 }
46 46
47 onBannerChange (input: HTMLInputElement) { 47 onBannerChange (input: HTMLInputElement) {
48 this.bannerfileInput = new ElementRef(input) 48 this.bannerfileInput = new ElementRef(input)
49 49
50 const bannerfile = this.bannerfileInput.nativeElement.files[ 0 ] 50 const bannerfile = this.bannerfileInput.nativeElement.files[0]
51 if (bannerfile.size > this.maxBannerSize) { 51 if (bannerfile.size > this.maxBannerSize) {
52 this.notifier.error('Error', $localize`This image is too large.`) 52 this.notifier.error('Error', $localize`This image is too large.`)
53 return 53 return
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
index b06c2bae6..a4adfd1b7 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
@@ -17,6 +17,8 @@ export type ActorAvatarSize = '18' | '25' | '32' | '34' | '36' | '40' | '100' |
17 templateUrl: './actor-avatar.component.html' 17 templateUrl: './actor-avatar.component.html'
18}) 18})
19export class ActorAvatarComponent { 19export class ActorAvatarComponent {
20 private _title: string
21
20 @Input() account: ActorInput 22 @Input() account: ActorInput
21 @Input() channel: ActorInput 23 @Input() channel: ActorInput
22 24
@@ -33,8 +35,6 @@ export class ActorAvatarComponent {
33 this._title = value 35 this._title = value
34 } 36 }
35 37
36 private _title: string
37
38 get title () { 38 get title () {
39 if (this._title) return this._title 39 if (this._title) return this._title
40 if (this.account) return $localize`${this.account.name} (account page)` 40 if (this.account) return $localize`${this.account.name} (account page)`
@@ -50,22 +50,6 @@ export class ActorAvatarComponent {
50 return '' 50 return ''
51 } 51 }
52 52
53 getClass (type: 'avatar' | 'initial') {
54 const base = [ 'avatar' ]
55
56 if (this.size) base.push(`avatar-${this.size}`)
57
58 if (this.channel) base.push('channel')
59 else base.push('account')
60
61 if (type === 'initial' && this.initial) {
62 base.push('initial')
63 base.push(this.getColorTheme())
64 }
65
66 return base
67 }
68
69 get defaultAvatarUrl () { 53 get defaultAvatarUrl () {
70 if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL() 54 if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL()
71 55
@@ -86,6 +70,22 @@ export class ActorAvatarComponent {
86 return name.slice(0, 1) 70 return name.slice(0, 1)
87 } 71 }
88 72
73 getClass (type: 'avatar' | 'initial') {
74 const base = [ 'avatar' ]
75
76 if (this.size) base.push(`avatar-${this.size}`)
77
78 if (this.channel) base.push('channel')
79 else base.push('account')
80
81 if (type === 'initial' && this.initial) {
82 base.push('initial')
83 base.push(this.getColorTheme())
84 }
85
86 return base
87 }
88
89 hasActor () { 89 hasActor () {
90 return !!this.account || !!this.channel 90 return !!this.account || !!this.channel
91 } 91 }
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts
index 8c1357d7a..35b413b60 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts
@@ -39,9 +39,13 @@ export class ChannelMiniatureMarkupComponent implements CustomMarkupComponent, O
39 ngOnInit () { 39 ngOnInit () {
40 this.findInBulk.getChannel(this.name) 40 this.findInBulk.getChannel(this.name)
41 .pipe( 41 .pipe(
42 tap(channel => this.channel = channel), 42 tap(channel => {
43 this.channel = channel
44 }),
43 switchMap(() => from(this.markdown.textMarkdownToHTML(this.channel.description))), 45 switchMap(() => from(this.markdown.textMarkdownToHTML(this.channel.description))),
44 tap(html => this.descriptionHTML = html), 46 tap(html => {
47 this.descriptionHTML = html
48 }),
45 switchMap(() => this.loadVideosObservable()), 49 switchMap(() => this.loadVideosObservable()),
46 finalize(() => this.loaded.emit(true)) 50 finalize(() => this.loaded.emit(true))
47 ).subscribe({ 51 ).subscribe({
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts
index 56b43d85e..7315126e0 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts
@@ -1,10 +1,10 @@
1import { finalize } from 'rxjs/operators' 1import { finalize } from 'rxjs/operators'
2import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 2import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
3import { AuthService, Notifier } from '@app/core' 3import { AuthService, Notifier } from '@app/core'
4import { Video, VideoService } from '../../shared-main' 4import { FindInBulkService } from '@app/shared/shared-search'
5import { Video } from '../../shared-main'
5import { MiniatureDisplayOptions } from '../../shared-video-miniature' 6import { MiniatureDisplayOptions } from '../../shared-video-miniature'
6import { CustomMarkupComponent } from './shared' 7import { CustomMarkupComponent } from './shared'
7import { FindInBulkService } from '@app/shared/shared-search'
8 8
9/* 9/*
10 * Markup component that creates a video miniature only 10 * Markup component that creates a video miniature only
diff --git a/client/src/app/shared/shared-forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts
index adf6cb894..f2ce82360 100644
--- a/client/src/app/shared/shared-forms/form-reactive.ts
+++ b/client/src/app/shared/shared-forms/form-reactive.ts
@@ -51,7 +51,7 @@ export abstract class FormReactive {
51 } 51 }
52 52
53 // clear previous error message (if any) 53 // clear previous error message (if any)
54 formErrors[ field ] = '' 54 formErrors[field] = ''
55 const control = form.get(field) 55 const control = form.get(field)
56 56
57 if (control.dirty) this.formChanged = true 57 if (control.dirty) this.formChanged = true
@@ -59,9 +59,9 @@ export abstract class FormReactive {
59 // Don't care if dirty on force check 59 // Don't care if dirty on force check
60 const isDirty = control.dirty || forceCheck === true 60 const isDirty = control.dirty || forceCheck === true
61 if (control && isDirty && control.enabled && !control.valid) { 61 if (control && isDirty && control.enabled && !control.valid) {
62 const messages = validationMessages[ field ] 62 const messages = validationMessages[field]
63 for (const key of Object.keys(control.errors)) { 63 for (const key of Object.keys(control.errors)) {
64 formErrors[ field ] += messages[ key ] + ' ' 64 formErrors[field] += messages[key] + ' '
65 } 65 }
66 } 66 }
67 } 67 }
diff --git a/client/src/app/shared/shared-forms/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validator.service.ts
index 41c8b76bd..c0664de5f 100644
--- a/client/src/app/shared/shared-forms/form-validator.service.ts
+++ b/client/src/app/shared/shared-forms/form-validator.service.ts
@@ -28,11 +28,11 @@ export class FormValidatorService {
28 continue 28 continue
29 } 29 }
30 30
31 if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } 31 if (field?.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string }
32 32
33 const defaultValue = defaultValues[name] || '' 33 const defaultValue = defaultValues[name] || ''
34 34
35 if (field && field.VALIDATORS) group[name] = [ defaultValue, field.VALIDATORS ] 35 if (field?.VALIDATORS) group[name] = [ defaultValue, field.VALIDATORS ]
36 else group[name] = [ defaultValue ] 36 else group[name] = [ defaultValue ]
37 } 37 }
38 38
@@ -62,11 +62,11 @@ export class FormValidatorService {
62 continue 62 continue
63 } 63 }
64 64
65 if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } 65 if (field?.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string }
66 66
67 const defaultValue = defaultValues[name] || '' 67 const defaultValue = defaultValues[name] || ''
68 68
69 if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS as ValidatorFn[])) 69 if (field?.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS as ValidatorFn[]))
70 else form.addControl(name, new FormControl(defaultValue)) 70 else form.addControl(name, new FormControl(defaultValue))
71 } 71 }
72 } 72 }
diff --git a/client/src/app/shared/shared-forms/reactive-file.component.ts b/client/src/app/shared/shared-forms/reactive-file.component.ts
index fac3dfb3a..9d27ad07a 100644
--- a/client/src/app/shared/shared-forms/reactive-file.component.ts
+++ b/client/src/app/shared/shared-forms/reactive-file.component.ts
@@ -43,7 +43,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
43 } 43 }
44 44
45 fileChange (event: any) { 45 fileChange (event: any) {
46 if (event.target.files && event.target.files.length) { 46 if (event.target.files?.length) {
47 const [ file ] = event.target.files 47 const [ file ] = event.target.files
48 48
49 if (file.size > this.maxFileSize) { 49 if (file.size > this.maxFileSize) {
diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts
index a47f07fc3..cb5f31c8e 100644
--- a/client/src/app/shared/shared-icons/global-icon.component.ts
+++ b/client/src/app/shared/shared-icons/global-icon.component.ts
@@ -3,77 +3,77 @@ import { HooksService } from '@app/core/plugins/hooks.service'
3 3
4const icons = { 4const icons = {
5 // misc icons 5 // misc icons
6 'npm': require('!!raw-loader?!../../../assets/images/misc/npm.svg').default, 6 npm: require('!!raw-loader?!../../../assets/images/misc/npm.svg').default,
7 'markdown': require('!!raw-loader?!../../../assets/images/misc/markdown.svg').default, 7 markdown: require('!!raw-loader?!../../../assets/images/misc/markdown.svg').default,
8 'language': require('!!raw-loader?!../../../assets/images/misc/language.svg').default, 8 language: require('!!raw-loader?!../../../assets/images/misc/language.svg').default,
9 'video-lang': require('!!raw-loader?!../../../assets/images/misc/video-lang.svg').default, 9 'video-lang': require('!!raw-loader?!../../../assets/images/misc/video-lang.svg').default,
10 'support': require('!!raw-loader?!../../../assets/images/misc/support.svg').default, 10 support: require('!!raw-loader?!../../../assets/images/misc/support.svg').default,
11 'peertube-x': require('!!raw-loader?!../../../assets/images/misc/peertube-x.svg').default, 11 'peertube-x': require('!!raw-loader?!../../../assets/images/misc/peertube-x.svg').default,
12 'robot': require('!!raw-loader?!../../../assets/images/misc/miscellaneous-services.svg').default, // material ui 12 robot: require('!!raw-loader?!../../../assets/images/misc/miscellaneous-services.svg').default, // material ui
13 'videos': require('!!raw-loader?!../../../assets/images/misc/video-library.svg').default, // material ui 13 videos: require('!!raw-loader?!../../../assets/images/misc/video-library.svg').default, // material ui
14 'history': require('!!raw-loader?!../../../assets/images/misc/history.svg').default, // material ui 14 history: require('!!raw-loader?!../../../assets/images/misc/history.svg').default, // material ui
15 'subscriptions': require('!!raw-loader?!../../../assets/images/misc/subscriptions.svg').default, // material ui 15 subscriptions: require('!!raw-loader?!../../../assets/images/misc/subscriptions.svg').default, // material ui
16 'playlist-add': require('!!raw-loader?!../../../assets/images/misc/playlist-add.svg').default, // material ui 16 'playlist-add': require('!!raw-loader?!../../../assets/images/misc/playlist-add.svg').default, // material ui
17 'follower': require('!!raw-loader?!../../../assets/images/misc/account-arrow-left.svg').default, // material ui 17 follower: require('!!raw-loader?!../../../assets/images/misc/account-arrow-left.svg').default, // material ui
18 'following': require('!!raw-loader?!../../../assets/images/misc/account-arrow-right.svg').default, // material ui 18 following: require('!!raw-loader?!../../../assets/images/misc/account-arrow-right.svg').default, // material ui
19 'flame': require('!!raw-loader?!../../../assets/images/misc/flame.svg').default, 19 flame: require('!!raw-loader?!../../../assets/images/misc/flame.svg').default,
20 'local': require('!!raw-loader?!../../../assets/images/misc/local.svg').default, 20 local: require('!!raw-loader?!../../../assets/images/misc/local.svg').default,
21 21
22 // feather icons 22 // feather icons
23 'flag': require('!!raw-loader?!../../../assets/images/feather/flag.svg').default, 23 flag: require('!!raw-loader?!../../../assets/images/feather/flag.svg').default,
24 'playlists': require('!!raw-loader?!../../../assets/images/feather/list.svg').default, 24 playlists: require('!!raw-loader?!../../../assets/images/feather/list.svg').default,
25 'syndication': require('!!raw-loader?!../../../assets/images/feather/syndication.svg').default, 25 syndication: require('!!raw-loader?!../../../assets/images/feather/syndication.svg').default,
26 'help': require('!!raw-loader?!../../../assets/images/feather/help.svg').default, 26 help: require('!!raw-loader?!../../../assets/images/feather/help.svg').default,
27 'alert': require('!!raw-loader?!../../../assets/images/feather/alert.svg').default, 27 alert: require('!!raw-loader?!../../../assets/images/feather/alert.svg').default,
28 'globe': require('!!raw-loader?!../../../assets/images/feather/globe.svg').default, 28 globe: require('!!raw-loader?!../../../assets/images/feather/globe.svg').default,
29 'home': require('!!raw-loader?!../../../assets/images/feather/home.svg').default, 29 home: require('!!raw-loader?!../../../assets/images/feather/home.svg').default,
30 'recently-added': require('!!raw-loader?!../../../assets/images/feather/recently-added.svg').default, 30 'recently-added': require('!!raw-loader?!../../../assets/images/feather/recently-added.svg').default,
31 'trending': require('!!raw-loader?!../../../assets/images/feather/trending.svg').default, 31 trending: require('!!raw-loader?!../../../assets/images/feather/trending.svg').default,
32 'search': require('!!raw-loader?!../../../assets/images/feather/search.svg').default, 32 search: require('!!raw-loader?!../../../assets/images/feather/search.svg').default,
33 'upload': require('!!raw-loader?!../../../assets/images/feather/upload.svg').default, 33 upload: require('!!raw-loader?!../../../assets/images/feather/upload.svg').default,
34 'dislike': require('!!raw-loader?!../../../assets/images/feather/dislike.svg').default, 34 dislike: require('!!raw-loader?!../../../assets/images/feather/dislike.svg').default,
35 'like': require('!!raw-loader?!../../../assets/images/feather/like.svg').default, 35 like: require('!!raw-loader?!../../../assets/images/feather/like.svg').default,
36 'no': require('!!raw-loader?!../../../assets/images/feather/no.svg').default, 36 no: require('!!raw-loader?!../../../assets/images/feather/no.svg').default,
37 'cloud-download': require('!!raw-loader?!../../../assets/images/feather/cloud-download.svg').default, 37 'cloud-download': require('!!raw-loader?!../../../assets/images/feather/cloud-download.svg').default,
38 'clock': require('!!raw-loader?!../../../assets/images/feather/clock.svg').default, 38 clock: require('!!raw-loader?!../../../assets/images/feather/clock.svg').default,
39 'cog': require('!!raw-loader?!../../../assets/images/feather/cog.svg').default, 39 cog: require('!!raw-loader?!../../../assets/images/feather/cog.svg').default,
40 'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default, 40 delete: require('!!raw-loader?!../../../assets/images/feather/delete.svg').default,
41 'bell': require('!!raw-loader?!../../../assets/images/feather/bell.svg').default, 41 bell: require('!!raw-loader?!../../../assets/images/feather/bell.svg').default,
42 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default, 42 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default,
43 'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default, 43 'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default,
44 'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default, 44 download: require('!!raw-loader?!../../../assets/images/feather/download.svg').default,
45 'ownership-change': require('!!raw-loader?!../../../assets/images/feather/share.svg').default, 45 'ownership-change': require('!!raw-loader?!../../../assets/images/feather/share.svg').default,
46 'share': require('!!raw-loader?!../../../assets/images/feather/share-2.svg').default, 46 share: require('!!raw-loader?!../../../assets/images/feather/share-2.svg').default,
47 'channel': require('!!raw-loader?!../../../assets/images/feather/tv.svg').default, 47 channel: require('!!raw-loader?!../../../assets/images/feather/tv.svg').default,
48 'user': require('!!raw-loader?!../../../assets/images/feather/user.svg').default, 48 user: require('!!raw-loader?!../../../assets/images/feather/user.svg').default,
49 'user-x': require('!!raw-loader?!../../../assets/images/feather/user-x.svg').default, 49 'user-x': require('!!raw-loader?!../../../assets/images/feather/user-x.svg').default,
50 'users': require('!!raw-loader?!../../../assets/images/feather/users.svg').default, 50 users: require('!!raw-loader?!../../../assets/images/feather/users.svg').default,
51 'user-add': require('!!raw-loader?!../../../assets/images/feather/user-plus.svg').default, 51 'user-add': require('!!raw-loader?!../../../assets/images/feather/user-plus.svg').default,
52 'add': require('!!raw-loader?!../../../assets/images/feather/plus-circle.svg').default, 52 add: require('!!raw-loader?!../../../assets/images/feather/plus-circle.svg').default,
53 'cloud-error': require('!!raw-loader?!../../../assets/images/feather/cloud-off.svg').default, 53 'cloud-error': require('!!raw-loader?!../../../assets/images/feather/cloud-off.svg').default,
54 'undo': require('!!raw-loader?!../../../assets/images/feather/corner-up-left.svg').default, 54 undo: require('!!raw-loader?!../../../assets/images/feather/corner-up-left.svg').default,
55 'circle-tick': require('!!raw-loader?!../../../assets/images/feather/check-circle.svg').default, 55 'circle-tick': require('!!raw-loader?!../../../assets/images/feather/check-circle.svg').default,
56 'more-horizontal': require('!!raw-loader?!../../../assets/images/feather/more-horizontal.svg').default, 56 'more-horizontal': require('!!raw-loader?!../../../assets/images/feather/more-horizontal.svg').default,
57 'more-vertical': require('!!raw-loader?!../../../assets/images/feather/more-vertical.svg').default, 57 'more-vertical': require('!!raw-loader?!../../../assets/images/feather/more-vertical.svg').default,
58 'play': require('!!raw-loader?!../../../assets/images/feather/play.svg').default, 58 play: require('!!raw-loader?!../../../assets/images/feather/play.svg').default,
59 'p2p': require('!!raw-loader?!../../../assets/images/feather/airplay.svg').default, 59 p2p: require('!!raw-loader?!../../../assets/images/feather/airplay.svg').default,
60 'fullscreen': require('!!raw-loader?!../../../assets/images/feather/maximize.svg').default, 60 fullscreen: require('!!raw-loader?!../../../assets/images/feather/maximize.svg').default,
61 'exit-fullscreen': require('!!raw-loader?!../../../assets/images/feather/minimize.svg').default, 61 'exit-fullscreen': require('!!raw-loader?!../../../assets/images/feather/minimize.svg').default,
62 'film': require('!!raw-loader?!../../../assets/images/feather/film.svg').default, 62 film: require('!!raw-loader?!../../../assets/images/feather/film.svg').default,
63 'edit': require('!!raw-loader?!../../../assets/images/feather/edit-2.svg').default, 63 edit: require('!!raw-loader?!../../../assets/images/feather/edit-2.svg').default,
64 'sensitive': require('!!raw-loader?!../../../assets/images/feather/eye.svg').default, 64 sensitive: require('!!raw-loader?!../../../assets/images/feather/eye.svg').default,
65 'unsensitive': require('!!raw-loader?!../../../assets/images/feather/eye-off.svg').default, 65 unsensitive: require('!!raw-loader?!../../../assets/images/feather/eye-off.svg').default,
66 'refresh': require('!!raw-loader?!../../../assets/images/feather/refresh-cw.svg').default, 66 refresh: require('!!raw-loader?!../../../assets/images/feather/refresh-cw.svg').default,
67 'command': require('!!raw-loader?!../../../assets/images/feather/command.svg').default, 67 command: require('!!raw-loader?!../../../assets/images/feather/command.svg').default,
68 'go': require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default, 68 go: require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default,
69 'cross': require('!!raw-loader?!../../../assets/images/feather/x.svg').default, 69 cross: require('!!raw-loader?!../../../assets/images/feather/x.svg').default,
70 'tick': require('!!raw-loader?!../../../assets/images/feather/check.svg').default, 70 tick: require('!!raw-loader?!../../../assets/images/feather/check.svg').default,
71 'columns': require('!!raw-loader?!../../../assets/images/feather/columns.svg').default, 71 columns: require('!!raw-loader?!../../../assets/images/feather/columns.svg').default,
72 'live': require('!!raw-loader?!../../../assets/images/feather/live.svg').default, 72 live: require('!!raw-loader?!../../../assets/images/feather/live.svg').default,
73 'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default, 73 repeat: require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default,
74 'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default, 74 'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default,
75 'codesandbox': require('!!raw-loader?!../../../assets/images/feather/codesandbox.svg').default, 75 codesandbox: require('!!raw-loader?!../../../assets/images/feather/codesandbox.svg').default,
76 'award': require('!!raw-loader?!../../../assets/images/feather/award.svg').default 76 award: require('!!raw-loader?!../../../assets/images/feather/award.svg').default
77} 77}
78 78
79export type GlobalIconName = keyof typeof icons 79export type GlobalIconName = keyof typeof icons
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
index 855c06aa1..1eb7b49b6 100644
--- a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
@@ -7,7 +7,7 @@ import { About } from '@shared/models/server'
7@Component({ 7@Component({
8 selector: 'my-instance-about-accordion', 8 selector: 'my-instance-about-accordion',
9 templateUrl: './instance-about-accordion.component.html', 9 templateUrl: './instance-about-accordion.component.html',
10 styleUrls: ['./instance-about-accordion.component.scss'] 10 styleUrls: [ './instance-about-accordion.component.scss' ]
11}) 11})
12export class InstanceAboutAccordionComponent implements OnInit { 12export class InstanceAboutAccordionComponent implements OnInit {
13 @ViewChild('accordion', { static: true }) accordion: NgbAccordion 13 @ViewChild('accordion', { static: true }) accordion: NgbAccordion
diff --git a/client/src/app/shared/shared-instance/instance-follow.service.ts b/client/src/app/shared/shared-instance/instance-follow.service.ts
index af44020cf..a6799d3e1 100644
--- a/client/src/app/shared/shared-instance/instance-follow.service.ts
+++ b/client/src/app/shared/shared-instance/instance-follow.service.ts
@@ -19,10 +19,10 @@ export class InstanceFollowService {
19 } 19 }
20 20
21 getFollowing (options: { 21 getFollowing (options: {
22 pagination: RestPagination, 22 pagination: RestPagination
23 sort: SortMeta, 23 sort: SortMeta
24 search?: string, 24 search?: string
25 actorType?: ActivityPubActorType, 25 actorType?: ActivityPubActorType
26 state?: FollowState 26 state?: FollowState
27 }): Observable<ResultList<ActorFollow>> { 27 }): Observable<ResultList<ActorFollow>> {
28 const { pagination, sort, search, state, actorType } = options 28 const { pagination, sort, search, state, actorType } = options
@@ -42,10 +42,10 @@ export class InstanceFollowService {
42 } 42 }
43 43
44 getFollowers (options: { 44 getFollowers (options: {
45 pagination: RestPagination, 45 pagination: RestPagination
46 sort: SortMeta, 46 sort: SortMeta
47 search?: string, 47 search?: string
48 actorType?: ActivityPubActorType, 48 actorType?: ActivityPubActorType
49 state?: FollowState 49 state?: FollowState
50 }): Observable<ResultList<ActorFollow>> { 50 }): Observable<ResultList<ActorFollow>> {
51 const { pagination, sort, search, state, actorType } = options 51 const { pagination, sort, search, state, actorType } = options
diff --git a/client/src/app/shared/shared-instance/instance-statistics.component.ts b/client/src/app/shared/shared-instance/instance-statistics.component.ts
index 40aa8a4c0..0618efe69 100644
--- a/client/src/app/shared/shared-instance/instance-statistics.component.ts
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.ts
@@ -17,6 +17,8 @@ export class InstanceStatisticsComponent implements OnInit {
17 17
18 ngOnInit () { 18 ngOnInit () {
19 this.serverService.getServerStats() 19 this.serverService.getServerStats()
20 .subscribe(res => this.serverStats = res) 20 .subscribe(res => {
21 this.serverStats = res
22 })
21 } 23 }
22} 24}
diff --git a/client/src/app/shared/shared-instance/instance.service.ts b/client/src/app/shared/shared-instance/instance.service.ts
index 70e022178..0241f56ef 100644
--- a/client/src/app/shared/shared-instance/instance.service.ts
+++ b/client/src/app/shared/shared-instance/instance.service.ts
@@ -51,7 +51,7 @@ export class InstanceService {
51 } 51 }
52 52
53 for (const key of Object.keys(html)) { 53 for (const key of Object.keys(html)) {
54 html[ key ] = await this.markdownService.textMarkdownToHTML(about.instance[ key ]) 54 html[key] = await this.markdownService.textMarkdownToHTML(about.instance[key])
55 } 55 }
56 56
57 return html 57 return html
diff --git a/client/src/app/shared/shared-instance/shared-instance.module.ts b/client/src/app/shared/shared-instance/shared-instance.module.ts
index 5efb95a7d..13c681ab8 100644
--- a/client/src/app/shared/shared-instance/shared-instance.module.ts
+++ b/client/src/app/shared/shared-instance/shared-instance.module.ts
@@ -1,7 +1,6 @@
1 1
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' 3import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
4import { SharedCustomMarkupModule } from '../shared-custom-markup'
5import { SharedMainModule } from '../shared-main/shared-main.module' 4import { SharedMainModule } from '../shared-main/shared-main.module'
6import { FeatureBooleanComponent } from './feature-boolean.component' 5import { FeatureBooleanComponent } from './feature-boolean.component'
7import { InstanceAboutAccordionComponent } from './instance-about-accordion.component' 6import { InstanceAboutAccordionComponent } from './instance-about-accordion.component'
diff --git a/client/src/app/shared/shared-main/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts
index 7b5611f35..92606e7fa 100644
--- a/client/src/app/shared/shared-main/account/account.model.ts
+++ b/client/src/app/shared/shared-main/account/account.model.ts
@@ -1,4 +1,4 @@
1import { Account as ServerAccount, Actor as ServerActor, ActorImage } from '@shared/models' 1import { Account as ServerAccount, ActorImage } from '@shared/models'
2import { Actor } from './actor.model' 2import { Actor } from './actor.model'
3 3
4export class Account extends Actor implements ServerAccount { 4export class Account extends Actor implements ServerAccount {
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
index 2fccc472a..082f44fb9 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -20,7 +20,7 @@ export abstract class Actor implements ServerActor {
20 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { 20 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
21 if (actor?.avatar?.url) return actor.avatar.url 21 if (actor?.avatar?.url) return actor.avatar.url
22 22
23 if (actor && actor.avatar) { 23 if (actor?.avatar) {
24 const absoluteAPIUrl = getAbsoluteAPIUrl() 24 const absoluteAPIUrl = getAbsoluteAPIUrl()
25 25
26 return absoluteAPIUrl + actor.avatar.path 26 return absoluteAPIUrl + actor.avatar.path
diff --git a/client/src/app/shared/shared-main/angular/autofocus.directive.ts b/client/src/app/shared/shared-main/angular/autofocus.directive.ts
index 5f087d79d..2da492ea1 100644
--- a/client/src/app/shared/shared-main/angular/autofocus.directive.ts
+++ b/client/src/app/shared/shared-main/angular/autofocus.directive.ts
@@ -1,7 +1,7 @@
1import { AfterViewInit, Directive, ElementRef } from '@angular/core' 1import { AfterViewInit, Directive, ElementRef } from '@angular/core'
2 2
3@Directive({ 3@Directive({
4 selector: '[autofocus]' 4 selector: '[myAutofocus]'
5}) 5})
6export class AutofocusDirective implements AfterViewInit { 6export class AutofocusDirective implements AfterViewInit {
7 constructor (private host: ElementRef) { } 7 constructor (private host: ElementRef) { }
diff --git a/client/src/app/shared/shared-main/angular/link.component.ts b/client/src/app/shared/shared-main/angular/link.component.ts
index 597a16871..ecbd9151c 100644
--- a/client/src/app/shared/shared-main/angular/link.component.ts
+++ b/client/src/app/shared/shared-main/angular/link.component.ts
@@ -1,4 +1,4 @@
1import { Component, Input, ViewEncapsulation } from '@angular/core' 1import { Component, Input } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-link', 4 selector: 'my-link',
diff --git a/client/src/app/shared/shared-main/angular/peertube-template.directive.ts b/client/src/app/shared/shared-main/angular/peertube-template.directive.ts
index e04c25d9a..dc0cde12d 100644
--- a/client/src/app/shared/shared-main/angular/peertube-template.directive.ts
+++ b/client/src/app/shared/shared-main/angular/peertube-template.directive.ts
@@ -1,6 +1,7 @@
1import { Directive, Input, TemplateRef } from '@angular/core' 1import { Directive, Input, TemplateRef } from '@angular/core'
2 2
3@Directive({ 3@Directive({
4 // eslint-disable-next-line @angular-eslint/directive-selector
4 selector: '[ptTemplate]' 5 selector: '[ptTemplate]'
5}) 6})
6export class PeerTubeTemplateDirective <T extends string> { 7export class PeerTubeTemplateDirective <T extends string> {
diff --git a/client/src/app/shared/shared-main/feeds/syndication.model.ts b/client/src/app/shared/shared-main/feeds/syndication.model.ts
index 2466ae7c6..cd6fbdb48 100644
--- a/client/src/app/shared/shared-main/feeds/syndication.model.ts
+++ b/client/src/app/shared/shared-main/feeds/syndication.model.ts
@@ -1,7 +1,7 @@
1import { FeedFormat } from '@shared/models' 1import { FeedFormat } from '@shared/models'
2 2
3export interface Syndication { 3export interface Syndication {
4 format: FeedFormat, 4 format: FeedFormat
5 label: string, 5 label: string
6 url: string 6 url: string
7} 7}
diff --git a/client/src/app/shared/shared-main/misc/help.component.ts b/client/src/app/shared/shared-main/misc/help.component.ts
index 76e255d99..37e2abd97 100644
--- a/client/src/app/shared/shared-main/misc/help.component.ts
+++ b/client/src/app/shared/shared-main/misc/help.component.ts
@@ -71,18 +71,18 @@ export class HelpComponent implements OnInit, OnChanges, AfterContentInit {
71 } 71 }
72 72
73 private formatMarkdownSupport (rules: string[]) { 73 private formatMarkdownSupport (rules: string[]) {
74 // tslint:disable:max-line-length 74 /* eslint-disable max-len */
75 return $localize`<a href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noopener noreferrer">Markdown</a> compatible that supports:` + 75 return $localize`<a href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noopener noreferrer">Markdown</a> compatible that supports:` +
76 this.createMarkdownList(rules) 76 this.createMarkdownList(rules)
77 } 77 }
78 78
79 private createMarkdownList (rules: string[]) { 79 private createMarkdownList (rules: string[]) {
80 const rulesToText = { 80 const rulesToText = {
81 'emphasis': $localize`Emphasis`, 81 emphasis: $localize`Emphasis`,
82 'link': $localize`Links`, 82 link: $localize`Links`,
83 'newline': $localize`New lines`, 83 newline: $localize`New lines`,
84 'list': $localize`Lists`, 84 list: $localize`Lists`,
85 'image': $localize`Images` 85 image: $localize`Images`
86 } 86 }
87 87
88 const bullets = rules.map(r => rulesToText[r]) 88 const bullets = rules.map(r => rulesToText[r])
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.ts b/client/src/app/shared/shared-main/misc/list-overflow.component.ts
index 144e0f156..fbc481093 100644
--- a/client/src/app/shared/shared-main/misc/list-overflow.component.ts
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.ts
@@ -22,7 +22,7 @@ export interface ListOverflowItem {
22} 22}
23 23
24@Component({ 24@Component({
25 selector: 'list-overflow', 25 selector: 'my-list-overflow',
26 templateUrl: './list-overflow.component.html', 26 templateUrl: './list-overflow.component.html',
27 styleUrls: [ './list-overflow.component.scss' ], 27 styleUrls: [ './list-overflow.component.scss' ],
28 changeDetection: ChangeDetectionStrategy.OnPush 28 changeDetection: ChangeDetectionStrategy.OnPush
@@ -65,7 +65,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
65 let showItemsUntilIndexExcluded: number 65 let showItemsUntilIndexExcluded: number
66 let accWidth = 0 66 let accWidth = 0
67 67
68 for (const [index, el] of this.itemsRendered.toArray().entries()) { 68 for (const [ index, el ] of this.itemsRendered.toArray().entries()) {
69 accWidth += el.nativeElement.getBoundingClientRect().width 69 accWidth += el.nativeElement.getBoundingClientRect().width
70 if (showItemsUntilIndexExcluded === undefined) { 70 if (showItemsUntilIndexExcluded === undefined) {
71 showItemsUntilIndexExcluded = (parentWidth < accWidth) ? index : undefined 71 showItemsUntilIndexExcluded = (parentWidth < accWidth) ? index : undefined
diff --git a/client/src/app/shared/shared-main/misc/simple-search-input.component.ts b/client/src/app/shared/shared-main/misc/simple-search-input.component.ts
index 224d71134..292ec4c82 100644
--- a/client/src/app/shared/shared-main/misc/simple-search-input.component.ts
+++ b/client/src/app/shared/shared-main/misc/simple-search-input.component.ts
@@ -4,7 +4,7 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild }
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5 5
6@Component({ 6@Component({
7 selector: 'simple-search-input', 7 selector: 'my-simple-search-input',
8 templateUrl: './simple-search-input.component.html', 8 templateUrl: './simple-search-input.component.html',
9 styleUrls: [ './simple-search-input.component.scss' ] 9 styleUrls: [ './simple-search-input.component.scss' ]
10}) 10})
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts
index 2cafb6c55..e7e34ce1e 100644
--- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts
+++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts
@@ -66,7 +66,7 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
66 .subscribe(() => this.updateChildLabels(window.location.pathname)) 66 .subscribe(() => this.updateChildLabels(window.location.pathname))
67 67
68 this.hasIcons = this.menuEntries.some( 68 this.hasIcons = this.menuEntries.some(
69 e => e.children && e.children.some(c => !!c.iconName) 69 e => e.children?.some(c => !!c.iconName)
70 ) 70 )
71 } 71 }
72 72
diff --git a/client/src/app/shared/shared-main/users/user-notification.service.ts b/client/src/app/shared/shared-main/users/user-notification.service.ts
index 9014b48a8..09fee87a3 100644
--- a/client/src/app/shared/shared-main/users/user-notification.service.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.service.ts
@@ -1,11 +1,11 @@
1import { SortMeta } from 'primeng/api'
1import { catchError, map, tap } from 'rxjs/operators' 2import { catchError, map, tap } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
4import { ComponentPaginationLight, RestExtractor, RestService, User, PeerTubeSocket, AuthService } from '@app/core' 5import { AuthService, ComponentPaginationLight, PeerTubeSocket, RestExtractor, RestService } from '@app/core'
5import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models' 6import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models'
6import { environment } from '../../../../environments/environment' 7import { environment } from '../../../../environments/environment'
7import { UserNotification } from './user-notification.model' 8import { UserNotification } from './user-notification.model'
8import { SortMeta } from 'primeng/api'
9 9
10@Injectable() 10@Injectable()
11export class UserNotificationService { 11export class UserNotificationService {
@@ -23,7 +23,7 @@ export class UserNotificationService {
23 listMyNotifications (parameters: { 23 listMyNotifications (parameters: {
24 pagination: ComponentPaginationLight 24 pagination: ComponentPaginationLight
25 ignoreLoadingBar?: boolean 25 ignoreLoadingBar?: boolean
26 unread?: boolean, 26 unread?: boolean
27 sort?: SortMeta 27 sort?: SortMeta
28 }) { 28 }) {
29 const { pagination, ignoreLoadingBar, unread, sort } = parameters 29 const { pagination, ignoreLoadingBar, unread, sort } = parameters
diff --git a/client/src/app/shared/shared-main/users/user-quota.component.ts b/client/src/app/shared/shared-main/users/user-quota.component.ts
index b38619186..5a95f1209 100644
--- a/client/src/app/shared/shared-main/users/user-quota.component.ts
+++ b/client/src/app/shared/shared-main/users/user-quota.component.ts
@@ -6,7 +6,7 @@ import { BytesPipe } from '../angular'
6@Component({ 6@Component({
7 selector: 'my-user-quota', 7 selector: 'my-user-quota',
8 templateUrl: './user-quota.component.html', 8 templateUrl: './user-quota.component.html',
9 styleUrls: ['./user-quota.component.scss'] 9 styleUrls: [ './user-quota.component.scss' ]
10}) 10})
11 11
12export class UserQuotaComponent implements OnInit { 12export class UserQuotaComponent implements OnInit {
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
index a9dcf2fa2..66d4cac68 100644
--- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
@@ -1,6 +1,5 @@
1import { getAbsoluteAPIUrl } from '@app/helpers' 1import { getAbsoluteAPIUrl } from '@app/helpers'
2import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@shared/models' 2import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@shared/models'
3import { Account } from '../account/account.model'
4import { Actor } from '../account/actor.model' 3import { Actor } from '../account/actor.model'
5 4
6export class VideoChannel extends Actor implements ServerVideoChannel { 5export class VideoChannel extends Actor implements ServerVideoChannel {
@@ -25,14 +24,14 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
25 24
26 viewsPerDay?: ViewsPerDate[] 25 viewsPerDay?: ViewsPerDate[]
27 26
28 static GET_ACTOR_AVATAR_URL (actor: object) { 27 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
29 return Actor.GET_ACTOR_AVATAR_URL(actor) 28 return Actor.GET_ACTOR_AVATAR_URL(actor)
30 } 29 }
31 30
32 static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) { 31 static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) {
33 if (channel?.banner?.url) return channel.banner.url 32 if (channel?.banner?.url) return channel.banner.url
34 33
35 if (channel && channel.banner) { 34 if (channel?.banner) {
36 const absoluteAPIUrl = getAbsoluteAPIUrl() 35 const absoluteAPIUrl = getAbsoluteAPIUrl()
37 36
38 return absoluteAPIUrl + channel.banner.path 37 return absoluteAPIUrl + channel.banner.path
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
index a89f1065a..7560a35a8 100644
--- a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
@@ -15,6 +15,12 @@ export class VideoChannelService {
15 15
16 videoChannelLoaded = new ReplaySubject<VideoChannel>(1) 16 videoChannelLoaded = new ReplaySubject<VideoChannel>(1)
17 17
18 constructor (
19 private authHttp: HttpClient,
20 private restService: RestService,
21 private restExtractor: RestExtractor
22 ) { }
23
18 static extractVideoChannels (result: ResultList<VideoChannelServer>) { 24 static extractVideoChannels (result: ResultList<VideoChannelServer>) {
19 const videoChannels: VideoChannel[] = [] 25 const videoChannels: VideoChannel[] = []
20 26
@@ -25,12 +31,6 @@ export class VideoChannelService {
25 return { data: videoChannels, total: result.total } 31 return { data: videoChannels, total: result.total }
26 } 32 }
27 33
28 constructor (
29 private authHttp: HttpClient,
30 private restService: RestService,
31 private restExtractor: RestExtractor
32 ) { }
33
34 getVideoChannel (videoChannelName: string) { 34 getVideoChannel (videoChannelName: string) {
35 return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName) 35 return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName)
36 .pipe( 36 .pipe(
diff --git a/client/src/app/shared/shared-main/video/redundancy.service.ts b/client/src/app/shared/shared-main/video/redundancy.service.ts
index 6e839e655..966d7fafd 100644
--- a/client/src/app/shared/shared-main/video/redundancy.service.ts
+++ b/client/src/app/shared/shared-main/video/redundancy.service.ts
@@ -30,8 +30,8 @@ export class RedundancyService {
30 } 30 }
31 31
32 listVideoRedundancies (options: { 32 listVideoRedundancies (options: {
33 pagination: RestPagination, 33 pagination: RestPagination
34 sort: SortMeta, 34 sort: SortMeta
35 target?: VideoRedundanciesTarget 35 target?: VideoRedundanciesTarget
36 }): Observable<ResultList<VideoRedundancy>> { 36 }): Observable<ResultList<VideoRedundancy>> {
37 const { pagination, sort, target } = options 37 const { pagination, sort, target } = options
diff --git a/client/src/app/shared/shared-main/video/video-edit.model.ts b/client/src/app/shared/shared-main/video/video-edit.model.ts
index 757b686c0..ea0456942 100644
--- a/client/src/app/shared/shared-main/video/video-edit.model.ts
+++ b/client/src/app/shared/shared-main/video/video-edit.model.ts
@@ -29,11 +29,11 @@ export class VideoEdit implements VideoUpdate {
29 29
30 constructor ( 30 constructor (
31 video?: Video & { 31 video?: Video & {
32 tags: string[], 32 tags: string[]
33 commentsEnabled: boolean, 33 commentsEnabled: boolean
34 downloadEnabled: boolean, 34 downloadEnabled: boolean
35 support: string, 35 support: string
36 thumbnailUrl: string, 36 thumbnailUrl: string
37 previewUrl: string 37 previewUrl: string
38 }) { 38 }) {
39 if (video) { 39 if (video) {
@@ -64,7 +64,7 @@ export class VideoEdit implements VideoUpdate {
64 64
65 patch (values: { [ id: string ]: any }) { 65 patch (values: { [ id: string ]: any }) {
66 Object.keys(values).forEach((key) => { 66 Object.keys(values).forEach((key) => {
67 this[ key ] = values[ key ] 67 this[key] = values[key]
68 }) 68 })
69 69
70 // If schedule publication, the video is private and will be changed to public privacy 70 // If schedule publication, the video is private and will be changed to public privacy
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index b7720c8d2..7471a933b 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -100,7 +100,7 @@ export class Video implements VideoServerModel {
100 return '/videos/update/' + video.uuid 100 return '/videos/update/' + video.uuid
101 } 101 }
102 102
103 constructor (hash: VideoServerModel, translations = {}) { 103 constructor (hash: VideoServerModel, translations: { [ id: string ]: string } = {}) {
104 const absoluteAPIUrl = getAbsoluteAPIUrl() 104 const absoluteAPIUrl = getAbsoluteAPIUrl()
105 105
106 this.createdAt = new Date(hash.createdAt.toString()) 106 this.createdAt = new Date(hash.createdAt.toString())
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index 4a97719fa..60cc9d160 100644
--- a/client/src/app/shared/shared-main/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -30,10 +30,10 @@ import { Video } from './video.model'
30 30
31export interface VideosProvider { 31export interface VideosProvider {
32 getVideos (parameters: { 32 getVideos (parameters: {
33 videoPagination: ComponentPaginationLight, 33 videoPagination: ComponentPaginationLight
34 sort: VideoSortField, 34 sort: VideoSortField
35 filter?: VideoFilter, 35 filter?: VideoFilter
36 categoryOneOf?: number[], 36 categoryOneOf?: number[]
37 languageOneOf?: string[] 37 languageOneOf?: string[]
38 nsfwPolicy: NSFWPolicyType 38 nsfwPolicy: NSFWPolicyType
39 }): Observable<ResultList<Video>> 39 }): Observable<ResultList<Video>>
@@ -145,8 +145,8 @@ export class VideoService implements VideosProvider {
145 } 145 }
146 146
147 getAccountVideos (parameters: { 147 getAccountVideos (parameters: {
148 account: Pick<Account, 'nameWithHost'>, 148 account: Pick<Account, 'nameWithHost'>
149 videoPagination: ComponentPaginationLight, 149 videoPagination: ComponentPaginationLight
150 sort: VideoSortField 150 sort: VideoSortField
151 nsfwPolicy?: NSFWPolicyType 151 nsfwPolicy?: NSFWPolicyType
152 videoFilter?: VideoFilter 152 videoFilter?: VideoFilter
@@ -180,9 +180,9 @@ export class VideoService implements VideosProvider {
180 } 180 }
181 181
182 getVideoChannelVideos (parameters: { 182 getVideoChannelVideos (parameters: {
183 videoChannel: Pick<VideoChannel, 'nameWithHost'>, 183 videoChannel: Pick<VideoChannel, 'nameWithHost'>
184 videoPagination: ComponentPaginationLight, 184 videoPagination: ComponentPaginationLight
185 sort: VideoSortField, 185 sort: VideoSortField
186 nsfwPolicy?: NSFWPolicyType 186 nsfwPolicy?: NSFWPolicyType
187 videoFilter?: VideoFilter 187 videoFilter?: VideoFilter
188 }): Observable<ResultList<Video>> { 188 }): Observable<ResultList<Video>> {
diff --git a/client/src/app/shared/shared-moderation/abuse.service.ts b/client/src/app/shared/shared-moderation/abuse.service.ts
index bf98d4b36..f45b5c8e8 100644
--- a/client/src/app/shared/shared-moderation/abuse.service.ts
+++ b/client/src/app/shared/shared-moderation/abuse.service.ts
@@ -30,8 +30,8 @@ export class AbuseService {
30 ) { } 30 ) { }
31 31
32 getAdminAbuses (options: { 32 getAdminAbuses (options: {
33 pagination: RestPagination, 33 pagination: RestPagination
34 sort: SortMeta, 34 sort: SortMeta
35 search?: string 35 search?: string
36 }): Observable<ResultList<AdminAbuse>> { 36 }): Observable<ResultList<AdminAbuse>> {
37 const { pagination, sort, search } = options 37 const { pagination, sort, search } = options
@@ -51,8 +51,8 @@ export class AbuseService {
51 } 51 }
52 52
53 getUserAbuses (options: { 53 getUserAbuses (options: {
54 pagination: RestPagination, 54 pagination: RestPagination
55 sort: SortMeta, 55 sort: SortMeta
56 search?: string 56 search?: string
57 }): Observable<ResultList<UserAbuse>> { 57 }): Observable<ResultList<UserAbuse>> {
58 const { pagination, sort, search } = options 58 const { pagination, sort, search } = options
@@ -74,7 +74,7 @@ export class AbuseService {
74 reportVideo (parameters: AbuseCreate) { 74 reportVideo (parameters: AbuseCreate) {
75 const url = AbuseService.BASE_ABUSE_URL 75 const url = AbuseService.BASE_ABUSE_URL
76 76
77 const body = omit(parameters, ['id']) 77 const body = omit(parameters, [ 'id' ])
78 78
79 return this.authHttp.post(url, body) 79 return this.authHttp.post(url, body)
80 .pipe( 80 .pipe(
@@ -147,11 +147,13 @@ export class AbuseService {
147 { 147 {
148 id: 'spamOrMisleading', 148 id: 'spamOrMisleading',
149 label: $localize`Spam, ad or false news`, 149 label: $localize`Spam, ad or false news`,
150 // eslint-disable-next-line max-len
150 help: $localize`Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.` 151 help: $localize`Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.`
151 }, 152 },
152 { 153 {
153 id: 'privacy', 154 id: 'privacy',
154 label: $localize`Privacy breach or doxxing`, 155 label: $localize`Privacy breach or doxxing`,
156 // eslint-disable-next-line max-len
155 help: $localize`Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).` 157 help: $localize`Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).`
156 }, 158 },
157 { 159 {
@@ -162,6 +164,7 @@ export class AbuseService {
162 { 164 {
163 id: 'serverRules', 165 id: 'serverRules',
164 label: $localize`Breaks server rules`, 166 label: $localize`Breaks server rules`,
167 // eslint-disable-next-line max-len
165 description: $localize`Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.` 168 description: $localize`Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.`
166 } 169 }
167 ] 170 ]
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.ts b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
index 36f2a591b..9ed00bc12 100644
--- a/client/src/app/shared/shared-moderation/account-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
@@ -1,14 +1,13 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Directive, OnInit } from '@angular/core' 2import { Directive, OnInit } from '@angular/core'
3import { Notifier, RestPagination, RestTable } from '@app/core' 3import { Notifier, RestPagination, RestTable } from '@app/core'
4import { Account } from '@app/shared/shared-main'
5import { AccountBlock } from './account-block.model' 4import { AccountBlock } from './account-block.model'
6import { BlocklistComponentType, BlocklistService } from './blocklist.service' 5import { BlocklistComponentType, BlocklistService } from './blocklist.service'
7 6
8@Directive() 7@Directive()
9// tslint:disable-next-line: directive-class-suffix 8// eslint-disable-next-line @angular-eslint/directive-class-suffix
10export class GenericAccountBlocklistComponent extends RestTable implements OnInit { 9export class GenericAccountBlocklistComponent extends RestTable implements OnInit {
11 // @ts-ignore: "Abstract methods can only appear within an abstract class" 10 // @ts-expect-error: "Abstract methods can only appear within an abstract class"
12 abstract mode: BlocklistComponentType 11 abstract mode: BlocklistComponentType
13 12
14 blockedAccounts: AccountBlock[] = [] 13 blockedAccounts: AccountBlock[] = []
@@ -23,7 +22,7 @@ export class GenericAccountBlocklistComponent extends RestTable implements OnIni
23 super() 22 super()
24 } 23 }
25 24
26 // @ts-ignore: "Abstract methods can only appear within an abstract class" 25 // @ts-expect-error: "Abstract methods can only appear within an abstract class"
27 abstract getIdentifier (): string 26 abstract getIdentifier (): string
28 27
29 ngOnInit () { 28 ngOnInit () {
diff --git a/client/src/app/shared/shared-moderation/blocklist.service.ts b/client/src/app/shared/shared-moderation/blocklist.service.ts
index de677a77b..db2a8c584 100644
--- a/client/src/app/shared/shared-moderation/blocklist.service.ts
+++ b/client/src/app/shared/shared-moderation/blocklist.service.ts
@@ -21,7 +21,7 @@ export class BlocklistService {
21 private restService: RestService 21 private restService: RestService
22 ) { } 22 ) { }
23 23
24 /*********************** User -> Account blocklist ***********************/ 24 /** ********************* User -> Account blocklist ***********************/
25 25
26 getUserAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) { 26 getUserAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
27 const { pagination, sort, search } = options 27 const { pagination, sort, search } = options
@@ -53,7 +53,7 @@ export class BlocklistService {
53 .pipe(catchError(err => this.restExtractor.handleError(err))) 53 .pipe(catchError(err => this.restExtractor.handleError(err)))
54 } 54 }
55 55
56 /*********************** User -> Server blocklist ***********************/ 56 /** ********************* User -> Server blocklist ***********************/
57 57
58 getUserServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) { 58 getUserServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
59 const { pagination, sort, search } = options 59 const { pagination, sort, search } = options
@@ -84,7 +84,7 @@ export class BlocklistService {
84 .pipe(catchError(err => this.restExtractor.handleError(err))) 84 .pipe(catchError(err => this.restExtractor.handleError(err)))
85 } 85 }
86 86
87 /*********************** Instance -> Account blocklist ***********************/ 87 /** ********************* Instance -> Account blocklist ***********************/
88 88
89 getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) { 89 getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
90 const { pagination, sort, search } = options 90 const { pagination, sort, search } = options
@@ -116,7 +116,7 @@ export class BlocklistService {
116 .pipe(catchError(err => this.restExtractor.handleError(err))) 116 .pipe(catchError(err => this.restExtractor.handleError(err)))
117 } 117 }
118 118
119 /*********************** Instance -> Server blocklist ***********************/ 119 /** ********************* Instance -> Server blocklist ***********************/
120 120
121 getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) { 121 getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
122 const { pagination, sort, search } = options 122 const { pagination, sort, search } = options
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.ts b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
index da09b1d0e..1ba7a1b4d 100644
--- a/client/src/app/shared/shared-moderation/server-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
@@ -6,11 +6,11 @@ import { ServerBlock } from '@shared/models'
6import { BlocklistComponentType, BlocklistService } from './blocklist.service' 6import { BlocklistComponentType, BlocklistService } from './blocklist.service'
7 7
8@Directive() 8@Directive()
9// tslint:disable-next-line: directive-class-suffix 9// eslint-disable-next-line @angular-eslint/directive-class-suffix
10export class GenericServerBlocklistComponent extends RestTable implements OnInit { 10export class GenericServerBlocklistComponent extends RestTable implements OnInit {
11 @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent 11 @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent
12 12
13 // @ts-ignore: "Abstract methods can only appear within an abstract class" 13 // @ts-expect-error: "Abstract methods can only appear within an abstract class"
14 public abstract mode: BlocklistComponentType 14 public abstract mode: BlocklistComponentType
15 15
16 blockedServers: ServerBlock[] = [] 16 blockedServers: ServerBlock[] = []
@@ -25,7 +25,7 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit
25 super() 25 super()
26 } 26 }
27 27
28 // @ts-ignore: "Abstract methods can only appear within an abstract class" 28 // @ts-expect-error: "Abstract methods can only appear within an abstract class"
29 public abstract getIdentifier (): string 29 public abstract getIdentifier (): string
30 30
31 ngOnInit () { 31 ngOnInit () {
@@ -34,8 +34,8 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit
34 34
35 unblockServer (serverBlock: ServerBlock) { 35 unblockServer (serverBlock: ServerBlock) {
36 const operation = (host: string) => this.mode === BlocklistComponentType.Account 36 const operation = (host: string) => this.mode === BlocklistComponentType.Account
37 ? this.blocklistService.unblockServerByUser(host) 37 ? this.blocklistService.unblockServerByUser(host)
38 : this.blocklistService.unblockServerByInstance(host) 38 : this.blocklistService.unblockServerByInstance(host)
39 const host = serverBlock.blockedServer.host 39 const host = serverBlock.blockedServer.host
40 40
41 operation(host).subscribe( 41 operation(host).subscribe(
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts
index bc6952620..f6c29dcfa 100644
--- a/client/src/app/shared/shared-moderation/video-block.component.ts
+++ b/client/src/app/shared/shared-moderation/video-block.component.ts
@@ -51,8 +51,8 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
51 } 51 }
52 52
53 block () { 53 block () {
54 const reason = this.form.value[ 'reason' ] || undefined 54 const reason = this.form.value['reason'] || undefined
55 const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined 55 const unfederate = this.video.isLocal ? this.form.value['unfederate'] : undefined
56 56
57 this.videoBlocklistService.blockVideo(this.video.id, reason, unfederate) 57 this.videoBlocklistService.blockVideo(this.video.id, reason, unfederate)
58 .subscribe({ 58 .subscribe({
diff --git a/client/src/app/shared/shared-support-modal/support-modal.component.ts b/client/src/app/shared/shared-support-modal/support-modal.component.ts
index a0b9fada6..08e997f7b 100644
--- a/client/src/app/shared/shared-support-modal/support-modal.component.ts
+++ b/client/src/app/shared/shared-support-modal/support-modal.component.ts
@@ -28,7 +28,9 @@ export class SupportModalComponent {
28 const support = this.video?.support || this.videoChannel.support 28 const support = this.video?.support || this.videoChannel.support
29 29
30 this.markdownService.enhancedMarkdownToHTML(support) 30 this.markdownService.enhancedMarkdownToHTML(support)
31 .then(r => this.htmlSupport = r) 31 .then(r => {
32 this.htmlSupport = r
33 })
32 34
33 this.displayName = this.video 35 this.displayName = this.video
34 ? this.video.channel.displayName 36 ? this.video.channel.displayName
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
index 4aac60c2b..5d6e11c04 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
@@ -80,7 +80,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
80 } 80 }
81 81
82 updateDetails (onlyKeys?: string[]) { 82 updateDetails (onlyKeys?: string[]) {
83 const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] 83 const nsfwPolicy = this.form.value['nsfwPolicy']
84 const webTorrentEnabled = this.form.value['webTorrentEnabled'] 84 const webTorrentEnabled = this.form.value['webTorrentEnabled']
85 const autoPlayVideo = this.form.value['autoPlayVideo'] 85 const autoPlayVideo = this.form.value['autoPlayVideo']
86 const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] 86 const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
index 666199523..a951134eb 100644
--- a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
@@ -6,7 +6,7 @@ import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
6@Component({ 6@Component({
7 selector: 'my-remote-subscribe', 7 selector: 'my-remote-subscribe',
8 templateUrl: './remote-subscribe.component.html', 8 templateUrl: './remote-subscribe.component.html',
9 styleUrls: ['./remote-subscribe.component.scss'] 9 styleUrls: [ './remote-subscribe.component.scss' ]
10}) 10})
11export class RemoteSubscribeComponent extends FormReactive implements OnInit { 11export class RemoteSubscribeComponent extends FormReactive implements OnInit {
12 @Input() uri: string 12 @Input() uri: string
@@ -42,17 +42,21 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
42 // Should not have CORS error because https://tools.ietf.org/html/rfc7033#section-5 42 // Should not have CORS error because https://tools.ietf.org/html/rfc7033#section-5
43 fetch(`${protocol}//${hostname}/.well-known/webfinger?resource=acct:${username}@${hostname}`) 43 fetch(`${protocol}//${hostname}/.well-known/webfinger?resource=acct:${username}@${hostname}`)
44 .then(response => response.json()) 44 .then(response => response.json())
45 .then(data => new Promise((res, rej) => { 45 .then(data => {
46 if (!data || Array.isArray(data.links) === false) return rej() 46 if (!data || Array.isArray(data.links) === false) {
47 throw new Error('Not links in webfinger response')
48 }
47 49
48 const link: { template: string } = data.links.find((link: any) => { 50 const link: { template: string } = data.links.find((link: any) => {
49 return link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe' 51 return link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe'
50 }) 52 })
51 53
52 if (link && link.template.includes('{uri}')) { 54 if (link?.template.includes('{uri}')) {
53 res(link.template.replace('{uri}', encodeURIComponent(this.uri))) 55 return link.template.replace('{uri}', encodeURIComponent(this.uri))
54 } 56 }
55 })) 57
58 throw new Error('No subscribe template in webfinger response')
59 })
56 .then(window.open) 60 .then(window.open)
57 .catch(err => { 61 .catch(err => {
58 console.error(err) 62 console.error(err)
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
index 796493bd9..180bc0565 100644
--- a/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
@@ -137,7 +137,7 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
137 this.notifier.success( 137 this.notifier.success(
138 this.account 138 this.account
139 ? $localize`Unsubscribed from all channels of ${this.account.nameWithHost}` 139 ? $localize`Unsubscribed from all channels of ${this.account.nameWithHost}`
140 : $localize`Unsubscribed from ${this.videoChannels[ 0 ].nameWithHost}`, 140 : $localize`Unsubscribed from ${this.videoChannels[0].nameWithHost}`,
141 141
142 $localize`Unsubscribed` 142 $localize`Unsubscribed`
143 ) 143 )
@@ -157,7 +157,7 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
157 157
158 subscribeStatus (subscribed: boolean) { 158 subscribeStatus (subscribed: boolean) {
159 const accumulator: string[] = [] 159 const accumulator: string[] = []
160 for (const [key, value] of this.subscribed.entries()) { 160 for (const [ key, value ] of this.subscribed.entries()) {
161 if (value === subscribed) accumulator.push(key) 161 if (value === subscribed) accumulator.push(key)
162 } 162 }
163 163
diff --git a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
index bb44660d2..e4fc09b36 100644
--- a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
+++ b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
@@ -46,8 +46,8 @@ export class UserSubscriptionService {
46 } 46 }
47 47
48 getUserSubscriptionVideos (parameters: { 48 getUserSubscriptionVideos (parameters: {
49 videoPagination: ComponentPaginationLight, 49 videoPagination: ComponentPaginationLight
50 sort: VideoSortField, 50 sort: VideoSortField
51 skipCount?: boolean 51 skipCount?: boolean
52 }): Observable<ResultList<Video>> { 52 }): Observable<ResultList<Video>> {
53 const { videoPagination, sort, skipCount } = parameters 53 const { videoPagination, sort, skipCount } = parameters
@@ -131,16 +131,16 @@ export class UserSubscriptionService {
131 131
132 listenToSubscriptionCacheChange (nameWithHost: string) { 132 listenToSubscriptionCacheChange (nameWithHost: string) {
133 if (nameWithHost in this.myAccountSubscriptionCacheObservable) { 133 if (nameWithHost in this.myAccountSubscriptionCacheObservable) {
134 return this.myAccountSubscriptionCacheObservable[ nameWithHost ] 134 return this.myAccountSubscriptionCacheObservable[nameWithHost]
135 } 135 }
136 136
137 const obs = this.existsObservable 137 const obs = this.existsObservable
138 .pipe( 138 .pipe(
139 filter(existsResult => existsResult[ nameWithHost ] !== undefined), 139 filter(existsResult => existsResult[nameWithHost] !== undefined),
140 map(existsResult => existsResult[ nameWithHost ]) 140 map(existsResult => existsResult[nameWithHost])
141 ) 141 )
142 142
143 this.myAccountSubscriptionCacheObservable[ nameWithHost ] = obs 143 this.myAccountSubscriptionCacheObservable[nameWithHost] = obs
144 return obs 144 return obs
145 } 145 }
146 146
@@ -150,16 +150,16 @@ export class UserSubscriptionService {
150 if (nameWithHost in this.myAccountSubscriptionCache) { 150 if (nameWithHost in this.myAccountSubscriptionCache) {
151 logger('Found cache for %d.', nameWithHost) 151 logger('Found cache for %d.', nameWithHost)
152 152
153 return of(this.myAccountSubscriptionCache[ nameWithHost ]) 153 return of(this.myAccountSubscriptionCache[nameWithHost])
154 } 154 }
155 155
156 this.existsSubject.next(nameWithHost) 156 this.existsSubject.next(nameWithHost)
157 157
158 logger('Fetching from network for %d.', nameWithHost) 158 logger('Fetching from network for %d.', nameWithHost)
159 return this.existsObservable.pipe( 159 return this.existsObservable.pipe(
160 filter(existsResult => existsResult[ nameWithHost ] !== undefined), 160 filter(existsResult => existsResult[nameWithHost] !== undefined),
161 map(existsResult => existsResult[ nameWithHost ]), 161 map(existsResult => existsResult[nameWithHost]),
162 tap(result => this.myAccountSubscriptionCache[ nameWithHost ] = result) 162 tap(result => this.myAccountSubscriptionCache[nameWithHost] = result)
163 ) 163 )
164 } 164 }
165 165
diff --git a/client/src/app/shared/shared-video-comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts
index ba0f57e8f..adab4cfbd 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.model.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.model.ts
@@ -1,6 +1,10 @@
1import { getAbsoluteAPIUrl } from '@app/helpers' 1import { getAbsoluteAPIUrl } from '@app/helpers'
2import { Account, Actor, Video } from '@app/shared/shared-main' 2import { Actor, Video } from '@app/shared/shared-main'
3import { Account as AccountInterface, VideoComment as VideoCommentServerModel, VideoCommentAdmin as VideoCommentAdminServerModel } from '@shared/models' 3import {
4 Account as AccountInterface,
5 VideoComment as VideoCommentServerModel,
6 VideoCommentAdmin as VideoCommentAdminServerModel
7} from '@shared/models'
4 8
5export class VideoComment implements VideoCommentServerModel { 9export class VideoComment implements VideoCommentServerModel {
6 id: number 10 id: number
diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts
index 4f1452116..5550c96e4 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.service.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts
@@ -37,8 +37,8 @@ export class VideoCommentService {
37 37
38 return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) 38 return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment)
39 .pipe( 39 .pipe(
40 map(data => this.extractVideoComment(data.comment)), 40 map(data => this.extractVideoComment(data.comment)),
41 catchError(err => this.restExtractor.handleError(err)) 41 catchError(err => this.restExtractor.handleError(err))
42 ) 42 )
43 } 43 }
44 44
@@ -54,8 +54,8 @@ export class VideoCommentService {
54 } 54 }
55 55
56 getAdminVideoComments (options: { 56 getAdminVideoComments (options: {
57 pagination: RestPagination, 57 pagination: RestPagination
58 sort: SortMeta, 58 sort: SortMeta
59 search?: string 59 search?: string
60 }): Observable<ResultList<VideoCommentAdmin>> { 60 }): Observable<ResultList<VideoCommentAdmin>> {
61 const { pagination, sort, search } = options 61 const { pagination, sort, search } = options
@@ -75,8 +75,8 @@ export class VideoCommentService {
75 } 75 }
76 76
77 getVideoCommentThreads (parameters: { 77 getVideoCommentThreads (parameters: {
78 videoId: number | string, 78 videoId: number | string
79 componentPagination: ComponentPaginationLight, 79 componentPagination: ComponentPaginationLight
80 sort: string 80 sort: string
81 }): Observable<ThreadsResultList<VideoComment>> { 81 }): Observable<ThreadsResultList<VideoComment>> {
82 const { videoId, componentPagination, sort } = parameters 82 const { videoId, componentPagination, sort } = parameters
@@ -95,7 +95,7 @@ export class VideoCommentService {
95 } 95 }
96 96
97 getVideoThreadComments (parameters: { 97 getVideoThreadComments (parameters: {
98 videoId: number | string, 98 videoId: number | string
99 threadId: number 99 threadId: number
100 }): Observable<VideoCommentThreadTree> { 100 }): Observable<VideoCommentThreadTree> {
101 const { videoId, threadId } = parameters 101 const { videoId, threadId } = parameters
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
index d8b2ee17d..f12ae2ee5 100644
--- a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
@@ -42,7 +42,7 @@ enum GroupDate {
42} 42}
43 43
44@Directive() 44@Directive()
45// tslint:disable-next-line: directive-class-suffix 45// eslint-disable-next-line @angular-eslint/directive-class-suffix
46export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterContentInit, DisableForReuseHook { 46export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterContentInit, DisableForReuseHook {
47 @ViewChild('videoListHeader', { static: true, read: ViewContainerRef }) videoListHeader: ViewContainerRef 47 @ViewChild('videoListHeader', { static: true, read: ViewContainerRef }) videoListHeader: ViewContainerRef
48 48
@@ -174,7 +174,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterConte
174 ngAfterContentInit () { 174 ngAfterContentInit () {
175 if (this.videoListHeader) { 175 if (this.videoListHeader) {
176 // some components don't use the header: they use their own template, like my-history.component.html 176 // some components don't use the header: they use their own template, like my-history.component.html
177 this.setHeader.apply(this, [ this.HeaderComponent, this.headerComponentInjector ]) 177 this.setHeader(this.HeaderComponent, this.headerComponentInjector)
178 } 178 }
179 } 179 }
180 180
@@ -278,7 +278,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterConte
278 278
279 if (currentGroupedDate !== period.value) { 279 if (currentGroupedDate !== period.value) {
280 currentGroupedDate = period.value 280 currentGroupedDate = period.value
281 this.groupedDates[ video.id ] = currentGroupedDate 281 this.groupedDates[video.id] = currentGroupedDate
282 } 282 }
283 283
284 break 284 break
@@ -302,13 +302,13 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterConte
302 i: Injector = this.headerComponentInjector 302 i: Injector = this.headerComponentInjector
303 ) { 303 ) {
304 const injector = i || Injector.create({ 304 const injector = i || Injector.create({
305 providers: [{ 305 providers: [ {
306 provide: 'data', 306 provide: 'data',
307 useValue: { 307 useValue: {
308 titlePage: this.titlePage, 308 titlePage: this.titlePage,
309 titleTooltip: this.titleTooltip 309 titleTooltip: this.titleTooltip
310 } 310 }
311 }] 311 } ]
312 }) 312 })
313 const viewContainerRef = this.videoListHeader 313 const viewContainerRef = this.videoListHeader
314 viewContainerRef.clear() 314 viewContainerRef.clear()
@@ -331,9 +331,9 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterConte
331 protected loadPageRouteParams (_queryParams: Params) { /* empty */ } 331 protected loadPageRouteParams (_queryParams: Params) { /* empty */ }
332 332
333 protected loadRouteParams (queryParams: Params) { 333 protected loadRouteParams (queryParams: Params) {
334 this.sort = queryParams[ 'sort' ] as VideoSortField || this.defaultSort 334 this.sort = queryParams['sort'] as VideoSortField || this.defaultSort
335 this.categoryOneOf = queryParams[ 'categoryOneOf' ] 335 this.categoryOneOf = queryParams['categoryOneOf']
336 this.angularState = queryParams[ 'a-state' ] 336 this.angularState = queryParams['a-state']
337 337
338 this.loadPageRouteParams(queryParams) 338 this.loadPageRouteParams(queryParams)
339 } 339 }
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts
index 28fefb9dd..5328f5170 100644
--- a/client/src/app/shared/shared-video-miniature/video-download.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts
@@ -210,10 +210,10 @@ export class VideoDownloadComponent {
210 210
211 private getMetadataFormat (format: any) { 211 private getMetadataFormat (format: any) {
212 const keyToTranslateFunction = { 212 const keyToTranslateFunction = {
213 'encoder': (value: string) => ({ label: $localize`Encoder`, value }), 213 encoder: (value: string) => ({ label: $localize`Encoder`, value }),
214 'format_long_name': (value: string) => ({ label: $localize`Format name`, value }), 214 format_long_name: (value: string) => ({ label: $localize`Format name`, value }),
215 'size': (value: number) => ({ label: $localize`Size`, value: this.bytesPipe.transform(value, 2) }), 215 size: (value: number) => ({ label: $localize`Size`, value: this.bytesPipe.transform(value, 2) }),
216 'bit_rate': (value: number) => ({ 216 bit_rate: (value: number) => ({
217 label: $localize`Bitrate`, 217 label: $localize`Bitrate`,
218 value: `${this.numbersPipe.transform(value)}bps` 218 value: `${this.numbersPipe.transform(value)}bps`
219 }) 219 })
@@ -234,9 +234,9 @@ export class VideoDownloadComponent {
234 if (!stream) return undefined 234 if (!stream) return undefined
235 235
236 let keyToTranslateFunction = { 236 let keyToTranslateFunction = {
237 'codec_long_name': (value: string) => ({ label: $localize`Codec`, value }), 237 codec_long_name: (value: string) => ({ label: $localize`Codec`, value }),
238 'profile': (value: string) => ({ label: $localize`Profile`, value }), 238 profile: (value: string) => ({ label: $localize`Profile`, value }),
239 'bit_rate': (value: number) => ({ 239 bit_rate: (value: number) => ({
240 label: $localize`Bitrate`, 240 label: $localize`Bitrate`,
241 value: `${this.numbersPipe.transform(value)}bps` 241 value: `${this.numbersPipe.transform(value)}bps`
242 }) 242 })
@@ -244,15 +244,15 @@ export class VideoDownloadComponent {
244 244
245 if (type === 'video') { 245 if (type === 'video') {
246 keyToTranslateFunction = Object.assign(keyToTranslateFunction, { 246 keyToTranslateFunction = Object.assign(keyToTranslateFunction, {
247 'width': (value: number) => ({ label: $localize`Resolution`, value: `${value}x${stream.height}` }), 247 width: (value: number) => ({ label: $localize`Resolution`, value: `${value}x${stream.height}` }),
248 'display_aspect_ratio': (value: string) => ({ label: $localize`Aspect ratio`, value }), 248 display_aspect_ratio: (value: string) => ({ label: $localize`Aspect ratio`, value }),
249 'avg_frame_rate': (value: string) => ({ label: $localize`Average frame rate`, value }), 249 avg_frame_rate: (value: string) => ({ label: $localize`Average frame rate`, value }),
250 'pix_fmt': (value: string) => ({ label: $localize`Pixel format`, value }) 250 pix_fmt: (value: string) => ({ label: $localize`Pixel format`, value })
251 }) 251 })
252 } else { 252 } else {
253 keyToTranslateFunction = Object.assign(keyToTranslateFunction, { 253 keyToTranslateFunction = Object.assign(keyToTranslateFunction, {
254 'sample_rate': (value: number) => ({ label: $localize`Sample rate`, value }), 254 sample_rate: (value: number) => ({ label: $localize`Sample rate`, value }),
255 'channel_layout': (value: number) => ({ label: $localize`Channel Layout`, value }) 255 channel_layout: (value: number) => ({ label: $localize`Channel Layout`, value })
256 }) 256 })
257 } 257 }
258 258
diff --git a/client/src/app/shared/shared-video-miniature/video-list-header.component.ts b/client/src/app/shared/shared-video-miniature/video-list-header.component.ts
index 08a961be1..fed696672 100644
--- a/client/src/app/shared/shared-video-miniature/video-list-header.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-list-header.component.ts
@@ -11,7 +11,7 @@ export abstract class GenericHeaderComponent {
11 11
12@Component({ 12@Component({
13 selector: 'my-video-list-header', 13 selector: 'my-video-list-header',
14 // tslint:disable-next-line:use-component-view-encapsulation 14 // eslint-disable-next-line @angular-eslint/use-component-view-encapsulation
15 encapsulation: ViewEncapsulation.None, 15 encapsulation: ViewEncapsulation.None,
16 templateUrl: './video-list-header.component.html' 16 templateUrl: './video-list-header.component.html'
17}) 17})
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
index d64ee9b98..456b36926 100644
--- a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
@@ -108,7 +108,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
108 } 108 }
109 109
110 isInSelectionMode () { 110 isInSelectionMode () {
111 return Object.keys(this._selection).some(k => this._selection[ k ] === true) 111 return Object.keys(this._selection).some(k => this._selection[k] === true)
112 } 112 }
113 113
114 generateSyndicationList () { 114 generateSyndicationList () {
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
index df3aeddb7..553930595 100644
--- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
@@ -191,7 +191,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
191 } 191 }
192 192
193 createPlaylist () { 193 createPlaylist () {
194 const displayName = this.form.value[ 'displayName' ] 194 const displayName = this.form.value['displayName']
195 195
196 const videoPlaylistCreate: VideoPlaylistCreate = { 196 const videoPlaylistCreate: VideoPlaylistCreate = {
197 displayName, 197 displayName,
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts
index f25f10ede..b661378bd 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts
@@ -11,7 +11,7 @@ export class VideoPlaylistElement implements ServerVideoPlaylistElement {
11 11
12 video?: Video 12 video?: Video
13 13
14 constructor (hash: ServerVideoPlaylistElement, translations: {}) { 14 constructor (hash: ServerVideoPlaylistElement, translations: { [ id: string ]: string } = {}) {
15 this.id = hash.id 15 this.id = hash.id
16 this.position = hash.position 16 this.position = hash.position
17 this.startTimestamp = hash.startTimestamp 17 this.startTimestamp = hash.startTimestamp
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
index fcc2ce705..6b38d9ca3 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
@@ -48,7 +48,7 @@ export class VideoPlaylist implements ServerVideoPlaylist {
48 return buildPlaylistWatchPath({ shortUUID: playlist.shortUUID || playlist.uuid }) 48 return buildPlaylistWatchPath({ shortUUID: playlist.shortUUID || playlist.uuid })
49 } 49 }
50 50
51 constructor (hash: ServerVideoPlaylist, translations: {}) { 51 constructor (hash: ServerVideoPlaylist, translations: { [ id: string ]: string }) {
52 const absoluteAPIUrl = getAbsoluteAPIUrl() 52 const absoluteAPIUrl = getAbsoluteAPIUrl()
53 53
54 this.id = hash.id 54 this.id = hash.id
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts
index a3f1393ff..0a01af593 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts
@@ -279,18 +279,18 @@ export class VideoPlaylistService {
279 } 279 }
280 280
281 listenToVideoPlaylistChange (videoId: number) { 281 listenToVideoPlaylistChange (videoId: number) {
282 if (this.videoExistsObservableCache[ videoId ]) { 282 if (this.videoExistsObservableCache[videoId]) {
283 return this.videoExistsObservableCache[ videoId ] 283 return this.videoExistsObservableCache[videoId]
284 } 284 }
285 285
286 const obs = this.videoExistsInPlaylistObservable 286 const obs = this.videoExistsInPlaylistObservable
287 .pipe( 287 .pipe(
288 map(existsResult => existsResult[ videoId ]), 288 map(existsResult => existsResult[videoId]),
289 filter(r => !!r), 289 filter(r => !!r),
290 tap(result => this.videoExistsCache[ videoId ] = result) 290 tap(result => this.videoExistsCache[videoId] = result)
291 ) 291 )
292 292
293 this.videoExistsObservableCache[ videoId ] = obs 293 this.videoExistsObservableCache[videoId] = obs
294 return obs 294 return obs
295 } 295 }
296 296
diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
index 78f0944ef..a1b07aea6 100644
--- a/client/src/assets/player/p2p-media-loader/hls-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
@@ -13,6 +13,8 @@ type Metadata = {
13 levels: Level[] 13 levels: Level[]
14} 14}
15 15
16type HookFn = (player: videojs.Player, hljs: Hlsjs) => void
17
16const registerSourceHandler = function (vjs: typeof videojs) { 18const registerSourceHandler = function (vjs: typeof videojs) {
17 if (!Hlsjs.isSupported()) { 19 if (!Hlsjs.isSupported()) {
18 console.warn('Hls.js is not supported in this browser!') 20 console.warn('Hls.js is not supported in this browser!')
@@ -82,7 +84,7 @@ const registerConfigPlugin = function (vjs: typeof videojs) {
82} 84}
83 85
84class Html5Hlsjs { 86class Html5Hlsjs {
85 private static readonly hooks: { [id: string]: Function[] } = {} 87 private static readonly hooks: { [id: string]: HookFn[] } = {}
86 88
87 private readonly videoElement: HTMLVideoElement 89 private readonly videoElement: HTMLVideoElement
88 private readonly errorCounts: ErrorCounts = {} 90 private readonly errorCounts: ErrorCounts = {}
@@ -131,7 +133,8 @@ class Html5Hlsjs {
131 errorTxt = 'You aborted the video playback' 133 errorTxt = 'You aborted the video playback'
132 break 134 break
133 case mediaError.MEDIA_ERR_DECODE: 135 case mediaError.MEDIA_ERR_DECODE:
134 errorTxt = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support' 136 errorTxt = 'The video playback was aborted due to a corruption problem or because the video used features ' +
137 'your browser did not support'
135 this._handleMediaError(mediaError) 138 this._handleMediaError(mediaError)
136 break 139 break
137 case mediaError.MEDIA_ERR_NETWORK: 140 case mediaError.MEDIA_ERR_NETWORK:
@@ -182,58 +185,57 @@ class Html5Hlsjs {
182 this.hls.destroy() 185 this.hls.destroy()
183 } 186 }
184 187
185 static addHook (type: string, callback: Function) { 188 static addHook (type: string, callback: HookFn) {
186 Html5Hlsjs.hooks[ type ] = this.hooks[ type ] || [] 189 Html5Hlsjs.hooks[type] = this.hooks[type] || []
187 Html5Hlsjs.hooks[ type ].push(callback) 190 Html5Hlsjs.hooks[type].push(callback)
188 } 191 }
189 192
190 static removeHook (type: string, callback: Function) { 193 static removeHook (type: string, callback: HookFn) {
191 if (Html5Hlsjs.hooks[ type ] === undefined) return false 194 if (Html5Hlsjs.hooks[type] === undefined) return false
192 195
193 const index = Html5Hlsjs.hooks[ type ].indexOf(callback) 196 const index = Html5Hlsjs.hooks[type].indexOf(callback)
194 if (index === -1) return false 197 if (index === -1) return false
195 198
196 Html5Hlsjs.hooks[ type ].splice(index, 1) 199 Html5Hlsjs.hooks[type].splice(index, 1)
197 200
198 return true 201 return true
199 } 202 }
200 203
201 private _executeHooksFor (type: string) { 204 private _executeHooksFor (type: string) {
202 if (Html5Hlsjs.hooks[ type ] === undefined) { 205 if (Html5Hlsjs.hooks[type] === undefined) {
203 return 206 return
204 } 207 }
205 208
206 // ES3 and IE < 9 209 // ES3 and IE < 9
207 for (let i = 0; i < Html5Hlsjs.hooks[ type ].length; i++) { 210 for (let i = 0; i < Html5Hlsjs.hooks[type].length; i++) {
208 Html5Hlsjs.hooks[ type ][ i ](this.player, this.hls) 211 Html5Hlsjs.hooks[type][i](this.player, this.hls)
209 } 212 }
210 } 213 }
211 214
212 private _handleMediaError (error: any) { 215 private _handleMediaError (error: any) {
213 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] === 1) { 216 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) {
214 console.info('trying to recover media error') 217 console.info('trying to recover media error')
215 this.hls.recoverMediaError() 218 this.hls.recoverMediaError()
216 return 219 return
217 } 220 }
218 221
219 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] === 2) { 222 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 2) {
220 console.info('2nd try to recover media error (by swapping audio codec') 223 console.info('2nd try to recover media error (by swapping audio codec')
221 this.hls.swapAudioCodec() 224 this.hls.swapAudioCodec()
222 this.hls.recoverMediaError() 225 this.hls.recoverMediaError()
223 return 226 return
224 } 227 }
225 228
226 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] > 2) { 229 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
227 console.info('bubbling media error up to VIDEOJS') 230 console.info('bubbling media error up to VIDEOJS')
228 this.hls.destroy() 231 this.hls.destroy()
229 this.tech.error = () => error 232 this.tech.error = () => error
230 this.tech.trigger('error') 233 this.tech.trigger('error')
231 return
232 } 234 }
233 } 235 }
234 236
235 private _handleNetworkError (error: any) { 237 private _handleNetworkError (error: any) {
236 if (this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] <= 5) { 238 if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= 5) {
237 console.info('trying to recover network error') 239 console.info('trying to recover network error')
238 240
239 // Wait 1 second and retry 241 // Wait 1 second and retry
@@ -241,7 +243,7 @@ class Html5Hlsjs {
241 243
242 // Reset error count on success 244 // Reset error count on success
243 this.hls.once(Hlsjs.Events.FRAG_LOADED, () => { 245 this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
244 this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] = 0 246 this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] = 0
245 }) 247 })
246 248
247 return 249 return
@@ -259,8 +261,8 @@ class Html5Hlsjs {
259 } 261 }
260 262
261 // increment/set error count 263 // increment/set error count
262 if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1 264 if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1
263 else this.errorCounts[ data.type ] = 1 265 else this.errorCounts[data.type] = 1
264 266
265 if (data.fatal) console.warn(error.message) 267 if (data.fatal) console.warn(error.message)
266 else console.error(error.message, data) 268 else console.error(error.message, data)
@@ -300,7 +302,7 @@ class Html5Hlsjs {
300 let isAuto = true 302 let isAuto = true
301 303
302 for (let i = 0; i < qualityLevels.length; i++) { 304 for (let i = 0; i < qualityLevels.length; i++) {
303 if (!qualityLevels[ i ]._enabled) { 305 if (!qualityLevels[i]._enabled) {
304 isAuto = false 306 isAuto = false
305 break 307 break
306 } 308 }
@@ -316,7 +318,7 @@ class Html5Hlsjs {
316 let selectedTrack: number 318 let selectedTrack: number
317 319
318 for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) { 320 for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) {
319 if (qualityLevels[ selectedTrack ]._enabled) { 321 if (qualityLevels[selectedTrack]._enabled) {
320 break 322 break
321 } 323 }
322 } 324 }
@@ -327,11 +329,11 @@ class Html5Hlsjs {
327 private _handleQualityLevels () { 329 private _handleQualityLevels () {
328 if (!this.metadata) return 330 if (!this.metadata) return
329 331
330 const qualityLevels = this.player.qualityLevels && this.player.qualityLevels() 332 const qualityLevels = this.player.qualityLevels?.()
331 if (!qualityLevels) return 333 if (!qualityLevels) return
332 334
333 for (let i = 0; i < this.metadata.levels.length; i++) { 335 for (let i = 0; i < this.metadata.levels.length; i++) {
334 const details = this.metadata.levels[ i ] 336 const details = this.metadata.levels[i]
335 const representation: QualityLevelRepresentation = { 337 const representation: QualityLevelRepresentation = {
336 id: i, 338 id: i,
337 width: details.width, 339 width: details.width,
@@ -345,11 +347,11 @@ class Html5Hlsjs {
345 representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) { 347 representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) {
346 // Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on) 348 // Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on)
347 if (typeof toggle === 'boolean') { 349 if (typeof toggle === 'boolean') {
348 this[ level ]._enabled = toggle 350 this[level]._enabled = toggle
349 self._relayQualityChange(this) 351 self._relayQualityChange(this)
350 } 352 }
351 353
352 return this[ level ]._enabled 354 return this[level]._enabled
353 } 355 }
354 356
355 qualityLevels.addQualityLevel(representation) 357 qualityLevels.addQualityLevel(representation)
@@ -395,7 +397,7 @@ class Html5Hlsjs {
395 const playerAudioTracks = this.tech.audioTracks() 397 const playerAudioTracks = this.tech.audioTracks()
396 for (let j = 0; j < playerAudioTracks.length; j++) { 398 for (let j = 0; j < playerAudioTracks.length; j++) {
397 // FIXME: typings 399 // FIXME: typings
398 if ((playerAudioTracks[ j ] as any).enabled) { 400 if ((playerAudioTracks[j] as any).enabled) {
399 this.hls.audioTrack = j 401 this.hls.audioTrack = j
400 break 402 break
401 } 403 }
@@ -412,8 +414,8 @@ class Html5Hlsjs {
412 playerAudioTracks.addTrack(new this.vjs.AudioTrack({ 414 playerAudioTracks.addTrack(new this.vjs.AudioTrack({
413 id: i.toString(), 415 id: i.toString(),
414 kind: 'alternative', 416 kind: 'alternative',
415 label: hlsAudioTracks[ i ].name || hlsAudioTracks[ i ].lang, 417 label: hlsAudioTracks[i].name || hlsAudioTracks[i].lang,
416 language: hlsAudioTracks[ i ].lang, 418 language: hlsAudioTracks[i].lang,
417 enabled: i === this.hls.audioTrack 419 enabled: i === this.hls.audioTrack
418 })) 420 }))
419 } 421 }
@@ -430,8 +432,8 @@ class Html5Hlsjs {
430 } 432 }
431 433
432 private _isSameTextTrack (track1: TextTrack, track2: TextTrack) { 434 private _isSameTextTrack (track1: TextTrack, track2: TextTrack) {
433 return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2) 435 return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2) &&
434 && track1.kind === track2.kind 436 track1.kind === track2.kind
435 } 437 }
436 438
437 private _updateSelectedTextTrack () { 439 private _updateSelectedTextTrack () {
@@ -439,16 +441,16 @@ class Html5Hlsjs {
439 let activeTrack: TextTrack = null 441 let activeTrack: TextTrack = null
440 442
441 for (let j = 0; j < playerTextTracks.length; j++) { 443 for (let j = 0; j < playerTextTracks.length; j++) {
442 if (playerTextTracks[ j ].mode === 'showing') { 444 if (playerTextTracks[j].mode === 'showing') {
443 activeTrack = playerTextTracks[ j ] 445 activeTrack = playerTextTracks[j]
444 break 446 break
445 } 447 }
446 } 448 }
447 449
448 const hlsjsTracks = this.videoElement.textTracks 450 const hlsjsTracks = this.videoElement.textTracks
449 for (let k = 0; k < hlsjsTracks.length; k++) { 451 for (let k = 0; k < hlsjsTracks.length; k++) {
450 if (hlsjsTracks[ k ].kind === 'subtitles' || hlsjsTracks[ k ].kind === 'captions') { 452 if (hlsjsTracks[k].kind === 'subtitles' || hlsjsTracks[k].kind === 'captions') {
451 hlsjsTracks[ k ].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[ k ], activeTrack) 453 hlsjsTracks[k].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[k], activeTrack)
452 ? 'showing' 454 ? 'showing'
453 : 'disabled' 455 : 'disabled'
454 } 456 }
@@ -460,11 +462,11 @@ class Html5Hlsjs {
460 this.videoElement.removeEventListener('play', this.handlers.play) 462 this.videoElement.removeEventListener('play', this.handlers.play)
461 } 463 }
462 464
463 private _oneLevelObjClone (obj: object) { 465 private _oneLevelObjClone (obj: { [ id: string ]: any }) {
464 const result = {} 466 const result = {}
465 const objKeys = Object.keys(obj) 467 const objKeys = Object.keys(obj)
466 for (let i = 0; i < objKeys.length; i++) { 468 for (let i = 0; i < objKeys.length; i++) {
467 result[ objKeys[ i ] ] = obj[ objKeys[ i ] ] 469 result[objKeys[i]] = obj[objKeys[i]]
468 } 470 }
469 471
470 return result 472 return result
@@ -475,8 +477,8 @@ class Html5Hlsjs {
475 477
476 // Filter out tracks that is displayable (captions or subtitles) 478 // Filter out tracks that is displayable (captions or subtitles)
477 for (let idx = 0; idx < textTracks.length; idx++) { 479 for (let idx = 0; idx < textTracks.length; idx++) {
478 if (textTracks[ idx ].kind === 'subtitles' || textTracks[ idx ].kind === 'captions') { 480 if (textTracks[idx].kind === 'subtitles' || textTracks[idx].kind === 'captions') {
479 displayableTracks.push(textTracks[ idx ]) 481 displayableTracks.push(textTracks[idx])
480 } 482 }
481 } 483 }
482 484
@@ -493,14 +495,14 @@ class Html5Hlsjs {
493 let isAdded = false 495 let isAdded = false
494 496
495 for (let jdx = 0; jdx < playerTextTracks.length; jdx++) { 497 for (let jdx = 0; jdx < playerTextTracks.length; jdx++) {
496 if (this._isSameTextTrack(displayableTracks[ idx ], playerTextTracks[ jdx ])) { 498 if (this._isSameTextTrack(displayableTracks[idx], playerTextTracks[jdx])) {
497 isAdded = true 499 isAdded = true
498 break 500 break
499 } 501 }
500 } 502 }
501 503
502 if (!isAdded) { 504 if (!isAdded) {
503 const hlsjsTextTrack = displayableTracks[ idx ] 505 const hlsjsTextTrack = displayableTracks[idx]
504 this.player.addRemoteTextTrack({ 506 this.player.addRemoteTextTrack({
505 kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind, 507 kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind,
506 label: this._getTextTrackLabel(hlsjsTextTrack), 508 label: this._getTextTrackLabel(hlsjsTextTrack),
@@ -536,12 +538,12 @@ class Html5Hlsjs {
536 const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue 538 const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue
537 539
538 for (let r = 0; r < captionScreen.rows.length; r++) { 540 for (let r = 0; r < captionScreen.rows.length; r++) {
539 row = captionScreen.rows[ r ] 541 row = captionScreen.rows[r]
540 text = '' 542 text = ''
541 543
542 if (!row.isEmpty()) { 544 if (!row.isEmpty()) {
543 for (let c = 0; c < row.chars.length; c++) { 545 for (let c = 0; c < row.chars.length; c++) {
544 text += row.chars[ c ].ucharj 546 text += row.chars[c].ucharj
545 } 547 }
546 548
547 cue = new VTTCue(startTime, endTime, text.trim()) 549 cue = new VTTCue(startTime, endTime, text.trim())
@@ -552,7 +554,7 @@ class Html5Hlsjs {
552 const configKeys = Object.keys(captionConfig) 554 const configKeys = Object.keys(captionConfig)
553 555
554 for (let k = 0; k < configKeys.length; k++) { 556 for (let k = 0; k < configKeys.length; k++) {
555 cue[ configKeys[ k ] ] = captionConfig[ configKeys[ k ] ] 557 cue[configKeys[k]] = captionConfig[configKeys[k]]
556 } 558 }
557 } 559 }
558 track.addCue(cue) 560 track.addCue(cue)
@@ -567,7 +569,7 @@ class Html5Hlsjs {
567 const techOptions = this.tech.options_ as HlsjsConfigHandlerOptions 569 const techOptions = this.tech.options_ as HlsjsConfigHandlerOptions
568 const srOptions_ = this.player.srOptions_ 570 const srOptions_ = this.player.srOptions_
569 571
570 const hlsjsConfigRef = srOptions_ && srOptions_.hlsjsConfig || techOptions.hlsjsConfig 572 const hlsjsConfigRef = srOptions_?.hlsjsConfig || techOptions.hlsjsConfig
571 // Hls.js will write to the reference thus change the object for later streams 573 // Hls.js will write to the reference thus change the object for later streams
572 this.hlsjsConfig = hlsjsConfigRef ? this._oneLevelObjClone(hlsjsConfigRef) : {} 574 this.hlsjsConfig = hlsjsConfigRef ? this._oneLevelObjClone(hlsjsConfigRef) : {}
573 575
@@ -575,7 +577,7 @@ class Html5Hlsjs {
575 this.hlsjsConfig.autoStartLoad = false 577 this.hlsjsConfig.autoStartLoad = false
576 } 578 }
577 579
578 const captionConfig = srOptions_ && srOptions_.captionConfig || techOptions.captionConfig 580 const captionConfig = srOptions_?.captionConfig || techOptions.captionConfig
579 if (captionConfig) { 581 if (captionConfig) {
580 this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig) 582 this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig)
581 } 583 }
diff --git a/client/src/assets/player/p2p-media-loader/segment-validator.ts b/client/src/assets/player/p2p-media-loader/segment-validator.ts
index d0a4c4a3f..f7f83a8a4 100644
--- a/client/src/assets/player/p2p-media-loader/segment-validator.ts
+++ b/client/src/assets/player/p2p-media-loader/segment-validator.ts
@@ -86,6 +86,7 @@ async function sha256Hex (data?: ArrayBuffer) {
86 86
87 // Fallback for non HTTPS context 87 // Fallback for non HTTPS context
88 const shaModule = (await import('sha.js') as any).default 88 const shaModule = (await import('sha.js') as any).default
89 // eslint-disable-next-line new-cap
89 return new shaModule.sha256().update(Buffer.from(data)).digest('hex') 90 return new shaModule.sha256().update(Buffer.from(data)).digest('hex')
90} 91}
91 92
@@ -97,7 +98,9 @@ function bufferToHex (buffer?: ArrayBuffer) {
97 const h = '0123456789abcdef' 98 const h = '0123456789abcdef'
98 const o = new Uint8Array(buffer) 99 const o = new Uint8Array(buffer)
99 100
100 o.forEach((v: any) => s += h[ v >> 4 ] + h[ v & 15 ]) 101 o.forEach((v: any) => {
102 s += h[v >> 4] + h[v & 15]
103 })
101 104
102 return s 105 return s
103} 106}
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index c45e8f53e..f3c21fc4c 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -435,8 +435,6 @@ export class PeertubePlayerManager {
435 const p2pMediaLoaderOptions = options.p2pMediaLoader 435 const p2pMediaLoaderOptions = options.p2pMediaLoader
436 436
437 const autoplay = this.getAutoPlayValue(commonOptions.autoplay) === 'play' 437 const autoplay = this.getAutoPlayValue(commonOptions.autoplay) === 'play'
438 ? true
439 : false
440 438
441 const webtorrent = { 439 const webtorrent = {
442 autoplay, 440 autoplay,
@@ -459,10 +457,10 @@ export class PeertubePlayerManager {
459 theaterButton: boolean 457 theaterButton: boolean
460 captions: boolean 458 captions: boolean
461 459
462 nextVideo?: Function 460 nextVideo?: () => void
463 hasNextVideo?: () => boolean 461 hasNextVideo?: () => boolean
464 462
465 previousVideo?: Function 463 previousVideo?: () => void
466 hasPreviousVideo?: () => boolean 464 hasPreviousVideo?: () => boolean
467 }) { 465 }) {
468 const settingEntries = [] 466 const settingEntries = []
@@ -487,7 +485,7 @@ export class PeertubePlayerManager {
487 } 485 }
488 486
489 Object.assign(children, { 487 Object.assign(children, {
490 'previousVideoButton': buttonOptions 488 previousVideoButton: buttonOptions
491 }) 489 })
492 } 490 }
493 491
@@ -505,35 +503,35 @@ export class PeertubePlayerManager {
505 } 503 }
506 504
507 Object.assign(children, { 505 Object.assign(children, {
508 'nextVideoButton': buttonOptions 506 nextVideoButton: buttonOptions
509 }) 507 })
510 } 508 }
511 509
512 Object.assign(children, { 510 Object.assign(children, {
513 'currentTimeDisplay': {}, 511 currentTimeDisplay: {},
514 'timeDivider': {}, 512 timeDivider: {},
515 'durationDisplay': {}, 513 durationDisplay: {},
516 'liveDisplay': {}, 514 liveDisplay: {},
517 515
518 'flexibleWidthSpacer': {}, 516 flexibleWidthSpacer: {},
519 'progressControl': { 517 progressControl: {
520 children: { 518 children: {
521 'seekBar': { 519 seekBar: {
522 children: { 520 children: {
523 [loadProgressBar]: {}, 521 [loadProgressBar]: {},
524 'mouseTimeDisplay': {}, 522 mouseTimeDisplay: {},
525 'playProgressBar': {} 523 playProgressBar: {}
526 } 524 }
527 } 525 }
528 } 526 }
529 }, 527 },
530 528
531 'p2PInfoButton': {}, 529 p2PInfoButton: {},
532 530
533 'muteToggle': {}, 531 muteToggle: {},
534 'volumeControl': {}, 532 volumeControl: {},
535 533
536 'settingsButton': { 534 settingsButton: {
537 setup: { 535 setup: {
538 maxHeightOffset: 40 536 maxHeightOffset: 40
539 }, 537 },
@@ -543,18 +541,18 @@ export class PeertubePlayerManager {
543 541
544 if (options.peertubeLink === true) { 542 if (options.peertubeLink === true) {
545 Object.assign(children, { 543 Object.assign(children, {
546 'peerTubeLinkButton': { shortUUID: options.videoShortUUID } as PeerTubeLinkButtonOptions 544 peerTubeLinkButton: { shortUUID: options.videoShortUUID } as PeerTubeLinkButtonOptions
547 }) 545 })
548 } 546 }
549 547
550 if (options.theaterButton === true) { 548 if (options.theaterButton === true) {
551 Object.assign(children, { 549 Object.assign(children, {
552 'theaterButton': {} 550 theaterButton: {}
553 }) 551 })
554 } 552 }
555 553
556 Object.assign(children, { 554 Object.assign(children, {
557 'fullscreenToggle': {} 555 fullscreenToggle: {}
558 }) 556 })
559 557
560 return children 558 return children
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts
index ade8e2ee4..b4841b235 100644
--- a/client/src/assets/player/peertube-plugin.ts
+++ b/client/src/assets/player/peertube-plugin.ts
@@ -211,7 +211,7 @@ class PeerTubePlugin extends Plugin {
211 const body = new URLSearchParams() 211 const body = new URLSearchParams()
212 body.append('currentTime', currentTime.toString()) 212 body.append('currentTime', currentTime.toString())
213 213
214 const headers = new Headers({ 'Authorization': authorizationHeader }) 214 const headers = new Headers({ Authorization: authorizationHeader })
215 215
216 return fetch(url, { method: 'PUT', body, headers }) 216 return fetch(url, { method: 'PUT', body, headers })
217 } 217 }
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index f0eb129d4..97828c802 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -1,3 +1,6 @@
1// FIXME: lint
2/* eslint-disable @typescript-eslint/ban-types */
3
1import { HlsConfig, Level } from 'hls.js' 4import { HlsConfig, Level } from 'hls.js'
2import videojs from 'video.js' 5import videojs from 'video.js'
3import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models' 6import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models'
@@ -93,7 +96,7 @@ type VideoJSCaption = {
93} 96}
94 97
95type UserWatching = { 98type UserWatching = {
96 url: string, 99 url: string
97 authorizationHeader: string 100 authorizationHeader: string
98} 101}
99 102
@@ -166,7 +169,7 @@ type VideoJSPluginOptions = {
166} 169}
167 170
168type LoadedQualityData = { 171type LoadedQualityData = {
169 qualitySwitchCallback: Function, 172 qualitySwitchCallback: (resolutionId: number, type: 'video') => void
170 qualityData: { 173 qualityData: {
171 video: { 174 video: {
172 id: number 175 id: number
@@ -177,7 +180,7 @@ type LoadedQualityData = {
177} 180}
178 181
179type ResolutionUpdateData = { 182type ResolutionUpdateData = {
180 auto: boolean, 183 auto: boolean
181 resolutionId: number 184 resolutionId: number
182 id?: number 185 id?: number
183} 186}
diff --git a/client/src/assets/player/stats/stats-card.ts b/client/src/assets/player/stats/stats-card.ts
index b271d0526..a32f6fb97 100644
--- a/client/src/assets/player/stats/stats-card.ts
+++ b/client/src/assets/player/stats/stats-card.ts
@@ -39,10 +39,6 @@ class StatsCard extends Component {
39 intervalMs = 300 39 intervalMs = 300
40 playerNetworkInfo: PlayerNetworkInfo = {} 40 playerNetworkInfo: PlayerNetworkInfo = {}
41 41
42 constructor (player: videojs.Player, options: StatsCardOptions) {
43 super(player, options)
44 }
45
46 createEl () { 42 createEl () {
47 const container = super.createEl('div', { 43 const container = super.createEl('div', {
48 className: 'vjs-stats-content', 44 className: 'vjs-stats-content',
@@ -81,9 +77,8 @@ class StatsCard extends Component {
81 } 77 }
82 78
83 toggle () { 79 toggle () {
84 this.updateInterval 80 if (this.updateInterval) this.hide()
85 ? this.hide() 81 else this.show()
86 : this.show()
87 } 82 }
88 83
89 show () { 84 show () {
diff --git a/client/src/assets/player/translations-manager.ts b/client/src/assets/player/translations-manager.ts
index d5a09a31a..8a6e67dda 100644
--- a/client/src/assets/player/translations-manager.ts
+++ b/client/src/assets/player/translations-manager.ts
@@ -23,13 +23,13 @@ export class TranslationsManager {
23 23
24 let p: Promise<any> 24 let p: Promise<any>
25 25
26 if (TranslationsManager.videojsLocaleCache[ path ]) { 26 if (TranslationsManager.videojsLocaleCache[path]) {
27 p = Promise.resolve(TranslationsManager.videojsLocaleCache[ path ]) 27 p = Promise.resolve(TranslationsManager.videojsLocaleCache[path])
28 } else { 28 } else {
29 p = fetch(path + '/player.json') 29 p = fetch(path + '/player.json')
30 .then(res => res.json()) 30 .then(res => res.json())
31 .then(json => { 31 .then(json => {
32 TranslationsManager.videojsLocaleCache[ path ] = json 32 TranslationsManager.videojsLocaleCache[path] = json
33 return json 33 return json
34 }) 34 })
35 .catch(err => { 35 .catch(err => {
diff --git a/client/src/assets/player/upnext/end-card.ts b/client/src/assets/player/upnext/end-card.ts
index 8fabfc3fd..61668e407 100644
--- a/client/src/assets/player/upnext/end-card.ts
+++ b/client/src/assets/player/upnext/end-card.ts
@@ -9,7 +9,9 @@ function getMainTemplate (options: any) {
9 <div class="vjs-upnext-autoplay-icon"> 9 <div class="vjs-upnext-autoplay-icon">
10 <svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%"> 10 <svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%">
11 <circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle> 11 <circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle>
12 <circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"></circle> 12 <circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5"
13 stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"
14 ></circle>
13 <polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg> 15 <polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg>
14 </div> 16 </div>
15 <span class="vjs-upnext-bottom"> 17 <span class="vjs-upnext-bottom">
@@ -22,7 +24,7 @@ function getMainTemplate (options: any) {
22} 24}
23 25
24export interface EndCardOptions extends videojs.ComponentOptions { 26export interface EndCardOptions extends videojs.ComponentOptions {
25 next: Function, 27 next: () => void
26 getTitle: () => string 28 getTitle: () => string
27 timeout: number 29 timeout: number
28 cancelText: string 30 cancelText: string
@@ -99,11 +101,11 @@ class EndCard extends Component {
99 return container 101 return container
100 } 102 }
101 103
102 showCard (cb: Function) { 104 showCard (cb: (value: boolean) => void) {
103 let timeout: any 105 let timeout: any
104 106
105 this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart) 107 this.autoplayRing.setAttribute('stroke-dasharray', `${this.dashOffsetStart}`)
106 this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart) 108 this.autoplayRing.setAttribute('stroke-dashoffset', `${-this.dashOffsetStart}`)
107 109
108 this.title.innerHTML = this.options_.getTitle() 110 this.title.innerHTML = this.options_.getTitle()
109 111
@@ -123,7 +125,7 @@ class EndCard extends Component {
123 }) 125 })
124 126
125 const goToPercent = (percent: number) => { 127 const goToPercent = (percent: number) => {
126 const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100) 128 const newOffset = Math.max(-this.dashOffsetTotal, -this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
127 this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset) 129 this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
128 } 130 }
129 131
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index f0a1b1aee..f2e9adb14 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -17,7 +17,7 @@ function isIOS () {
17 // Detect iPad Desktop mode 17 // Detect iPad Desktop mode
18 return !!(navigator.maxTouchPoints && 18 return !!(navigator.maxTouchPoints &&
19 navigator.maxTouchPoints > 2 && 19 navigator.maxTouchPoints > 2 &&
20 /MacIntel/.test(navigator.platform)) 20 navigator.platform.includes('MacIntel'))
21} 21}
22 22
23function isSafari () { 23function isSafari () {
diff --git a/client/src/assets/player/videojs-components/p2p-info-button.ts b/client/src/assets/player/videojs-components/p2p-info-button.ts
index 81f9544f4..07ed18989 100644
--- a/client/src/assets/player/videojs-components/p2p-info-button.ts
+++ b/client/src/assets/player/videojs-components/p2p-info-button.ts
@@ -96,11 +96,11 @@ class P2pInfoButton extends Button {
96 } 96 }
97 subDivWebtorrent.title += this.player().localize('Total uploaded: ') + totalUploaded.join(' ') 97 subDivWebtorrent.title += this.player().localize('Total uploaded: ') + totalUploaded.join(' ')
98 98
99 downloadSpeedNumber.textContent = downloadSpeed[ 0 ] 99 downloadSpeedNumber.textContent = downloadSpeed[0]
100 downloadSpeedUnit.textContent = ' ' + downloadSpeed[ 1 ] 100 downloadSpeedUnit.textContent = ' ' + downloadSpeed[1]
101 101
102 uploadSpeedNumber.textContent = uploadSpeed[ 0 ] 102 uploadSpeedNumber.textContent = uploadSpeed[0]
103 uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ] 103 uploadSpeedUnit.textContent = ' ' + uploadSpeed[1]
104 104
105 peersNumber.textContent = numPeers.toString() 105 peersNumber.textContent = numPeers.toString()
106 peersText.textContent = ' ' + (numPeers > 1 ? this.player().localize('peers') : this.player_.localize('peer')) 106 peersText.textContent = ' ' + (numPeers > 1 ? this.player().localize('peers') : this.player_.localize('peer'))
diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts
index 73ad47d2b..c1f502600 100644
--- a/client/src/assets/player/videojs-components/resolution-menu-item.ts
+++ b/client/src/assets/player/videojs-components/resolution-menu-item.ts
@@ -6,7 +6,7 @@ const MenuItem = videojs.getComponent('MenuItem')
6export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions { 6export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions {
7 labels?: { [id: number]: string } 7 labels?: { [id: number]: string }
8 id: number 8 id: number
9 callback: Function 9 callback: (resolutionId: number, type: 'video') => void
10} 10}
11 11
12class ResolutionMenuItem extends MenuItem { 12class ResolutionMenuItem extends MenuItem {
@@ -14,7 +14,7 @@ class ResolutionMenuItem extends MenuItem {
14 private readonly label: string 14 private readonly label: string
15 // Only used for the automatic item 15 // Only used for the automatic item
16 private readonly labels: { [id: number]: string } 16 private readonly labels: { [id: number]: string }
17 private readonly callback: Function 17 private readonly callback: (resolutionId: number, type: 'video') => void
18 18
19 private autoResolutionPossible: boolean 19 private autoResolutionPossible: boolean
20 private currentResolutionLabel: string 20 private currentResolutionLabel: string
diff --git a/client/src/assets/player/videojs-components/settings-dialog.ts b/client/src/assets/player/videojs-components/settings-dialog.ts
index 41911e7e8..8cd98967f 100644
--- a/client/src/assets/player/videojs-components/settings-dialog.ts
+++ b/client/src/assets/player/videojs-components/settings-dialog.ts
@@ -12,8 +12,6 @@ class SettingsDialog extends Component {
12 /** 12 /**
13 * Create the component's DOM element 13 * Create the component's DOM element
14 * 14 *
15 * @return {Element}
16 * @method createEl
17 */ 15 */
18 createEl () { 16 createEl () {
19 const uniqueId = this.id() 17 const uniqueId = this.id()
@@ -25,7 +23,7 @@ class SettingsDialog extends Component {
25 innerHTML: '', 23 innerHTML: '',
26 tabIndex: -1 24 tabIndex: -1
27 }, { 25 }, {
28 'role': 'dialog', 26 role: 'dialog',
29 'aria-labelledby': dialogLabelId, 27 'aria-labelledby': dialogLabelId,
30 'aria-describedby': dialogDescriptionId 28 'aria-describedby': dialogDescriptionId
31 }) 29 })
diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts
index f1342f179..1871d41f8 100644
--- a/client/src/assets/player/videojs-components/settings-menu-item.ts
+++ b/client/src/assets/player/videojs-components/settings-menu-item.ts
@@ -1,8 +1,8 @@
1import videojs from 'video.js'
1// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu 2// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
2import { toTitleCase } from '../utils' 3import { toTitleCase } from '../utils'
3import videojs from 'video.js'
4import { SettingsButton } from './settings-menu-button'
5import { SettingsDialog } from './settings-dialog' 4import { SettingsDialog } from './settings-dialog'
5import { SettingsButton } from './settings-menu-button'
6import { SettingsPanel } from './settings-panel' 6import { SettingsPanel } from './settings-panel'
7import { SettingsPanelChild } from './settings-panel-child' 7import { SettingsPanelChild } from './settings-panel-child'
8 8
@@ -57,7 +57,7 @@ class SettingsMenuItem extends MenuItem {
57 const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this }) 57 const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this })
58 58
59 this.subMenu = new SubMenuComponent(this.player(), newOptions) as any // FIXME: typings 59 this.subMenu = new SubMenuComponent(this.player(), newOptions) as any // FIXME: typings
60 const subMenuClass = this.subMenu.buildCSSClass().split(' ')[ 0 ] 60 const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0]
61 this.settingsSubMenuEl_.className += ' ' + subMenuClass 61 this.settingsSubMenuEl_.className += ' ' + subMenuClass
62 62
63 this.eventHandlers() 63 this.eventHandlers()
@@ -104,7 +104,7 @@ class SettingsMenuItem extends MenuItem {
104 target = event.currentTarget 104 target = event.currentTarget
105 } 105 }
106 106
107 if (target && target.classList.contains('vjs-back-button')) { 107 if (target?.classList.contains('vjs-back-button')) {
108 this.loadMainMenu() 108 this.loadMainMenu()
109 return 109 return
110 } 110 }
@@ -121,8 +121,6 @@ class SettingsMenuItem extends MenuItem {
121 /** 121 /**
122 * Create the component's DOM element 122 * Create the component's DOM element
123 * 123 *
124 * @return {Element}
125 * @method createEl
126 */ 124 */
127 createEl () { 125 createEl () {
128 const el = videojs.dom.createEl('li', { 126 const el = videojs.dom.createEl('li', {
@@ -198,14 +196,14 @@ class SettingsMenuItem extends MenuItem {
198 const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ] 196 const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ]
199 197
200 for (let p = 0; p < prefix.length; p++) { 198 for (let p = 0; p < prefix.length; p++) {
201 if (!prefix[ p ]) { 199 if (!prefix[p]) {
202 type = type.toLowerCase() 200 type = type.toLowerCase()
203 } 201 }
204 202
205 if (action === 'addEvent') { 203 if (action === 'addEvent') {
206 element.addEventListener(prefix[ p ] + type, callback, false) 204 element.addEventListener(prefix[p] + type, callback, false)
207 } else if (action === 'removeEvent') { 205 } else if (action === 'removeEvent') {
208 element.removeEventListener(prefix[ p ] + type, callback, false) 206 element.removeEventListener(prefix[p] + type, callback, false)
209 } 207 }
210 } 208 }
211 } 209 }
@@ -292,7 +290,10 @@ class SettingsMenuItem extends MenuItem {
292 // Thus we get the submenu value based on the labelEl of playbackRateMenuButton 290 // Thus we get the submenu value based on the labelEl of playbackRateMenuButton
293 if (subMenu === 'PlaybackRateMenuButton') { 291 if (subMenu === 'PlaybackRateMenuButton') {
294 const html = (this.subMenu as any).labelEl_.innerHTML 292 const html = (this.subMenu as any).labelEl_.innerHTML
295 setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = html, 250) 293
294 setTimeout(() => {
295 this.settingsSubMenuValueEl_.innerHTML = html
296 }, 250)
296 } else { 297 } else {
297 // Loop trough the submenu items to find the selected child 298 // Loop trough the submenu items to find the selected child
298 for (const subMenuItem of this.subMenu.menu.children_) { 299 for (const subMenuItem of this.subMenu.menu.children_) {
diff --git a/client/src/assets/player/videojs-components/settings-panel-child.ts b/client/src/assets/player/videojs-components/settings-panel-child.ts
index d1582412c..161420c38 100644
--- a/client/src/assets/player/videojs-components/settings-panel-child.ts
+++ b/client/src/assets/player/videojs-components/settings-panel-child.ts
@@ -4,10 +4,6 @@ const Component = videojs.getComponent('Component')
4 4
5class SettingsPanelChild extends Component { 5class SettingsPanelChild extends Component {
6 6
7 constructor (player: videojs.Player, options?: videojs.ComponentOptions) {
8 super(player, options)
9 }
10
11 createEl () { 7 createEl () {
12 return super.createEl('div', { 8 return super.createEl('div', {
13 className: 'vjs-settings-panel-child', 9 className: 'vjs-settings-panel-child',
diff --git a/client/src/assets/player/videojs-components/settings-panel.ts b/client/src/assets/player/videojs-components/settings-panel.ts
index 1ad8bb1fc..28b579bdd 100644
--- a/client/src/assets/player/videojs-components/settings-panel.ts
+++ b/client/src/assets/player/videojs-components/settings-panel.ts
@@ -4,10 +4,6 @@ const Component = videojs.getComponent('Component')
4 4
5class SettingsPanel extends Component { 5class SettingsPanel extends Component {
6 6
7 constructor (player: videojs.Player, options?: videojs.ComponentOptions) {
8 super(player, options)
9 }
10
11 createEl () { 7 createEl () {
12 return super.createEl('div', { 8 return super.createEl('div', {
13 className: 'vjs-settings-panel', 9 className: 'vjs-settings-panel',
diff --git a/client/src/assets/player/webtorrent/peertube-chunk-store.ts b/client/src/assets/player/webtorrent/peertube-chunk-store.ts
index 66762bef8..93ca8e1d8 100644
--- a/client/src/assets/player/webtorrent/peertube-chunk-store.ts
+++ b/client/src/assets/player/webtorrent/peertube-chunk-store.ts
@@ -36,7 +36,7 @@ export class PeertubeChunkStore extends EventEmitter {
36 36
37 chunkLength: number 37 chunkLength: number
38 38
39 private pendingPut: { id: number, buf: Buffer, cb: Function }[] = [] 39 private pendingPut: { id: number, buf: Buffer, cb: (err?: Error) => void }[] = []
40 // If the store is full 40 // If the store is full
41 private memoryChunks: { [ id: number ]: Buffer | true } = {} 41 private memoryChunks: { [ id: number ]: Buffer | true } = {}
42 private databaseName: string 42 private databaseName: string
@@ -54,7 +54,7 @@ export class PeertubeChunkStore extends EventEmitter {
54 this.databaseName = 'webtorrent-chunks-' 54 this.databaseName = 'webtorrent-chunks-'
55 55
56 if (!opts) opts = {} 56 if (!opts) opts = {}
57 if (opts.torrent && opts.torrent.infoHash) this.databaseName += opts.torrent.infoHash 57 if (opts.torrent?.infoHash) this.databaseName += opts.torrent.infoHash
58 else this.databaseName += '-default' 58 else this.databaseName += '-default'
59 59
60 this.setMaxListeners(100) 60 this.setMaxListeners(100)
@@ -106,7 +106,9 @@ export class PeertubeChunkStore extends EventEmitter {
106 } catch (err) { 106 } catch (err) {
107 console.log('Cannot bulk insert chunks. Store them in memory.', { err }) 107 console.log('Cannot bulk insert chunks. Store them in memory.', { err })
108 108
109 processing.forEach(p => this.memoryChunks[ p.id ] = p.buf) 109 processing.forEach(p => {
110 this.memoryChunks[p.id] = p.buf
111 })
110 } finally { 112 } finally {
111 processing.forEach(p => p.cb()) 113 processing.forEach(p => p.cb())
112 } 114 }
diff --git a/client/src/assets/player/webtorrent/video-renderer.ts b/client/src/assets/player/webtorrent/video-renderer.ts
index c3cbea725..8fb3e9672 100644
--- a/client/src/assets/player/webtorrent/video-renderer.ts
+++ b/client/src/assets/player/webtorrent/video-renderer.ts
@@ -3,7 +3,7 @@
3 3
4const MediaElementWrapper = require('mediasource') 4const MediaElementWrapper = require('mediasource')
5import { extname } from 'path' 5import { extname } from 'path'
6const videostream = require('videostream') 6const Videostream = require('videostream')
7 7
8const VIDEOSTREAM_EXTS = [ 8const VIDEOSTREAM_EXTS = [
9 '.m4a', 9 '.m4a',
@@ -34,7 +34,7 @@ function renderMedia (file: any, elem: HTMLVideoElement, opts: RenderMediaOption
34 let renderer: any 34 let renderer: any
35 35
36 try { 36 try {
37 if (VIDEOSTREAM_EXTS.indexOf(extension) >= 0) { 37 if (VIDEOSTREAM_EXTS.includes(extension)) {
38 renderer = useVideostream() 38 renderer = useVideostream()
39 } else { 39 } else {
40 renderer = useMediaSource() 40 renderer = useMediaSource()
@@ -51,7 +51,7 @@ function renderMedia (file: any, elem: HTMLVideoElement, opts: RenderMediaOption
51 return callback(err) 51 return callback(err)
52 }) 52 })
53 preparedElem.addEventListener('loadstart', onLoadStart) 53 preparedElem.addEventListener('loadstart', onLoadStart)
54 return new videostream(file, preparedElem) 54 return new Videostream(file, preparedElem)
55 } 55 }
56 56
57 function useMediaSource (useVP9 = false) { 57 function useMediaSource (useVP9 = false) {
@@ -62,7 +62,7 @@ function renderMedia (file: any, elem: HTMLVideoElement, opts: RenderMediaOption
62 preparedElem.removeEventListener('error', onError) 62 preparedElem.removeEventListener('error', onError)
63 63
64 // Try with vp9 before returning an error 64 // Try with vp9 before returning an error
65 if (codecs.indexOf('vp8') !== -1) return fallbackToMediaSource(true) 65 if (codecs.includes('vp8')) return fallbackToMediaSource(true)
66 66
67 return callback(err) 67 return callback(err)
68 }) 68 })
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
index 17d369c10..0587ddee6 100644
--- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
@@ -17,8 +17,8 @@ import { renderVideo } from './video-renderer'
17const CacheChunkStore = require('cache-chunk-store') 17const CacheChunkStore = require('cache-chunk-store')
18 18
19type PlayOptions = { 19type PlayOptions = {
20 forcePlay?: boolean, 20 forcePlay?: boolean
21 seek?: number, 21 seek?: number
22 delay?: number 22 delay?: number
23} 23}
24 24
@@ -126,8 +126,8 @@ class WebTorrentPlugin extends Plugin {
126 updateVideoFile ( 126 updateVideoFile (
127 videoFile?: VideoFile, 127 videoFile?: VideoFile,
128 options: { 128 options: {
129 forcePlay?: boolean, 129 forcePlay?: boolean
130 seek?: number, 130 seek?: number
131 delay?: number 131 delay?: number
132 } = {}, 132 } = {},
133 done: () => void = () => { /* empty */ } 133 done: () => void = () => { /* empty */ }
@@ -248,7 +248,7 @@ class WebTorrentPlugin extends Plugin {
248 magnetOrTorrentUrl: string, 248 magnetOrTorrentUrl: string,
249 previousVideoFile: VideoFile, 249 previousVideoFile: VideoFile,
250 options: PlayOptions, 250 options: PlayOptions,
251 done: Function 251 done: (err?: Error) => void
252 ) { 252 ) {
253 if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done) 253 if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done)
254 254
@@ -272,7 +272,7 @@ class WebTorrentPlugin extends Plugin {
272 this.stopTorrent(oldTorrent) 272 this.stopTorrent(oldTorrent)
273 273
274 // We use a fake renderer so we download correct pieces of the next file 274 // We use a fake renderer so we download correct pieces of the next file
275 if (options.delay) this.renderFileInFakeElement(torrent.files[ 0 ], options.delay) 275 if (options.delay) this.renderFileInFakeElement(torrent.files[0], options.delay)
276 } 276 }
277 277
278 // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution) 278 // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution)
@@ -288,7 +288,7 @@ class WebTorrentPlugin extends Plugin {
288 if (options.seek) this.player.currentTime(options.seek) 288 if (options.seek) this.player.currentTime(options.seek)
289 289
290 const renderVideoOptions = { autoplay: false, controls: true } 290 const renderVideoOptions = { autoplay: false, controls: true }
291 renderVideo(torrent.files[ 0 ], this.playerElement, renderVideoOptions, (err, renderer) => { 291 renderVideo(torrent.files[0], this.playerElement, renderVideoOptions, (err, renderer) => {
292 this.renderer = renderer 292 this.renderer = renderer
293 293
294 if (err) return this.fallbackToHttp(options, done) 294 if (err) return this.fallbackToHttp(options, done)
@@ -321,7 +321,7 @@ class WebTorrentPlugin extends Plugin {
321 if (err.message.indexOf('incorrect info hash') !== -1) { 321 if (err.message.indexOf('incorrect info hash') !== -1) {
322 console.error('Incorrect info hash detected, falling back to torrent file.') 322 console.error('Incorrect info hash detected, falling back to torrent file.')
323 const newOptions = { forcePlay: true, seek: options.seek } 323 const newOptions = { forcePlay: true, seek: options.seek }
324 return this.addTorrent(this.torrent[ 'xs' ], previousVideoFile, newOptions, done) 324 return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done)
325 } 325 }
326 326
327 // Remote instance is down 327 // Remote instance is down
@@ -340,7 +340,7 @@ class WebTorrentPlugin extends Plugin {
340 if (playPromise !== undefined) { 340 if (playPromise !== undefined) {
341 return playPromise.then(() => done()) 341 return playPromise.then(() => done())
342 .catch((err: Error) => { 342 .catch((err: Error) => {
343 if (err.message.indexOf('The play() request was interrupted by a call to pause()') !== -1) { 343 if (err.message.includes('The play() request was interrupted by a call to pause()')) {
344 return 344 return
345 } 345 }
346 346
@@ -479,7 +479,7 @@ class WebTorrentPlugin extends Plugin {
479 } 479 }
480 480
481 private isPlayerWaiting () { 481 private isPlayerWaiting () {
482 return this.player && this.player.hasClass('vjs-waiting') 482 return this.player?.hasClass('vjs-waiting')
483 } 483 }
484 484
485 private runTorrentInfoScheduler () { 485 private runTorrentInfoScheduler () {
@@ -513,7 +513,7 @@ class WebTorrentPlugin extends Plugin {
513 }, this.CONSTANTS.INFO_SCHEDULER) 513 }, this.CONSTANTS.INFO_SCHEDULER)
514 } 514 }
515 515
516 private fallbackToHttp (options: PlayOptions, done?: Function) { 516 private fallbackToHttp (options: PlayOptions, done?: (err?: Error) => void) {
517 const paused = this.player.paused() 517 const paused = this.player.paused()
518 518
519 this.disableAutoResolution(true) 519 this.disableAutoResolution(true)
@@ -565,7 +565,7 @@ class WebTorrentPlugin extends Plugin {
565 private stopTorrent (torrent: WebTorrent.Torrent) { 565 private stopTorrent (torrent: WebTorrent.Torrent) {
566 torrent.pause() 566 torrent.pause()
567 // Pause does not remove actual peers (in particular the webseed peer) 567 // Pause does not remove actual peers (in particular the webseed peer)
568 torrent.removePeer(torrent[ 'ws' ]) 568 torrent.removePeer(torrent['ws'])
569 } 569 }
570 570
571 private renderFileInFakeElement (file: WebTorrent.TorrentFile, delay: number) { 571 private renderFileInFakeElement (file: WebTorrent.TorrentFile, delay: number) {
diff --git a/client/src/polyfills.ts b/client/src/polyfills.ts
index d862de8d8..bbba3f05c 100644
--- a/client/src/polyfills.ts
+++ b/client/src/polyfills.ts
@@ -57,7 +57,7 @@ import 'core-js/es/object'
57/*************************************************************************************************** 57/***************************************************************************************************
58 * Zone JS is required by default for Angular itself. 58 * Zone JS is required by default for Angular itself.
59 */ 59 */
60// tslint:disable 60/* eslint-disable */
61import 'zone.js' // Included with Angular CLI. 61import 'zone.js' // Included with Angular CLI.
62 62
63/*************************************************************************************************** 63/***************************************************************************************************
diff --git a/client/src/root-helpers/bytes.ts b/client/src/root-helpers/bytes.ts
index ec8b55faa..bda786cc6 100644
--- a/client/src/root-helpers/bytes.ts
+++ b/client/src/root-helpers/bytes.ts
@@ -1,4 +1,4 @@
1const dictionary: Array<{ max: number; type: string }> = [ 1const dictionary: Array<{ max: number, type: string }> = [
2 { max: 1024, type: 'B' }, 2 { max: 1024, type: 'B' },
3 { max: 1048576, type: 'KB' }, 3 { max: 1048576, type: 'KB' },
4 { max: 1073741824, type: 'MB' }, 4 { max: 1073741824, type: 'MB' },
diff --git a/client/src/root-helpers/peertube-web-storage.ts b/client/src/root-helpers/peertube-web-storage.ts
index d4cad8a20..68a2462de 100644
--- a/client/src/root-helpers/peertube-web-storage.ts
+++ b/client/src/root-helpers/peertube-web-storage.ts
@@ -5,7 +5,7 @@ const valuesMap = new Map()
5function proxify (instance: MemoryStorage) { 5function proxify (instance: MemoryStorage) {
6 return new Proxy(instance, { 6 return new Proxy(instance, {
7 set: function (obj, prop: string | symbol, value) { 7 set: function (obj, prop: string | symbol, value) {
8 if (MemoryStorage.prototype.hasOwnProperty(prop)) { 8 if (Object.prototype.hasOwnProperty.call(MemoryStorage, prop)) {
9 // FIXME: symbol typing issue https://github.com/microsoft/TypeScript/issues/1863 9 // FIXME: symbol typing issue https://github.com/microsoft/TypeScript/issues/1863
10 instance[prop as any] = value 10 instance[prop as any] = value
11 } else { 11 } else {
@@ -14,7 +14,7 @@ function proxify (instance: MemoryStorage) {
14 return true 14 return true
15 }, 15 },
16 get: function (target, name: string | symbol | number) { 16 get: function (target, name: string | symbol | number) {
17 if (MemoryStorage.prototype.hasOwnProperty(name)) { 17 if (Object.prototype.hasOwnProperty.call(MemoryStorage, name)) {
18 // FIXME: symbol typing issue https://github.com/microsoft/TypeScript/issues/1863 18 // FIXME: symbol typing issue https://github.com/microsoft/TypeScript/issues/1863
19 return instance[name as any] 19 return instance[name as any]
20 } 20 }
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts
index d14ac4acd..f1687d91d 100644
--- a/client/src/root-helpers/plugins-manager.ts
+++ b/client/src/root-helpers/plugins-manager.ts
@@ -155,7 +155,7 @@ class PluginsManager {
155 try { 155 try {
156 if (!isReload) this.loadedScopes.push(scope) 156 if (!isReload) this.loadedScopes.push(scope)
157 157
158 const toLoad = this.scopes[ scope ] 158 const toLoad = this.scopes[scope]
159 if (!Array.isArray(toLoad)) { 159 if (!Array.isArray(toLoad)) {
160 this.loadingScopes[scope] = false 160 this.loadingScopes[scope] = false
161 this.pluginsLoaded[scope].next(true) 161 this.pluginsLoaded[scope].next(true)
@@ -168,11 +168,11 @@ class PluginsManager {
168 for (const pluginInfo of toLoad) { 168 for (const pluginInfo of toLoad) {
169 const clientScript = pluginInfo.clientScript 169 const clientScript = pluginInfo.clientScript
170 170
171 if (this.loadedScripts[ clientScript.script ]) continue 171 if (this.loadedScripts[clientScript.script]) continue
172 172
173 promises.push(this.loadPlugin(pluginInfo)) 173 promises.push(this.loadPlugin(pluginInfo))
174 174
175 this.loadedScripts[ clientScript.script ] = true 175 this.loadedScripts[clientScript.script] = true
176 } 176 }
177 177
178 await Promise.all(promises) 178 await Promise.all(promises)
diff --git a/client/src/standalone/player/definitions.ts b/client/src/standalone/player/definitions.ts
index cc5203ed5..495f1a98c 100644
--- a/client/src/standalone/player/definitions.ts
+++ b/client/src/standalone/player/definitions.ts
@@ -21,5 +21,5 @@ export type PeerTubeTextTrack = {
21 id: string 21 id: string
22 label: string 22 label: string
23 src: string 23 src: string
24 mode: 'showing' | 'disabled' 24 mode: TextTrackMode
25} 25}
diff --git a/client/src/standalone/player/events.ts b/client/src/standalone/player/events.ts
index 28a13c727..7a8e9dbec 100644
--- a/client/src/standalone/player/events.ts
+++ b/client/src/standalone/player/events.ts
@@ -1,7 +1,7 @@
1import { EventHandler } from './definitions' 1import { EventHandler } from './definitions'
2 2
3interface PlayerEventRegistrar { 3interface PlayerEventRegistrar {
4 registrations: Function[] 4 registrations: EventHandler<any>[]
5} 5}
6 6
7interface PlayerEventRegistrationMap { 7interface PlayerEventRegistrationMap {
@@ -20,28 +20,28 @@ export class EventRegistrar {
20 20
21 public registerTypes (names: string[]) { 21 public registerTypes (names: string[]) {
22 for (const name of names) { 22 for (const name of names) {
23 this.eventRegistrations[ name ] = { registrations: [] } 23 this.eventRegistrations[name] = { registrations: [] }
24 } 24 }
25 } 25 }
26 26
27 public fire<T> (name: string, event: T) { 27 public fire<T> (name: string, event: T) {
28 this.eventRegistrations[ name ].registrations.forEach(x => x(event)) 28 this.eventRegistrations[name].registrations.forEach(x => x(event))
29 } 29 }
30 30
31 public addListener<T> (name: string, handler: EventHandler<T>) { 31 public addListener<T> (name: string, handler: EventHandler<T>) {
32 if (!this.eventRegistrations[ name ]) { 32 if (!this.eventRegistrations[name]) {
33 console.warn(`PeerTube: addEventListener(): The event '${name}' is not supported`) 33 console.warn(`PeerTube: addEventListener(): The event '${name}' is not supported`)
34 return false 34 return false
35 } 35 }
36 36
37 this.eventRegistrations[ name ].registrations.push(handler) 37 this.eventRegistrations[name].registrations.push(handler)
38 return true 38 return true
39 } 39 }
40 40
41 public removeListener<T> (name: string, handler: EventHandler<T>) { 41 public removeListener<T> (name: string, handler: EventHandler<T>) {
42 if (!this.eventRegistrations[ name ]) return false 42 if (!this.eventRegistrations[name]) return false
43 43
44 this.eventRegistrations[ name ].registrations = this.eventRegistrations[ name ].registrations.filter(x => x === handler) 44 this.eventRegistrations[name].registrations = this.eventRegistrations[name].registrations.filter(x => x === handler)
45 45
46 return true 46 return true
47 } 47 }
diff --git a/client/src/standalone/player/player.ts b/client/src/standalone/player/player.ts
index 9776fda12..bbe37a42b 100644
--- a/client/src/standalone/player/player.ts
+++ b/client/src/standalone/player/player.ts
@@ -17,7 +17,7 @@ const PASSTHROUGH_EVENTS = [
17 */ 17 */
18export class PeerTubePlayer { 18export class PeerTubePlayer {
19 19
20 private eventRegistrar: EventRegistrar = new EventRegistrar() 20 private readonly eventRegistrar: EventRegistrar = new EventRegistrar()
21 private channel: Channel.MessagingChannel 21 private channel: Channel.MessagingChannel
22 private readyPromise: Promise<void> 22 private readyPromise: Promise<void>
23 23
@@ -31,8 +31,8 @@ export class PeerTubePlayer {
31 * @param scope 31 * @param scope
32 */ 32 */
33 constructor ( 33 constructor (
34 private embedElement: HTMLIFrameElement, 34 private readonly embedElement: HTMLIFrameElement,
35 private scope?: string 35 private readonly scope?: string
36 ) { 36 ) {
37 this.eventRegistrar.registerTypes(PASSTHROUGH_EVENTS) 37 this.eventRegistrar.registerTypes(PASSTHROUGH_EVENTS)
38 38
@@ -90,6 +90,7 @@ export class PeerTubePlayer {
90 90
91 /** 91 /**
92 * Tell the embed to change the audio volume 92 * Tell the embed to change the audio volume
93 *
93 * @param value A number from 0 to 1 94 * @param value A number from 0 to 1
94 */ 95 */
95 async setVolume (value: number) { 96 async setVolume (value: number) {
@@ -98,14 +99,16 @@ export class PeerTubePlayer {
98 99
99 /** 100 /**
100 * Get the current volume level in the embed. 101 * Get the current volume level in the embed.
102 *
101 * @param value A number from 0 to 1 103 * @param value A number from 0 to 1
102 */ 104 */
103 async getVolume (): Promise<number> { 105 async getVolume (): Promise<number> {
104 return this.sendMessage<void, number>('getVolume') 106 return this.sendMessage<undefined, number>('getVolume')
105 } 107 }
106 108
107 /** 109 /**
108 * Tell the embed to change the current caption 110 * Tell the embed to change the current caption
111 *
109 * @param value Caption id 112 * @param value Caption id
110 */ 113 */
111 async setCaption (value: string) { 114 async setCaption (value: string) {
@@ -116,11 +119,12 @@ export class PeerTubePlayer {
116 * Get video captions 119 * Get video captions
117 */ 120 */
118 async getCaptions (): Promise<PeerTubeTextTrack[]> { 121 async getCaptions (): Promise<PeerTubeTextTrack[]> {
119 return this.sendMessage<void, PeerTubeTextTrack[]>('getCaptions') 122 return this.sendMessage<undefined, PeerTubeTextTrack[]>('getCaptions')
120 } 123 }
121 124
122 /** 125 /**
123 * Tell the embed to seek to a specific position (in seconds) 126 * Tell the embed to seek to a specific position (in seconds)
127 *
124 * @param seconds 128 * @param seconds
125 */ 129 */
126 async seek (seconds: number) { 130 async seek (seconds: number) {
@@ -143,21 +147,21 @@ export class PeerTubePlayer {
143 * resolutions change. 147 * resolutions change.
144 */ 148 */
145 async getResolutions (): Promise<PeerTubeResolution[]> { 149 async getResolutions (): Promise<PeerTubeResolution[]> {
146 return this.sendMessage<void, PeerTubeResolution[]>('getResolutions') 150 return this.sendMessage<undefined, PeerTubeResolution[]>('getResolutions')
147 } 151 }
148 152
149 /** 153 /**
150 * Retrieve a list of available playback rates. 154 * Retrieve a list of available playback rates.
151 */ 155 */
152 async getPlaybackRates (): Promise<number[]> { 156 async getPlaybackRates (): Promise<number[]> {
153 return this.sendMessage<void, number[]>('getPlaybackRates') 157 return this.sendMessage<undefined, number[]>('getPlaybackRates')
154 } 158 }
155 159
156 /** 160 /**
157 * Get the current playback rate. Defaults to 1 (1x playback rate). 161 * Get the current playback rate. Defaults to 1 (1x playback rate).
158 */ 162 */
159 async getPlaybackRate (): Promise<number> { 163 async getPlaybackRate (): Promise<number> {
160 return this.sendMessage<void, number>('getPlaybackRate') 164 return this.sendMessage<undefined, number>('getPlaybackRate')
161 } 165 }
162 166
163 /** 167 /**
@@ -188,7 +192,7 @@ export class PeerTubePlayer {
188 * Get video position currently played (starts from 1) 192 * Get video position currently played (starts from 1)
189 */ 193 */
190 async getCurrentPosition () { 194 async getCurrentPosition () {
191 return this.sendMessage<void, number>('getCurrentPosition') 195 return this.sendMessage<undefined, number>('getCurrentPosition')
192 } 196 }
193 197
194 private constructChannel () { 198 private constructChannel () {
@@ -201,8 +205,8 @@ export class PeerTubePlayer {
201 } 205 }
202 206
203 private prepareToBeReady () { 207 private prepareToBeReady () {
204 let readyResolve: Function 208 let readyResolve: () => void
205 let readyReject: Function 209 let readyReject: () => void
206 210
207 this.readyPromise = new Promise<void>((res, rej) => { 211 this.readyPromise = new Promise<void>((res, rej) => {
208 readyResolve = res 212 readyResolve = res
@@ -219,7 +223,8 @@ export class PeerTubePlayer {
219 private sendMessage<TIn, TOut> (method: string, params?: TIn): Promise<TOut> { 223 private sendMessage<TIn, TOut> (method: string, params?: TIn): Promise<TOut> {
220 return new Promise<TOut>((resolve, reject) => { 224 return new Promise<TOut>((resolve, reject) => {
221 this.channel.call({ 225 this.channel.call({
222 method, params, 226 method,
227 params,
223 success: result => resolve(result), 228 success: result => resolve(result),
224 error: error => reject(error) 229 error: error => reject(error)
225 }) 230 })
@@ -228,4 +233,4 @@ export class PeerTubePlayer {
228} 233}
229 234
230// put it on the window as well as the export 235// put it on the window as well as the export
231(window[ 'PeerTubePlayer' ] as any) = PeerTubePlayer 236(window['PeerTubePlayer'] as any) = PeerTubePlayer
diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts
index 75174f2f8..b5c9da431 100644
--- a/client/src/standalone/videos/embed-api.ts
+++ b/client/src/standalone/videos/embed-api.ts
@@ -13,7 +13,8 @@ export class PeerTubeEmbedApi {
13 private isReady = false 13 private isReady = false
14 private resolutions: PeerTubeResolution[] = [] 14 private resolutions: PeerTubeResolution[] = []
15 15
16 constructor (private embed: PeerTubeEmbed) { 16 constructor (private readonly embed: PeerTubeEmbed) {
17
17 } 18 }
18 19
19 initialize () { 20 initialize () {
@@ -45,7 +46,7 @@ export class PeerTubeEmbedApi {
45 channel.bind('getResolutions', (txn, params) => this.resolutions) 46 channel.bind('getResolutions', (txn, params) => this.resolutions)
46 47
47 channel.bind('getCaptions', (txn, params) => this.getCaptions()) 48 channel.bind('getCaptions', (txn, params) => this.getCaptions())
48 channel.bind('setCaption', (txn, id) => this.setCaption(id)), 49 channel.bind('setCaption', (txn, id) => this.setCaption(id))
49 50
50 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate)) 51 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
51 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate()) 52 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
@@ -79,14 +80,12 @@ export class PeerTubeEmbedApi {
79 } 80 }
80 81
81 private getCaptions (): PeerTubeTextTrack[] { 82 private getCaptions (): PeerTubeTextTrack[] {
82 return this.embed.player.textTracks().tracks_.map(t => { 83 return this.embed.player.textTracks().tracks_.map(t => ({
83 return { 84 id: t.id,
84 id: t.id, 85 src: t.src,
85 src: t.src, 86 label: t.label,
86 label: t.label, 87 mode: t.mode
87 mode: t.mode as any 88 }))
88 }
89 })
90 } 89 }
91 90
92 private setCaption (id: string) { 91 private setCaption (id: string) {
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index c9a4e541c..dad717108 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -64,17 +64,11 @@ export class PeerTubeEmbed {
64 private playlistElements: VideoPlaylistElement[] 64 private playlistElements: VideoPlaylistElement[]
65 private currentPlaylistElement: VideoPlaylistElement 65 private currentPlaylistElement: VideoPlaylistElement
66 66
67 private wrapperElement: HTMLElement 67 private readonly wrapperElement: HTMLElement
68 68
69 private pluginsManager: PluginsManager 69 private pluginsManager: PluginsManager
70 70
71 static async main () { 71 constructor (private readonly videoWrapperId: string) {
72 const videoContainerId = 'video-wrapper'
73 const embed = new PeerTubeEmbed(videoContainerId)
74 await embed.init()
75 }
76
77 constructor (private videoWrapperId: string) {
78 this.wrapperElement = document.getElementById(this.videoWrapperId) 72 this.wrapperElement = document.getElementById(this.videoWrapperId)
79 73
80 try { 74 try {
@@ -84,6 +78,12 @@ export class PeerTubeEmbed {
84 } 78 }
85 } 79 }
86 80
81 static async main () {
82 const videoContainerId = 'video-wrapper'
83 const embed = new PeerTubeEmbed(videoContainerId)
84 await embed.init()
85 }
86
87 getVideoUrl (id: string) { 87 getVideoUrl (id: string) {
88 return window.location.origin + '/api/v1/videos/' + id 88 return window.location.origin + '/api/v1/videos/' + id
89 } 89 }
@@ -316,7 +316,7 @@ export class PeerTubeEmbed {
316 while (total > elements.length && i < 10) { 316 while (total > elements.length && i < 10) {
317 const result = await this.loadPlaylistElements(playlistId, elements.length) 317 const result = await this.loadPlaylistElements(playlistId, elements.length)
318 318
319 const json = await result.json() as ResultList<VideoPlaylistElement> 319 const json = await result.json()
320 total = json.total 320 total = json.total
321 321
322 elements = elements.concat(json.data) 322 elements = elements.concat(json.data)
@@ -469,7 +469,7 @@ export class PeerTubeEmbed {
469 // Issue when we parsed config from HTML, fallback to API 469 // Issue when we parsed config from HTML, fallback to API
470 if (!this.config) { 470 if (!this.config) {
471 this.config = await this.refreshFetch('/api/v1/config') 471 this.config = await this.refreshFetch('/api/v1/config')
472 .then(res => res.json()) 472 .then(res => res.json())
473 } 473 }
474 474
475 const videoInfoPromise = videoResponse.json() 475 const videoInfoPromise = videoResponse.json()
@@ -506,7 +506,7 @@ export class PeerTubeEmbed {
506 this.currentPlaylistElement = videoPlaylistElement 506 this.currentPlaylistElement = videoPlaylistElement
507 507
508 this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid) 508 this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
509 .catch(err => console.error(err)) 509 .catch(err => console.error(err))
510 } 510 }
511 } 511 }
512 : undefined 512 : undefined
@@ -542,7 +542,9 @@ export class PeerTubeEmbed {
542 isLive: videoInfo.isLive, 542 isLive: videoInfo.isLive,
543 543
544 playerElement: this.playerElement, 544 playerElement: this.playerElement,
545 onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, 545 onPlayerElementChange: (element: HTMLVideoElement) => {
546 this.playerElement = element
547 },
546 548
547 videoDuration: videoInfo.duration, 549 videoDuration: videoInfo.duration,
548 enableHotkeys: true, 550 enableHotkeys: true,
@@ -577,10 +579,13 @@ export class PeerTubeEmbed {
577 }) 579 })
578 } 580 }
579 581
580 this.player = await PeertubePlayerManager.initialize(this.mode, options, (player: videojs.Player) => this.player = player) 582 this.player = await PeertubePlayerManager.initialize(this.mode, options, (player: videojs.Player) => {
583 this.player = player
584 })
585
581 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) 586 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
582 587
583 window[ 'videojsPlayer' ] = this.player 588 window['videojsPlayer'] = this.player
584 589
585 this.buildCSS() 590 this.buildCSS()
586 591
@@ -656,7 +661,7 @@ export class PeerTubeEmbed {
656 this.player.dispose() 661 this.player.dispose()
657 this.playerElement = null 662 this.playerElement = null
658 this.displayError('This video is not available because the remote instance is not responding.', translations) 663 this.displayError('This video is not available because the remote instance is not responding.', translations)
659 return 664
660 } 665 }
661 } 666 }
662 667
@@ -694,9 +699,9 @@ export class PeerTubeEmbed {
694 699
695 private async buildCaptions (serverTranslations: any, captionsResponse: Response): Promise<VideoJSCaption[]> { 700 private async buildCaptions (serverTranslations: any, captionsResponse: Response): Promise<VideoJSCaption[]> {
696 if (captionsResponse.ok) { 701 if (captionsResponse.ok) {
697 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption> 702 const { data } = await captionsResponse.json()
698 703
699 return data.map(c => ({ 704 return data.map((c: VideoCaption) => ({
700 label: peertubeTranslate(c.language.label, serverTranslations), 705 label: peertubeTranslate(c.language.label, serverTranslations),
701 language: c.language.id, 706 language: c.language.id,
702 src: window.location.origin + c.captionPath 707 src: window.location.origin + c.captionPath
@@ -733,7 +738,7 @@ export class PeerTubeEmbed {
733 738
734 private getResourceId () { 739 private getResourceId () {
735 const urlParts = window.location.pathname.split('/') 740 const urlParts = window.location.pathname.split('/')
736 return urlParts[ urlParts.length - 1 ] 741 return urlParts[urlParts.length - 1]
737 } 742 }
738 743
739 private isPlaylistEmbed () { 744 private isPlaylistEmbed () {
@@ -751,7 +756,7 @@ export class PeerTubeEmbed {
751 } 756 }
752 757
753 private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers { 758 private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers {
754 function unimplemented (): any { 759 const unimplemented = () => {
755 throw new Error('This helper is not implemented in embed.') 760 throw new Error('This helper is not implemented in embed.')
756 } 761 }
757 762
@@ -780,9 +785,7 @@ export class PeerTubeEmbed {
780 enhancedMarkdownToHTML: unimplemented 785 enhancedMarkdownToHTML: unimplemented
781 }, 786 },
782 787
783 translate: (value: string) => { 788 translate: (value: string) => Promise.resolve(peertubeTranslate(value, translations))
784 return Promise.resolve(peertubeTranslate(value, translations))
785 }
786 } 789 }
787 } 790 }
788} 791}
diff --git a/client/src/standalone/videos/test-embed.ts b/client/src/standalone/videos/test-embed.ts
index 6e035c0c9..066b3e024 100644
--- a/client/src/standalone/videos/test-embed.ts
+++ b/client/src/standalone/videos/test-embed.ts
@@ -4,11 +4,11 @@ import { PeerTubePlayer } from '../player/player'
4 4
5window.addEventListener('load', async () => { 5window.addEventListener('load', async () => {
6 const urlParts = window.location.href.split('/') 6 const urlParts = window.location.href.split('/')
7 const lastPart = urlParts[ urlParts.length - 1 ] 7 const lastPart = urlParts[urlParts.length - 1]
8 8
9 const isPlaylist = window.location.pathname.startsWith('/video-playlists/') 9 const isPlaylist = window.location.pathname.startsWith('/video-playlists/')
10 10
11 const elementId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ] 11 const elementId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[0]
12 12
13 const iframe = document.createElement('iframe') 13 const iframe = document.createElement('iframe')
14 iframe.src = isPlaylist 14 iframe.src = isPlaylist
@@ -18,14 +18,14 @@ window.addEventListener('load', async () => {
18 const mainElement = document.querySelector('#host') 18 const mainElement = document.querySelector('#host')
19 mainElement.appendChild(iframe) 19 mainElement.appendChild(iframe)
20 20
21 console.log(`Document finished loading.`) 21 console.log('Document finished loading.')
22 const player = new PeerTubePlayer(document.querySelector('iframe')) 22 const player = new PeerTubePlayer(document.querySelector('iframe'))
23 23
24 window[ 'player' ] = player 24 window['player'] = player
25 25
26 console.log(`Awaiting player ready...`) 26 console.log('Awaiting player ready...')
27 await player.ready 27 await player.ready
28 console.log(`Player is ready.`) 28 console.log('Player is ready.')
29 29
30 const monitoredEvents = [ 30 const monitoredEvents = [
31 'pause', 31 'pause',
@@ -39,7 +39,9 @@ window.addEventListener('load', async () => {
39 console.log(`PLAYER: now listening for event '${e}'`) 39 console.log(`PLAYER: now listening for event '${e}'`)
40 40
41 player.getCurrentPosition() 41 player.getCurrentPosition()
42 .then(position => document.getElementById('playlist-position').innerHTML = position + '') 42 .then(position => {
43 document.getElementById('playlist-position').innerHTML = position + ''
44 })
43 }) 45 })
44 46
45 let playbackRates: number[] = [] 47 let playbackRates: number[] = []
@@ -105,7 +107,7 @@ window.addEventListener('load', async () => {
105 107
106 updateCaptions() 108 updateCaptions()
107 109
108 const updateResolutions = ((resolutions: PeerTubeResolution[]) => { 110 const updateResolutions = (resolutions: PeerTubeResolution[]) => {
109 const resolutionListEl = document.querySelector('#resolution-list') 111 const resolutionListEl = document.querySelector('#resolution-list')
110 resolutionListEl.innerHTML = '' 112 resolutionListEl.innerHTML = ''
111 113
@@ -126,7 +128,7 @@ window.addEventListener('load', async () => {
126 resolutionListEl.appendChild(itemEl) 128 resolutionListEl.appendChild(itemEl)
127 } 129 }
128 }) 130 })
129 }) 131 }
130 132
131 player.getResolutions().then( 133 player.getResolutions().then(
132 resolutions => updateResolutions(resolutions)) 134 resolutions => updateResolutions(resolutions))
diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts
index 59bcbc5ff..3415ef08f 100644
--- a/client/src/types/register-client-option.model.ts
+++ b/client/src/types/register-client-option.model.ts
@@ -30,16 +30,16 @@ export type RegisterClientHelpers = {
30 getServerConfig: () => Promise<ServerConfig> 30 getServerConfig: () => Promise<ServerConfig>
31 31
32 notifier: { 32 notifier: {
33 info: (text: string, title?: string, timeout?: number) => void, 33 info: (text: string, title?: string, timeout?: number) => void
34 error: (text: string, title?: string, timeout?: number) => void, 34 error: (text: string, title?: string, timeout?: number) => void
35 success: (text: string, title?: string, timeout?: number) => void 35 success: (text: string, title?: string, timeout?: number) => void
36 } 36 }
37 37
38 showModal: (input: { 38 showModal: (input: {
39 title: string, 39 title: string
40 content: string, 40 content: string
41 close?: boolean, 41 close?: boolean
42 cancel?: { value: string, action?: () => void }, 42 cancel?: { value: string, action?: () => void }
43 confirm?: { value: string, action?: () => void } 43 confirm?: { value: string, action?: () => void }
44 }) => void 44 }) => void
45 45
diff --git a/client/src/typings.d.ts b/client/src/typings.d.ts
index 24473aede..ad21fdedf 100644
--- a/client/src/typings.d.ts
+++ b/client/src/typings.d.ts
@@ -1,4 +1,5 @@
1/* SystemJS module definition */ 1/* SystemJS module definition */
2// eslint-disable-next-line no-var
2declare var module: NodeModule 3declare var module: NodeModule
3 4
4interface NodeModule { 5interface NodeModule {