diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index 83b1c6a31..dd62a4aab 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html @@ -271,10 +271,9 @@ - - - - + + + @@ -283,17 +282,20 @@
{{ formErrors.instance.defaultClientRoute }}
-
- -
- -
-
{{ formErrors.instance.defaultTrendingRoute }}
+
+ + + +
+ +
+
{{ formErrors.trending.videos.algorithms.default }}
+
+
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 e6fc4582b..9a46a2e59 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 @@ -186,12 +186,6 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A languages: null, defaultClientRoute: null, - defaultTrendingRoute: null, - pages: { - hot: { - enabled: null - } - }, customizations: { javascript: null, @@ -230,6 +224,14 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A } } }, + trending: { + videos: { + algorithms: { + enabled: null, + default: null + } + } + }, admin: { email: ADMIN_EMAIL_VALIDATOR }, @@ -370,8 +372,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A return this.form.value['followings']['instance']['autoFollowIndex']['enabled'] === true } - isTrendingHotEnabled () { - return this.form.value['instance']['pages']['hot']['enabled'] === true + trendingVideosAlgorithmsEnabledIncludes (algorithm: string) { + return this.form.value['trending']['videos']['algorithms']['enabled'].find((e: string) => e === algorithm) } async formValidated () { diff --git a/client/src/app/+videos/video-list/trending/index.ts b/client/src/app/+videos/video-list/trending/index.ts index 93f4b1df6..70835885a 100644 --- a/client/src/app/+videos/video-list/trending/index.ts +++ b/client/src/app/+videos/video-list/trending/index.ts @@ -1,4 +1,2 @@ export * from './video-trending-header.component' -export * from './video-hot.component' -export * from './video-most-viewed.component' -export * from './video-most-liked.component' +export * from './video-trending.component' diff --git a/client/src/app/+videos/video-list/trending/video-hot.component.ts b/client/src/app/+videos/video-list/trending/video-hot.component.ts deleted file mode 100644 index 1617eb21e..000000000 --- a/client/src/app/+videos/video-list/trending/video-hot.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Component, ComponentFactoryResolver, Injector, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { VideoSortField } from '@shared/models' -import { VideoTrendingHeaderComponent } from './video-trending-header.component' - -@Component({ - selector: 'my-videos-hot', - styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoHotComponent extends AbstractVideoList implements OnInit, OnDestroy { - HeaderComponent = VideoTrendingHeaderComponent - titlePage: string - defaultSort: VideoSortField = '-hot' - - useUserVideoPreferences = true - - constructor ( - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - protected cfr: ComponentFactoryResolver, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.headerComponentInjector = this.getInjector() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.trending-videos.videos.list.params', - 'filter:api.trending-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } - - getInjector () { - return Injector.create({ - providers: [{ - provide: 'data', - useValue: { - model: this.defaultSort - } - }] - }) - } -} diff --git a/client/src/app/+videos/video-list/trending/video-most-liked.component.ts b/client/src/app/+videos/video-list/trending/video-most-liked.component.ts deleted file mode 100644 index 1781cc6aa..000000000 --- a/client/src/app/+videos/video-list/trending/video-most-liked.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, ComponentFactoryResolver, Injector, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { VideoSortField } from '@shared/models' -import { VideoTrendingHeaderComponent } from './video-trending-header.component' - -@Component({ - selector: 'my-videos-most-liked', - styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoMostLikedComponent extends AbstractVideoList implements OnInit { - HeaderComponent = VideoTrendingHeaderComponent - titlePage: string - defaultSort: VideoSortField = '-likes' - - useUserVideoPreferences = true - - constructor ( - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - protected cfr: ComponentFactoryResolver, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.headerComponentInjector = this.getInjector() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.most-liked-videos.videos.list.params', - 'filter:api.most-liked-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } - - getInjector () { - return Injector.create({ - providers: [{ - provide: 'data', - useValue: { - model: this.defaultSort - } - }] - }) - } -} diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.html b/client/src/app/+videos/video-list/trending/video-trending-header.component.html index a025bf1a2..7eb1e4f95 100644 --- a/client/src/app/+videos/video-list/trending/video-trending-header.component.html +++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.html @@ -1,6 +1,8 @@
- + + +
\ No newline at end of file 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 e49b61c68..33eaa2c1e 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 @@ -1,30 +1,34 @@ -import { Component, Inject, OnInit } from '@angular/core' -import { Router } from '@angular/router' +import { Component, HostBinding, Inject, OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' import { VideoListHeaderComponent } from '@app/shared/shared-video-miniature' import { GlobalIconName } from '@app/shared/shared-icons' -import { VideoSortField } from '@shared/models' import { ServerService } from '@app/core/server/server.service' +import { Subscription } from 'rxjs' +import { RedirectService } from '@app/core' interface VideoTrendingHeaderItem { label: string iconName: GlobalIconName - value: VideoSortField - path: string + value: string tooltip?: string hidden?: boolean } @Component({ selector: 'video-trending-title-page', - host: { 'class': 'title-page title-page-single' }, styleUrls: [ './video-trending-header.component.scss' ], templateUrl: './video-trending-header.component.html' }) -export class VideoTrendingHeaderComponent extends VideoListHeaderComponent implements OnInit { +export class VideoTrendingHeaderComponent extends VideoListHeaderComponent implements OnInit, OnDestroy { + @HostBinding('class') class = 'title-page title-page-single' + buttons: VideoTrendingHeaderItem[] + private algorithmChangeSub: Subscription + constructor ( @Inject('data') public data: any, + private route: ActivatedRoute, private router: Router, private serverService: ServerService ) { @@ -34,23 +38,20 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple { label: $localize`:A variant of Trending videos based on the number of recent interactions:Hot`, iconName: 'flame', - value: '-hot', - path: 'hot', + value: 'hot', tooltip: $localize`Videos totalizing the most interactions for recent videos`, hidden: true }, { label: $localize`:Main variant of Trending videos based on number of recent views:Views`, iconName: 'trending', - value: '-trending', - path: 'most-viewed', - tooltip: $localize`Videos totalizing the most views during the last 24 hours`, + value: 'most-viewed', + tooltip: $localize`Videos totalizing the most views during the last 24 hours` }, { label: $localize`:A variant of Trending videos based on the number of likes:Likes`, iconName: 'like', - value: '-likes', - path: 'most-liked', + value: 'most-liked', tooltip: $localize`Videos that have the most likes` } ] @@ -59,20 +60,40 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple ngOnInit () { this.serverService.getConfig() .subscribe(config => { - // don't filter if auto-blacklist is not enabled as this will be the only list - if (config.instance.pages.hot.enabled) { - const index = this.buttons.findIndex(b => b.path === 'hot') - this.buttons[index].hidden = false - } + this.buttons = this.buttons.map(b => { + b.hidden = !config.trending.videos.algorithms.enabled.includes(b.value) + return b + }) }) + + this.algorithmChangeSub = this.route.queryParams.subscribe( + queryParams => { + const algorithm = queryParams['alg'] + if (algorithm) { + this.data.model = algorithm + } else { + this.data.model = RedirectService.DEFAULT_TRENDING_ALGORITHM + } + } + ) } - get visibleButtons () { - return this.buttons.filter(b => !b.hidden) + ngOnDestroy () { + if (this.algorithmChangeSub) this.algorithmChangeSub.unsubscribe() } setSort () { - const path = this.buttons.find(b => b.value === this.data.model).path - this.router.navigate([ `/videos/${path}` ]) + const alg = this.data.model !== RedirectService.DEFAULT_TRENDING_ALGORITHM + ? this.data.model + : undefined + + this.router.navigate( + [], + { + relativeTo: this.route, + queryParams: { alg }, + queryParamsHandling: 'merge' + } + ) } } diff --git a/client/src/app/+videos/video-list/trending/video-most-viewed.component.ts b/client/src/app/+videos/video-list/trending/video-trending.component.ts similarity index 70% rename from client/src/app/+videos/video-list/trending/video-most-viewed.component.ts rename to client/src/app/+videos/video-list/trending/video-trending.component.ts index 98ced42d6..6128c4acd 100644 --- a/client/src/app/+videos/video-list/trending/video-most-viewed.component.ts +++ b/client/src/app/+videos/video-list/trending/video-trending.component.ts @@ -1,25 +1,28 @@ import { Component, ComponentFactoryResolver, Injector, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' +import { AuthService, LocalStorageService, Notifier, RedirectService, ScreenService, ServerService, UserService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' import { immutableAssign } from '@app/helpers' import { VideoService } from '@app/shared/shared-main' import { AbstractVideoList } from '@app/shared/shared-video-miniature' import { VideoSortField } from '@shared/models' +import { Subscription } from 'rxjs' import { VideoTrendingHeaderComponent } from './video-trending-header.component' @Component({ - selector: 'my-videos-most-viewed', + selector: 'my-videos-hot', styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' }) -export class VideoMostViewedComponent extends AbstractVideoList implements OnInit, OnDestroy { +export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { HeaderComponent = VideoTrendingHeaderComponent titlePage: string defaultSort: VideoSortField = '-trending' useUserVideoPreferences = true + private algorithmChangeSub: Subscription + constructor ( protected router: Router, protected serverService: ServerService, @@ -35,6 +38,8 @@ export class VideoMostViewedComponent extends AbstractVideoList implements OnIni ) { super() + this.defaultSort = this.parseAlgorithm(RedirectService.DEFAULT_TRENDING_ALGORITHM) + this.headerComponentInjector = this.getInjector() } @@ -43,23 +48,19 @@ export class VideoMostViewedComponent extends AbstractVideoList implements OnIni this.generateSyndicationList() - this.serverService.getConfig().subscribe( - config => { - const trendingDays = config.trending.videos.intervalDays + this.algorithmChangeSub = this.route.queryParams.subscribe( + queryParams => { + const algorithm = queryParams['alg'] || RedirectService.DEFAULT_TRENDING_ALGORITHM - if (trendingDays === 1) { - this.titleTooltip = $localize`Trending videos are those totalizing the greatest number of views during the last 24 hours` - } else { - this.titleTooltip = $localize`Trending videos are those totalizing the greatest number of views during the last ${trendingDays} days` - } - - this.headerComponentInjector = this.getInjector() - this.setHeader() - }) + this.sort = this.parseAlgorithm(algorithm) + this.reloadVideos() + } + ) } ngOnDestroy () { super.ngOnDestroy() + if (this.algorithmChangeSub) this.algorithmChangeSub.unsubscribe() } getVideosObservable (page: number) { @@ -96,4 +97,15 @@ export class VideoMostViewedComponent extends AbstractVideoList implements OnIni }] }) } + + private parseAlgorithm (algorithm: string): VideoSortField { + switch (algorithm) { + case 'most-viewed': + return '-trending' + case 'most-liked': + return '-likes' + default: + return '-' + algorithm as VideoSortField + } + } } diff --git a/client/src/app/+videos/videos-routing.module.ts b/client/src/app/+videos/videos-routing.module.ts index 973935af8..16e3b9bb2 100644 --- a/client/src/app/+videos/videos-routing.module.ts +++ b/client/src/app/+videos/videos-routing.module.ts @@ -1,11 +1,9 @@ import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' -import { LoginGuard, TrendingGuard } from '@app/core' +import { LoginGuard } from '@app/core' import { MetaGuard } from '@ngx-meta/core' +import { VideoTrendingComponent } from './video-list' import { VideoOverviewComponent } from './video-list/overview/video-overview.component' -import { VideoHotComponent } from './video-list/trending/video-hot.component' -import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' -import { VideoMostViewedComponent } from './video-list/trending/video-most-viewed.component' import { VideoLocalComponent } from './video-list/video-local.component' import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' @@ -28,46 +26,16 @@ const videosRoutes: Routes = [ }, { path: 'trending', - canActivate: [ TrendingGuard ] - }, - { - path: 'hot', - component: VideoHotComponent, + component: VideoTrendingComponent, data: { meta: { - title: $localize`Hot videos` - }, - reuse: { - enabled: true, - key: 'hot-videos-list' - } - } - }, - { - path: 'most-viewed', - component: VideoMostViewedComponent, - data: { - meta: { - title: $localize`Most viewed videos` - }, - reuse: { - enabled: true, - key: 'most-viewed-videos-list' + title: $localize`Trending videos` } } }, { path: 'most-liked', - component: VideoMostLikedComponent, - data: { - meta: { - title: $localize`Most liked videos` - }, - reuse: { - enabled: true, - key: 'most-liked-videos-list' - } - } + redirectTo: 'trending?alg=most-liked' }, { path: 'recently-added', diff --git a/client/src/app/+videos/videos.module.ts b/client/src/app/+videos/videos.module.ts index ae9c680eb..61d012d63 100644 --- a/client/src/app/+videos/videos.module.ts +++ b/client/src/app/+videos/videos.module.ts @@ -1,16 +1,12 @@ -import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { SharedFormModule } from '@app/shared/shared-forms' import { SharedGlobalIconModule } from '@app/shared/shared-icons' import { SharedMainModule } from '@app/shared/shared-main' import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' -import { OverviewService } from './video-list' +import { OverviewService, VideoTrendingComponent } from './video-list' import { VideoOverviewComponent } from './video-list/overview/video-overview.component' import { VideoTrendingHeaderComponent } from './video-list/trending/video-trending-header.component' -import { VideoHotComponent } from './video-list/trending/video-hot.component' -import { VideoMostViewedComponent } from './video-list/trending/video-most-viewed.component' -import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' import { VideoLocalComponent } from './video-list/video-local.component' import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' @@ -32,9 +28,7 @@ import { VideosComponent } from './videos.component' VideosComponent, VideoTrendingHeaderComponent, - VideoMostViewedComponent, - VideoHotComponent, - VideoMostLikedComponent, + VideoTrendingComponent, VideoRecentlyAddedComponent, VideoLocalComponent, VideoUserSubscriptionsComponent, diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 77d876f8e..108b127be 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -8,7 +8,7 @@
- + {{ instanceName }} diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index ca4b69899..66d871b4a 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -66,8 +66,8 @@ export class AppComponent implements OnInit, AfterViewInit { return this.serverConfig.instance.name } - get defaultRoute () { - return RedirectService.DEFAULT_ROUTE + goToDefaultRoute () { + return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE) } ngOnInit () { diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 32dfc8f36..2392a234c 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts @@ -15,7 +15,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard' import { Notifier } from './notification' import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer' import { RestExtractor, RestService } from './rest' -import { LoginGuard, RedirectService, UserRightGuard, UnloggedGuard, TrendingGuard } from './routing' +import { LoginGuard, RedirectService, UserRightGuard, UnloggedGuard } from './routing' import { CanDeactivateGuard } from './routing/can-deactivate-guard.service' import { ServerConfigResolver } from './routing/server-config-resolver.service' import { ScopedTokensService } from './scoped-tokens' @@ -56,7 +56,6 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra LoginGuard, UserRightGuard, UnloggedGuard, - TrendingGuard, PluginService, HooksService, diff --git a/client/src/app/core/routing/index.ts b/client/src/app/core/routing/index.ts index b3985d870..239c27caf 100644 --- a/client/src/app/core/routing/index.ts +++ b/client/src/app/core/routing/index.ts @@ -8,4 +8,3 @@ export * from './redirect.service' export * from './server-config-resolver.service' export * from './unlogged-guard.service' export * from './user-right-guard.service' -export * from './trending-guard.service' diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts index 76e28e461..6d26fb504 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts @@ -7,14 +7,13 @@ export class RedirectService { // Default route could change according to the instance configuration static INIT_DEFAULT_ROUTE = '/videos/trending' static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE - static INIT_DEFAULT_TRENDING_ROUTE = '/videos/most-viewed' - static DEFAULT_TRENDING_ROUTE = RedirectService.INIT_DEFAULT_TRENDING_ROUTE + static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed' + static DEFAULT_TRENDING_ALGORITHM = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM private previousUrl: string private currentUrl: string private redirectingToHomepage = false - private redirectingToTrending = false constructor ( private router: Router, @@ -22,27 +21,25 @@ export class RedirectService { ) { // The config is first loaded from the cache so try to get the default route const tmpConfig = this.serverService.getTmpConfig() - if (tmpConfig && tmpConfig.instance) { - if (tmpConfig.instance.defaultClientRoute) { - RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute - } - if (tmpConfig.instance.defaultTrendingRoute) { - RedirectService.DEFAULT_TRENDING_ROUTE = tmpConfig.instance.defaultTrendingRoute - } + if (tmpConfig?.instance?.defaultClientRoute) { + RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute + } + if (tmpConfig?.trending?.videos?.algorithms?.default) { + RedirectService.DEFAULT_TRENDING_ALGORITHM = tmpConfig.trending.videos.algorithms.default } // Load default route this.serverService.getConfig() .subscribe(config => { const defaultRouteConfig = config.instance.defaultClientRoute - const defaultTrendingConfig = config.instance.defaultTrendingRoute + const defaultTrendingConfig = config.trending.videos.algorithms.default if (defaultRouteConfig) { RedirectService.DEFAULT_ROUTE = defaultRouteConfig } if (defaultTrendingConfig) { - RedirectService.DEFAULT_TRENDING_ROUTE = defaultTrendingConfig + RedirectService.DEFAULT_TRENDING_ALGORITHM = defaultTrendingConfig } }) @@ -70,15 +67,6 @@ export class RedirectService { return this.redirectToHomepage() } - redirectToTrending () { - if (this.redirectingToTrending) return - - this.redirectingToTrending = true - - this.router.navigate([ RedirectService.DEFAULT_TRENDING_ROUTE ]) - .then(() => this.redirectingToTrending = false) - } - redirectToHomepage (skipLocationChange = false) { if (this.redirectingToHomepage) return @@ -86,7 +74,7 @@ export class RedirectService { console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE) - this.router.navigate([ RedirectService.DEFAULT_ROUTE ], { skipLocationChange }) + this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) .then(() => this.redirectingToHomepage = false) .catch(() => { this.redirectingToHomepage = false @@ -98,7 +86,7 @@ export class RedirectService { ) RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE - return this.router.navigate([ RedirectService.DEFAULT_ROUTE ], { skipLocationChange }) + return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) }) } diff --git a/client/src/app/core/routing/trending-guard.service.ts b/client/src/app/core/routing/trending-guard.service.ts deleted file mode 100644 index 7db7fe994..000000000 --- a/client/src/app/core/routing/trending-guard.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Injectable } from '@angular/core' -import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router' -import { RedirectService } from './redirect.service' - -@Injectable() -export class TrendingGuard implements CanActivate { - - constructor (private redirectService: RedirectService) {} - - canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { - this.redirectService.redirectToTrending() - return false - } -} diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index 5f13190b4..a38883eee 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -39,12 +39,6 @@ export class ServerService { isNSFW: false, defaultNSFWPolicy: 'do_not_list' as 'do_not_list', defaultClientRoute: '', - defaultTrendingRoute: '', - pages: { - hot: { - enabled: true - } - }, customizations: { javascript: '', css: '' @@ -131,7 +125,11 @@ export class ServerService { }, trending: { videos: { - intervalDays: 0 + intervalDays: 0, + algorithms: { + enabled: [ 'hot', 'most-viewed', 'most-liked' ], + default: 'most-viewed' + } } }, autoBlacklist: { diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index fc57b970b..9aa397edd 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html @@ -127,14 +127,10 @@ Discover - + Trending - - - - 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 67bbf7d7a..08a961be1 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 @@ -1,16 +1,22 @@ import { Component, Inject, ViewEncapsulation } from '@angular/core' +export interface GenericHeaderData { + titlePage: string + titleTooltip?: string +} + export abstract class GenericHeaderComponent { - constructor (@Inject('data') public data: any) {} + constructor (@Inject('data') public data: GenericHeaderData) {} } @Component({ selector: 'my-video-list-header', + // tslint:disable-next-line:use-component-view-encapsulation encapsulation: ViewEncapsulation.None, templateUrl: './video-list-header.component.html' }) export class VideoListHeaderComponent extends GenericHeaderComponent { - constructor (@Inject('data') public data: any) { + constructor (@Inject('data') public data: GenericHeaderData) { super(data) } } diff --git a/config/default.yaml b/config/default.yaml index 43c7f4a53..b9e382fa7 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -106,6 +106,12 @@ log: trending: videos: interval_days: 7 # Compute trending videos for the last x days + algorithms: + enabled: + - 'hot' # adaptation of the Reddit 'Hot' algorithm + - 'most-viewed' # default, used initially by PeerTube as the trending page + - 'most-liked' + default: 'most-viewed' # Cache remote videos on your server, to help other instances to broadcast the video # You can define multiple caches using different sizes/strategies @@ -366,10 +372,6 @@ instance: # - 18 # Food default_client_route: '/videos/trending' - default_trending_route: '/videos/most-viewed' - pages: - hot: - enabled: true # Whether or not the instance is dedicated to NSFW content # Enabling it will allow other administrators to know that you are mainly federating sensitive content diff --git a/config/production.yaml.example b/config/production.yaml.example index f9f3abc18..b616c6ced 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -106,6 +106,12 @@ log: trending: videos: interval_days: 7 # Compute trending videos for the last x days + algorithms: + enabled: + - 'hot' # adaptation of the Reddit 'Hot' algorithm + - 'most-viewed' # default, used initially by PeerTube as the trending page + - 'most-liked' + default: 'most-viewed' # Cache remote videos on your server, to help other instances to broadcast the video # You can define multiple caches using different sizes/strategies @@ -380,10 +386,6 @@ instance: # - 18 # Food default_client_route: '/videos/trending' - default_trending_route: '/videos/most-viewed' - pages: - hot: - enabled: true # Whether or not the instance is dedicated to NSFW content # Enabling it will allow other administrators to know that you are mainly federating sensitive content diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 24e7601ec..45c03be24 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -68,12 +68,6 @@ async function getConfig (req: express.Request, res: express.Response) { isNSFW: CONFIG.INSTANCE.IS_NSFW, defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, - defaultTrendingRoute: CONFIG.INSTANCE.DEFAULT_TRENDING_ROUTE, - pages: { - hot: { - enabled: CONFIG.INSTANCE.PAGES.HOT.ENABLED - } - }, customizations: { javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS @@ -189,7 +183,11 @@ async function getConfig (req: express.Request, res: express.Response) { }, trending: { videos: { - intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS + intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS, + algorithms: { + enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED, + default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT + } } }, tracker: { @@ -371,12 +369,6 @@ function customConfig (): CustomConfig { defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, - defaultTrendingRoute: CONFIG.INSTANCE.DEFAULT_TRENDING_ROUTE, - pages: { - hot: { - enabled: CONFIG.INSTANCE.PAGES.HOT.ENABLED - } - }, customizations: { css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS, @@ -467,6 +459,14 @@ function customConfig (): CustomConfig { } } }, + trending: { + videos: { + algorithms: { + enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED, + default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT + } + } + }, autoBlacklist: { videos: { ofUsers: { diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index e474959b2..6aae5e821 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts @@ -230,7 +230,6 @@ const customConfigKeysToKeep = [ 'instance-description', 'instance-terms', 'instance-defaultClientRoute', - 'instance-defaultTrendingRoute', 'instance-defaultNSFWPolicy', 'instance-customizations-javascript', 'instance-customizations-css', diff --git a/server/initializers/config.ts b/server/initializers/config.ts index e1f807752..fc4a8b709 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts @@ -110,7 +110,11 @@ const CONFIG = { }, TRENDING: { VIDEOS: { - INTERVAL_DAYS: config.get('trending.videos.interval_days') + INTERVAL_DAYS: config.get('trending.videos.interval_days'), + ALGORITHMS: { + get ENABLED () { return config.get('trending.videos.algorithms.enabled') }, + get DEFAULT () { return config.get('trending.videos.algorithms.default') } + } } }, REDUNDANCY: { @@ -281,12 +285,6 @@ const CONFIG = { get DEFAULT_NSFW_POLICY () { return config.get('instance.default_nsfw_policy') }, get DEFAULT_CLIENT_ROUTE () { return config.get('instance.default_client_route') }, - get DEFAULT_TRENDING_ROUTE () { return config.get('instance.default_trending_route') }, - PAGES: { - HOT: { - get ENABLED () { return config.get('instance.pages.hot.enabled') } - } - }, CUSTOMIZATIONS: { get JAVASCRIPT () { return config.get('instance.customizations.javascript') }, diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 0efe1157f..52c28799f 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts @@ -17,7 +17,6 @@ const customConfigUpdateValidator = [ body('instance.terms').exists().withMessage('Should have a valid instance terms'), body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid).withMessage('Should have a valid NSFW policy'), body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'), - body('instance.defaultTrendingRoute').exists().withMessage('Should have a valid instance default trending route'), body('instance.customizations.css').exists().withMessage('Should have a valid instance CSS customization'), body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'), @@ -55,6 +54,9 @@ const customConfigUpdateValidator = [ body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), + body('trending.videos.algorithms.default').exists().withMessage('Should have a valid default trending algorithm'), + body('trending.videos.algorithms.enabled').exists().withMessage('Should have a valid array of enabled trending algorithms'), + body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index e58e0cd9f..d3ae5fe0a 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts @@ -44,12 +44,6 @@ describe('Test config API validators', function () { defaultNSFWPolicy: 'blur', defaultClientRoute: '/videos/recently-added', - defaultTrendingRoute: '/videos/trending', - pages: { - hot: { - enabled: true - } - }, customizations: { javascript: 'alert("coucou")', @@ -142,6 +136,14 @@ describe('Test config API validators', function () { } } }, + trending: { + videos: { + algorithms: { + enabled: [ 'hot', 'most-viewed', 'most-liked' ], + default: 'most-viewed' + } + } + }, autoBlacklist: { videos: { ofUsers: { diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index 328f4852a..e0575bdfd 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts @@ -276,12 +276,6 @@ describe('Test config', function () { defaultNSFWPolicy: 'blur' as 'blur', defaultClientRoute: '/videos/recently-added', - defaultTrendingRoute: '/videos/trending', - pages: { - hot: { - enabled: true - } - }, customizations: { javascript: 'alert("coucou")', @@ -372,6 +366,14 @@ describe('Test config', function () { } } }, + trending: { + videos: { + algorithms: { + enabled: [ 'hot', 'most-viewed', 'most-liked' ], + default: 'hot' + } + } + }, autoBlacklist: { videos: { ofUsers: { diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index 5152ec693..4e09e0412 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts @@ -69,12 +69,6 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti defaultNSFWPolicy: 'blur', defaultClientRoute: '/videos/recently-added', - defaultTrendingRoute: '/videos/trending', - pages: { - hot: { - enabled: true - } - }, customizations: { javascript: 'alert("coucou")', @@ -165,6 +159,14 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti } } }, + trending: { + videos: { + algorithms: { + enabled: [ 'hot', 'most-viewed', 'most-liked' ], + default: 'hot' + } + } + }, autoBlacklist: { videos: { ofUsers: { diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index fcc29e5d7..a57237414 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts @@ -33,12 +33,6 @@ export interface CustomConfig { defaultNSFWPolicy: NSFWPolicyType defaultClientRoute: string - defaultTrendingRoute: string - pages: { - hot: { - enabled: boolean - } - } customizations: { javascript?: string @@ -131,6 +125,15 @@ export interface CustomConfig { } } + trending: { + videos: { + algorithms: { + enabled: string[] + default: string + } + } + } + autoBlacklist: { videos: { ofUsers: { diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index a2d93ce73..47d0e623b 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts @@ -39,12 +39,6 @@ export interface ServerConfig { isNSFW: boolean defaultNSFWPolicy: NSFWPolicyType defaultClientRoute: string - defaultTrendingRoute: string - pages: { - hot: { - enabled: boolean - } - } customizations: { javascript: string css: string @@ -180,6 +174,10 @@ export interface ServerConfig { trending: { videos: { intervalDays: number + algorithms: { + enabled: string[] + default: string + } } }