aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/forms/form-validators/index.ts2
-rw-r--r--client/src/app/shared/forms/form-validators/video-block-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-blacklist-validators.service.ts)10
-rw-r--r--client/src/app/shared/images/global-icon.component.ts3
-rw-r--r--client/src/app/shared/index.ts2
-rw-r--r--client/src/app/shared/shared.module.ts14
-rw-r--r--client/src/app/shared/users/user-notification.model.ts6
-rw-r--r--client/src/app/shared/users/user-notifications.component.html12
-rw-r--r--client/src/app/shared/video-blacklist/index.ts1
-rw-r--r--client/src/app/shared/video-blacklist/video-blacklist.service.ts89
-rw-r--r--client/src/app/shared/video-block/index.ts1
-rw-r--r--client/src/app/shared/video-block/video-block.service.ts77
-rw-r--r--client/src/app/shared/video/abstract-video-list.html2
-rw-r--r--client/src/app/shared/video/modals/video-block.component.html (renamed from client/src/app/shared/video/modals/video-blacklist.component.html)4
-rw-r--r--client/src/app/shared/video/modals/video-block.component.scss (renamed from client/src/app/shared/video/modals/video-blacklist.component.scss)0
-rw-r--r--client/src/app/shared/video/modals/video-block.component.ts (renamed from client/src/app/shared/video/modals/video-blacklist.component.ts)31
-rw-r--r--client/src/app/shared/video/video-actions-dropdown.component.html2
-rw-r--r--client/src/app/shared/video/video-actions-dropdown.component.ts54
-rw-r--r--client/src/app/shared/video/video-miniature.component.html8
-rw-r--r--client/src/app/shared/video/video-miniature.component.scss8
-rw-r--r--client/src/app/shared/video/video-miniature.component.ts12
-rw-r--r--client/src/app/shared/video/video.model.ts12
21 files changed, 169 insertions, 181 deletions
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts
index e3de3ae13..4a01b1622 100644
--- a/client/src/app/shared/forms/form-validators/index.ts
+++ b/client/src/app/shared/forms/form-validators/index.ts
@@ -6,7 +6,7 @@ export * from './login-validators.service'
6export * from './reset-password-validators.service' 6export * from './reset-password-validators.service'
7export * from './user-validators.service' 7export * from './user-validators.service'
8export * from './video-abuse-validators.service' 8export * from './video-abuse-validators.service'
9export * from './video-blacklist-validators.service' 9export * from './video-block-validators.service'
10export * from './video-channel-validators.service' 10export * from './video-channel-validators.service'
11export * from './video-comment-validators.service' 11export * from './video-comment-validators.service'
12export * from './video-validators.service' 12export * from './video-validators.service'
diff --git a/client/src/app/shared/forms/form-validators/video-blacklist-validators.service.ts b/client/src/app/shared/forms/form-validators/video-block-validators.service.ts
index 07d1f264a..dc8257761 100644
--- a/client/src/app/shared/forms/form-validators/video-blacklist-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/video-block-validators.service.ts
@@ -4,15 +4,15 @@ import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from '@app/shared'
5 5
6@Injectable() 6@Injectable()
7export class VideoBlacklistValidatorsService { 7export class VideoBlockValidatorsService {
8 readonly VIDEO_BLACKLIST_REASON: BuildFormValidator 8 readonly VIDEO_BLOCK_REASON: BuildFormValidator
9 9
10 constructor (private i18n: I18n) { 10 constructor (private i18n: I18n) {
11 this.VIDEO_BLACKLIST_REASON = { 11 this.VIDEO_BLOCK_REASON = {
12 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ], 12 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ],
13 MESSAGES: { 13 MESSAGES: {
14 'minlength': this.i18n('Blacklist reason must be at least 2 characters long.'), 14 'minlength': this.i18n('Block reason must be at least 2 characters long.'),
15 'maxlength': this.i18n('Blacklist reason cannot be more than 300 characters long.') 15 'maxlength': this.i18n('Block reason cannot be more than 300 characters long.')
16 } 16 }
17 } 17 }
18 } 18 }
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts
index d2700f6c3..169882685 100644
--- a/client/src/app/shared/images/global-icon.component.ts
+++ b/client/src/app/shared/images/global-icon.component.ts
@@ -56,7 +56,8 @@ const icons = {
56 'refresh': require('!!raw-loader?!../../../assets/images/global/refresh.svg').default, 56 'refresh': require('!!raw-loader?!../../../assets/images/global/refresh.svg').default,
57 'npm': require('!!raw-loader?!../../../assets/images/global/npm.svg').default, 57 'npm': require('!!raw-loader?!../../../assets/images/global/npm.svg').default,
58 'fullscreen': require('!!raw-loader?!../../../assets/images/global/fullscreen.svg').default, 58 'fullscreen': require('!!raw-loader?!../../../assets/images/global/fullscreen.svg').default,
59 'exit-fullscreen': require('!!raw-loader?!../../../assets/images/global/exit-fullscreen.svg').default 59 'exit-fullscreen': require('!!raw-loader?!../../../assets/images/global/exit-fullscreen.svg').default,
60 'robot': require('!!raw-loader?!../../../assets/images/global/robot.svg').default
60} 61}
61 62
62export type GlobalIconName = keyof typeof icons 63export type GlobalIconName = keyof typeof icons
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
index 136730c91..8be578d9f 100644
--- a/client/src/app/shared/index.ts
+++ b/client/src/app/shared/index.ts
@@ -3,5 +3,5 @@ export * from './forms'
3export * from './rest' 3export * from './rest'
4export * from './users' 4export * from './users'
5export * from './video-abuse' 5export * from './video-abuse'
6export * from './video-blacklist' 6export * from './video-block'
7export * from './shared.module' 7export * from './shared.module'
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 813f76672..2035097d7 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -35,7 +35,7 @@ import {
35 UserValidatorsService, 35 UserValidatorsService,
36 VideoAbuseValidatorsService, 36 VideoAbuseValidatorsService,
37 VideoAcceptOwnershipValidatorsService, 37 VideoAcceptOwnershipValidatorsService,
38 VideoBlacklistValidatorsService, 38 VideoBlockValidatorsService,
39 VideoChangeOwnershipValidatorsService, 39 VideoChangeOwnershipValidatorsService,
40 VideoChannelValidatorsService, 40 VideoChannelValidatorsService,
41 VideoCommentValidatorsService, 41 VideoCommentValidatorsService,
@@ -78,7 +78,7 @@ import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playli
78import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' 78import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
79import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 79import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
80import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' 80import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
81import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' 81import { VideoBlockComponent } from '@app/shared/video/modals/video-block.component'
82import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' 82import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
83import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' 83import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
84import { RedundancyService } from '@app/shared/video/redundancy.service' 84import { RedundancyService } from '@app/shared/video/redundancy.service'
@@ -102,7 +102,7 @@ import { LoaderComponent } from './misc/loader.component'
102import { RestExtractor, RestService } from './rest' 102import { RestExtractor, RestService } from './rest'
103import { UserService } from './users' 103import { UserService } from './users'
104import { VideoAbuseService } from './video-abuse' 104import { VideoAbuseService } from './video-abuse'
105import { VideoBlacklistService } from './video-blacklist' 105import { VideoBlockService } from './video-block'
106import { VideoOwnershipService } from './video-ownership' 106import { VideoOwnershipService } from './video-ownership'
107import { FeedComponent } from './video/feed.component' 107import { FeedComponent } from './video/feed.component'
108import { VideoMiniatureComponent } from './video/video-miniature.component' 108import { VideoMiniatureComponent } from './video/video-miniature.component'
@@ -147,7 +147,7 @@ import { VideoService } from './video/video.service'
147 147
148 VideoDownloadComponent, 148 VideoDownloadComponent,
149 VideoReportComponent, 149 VideoReportComponent,
150 VideoBlacklistComponent, 150 VideoBlockComponent,
151 151
152 FeedComponent, 152 FeedComponent,
153 153
@@ -230,7 +230,7 @@ import { VideoService } from './video/video.service'
230 230
231 VideoDownloadComponent, 231 VideoDownloadComponent,
232 VideoReportComponent, 232 VideoReportComponent,
233 VideoBlacklistComponent, 233 VideoBlockComponent,
234 234
235 FeedComponent, 235 FeedComponent,
236 236
@@ -282,7 +282,7 @@ import { VideoService } from './video/video.service'
282 RestExtractor, 282 RestExtractor,
283 RestService, 283 RestService,
284 VideoAbuseService, 284 VideoAbuseService,
285 VideoBlacklistService, 285 VideoBlockService,
286 VideoOwnershipService, 286 VideoOwnershipService,
287 UserService, 287 UserService,
288 VideoService, 288 VideoService,
@@ -305,7 +305,7 @@ import { VideoService } from './video/video.service'
305 VideoCommentValidatorsService, 305 VideoCommentValidatorsService,
306 VideoValidatorsService, 306 VideoValidatorsService,
307 VideoCaptionsValidatorsService, 307 VideoCaptionsValidatorsService,
308 VideoBlacklistValidatorsService, 308 VideoBlockValidatorsService,
309 OverviewService, 309 OverviewService,
310 VideoChangeOwnershipValidatorsService, 310 VideoChangeOwnershipValidatorsService,
311 VideoAcceptOwnershipValidatorsService, 311 VideoAcceptOwnershipValidatorsService,
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts
index 7b8368d87..bc1861c64 100644
--- a/client/src/app/shared/users/user-notification.model.ts
+++ b/client/src/app/shared/users/user-notification.model.ts
@@ -96,7 +96,7 @@ export class UserNotification implements UserNotificationServer {
96 this.videoUrl = this.buildVideoUrl(this.video) 96 this.videoUrl = this.buildVideoUrl(this.video)
97 break 97 break
98 98
99 case UserNotificationType.UNBLACKLIST_ON_MY_VIDEO: 99 case UserNotificationType.UNBLOCK_ON_MY_VIDEO:
100 this.videoUrl = this.buildVideoUrl(this.video) 100 this.videoUrl = this.buildVideoUrl(this.video)
101 break 101 break
102 102
@@ -112,7 +112,7 @@ export class UserNotification implements UserNotificationServer {
112 this.videoUrl = this.buildVideoUrl(this.videoAbuse.video) 112 this.videoUrl = this.buildVideoUrl(this.videoAbuse.video)
113 break 113 break
114 114
115 case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: 115 case UserNotificationType.VIDEO_AUTO_BLOCK_FOR_MODERATORS:
116 this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' 116 this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list'
117 // Backward compatibility where we did not assign videoBlacklist to this type of notification before 117 // Backward compatibility where we did not assign videoBlacklist to this type of notification before
118 if (!this.videoBlacklist) this.videoBlacklist = { id: null, video: this.video } 118 if (!this.videoBlacklist) this.videoBlacklist = { id: null, video: this.video }
@@ -120,7 +120,7 @@ export class UserNotification implements UserNotificationServer {
120 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) 120 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
121 break 121 break
122 122
123 case UserNotificationType.BLACKLIST_ON_MY_VIDEO: 123 case UserNotificationType.BLOCK_ON_MY_VIDEO:
124 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) 124 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
125 break 125 break
126 126
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html
index 8dbe6e329..5a102995a 100644
--- a/client/src/app/shared/users/user-notifications.component.html
+++ b/client/src/app/shared/users/user-notifications.component.html
@@ -26,19 +26,19 @@
26 </ng-template> 26 </ng-template>
27 </ng-container> 27 </ng-container>
28 28
29 <ng-container *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO"> 29 <ng-container *ngSwitchCase="UserNotificationType.UNBLOCK_ON_MY_VIDEO">
30 <my-global-icon iconName="undo"></my-global-icon> 30 <my-global-icon iconName="undo"></my-global-icon>
31 31
32 <div class="message" i18n> 32 <div class="message" i18n>
33 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been unblacklisted 33 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been unblocked
34 </div> 34 </div>
35 </ng-container> 35 </ng-container>
36 36
37 <ng-container *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO"> 37 <ng-container *ngSwitchCase="UserNotificationType.BLOCK_ON_MY_VIDEO">
38 <my-global-icon iconName="no"></my-global-icon> 38 <my-global-icon iconName="no"></my-global-icon>
39 39
40 <div class="message" i18n> 40 <div class="message" i18n>
41 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been blacklisted 41 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been blocked
42 </div> 42 </div>
43 </ng-container> 43 </ng-container>
44 44
@@ -50,11 +50,11 @@
50 </div> 50 </div>
51 </ng-container> 51 </ng-container>
52 52
53 <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS"> 53 <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLOCK_FOR_MODERATORS">
54 <my-global-icon iconName="no"></my-global-icon> 54 <my-global-icon iconName="no"></my-global-icon>
55 55
56 <div class="message" i18n> 56 <div class="message" i18n>
57 The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a> 57 The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blocked</a>
58 </div> 58 </div>
59 </ng-container> 59 </ng-container>
60 60
diff --git a/client/src/app/shared/video-blacklist/index.ts b/client/src/app/shared/video-blacklist/index.ts
deleted file mode 100644
index bfb026441..000000000
--- a/client/src/app/shared/video-blacklist/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-blacklist.service'
diff --git a/client/src/app/shared/video-blacklist/video-blacklist.service.ts b/client/src/app/shared/video-blacklist/video-blacklist.service.ts
deleted file mode 100644
index c0e13a651..000000000
--- a/client/src/app/shared/video-blacklist/video-blacklist.service.ts
+++ /dev/null
@@ -1,89 +0,0 @@
1import { catchError, map, concatMap, toArray } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { SortMeta } from 'primeng/api'
5import { from as observableFrom, Observable } from 'rxjs'
6import { VideoBlacklist, VideoBlacklistType, ResultList } from '../../../../../shared'
7import { Video } from '../video/video.model'
8import { environment } from '../../../environments/environment'
9import { RestExtractor, RestPagination, RestService } from '../rest'
10import { ComponentPaginationLight } from '../rest/component-pagination.model'
11
12@Injectable()
13export class VideoBlacklistService {
14 private static BASE_VIDEOS_URL = environment.apiUrl + '/api/v1/videos/'
15
16 constructor (
17 private authHttp: HttpClient,
18 private restService: RestService,
19 private restExtractor: RestExtractor
20 ) {}
21
22 listBlacklist (options: {
23 pagination: RestPagination,
24 sort: SortMeta,
25 search?: string
26 type?: VideoBlacklistType
27 }): Observable<ResultList<VideoBlacklist>> {
28 const { pagination, sort, search, type } = options
29
30 let params = new HttpParams()
31 params = this.restService.addRestGetParams(params, pagination, sort)
32
33 if (search) params = params.append('search', search)
34 if (type) params = params.append('type', type.toString())
35
36 return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
37 .pipe(
38 map(res => this.restExtractor.convertResultListDateToHuman(res)),
39 catchError(res => this.restExtractor.handleError(res))
40 )
41 }
42
43 getAutoBlacklistedAsVideoList (videoPagination: ComponentPaginationLight): Observable<ResultList<Video>> {
44 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
45
46 // prioritize first created since waiting longest
47 const AUTO_BLACKLIST_SORT = 'createdAt'
48
49 let params = new HttpParams()
50 params = this.restService.addRestGetParams(params, pagination, AUTO_BLACKLIST_SORT)
51
52 params = params.set('type', VideoBlacklistType.AUTO_BEFORE_PUBLISHED.toString())
53
54 return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
55 .pipe(
56 map(res => {
57 return {
58 total: res.total,
59 data: res.data.map(videoBlacklist => new Video(videoBlacklist.video))
60 }
61 }),
62 catchError(res => this.restExtractor.handleError(res))
63 )
64 }
65
66 removeVideoFromBlacklist (videoIdArgs: number | number[]) {
67 const videoIds = Array.isArray(videoIdArgs) ? videoIdArgs : [ videoIdArgs ]
68
69 return observableFrom(videoIds)
70 .pipe(
71 concatMap(id => this.authHttp.delete(VideoBlacklistService.BASE_VIDEOS_URL + id + '/blacklist')),
72 toArray(),
73 catchError(err => this.restExtractor.handleError(err))
74 )
75 }
76
77 blacklistVideo (videoId: number, reason: string, unfederate: boolean) {
78 const body = {
79 unfederate,
80 reason
81 }
82
83 return this.authHttp.post(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist', body)
84 .pipe(
85 map(this.restExtractor.extractDataBool),
86 catchError(res => this.restExtractor.handleError(res))
87 )
88 }
89}
diff --git a/client/src/app/shared/video-block/index.ts b/client/src/app/shared/video-block/index.ts
new file mode 100644
index 000000000..a99551a38
--- /dev/null
+++ b/client/src/app/shared/video-block/index.ts
@@ -0,0 +1 @@
export * from './video-block.service'
diff --git a/client/src/app/shared/video-block/video-block.service.ts b/client/src/app/shared/video-block/video-block.service.ts
new file mode 100644
index 000000000..67ca1d85b
--- /dev/null
+++ b/client/src/app/shared/video-block/video-block.service.ts
@@ -0,0 +1,77 @@
1import { catchError, map, concatMap, toArray } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { SortMeta } from 'primeng/api'
5import { from as observableFrom, Observable } from 'rxjs'
6import { VideoBlocklist, VideoBlockType, ResultList } from '../../../../../shared'
7import { environment } from '../../../environments/environment'
8import { RestExtractor, RestPagination, RestService } from '../rest'
9
10@Injectable()
11export class VideoBlockService {
12 private static BASE_VIDEOS_URL = environment.apiUrl + '/api/v1/videos/'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restService: RestService,
17 private restExtractor: RestExtractor
18 ) {}
19
20 listBlocks (options: {
21 pagination: RestPagination
22 sort: SortMeta
23 search?: string
24 type?: VideoBlockType
25 }): Observable<ResultList<VideoBlocklist>> {
26 const { pagination, sort, search, type } = options
27
28 let params = new HttpParams()
29 params = this.restService.addRestGetParams(params, pagination, sort)
30
31 if (search) {
32 const filters = this.restService.parseQueryStringFilter(search, {
33 type: {
34 prefix: 'type:',
35 handler: v => {
36 if (v === 'manual') return VideoBlockType.MANUAL
37 if (v === 'auto') return VideoBlockType.AUTO_BEFORE_PUBLISHED
38
39 return undefined
40 }
41 }
42 })
43
44 params = this.restService.addObjectParams(params, filters)
45 }
46
47 return this.authHttp.get<ResultList<VideoBlocklist>>(VideoBlockService.BASE_VIDEOS_URL + 'blacklist', { params })
48 .pipe(
49 map(res => this.restExtractor.convertResultListDateToHuman(res)),
50 catchError(res => this.restExtractor.handleError(res))
51 )
52 }
53
54 unblockVideo (videoIdArgs: number | number[]) {
55 const videoIds = Array.isArray(videoIdArgs) ? videoIdArgs : [ videoIdArgs ]
56
57 return observableFrom(videoIds)
58 .pipe(
59 concatMap(id => this.authHttp.delete(VideoBlockService.BASE_VIDEOS_URL + id + '/blacklist')),
60 toArray(),
61 catchError(err => this.restExtractor.handleError(err))
62 )
63 }
64
65 blockVideo (videoId: number, reason: string, unfederate: boolean) {
66 const body = {
67 unfederate,
68 reason
69 }
70
71 return this.authHttp.post(VideoBlockService.BASE_VIDEOS_URL + videoId + '/blacklist', body)
72 .pipe(
73 map(this.restExtractor.extractDataBool),
74 catchError(res => this.restExtractor.handleError(res))
75 )
76 }
77}
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index cd8a5b840..8ce3b25b0 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -39,7 +39,7 @@
39 [fitWidth]="true" 39 [fitWidth]="true"
40 [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType" 40 [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
41 [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions" 41 [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions"
42 (videoBlacklisted)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)" 42 (videoBlocked)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)"
43 > 43 >
44 </my-video-miniature> 44 </my-video-miniature>
45 </ng-container> 45 </ng-container>
diff --git a/client/src/app/shared/video/modals/video-blacklist.component.html b/client/src/app/shared/video/modals/video-block.component.html
index 8f06a6b02..a8dd30b5e 100644
--- a/client/src/app/shared/video/modals/video-blacklist.component.html
+++ b/client/src/app/shared/video/modals/video-block.component.html
@@ -1,12 +1,12 @@
1<ng-template #modal> 1<ng-template #modal>
2 <div class="modal-header"> 2 <div class="modal-header">
3 <h4 i18n class="modal-title">Blacklist video</h4> 3 <h4 i18n class="modal-title">Blocklist video</h4>
4 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> 4 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
5 </div> 5 </div>
6 6
7 <div class="modal-body"> 7 <div class="modal-body">
8 8
9 <form novalidate [formGroup]="form" (ngSubmit)="blacklist()"> 9 <form novalidate [formGroup]="form" (ngSubmit)="block()">
10 <div class="form-group"> 10 <div class="form-group">
11 <textarea 11 <textarea
12 i18n-placeholder placeholder="Reason..." formControlName="reason" 12 i18n-placeholder placeholder="Reason..." formControlName="reason"
diff --git a/client/src/app/shared/video/modals/video-blacklist.component.scss b/client/src/app/shared/video/modals/video-block.component.scss
index afcdb9a16..afcdb9a16 100644
--- a/client/src/app/shared/video/modals/video-blacklist.component.scss
+++ b/client/src/app/shared/video/modals/video-block.component.scss
diff --git a/client/src/app/shared/video/modals/video-blacklist.component.ts b/client/src/app/shared/video/modals/video-block.component.ts
index 6ef9c250b..1a25e0578 100644
--- a/client/src/app/shared/video/modals/video-blacklist.component.ts
+++ b/client/src/app/shared/video/modals/video-block.component.ts
@@ -1,24 +1,24 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier, RedirectService } from '@app/core' 2import { Notifier, RedirectService } from '@app/core'
3import { VideoBlacklistService } from '../../../shared/video-blacklist' 3import { VideoBlockService } from '../../video-block'
4import { I18n } from '@ngx-translate/i18n-polyfill' 4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 5import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 6import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
8import { FormReactive, VideoBlacklistValidatorsService } from '@app/shared/forms' 8import { FormReactive, VideoBlockValidatorsService } from '@app/shared/forms'
9import { Video } from '@app/shared/video/video.model' 9import { Video } from '@app/shared/video/video.model'
10 10
11@Component({ 11@Component({
12 selector: 'my-video-blacklist', 12 selector: 'my-video-block',
13 templateUrl: './video-blacklist.component.html', 13 templateUrl: './video-block.component.html',
14 styleUrls: [ './video-blacklist.component.scss' ] 14 styleUrls: [ './video-block.component.scss' ]
15}) 15})
16export class VideoBlacklistComponent extends FormReactive implements OnInit { 16export class VideoBlockComponent extends FormReactive implements OnInit {
17 @Input() video: Video = null 17 @Input() video: Video = null
18 18
19 @ViewChild('modal', { static: true }) modal: NgbModal 19 @ViewChild('modal', { static: true }) modal: NgbModal
20 20
21 @Output() videoBlacklisted = new EventEmitter() 21 @Output() videoBlocked = new EventEmitter()
22 22
23 error: string = null 23 error: string = null
24 24
@@ -27,10 +27,9 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
27 constructor ( 27 constructor (
28 protected formValidatorService: FormValidatorService, 28 protected formValidatorService: FormValidatorService,
29 private modalService: NgbModal, 29 private modalService: NgbModal,
30 private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, 30 private videoBlockValidatorsService: VideoBlockValidatorsService,
31 private videoBlacklistService: VideoBlacklistService, 31 private videoBlocklistService: VideoBlockService,
32 private notifier: Notifier, 32 private notifier: Notifier,
33 private redirectService: RedirectService,
34 private i18n: I18n 33 private i18n: I18n
35 ) { 34 ) {
36 super() 35 super()
@@ -40,7 +39,7 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
40 const defaultValues = { unfederate: 'true' } 39 const defaultValues = { unfederate: 'true' }
41 40
42 this.buildForm({ 41 this.buildForm({
43 reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON, 42 reason: this.videoBlockValidatorsService.VIDEO_BLOCK_REASON,
44 unfederate: null 43 unfederate: null
45 }, defaultValues) 44 }, defaultValues)
46 } 45 }
@@ -54,20 +53,20 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
54 this.openedModal = null 53 this.openedModal = null
55 } 54 }
56 55
57 blacklist () { 56 block () {
58 const reason = this.form.value[ 'reason' ] || undefined 57 const reason = this.form.value[ 'reason' ] || undefined
59 const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined 58 const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined
60 59
61 this.videoBlacklistService.blacklistVideo(this.video.id, reason, unfederate) 60 this.videoBlocklistService.blockVideo(this.video.id, reason, unfederate)
62 .subscribe( 61 .subscribe(
63 () => { 62 () => {
64 this.notifier.success(this.i18n('Video blacklisted.')) 63 this.notifier.success(this.i18n('Video blocked.'))
65 this.hide() 64 this.hide()
66 65
67 this.video.blacklisted = true 66 this.video.blacklisted = true
68 this.video.blacklistedReason = reason 67 this.video.blockedReason = reason
69 68
70 this.videoBlacklisted.emit() 69 this.videoBlocked.emit()
71 }, 70 },
72 71
73 err => this.notifier.error(err.message) 72 err => this.notifier.error(err.message)
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.html b/client/src/app/shared/video/video-actions-dropdown.component.html
index ec03fa55d..3c8271b65 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.html
+++ b/client/src/app/shared/video/video-actions-dropdown.component.html
@@ -17,5 +17,5 @@
17 17
18 <my-video-download #videoDownloadModal></my-video-download> 18 <my-video-download #videoDownloadModal></my-video-download>
19 <my-video-report #videoReportModal [video]="video"></my-video-report> 19 <my-video-report #videoReportModal [video]="video"></my-video-report>
20 <my-video-blacklist #videoBlacklistModal [video]="video" (videoBlacklisted)="onVideoBlacklisted()"></my-video-blacklist> 20 <my-video-block #videoBlockModal [video]="video" (videoBlocked)="onVideoBlocked()"></my-video-block>
21</ng-container> 21</ng-container>
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.ts b/client/src/app/shared/video/video-actions-dropdown.component.ts
index 4e5fc6476..1f5763610 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.ts
+++ b/client/src/app/shared/video/video-actions-dropdown.component.ts
@@ -9,8 +9,8 @@ import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
9import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' 9import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
10import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' 10import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
11import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' 11import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
12import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' 12import { VideoBlockComponent } from '@app/shared/video/modals/video-block.component'
13import { VideoBlacklistService } from '@app/shared/video-blacklist' 13import { VideoBlockService } from '@app/shared/video-block'
14import { ScreenService } from '@app/shared/misc/screen.service' 14import { ScreenService } from '@app/shared/misc/screen.service'
15import { VideoCaption } from '@shared/models' 15import { VideoCaption } from '@shared/models'
16import { RedundancyService } from '@app/shared/video/redundancy.service' 16import { RedundancyService } from '@app/shared/video/redundancy.service'
@@ -36,7 +36,7 @@ export class VideoActionsDropdownComponent implements OnChanges {
36 36
37 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent 37 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
38 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent 38 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
39 @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent 39 @ViewChild('videoBlockModal') videoBlockModal: VideoBlockComponent
40 40
41 @Input() video: Video | VideoDetails 41 @Input() video: Video | VideoDetails
42 @Input() videoCaptions: VideoCaption[] = [] 42 @Input() videoCaptions: VideoCaption[] = []
@@ -59,8 +59,8 @@ export class VideoActionsDropdownComponent implements OnChanges {
59 @Input() buttonDirection: DropdownDirection = 'vertical' 59 @Input() buttonDirection: DropdownDirection = 'vertical'
60 60
61 @Output() videoRemoved = new EventEmitter() 61 @Output() videoRemoved = new EventEmitter()
62 @Output() videoUnblacklisted = new EventEmitter() 62 @Output() videoUnblocked = new EventEmitter()
63 @Output() videoBlacklisted = new EventEmitter() 63 @Output() videoBlocked = new EventEmitter()
64 @Output() modalOpened = new EventEmitter() 64 @Output() modalOpened = new EventEmitter()
65 65
66 videoActions: DropdownAction<{ video: Video }>[][] = [] 66 videoActions: DropdownAction<{ video: Video }>[][] = []
@@ -71,7 +71,7 @@ export class VideoActionsDropdownComponent implements OnChanges {
71 private authService: AuthService, 71 private authService: AuthService,
72 private notifier: Notifier, 72 private notifier: Notifier,
73 private confirmService: ConfirmService, 73 private confirmService: ConfirmService,
74 private videoBlacklistService: VideoBlacklistService, 74 private videoBlocklistService: VideoBlockService,
75 private screenService: ScreenService, 75 private screenService: ScreenService,
76 private videoService: VideoService, 76 private videoService: VideoService,
77 private redundancyService: RedundancyService, 77 private redundancyService: RedundancyService,
@@ -117,10 +117,10 @@ export class VideoActionsDropdownComponent implements OnChanges {
117 this.videoReportModal.show() 117 this.videoReportModal.show()
118 } 118 }
119 119
120 showBlacklistModal () { 120 showBlockModal () {
121 this.modalOpened.emit() 121 this.modalOpened.emit()
122 122
123 this.videoBlacklistModal.show() 123 this.videoBlockModal.show()
124 } 124 }
125 125
126 /* Actions checker */ 126 /* Actions checker */
@@ -133,12 +133,12 @@ export class VideoActionsDropdownComponent implements OnChanges {
133 return this.video.isRemovableBy(this.user) 133 return this.video.isRemovableBy(this.user)
134 } 134 }
135 135
136 isVideoBlacklistable () { 136 isVideoBlockable () {
137 return this.video.isBlackistableBy(this.user) 137 return this.video.isBlockableBy(this.user)
138 } 138 }
139 139
140 isVideoUnblacklistable () { 140 isVideoUnblockable () {
141 return this.video.isUnblacklistableBy(this.user) 141 return this.video.isUnblockableBy(this.user)
142 } 142 }
143 143
144 isVideoDownloadable () { 144 isVideoDownloadable () {
@@ -151,22 +151,22 @@ export class VideoActionsDropdownComponent implements OnChanges {
151 151
152 /* Action handlers */ 152 /* Action handlers */
153 153
154 async unblacklistVideo () { 154 async unblockVideo () {
155 const confirmMessage = this.i18n( 155 const confirmMessage = this.i18n(
156 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' 156 'Do you really want to unblock this video? It will be available again in the videos list.'
157 ) 157 )
158 158
159 const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist')) 159 const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblock'))
160 if (res === false) return 160 if (res === false) return
161 161
162 this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( 162 this.videoBlocklistService.unblockVideo(this.video.id).subscribe(
163 () => { 163 () => {
164 this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })) 164 this.notifier.success(this.i18n('Video {{name}} unblocked.', { name: this.video.name }))
165 165
166 this.video.blacklisted = false 166 this.video.blacklisted = false
167 this.video.blacklistedReason = null 167 this.video.blockedReason = null
168 168
169 this.videoUnblacklisted.emit() 169 this.videoUnblocked.emit()
170 }, 170 },
171 171
172 err => this.notifier.error(err.message) 172 err => this.notifier.error(err.message)
@@ -203,8 +203,8 @@ export class VideoActionsDropdownComponent implements OnChanges {
203 ) 203 )
204 } 204 }
205 205
206 onVideoBlacklisted () { 206 onVideoBlocked () {
207 this.videoBlacklisted.emit() 207 this.videoBlocked.emit()
208 } 208 }
209 209
210 getPlaylistDropdownPlacement () { 210 getPlaylistDropdownPlacement () {
@@ -239,16 +239,16 @@ export class VideoActionsDropdownComponent implements OnChanges {
239 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.update && this.isVideoUpdatable() 239 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.update && this.isVideoUpdatable()
240 }, 240 },
241 { 241 {
242 label: this.i18n('Blacklist'), 242 label: this.i18n('Block'),
243 handler: () => this.showBlacklistModal(), 243 handler: () => this.showBlockModal(),
244 iconName: 'no', 244 iconName: 'no',
245 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.blacklist && this.isVideoBlacklistable() 245 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.blacklist && this.isVideoBlockable()
246 }, 246 },
247 { 247 {
248 label: this.i18n('Unblacklist'), 248 label: this.i18n('Unblock'),
249 handler: () => this.unblacklistVideo(), 249 handler: () => this.unblockVideo(),
250 iconName: 'undo', 250 iconName: 'undo',
251 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.blacklist && this.isVideoUnblacklistable() 251 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.blacklist && this.isVideoUnblockable()
252 }, 252 },
253 { 253 {
254 label: this.i18n('Mirror'), 254 label: this.i18n('Mirror'),
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html
index 3e23cf18c..575505f63 100644
--- a/client/src/app/shared/video/video-miniature.component.html
+++ b/client/src/app/shared/video/video-miniature.component.html
@@ -43,9 +43,9 @@
43 </div> 43 </div>
44 </div> 44 </div>
45 45
46 <div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blacklisted"> 46 <div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blocked">
47 <span class="blacklisted-label" i18n>Blacklisted</span> 47 <span class="blocked-label" i18n>Blocked</span>
48 <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span> 48 <span class="blocked-reason" *ngIf="video.blockedReason">{{ video.blockedReason }}</span>
49 </div> 49 </div>
50 50
51 <div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw"> 51 <div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
@@ -57,7 +57,7 @@
57 <!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown: https://github.com/ng-bootstrap/ng-bootstrap/issues/3495 --> 57 <!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown: https://github.com/ng-bootstrap/ng-bootstrap/issues/3495 -->
58 <my-video-actions-dropdown 58 <my-video-actions-dropdown
59 *ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left auto" 59 *ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left auto"
60 (videoRemoved)="onVideoRemoved()" (videoBlacklisted)="onVideoBlacklisted()" (videoUnblacklisted)="onVideoUnblacklisted()" 60 (videoRemoved)="onVideoRemoved()" (videoBlocked)="onVideoBlocked()" (videoUnblocked)="onVideoUnblocked()"
61 ></my-video-actions-dropdown> 61 ></my-video-actions-dropdown>
62 </div> 62 </div>
63 </div> 63 </div>
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss
index 849bd54bb..34f34f228 100644
--- a/client/src/app/shared/video/video-miniature.component.scss
+++ b/client/src/app/shared/video/video-miniature.component.scss
@@ -45,15 +45,15 @@ $more-margin-right: 15px;
45 } 45 }
46 46
47 .video-info-privacy, 47 .video-info-privacy,
48 .video-info-blacklisted .blacklisted-label, 48 .video-info-blocked .blocked-label,
49 .video-info-nsfw { 49 .video-info-nsfw {
50 font-weight: $font-semibold; 50 font-weight: $font-semibold;
51 } 51 }
52 52
53 .video-info-blacklisted { 53 .video-info-blocked {
54 color: red; 54 color: red;
55 55
56 .blacklisted-reason::before { 56 .blocked-reason::before {
57 content: ' - '; 57 content: ' - ';
58 } 58 }
59 } 59 }
@@ -160,7 +160,7 @@ $more-margin-right: 15px;
160 margin-top: 5px; 160 margin-top: 5px;
161 } 161 }
162 162
163 .video-info-blacklisted { 163 .video-info-blocked {
164 margin-top: 3px; 164 margin-top: 3px;
165 } 165 }
166 } 166 }
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts
index aa1726ca7..f0b0992e2 100644
--- a/client/src/app/shared/video/video-miniature.component.ts
+++ b/client/src/app/shared/video/video-miniature.component.ts
@@ -59,8 +59,8 @@ export class VideoMiniatureComponent implements OnInit {
59 59
60 @Input() useLazyLoadUrl = false 60 @Input() useLazyLoadUrl = false
61 61
62 @Output() videoBlacklisted = new EventEmitter() 62 @Output() videoBlocked = new EventEmitter()
63 @Output() videoUnblacklisted = new EventEmitter() 63 @Output() videoUnblocked = new EventEmitter()
64 @Output() videoRemoved = new EventEmitter() 64 @Output() videoRemoved = new EventEmitter()
65 65
66 videoActionsDisplayOptions: VideoActionsDisplayType = { 66 videoActionsDisplayOptions: VideoActionsDisplayType = {
@@ -184,12 +184,12 @@ export class VideoMiniatureComponent implements OnInit {
184 this.loadWatchLater() 184 this.loadWatchLater()
185 } 185 }
186 186
187 onVideoBlacklisted () { 187 onVideoBlocked () {
188 this.videoBlacklisted.emit() 188 this.videoBlocked.emit()
189 } 189 }
190 190
191 onVideoUnblacklisted () { 191 onVideoUnblocked () {
192 this.videoUnblacklisted.emit() 192 this.videoUnblocked.emit()
193 } 193 }
194 194
195 onVideoRemoved () { 195 onVideoRemoved () {
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 97759f9c1..2b3d915ef 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -54,7 +54,7 @@ export class Video implements VideoServerModel {
54 state?: VideoConstant<VideoState> 54 state?: VideoConstant<VideoState>
55 scheduledUpdate?: VideoScheduleUpdate 55 scheduledUpdate?: VideoScheduleUpdate
56 blacklisted?: boolean 56 blacklisted?: boolean
57 blacklistedReason?: string 57 blockedReason?: string
58 58
59 account: { 59 account: {
60 id: number 60 id: number
@@ -140,7 +140,7 @@ export class Video implements VideoServerModel {
140 if (this.state) this.state.label = peertubeTranslate(this.state.label, translations) 140 if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
141 141
142 this.blacklisted = hash.blacklisted 142 this.blacklisted = hash.blacklisted
143 this.blacklistedReason = hash.blacklistedReason 143 this.blockedReason = hash.blacklistedReason
144 144
145 this.userHistory = hash.userHistory 145 this.userHistory = hash.userHistory
146 146
@@ -163,12 +163,12 @@ export class Video implements VideoServerModel {
163 return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO)) 163 return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
164 } 164 }
165 165
166 isBlackistableBy (user: AuthUser) { 166 isBlockableBy (user: AuthUser) {
167 return this.blacklisted !== true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true 167 return this.blacklisted !== true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLOCKS) === true
168 } 168 }
169 169
170 isUnblacklistableBy (user: AuthUser) { 170 isUnblockableBy (user: AuthUser) {
171 return this.blacklisted === true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true 171 return this.blacklisted === true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLOCKS) === true
172 } 172 }
173 173
174 isUpdatableBy (user: AuthUser) { 174 isUpdatableBy (user: AuthUser) {