aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts3
-rw-r--r--client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts3
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.ts11
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts10
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.html10
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.ts20
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending-header.component.ts12
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending.component.ts7
-rw-r--r--client/src/app/app.component.ts2
-rw-r--r--client/src/app/core/routing/redirect.service.ts35
-rw-r--r--client/src/app/core/server/server.service.ts25
-rw-r--r--client/src/app/core/theme/theme.service.ts14
-rw-r--r--client/src/app/shared/shared-main/angular/from-now.pipe.ts29
-rw-r--r--client/src/index.html1
-rw-r--r--client/src/root-helpers/plugins.ts7
-rw-r--r--client/src/standalone/videos/embed.html12
-rw-r--r--client/src/standalone/videos/embed.ts52
17 files changed, 153 insertions, 100 deletions
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
index 1a95980ae..6af224920 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
@@ -5,8 +5,7 @@ import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
5import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core' 5import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core'
6import { PluginService } from '@app/core/plugins/plugin.service' 6import { PluginService } from '@app/core/plugins/plugin.service'
7import { compareSemVer } from '@shared/core-utils/miscs/miscs' 7import { compareSemVer } from '@shared/core-utils/miscs/miscs'
8import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' 8import { PeerTubePlugin, PluginType } from '@shared/models'
9import { PluginType } from '@shared/models/plugins/plugin.type'
10 9
11@Component({ 10@Component({
12 selector: 'my-plugin-list-installed', 11 selector: 'my-plugin-list-installed',
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
index d2c179aba..0a6e57904 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
@@ -4,8 +4,7 @@ import { Component, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' 5import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
6import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core' 6import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
7import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model' 7import { PeerTubePluginIndex, PluginType } from '@shared/models'
8import { PluginType } from '@shared/models/plugins/plugin.type'
9 8
10@Component({ 9@Component({
11 selector: 'my-plugin-search', 10 selector: 'my-plugin-search',
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 34119f7ab..3d916dbce 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
@@ -21,8 +21,15 @@ import {
21import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms' 21import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms'
22import { InstanceService } from '@app/shared/shared-instance' 22import { InstanceService } from '@app/shared/shared-instance'
23import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' 23import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main'
24import { LiveVideo, ServerConfig, VideoConstant, VideoDetails, VideoPrivacy } from '@shared/models' 24import {
25import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' 25 LiveVideo,
26 RegisterClientFormFieldOptions,
27 RegisterClientVideoFieldOptions,
28 ServerConfig,
29 VideoConstant,
30 VideoDetails,
31 VideoPrivacy
32} from '@shared/models'
26import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' 33import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
27import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' 34import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
28import { VideoEditType } from './video-edit.type' 35import { VideoEditType } from './video-edit.type'
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 3aae24732..23bd5ef76 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 { VideoPrivacy, VideoUpdate } from '@shared/models' 8import { ServerErrorCode, VideoPrivacy, 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
@@ -113,7 +113,13 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
113 this.loadingBar.useRef().complete() 113 this.loadingBar.useRef().complete()
114 this.isImportingVideo = false 114 this.isImportingVideo = false
115 this.firstStepError.emit() 115 this.firstStepError.emit()
116 this.notifier.error(err.message) 116
117 let message = err.message
118 if (err.body?.code === ServerErrorCode.INCORRECT_FILES_IN_TORRENT) {
119 message = $localize`Torrents with only 1 file are supported.`
120 }
121
122 this.notifier.error(message)
117 } 123 }
118 ) 124 )
119 } 125 }
diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html
index dc8c2f21d..ac75d9ff8 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.html
+++ b/client/src/app/+videos/+video-edit/video-add.component.html
@@ -20,8 +20,8 @@
20 <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container> 20 <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container>
21 </div> 21 </div>
22 22
23 <div ngbNav #nav="ngbNav" class="nav-tabs video-add-nav" [ngClass]="{ 'hide-nav': secondStepType !== undefined }"> 23 <div ngbNav #nav="ngbNav" class="nav-tabs video-add-nav" [activeId]="activeNav" (activeIdChange)="onNavChange($event)" [ngClass]="{ 'hide-nav': !!secondStepType }">
24 <ng-container ngbNavItem> 24 <ng-container ngbNavItem="upload">
25 <a ngbNavLink> 25 <a ngbNavLink>
26 <span i18n>Upload a file</span> 26 <span i18n>Upload a file</span>
27 </a> 27 </a>
@@ -31,7 +31,7 @@
31 </ng-template> 31 </ng-template>
32 </ng-container> 32 </ng-container>
33 33
34 <ng-container ngbNavItem *ngIf="isVideoImportHttpEnabled()"> 34 <ng-container ngbNavItem="import-url" *ngIf="isVideoImportHttpEnabled()">
35 <a ngbNavLink> 35 <a ngbNavLink>
36 <span i18n>Import with URL</span> 36 <span i18n>Import with URL</span>
37 </a> 37 </a>
@@ -41,7 +41,7 @@
41 </ng-template> 41 </ng-template>
42 </ng-container> 42 </ng-container>
43 43
44 <ng-container ngbNavItem *ngIf="isVideoImportTorrentEnabled()"> 44 <ng-container ngbNavItem="import-torrent" *ngIf="isVideoImportTorrentEnabled()">
45 <a ngbNavLink> 45 <a ngbNavLink>
46 <span i18n>Import with torrent</span> 46 <span i18n>Import with torrent</span>
47 </a> 47 </a>
@@ -51,7 +51,7 @@
51 </ng-template> 51 </ng-template>
52 </ng-container> 52 </ng-container>
53 53
54 <ng-container ngbNavItem *ngIf="isVideoLiveEnabled()"> 54 <ng-container ngbNavItem="go-live" *ngIf="isVideoLiveEnabled()">
55 <a ngbNavLink> 55 <a ngbNavLink>
56 <span i18n>Go live</span> 56 <span i18n>Go live</span>
57 </a> 57 </a>
diff --git a/client/src/app/+videos/+video-edit/video-add.component.ts b/client/src/app/+videos/+video-edit/video-add.component.ts
index 441d5a3db..d735c936c 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add.component.ts
@@ -1,4 +1,5 @@
1import { Component, HostListener, OnInit, ViewChild } from '@angular/core' 1import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
2import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core' 3import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core'
3import { ServerConfig } from '@shared/models' 4import { ServerConfig } from '@shared/models'
4import { VideoEditType } from './shared/video-edit.type' 5import { VideoEditType } from './shared/video-edit.type'
@@ -22,11 +23,16 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
22 23
23 secondStepType: VideoEditType 24 secondStepType: VideoEditType
24 videoName: string 25 videoName: string
25 serverConfig: ServerConfig 26
27 activeNav: string
28
29 private serverConfig: ServerConfig
26 30
27 constructor ( 31 constructor (
28 private auth: AuthService, 32 private auth: AuthService,
29 private serverService: ServerService 33 private serverService: ServerService,
34 private route: ActivatedRoute,
35 private router: Router
30 ) {} 36 ) {}
31 37
32 get userInformationLoaded () { 38 get userInformationLoaded () {
@@ -42,6 +48,16 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
42 .subscribe(config => this.serverConfig = config) 48 .subscribe(config => this.serverConfig = config)
43 49
44 this.user = this.auth.getUser() 50 this.user = this.auth.getUser()
51
52 if (this.route.snapshot.fragment) {
53 this.onNavChange(this.route.snapshot.fragment)
54 }
55 }
56
57 onNavChange (newActiveNav: string) {
58 this.activeNav = newActiveNav
59
60 this.router.navigate([], { fragment: this.activeNav })
45 } 61 }
46 62
47 onFirstStepDone (type: VideoEditType, videoName: string) { 63 onFirstStepDone (type: VideoEditType, videoName: string) {
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 55040f3c9..bbb02a236 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
@@ -31,7 +31,8 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
31 private route: ActivatedRoute, 31 private route: ActivatedRoute,
32 private router: Router, 32 private router: Router,
33 private auth: AuthService, 33 private auth: AuthService,
34 private serverService: ServerService 34 private serverService: ServerService,
35 private redirectService: RedirectService
35 ) { 36 ) {
36 super(data) 37 super(data)
37 38
@@ -84,12 +85,7 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
84 85
85 this.algorithmChangeSub = this.route.queryParams.subscribe( 86 this.algorithmChangeSub = this.route.queryParams.subscribe(
86 queryParams => { 87 queryParams => {
87 const algorithm = queryParams['alg'] 88 this.data.model = queryParams['alg'] || this.redirectService.getDefaultTrendingAlgorithm()
88 if (algorithm) {
89 this.data.model = algorithm
90 } else {
91 this.data.model = RedirectService.DEFAULT_TRENDING_ALGORITHM
92 }
93 } 89 }
94 ) 90 )
95 } 91 }
@@ -99,7 +95,7 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
99 } 95 }
100 96
101 setSort () { 97 setSort () {
102 const alg = this.data.model !== RedirectService.DEFAULT_TRENDING_ALGORITHM 98 const alg = this.data.model !== this.redirectService.getDefaultTrendingAlgorithm()
103 ? this.data.model 99 ? this.data.model
104 : undefined 100 : undefined
105 101
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 e50d6ec3a..ebec672f3 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
@@ -35,11 +35,12 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
35 protected storageService: LocalStorageService, 35 protected storageService: LocalStorageService,
36 protected cfr: ComponentFactoryResolver, 36 protected cfr: ComponentFactoryResolver,
37 private videoService: VideoService, 37 private videoService: VideoService,
38 private redirectService: RedirectService,
38 private hooks: HooksService 39 private hooks: HooksService
39 ) { 40 ) {
40 super() 41 super()
41 42
42 this.defaultSort = this.parseAlgorithm(RedirectService.DEFAULT_TRENDING_ALGORITHM) 43 this.defaultSort = this.parseAlgorithm(this.redirectService.getDefaultTrendingAlgorithm())
43 44
44 this.headerComponentInjector = this.getInjector() 45 this.headerComponentInjector = this.getInjector()
45 } 46 }
@@ -106,7 +107,7 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
106 } 107 }
107 108
108 protected loadPageRouteParams (queryParams: Params) { 109 protected loadPageRouteParams (queryParams: Params) {
109 const algorithm = queryParams['alg'] || RedirectService.DEFAULT_TRENDING_ALGORITHM 110 const algorithm = queryParams['alg'] || this.redirectService.getDefaultTrendingAlgorithm()
110 111
111 this.sort = this.parseAlgorithm(algorithm) 112 this.sort = this.parseAlgorithm(algorithm)
112 } 113 }
@@ -115,8 +116,10 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
115 switch (algorithm) { 116 switch (algorithm) {
116 case 'most-viewed': 117 case 'most-viewed':
117 return '-trending' 118 return '-trending'
119
118 case 'most-liked': 120 case 'most-liked':
119 return '-likes' 121 return '-likes'
122
120 default: 123 default:
121 return '-' + algorithm as VideoSortField 124 return '-' + algorithm as VideoSortField
122 } 125 }
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 66d871b4a..239e275a4 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -67,7 +67,7 @@ export class AppComponent implements OnInit, AfterViewInit {
67 } 67 }
68 68
69 goToDefaultRoute () { 69 goToDefaultRoute () {
70 return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE) 70 return this.router.navigateByUrl(this.redirectService.getDefaultRoute())
71 } 71 }
72 72
73 ngOnInit () { 73 ngOnInit () {
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts
index 6d26fb504..cf690a4d0 100644
--- a/client/src/app/core/routing/redirect.service.ts
+++ b/client/src/app/core/routing/redirect.service.ts
@@ -6,14 +6,14 @@ import { ServerService } from '../server'
6export class RedirectService { 6export class RedirectService {
7 // Default route could change according to the instance configuration 7 // Default route could change according to the instance configuration
8 static INIT_DEFAULT_ROUTE = '/videos/trending' 8 static INIT_DEFAULT_ROUTE = '/videos/trending'
9 static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE
10 static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed' 9 static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed'
11 static DEFAULT_TRENDING_ALGORITHM = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM
12 10
13 private previousUrl: string 11 private previousUrl: string
14 private currentUrl: string 12 private currentUrl: string
15 13
16 private redirectingToHomepage = false 14 private redirectingToHomepage = false
15 private defaultTrendingAlgorithm = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM
16 private defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
17 17
18 constructor ( 18 constructor (
19 private router: Router, 19 private router: Router,
@@ -22,10 +22,10 @@ export class RedirectService {
22 // The config is first loaded from the cache so try to get the default route 22 // The config is first loaded from the cache so try to get the default route
23 const tmpConfig = this.serverService.getTmpConfig() 23 const tmpConfig = this.serverService.getTmpConfig()
24 if (tmpConfig?.instance?.defaultClientRoute) { 24 if (tmpConfig?.instance?.defaultClientRoute) {
25 RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute 25 this.defaultRoute = tmpConfig.instance.defaultClientRoute
26 } 26 }
27 if (tmpConfig?.trending?.videos?.algorithms?.default) { 27 if (tmpConfig?.trending?.videos?.algorithms?.default) {
28 RedirectService.DEFAULT_TRENDING_ALGORITHM = tmpConfig.trending.videos.algorithms.default 28 this.defaultTrendingAlgorithm = tmpConfig.trending.videos.algorithms.default
29 } 29 }
30 30
31 // Load default route 31 // Load default route
@@ -34,13 +34,8 @@ export class RedirectService {
34 const defaultRouteConfig = config.instance.defaultClientRoute 34 const defaultRouteConfig = config.instance.defaultClientRoute
35 const defaultTrendingConfig = config.trending.videos.algorithms.default 35 const defaultTrendingConfig = config.trending.videos.algorithms.default
36 36
37 if (defaultRouteConfig) { 37 if (defaultRouteConfig) this.defaultRoute = defaultRouteConfig
38 RedirectService.DEFAULT_ROUTE = defaultRouteConfig 38 if (defaultTrendingConfig) this.defaultTrendingAlgorithm = defaultTrendingConfig
39 }
40
41 if (defaultTrendingConfig) {
42 RedirectService.DEFAULT_TRENDING_ALGORITHM = defaultTrendingConfig
43 }
44 }) 39 })
45 40
46 // Track previous url 41 // Track previous url
@@ -53,6 +48,14 @@ export class RedirectService {
53 }) 48 })
54 } 49 }
55 50
51 getDefaultRoute () {
52 return this.defaultRoute
53 }
54
55 getDefaultTrendingAlgorithm () {
56 return this.defaultTrendingAlgorithm
57 }
58
56 redirectToPreviousRoute () { 59 redirectToPreviousRoute () {
57 const exceptions = [ 60 const exceptions = [
58 '/verify-account', 61 '/verify-account',
@@ -72,21 +75,21 @@ export class RedirectService {
72 75
73 this.redirectingToHomepage = true 76 this.redirectingToHomepage = true
74 77
75 console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE) 78 console.log('Redirecting to %s...', this.defaultRoute)
76 79
77 this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) 80 this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
78 .then(() => this.redirectingToHomepage = false) 81 .then(() => this.redirectingToHomepage = false)
79 .catch(() => { 82 .catch(() => {
80 this.redirectingToHomepage = false 83 this.redirectingToHomepage = false
81 84
82 console.error( 85 console.error(
83 'Cannot navigate to %s, resetting default route to %s.', 86 'Cannot navigate to %s, resetting default route to %s.',
84 RedirectService.DEFAULT_ROUTE, 87 this.defaultRoute,
85 RedirectService.INIT_DEFAULT_ROUTE 88 RedirectService.INIT_DEFAULT_ROUTE
86 ) 89 )
87 90
88 RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE 91 this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
89 return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) 92 return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
90 }) 93 })
91 94
92 } 95 }
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 906191ae1..e48786e18 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -3,7 +3,6 @@ import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
3import { HttpClient } from '@angular/common/http' 3import { 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 { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
7import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' 6import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
8import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models' 7import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
9import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
@@ -16,8 +15,6 @@ export class ServerService {
16 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' 15 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
17 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats' 16 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats'
18 17
19 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
20
21 configReloaded = new Subject<ServerConfig>() 18 configReloaded = new Subject<ServerConfig>()
22 19
23 private localeObservable: Observable<any> 20 private localeObservable: Observable<any>
@@ -212,7 +209,6 @@ export class ServerService {
212 if (!this.configObservable) { 209 if (!this.configObservable) {
213 this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) 210 this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
214 .pipe( 211 .pipe(
215 tap(config => this.saveConfigLocally(config)),
216 tap(config => { 212 tap(config => {
217 this.config = config 213 this.config = config
218 this.configLoaded = true 214 this.configLoaded = true
@@ -343,20 +339,15 @@ export class ServerService {
343 ) 339 )
344 } 340 }
345 341
346 private saveConfigLocally (config: ServerConfig) {
347 peertubeLocalStorage.setItem(ServerService.CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config))
348 }
349
350 private loadConfigLocally () { 342 private loadConfigLocally () {
351 const configString = peertubeLocalStorage.getItem(ServerService.CONFIG_LOCAL_STORAGE_KEY) 343 const configString = window['PeerTubeServerConfig']
352 344 if (!configString) return
353 if (configString) { 345
354 try { 346 try {
355 const parsed = JSON.parse(configString) 347 const parsed = JSON.parse(configString)
356 Object.assign(this.config, parsed) 348 Object.assign(this.config, parsed)
357 } catch (err) { 349 } catch (err) {
358 console.error('Cannot parse config saved in local storage.', err) 350 console.error('Cannot parse config saved in from index.html.', err)
359 }
360 } 351 }
361 } 352 }
362} 353}
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts
index 4c4611d01..e7a5ae17a 100644
--- a/client/src/app/core/theme/theme.service.ts
+++ b/client/src/app/core/theme/theme.service.ts
@@ -82,7 +82,19 @@ export class ThemeService {
82 : this.userService.getAnonymousUser().theme 82 : this.userService.getAnonymousUser().theme
83 83
84 if (theme !== 'instance-default') return theme 84 if (theme !== 'instance-default') return theme
85 return this.serverConfig.theme.default 85
86 const instanceTheme = this.serverConfig.theme.default
87 if (instanceTheme !== 'default') return instanceTheme
88
89 // Default to dark theme if available and wanted by the user
90 if (
91 this.themes.find(t => t.name === 'dark') &&
92 window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
93 ) {
94 return 'dark'
95 }
96
97 return instanceTheme
86 } 98 }
87 99
88 private loadTheme (name: string) { 100 private loadTheme (name: string) {
diff --git a/client/src/app/shared/shared-main/angular/from-now.pipe.ts b/client/src/app/shared/shared-main/angular/from-now.pipe.ts
index 5e7832807..d62c1f88e 100644
--- a/client/src/app/shared/shared-main/angular/from-now.pipe.ts
+++ b/client/src/app/shared/shared-main/angular/from-now.pipe.ts
@@ -3,32 +3,37 @@ import { Pipe, PipeTransform } from '@angular/core'
3// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site 3// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
4@Pipe({ name: 'myFromNow' }) 4@Pipe({ name: 'myFromNow' })
5export class FromNowPipe implements PipeTransform { 5export class FromNowPipe implements PipeTransform {
6
7 transform (arg: number | Date | string) { 6 transform (arg: number | Date | string) {
8 const argDate = new Date(arg) 7 const argDate = new Date(arg)
9 const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000) 8 const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000)
10 9
11 let interval = Math.round(seconds / 31536000) 10 let interval = Math.floor(seconds / 31536000)
12 if (interval > 1) return $localize`${interval} years ago` 11 if (interval > 1) return $localize`${interval} years ago`
13 if (interval === 1) return $localize`${interval} year ago` 12 if (interval === 1) return $localize`1 year ago`
14 13
15 interval = Math.round(seconds / 2592000) 14 interval = Math.floor(seconds / 2419200)
15 // 12 months = 360 days, but a year ~ 365 days
16 // Display "1 year ago" rather than "12 months ago"
17 if (interval >= 12) return $localize`1 year ago`
16 if (interval > 1) return $localize`${interval} months ago` 18 if (interval > 1) return $localize`${interval} months ago`
17 if (interval === 1) return $localize`${interval} month ago` 19 if (interval === 1) return $localize`1 month ago`
18 20
19 interval = Math.round(seconds / 604800) 21 interval = Math.floor(seconds / 604800)
22 // 4 weeks ~ 28 days, but our month is 30 days
23 // Display "1 month ago" rather than "4 weeks ago"
24 if (interval >= 4) return $localize`1 month ago`
20 if (interval > 1) return $localize`${interval} weeks ago` 25 if (interval > 1) return $localize`${interval} weeks ago`
21 if (interval === 1) return $localize`${interval} week ago` 26 if (interval === 1) return $localize`1 week ago`
22 27
23 interval = Math.round(seconds / 86400) 28 interval = Math.floor(seconds / 86400)
24 if (interval > 1) return $localize`${interval} days ago` 29 if (interval > 1) return $localize`${interval} days ago`
25 if (interval === 1) return $localize`${interval} day ago` 30 if (interval === 1) return $localize`1 day ago`
26 31
27 interval = Math.round(seconds / 3600) 32 interval = Math.floor(seconds / 3600)
28 if (interval > 1) return $localize`${interval} hours ago` 33 if (interval > 1) return $localize`${interval} hours ago`
29 if (interval === 1) return $localize`${interval} hour ago` 34 if (interval === 1) return $localize`1 hour ago`
30 35
31 interval = Math.round(seconds / 60) 36 interval = Math.floor(seconds / 60)
32 if (interval >= 1) return $localize`${interval} min ago` 37 if (interval >= 1) return $localize`${interval} min ago`
33 38
34 return $localize`just now` 39 return $localize`just now`
diff --git a/client/src/index.html b/client/src/index.html
index 72c184dc1..28667cdd0 100644
--- a/client/src/index.html
+++ b/client/src/index.html
@@ -29,6 +29,7 @@
29 <!-- description tag --> 29 <!-- description tag -->
30 <!-- custom css tag --> 30 <!-- custom css tag -->
31 <!-- meta tags --> 31 <!-- meta tags -->
32 <!-- server config -->
32 33
33 <!-- /!\ Do not remove it /!\ --> 34 <!-- /!\ Do not remove it /!\ -->
34 </head> 35 </head>
diff --git a/client/src/root-helpers/plugins.ts b/client/src/root-helpers/plugins.ts
index 5344c0468..8c1c858b7 100644
--- a/client/src/root-helpers/plugins.ts
+++ b/client/src/root-helpers/plugins.ts
@@ -1,14 +1,15 @@
1import { RegisterClientHelpers } from 'src/types/register-client-option.model' 1import { RegisterClientHelpers } from 'src/types/register-client-option.model'
2import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' 2import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
3import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model'
4import { 3import {
5 ClientHookName, 4 ClientHookName,
6 clientHookObject, 5 clientHookObject,
7 ClientScript, 6 ClientScript,
8 PluginType, 7 PluginType,
8 RegisterClientFormFieldOptions,
9 RegisterClientHookOptions, 9 RegisterClientHookOptions,
10 ServerConfigPlugin, 10 RegisterClientSettingsScript,
11 RegisterClientSettingsScript 11 RegisterClientVideoFieldOptions,
12 ServerConfigPlugin
12} from '../../../shared/models' 13} from '../../../shared/models'
13import { ClientScript as ClientScriptModule } from '../types/client-script.model' 14import { ClientScript as ClientScriptModule } from '../types/client-script.model'
14import { importModule } from './utils' 15import { importModule } from './utils'
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html
index 7d09bfb8f..e13a4dc24 100644
--- a/client/src/standalone/videos/embed.html
+++ b/client/src/standalone/videos/embed.html
@@ -1,14 +1,22 @@
1<!DOCTYPE html> 1<!DOCTYPE html>
2<html> 2<html>
3 <head> 3 <head>
4 <!-- title tag -->
5
6 <meta charset="UTF-8"> 4 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width, initial-scale=1"> 5 <meta name="viewport" content="width=device-width, initial-scale=1">
8 <meta name="robots" content="noindex"> 6 <meta name="robots" content="noindex">
9 <meta property="og:platform" content="PeerTube" /> 7 <meta property="og:platform" content="PeerTube" />
10 8
9
10 <!-- /!\ The following comment is used by the server to prerender some tags /!\ -->
11
12 <!-- title tag -->
13 <!-- description tag -->
11 <!-- custom css tag --> 14 <!-- custom css tag -->
15 <!-- meta tags -->
16 <!-- server config -->
17
18 <!-- /!\ Do not remove it /!\ -->
19
12 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" /> 20 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" />
13 </head> 21 </head>
14 22
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 3a90fdc58..fc61d3730 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -1,28 +1,28 @@
1import './embed.scss' 1import './embed.scss'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { peertubeTranslate } from '../../../../shared/core-utils/i18n' 3import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
4import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { 5import {
6 ClientHookName,
7 HTMLServerConfig,
8 PluginType,
5 ResultList, 9 ResultList,
6 ServerConfig,
7 UserRefreshToken, 10 UserRefreshToken,
8 VideoCaption, 11 VideoCaption,
9 VideoDetails, 12 VideoDetails,
10 VideoPlaylist, 13 VideoPlaylist,
11 VideoPlaylistElement, 14 VideoPlaylistElement,
12 VideoStreamingPlaylistType, 15 VideoStreamingPlaylistType
13 PluginType,
14 ClientHookName
15} from '../../../../shared/models' 16} from '../../../../shared/models'
16import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
17import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' 17import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
18import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 18import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
19import { TranslationsManager } from '../../assets/player/translations-manager' 19import { TranslationsManager } from '../../assets/player/translations-manager'
20import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
20import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins' 21import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins'
21import { Tokens } from '../../root-helpers/users' 22import { Tokens } from '../../root-helpers/users'
22import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
23import { objectToUrlEncoded } from '../../root-helpers/utils' 23import { objectToUrlEncoded } from '../../root-helpers/utils'
24import { PeerTubeEmbedApi } from './embed-api'
25import { RegisterClientHelpers } from '../../types/register-client-option.model' 24import { RegisterClientHelpers } from '../../types/register-client-option.model'
25import { PeerTubeEmbedApi } from './embed-api'
26 26
27type Translations = { [ id: string ]: string } 27type Translations = { [ id: string ]: string }
28 28
@@ -56,8 +56,9 @@ export class PeerTubeEmbed {
56 CLIENT_SECRET: 'client_secret' 56 CLIENT_SECRET: 'client_secret'
57 } 57 }
58 58
59 config: HTMLServerConfig
60
59 private translationsPromise: Promise<{ [id: string]: string }> 61 private translationsPromise: Promise<{ [id: string]: string }>
60 private configPromise: Promise<ServerConfig>
61 private PeertubePlayerManagerModulePromise: Promise<any> 62 private PeertubePlayerManagerModulePromise: Promise<any>
62 63
63 private playlist: VideoPlaylist 64 private playlist: VideoPlaylist
@@ -77,6 +78,12 @@ export class PeerTubeEmbed {
77 78
78 constructor (private videoWrapperId: string) { 79 constructor (private videoWrapperId: string) {
79 this.wrapperElement = document.getElementById(this.videoWrapperId) 80 this.wrapperElement = document.getElementById(this.videoWrapperId)
81
82 try {
83 this.config = JSON.parse(window['PeerTubeServerConfig'])
84 } catch (err) {
85 console.error('Cannot parse HTML config.', err)
86 }
80 } 87 }
81 88
82 getVideoUrl (id: string) { 89 getVideoUrl (id: string) {
@@ -166,11 +173,6 @@ export class PeerTubeEmbed {
166 return this.refreshFetch(url.toString(), { headers: this.headers }) 173 return this.refreshFetch(url.toString(), { headers: this.headers })
167 } 174 }
168 175
169 loadConfig (): Promise<ServerConfig> {
170 return this.refreshFetch('/api/v1/config')
171 .then(res => res.json())
172 }
173
174 removeElement (element: HTMLElement) { 176 removeElement (element: HTMLElement) {
175 element.parentElement.removeChild(element) 177 element.parentElement.removeChild(element)
176 } 178 }
@@ -466,6 +468,12 @@ export class PeerTubeEmbed {
466 this.playerElement.setAttribute('playsinline', 'true') 468 this.playerElement.setAttribute('playsinline', 'true')
467 this.wrapperElement.appendChild(this.playerElement) 469 this.wrapperElement.appendChild(this.playerElement)
468 470
471 // Issue when we parsed config from HTML, fallback to API
472 if (!this.config) {
473 this.config = await this.refreshFetch('/api/v1/config')
474 .then(res => res.json())
475 }
476
469 const videoInfoPromise = videoResponse.json() 477 const videoInfoPromise = videoResponse.json()
470 .then((videoInfo: VideoDetails) => { 478 .then((videoInfo: VideoDetails) => {
471 if (!alreadyHadPlayer) this.loadPlaceholder(videoInfo) 479 if (!alreadyHadPlayer) this.loadPlaceholder(videoInfo)
@@ -473,15 +481,14 @@ export class PeerTubeEmbed {
473 return videoInfo 481 return videoInfo
474 }) 482 })
475 483
476 const [ videoInfoTmp, serverTranslations, captionsResponse, config, PeertubePlayerManagerModule ] = await Promise.all([ 484 const [ videoInfoTmp, serverTranslations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([
477 videoInfoPromise, 485 videoInfoPromise,
478 this.translationsPromise, 486 this.translationsPromise,
479 captionsPromise, 487 captionsPromise,
480 this.configPromise,
481 this.PeertubePlayerManagerModulePromise 488 this.PeertubePlayerManagerModulePromise
482 ]) 489 ])
483 490
484 await this.ensurePluginsAreLoaded(config, serverTranslations) 491 await this.ensurePluginsAreLoaded(serverTranslations)
485 492
486 const videoInfo: VideoDetails = videoInfoTmp 493 const videoInfo: VideoDetails = videoInfoTmp
487 494
@@ -576,7 +583,7 @@ export class PeerTubeEmbed {
576 583
577 this.buildCSS() 584 this.buildCSS()
578 585
579 await this.buildDock(videoInfo, config) 586 await this.buildDock(videoInfo)
580 587
581 this.initializeApi() 588 this.initializeApi()
582 589
@@ -598,7 +605,6 @@ export class PeerTubeEmbed {
598 private async initCore () { 605 private async initCore () {
599 if (this.userTokens) this.setHeadersFromTokens() 606 if (this.userTokens) this.setHeadersFromTokens()
600 607
601 this.configPromise = this.loadConfig()
602 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language) 608 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language)
603 this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager') 609 this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager')
604 610
@@ -653,7 +659,7 @@ export class PeerTubeEmbed {
653 } 659 }
654 } 660 }
655 661
656 private async buildDock (videoInfo: VideoDetails, config: ServerConfig) { 662 private async buildDock (videoInfo: VideoDetails) {
657 if (!this.controls) return 663 if (!this.controls) return
658 664
659 // On webtorrent fallback, player may have been disposed 665 // On webtorrent fallback, player may have been disposed
@@ -661,7 +667,7 @@ export class PeerTubeEmbed {
661 667
662 const title = this.title ? videoInfo.name : undefined 668 const title = this.title ? videoInfo.name : undefined
663 669
664 const description = config.tracker.enabled && this.warningTitle 670 const description = this.config.tracker.enabled && this.warningTitle
665 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>' 671 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
666 : undefined 672 : undefined
667 673
@@ -733,10 +739,10 @@ export class PeerTubeEmbed {
733 return window.location.pathname.split('/')[1] === 'video-playlists' 739 return window.location.pathname.split('/')[1] === 'video-playlists'
734 } 740 }
735 741
736 private async ensurePluginsAreLoaded (config: ServerConfig, translations?: { [ id: string ]: string }) { 742 private async ensurePluginsAreLoaded (translations?: { [ id: string ]: string }) {
737 if (config.plugin.registered.length === 0) return 743 if (this.config.plugin.registered.length === 0) return
738 744
739 for (const plugin of config.plugin.registered) { 745 for (const plugin of this.config.plugin.registered) {
740 for (const key of Object.keys(plugin.clientScripts)) { 746 for (const key of Object.keys(plugin.clientScripts)) {
741 const clientScript = plugin.clientScripts[key] 747 const clientScript = plugin.clientScripts[key]
742 748