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 }}
-
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
+ }
}
}