aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-10-21 10:19:42 +0200
committerChocobozzz <me@florianbigard.com>2021-10-21 10:19:42 +0200
commita2c3564a31268e4e60f05952aa3731e114ac1e30 (patch)
treef0bf29c8b304ec61ded60328f1a488338c2d4c8e
parent5196817c5d7cf86b35f3fa2cfe108ba283944482 (diff)
downloadPeerTube-a2c3564a31268e4e60f05952aa3731e114ac1e30.tar.gz
PeerTube-a2c3564a31268e4e60f05952aa3731e114ac1e30.tar.zst
PeerTube-a2c3564a31268e4e60f05952aa3731e114ac1e30.zip
Improve moderation dropdown UX
-rw-r--r--client/src/app/+accounts/accounts.component.ts10
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts7
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.html4
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.ts2
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts252
5 files changed, 154 insertions, 121 deletions
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts
index e90816c5a..3cb117fcc 100644
--- a/client/src/app/+accounts/accounts.component.ts
+++ b/client/src/app/+accounts/accounts.component.ts
@@ -36,7 +36,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
36 accountDescriptionHTML = '' 36 accountDescriptionHTML = ''
37 accountDescriptionExpanded = false 37 accountDescriptionExpanded = false
38 38
39 prependModerationActions: DropdownAction<any>[] 39 prependModerationActions: DropdownAction<any>[] = []
40 40
41 private routeSub: Subscription 41 private routeSub: Subscription
42 42
@@ -151,8 +151,6 @@ export class AccountsComponent implements OnInit, OnDestroy {
151 private async onAccount (account: Account) { 151 private async onAccount (account: Account) {
152 this.accountFollowerTitle = $localize`${account.followersCount} direct account followers` 152 this.accountFollowerTitle = $localize`${account.followersCount} direct account followers`
153 153
154 this.prependModerationActions = undefined
155
156 this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description) 154 this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description)
157 155
158 // After the markdown renderer to avoid layout changes 156 // After the markdown renderer to avoid layout changes
@@ -184,6 +182,8 @@ export class AccountsComponent implements OnInit, OnDestroy {
184 } 182 }
185 183
186 private updateModerationActions () { 184 private updateModerationActions () {
185 this.prependModerationActions = []
186
187 if (!this.authService.isLoggedIn()) return 187 if (!this.authService.isLoggedIn()) return
188 188
189 this.authService.userInformationLoaded.subscribe( 189 this.authService.userInformationLoaded.subscribe(
@@ -193,6 +193,10 @@ export class AccountsComponent implements OnInit, OnDestroy {
193 // It's not our account, we can report it 193 // It's not our account, we can report it
194 this.prependModerationActions = [ 194 this.prependModerationActions = [
195 { 195 {
196 label: $localize`Report`,
197 isHeader: true
198 },
199 {
196 label: $localize`Report this account`, 200 label: $localize`Report this account`,
197 handler: () => this.showReportModal() 201 handler: () => this.showReportModal()
198 } 202 }
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
index 5bbcffe82..cabea7551 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
@@ -197,8 +197,11 @@ export class VideoCommentComponent implements OnInit, OnChanges {
197 }) 197 })
198 } 198 }
199 199
200 if (this.prependModerationActions.length === 0) { 200 if (this.prependModerationActions.length !== 0) {
201 this.prependModerationActions = undefined 201 this.prependModerationActions.unshift({
202 label: $localize`Actions on comment`,
203 isHeader: true
204 })
202 } 205 }
203 } 206 }
204 207
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
index d0487f0b8..c465b6903 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
@@ -13,7 +13,7 @@
13 <ng-container *ngFor="let actions of getActions()"> 13 <ng-container *ngFor="let actions of getActions()">
14 14
15 <ng-container *ngFor="let action of actions"> 15 <ng-container *ngFor="let action of actions">
16 <ng-container *ngIf="action.isDisplayed === undefined || action.isDisplayed(entry) === true"> 16 <div [ngClass]="action.class" *ngIf="action.isDisplayed === undefined || action.isDisplayed(entry) === true">
17 17
18 <ng-template #templateActionLabel let-action> 18 <ng-template #templateActionLabel let-action>
19 <my-global-icon *ngIf="action.iconName" [iconName]="action.iconName" [ngClass]="'icon-' + action.iconName" aria-hidden="true"></my-global-icon> 19 <my-global-icon *ngIf="action.iconName" [iconName]="action.iconName" [ngClass]="'icon-' + action.iconName" aria-hidden="true"></my-global-icon>
@@ -45,7 +45,7 @@
45 <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container> 45 <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container>
46 </h6> 46 </h6>
47 47
48 </ng-container> 48 </div>
49 </ng-container> 49 </ng-container>
50 50
51 <div *ngIf="areActionsDisplayed(actions, entry)" class="dropdown-divider"></div> 51 <div *ngIf="areActionsDisplayed(actions, entry)" class="dropdown-divider"></div>
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
index 36d7d6229..67ac6e1aa 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
@@ -9,6 +9,8 @@ export type DropdownAction<T> = {
9 handler?: (a: T) => any 9 handler?: (a: T) => any
10 linkBuilder?: (a: T) => (string | number)[] 10 linkBuilder?: (a: T) => (string | number)[]
11 isDisplayed?: (a: T) => boolean 11 isDisplayed?: (a: T) => boolean
12
13 class?: string[]
12 isHeader?: boolean 14 isHeader?: boolean
13} 15}
14 16
diff --git a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
index 9f1e1bf9b..1c5240db4 100644
--- a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
@@ -241,139 +241,163 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
241 return [ '/admin', 'users', 'update', user.id ] 241 return [ '/admin', 'users', 'update', user.id ]
242 } 242 }
243 243
244 private isMyUser (user: User) {
245 return user && this.authService.getUser().id === user.id
246 }
247
248 private isMyAccount (account: Account) {
249 return account && this.authService.getUser().account.id === account.id
250 }
251
244 private buildActions () { 252 private buildActions () {
245 this.userActions = [] 253 this.userActions = []
246 254
247 if (this.prependActions) { 255 if (this.prependActions && this.prependActions.length !== 0) {
248 this.userActions = [ 256 this.userActions = [
249 this.prependActions 257 this.prependActions
250 ] 258 ]
251 } 259 }
252 260
253 if (this.authService.isLoggedIn()) { 261 const myAccountModerationActions = this.buildMyAccountModerationActions()
254 const authUser = this.authService.getUser() 262 const instanceModerationActions = this.buildInstanceModerationActions()
255 263
256 if (this.user && authUser.id === this.user.id) return 264 if (myAccountModerationActions.length !== 0) this.userActions.push(myAccountModerationActions)
265 if (instanceModerationActions.length !== 0) this.userActions.push(instanceModerationActions)
266 }
257 267
258 if (this.user && authUser.hasRight(UserRight.MANAGE_USERS) && authUser.canManage(this.user)) { 268 private buildMyAccountModerationActions () {
259 this.userActions.push([ 269 if (!this.account || !this.authService.isLoggedIn()) return []
260 { 270
261 label: $localize`Edit user`, 271 const myAccountActions: DropdownAction<{ user: User, account: Account }>[] = [
262 description: $localize`Change quota, role, and more.`, 272 {
263 linkBuilder: ({ user }) => this.getRouterUserEditLink(user) 273 label: $localize`Personal moderation`,
264 }, 274 class: [ 'red' ],
265 { 275 isHeader: true
266 label: $localize`Delete user`, 276 },
267 description: $localize`Videos will be deleted, comments will be tombstoned.`, 277 {
268 handler: ({ user }) => this.removeUser(user) 278 label: $localize`Mute this account`,
269 }, 279 description: $localize`Hide any content from that user from you.`,
270 { 280 isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByUser === false,
271 label: $localize`Ban`, 281 handler: ({ account }) => this.blockAccountByUser(account)
272 description: $localize`User won't be able to login anymore, but videos and comments will be kept as is.`, 282 },
273 handler: ({ user }) => this.openBanUserModal(user), 283 {
274 isDisplayed: ({ user }) => !user.blocked 284 label: $localize`Unmute this account`,
275 }, 285 description: $localize`Show back content from that user for you.`,
276 { 286 isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByUser === true,
277 label: $localize`Unban user`, 287 handler: ({ account }) => this.unblockAccountByUser(account)
278 description: $localize`Allow the user to login and create videos/comments again`, 288 },
279 handler: ({ user }) => this.unbanUser(user), 289 {
280 isDisplayed: ({ user }) => user.blocked 290 label: $localize`Mute the instance`,
281 }, 291 description: $localize`Hide any content from that instance for you.`,
282 { 292 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
283 label: $localize`Set Email as Verified`, 293 handler: ({ account }) => this.blockServerByUser(account.host)
284 handler: ({ user }) => this.setEmailAsVerified(user), 294 },
285 isDisplayed: ({ user }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false 295 {
286 } 296 label: $localize`Unmute the instance`,
287 ]) 297 description: $localize`Show back content from that instance for you.`,
298 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
299 handler: ({ account }) => this.unblockServerByUser(account.host)
300 },
301 {
302 label: $localize`Remove comments from your videos`,
303 description: $localize`Remove comments made by this account on your videos.`,
304 isDisplayed: ({ account }) => !this.isMyAccount(account),
305 handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'my-videos' })
288 } 306 }
307 ]
289 308
290 // Actions on accounts/servers 309 return myAccountActions
291 if (this.account) { 310 }
292 // User actions 311
293 this.userActions.push([ 312 private buildInstanceModerationActions () {
294 { 313 if (!this.authService.isLoggedIn()) return []
295 label: $localize`Mute this account`, 314
296 description: $localize`Hide any content from that user from you.`, 315 const authUser = this.authService.getUser()
297 isDisplayed: ({ account }) => account.mutedByUser === false, 316
298 handler: ({ account }) => this.blockAccountByUser(account) 317 let instanceActions: DropdownAction<{ user: User, account: Account }>[] = []
299 }, 318
300 { 319 if (this.user && authUser.hasRight(UserRight.MANAGE_USERS) && authUser.canManage(this.user)) {
301 label: $localize`Unmute this account`, 320 instanceActions = instanceActions.concat([
302 description: $localize`Show back content from that user for you.`, 321 {
303 isDisplayed: ({ account }) => account.mutedByUser === true, 322 label: $localize`Edit user`,
304 handler: ({ account }) => this.unblockAccountByUser(account) 323 description: $localize`Change quota, role, and more.`,
305 }, 324 linkBuilder: ({ user }) => this.getRouterUserEditLink(user)
306 { 325 },
307 label: $localize`Mute the instance`, 326 {
308 description: $localize`Hide any content from that instance for you.`, 327 label: $localize`Delete user`,
309 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false, 328 description: $localize`Videos will be deleted, comments will be tombstoned.`,
310 handler: ({ account }) => this.blockServerByUser(account.host) 329 isDisplayed: ({ user }) => !this.isMyUser(user),
311 }, 330 handler: ({ user }) => this.removeUser(user)
312 { 331 },
313 label: $localize`Unmute the instance`, 332 {
314 description: $localize`Show back content from that instance for you.`, 333 label: $localize`Ban`,
315 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, 334 description: $localize`User won't be able to login anymore, but videos and comments will be kept as is.`,
316 handler: ({ account }) => this.unblockServerByUser(account.host) 335 handler: ({ user }) => this.openBanUserModal(user),
317 }, 336 isDisplayed: ({ user }) => !this.isMyUser(user) && !user.blocked
318 { 337 },
319 label: $localize`Remove comments from your videos`, 338 {
320 description: $localize`Remove comments made by this account on your videos.`, 339 label: $localize`Unban user`,
321 handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'my-videos' }) 340 description: $localize`Allow the user to login and create videos/comments again`,
322 } 341 handler: ({ user }) => this.unbanUser(user),
323 ]) 342 isDisplayed: ({ user }) => !this.isMyUser(user) && user.blocked
324 343 },
325 let instanceActions: DropdownAction<{ user: User, account: Account }>[] = [] 344 {
326 345 label: $localize`Set Email as Verified`,
327 // Instance actions on account blocklists 346 handler: ({ user }) => this.setEmailAsVerified(user),
328 if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { 347 isDisplayed: ({ user }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false
329 instanceActions = instanceActions.concat([
330 {
331 label: $localize`Mute this account by your instance`,
332 description: $localize`Hide any content from that user from you, your instance and its users.`,
333 isDisplayed: ({ account }) => account.mutedByInstance === false,
334 handler: ({ account }) => this.blockAccountByInstance(account)
335 },
336 {
337 label: $localize`Unmute this account by your instance`,
338 description: $localize`Show this user's content to the users of this instance again.`,
339 isDisplayed: ({ account }) => account.mutedByInstance === true,
340 handler: ({ account }) => this.unblockAccountByInstance(account)
341 }
342 ])
343 } 348 }
349 ])
350 }
344 351
345 // Instance actions on server blocklists 352 // Instance actions on account blocklists
346 if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { 353 if (this.account && authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) {
347 instanceActions = instanceActions.concat([ 354 instanceActions = instanceActions.concat([
348 { 355 {
349 label: $localize`Mute the instance by your instance`, 356 label: $localize`Mute this account`,
350 description: $localize`Hide any content from that instance from you, your instance and its users.`, 357 description: $localize`Hide any content from that user from you, your instance and its users.`,
351 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false, 358 isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByInstance === false,
352 handler: ({ account }) => this.blockServerByInstance(account.host) 359 handler: ({ account }) => this.blockAccountByInstance(account)
353 }, 360 },
354 { 361 {
355 label: $localize`Unmute the instance by your instance`, 362 label: $localize`Unmute this account`,
356 description: $localize`Show back content from that instance for you, your instance and its users.`, 363 description: $localize`Show this user's content to the users of this instance again.`,
357 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, 364 isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByInstance === true,
358 handler: ({ account }) => this.unblockServerByInstance(account.host) 365 handler: ({ account }) => this.unblockAccountByInstance(account)
359 }
360 ])
361 } 366 }
367 ])
368 }
362 369
363 if (authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) { 370 // Instance actions on server blocklists
364 instanceActions = instanceActions.concat([ 371 if (this.account && authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) {
365 { 372 instanceActions = instanceActions.concat([
366 label: $localize`Remove comments from your instance`, 373 {
367 description: $localize`Remove comments made by this account from your instance.`, 374 label: $localize`Mute the instance`,
368 handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'instance' }) 375 description: $localize`Hide any content from that instance from you, your instance and its users.`,
369 } 376 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
370 ]) 377 handler: ({ account }) => this.blockServerByInstance(account.host)
378 },
379 {
380 label: $localize`Unmute the instance by your instance`,
381 description: $localize`Show back content from that instance for you, your instance and its users.`,
382 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
383 handler: ({ account }) => this.unblockServerByInstance(account.host)
371 } 384 }
385 ])
386 }
372 387
373 if (instanceActions.length !== 0) { 388 if (this.account && authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) {
374 this.userActions.push(instanceActions) 389 instanceActions = instanceActions.concat([
390 {
391 label: $localize`Remove comments from your instance`,
392 description: $localize`Remove comments made by this account from your instance.`,
393 isDisplayed: ({ account }) => !this.isMyAccount(account),
394 handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'instance' })
375 } 395 }
376 } 396 ])
377 } 397 }
398
399 if (instanceActions.length === 0) return []
400
401 return [ { label: $localize`Instance moderation`, isHeader: true }, ...instanceActions ]
378 } 402 }
379} 403}