aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html13
-rw-r--r--client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html2
-rw-r--r--client/src/app/shared/shared-moderation/blocklist.service.ts8
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.html1
-rw-r--r--client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts76
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.html2
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.ts8
-rw-r--r--server/models/video/video-comment.ts8
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 @@
1import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core'
2import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' 2import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core'
3import { VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' 3import { VideoBlockComponent, VideoBlockService, VideoReportComponent, BlocklistService } from '@app/shared/shared-moderation'
4import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' 4import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
5import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { VideoCaption } from '@shared/models' 6import { VideoCaption } from '@shared/models'
7import { DropdownAction, DropdownButtonSize, DropdownDirection, RedundancyService, Video, VideoDetails, VideoService } from '../shared-main' 7import { DropdownAction, DropdownButtonSize, DropdownDirection, RedundancyService, Video, VideoDetails, VideoService, Actor } from '../shared-main'
8import { VideoAddToPlaylistComponent } from '../shared-video-playlist' 8import { VideoAddToPlaylistComponent } from '../shared-video-playlist'
9import { VideoDownloadComponent } from './video-download.component' 9import { 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