diff options
author | Chocobozzz <me@florianbigard.com> | 2021-10-21 10:19:42 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-10-21 10:19:42 +0200 |
commit | a2c3564a31268e4e60f05952aa3731e114ac1e30 (patch) | |
tree | f0bf29c8b304ec61ded60328f1a488338c2d4c8e | |
parent | 5196817c5d7cf86b35f3fa2cfe108ba283944482 (diff) | |
download | PeerTube-a2c3564a31268e4e60f05952aa3731e114ac1e30.tar.gz PeerTube-a2c3564a31268e4e60f05952aa3731e114ac1e30.tar.zst PeerTube-a2c3564a31268e4e60f05952aa3731e114ac1e30.zip |
Improve moderation dropdown UX
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 | } |