aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-01-20 14:58:05 +0100
committerChocobozzz <me@florianbigard.com>2023-01-20 14:58:20 +0100
commitcd940f40cb62fa105b14e1ce838e97f316c13c5c (patch)
tree96c9e2473b2c6243537e287787528492349ff92b /client/src/app
parent9436936cf68b6c1225298b0e30c23c40f0d15bda (diff)
downloadPeerTube-cd940f40cb62fa105b14e1ce838e97f316c13c5c.tar.gz
PeerTube-cd940f40cb62fa105b14e1ce838e97f316c13c5c.tar.zst
PeerTube-cd940f40cb62fa105b14e1ce838e97f316c13c5c.zip
Support bulk registration request removal
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html4
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.ts17
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html4
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts11
-rw-r--r--client/src/app/+admin/moderation/registration-list/admin-registration.service.ts16
-rw-r--r--client/src/app/+admin/moderation/registration-list/registration-list.component.html19
-rw-r--r--client/src/app/+admin/moderation/registration-list/registration-list.component.ts46
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.html4
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.ts13
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.html4
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.ts15
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.html4
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts14
-rw-r--r--client/src/app/core/renderer/linkifier.service.ts2
-rw-r--r--client/src/app/core/rest/rest-table.ts8
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts2
-rw-r--r--client/src/app/shared/shared-users/user-admin.service.ts2
17 files changed, 107 insertions, 78 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index 8fe0d2348..14c62f1af 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -9,14 +9,14 @@
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" 9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers"
12 [(selection)]="selectedFollows" 12 [(selection)]="selectedRows"
13> 13>
14 <ng-template pTemplate="caption"> 14 <ng-template pTemplate="caption">
15 <div class="caption"> 15 <div class="caption">
16 <div class="left-buttons"> 16 <div class="left-buttons">
17 <my-action-dropdown 17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange" 18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkFollowsActions" [entry]="selectedFollows" 19 [actions]="bulkActions" [entry]="selectedRows"
20 > 20 >
21 </my-action-dropdown> 21 </my-action-dropdown>
22 </div> 22 </div>
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
index b2d333e83..6dd64fc57 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -12,7 +12,7 @@ import { ActorFollow } from '@shared/models'
12 templateUrl: './followers-list.component.html', 12 templateUrl: './followers-list.component.html',
13 styleUrls: [ './followers-list.component.scss' ] 13 styleUrls: [ './followers-list.component.scss' ]
14}) 14})
15export class FollowersListComponent extends RestTable implements OnInit { 15export class FollowersListComponent extends RestTable <ActorFollow> implements OnInit {
16 followers: ActorFollow[] = [] 16 followers: ActorFollow[] = []
17 totalRecords = 0 17 totalRecords = 0
18 sort: SortMeta = { field: 'createdAt', order: -1 } 18 sort: SortMeta = { field: 'createdAt', order: -1 }
@@ -20,8 +20,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
20 20
21 searchFilters: AdvancedInputFilter[] = [] 21 searchFilters: AdvancedInputFilter[] = []
22 22
23 selectedFollows: ActorFollow[] = [] 23 bulkActions: DropdownAction<ActorFollow[]>[] = []
24 bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
25 24
26 constructor ( 25 constructor (
27 private confirmService: ConfirmService, 26 private confirmService: ConfirmService,
@@ -36,7 +35,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
36 35
37 this.searchFilters = this.followService.buildFollowsListFilters() 36 this.searchFilters = this.followService.buildFollowsListFilters()
38 37
39 this.bulkFollowsActions = [ 38 this.bulkActions = [
40 { 39 {
41 label: $localize`Reject`, 40 label: $localize`Reject`,
42 handler: follows => this.rejectFollower(follows), 41 handler: follows => this.rejectFollower(follows),
@@ -105,12 +104,14 @@ export class FollowersListComponent extends RestTable implements OnInit {
105 } 104 }
106 105
107 async deleteFollowers (follows: ActorFollow[]) { 106 async deleteFollowers (follows: ActorFollow[]) {
107 const icuParams = { count: follows.length, followerName: this.buildFollowerName(follows[0]) }
108
108 let message = $localize`Deleted followers will be able to send again a follow request.` 109 let message = $localize`Deleted followers will be able to send again a follow request.`
109 message += '<br /><br />' 110 message += '<br /><br />'
110 111
111 // eslint-disable-next-line max-len 112 // eslint-disable-next-line max-len
112 message += prepareIcu($localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)( 113 message += prepareIcu($localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)(
113 { count: follows.length, followerName: this.buildFollowerName(follows[0]) }, 114 icuParams,
114 $localize`Do you really want to delete these follow requests?` 115 $localize`Do you really want to delete these follow requests?`
115 ) 116 )
116 117
@@ -122,7 +123,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
122 next: () => { 123 next: () => {
123 // eslint-disable-next-line max-len 124 // eslint-disable-next-line max-len
124 const message = prepareIcu($localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)( 125 const message = prepareIcu($localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
125 { count: follows.length, followerName: this.buildFollowerName(follows[0]) }, 126 icuParams,
126 $localize`Follow requests removed` 127 $localize`Follow requests removed`
127 ) 128 )
128 129
@@ -139,10 +140,6 @@ export class FollowersListComponent extends RestTable implements OnInit {
139 return follow.follower.name + '@' + follow.follower.host 140 return follow.follower.name + '@' + follow.follower.host
140 } 141 }
141 142
142 isInSelectionMode () {
143 return this.selectedFollows.length !== 0
144 }
145
146 protected reloadData () { 143 protected reloadData () {
147 this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search }) 144 this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search })
148 .subscribe({ 145 .subscribe({
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index f7abb7ede..eca79be71 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -9,14 +9,14 @@
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" 9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts"
12 [(selection)]="selectedFollows" 12 [(selection)]="selectedRows"
13> 13>
14 <ng-template pTemplate="caption"> 14 <ng-template pTemplate="caption">
15 <div class="caption"> 15 <div class="caption">
16 <div class="left-buttons"> 16 <div class="left-buttons">
17 <my-action-dropdown 17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange" 18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkFollowsActions" [entry]="selectedFollows" 19 [actions]="bulkActions" [entry]="selectedRows"
20 > 20 >
21 </my-action-dropdown> 21 </my-action-dropdown>
22 22
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index e3a56651a..e986a610a 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -12,7 +12,7 @@ import { prepareIcu } from '@app/helpers'
12 templateUrl: './following-list.component.html', 12 templateUrl: './following-list.component.html',
13 styleUrls: [ './following-list.component.scss' ] 13 styleUrls: [ './following-list.component.scss' ]
14}) 14})
15export class FollowingListComponent extends RestTable implements OnInit { 15export class FollowingListComponent extends RestTable <ActorFollow> implements OnInit {
16 @ViewChild('followModal') followModal: FollowModalComponent 16 @ViewChild('followModal') followModal: FollowModalComponent
17 17
18 following: ActorFollow[] = [] 18 following: ActorFollow[] = []
@@ -22,8 +22,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
22 22
23 searchFilters: AdvancedInputFilter[] = [] 23 searchFilters: AdvancedInputFilter[] = []
24 24
25 selectedFollows: ActorFollow[] = [] 25 bulkActions: DropdownAction<ActorFollow[]>[] = []
26 bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
27 26
28 constructor ( 27 constructor (
29 private notifier: Notifier, 28 private notifier: Notifier,
@@ -38,7 +37,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
38 37
39 this.searchFilters = this.followService.buildFollowsListFilters() 38 this.searchFilters = this.followService.buildFollowsListFilters()
40 39
41 this.bulkFollowsActions = [ 40 this.bulkActions = [
42 { 41 {
43 label: $localize`Delete`, 42 label: $localize`Delete`,
44 handler: follows => this.removeFollowing(follows) 43 handler: follows => this.removeFollowing(follows)
@@ -58,10 +57,6 @@ export class FollowingListComponent extends RestTable implements OnInit {
58 return follow.following.name === 'peertube' 57 return follow.following.name === 'peertube'
59 } 58 }
60 59
61 isInSelectionMode () {
62 return this.selectedFollows.length !== 0
63 }
64
65 buildFollowingName (follow: ActorFollow) { 60 buildFollowingName (follow: ActorFollow) {
66 return follow.following.name + '@' + follow.following.host 61 return follow.following.name + '@' + follow.following.host
67 } 62 }
diff --git a/client/src/app/+admin/moderation/registration-list/admin-registration.service.ts b/client/src/app/+admin/moderation/registration-list/admin-registration.service.ts
index 012f942b3..10e2938c7 100644
--- a/client/src/app/+admin/moderation/registration-list/admin-registration.service.ts
+++ b/client/src/app/+admin/moderation/registration-list/admin-registration.service.ts
@@ -1,8 +1,10 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { catchError } from 'rxjs/operators' 2import { from } from 'rxjs'
3import { catchError, concatMap, toArray } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
5import { RestExtractor, RestPagination, RestService } from '@app/core' 6import { RestExtractor, RestPagination, RestService } from '@app/core'
7import { arrayify } from '@shared/core-utils'
6import { ResultList, UserRegistration } from '@shared/models' 8import { ResultList, UserRegistration } from '@shared/models'
7import { environment } from '../../../../environments/environment' 9import { environment } from '../../../../environments/environment'
8 10
@@ -54,10 +56,14 @@ export class AdminRegistrationService {
54 .pipe(catchError(res => this.restExtractor.handleError(res))) 56 .pipe(catchError(res => this.restExtractor.handleError(res)))
55 } 57 }
56 58
57 removeRegistration (registration: UserRegistration) { 59 removeRegistrations (registrationsArg: UserRegistration | UserRegistration[]) {
58 const url = AdminRegistrationService.BASE_REGISTRATION_URL + '/' + registration.id 60 const registrations = arrayify(registrationsArg)
59 61
60 return this.authHttp.delete(url) 62 return from(registrations)
61 .pipe(catchError(res => this.restExtractor.handleError(res))) 63 .pipe(
64 concatMap(r => this.authHttp.delete(AdminRegistrationService.BASE_REGISTRATION_URL + '/' + r.id)),
65 toArray(),
66 catchError(err => this.restExtractor.handleError(err))
67 )
62 } 68 }
63} 69}
diff --git a/client/src/app/+admin/moderation/registration-list/registration-list.component.html b/client/src/app/+admin/moderation/registration-list/registration-list.component.html
index 4f9d06acc..a2b888101 100644
--- a/client/src/app/+admin/moderation/registration-list/registration-list.component.html
+++ b/client/src/app/+admin/moderation/registration-list/registration-list.component.html
@@ -7,12 +7,20 @@
7 [value]="registrations" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start" 7 [value]="registrations" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
8 [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" 8 [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" 9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [(selection)]="selectedRows" [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} registrations" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} registrations"
12 [expandedRowKeys]="expandedRows" 12 [expandedRowKeys]="expandedRows"
13> 13>
14 <ng-template pTemplate="caption"> 14 <ng-template pTemplate="caption">
15 <div class="caption"> 15 <div class="caption">
16 <div class="left-buttons">
17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkActions" [entry]="selectedRows"
20 >
21 </my-action-dropdown>
22 </div>
23
16 <div class="ms-auto"> 24 <div class="ms-auto">
17 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> 25 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
18 </div> 26 </div>
@@ -21,6 +29,9 @@
21 29
22 <ng-template pTemplate="header"> 30 <ng-template pTemplate="header">
23 <tr> <!-- header --> 31 <tr> <!-- header -->
32 <th style="width: 40px">
33 <p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
34 </th>
24 <th style="width: 40px;"></th> 35 <th style="width: 40px;"></th>
25 <th style="width: 150px;"></th> 36 <th style="width: 150px;"></th>
26 <th i18n>Account</th> 37 <th i18n>Account</th>
@@ -34,7 +45,11 @@
34 </ng-template> 45 </ng-template>
35 46
36 <ng-template pTemplate="body" let-expanded="expanded" let-registration> 47 <ng-template pTemplate="body" let-expanded="expanded" let-registration>
37 <tr> 48 <tr [pSelectableRow]="registration">
49 <td class="checkbox-cell">
50 <p-tableCheckbox [value]="registration" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
51 </td>
52
38 <td class="expand-cell" [pRowToggler]="registration"> 53 <td class="expand-cell" [pRowToggler]="registration">
39 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon> 54 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
40 </td> 55 </td>
diff --git a/client/src/app/+admin/moderation/registration-list/registration-list.component.ts b/client/src/app/+admin/moderation/registration-list/registration-list.component.ts
index 37514edf5..569eccaa2 100644
--- a/client/src/app/+admin/moderation/registration-list/registration-list.component.ts
+++ b/client/src/app/+admin/moderation/registration-list/registration-list.component.ts
@@ -1,7 +1,8 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 4import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
5import { prepareIcu } from '@app/helpers'
5import { AdvancedInputFilter } from '@app/shared/shared-forms' 6import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main' 7import { DropdownAction } from '@app/shared/shared-main'
7import { UserRegistration, UserRegistrationState } from '@shared/models' 8import { UserRegistration, UserRegistrationState } from '@shared/models'
@@ -13,7 +14,7 @@ import { ProcessRegistrationModalComponent } from './process-registration-modal.
13 templateUrl: './registration-list.component.html', 14 templateUrl: './registration-list.component.html',
14 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './registration-list.component.scss' ] 15 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './registration-list.component.scss' ]
15}) 16})
16export class RegistrationListComponent extends RestTable implements OnInit { 17export class RegistrationListComponent extends RestTable <UserRegistration> implements OnInit {
17 @ViewChild('processRegistrationModal', { static: true }) processRegistrationModal: ProcessRegistrationModalComponent 18 @ViewChild('processRegistrationModal', { static: true }) processRegistrationModal: ProcessRegistrationModalComponent
18 19
19 registrations: (UserRegistration & { registrationReasonHTML?: string, moderationResponseHTML?: string })[] = [] 20 registrations: (UserRegistration & { registrationReasonHTML?: string, moderationResponseHTML?: string })[] = []
@@ -22,6 +23,7 @@ export class RegistrationListComponent extends RestTable implements OnInit {
22 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 23 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
23 24
24 registrationActions: DropdownAction<UserRegistration>[][] = [] 25 registrationActions: DropdownAction<UserRegistration>[][] = []
26 bulkActions: DropdownAction<UserRegistration[]>[] = []
25 27
26 inputFilters: AdvancedInputFilter[] = [] 28 inputFilters: AdvancedInputFilter[] = []
27 29
@@ -33,6 +35,7 @@ export class RegistrationListComponent extends RestTable implements OnInit {
33 private server: ServerService, 35 private server: ServerService,
34 private notifier: Notifier, 36 private notifier: Notifier,
35 private markdownRenderer: MarkdownService, 37 private markdownRenderer: MarkdownService,
38 private confirmService: ConfirmService,
36 private adminRegistrationService: AdminRegistrationService 39 private adminRegistrationService: AdminRegistrationService
37 ) { 40 ) {
38 super() 41 super()
@@ -40,22 +43,28 @@ export class RegistrationListComponent extends RestTable implements OnInit {
40 this.registrationActions = [ 43 this.registrationActions = [
41 [ 44 [
42 { 45 {
43 label: $localize`Accept this registration`, 46 label: $localize`Accept this request`,
44 handler: registration => this.openRegistrationRequestProcessModal(registration, 'accept'), 47 handler: registration => this.openRegistrationRequestProcessModal(registration, 'accept'),
45 isDisplayed: registration => registration.state.id === UserRegistrationState.PENDING 48 isDisplayed: registration => registration.state.id === UserRegistrationState.PENDING
46 }, 49 },
47 { 50 {
48 label: $localize`Reject this registration`, 51 label: $localize`Reject this request`,
49 handler: registration => this.openRegistrationRequestProcessModal(registration, 'reject'), 52 handler: registration => this.openRegistrationRequestProcessModal(registration, 'reject'),
50 isDisplayed: registration => registration.state.id === UserRegistrationState.PENDING 53 isDisplayed: registration => registration.state.id === UserRegistrationState.PENDING
51 }, 54 },
52 { 55 {
53 label: $localize`Remove this registration request`, 56 label: $localize`Remove this request`,
54 handler: registration => this.removeRegistration(registration), 57 handler: registration => this.removeRegistrations([ registration ])
55 isDisplayed: registration => registration.state.id !== UserRegistrationState.PENDING
56 } 58 }
57 ] 59 ]
58 ] 60 ]
61
62 this.bulkActions = [
63 {
64 label: $localize`Delete`,
65 handler: registrations => this.removeRegistrations(registrations)
66 }
67 ]
59 } 68 }
60 69
61 ngOnInit () { 70 ngOnInit () {
@@ -107,11 +116,28 @@ export class RegistrationListComponent extends RestTable implements OnInit {
107 this.processRegistrationModal.openModal(registration, mode) 116 this.processRegistrationModal.openModal(registration, mode)
108 } 117 }
109 118
110 private removeRegistration (registration: UserRegistration) { 119 private async removeRegistrations (registrations: UserRegistration[]) {
111 this.adminRegistrationService.removeRegistration(registration) 120 const icuParams = { count: registrations.length, username: registrations[0].username }
121
122 // eslint-disable-next-line max-len
123 const message = prepareIcu($localize`Do you really want to delete {count, plural, =1 {{username} registration request?} other {{count} registration requests?}}`)(
124 icuParams,
125 $localize`Do you really want to delete these registration requests?`
126 )
127
128 const res = await this.confirmService.confirm(message, $localize`Delete`)
129 if (res === false) return
130
131 this.adminRegistrationService.removeRegistrations(registrations)
112 .subscribe({ 132 .subscribe({
113 next: () => { 133 next: () => {
114 this.notifier.success($localize`Registration request deleted.`) 134 // eslint-disable-next-line max-len
135 const message = prepareIcu($localize`Removed {count, plural, =1 {{username} registration request} other {{count} registration requests}}`)(
136 icuParams,
137 $localize`Registration requests removed`
138 )
139
140 this.notifier.success(message)
115 this.reloadData() 141 this.reloadData()
116 }, 142 },
117 143
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.html b/client/src/app/+admin/overview/comments/video-comment-list.component.html
index d2ca5f700..b0d8131bf 100644
--- a/client/src/app/+admin/overview/comments/video-comment-list.component.html
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.html
@@ -13,14 +13,14 @@
13 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" 13 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
14 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 14 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
15 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments" 15 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments"
16 [expandedRowKeys]="expandedRows" [(selection)]="selectedComments" 16 [expandedRowKeys]="expandedRows" [(selection)]="selectedRows"
17> 17>
18 <ng-template pTemplate="caption"> 18 <ng-template pTemplate="caption">
19 <div class="caption"> 19 <div class="caption">
20 <div> 20 <div>
21 <my-action-dropdown 21 <my-action-dropdown
22 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange" 22 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
23 [actions]="bulkCommentActions" [entry]="selectedComments" 23 [actions]="bulkActions" [entry]="selectedRows"
24 > 24 >
25 </my-action-dropdown> 25 </my-action-dropdown>
26 </div> 26 </div>
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.ts b/client/src/app/+admin/overview/comments/video-comment-list.component.ts
index c95d2ffeb..cfa68ed9d 100644
--- a/client/src/app/+admin/overview/comments/video-comment-list.component.ts
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.ts
@@ -14,7 +14,7 @@ import { prepareIcu } from '@app/helpers'
14 templateUrl: './video-comment-list.component.html', 14 templateUrl: './video-comment-list.component.html',
15 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ] 15 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ]
16}) 16})
17export class VideoCommentListComponent extends RestTable implements OnInit { 17export class VideoCommentListComponent extends RestTable <VideoCommentAdmin> implements OnInit {
18 comments: VideoCommentAdmin[] 18 comments: VideoCommentAdmin[]
19 totalRecords = 0 19 totalRecords = 0
20 sort: SortMeta = { field: 'createdAt', order: -1 } 20 sort: SortMeta = { field: 'createdAt', order: -1 }
@@ -40,8 +40,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
40 } 40 }
41 ] 41 ]
42 42
43 selectedComments: VideoCommentAdmin[] = [] 43 bulkActions: DropdownAction<VideoCommentAdmin[]>[] = []
44 bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = []
45 44
46 inputFilters: AdvancedInputFilter[] = [ 45 inputFilters: AdvancedInputFilter[] = [
47 { 46 {
@@ -100,7 +99,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
100 ngOnInit () { 99 ngOnInit () {
101 this.initialize() 100 this.initialize()
102 101
103 this.bulkCommentActions = [ 102 this.bulkActions = [
104 { 103 {
105 label: $localize`Delete`, 104 label: $localize`Delete`,
106 handler: comments => this.removeComments(comments), 105 handler: comments => this.removeComments(comments),
@@ -118,10 +117,6 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
118 return this.markdownRenderer.textMarkdownToHTML({ markdown: text, withHtml: true, withEmoji: true }) 117 return this.markdownRenderer.textMarkdownToHTML({ markdown: text, withHtml: true, withEmoji: true })
119 } 118 }
120 119
121 isInSelectionMode () {
122 return this.selectedComments.length !== 0
123 }
124
125 reloadData () { 120 reloadData () {
126 this.videoCommentService.getAdminVideoComments({ 121 this.videoCommentService.getAdminVideoComments({
127 pagination: this.pagination, 122 pagination: this.pagination,
@@ -162,7 +157,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
162 157
163 error: err => this.notifier.error(err.message), 158 error: err => this.notifier.error(err.message),
164 159
165 complete: () => this.selectedComments = [] 160 complete: () => this.selectedRows = []
166 }) 161 })
167 } 162 }
168 163
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html
index 5e5ac368c..7eb5e0fc7 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html
@@ -6,7 +6,7 @@
6<p-table 6<p-table
7 [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start" 7 [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
8 [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" 8 [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
9 [(selection)]="selectedUsers" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" 9 [(selection)]="selectedRows" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
12 [expandedRowKeys]="expandedRows" 12 [expandedRowKeys]="expandedRows"
@@ -16,7 +16,7 @@
16 <div class="left-buttons"> 16 <div class="left-buttons">
17 <my-action-dropdown 17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange" 18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkUserActions" [entry]="selectedUsers" 19 [actions]="bulkActions" [entry]="selectedRows"
20 > 20 >
21 </my-action-dropdown> 21 </my-action-dropdown>
22 22
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
index 99987fdff..7d62302f8 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
@@ -22,7 +22,7 @@ type UserForList = User & {
22 templateUrl: './user-list.component.html', 22 templateUrl: './user-list.component.html',
23 styleUrls: [ './user-list.component.scss' ] 23 styleUrls: [ './user-list.component.scss' ]
24}) 24})
25export class UserListComponent extends RestTable implements OnInit { 25export class UserListComponent extends RestTable <User> implements OnInit {
26 private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns' 26 private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
27 27
28 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent 28 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
@@ -35,8 +35,7 @@ export class UserListComponent extends RestTable implements OnInit {
35 35
36 highlightBannedUsers = false 36 highlightBannedUsers = false
37 37
38 selectedUsers: User[] = [] 38 bulkActions: DropdownAction<User[]>[][] = []
39 bulkUserActions: DropdownAction<User[]>[][] = []
40 columns: { id: string, label: string }[] 39 columns: { id: string, label: string }[]
41 40
42 inputFilters: AdvancedInputFilter[] = [ 41 inputFilters: AdvancedInputFilter[] = [
@@ -95,7 +94,7 @@ export class UserListComponent extends RestTable implements OnInit {
95 94
96 this.initialize() 95 this.initialize()
97 96
98 this.bulkUserActions = [ 97 this.bulkActions = [
99 [ 98 [
100 { 99 {
101 label: $localize`Delete`, 100 label: $localize`Delete`,
@@ -249,7 +248,7 @@ export class UserListComponent extends RestTable implements OnInit {
249 const res = await this.confirmService.confirm(message, $localize`Delete`) 248 const res = await this.confirmService.confirm(message, $localize`Delete`)
250 if (res === false) return 249 if (res === false) return
251 250
252 this.userAdminService.removeUser(users) 251 this.userAdminService.removeUsers(users)
253 .subscribe({ 252 .subscribe({
254 next: () => { 253 next: () => {
255 this.notifier.success( 254 this.notifier.success(
@@ -284,12 +283,8 @@ export class UserListComponent extends RestTable implements OnInit {
284 }) 283 })
285 } 284 }
286 285
287 isInSelectionMode () {
288 return this.selectedUsers.length !== 0
289 }
290
291 protected reloadData () { 286 protected reloadData () {
292 this.selectedUsers = [] 287 this.selectedRows = []
293 288
294 this.userAdminService.getUsers({ 289 this.userAdminService.getUsers({
295 pagination: this.pagination, 290 pagination: this.pagination,
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html
index a6cd2e257..5b8405ad9 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.html
+++ b/client/src/app/+admin/overview/videos/video-list.component.html
@@ -6,7 +6,7 @@
6<p-table 6<p-table
7 [value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start" 7 [value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
8 [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" 8 [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
9 [(selection)]="selectedVideos" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" 9 [(selection)]="selectedRows" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos"
12 [expandedRowKeys]="expandedRows" [ngClass]="{ loading: loading }" 12 [expandedRowKeys]="expandedRows" [ngClass]="{ loading: loading }"
@@ -16,7 +16,7 @@
16 <div class="left-buttons"> 16 <div class="left-buttons">
17 <my-action-dropdown 17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange" 18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkVideoActions" [entry]="selectedVideos" 19 [actions]="bulkActions" [entry]="selectedRows"
20 > 20 >
21 </my-action-dropdown> 21 </my-action-dropdown>
22 </div> 22 </div>
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
index 4d3e9873c..cd5d2eb34 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.ts
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -17,7 +17,7 @@ import { VideoAdminService } from './video-admin.service'
17 templateUrl: './video-list.component.html', 17 templateUrl: './video-list.component.html',
18 styleUrls: [ './video-list.component.scss' ] 18 styleUrls: [ './video-list.component.scss' ]
19}) 19})
20export class VideoListComponent extends RestTable implements OnInit { 20export class VideoListComponent extends RestTable <Video> implements OnInit {
21 @ViewChild('videoBlockModal') videoBlockModal: VideoBlockComponent 21 @ViewChild('videoBlockModal') videoBlockModal: VideoBlockComponent
22 22
23 videos: Video[] = [] 23 videos: Video[] = []
@@ -26,9 +26,7 @@ export class VideoListComponent extends RestTable implements OnInit {
26 sort: SortMeta = { field: 'publishedAt', order: -1 } 26 sort: SortMeta = { field: 'publishedAt', order: -1 }
27 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 27 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
28 28
29 bulkVideoActions: DropdownAction<Video[]>[][] = [] 29 bulkActions: DropdownAction<Video[]>[][] = []
30
31 selectedVideos: Video[] = []
32 30
33 inputFilters: AdvancedInputFilter[] 31 inputFilters: AdvancedInputFilter[]
34 32
@@ -72,7 +70,7 @@ export class VideoListComponent extends RestTable implements OnInit {
72 70
73 this.inputFilters = this.videoAdminService.buildAdminInputFilter() 71 this.inputFilters = this.videoAdminService.buildAdminInputFilter()
74 72
75 this.bulkVideoActions = [ 73 this.bulkActions = [
76 [ 74 [
77 { 75 {
78 label: $localize`Delete`, 76 label: $localize`Delete`,
@@ -126,10 +124,6 @@ export class VideoListComponent extends RestTable implements OnInit {
126 return 'VideoListComponent' 124 return 'VideoListComponent'
127 } 125 }
128 126
129 isInSelectionMode () {
130 return this.selectedVideos.length !== 0
131 }
132
133 getPrivacyBadgeClass (video: Video) { 127 getPrivacyBadgeClass (video: Video) {
134 if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green' 128 if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green'
135 129
@@ -190,7 +184,7 @@ export class VideoListComponent extends RestTable implements OnInit {
190 } 184 }
191 185
192 reloadData () { 186 reloadData () {
193 this.selectedVideos = [] 187 this.selectedRows = []
194 188
195 this.loading = true 189 this.loading = true
196 190
diff --git a/client/src/app/core/renderer/linkifier.service.ts b/client/src/app/core/renderer/linkifier.service.ts
index 78df92cc9..d99591d6c 100644
--- a/client/src/app/core/renderer/linkifier.service.ts
+++ b/client/src/app/core/renderer/linkifier.service.ts
@@ -15,7 +15,7 @@ export class LinkifierService {
15 }, 15 },
16 formatHref: { 16 formatHref: {
17 mention: (href: string) => { 17 mention: (href: string) => {
18 return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + href.substr(1) 18 return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + href.substring(1)
19 } 19 }
20 } 20 }
21 } 21 }
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts
index ec5646b5d..cfaa16af9 100644
--- a/client/src/app/core/rest/rest-table.ts
+++ b/client/src/app/core/rest/rest-table.ts
@@ -7,7 +7,7 @@ import { RestPagination } from './rest-pagination'
7 7
8const debugLogger = debug('peertube:tables:RestTable') 8const debugLogger = debug('peertube:tables:RestTable')
9 9
10export abstract class RestTable { 10export abstract class RestTable <T = unknown> {
11 11
12 abstract totalRecords: number 12 abstract totalRecords: number
13 abstract sort: SortMeta 13 abstract sort: SortMeta
@@ -17,6 +17,8 @@ export abstract class RestTable {
17 rowsPerPage = this.rowsPerPageOptions[0] 17 rowsPerPage = this.rowsPerPageOptions[0]
18 expandedRows = {} 18 expandedRows = {}
19 19
20 selectedRows: T[] = []
21
20 search: string 22 search: string
21 23
22 protected route: ActivatedRoute 24 protected route: ActivatedRoute
@@ -75,6 +77,10 @@ export abstract class RestTable {
75 this.reloadData() 77 this.reloadData()
76 } 78 }
77 79
80 isInSelectionMode () {
81 return this.selectedRows.length !== 0
82 }
83
78 protected abstract reloadData (): void 84 protected abstract reloadData (): void
79 85
80 private getSortLocalStorageKey () { 86 private getSortLocalStorageKey () {
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 c69a45c25..50dccf862 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
@@ -105,7 +105,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
105 const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`) 105 const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`)
106 if (res === false) return 106 if (res === false) return
107 107
108 this.userAdminService.removeUser(user) 108 this.userAdminService.removeUsers(user)
109 .subscribe({ 109 .subscribe({
110 next: () => { 110 next: () => {
111 this.notifier.success($localize`User ${user.username} deleted.`) 111 this.notifier.success($localize`User ${user.username} deleted.`)
diff --git a/client/src/app/shared/shared-users/user-admin.service.ts b/client/src/app/shared/shared-users/user-admin.service.ts
index 0b04023a3..6224f0bd5 100644
--- a/client/src/app/shared/shared-users/user-admin.service.ts
+++ b/client/src/app/shared/shared-users/user-admin.service.ts
@@ -64,7 +64,7 @@ export class UserAdminService {
64 ) 64 )
65 } 65 }
66 66
67 removeUser (usersArg: UserServerModel | UserServerModel[]) { 67 removeUsers (usersArg: UserServerModel | UserServerModel[]) {
68 const users = arrayify(usersArg) 68 const users = arrayify(usersArg)
69 69
70 return from(users) 70 return from(users)