diff options
8 files changed, 81 insertions, 37 deletions
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html index 26096da02..185f13275 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html | |||
@@ -1,11 +1,18 @@ | |||
1 | <h1 class="sr-only" i18n>Settings</h1> | 1 | <h1 class="sr-only" i18n>Settings</h1> |
2 | <div class="form-row"> <!-- profile grid --> | 2 | <div class="form-row"> <!-- preview --> |
3 | <div class="form-group col-12 col-lg-4 col-xl-3"></div> | ||
4 | |||
5 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | ||
6 | <my-actor-avatar-info [actor]="user.account" (avatarChange)="onAvatarChange($event)"></my-actor-avatar-info> | ||
7 | </div> | ||
8 | </div> | ||
9 | |||
10 | <div class="form-row"> <!-- profile settings grid --> | ||
3 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 11 | <div class="form-group col-12 col-lg-4 col-xl-3"> |
4 | <h2 i18n class="account-title">PROFILE</h2> | 12 | <h2 i18n class="account-title">PROFILE SETTINGS</h2> |
5 | </div> | 13 | </div> |
6 | 14 | ||
7 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 15 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> |
8 | <my-actor-avatar-info [actor]="user.account" (avatarChange)="onAvatarChange($event)"></my-actor-avatar-info> | ||
9 | 16 | ||
10 | <div class="user-quota mb-3"> | 17 | <div class="user-quota mb-3"> |
11 | <div> | 18 | <div> |
diff --git a/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html b/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html index 0467cabf5..1ab1b7343 100644 --- a/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html +++ b/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html | |||
@@ -15,7 +15,7 @@ | |||
15 | <ng-container *ngFor="let video of (videos$ | async); let i = index; let length = count"> | 15 | <ng-container *ngFor="let video of (videos$ | async); let i = index; let length = count"> |
16 | <my-video-miniature | 16 | <my-video-miniature |
17 | [displayOptions]="displayOptions" [video]="video" [user]="userMiniature" | 17 | [displayOptions]="displayOptions" [video]="video" [user]="userMiniature" |
18 | (videoBlocked)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()"> | 18 | (videoBlocked)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()" (videoAccountMuted)="onVideoRemoved()"> |
19 | </my-video-miniature> | 19 | </my-video-miniature> |
20 | 20 | ||
21 | <hr *ngIf="!playlist && i == 0 && length > 1" /> | 21 | <hr *ngIf="!playlist && i == 0 && length > 1" /> |
diff --git a/client/src/app/shared/shared-moderation/blocklist.service.ts b/client/src/app/shared/shared-moderation/blocklist.service.ts index 0caa92782..de677a77b 100644 --- a/client/src/app/shared/shared-moderation/blocklist.service.ts +++ b/client/src/app/shared/shared-moderation/blocklist.service.ts | |||
@@ -39,14 +39,14 @@ export class BlocklistService { | |||
39 | ) | 39 | ) |
40 | } | 40 | } |
41 | 41 | ||
42 | blockAccountByUser (account: Account) { | 42 | blockAccountByUser (account: Pick<Account, 'nameWithHost'>) { |
43 | const body = { accountName: account.nameWithHost } | 43 | const body = { accountName: account.nameWithHost } |
44 | 44 | ||
45 | return this.authHttp.post(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', body) | 45 | return this.authHttp.post(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', body) |
46 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 46 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
47 | } | 47 | } |
48 | 48 | ||
49 | unblockAccountByUser (account: Account) { | 49 | unblockAccountByUser (account: Pick<Account, 'nameWithHost'>) { |
50 | const path = BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts/' + account.nameWithHost | 50 | const path = BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts/' + account.nameWithHost |
51 | 51 | ||
52 | return this.authHttp.delete(path) | 52 | return this.authHttp.delete(path) |
@@ -102,14 +102,14 @@ export class BlocklistService { | |||
102 | ) | 102 | ) |
103 | } | 103 | } |
104 | 104 | ||
105 | blockAccountByInstance (account: Account) { | 105 | blockAccountByInstance (account: Pick<Account, 'nameWithHost'>) { |
106 | const body = { accountName: account.nameWithHost } | 106 | const body = { accountName: account.nameWithHost } |
107 | 107 | ||
108 | return this.authHttp.post(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', body) | 108 | return this.authHttp.post(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', body) |
109 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 109 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
110 | } | 110 | } |
111 | 111 | ||
112 | unblockAccountByInstance (account: Account) { | 112 | unblockAccountByInstance (account: Pick<Account, 'nameWithHost'>) { |
113 | const path = BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts/' + account.nameWithHost | 113 | const path = BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts/' + account.nameWithHost |
114 | 114 | ||
115 | return this.authHttp.delete(path) | 115 | return this.authHttp.delete(path) |
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 1e919ee72..9df0b5652 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 | |||
@@ -41,6 +41,7 @@ | |||
41 | [video]="video" [user]="userMiniature" [ownerDisplayType]="ownerDisplayType" | 41 | [video]="video" [user]="userMiniature" [ownerDisplayType]="ownerDisplayType" |
42 | [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions" | 42 | [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions" |
43 | (videoBlocked)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)" | 43 | (videoBlocked)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)" |
44 | (videoAccountMuted)="reloadVideos()" | ||
44 | > | 45 | > |
45 | </my-video-miniature> | 46 | </my-video-miniature> |
46 | </div> | 47 | </div> |
diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts index db8d1c309..9bd0741df 100644 --- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' |
2 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' | 2 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' |
3 | import { VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' | 3 | import { VideoBlockComponent, VideoBlockService, VideoReportComponent, BlocklistService } from '@app/shared/shared-moderation' |
4 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { VideoCaption } from '@shared/models' | 6 | import { VideoCaption } from '@shared/models' |
7 | import { DropdownAction, DropdownButtonSize, DropdownDirection, RedundancyService, Video, VideoDetails, VideoService } from '../shared-main' | 7 | import { DropdownAction, DropdownButtonSize, DropdownDirection, RedundancyService, Video, VideoDetails, VideoService, Actor } from '../shared-main' |
8 | import { VideoAddToPlaylistComponent } from '../shared-video-playlist' | 8 | import { VideoAddToPlaylistComponent } from '../shared-video-playlist' |
9 | import { VideoDownloadComponent } from './video-download.component' | 9 | import { VideoDownloadComponent } from './video-download.component' |
10 | 10 | ||
@@ -16,6 +16,7 @@ export type VideoActionsDisplayType = { | |||
16 | delete?: boolean | 16 | delete?: boolean |
17 | report?: boolean | 17 | report?: boolean |
18 | duplicate?: boolean | 18 | duplicate?: boolean |
19 | mute?: boolean | ||
19 | } | 20 | } |
20 | 21 | ||
21 | @Component({ | 22 | @Component({ |
@@ -41,7 +42,8 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
41 | blacklist: true, | 42 | blacklist: true, |
42 | delete: true, | 43 | delete: true, |
43 | report: true, | 44 | report: true, |
44 | duplicate: true | 45 | duplicate: true, |
46 | mute: true | ||
45 | } | 47 | } |
46 | @Input() placement = 'left' | 48 | @Input() placement = 'left' |
47 | 49 | ||
@@ -54,6 +56,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
54 | @Output() videoRemoved = new EventEmitter() | 56 | @Output() videoRemoved = new EventEmitter() |
55 | @Output() videoUnblocked = new EventEmitter() | 57 | @Output() videoUnblocked = new EventEmitter() |
56 | @Output() videoBlocked = new EventEmitter() | 58 | @Output() videoBlocked = new EventEmitter() |
59 | @Output() videoAccountMuted = new EventEmitter() | ||
57 | @Output() modalOpened = new EventEmitter() | 60 | @Output() modalOpened = new EventEmitter() |
58 | 61 | ||
59 | videoActions: DropdownAction<{ video: Video }>[][] = [] | 62 | videoActions: DropdownAction<{ video: Video }>[][] = [] |
@@ -64,6 +67,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
64 | private authService: AuthService, | 67 | private authService: AuthService, |
65 | private notifier: Notifier, | 68 | private notifier: Notifier, |
66 | private confirmService: ConfirmService, | 69 | private confirmService: ConfirmService, |
70 | private blocklistService: BlocklistService, | ||
67 | private videoBlocklistService: VideoBlockService, | 71 | private videoBlocklistService: VideoBlockService, |
68 | private screenService: ScreenService, | 72 | private screenService: ScreenService, |
69 | private videoService: VideoService, | 73 | private videoService: VideoService, |
@@ -142,6 +146,10 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
142 | return this.video.canBeDuplicatedBy(this.user) | 146 | return this.video.canBeDuplicatedBy(this.user) |
143 | } | 147 | } |
144 | 148 | ||
149 | isVideoAccountMutable () { | ||
150 | return this.video.account.id !== this.user.account.id | ||
151 | } | ||
152 | |||
145 | /* Action handlers */ | 153 | /* Action handlers */ |
146 | 154 | ||
147 | async unblockVideo () { | 155 | async unblockVideo () { |
@@ -152,18 +160,19 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
152 | const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblock')) | 160 | const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblock')) |
153 | if (res === false) return | 161 | if (res === false) return |
154 | 162 | ||
155 | this.videoBlocklistService.unblockVideo(this.video.id).subscribe( | 163 | this.videoBlocklistService.unblockVideo(this.video.id) |
156 | () => { | 164 | .subscribe( |
157 | this.notifier.success(this.i18n('Video {{name}} unblocked.', { name: this.video.name })) | 165 | () => { |
166 | this.notifier.success(this.i18n('Video {{name}} unblocked.', { name: this.video.name })) | ||
158 | 167 | ||
159 | this.video.blacklisted = false | 168 | this.video.blacklisted = false |
160 | this.video.blockedReason = null | 169 | this.video.blockedReason = null |
161 | 170 | ||
162 | this.videoUnblocked.emit() | 171 | this.videoUnblocked.emit() |
163 | }, | 172 | }, |
164 | 173 | ||
165 | err => this.notifier.error(err.message) | 174 | err => this.notifier.error(err.message) |
166 | ) | 175 | ) |
167 | } | 176 | } |
168 | 177 | ||
169 | async removeVideo () { | 178 | async removeVideo () { |
@@ -186,14 +195,29 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
186 | 195 | ||
187 | duplicateVideo () { | 196 | duplicateVideo () { |
188 | this.redundancyService.addVideoRedundancy(this.video) | 197 | this.redundancyService.addVideoRedundancy(this.video) |
189 | .subscribe( | 198 | .subscribe( |
190 | () => { | 199 | () => { |
191 | const message = this.i18n('This video will be duplicated by your instance.') | 200 | const message = this.i18n('This video will be duplicated by your instance.') |
192 | this.notifier.success(message) | 201 | this.notifier.success(message) |
193 | }, | 202 | }, |
194 | 203 | ||
195 | err => this.notifier.error(err.message) | 204 | err => this.notifier.error(err.message) |
196 | ) | 205 | ) |
206 | } | ||
207 | |||
208 | muteVideoAccount () { | ||
209 | const params = { nameWithHost: Actor.CREATE_BY_STRING(this.video.account.name, this.video.account.host) } | ||
210 | |||
211 | this.blocklistService.blockAccountByUser(params) | ||
212 | .subscribe( | ||
213 | () => { | ||
214 | this.notifier.success(this.i18n('Account {{nameWithHost}} muted.', params)) | ||
215 | |||
216 | this.videoAccountMuted.emit() | ||
217 | }, | ||
218 | |||
219 | err => this.notifier.error(err.message) | ||
220 | ) | ||
197 | } | 221 | } |
198 | 222 | ||
199 | onVideoBlocked () { | 223 | onVideoBlocked () { |
@@ -218,7 +242,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
218 | iconName: 'playlist-add' | 242 | iconName: 'playlist-add' |
219 | } | 243 | } |
220 | ], | 244 | ], |
221 | [ | 245 | [ // actions regarding the video |
222 | { | 246 | { |
223 | label: this.i18n('Download'), | 247 | label: this.i18n('Download'), |
224 | handler: () => this.showDownloadModal(), | 248 | handler: () => this.showDownloadModal(), |
@@ -254,15 +278,21 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
254 | handler: () => this.removeVideo(), | 278 | handler: () => this.removeVideo(), |
255 | isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.delete && this.isVideoRemovable(), | 279 | isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.delete && this.isVideoRemovable(), |
256 | iconName: 'delete' | 280 | iconName: 'delete' |
257 | } | 281 | }, |
258 | ], | ||
259 | [ | ||
260 | { | 282 | { |
261 | label: this.i18n('Report'), | 283 | label: this.i18n('Report'), |
262 | handler: () => this.showReportModal(), | 284 | handler: () => this.showReportModal(), |
263 | isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.report, | 285 | isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.report, |
264 | iconName: 'alert' | 286 | iconName: 'alert' |
265 | } | 287 | } |
288 | ], | ||
289 | [ // actions regarding the account/its server | ||
290 | { | ||
291 | label: this.i18n('Mute account'), | ||
292 | handler: () => this.muteVideoAccount(), | ||
293 | isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.mute && this.isVideoAccountMutable(), | ||
294 | iconName: 'no' | ||
295 | } | ||
266 | ] | 296 | ] |
267 | ] | 297 | ] |
268 | } | 298 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html index 82afc866f..e5d91e69a 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.html +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html | |||
@@ -59,7 +59,7 @@ | |||
59 | <!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown: https://github.com/ng-bootstrap/ng-bootstrap/issues/3495 --> | 59 | <!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown: https://github.com/ng-bootstrap/ng-bootstrap/issues/3495 --> |
60 | <my-video-actions-dropdown | 60 | <my-video-actions-dropdown |
61 | *ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left auto" | 61 | *ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left auto" |
62 | (videoRemoved)="onVideoRemoved()" (videoBlocked)="onVideoBlocked()" (videoUnblocked)="onVideoUnblocked()" | 62 | (videoRemoved)="onVideoRemoved()" (videoBlocked)="onVideoBlocked()" (videoUnblocked)="onVideoUnblocked()" (videoAccountMuted)="onVideoAccountMuted()" |
63 | ></my-video-actions-dropdown> | 63 | ></my-video-actions-dropdown> |
64 | </div> | 64 | </div> |
65 | </div> | 65 | </div> |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index 6f32977b3..e1adbb6ad 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts | |||
@@ -60,6 +60,7 @@ export class VideoMiniatureComponent implements OnInit { | |||
60 | @Output() videoBlocked = new EventEmitter() | 60 | @Output() videoBlocked = new EventEmitter() |
61 | @Output() videoUnblocked = new EventEmitter() | 61 | @Output() videoUnblocked = new EventEmitter() |
62 | @Output() videoRemoved = new EventEmitter() | 62 | @Output() videoRemoved = new EventEmitter() |
63 | @Output() videoAccountMuted = new EventEmitter() | ||
63 | 64 | ||
64 | videoActionsDisplayOptions: VideoActionsDisplayType = { | 65 | videoActionsDisplayOptions: VideoActionsDisplayType = { |
65 | playlist: true, | 66 | playlist: true, |
@@ -68,7 +69,8 @@ export class VideoMiniatureComponent implements OnInit { | |||
68 | blacklist: true, | 69 | blacklist: true, |
69 | delete: true, | 70 | delete: true, |
70 | report: true, | 71 | report: true, |
71 | duplicate: true | 72 | duplicate: true, |
73 | mute: true | ||
72 | } | 74 | } |
73 | showActions = false | 75 | showActions = false |
74 | serverConfig: ServerConfig | 76 | serverConfig: ServerConfig |
@@ -206,6 +208,10 @@ export class VideoMiniatureComponent implements OnInit { | |||
206 | this.videoRemoved.emit() | 208 | this.videoRemoved.emit() |
207 | } | 209 | } |
208 | 210 | ||
211 | onVideoAccountMuted () { | ||
212 | this.videoAccountMuted.emit() | ||
213 | } | ||
214 | |||
209 | isUserLoggedIn () { | 215 | isUserLoggedIn () { |
210 | return this.authService.isLoggedIn() | 216 | return this.authService.isLoggedIn() |
211 | } | 217 | } |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index c465eb3e7..90625d987 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -444,11 +444,11 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
444 | } | 444 | } |
445 | const accountWhere = accountId | 445 | const accountWhere = accountId |
446 | ? { | 446 | ? { |
447 | [Op.and]: { | 447 | [Op.and]: { |
448 | ...accountExclusion, | 448 | ...accountExclusion, |
449 | [Op.eq]: accountId | 449 | [Op.eq]: accountId |
450 | } | ||
451 | } | 450 | } |
451 | } | ||
452 | : accountExclusion | 452 | : accountExclusion |
453 | 453 | ||
454 | const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined | 454 | const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined |