diff options
30 files changed, 256 insertions, 103 deletions
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 9e4691670..83b1c6a31 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 | |||
@@ -269,9 +269,13 @@ | |||
269 | <div class="peertube-select-container"> | 269 | <div class="peertube-select-container"> |
270 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute" class="form-control"> | 270 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute" class="form-control"> |
271 | <option i18n value="/videos/overview">Discover videos</option> | 271 | <option i18n value="/videos/overview">Discover videos</option> |
272 | <option i18n value="/videos/trending">Trending videos</option> | 272 | <optgroup i18n-label label="Trending pages"> |
273 | <option i18n value="/videos/hot">Hot videos</option> | 273 | <option i18n value="/videos/trending">Default trending page</option> |
274 | <option i18n value="/videos/most-liked">Most liked videos</option> | 274 | <option i18n value="/videos/hot" *ngIf="isTrendingHotEnabled()">Hot videos</option> |
275 | <option i18n value="/videos/hot" *ngIf="!isTrendingHotEnabled()" disabled>Hot videos</option> | ||
276 | <option i18n value="/videos/most-viewed">Most viewed videos</option> | ||
277 | <option i18n value="/videos/most-liked">Most liked videos</option> | ||
278 | </optgroup> | ||
275 | <option i18n value="/videos/recently-added">Recently added videos</option> | 279 | <option i18n value="/videos/recently-added">Recently added videos</option> |
276 | <option i18n value="/videos/local">Local videos</option> | 280 | <option i18n value="/videos/local">Local videos</option> |
277 | </select> | 281 | </select> |
@@ -279,6 +283,19 @@ | |||
279 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | 283 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> |
280 | </div> | 284 | </div> |
281 | 285 | ||
286 | <div class="form-group" formGroupName="instance"> | ||
287 | <label i18n for="instanceDefaultTrendingRoute">Default trending page</label> | ||
288 | <div class="peertube-select-container"> | ||
289 | <select id="instanceDefaultTrendingRoute" formControlName="defaultTrendingRoute" class="form-control"> | ||
290 | <option i18n value="/videos/hot" *ngIf="isTrendingHotEnabled()">Hot videos</option> | ||
291 | <option i18n value="/videos/hot" *ngIf="!isTrendingHotEnabled()" disabled>Hot videos</option> | ||
292 | <option i18n value="/videos/trending">Most viewed videos</option> | ||
293 | <option i18n value="/videos/most-liked">Most liked videos</option> | ||
294 | </select> | ||
295 | </div> | ||
296 | <div *ngIf="formErrors.instance.defaultTrendingRoute" class="form-error">{{ formErrors.instance.defaultTrendingRoute }}</div> | ||
297 | </div> | ||
298 | |||
282 | </div> | 299 | </div> |
283 | </div> | 300 | </div> |
284 | 301 | ||
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 330ab075a..e6fc4582b 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,6 +186,12 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
186 | languages: null, | 186 | languages: null, |
187 | 187 | ||
188 | defaultClientRoute: null, | 188 | defaultClientRoute: null, |
189 | defaultTrendingRoute: null, | ||
190 | pages: { | ||
191 | hot: { | ||
192 | enabled: null | ||
193 | } | ||
194 | }, | ||
189 | 195 | ||
190 | customizations: { | 196 | customizations: { |
191 | javascript: null, | 197 | javascript: null, |
@@ -364,6 +370,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
364 | return this.form.value['followings']['instance']['autoFollowIndex']['enabled'] === true | 370 | return this.form.value['followings']['instance']['autoFollowIndex']['enabled'] === true |
365 | } | 371 | } |
366 | 372 | ||
373 | isTrendingHotEnabled () { | ||
374 | return this.form.value['instance']['pages']['hot']['enabled'] === true | ||
375 | } | ||
376 | |||
367 | async formValidated () { | 377 | async formValidated () { |
368 | const value: CustomConfig = this.form.getRawValue() | 378 | const value: CustomConfig = this.form.getRawValue() |
369 | 379 | ||
diff --git a/client/src/app/+videos/video-list/trending/index.ts b/client/src/app/+videos/video-list/trending/index.ts index 8bae205a5..93f4b1df6 100644 --- a/client/src/app/+videos/video-list/trending/index.ts +++ b/client/src/app/+videos/video-list/trending/index.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export * from './video-trending-header.component' | 1 | export * from './video-trending-header.component' |
2 | export * from './video-trending.component' | ||
3 | export * from './video-hot.component' | 2 | export * from './video-hot.component' |
3 | export * from './video-most-viewed.component' | ||
4 | export * from './video-most-liked.component' | 4 | export * from './video-most-liked.component' |
diff --git a/client/src/app/+videos/video-list/trending/video-trending.component.ts b/client/src/app/+videos/video-list/trending/video-most-viewed.component.ts index e77231586..98ced42d6 100644 --- a/client/src/app/+videos/video-list/trending/video-trending.component.ts +++ b/client/src/app/+videos/video-list/trending/video-most-viewed.component.ts | |||
@@ -9,11 +9,11 @@ import { VideoSortField } from '@shared/models' | |||
9 | import { VideoTrendingHeaderComponent } from './video-trending-header.component' | 9 | import { VideoTrendingHeaderComponent } from './video-trending-header.component' |
10 | 10 | ||
11 | @Component({ | 11 | @Component({ |
12 | selector: 'my-videos-trending', | 12 | selector: 'my-videos-most-viewed', |
13 | styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], | 13 | styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], |
14 | templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' | 14 | templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' |
15 | }) | 15 | }) |
16 | export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { | 16 | export class VideoMostViewedComponent extends AbstractVideoList implements OnInit, OnDestroy { |
17 | HeaderComponent = VideoTrendingHeaderComponent | 17 | HeaderComponent = VideoTrendingHeaderComponent |
18 | titlePage: string | 18 | titlePage: string |
19 | defaultSort: VideoSortField = '-trending' | 19 | defaultSort: VideoSortField = '-trending' |
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 6319ee6d3..a025bf1a2 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,5 +1,5 @@ | |||
1 | <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" [(ngModel)]="data.model" (ngModelChange)="setSort()"> | 1 | <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" [(ngModel)]="data.model" (ngModelChange)="setSort()"> |
2 | <label *ngFor="let button of buttons" ngbButtonLabel class="btn-light" placement="bottom" [ngbTooltip]="button.tooltip" container="body"> | 2 | <label *ngFor="let button of visibleButtons" ngbButtonLabel class="btn-light" placement="bottom" [ngbTooltip]="button.tooltip" container="body"> |
3 | <my-global-icon [iconName]="button.iconName"></my-global-icon> | 3 | <my-global-icon [iconName]="button.iconName"></my-global-icon> |
4 | <input ngbButton type="radio" [value]="button.value"> {{ button.label }} | 4 | <input ngbButton type="radio" [value]="button.value"> {{ button.label }} |
5 | </label> | 5 | </label> |
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 125f14e33..e49b61c68 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,8 +1,9 @@ | |||
1 | import { Component, Inject } from '@angular/core' | 1 | import { Component, Inject, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { VideoListHeaderComponent } from '@app/shared/shared-video-miniature' | 3 | import { VideoListHeaderComponent } from '@app/shared/shared-video-miniature' |
4 | import { GlobalIconName } from '@app/shared/shared-icons' | 4 | import { GlobalIconName } from '@app/shared/shared-icons' |
5 | import { VideoSortField } from '@shared/models' | 5 | import { VideoSortField } from '@shared/models' |
6 | import { ServerService } from '@app/core/server/server.service' | ||
6 | 7 | ||
7 | interface VideoTrendingHeaderItem { | 8 | interface VideoTrendingHeaderItem { |
8 | label: string | 9 | label: string |
@@ -10,6 +11,7 @@ interface VideoTrendingHeaderItem { | |||
10 | value: VideoSortField | 11 | value: VideoSortField |
11 | path: string | 12 | path: string |
12 | tooltip?: string | 13 | tooltip?: string |
14 | hidden?: boolean | ||
13 | } | 15 | } |
14 | 16 | ||
15 | @Component({ | 17 | @Component({ |
@@ -18,12 +20,13 @@ interface VideoTrendingHeaderItem { | |||
18 | styleUrls: [ './video-trending-header.component.scss' ], | 20 | styleUrls: [ './video-trending-header.component.scss' ], |
19 | templateUrl: './video-trending-header.component.html' | 21 | templateUrl: './video-trending-header.component.html' |
20 | }) | 22 | }) |
21 | export class VideoTrendingHeaderComponent extends VideoListHeaderComponent { | 23 | export class VideoTrendingHeaderComponent extends VideoListHeaderComponent implements OnInit { |
22 | buttons: VideoTrendingHeaderItem[] | 24 | buttons: VideoTrendingHeaderItem[] |
23 | 25 | ||
24 | constructor ( | 26 | constructor ( |
25 | @Inject('data') public data: any, | 27 | @Inject('data') public data: any, |
26 | private router: Router | 28 | private router: Router, |
29 | private serverService: ServerService | ||
27 | ) { | 30 | ) { |
28 | super(data) | 31 | super(data) |
29 | 32 | ||
@@ -34,16 +37,17 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent { | |||
34 | value: '-hot', | 37 | value: '-hot', |
35 | path: 'hot', | 38 | path: 'hot', |
36 | tooltip: $localize`Videos totalizing the most interactions for recent videos`, | 39 | tooltip: $localize`Videos totalizing the most interactions for recent videos`, |
40 | hidden: true | ||
37 | }, | 41 | }, |
38 | { | 42 | { |
39 | label: $localize`:Main variant of Trending videos based on number of recent views:Views`, | 43 | label: $localize`:Main variant of Trending videos based on number of recent views:Views`, |
40 | iconName: 'trending', | 44 | iconName: 'trending', |
41 | value: '-trending', | 45 | value: '-trending', |
42 | path: 'trending', | 46 | path: 'most-viewed', |
43 | tooltip: $localize`Videos totalizing the most views during the last 24 hours`, | 47 | tooltip: $localize`Videos totalizing the most views during the last 24 hours`, |
44 | }, | 48 | }, |
45 | { | 49 | { |
46 | label: $localize`:a variant of Trending videos based on the number of likes:Likes`, | 50 | label: $localize`:A variant of Trending videos based on the number of likes:Likes`, |
47 | iconName: 'like', | 51 | iconName: 'like', |
48 | value: '-likes', | 52 | value: '-likes', |
49 | path: 'most-liked', | 53 | path: 'most-liked', |
@@ -52,6 +56,21 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent { | |||
52 | ] | 56 | ] |
53 | } | 57 | } |
54 | 58 | ||
59 | ngOnInit () { | ||
60 | this.serverService.getConfig() | ||
61 | .subscribe(config => { | ||
62 | // don't filter if auto-blacklist is not enabled as this will be the only list | ||
63 | if (config.instance.pages.hot.enabled) { | ||
64 | const index = this.buttons.findIndex(b => b.path === 'hot') | ||
65 | this.buttons[index].hidden = false | ||
66 | } | ||
67 | }) | ||
68 | } | ||
69 | |||
70 | get visibleButtons () { | ||
71 | return this.buttons.filter(b => !b.hidden) | ||
72 | } | ||
73 | |||
55 | setSort () { | 74 | setSort () { |
56 | const path = this.buttons.find(b => b.value === this.data.model).path | 75 | const path = this.buttons.find(b => b.value === this.data.model).path |
57 | this.router.navigate([ `/videos/${path}` ]) | 76 | this.router.navigate([ `/videos/${path}` ]) |
diff --git a/client/src/app/+videos/videos-routing.module.ts b/client/src/app/+videos/videos-routing.module.ts index b6850b436..973935af8 100644 --- a/client/src/app/+videos/videos-routing.module.ts +++ b/client/src/app/+videos/videos-routing.module.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { RouterModule, Routes } from '@angular/router' | 2 | import { RouterModule, Routes } from '@angular/router' |
3 | import { LoginGuard } from '@app/core' | 3 | import { LoginGuard, TrendingGuard } from '@app/core' |
4 | import { MetaGuard } from '@ngx-meta/core' | 4 | import { MetaGuard } from '@ngx-meta/core' |
5 | import { VideoOverviewComponent } from './video-list/overview/video-overview.component' | 5 | import { VideoOverviewComponent } from './video-list/overview/video-overview.component' |
6 | import { VideoHotComponent } from './video-list/trending/video-hot.component' | 6 | import { VideoHotComponent } from './video-list/trending/video-hot.component' |
7 | import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' | 7 | import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' |
8 | import { VideoTrendingComponent } from './video-list/trending/video-trending.component' | 8 | import { VideoMostViewedComponent } from './video-list/trending/video-most-viewed.component' |
9 | import { VideoLocalComponent } from './video-list/video-local.component' | 9 | import { VideoLocalComponent } from './video-list/video-local.component' |
10 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | 10 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
11 | import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' | 11 | import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' |
@@ -28,27 +28,31 @@ const videosRoutes: Routes = [ | |||
28 | }, | 28 | }, |
29 | { | 29 | { |
30 | path: 'trending', | 30 | path: 'trending', |
31 | component: VideoTrendingComponent, | 31 | canActivate: [ TrendingGuard ] |
32 | }, | ||
33 | { | ||
34 | path: 'hot', | ||
35 | component: VideoHotComponent, | ||
32 | data: { | 36 | data: { |
33 | meta: { | 37 | meta: { |
34 | title: $localize`Trending videos` | 38 | title: $localize`Hot videos` |
35 | }, | 39 | }, |
36 | reuse: { | 40 | reuse: { |
37 | enabled: true, | 41 | enabled: true, |
38 | key: 'trending-videos-list' | 42 | key: 'hot-videos-list' |
39 | } | 43 | } |
40 | } | 44 | } |
41 | }, | 45 | }, |
42 | { | 46 | { |
43 | path: 'hot', | 47 | path: 'most-viewed', |
44 | component: VideoHotComponent, | 48 | component: VideoMostViewedComponent, |
45 | data: { | 49 | data: { |
46 | meta: { | 50 | meta: { |
47 | title: $localize`Hot videos` | 51 | title: $localize`Most viewed videos` |
48 | }, | 52 | }, |
49 | reuse: { | 53 | reuse: { |
50 | enabled: true, | 54 | enabled: true, |
51 | key: 'hot-videos-list' | 55 | key: 'most-viewed-videos-list' |
52 | } | 56 | } |
53 | } | 57 | } |
54 | }, | 58 | }, |
diff --git a/client/src/app/+videos/videos.module.ts b/client/src/app/+videos/videos.module.ts index 4c88a0397..ae9c680eb 100644 --- a/client/src/app/+videos/videos.module.ts +++ b/client/src/app/+videos/videos.module.ts | |||
@@ -9,7 +9,7 @@ import { OverviewService } from './video-list' | |||
9 | import { VideoOverviewComponent } from './video-list/overview/video-overview.component' | 9 | import { VideoOverviewComponent } from './video-list/overview/video-overview.component' |
10 | import { VideoTrendingHeaderComponent } from './video-list/trending/video-trending-header.component' | 10 | import { VideoTrendingHeaderComponent } from './video-list/trending/video-trending-header.component' |
11 | import { VideoHotComponent } from './video-list/trending/video-hot.component' | 11 | import { VideoHotComponent } from './video-list/trending/video-hot.component' |
12 | import { VideoTrendingComponent } from './video-list/trending/video-trending.component' | 12 | import { VideoMostViewedComponent } from './video-list/trending/video-most-viewed.component' |
13 | import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' | 13 | import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' |
14 | import { VideoLocalComponent } from './video-list/video-local.component' | 14 | import { VideoLocalComponent } from './video-list/video-local.component' |
15 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | 15 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
@@ -32,7 +32,7 @@ import { VideosComponent } from './videos.component' | |||
32 | VideosComponent, | 32 | VideosComponent, |
33 | 33 | ||
34 | VideoTrendingHeaderComponent, | 34 | VideoTrendingHeaderComponent, |
35 | VideoTrendingComponent, | 35 | VideoMostViewedComponent, |
36 | VideoHotComponent, | 36 | VideoHotComponent, |
37 | VideoMostLikedComponent, | 37 | VideoMostLikedComponent, |
38 | VideoRecentlyAddedComponent, | 38 | VideoRecentlyAddedComponent, |
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index c4fc9995e..32dfc8f36 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts | |||
@@ -7,7 +7,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations' | |||
7 | import { PeerTubeSocket } from '@app/core/notification/peertube-socket.service' | 7 | import { PeerTubeSocket } from '@app/core/notification/peertube-socket.service' |
8 | import { HooksService } from '@app/core/plugins/hooks.service' | 8 | import { HooksService } from '@app/core/plugins/hooks.service' |
9 | import { PluginService } from '@app/core/plugins/plugin.service' | 9 | import { PluginService } from '@app/core/plugins/plugin.service' |
10 | import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service' | ||
11 | import { AuthService } from './auth' | 10 | import { AuthService } from './auth' |
12 | import { ConfirmService } from './confirm' | 11 | import { ConfirmService } from './confirm' |
13 | import { CheatSheetComponent } from './hotkeys' | 12 | import { CheatSheetComponent } from './hotkeys' |
@@ -16,7 +15,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard' | |||
16 | import { Notifier } from './notification' | 15 | import { Notifier } from './notification' |
17 | import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer' | 16 | import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer' |
18 | import { RestExtractor, RestService } from './rest' | 17 | import { RestExtractor, RestService } from './rest' |
19 | import { LoginGuard, RedirectService, UserRightGuard } from './routing' | 18 | import { LoginGuard, RedirectService, UserRightGuard, UnloggedGuard, TrendingGuard } from './routing' |
20 | import { CanDeactivateGuard } from './routing/can-deactivate-guard.service' | 19 | import { CanDeactivateGuard } from './routing/can-deactivate-guard.service' |
21 | import { ServerConfigResolver } from './routing/server-config-resolver.service' | 20 | import { ServerConfigResolver } from './routing/server-config-resolver.service' |
22 | import { ScopedTokensService } from './scoped-tokens' | 21 | import { ScopedTokensService } from './scoped-tokens' |
@@ -57,6 +56,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra | |||
57 | LoginGuard, | 56 | LoginGuard, |
58 | UserRightGuard, | 57 | UserRightGuard, |
59 | UnloggedGuard, | 58 | UnloggedGuard, |
59 | TrendingGuard, | ||
60 | 60 | ||
61 | PluginService, | 61 | PluginService, |
62 | HooksService, | 62 | HooksService, |
diff --git a/client/src/app/core/routing/index.ts b/client/src/app/core/routing/index.ts index 239c27caf..b3985d870 100644 --- a/client/src/app/core/routing/index.ts +++ b/client/src/app/core/routing/index.ts | |||
@@ -8,3 +8,4 @@ export * from './redirect.service' | |||
8 | export * from './server-config-resolver.service' | 8 | export * from './server-config-resolver.service' |
9 | export * from './unlogged-guard.service' | 9 | export * from './unlogged-guard.service' |
10 | export * from './user-right-guard.service' | 10 | export * from './user-right-guard.service' |
11 | 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 3218040bf..76e28e461 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts | |||
@@ -7,11 +7,14 @@ export class RedirectService { | |||
7 | // Default route could change according to the instance configuration | 7 | // Default route could change according to the instance configuration |
8 | static INIT_DEFAULT_ROUTE = '/videos/trending' | 8 | static INIT_DEFAULT_ROUTE = '/videos/trending' |
9 | static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE | 9 | static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE |
10 | static INIT_DEFAULT_TRENDING_ROUTE = '/videos/most-viewed' | ||
11 | static DEFAULT_TRENDING_ROUTE = RedirectService.INIT_DEFAULT_TRENDING_ROUTE | ||
10 | 12 | ||
11 | private previousUrl: string | 13 | private previousUrl: string |
12 | private currentUrl: string | 14 | private currentUrl: string |
13 | 15 | ||
14 | private redirectingToHomepage = false | 16 | private redirectingToHomepage = false |
17 | private redirectingToTrending = false | ||
15 | 18 | ||
16 | constructor ( | 19 | constructor ( |
17 | private router: Router, | 20 | private router: Router, |
@@ -19,18 +22,28 @@ export class RedirectService { | |||
19 | ) { | 22 | ) { |
20 | // The config is first loaded from the cache so try to get the default route | 23 | // The config is first loaded from the cache so try to get the default route |
21 | const tmpConfig = this.serverService.getTmpConfig() | 24 | const tmpConfig = this.serverService.getTmpConfig() |
22 | if (tmpConfig && tmpConfig.instance && tmpConfig.instance.defaultClientRoute) { | 25 | if (tmpConfig && tmpConfig.instance) { |
23 | RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute | 26 | if (tmpConfig.instance.defaultClientRoute) { |
27 | RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute | ||
28 | } | ||
29 | if (tmpConfig.instance.defaultTrendingRoute) { | ||
30 | RedirectService.DEFAULT_TRENDING_ROUTE = tmpConfig.instance.defaultTrendingRoute | ||
31 | } | ||
24 | } | 32 | } |
25 | 33 | ||
26 | // Load default route | 34 | // Load default route |
27 | this.serverService.getConfig() | 35 | this.serverService.getConfig() |
28 | .subscribe(config => { | 36 | .subscribe(config => { |
29 | const defaultRouteConfig = config.instance.defaultClientRoute | 37 | const defaultRouteConfig = config.instance.defaultClientRoute |
38 | const defaultTrendingConfig = config.instance.defaultTrendingRoute | ||
30 | 39 | ||
31 | if (defaultRouteConfig) { | 40 | if (defaultRouteConfig) { |
32 | RedirectService.DEFAULT_ROUTE = defaultRouteConfig | 41 | RedirectService.DEFAULT_ROUTE = defaultRouteConfig |
33 | } | 42 | } |
43 | |||
44 | if (defaultTrendingConfig) { | ||
45 | RedirectService.DEFAULT_TRENDING_ROUTE = defaultTrendingConfig | ||
46 | } | ||
34 | }) | 47 | }) |
35 | 48 | ||
36 | // Track previous url | 49 | // Track previous url |
@@ -57,6 +70,15 @@ export class RedirectService { | |||
57 | return this.redirectToHomepage() | 70 | return this.redirectToHomepage() |
58 | } | 71 | } |
59 | 72 | ||
73 | redirectToTrending () { | ||
74 | if (this.redirectingToTrending) return | ||
75 | |||
76 | this.redirectingToTrending = true | ||
77 | |||
78 | this.router.navigate([ RedirectService.DEFAULT_TRENDING_ROUTE ]) | ||
79 | .then(() => this.redirectingToTrending = false) | ||
80 | } | ||
81 | |||
60 | redirectToHomepage (skipLocationChange = false) { | 82 | redirectToHomepage (skipLocationChange = false) { |
61 | if (this.redirectingToHomepage) return | 83 | if (this.redirectingToHomepage) return |
62 | 84 | ||
diff --git a/client/src/app/core/routing/trending-guard.service.ts b/client/src/app/core/routing/trending-guard.service.ts new file mode 100644 index 000000000..7db7fe994 --- /dev/null +++ b/client/src/app/core/routing/trending-guard.service.ts | |||
@@ -0,0 +1,14 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router' | ||
3 | import { RedirectService } from './redirect.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class TrendingGuard implements CanActivate { | ||
7 | |||
8 | constructor (private redirectService: RedirectService) {} | ||
9 | |||
10 | canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { | ||
11 | this.redirectService.redirectToTrending() | ||
12 | return false | ||
13 | } | ||
14 | } | ||
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index b1d8fcf83..5f13190b4 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -36,9 +36,15 @@ export class ServerService { | |||
36 | name: 'PeerTube', | 36 | name: 'PeerTube', |
37 | shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' + | 37 | shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' + |
38 | 'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.', | 38 | 'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.', |
39 | defaultClientRoute: '', | ||
40 | isNSFW: false, | 39 | isNSFW: false, |
41 | defaultNSFWPolicy: 'do_not_list' as 'do_not_list', | 40 | defaultNSFWPolicy: 'do_not_list' as 'do_not_list', |
41 | defaultClientRoute: '', | ||
42 | defaultTrendingRoute: '', | ||
43 | pages: { | ||
44 | hot: { | ||
45 | enabled: true | ||
46 | } | ||
47 | }, | ||
42 | customizations: { | 48 | customizations: { |
43 | javascript: '', | 49 | javascript: '', |
44 | css: '' | 50 | css: '' |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 9aa397edd..fc57b970b 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -127,10 +127,14 @@ | |||
127 | <ng-container i18n>Discover</ng-container> | 127 | <ng-container i18n>Discover</ng-container> |
128 | </a> | 128 | </a> |
129 | 129 | ||
130 | <a routerLink="/videos/trending" routerLinkActive="active"> | 130 | <a routerLink="/videos/trending" routerLinkActive="active" [ngClass]="{ 'active': hot.isActive || mostViewed.isActive || mostLiked.isActive }"> |
131 | <my-global-icon iconName="trending" aria-hidden="true"></my-global-icon> | 131 | <my-global-icon iconName="trending" aria-hidden="true"></my-global-icon> |
132 | <ng-container i18n>Trending</ng-container> | 132 | <ng-container i18n>Trending</ng-container> |
133 | </a> | 133 | </a> |
134 | <a routerLink="/videos/hot" routerLinkActive #hot="routerLinkActive" hidden></a> | ||
135 | <a routerLink="/videos/most-viewed" routerLinkActive #mostViewed="routerLinkActive" hidden></a> | ||
136 | <a routerLink="/videos/most-liked" routerLinkActive #mostLiked="routerLinkActive" hidden></a> | ||
137 | |||
134 | 138 | ||
135 | <a routerLink="/videos/recently-added" routerLinkActive="active"> | 139 | <a routerLink="/videos/recently-added" routerLinkActive="active"> |
136 | <my-global-icon iconName="recently-added" aria-hidden="true"></my-global-icon> | 140 | <my-global-icon iconName="recently-added" aria-hidden="true"></my-global-icon> |
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss index 2eaf0dc70..0a8aa8fa4 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss | |||
@@ -5,7 +5,7 @@ | |||
5 | 5 | ||
6 | $iconSize: 16px; | 6 | $iconSize: 16px; |
7 | 7 | ||
8 | ::ng-deep .title-page.title-page-single { | 8 | ::ng-deep my-video-list-header { |
9 | display: flex; | 9 | display: flex; |
10 | flex-grow: 1; | 10 | flex-grow: 1; |
11 | } | 11 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-list-header.component.html b/client/src/app/shared/shared-video-miniature/video-list-header.component.html new file mode 100644 index 000000000..58db437b8 --- /dev/null +++ b/client/src/app/shared/shared-video-miniature/video-list-header.component.html | |||
@@ -0,0 +1,5 @@ | |||
1 | <h1 class="title-page title-page-single"> | ||
2 | <div placement="bottom" [ngbTooltip]="data.titleTooltip" container="body"> | ||
3 | {{ data.titlePage }} | ||
4 | </div> | ||
5 | </h1> \ No newline at end of file | ||
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 a07248b96..67bbf7d7a 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,17 +1,13 @@ | |||
1 | import { Component, Inject } from '@angular/core' | 1 | import { Component, Inject, ViewEncapsulation } from '@angular/core' |
2 | 2 | ||
3 | export abstract class GenericHeaderComponent { | 3 | export abstract class GenericHeaderComponent { |
4 | constructor (@Inject('data') public data: any) {} | 4 | constructor (@Inject('data') public data: any) {} |
5 | } | 5 | } |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'h1', | 8 | selector: 'my-video-list-header', |
9 | host: { 'class': 'title-page title-page-single' }, | 9 | encapsulation: ViewEncapsulation.None, |
10 | template: ` | 10 | templateUrl: './video-list-header.component.html' |
11 | <div placement="bottom" [ngbTooltip]="data.titleTooltip" container="body"> | ||
12 | {{ data.titlePage }} | ||
13 | </div> | ||
14 | ` | ||
15 | }) | 11 | }) |
16 | export class VideoListHeaderComponent extends GenericHeaderComponent { | 12 | export class VideoListHeaderComponent extends GenericHeaderComponent { |
17 | constructor (@Inject('data') public data: any) { | 13 | constructor (@Inject('data') public data: any) { |
diff --git a/config/default.yaml b/config/default.yaml index e4a5ee727..43c7f4a53 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -366,6 +366,10 @@ instance: | |||
366 | # - 18 # Food | 366 | # - 18 # Food |
367 | 367 | ||
368 | default_client_route: '/videos/trending' | 368 | default_client_route: '/videos/trending' |
369 | default_trending_route: '/videos/most-viewed' | ||
370 | pages: | ||
371 | hot: | ||
372 | enabled: true | ||
369 | 373 | ||
370 | # Whether or not the instance is dedicated to NSFW content | 374 | # Whether or not the instance is dedicated to NSFW content |
371 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content | 375 | # 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 f7b56cc4a..f9f3abc18 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -380,6 +380,10 @@ instance: | |||
380 | # - 18 # Food | 380 | # - 18 # Food |
381 | 381 | ||
382 | default_client_route: '/videos/trending' | 382 | default_client_route: '/videos/trending' |
383 | default_trending_route: '/videos/most-viewed' | ||
384 | pages: | ||
385 | hot: | ||
386 | enabled: true | ||
383 | 387 | ||
384 | # Whether or not the instance is dedicated to NSFW content | 388 | # Whether or not the instance is dedicated to NSFW content |
385 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content | 389 | # 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 44f3d3ef7..24e7601ec 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -65,9 +65,15 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
65 | instance: { | 65 | instance: { |
66 | name: CONFIG.INSTANCE.NAME, | 66 | name: CONFIG.INSTANCE.NAME, |
67 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | 67 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, |
68 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | ||
69 | isNSFW: CONFIG.INSTANCE.IS_NSFW, | 68 | isNSFW: CONFIG.INSTANCE.IS_NSFW, |
70 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | 69 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, |
70 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | ||
71 | defaultTrendingRoute: CONFIG.INSTANCE.DEFAULT_TRENDING_ROUTE, | ||
72 | pages: { | ||
73 | hot: { | ||
74 | enabled: CONFIG.INSTANCE.PAGES.HOT.ENABLED | ||
75 | } | ||
76 | }, | ||
71 | customizations: { | 77 | customizations: { |
72 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, | 78 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, |
73 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS | 79 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS |
@@ -362,8 +368,16 @@ function customConfig (): CustomConfig { | |||
362 | categories: CONFIG.INSTANCE.CATEGORIES, | 368 | categories: CONFIG.INSTANCE.CATEGORIES, |
363 | 369 | ||
364 | isNSFW: CONFIG.INSTANCE.IS_NSFW, | 370 | isNSFW: CONFIG.INSTANCE.IS_NSFW, |
365 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | ||
366 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | 371 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, |
372 | |||
373 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | ||
374 | defaultTrendingRoute: CONFIG.INSTANCE.DEFAULT_TRENDING_ROUTE, | ||
375 | pages: { | ||
376 | hot: { | ||
377 | enabled: CONFIG.INSTANCE.PAGES.HOT.ENABLED | ||
378 | } | ||
379 | }, | ||
380 | |||
367 | customizations: { | 381 | customizations: { |
368 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS, | 382 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS, |
369 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT | 383 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 6aae5e821..e474959b2 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -230,6 +230,7 @@ const customConfigKeysToKeep = [ | |||
230 | 'instance-description', | 230 | 'instance-description', |
231 | 'instance-terms', | 231 | 'instance-terms', |
232 | 'instance-defaultClientRoute', | 232 | 'instance-defaultClientRoute', |
233 | 'instance-defaultTrendingRoute', | ||
233 | 'instance-defaultNSFWPolicy', | 234 | 'instance-defaultNSFWPolicy', |
234 | 'instance-customizations-javascript', | 235 | 'instance-customizations-javascript', |
235 | 'instance-customizations-css', | 236 | 'instance-customizations-css', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index c7ef9b497..e1f807752 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -278,8 +278,16 @@ const CONFIG = { | |||
278 | get CATEGORIES () { return config.get<number[]>('instance.categories') || [] }, | 278 | get CATEGORIES () { return config.get<number[]>('instance.categories') || [] }, |
279 | 279 | ||
280 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, | 280 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, |
281 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, | ||
282 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, | 281 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, |
282 | |||
283 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, | ||
284 | get DEFAULT_TRENDING_ROUTE () { return config.get<string>('instance.default_trending_route') }, | ||
285 | PAGES: { | ||
286 | HOT: { | ||
287 | get ENABLED () { return config.get<boolean>('instance.pages.hot.enabled') } | ||
288 | } | ||
289 | }, | ||
290 | |||
283 | CUSTOMIZATIONS: { | 291 | CUSTOMIZATIONS: { |
284 | get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') }, | 292 | get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') }, |
285 | get CSS () { return config.get<string>('instance.customizations.css') } | 293 | get CSS () { return config.get<string>('instance.customizations.css') } |
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index faabf17d7..0efe1157f 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -15,8 +15,9 @@ const customConfigUpdateValidator = [ | |||
15 | body('instance.shortDescription').exists().withMessage('Should have a valid instance short description'), | 15 | body('instance.shortDescription').exists().withMessage('Should have a valid instance short description'), |
16 | body('instance.description').exists().withMessage('Should have a valid instance description'), | 16 | body('instance.description').exists().withMessage('Should have a valid instance description'), |
17 | body('instance.terms').exists().withMessage('Should have a valid instance terms'), | 17 | body('instance.terms').exists().withMessage('Should have a valid instance terms'), |
18 | body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'), | ||
19 | body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid).withMessage('Should have a valid NSFW policy'), | 18 | body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid).withMessage('Should have a valid NSFW policy'), |
19 | body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'), | ||
20 | body('instance.defaultTrendingRoute').exists().withMessage('Should have a valid instance default trending route'), | ||
20 | body('instance.customizations.css').exists().withMessage('Should have a valid instance CSS customization'), | 21 | body('instance.customizations.css').exists().withMessage('Should have a valid instance CSS customization'), |
21 | body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'), | 22 | body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'), |
22 | 23 | ||
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts index 8e0965244..3f31ac862 100644 --- a/server/models/video/video-query-builder.ts +++ b/server/models/video/video-query-builder.ts | |||
@@ -242,64 +242,49 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
242 | } | 242 | } |
243 | 243 | ||
244 | // We don't exclude results in this so if we do a count we don't need to add this complex clause | 244 | // We don't exclude results in this so if we do a count we don't need to add this complex clause |
245 | if (options.trendingDays && options.isCount !== true) { | 245 | if (options.isCount !== true) { |
246 | const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) | 246 | if (options.trendingDays) { |
247 | 247 | const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) | |
248 | joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate') | 248 | |
249 | replacements.viewsGteDate = viewsGteDate | 249 | joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate') |
250 | 250 | replacements.viewsGteDate = viewsGteDate | |
251 | attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"') | 251 | |
252 | 252 | attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"') | |
253 | group = 'GROUP BY "video"."id"' | 253 | |
254 | } else if (options.hot && options.isCount !== true) { | 254 | group = 'GROUP BY "video"."id"' |
255 | /** | 255 | } else if (options.hot) { |
256 | * "Hotness" is a measure based on absolute view/comment/like/dislike numbers, | 256 | /** |
257 | * with fixed weights only applied to their log values. | 257 | * "Hotness" is a measure based on absolute view/comment/like/dislike numbers, |
258 | * | 258 | * with fixed weights only applied to their log values. |
259 | * This algorithm gives little chance for an old video to have a good score, | 259 | * |
260 | * for which recent spikes in interactions could be a sign of "hotness" and | 260 | * This algorithm gives little chance for an old video to have a good score, |
261 | * justify a better score. However there are multiple ways to achieve that | 261 | * for which recent spikes in interactions could be a sign of "hotness" and |
262 | * goal, which is left for later. Yes, this is a TODO :) | 262 | * justify a better score. However there are multiple ways to achieve that |
263 | * | 263 | * goal, which is left for later. Yes, this is a TODO :) |
264 | * note: weights and base score are in number of half-days. | 264 | * |
265 | * see https://github.com/reddit-archive/reddit/blob/master/r2/r2/lib/db/_sorts.pyx#L47-L58 | 265 | * note: weights and base score are in number of half-days. |
266 | */ | 266 | * see https://github.com/reddit-archive/reddit/blob/master/r2/r2/lib/db/_sorts.pyx#L47-L58 |
267 | const weights = { | 267 | */ |
268 | like: 3, | 268 | const weights = { |
269 | dislike: 3, | 269 | like: 3, |
270 | view: 1 / 12, | 270 | dislike: 3, |
271 | comment: 2 // a comment takes more time than a like to do, but can be done multiple times | 271 | view: 1 / 12, |
272 | } | 272 | comment: 2 // a comment takes more time than a like to do, but can be done multiple times |
273 | 273 | } | |
274 | cte.push( // TODO: exclude blocklisted comments | ||
275 | '"totalCommentsWithoutVideoAuthor" AS (' + | ||
276 | 'SELECT "video"."id", ' + | ||
277 | 'COUNT("replies"."id") - (' + | ||
278 | 'SELECT COUNT("authorReplies"."id") ' + | ||
279 | 'FROM "videoComment" AS "authorReplies" ' + | ||
280 | 'LEFT JOIN "account" ON "account"."id" = "authorReplies"."accountId" ' + | ||
281 | 'LEFT JOIN "videoChannel" ON "videoChannel"."accountId" = "account"."id" ' + | ||
282 | 'WHERE "video"."channelId" = "videoChannel"."id" ' + | ||
283 | ') as "value" ' + | ||
284 | 'FROM "videoComment" AS "replies" ' + | ||
285 | 'LEFT JOIN "video" ON "video"."id" = "replies"."videoId" ' + | ||
286 | 'WHERE "replies"."videoId" = "video"."id" ' + | ||
287 | 'GROUP BY "video"."id"' + | ||
288 | ')' | ||
289 | ) | ||
290 | 274 | ||
291 | joins.push('LEFT JOIN "totalCommentsWithoutVideoAuthor" ON "video"."id" = "totalCommentsWithoutVideoAuthor"."id"') | 275 | joins.push('LEFT JOIN "videoComment" ON "video"."id" = "videoComment"."videoId"') |
292 | 276 | ||
293 | attributes.push( | 277 | attributes.push( |
294 | `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+) | 278 | `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+) |
295 | `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-) | 279 | `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-) |
296 | `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+) | 280 | `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+) |
297 | `+ LOG(GREATEST(1, "totalCommentsWithoutVideoAuthor"."value")) * ${weights.comment} ` + // comments (+) | 281 | `+ LOG(GREATEST(1, COUNT(DISTINCT "videoComment"."id"))) * ${weights.comment} ` + // comments (+) |
298 | '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days) | 282 | '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days) |
299 | 'AS "score"' | 283 | 'AS "score"' |
300 | ) | 284 | ) |
301 | 285 | ||
302 | group = 'GROUP BY "video"."id", "totalCommentsWithoutVideoAuthor"."value"' | 286 | group = 'GROUP BY "video"."id"' |
287 | } | ||
303 | } | 288 | } |
304 | 289 | ||
305 | if (options.historyOfUser) { | 290 | if (options.historyOfUser) { |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index e36cdeab2..e58e0cd9f 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -41,8 +41,16 @@ describe('Test config API validators', function () { | |||
41 | categories: [ 1, 2 ], | 41 | categories: [ 1, 2 ], |
42 | 42 | ||
43 | isNSFW: true, | 43 | isNSFW: true, |
44 | defaultClientRoute: '/videos/recently-added', | ||
45 | defaultNSFWPolicy: 'blur', | 44 | defaultNSFWPolicy: 'blur', |
45 | |||
46 | defaultClientRoute: '/videos/recently-added', | ||
47 | defaultTrendingRoute: '/videos/trending', | ||
48 | pages: { | ||
49 | hot: { | ||
50 | enabled: true | ||
51 | } | ||
52 | }, | ||
53 | |||
46 | customizations: { | 54 | customizations: { |
47 | javascript: 'alert("coucou")', | 55 | javascript: 'alert("coucou")', |
48 | css: 'body { background-color: red; }' | 56 | css: 'body { background-color: red; }' |
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index af25f4800..328f4852a 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -272,9 +272,17 @@ describe('Test config', function () { | |||
272 | languages: [ 'en', 'es' ], | 272 | languages: [ 'en', 'es' ], |
273 | categories: [ 1, 2 ], | 273 | categories: [ 1, 2 ], |
274 | 274 | ||
275 | defaultClientRoute: '/videos/recently-added', | ||
276 | isNSFW: true, | 275 | isNSFW: true, |
277 | defaultNSFWPolicy: 'blur' as 'blur', | 276 | defaultNSFWPolicy: 'blur' as 'blur', |
277 | |||
278 | defaultClientRoute: '/videos/recently-added', | ||
279 | defaultTrendingRoute: '/videos/trending', | ||
280 | pages: { | ||
281 | hot: { | ||
282 | enabled: true | ||
283 | } | ||
284 | }, | ||
285 | |||
278 | customizations: { | 286 | customizations: { |
279 | javascript: 'alert("coucou")', | 287 | javascript: 'alert("coucou")', |
280 | css: 'body { background-color: red; }' | 288 | css: 'body { background-color: red; }' |
diff --git a/server/tests/client.ts b/server/tests/client.ts index 7572fd34a..d608764ee 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts | |||
@@ -308,8 +308,8 @@ describe('Test a client controllers', function () { | |||
308 | shortDescription: 'my short description', | 308 | shortDescription: 'my short description', |
309 | description: 'my super description', | 309 | description: 'my super description', |
310 | terms: 'my super terms', | 310 | terms: 'my super terms', |
311 | defaultClientRoute: '/videos/recently-added', | ||
312 | defaultNSFWPolicy: 'blur', | 311 | defaultNSFWPolicy: 'blur', |
312 | defaultClientRoute: '/videos/recently-added', | ||
313 | customizations: { | 313 | customizations: { |
314 | javascript: 'alert("coucou")', | 314 | javascript: 'alert("coucou")', |
315 | css: 'body { background-color: red; }' | 315 | css: 'body { background-color: red; }' |
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index f7c488c0b..5152ec693 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts | |||
@@ -65,9 +65,17 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti | |||
65 | languages: [ 'en', 'es' ], | 65 | languages: [ 'en', 'es' ], |
66 | categories: [ 1, 2 ], | 66 | categories: [ 1, 2 ], |
67 | 67 | ||
68 | defaultClientRoute: '/videos/recently-added', | ||
69 | isNSFW: true, | 68 | isNSFW: true, |
70 | defaultNSFWPolicy: 'blur', | 69 | defaultNSFWPolicy: 'blur', |
70 | |||
71 | defaultClientRoute: '/videos/recently-added', | ||
72 | defaultTrendingRoute: '/videos/trending', | ||
73 | pages: { | ||
74 | hot: { | ||
75 | enabled: true | ||
76 | } | ||
77 | }, | ||
78 | |||
71 | customizations: { | 79 | customizations: { |
72 | javascript: 'alert("coucou")', | 80 | javascript: 'alert("coucou")', |
73 | css: 'body { background-color: red; }' | 81 | css: 'body { background-color: red; }' |
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index 9a6a24923..fcc29e5d7 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -30,8 +30,16 @@ export interface CustomConfig { | |||
30 | categories: number[] | 30 | categories: number[] |
31 | 31 | ||
32 | isNSFW: boolean | 32 | isNSFW: boolean |
33 | defaultClientRoute: string | ||
34 | defaultNSFWPolicy: NSFWPolicyType | 33 | defaultNSFWPolicy: NSFWPolicyType |
34 | |||
35 | defaultClientRoute: string | ||
36 | defaultTrendingRoute: string | ||
37 | pages: { | ||
38 | hot: { | ||
39 | enabled: boolean | ||
40 | } | ||
41 | } | ||
42 | |||
35 | customizations: { | 43 | customizations: { |
36 | javascript?: string | 44 | javascript?: string |
37 | css?: string | 45 | css?: string |
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index 2dcf98f4f..a2d93ce73 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts | |||
@@ -36,9 +36,15 @@ export interface ServerConfig { | |||
36 | instance: { | 36 | instance: { |
37 | name: string | 37 | name: string |
38 | shortDescription: string | 38 | shortDescription: string |
39 | defaultClientRoute: string | ||
40 | isNSFW: boolean | 39 | isNSFW: boolean |
41 | defaultNSFWPolicy: NSFWPolicyType | 40 | defaultNSFWPolicy: NSFWPolicyType |
41 | defaultClientRoute: string | ||
42 | defaultTrendingRoute: string | ||
43 | pages: { | ||
44 | hot: { | ||
45 | enabled: boolean | ||
46 | } | ||
47 | } | ||
42 | customizations: { | 48 | customizations: { |
43 | javascript: string | 49 | javascript: string |
44 | css: string | 50 | css: string |