diff options
17 files changed, 161 insertions, 41 deletions
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts index 205245675..f2beb6689 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts | |||
@@ -3,7 +3,7 @@ import { concatMap, map, switchMap, tap } from 'rxjs/operators' | |||
3 | import { Component, OnDestroy, OnInit } from '@angular/core' | 3 | import { Component, OnDestroy, OnInit } from '@angular/core' |
4 | import { ComponentPagination, hasMoreItems, ScreenService, User, UserService } from '@app/core' | 4 | import { ComponentPagination, hasMoreItems, ScreenService, User, UserService } from '@app/core' |
5 | import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | 5 | import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' |
6 | import { VideoSortField } from '@shared/models' | 6 | import { NSFWPolicyType, VideoSortField } from '@shared/models' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-account-video-channels', | 9 | selector: 'my-account-video-channels', |
@@ -31,6 +31,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
31 | onChannelDataSubject = new Subject<any>() | 31 | onChannelDataSubject = new Subject<any>() |
32 | 32 | ||
33 | userMiniature: User | 33 | userMiniature: User |
34 | nsfwPolicy: NSFWPolicyType | ||
34 | 35 | ||
35 | private accountSub: Subscription | 36 | private accountSub: Subscription |
36 | 37 | ||
@@ -52,7 +53,11 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
52 | }) | 53 | }) |
53 | 54 | ||
54 | this.userService.getAnonymousOrLoggedUser() | 55 | this.userService.getAnonymousOrLoggedUser() |
55 | .subscribe(user => this.userMiniature = user) | 56 | .subscribe(user => { |
57 | this.userMiniature = user | ||
58 | |||
59 | this.nsfwPolicy = user.nsfwPolicy | ||
60 | }) | ||
56 | } | 61 | } |
57 | 62 | ||
58 | ngOnDestroy () { | 63 | ngOnDestroy () { |
@@ -65,7 +70,14 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
65 | tap(res => this.channelPagination.totalItems = res.total), | 70 | tap(res => this.channelPagination.totalItems = res.total), |
66 | switchMap(res => from(res.data)), | 71 | switchMap(res => from(res.data)), |
67 | concatMap(videoChannel => { | 72 | concatMap(videoChannel => { |
68 | return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort) | 73 | const options = { |
74 | videoChannel, | ||
75 | videoPagination: this.videosPagination, | ||
76 | sort: this.videosSort, | ||
77 | nsfwPolicy: this.nsfwPolicy | ||
78 | } | ||
79 | |||
80 | return this.videoService.getVideoChannelVideos(options) | ||
69 | .pipe(map(data => ({ videoChannel, videos: data.data }))) | 81 | .pipe(map(data => ({ videoChannel, videos: data.data }))) |
70 | }) | 82 | }) |
71 | ) | 83 | ) |
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index 3134a8ee2..58d0719fd 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts | |||
@@ -6,6 +6,7 @@ import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenServi | |||
6 | import { immutableAssign } from '@app/helpers' | 6 | import { immutableAssign } from '@app/helpers' |
7 | import { Account, AccountService, VideoService } from '@app/shared/shared-main' | 7 | import { Account, AccountService, VideoService } from '@app/shared/shared-main' |
8 | import { AbstractVideoList } from '@app/shared/shared-video-miniature' | 8 | import { AbstractVideoList } from '@app/shared/shared-video-miniature' |
9 | import { VideoFilter } from '@shared/models' | ||
9 | 10 | ||
10 | @Component({ | 11 | @Component({ |
11 | selector: 'my-account-videos', | 12 | selector: 'my-account-videos', |
@@ -18,6 +19,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
18 | titlePage: string | 19 | titlePage: string |
19 | loadOnInit = false | 20 | loadOnInit = false |
20 | 21 | ||
22 | filter: VideoFilter = null | ||
23 | |||
21 | private account: Account | 24 | private account: Account |
22 | private accountSub: Subscription | 25 | private accountSub: Subscription |
23 | 26 | ||
@@ -40,6 +43,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
40 | ngOnInit () { | 43 | ngOnInit () { |
41 | super.ngOnInit() | 44 | super.ngOnInit() |
42 | 45 | ||
46 | this.enableAllFilterIfPossible() | ||
47 | |||
43 | // Parent get the account for us | 48 | // Parent get the account for us |
44 | this.accountSub = this.accountService.accountLoaded | 49 | this.accountSub = this.accountService.accountLoaded |
45 | .pipe(first()) | 50 | .pipe(first()) |
@@ -59,9 +64,16 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
59 | 64 | ||
60 | getVideosObservable (page: number) { | 65 | getVideosObservable (page: number) { |
61 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 66 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
67 | const options = { | ||
68 | account: this.account, | ||
69 | videoPagination: newPagination, | ||
70 | sort: this.sort, | ||
71 | nsfwPolicy: this.nsfwPolicy, | ||
72 | videoFilter: this.filter | ||
73 | } | ||
62 | 74 | ||
63 | return this.videoService | 75 | return this.videoService |
64 | .getAccountVideos(this.account, newPagination, this.sort) | 76 | .getAccountVideos(options) |
65 | .pipe( | 77 | .pipe( |
66 | tap(({ total }) => { | 78 | tap(({ total }) => { |
67 | this.titlePage = $localize`Published ${total} videos` | 79 | this.titlePage = $localize`Published ${total} videos` |
@@ -69,6 +81,12 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
69 | ) | 81 | ) |
70 | } | 82 | } |
71 | 83 | ||
84 | toggleModerationDisplay () { | ||
85 | this.filter = this.buildLocalFilter(this.filter, null) | ||
86 | |||
87 | this.reloadVideos() | ||
88 | } | ||
89 | |||
72 | generateSyndicationList () { | 90 | generateSyndicationList () { |
73 | this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) | 91 | this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) |
74 | } | 92 | } |
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts index e1ec6bbcb..645696f48 100644 --- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts +++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts | |||
@@ -6,6 +6,7 @@ import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenServi | |||
6 | import { immutableAssign } from '@app/helpers' | 6 | import { immutableAssign } from '@app/helpers' |
7 | import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | 7 | import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' |
8 | import { AbstractVideoList } from '@app/shared/shared-video-miniature' | 8 | import { AbstractVideoList } from '@app/shared/shared-video-miniature' |
9 | import { VideoFilter } from '@shared/models' | ||
9 | 10 | ||
10 | @Component({ | 11 | @Component({ |
11 | selector: 'my-video-channel-videos', | 12 | selector: 'my-video-channel-videos', |
@@ -18,6 +19,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
18 | titlePage: string | 19 | titlePage: string |
19 | loadOnInit = false | 20 | loadOnInit = false |
20 | 21 | ||
22 | filter: VideoFilter = null | ||
23 | |||
21 | private videoChannel: VideoChannel | 24 | private videoChannel: VideoChannel |
22 | private videoChannelSub: Subscription | 25 | private videoChannelSub: Subscription |
23 | 26 | ||
@@ -46,6 +49,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
46 | ngOnInit () { | 49 | ngOnInit () { |
47 | super.ngOnInit() | 50 | super.ngOnInit() |
48 | 51 | ||
52 | this.enableAllFilterIfPossible() | ||
53 | |||
49 | // Parent get the video channel for us | 54 | // Parent get the video channel for us |
50 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded | 55 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded |
51 | .pipe(first()) | 56 | .pipe(first()) |
@@ -65,9 +70,16 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
65 | 70 | ||
66 | getVideosObservable (page: number) { | 71 | getVideosObservable (page: number) { |
67 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 72 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
73 | const options = { | ||
74 | videoChannel: this.videoChannel, | ||
75 | videoPagination: newPagination, | ||
76 | sort: this.sort, | ||
77 | nsfwPolicy: this.nsfwPolicy, | ||
78 | videoFilter: this.filter | ||
79 | } | ||
68 | 80 | ||
69 | return this.videoService | 81 | return this.videoService |
70 | .getVideoChannelVideos(this.videoChannel, newPagination, this.sort, this.nsfwPolicy) | 82 | .getVideoChannelVideos(options) |
71 | .pipe( | 83 | .pipe( |
72 | tap(({ total }) => { | 84 | tap(({ total }) => { |
73 | this.titlePage = total === 1 | 85 | this.titlePage = total === 1 |
@@ -80,4 +92,10 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
80 | generateSyndicationList () { | 92 | generateSyndicationList () { |
81 | this.syndicationItems = this.videoService.getVideoChannelFeedUrls(this.videoChannel.id) | 93 | this.syndicationItems = this.videoService.getVideoChannelFeedUrls(this.videoChannel.id) |
82 | } | 94 | } |
95 | |||
96 | toggleModerationDisplay () { | ||
97 | this.filter = this.buildLocalFilter(this.filter, null) | ||
98 | |||
99 | this.reloadVideos() | ||
100 | } | ||
83 | } | 101 | } |
diff --git a/client/src/app/+videos/video-list/video-local.component.ts b/client/src/app/+videos/video-list/video-local.component.ts index 07063d4d4..20dd61db9 100644 --- a/client/src/app/+videos/video-list/video-local.component.ts +++ b/client/src/app/+videos/video-list/video-local.component.ts | |||
@@ -39,11 +39,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
39 | ngOnInit () { | 39 | ngOnInit () { |
40 | super.ngOnInit() | 40 | super.ngOnInit() |
41 | 41 | ||
42 | if (this.authService.isLoggedIn()) { | 42 | this.enableAllFilterIfPossible() |
43 | const user = this.authService.getUser() | ||
44 | this.displayModerationBlock = user.hasRight(UserRight.SEE_ALL_VIDEOS) | ||
45 | } | ||
46 | |||
47 | this.generateSyndicationList() | 43 | this.generateSyndicationList() |
48 | } | 44 | } |
49 | 45 | ||
@@ -77,7 +73,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
77 | } | 73 | } |
78 | 74 | ||
79 | toggleModerationDisplay () { | 75 | toggleModerationDisplay () { |
80 | this.filter = this.filter === 'local' ? 'all-local' as 'all-local' : 'local' as 'local' | 76 | this.filter = this.buildLocalFilter(this.filter, 'local') |
81 | 77 | ||
82 | this.reloadVideos() | 78 | this.reloadVideos() |
83 | } | 79 | } |
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 0e2d36081..c8a3ec043 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -134,16 +134,28 @@ export class VideoService implements VideosProvider { | |||
134 | ) | 134 | ) |
135 | } | 135 | } |
136 | 136 | ||
137 | getAccountVideos ( | 137 | getAccountVideos (parameters: { |
138 | account: Account, | 138 | account: Account, |
139 | videoPagination: ComponentPaginationLight, | 139 | videoPagination: ComponentPaginationLight, |
140 | sort: VideoSortField | 140 | sort: VideoSortField |
141 | ): Observable<ResultList<Video>> { | 141 | nsfwPolicy?: NSFWPolicyType |
142 | videoFilter?: VideoFilter | ||
143 | }): Observable<ResultList<Video>> { | ||
144 | const { account, videoPagination, sort, videoFilter, nsfwPolicy } = parameters | ||
145 | |||
142 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 146 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
143 | 147 | ||
144 | let params = new HttpParams() | 148 | let params = new HttpParams() |
145 | params = this.restService.addRestGetParams(params, pagination, sort) | 149 | params = this.restService.addRestGetParams(params, pagination, sort) |
146 | 150 | ||
151 | if (nsfwPolicy) { | ||
152 | params = params.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) | ||
153 | } | ||
154 | |||
155 | if (videoFilter) { | ||
156 | params = params.set('filter', videoFilter) | ||
157 | } | ||
158 | |||
147 | return this.authHttp | 159 | return this.authHttp |
148 | .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params }) | 160 | .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params }) |
149 | .pipe( | 161 | .pipe( |
@@ -152,12 +164,15 @@ export class VideoService implements VideosProvider { | |||
152 | ) | 164 | ) |
153 | } | 165 | } |
154 | 166 | ||
155 | getVideoChannelVideos ( | 167 | getVideoChannelVideos (parameters: { |
156 | videoChannel: VideoChannel, | 168 | videoChannel: VideoChannel, |
157 | videoPagination: ComponentPaginationLight, | 169 | videoPagination: ComponentPaginationLight, |
158 | sort: VideoSortField, | 170 | sort: VideoSortField, |
159 | nsfwPolicy?: NSFWPolicyType | 171 | nsfwPolicy?: NSFWPolicyType |
160 | ): Observable<ResultList<Video>> { | 172 | videoFilter?: VideoFilter |
173 | }): Observable<ResultList<Video>> { | ||
174 | const { videoChannel, videoPagination, sort, nsfwPolicy, videoFilter } = parameters | ||
175 | |||
161 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 176 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
162 | 177 | ||
163 | let params = new HttpParams() | 178 | let params = new HttpParams() |
@@ -167,6 +182,10 @@ export class VideoService implements VideosProvider { | |||
167 | params = params.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) | 182 | params = params.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) |
168 | } | 183 | } |
169 | 184 | ||
185 | if (videoFilter) { | ||
186 | params = params.set('filter', videoFilter) | ||
187 | } | ||
188 | |||
170 | return this.authHttp | 189 | return this.authHttp |
171 | .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params }) | 190 | .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params }) |
172 | .pipe( | 191 | .pipe( |
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.html b/client/src/app/shared/shared-video-miniature/abstract-video-list.html index 08962dff8..b1ac757db 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.html +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.html | |||
@@ -21,7 +21,7 @@ | |||
21 | <div class="dropdown-item"> | 21 | <div class="dropdown-item"> |
22 | <my-peertube-checkbox | 22 | <my-peertube-checkbox |
23 | (change)="toggleModerationDisplay()" | 23 | (change)="toggleModerationDisplay()" |
24 | inputName="display-unlisted-private" i18n-labelText labelText="Display unlisted and private videos" | 24 | inputName="display-unlisted-private" i18n-labelText labelText="Display all videos (private, unlisted or not yet published)" |
25 | ></my-peertube-checkbox> | 25 | ></my-peertube-checkbox> |
26 | </div> | 26 | </div> |
27 | </div> | 27 | </div> |
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 7841b60f7..9077e2f75 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 | |||
@@ -31,13 +31,21 @@ $iconSize: 16px; | |||
31 | 31 | ||
32 | .moderation-block { | 32 | .moderation-block { |
33 | div { | 33 | div { |
34 | @include button-with-icon($iconSize, 3px, -1px); | 34 | @include button-with-icon($iconSize, 3px, -2px); |
35 | } | 35 | } |
36 | 36 | ||
37 | margin-left: .2rem; | 37 | margin-left: .4rem; |
38 | display: flex; | 38 | display: flex; |
39 | justify-content: flex-end; | 39 | justify-content: flex-end; |
40 | align-items: center; | 40 | align-items: center; |
41 | |||
42 | .dropdown-item { | ||
43 | padding: 0; | ||
44 | |||
45 | ::ng-deep my-peertube-checkbox label { | ||
46 | padding: 3px 15px; | ||
47 | } | ||
48 | } | ||
41 | } | 49 | } |
42 | } | 50 | } |
43 | 51 | ||
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts index da05e15fb..2219ced30 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts | |||
@@ -15,7 +15,7 @@ import { | |||
15 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 15 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
16 | import { GlobalIconName } from '@app/shared/shared-icons' | 16 | import { GlobalIconName } from '@app/shared/shared-icons' |
17 | import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils/miscs/date' | 17 | import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils/miscs/date' |
18 | import { ServerConfig, VideoSortField } from '@shared/models' | 18 | import { ServerConfig, UserRight, VideoFilter, VideoSortField } from '@shared/models' |
19 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | 19 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' |
20 | import { Syndication, Video } from '../shared-main' | 20 | import { Syndication, Video } from '../shared-main' |
21 | import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' | 21 | import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' |
@@ -205,10 +205,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor | |||
205 | this.loadMoreVideos(true) | 205 | this.loadMoreVideos(true) |
206 | } | 206 | } |
207 | 207 | ||
208 | toggleModerationDisplay () { | ||
209 | throw new Error('toggleModerationDisplay is not implemented') | ||
210 | } | ||
211 | |||
212 | removeVideoFromArray (video: Video) { | 208 | removeVideoFromArray (video: Video) { |
213 | this.videos = this.videos.filter(v => v.id !== video.id) | 209 | this.videos = this.videos.filter(v => v.id !== video.id) |
214 | } | 210 | } |
@@ -268,6 +264,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor | |||
268 | return this.groupedDateLabels[this.groupedDates[video.id]] | 264 | return this.groupedDateLabels[this.groupedDates[video.id]] |
269 | } | 265 | } |
270 | 266 | ||
267 | toggleModerationDisplay () { | ||
268 | throw new Error('toggleModerationDisplay is not implemented') | ||
269 | } | ||
270 | |||
271 | // On videos hook for children that want to do something | 271 | // On videos hook for children that want to do something |
272 | protected onMoreVideos () { /* empty */ } | 272 | protected onMoreVideos () { /* empty */ } |
273 | 273 | ||
@@ -277,6 +277,28 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor | |||
277 | this.angularState = routeParams[ 'a-state' ] | 277 | this.angularState = routeParams[ 'a-state' ] |
278 | } | 278 | } |
279 | 279 | ||
280 | protected buildLocalFilter (existing: VideoFilter, base: VideoFilter) { | ||
281 | if (base === 'local') { | ||
282 | return existing === 'local' | ||
283 | ? 'all-local' as 'all-local' | ||
284 | : 'local' as 'local' | ||
285 | } | ||
286 | |||
287 | return existing === 'all' | ||
288 | ? null | ||
289 | : 'all' | ||
290 | } | ||
291 | |||
292 | protected enableAllFilterIfPossible () { | ||
293 | if (!this.authService.isLoggedIn()) return | ||
294 | |||
295 | this.authService.userInformationLoaded | ||
296 | .subscribe(() => { | ||
297 | const user = this.authService.getUser() | ||
298 | this.displayModerationBlock = user.hasRight(UserRight.SEE_ALL_VIDEOS) | ||
299 | }) | ||
300 | } | ||
301 | |||
280 | private calcPageSizes () { | 302 | private calcPageSizes () { |
281 | if (this.screenService.isInMobileView()) { | 303 | if (this.screenService.isInMobileView()) { |
282 | this.pagination.itemsPerPage = 5 | 304 | this.pagination.itemsPerPage = 5 |
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 259af7a77..7cb149f5f 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss | |||
@@ -65,6 +65,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; | |||
65 | opacity: .9; | 65 | opacity: .9; |
66 | } | 66 | } |
67 | 67 | ||
68 | &:active { | ||
69 | color: pvar(--mainForegroundColor) !important; | ||
70 | } | ||
71 | |||
68 | &::after { | 72 | &::after { |
69 | display: none; | 73 | display: none; |
70 | } | 74 | } |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index e99992236..8b309ae42 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -17,7 +17,7 @@ import * as magnetUtil from 'magnet-uri' | |||
17 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | 17 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS |
18 | 18 | ||
19 | function isVideoFilterValid (filter: VideoFilter) { | 19 | function isVideoFilterValid (filter: VideoFilter) { |
20 | return filter === 'local' || filter === 'all-local' | 20 | return filter === 'local' || filter === 'all-local' || filter === 'all' |
21 | } | 21 | } |
22 | 22 | ||
23 | function isVideoCategoryValid (value: any) { | 23 | function isVideoCategoryValid (value: any) { |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index ff90e347a..efab67a01 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -429,7 +429,10 @@ const commonVideosFiltersValidator = [ | |||
429 | if (areValidationErrors(req, res)) return | 429 | if (areValidationErrors(req, res)) return |
430 | 430 | ||
431 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 431 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
432 | if (req.query.filter === 'all-local' && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)) { | 432 | if ( |
433 | (req.query.filter === 'all-local' || req.query.filter === 'all') && | ||
434 | (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false) | ||
435 | ) { | ||
433 | res.status(401) | 436 | res.status(401) |
434 | .json({ error: 'You are not allowed to see all local videos.' }) | 437 | .json({ error: 'You are not allowed to see all local videos.' }) |
435 | 438 | ||
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts index b14bb16d6..25d5042b7 100644 --- a/server/models/video/video-query-builder.ts +++ b/server/models/video/video-query-builder.ts | |||
@@ -89,7 +89,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
89 | } | 89 | } |
90 | 90 | ||
91 | // Only list public/published videos | 91 | // Only list public/published videos |
92 | if (!options.filter || options.filter !== 'all-local') { | 92 | if (!options.filter || (options.filter !== 'all-local' && options.filter !== 'all')) { |
93 | and.push( | 93 | and.push( |
94 | `("video"."state" = ${VideoState.PUBLISHED} OR ` + | 94 | `("video"."state" = ${VideoState.PUBLISHED} OR ` + |
95 | `("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))` | 95 | `("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))` |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index edf757697..f365d3d51 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1085,7 +1085,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1085 | historyOfUser?: MUserId | 1085 | historyOfUser?: MUserId |
1086 | countVideos?: boolean | 1086 | countVideos?: boolean |
1087 | }) { | 1087 | }) { |
1088 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1088 | if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
1089 | throw new Error('Try to filter all-local but no user has not the see all videos right') | 1089 | throw new Error('Try to filter all-local but no user has not the see all videos right') |
1090 | } | 1090 | } |
1091 | 1091 | ||
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts index ec8654db2..bf8248b0e 100644 --- a/server/tests/api/check-params/videos-filter.ts +++ b/server/tests/api/check-params/videos-filter.ts | |||
@@ -78,28 +78,33 @@ describe('Test videos filters', function () { | |||
78 | await testEndpoints(server, server.accessToken, 'local', 200) | 78 | await testEndpoints(server, server.accessToken, 'local', 200) |
79 | }) | 79 | }) |
80 | 80 | ||
81 | it('Should fail to list all-local with a simple user', async function () { | 81 | it('Should fail to list all-local/all with a simple user', async function () { |
82 | await testEndpoints(server, userAccessToken, 'all-local', 401) | 82 | await testEndpoints(server, userAccessToken, 'all-local', 401) |
83 | await testEndpoints(server, userAccessToken, 'all', 401) | ||
83 | }) | 84 | }) |
84 | 85 | ||
85 | it('Should succeed to list all-local with a moderator', async function () { | 86 | it('Should succeed to list all-local/all with a moderator', async function () { |
86 | await testEndpoints(server, moderatorAccessToken, 'all-local', 200) | 87 | await testEndpoints(server, moderatorAccessToken, 'all-local', 200) |
88 | await testEndpoints(server, moderatorAccessToken, 'all', 200) | ||
87 | }) | 89 | }) |
88 | 90 | ||
89 | it('Should succeed to list all-local with an admin', async function () { | 91 | it('Should succeed to list all-local/all with an admin', async function () { |
90 | await testEndpoints(server, server.accessToken, 'all-local', 200) | 92 | await testEndpoints(server, server.accessToken, 'all-local', 200) |
93 | await testEndpoints(server, server.accessToken, 'all', 200) | ||
91 | }) | 94 | }) |
92 | 95 | ||
93 | // Because we cannot authenticate the user on the RSS endpoint | 96 | // Because we cannot authenticate the user on the RSS endpoint |
94 | it('Should fail on the feeds endpoint with the all-local filter', async function () { | 97 | it('Should fail on the feeds endpoint with the all-local/all filter', async function () { |
95 | await makeGetRequest({ | 98 | for (const filter of [ 'all', 'all-local' ]) { |
96 | url: server.url, | 99 | await makeGetRequest({ |
97 | path: '/feeds/videos.json', | 100 | url: server.url, |
98 | statusCodeExpected: 401, | 101 | path: '/feeds/videos.json', |
99 | query: { | 102 | statusCodeExpected: 401, |
100 | filter: 'all-local' | 103 | query: { |
101 | } | 104 | filter |
102 | }) | 105 | } |
106 | }) | ||
107 | } | ||
103 | }) | 108 | }) |
104 | 109 | ||
105 | it('Should succeed on the feeds endpoint with the local filter', async function () { | 110 | it('Should succeed on the feeds endpoint with the local filter', async function () { |
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts index 95e12e43c..6b9a4b6d4 100644 --- a/server/tests/api/videos/videos-filter.ts +++ b/server/tests/api/videos/videos-filter.ts | |||
@@ -116,6 +116,20 @@ describe('Test videos filter validator', function () { | |||
116 | } | 116 | } |
117 | } | 117 | } |
118 | }) | 118 | }) |
119 | |||
120 | it('Should display all videos by the admin or the moderator', async function () { | ||
121 | for (const server of servers) { | ||
122 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { | ||
123 | |||
124 | const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all') | ||
125 | expect(channelVideos).to.have.lengthOf(3) | ||
126 | expect(accountVideos).to.have.lengthOf(3) | ||
127 | |||
128 | expect(videos).to.have.lengthOf(5) | ||
129 | expect(searchVideos).to.have.lengthOf(5) | ||
130 | } | ||
131 | } | ||
132 | }) | ||
119 | }) | 133 | }) |
120 | 134 | ||
121 | after(async function () { | 135 | after(async function () { |
diff --git a/shared/models/videos/video-query.type.ts b/shared/models/videos/video-query.type.ts index f76a91aad..e641a401c 100644 --- a/shared/models/videos/video-query.type.ts +++ b/shared/models/videos/video-query.type.ts | |||
@@ -1 +1 @@ | |||
export type VideoFilter = 'local' | 'all-local' | export type VideoFilter = 'local' | 'all-local' | 'all' | ||
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 4a178e4d7..c16914eb2 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -3681,9 +3681,10 @@ components: | |||
3681 | in: query | 3681 | in: query |
3682 | required: false | 3682 | required: false |
3683 | description: > | 3683 | description: > |
3684 | Special filters (local for instance) which might require special rights: | 3684 | Special filters which might require special rights: |
3685 | * `local` - only videos local to the instance | 3685 | * `local` - only videos local to the instance |
3686 | * `all-local` - only videos local to the instance, but showing private and unlisted videos (requires Admin privileges) | 3686 | * `all-local` - only videos local to the instance, but showing private and unlisted videos (requires Admin privileges) |
3687 | * `all` - all videos, showing private and unlisted videos (requires Admin privileges) | ||
3687 | schema: | 3688 | schema: |
3688 | type: string | 3689 | type: string |
3689 | enum: | 3690 | enum: |