mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-24 09:40:28 -06:00
move from trending routes to alg param
This commit is contained in:
parent
3da68f0a78
commit
ba5d4a849c
@ -271,10 +271,9 @@
|
||||
<option i18n value="/videos/overview">Discover videos</option>
|
||||
<optgroup i18n-label label="Trending pages">
|
||||
<option i18n value="/videos/trending">Default trending page</option>
|
||||
<option i18n value="/videos/hot" *ngIf="isTrendingHotEnabled()">Hot videos</option>
|
||||
<option i18n value="/videos/hot" *ngIf="!isTrendingHotEnabled()" disabled>Hot videos</option>
|
||||
<option i18n value="/videos/most-viewed">Most viewed videos</option>
|
||||
<option i18n value="/videos/most-liked">Most liked videos</option>
|
||||
<option i18n value="/videos/trending?alg=hot" [disabled]="!trendingVideosAlgorithmsEnabledIncludes('hot')">Hot videos</option>
|
||||
<option i18n value="/videos/trending?alg=most-viewed" [disabled]="!trendingVideosAlgorithmsEnabledIncludes('most-viewed')">Most viewed videos</option>
|
||||
<option i18n value="/videos/trending?alg=most-liked" [disabled]="!trendingVideosAlgorithmsEnabledIncludes('most-liked')">Most liked videos</option>
|
||||
</optgroup>
|
||||
<option i18n value="/videos/recently-added">Recently added videos</option>
|
||||
<option i18n value="/videos/local">Local videos</option>
|
||||
@ -283,17 +282,20 @@
|
||||
<div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" formGroupName="instance">
|
||||
<label i18n for="instanceDefaultTrendingRoute">Default trending page</label>
|
||||
<div class="peertube-select-container">
|
||||
<select id="instanceDefaultTrendingRoute" formControlName="defaultTrendingRoute" class="form-control">
|
||||
<option i18n value="/videos/hot" *ngIf="isTrendingHotEnabled()">Hot videos</option>
|
||||
<option i18n value="/videos/hot" *ngIf="!isTrendingHotEnabled()" disabled>Hot videos</option>
|
||||
<option i18n value="/videos/trending">Most viewed videos</option>
|
||||
<option i18n value="/videos/most-liked">Most liked videos</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngIf="formErrors.instance.defaultTrendingRoute" class="form-error">{{ formErrors.instance.defaultTrendingRoute }}</div>
|
||||
<div class="form-group" formGroupName="trending">
|
||||
<ng-container formGroupName="videos">
|
||||
<ng-container formGroupName="algorithms">
|
||||
<label i18n for="trendingVideosAlgorithmsDefault">Default trending page</label>
|
||||
<div class="peertube-select-container">
|
||||
<select id="trendingVideosAlgorithmsDefault" formControlName="default" class="form-control">
|
||||
<option i18n value="hot">Hot videos</option>
|
||||
<option i18n value="most-viewed">Most viewed videos</option>
|
||||
<option i18n value="most-liked">Most liked videos</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngIf="formErrors.trending.videos.algorithms.default" class="form-error">{{ formErrors.trending.videos.algorithms.default }}</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -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 () {
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" [(ngModel)]="data.model" (ngModelChange)="setSort()">
|
||||
<label *ngFor="let button of visibleButtons" ngbButtonLabel class="btn-light" placement="bottom" [ngbTooltip]="button.tooltip" container="body">
|
||||
<my-global-icon [iconName]="button.iconName"></my-global-icon>
|
||||
<input ngbButton type="radio" [value]="button.value"> {{ button.label }}
|
||||
</label>
|
||||
<ng-container *ngFor="let button of buttons">
|
||||
<label *ngIf="!button.hidden" ngbButtonLabel class="btn-light" placement="bottom" [ngbTooltip]="button.tooltip" container="body">
|
||||
<my-global-icon [iconName]="button.iconName"></my-global-icon>
|
||||
<input ngbButton type="radio" [value]="button.value"> {{ button.label }}
|
||||
</label>
|
||||
</ng-container>
|
||||
</div>
|
@ -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'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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,
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="top-left-block">
|
||||
<span class="icon icon-menu" role="button" [title]="getToggleTitle()" (click)="menu.toggleMenu()"></span>
|
||||
|
||||
<a class="peertube-title" [routerLink]="defaultRoute">
|
||||
<a class="peertube-title c-hand" (click)="goToDefaultRoute()">
|
||||
<span class="icon icon-logo"></span>
|
||||
<span class="instance-name">{{ instanceName }}</span>
|
||||
</a>
|
||||
|
@ -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 () {
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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 })
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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: {
|
||||
|
@ -127,14 +127,10 @@
|
||||
<ng-container i18n>Discover</ng-container>
|
||||
</a>
|
||||
|
||||
<a routerLink="/videos/trending" routerLinkActive="active" [ngClass]="{ 'active': hot.isActive || mostViewed.isActive || mostLiked.isActive }">
|
||||
<a routerLink="/videos/trending" routerLinkActive="active">
|
||||
<my-global-icon iconName="trending" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Trending</ng-container>
|
||||
</a>
|
||||
<a routerLink="/videos/hot" routerLinkActive #hot="routerLinkActive" hidden></a>
|
||||
<a routerLink="/videos/most-viewed" routerLinkActive #mostViewed="routerLinkActive" hidden></a>
|
||||
<a routerLink="/videos/most-liked" routerLinkActive #mostLiked="routerLinkActive" hidden></a>
|
||||
|
||||
|
||||
<a routerLink="/videos/recently-added" routerLinkActive="active">
|
||||
<my-global-icon iconName="recently-added" aria-hidden="true"></my-global-icon>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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: {
|
||||
|
@ -230,7 +230,6 @@ const customConfigKeysToKeep = [
|
||||
'instance-description',
|
||||
'instance-terms',
|
||||
'instance-defaultClientRoute',
|
||||
'instance-defaultTrendingRoute',
|
||||
'instance-defaultNSFWPolicy',
|
||||
'instance-customizations-javascript',
|
||||
'instance-customizations-css',
|
||||
|
@ -110,7 +110,11 @@ const CONFIG = {
|
||||
},
|
||||
TRENDING: {
|
||||
VIDEOS: {
|
||||
INTERVAL_DAYS: config.get<number>('trending.videos.interval_days')
|
||||
INTERVAL_DAYS: config.get<number>('trending.videos.interval_days'),
|
||||
ALGORITHMS: {
|
||||
get ENABLED () { return config.get<string[]>('trending.videos.algorithms.enabled') },
|
||||
get DEFAULT () { return config.get<string>('trending.videos.algorithms.default') }
|
||||
}
|
||||
}
|
||||
},
|
||||
REDUNDANCY: {
|
||||
@ -281,12 +285,6 @@ const CONFIG = {
|
||||
get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
|
||||
|
||||
get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
|
||||
get DEFAULT_TRENDING_ROUTE () { return config.get<string>('instance.default_trending_route') },
|
||||
PAGES: {
|
||||
HOT: {
|
||||
get ENABLED () { return config.get<boolean>('instance.pages.hot.enabled') }
|
||||
}
|
||||
},
|
||||
|
||||
CUSTOMIZATIONS: {
|
||||
get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
|
||||
|
@ -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'),
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user