aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/shared/bulk/bulk.service.ts24
-rw-r--r--client/src/app/shared/moderation/user-moderation-dropdown.component.ts38
-rw-r--r--client/src/app/shared/shared.module.ts145
-rw-r--r--shared/models/bulk/index.ts1
-rw-r--r--shared/models/index.ts1
5 files changed, 134 insertions, 75 deletions
diff --git a/client/src/app/shared/bulk/bulk.service.ts b/client/src/app/shared/bulk/bulk.service.ts
new file mode 100644
index 000000000..b00db31ec
--- /dev/null
+++ b/client/src/app/shared/bulk/bulk.service.ts
@@ -0,0 +1,24 @@
1import { HttpClient } from '@angular/common/http'
2import { Injectable } from '@angular/core'
3import { environment } from '../../../environments/environment'
4import { RestExtractor, RestService } from '../rest'
5import { BulkRemoveCommentsOfBody } from '../../../../../shared'
6import { catchError } from 'rxjs/operators'
7
8@Injectable()
9export class BulkService {
10 static BASE_BULK_URL = environment.apiUrl + '/api/v1/bulk'
11
12 constructor (
13 private authHttp: HttpClient,
14 private restExtractor: RestExtractor,
15 private restService: RestService
16 ) { }
17
18 removeCommentsOf (body: BulkRemoveCommentsOfBody) {
19 const url = BulkService.BASE_BULK_URL + '/remove-comments-of'
20
21 return this.authHttp.post(url, body)
22 .pipe(catchError(err => this.restExtractor.handleError(err)))
23 }
24}
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
index f8ad7ce13..82f39050e 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
@@ -7,7 +7,8 @@ import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
7import { User, UserRight } from '../../../../../shared/models/users' 7import { User, UserRight } from '../../../../../shared/models/users'
8import { Account } from '@app/shared/account/account.model' 8import { Account } from '@app/shared/account/account.model'
9import { BlocklistService } from '@app/shared/blocklist' 9import { BlocklistService } from '@app/shared/blocklist'
10import { ServerConfig } from '@shared/models' 10import { ServerConfig, BulkRemoveCommentsOfBody } from '@shared/models'
11import { BulkService } from '../bulk/bulk.service'
11 12
12@Component({ 13@Component({
13 selector: 'my-user-moderation-dropdown', 14 selector: 'my-user-moderation-dropdown',
@@ -38,6 +39,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
38 private serverService: ServerService, 39 private serverService: ServerService,
39 private userService: UserService, 40 private userService: UserService,
40 private blocklistService: BlocklistService, 41 private blocklistService: BlocklistService,
42 private bulkService: BulkService,
41 private i18n: I18n 43 private i18n: I18n
42 ) { } 44 ) { }
43 45
@@ -229,6 +231,21 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
229 ) 231 )
230 } 232 }
231 233
234 async bulkRemoveCommentsOf (body: BulkRemoveCommentsOfBody) {
235 const message = this.i18n('Are you sure you want to remove all the comments of this account?')
236 const res = await this.confirmService.confirm(message, this.i18n('Delete account comments'))
237 if (res === false) return
238
239 this.bulkService.removeCommentsOf(body)
240 .subscribe(
241 () => {
242 this.notifier.success(this.i18n('Will remove comments of this account (may take several minutes).'))
243 },
244
245 err => this.notifier.error(err.message)
246 )
247 }
248
232 getRouterUserEditLink (user: User) { 249 getRouterUserEditLink (user: User) {
233 return [ '/admin', 'users', 'update', user.id ] 250 return [ '/admin', 'users', 'update', user.id ]
234 } 251 }
@@ -300,12 +317,17 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
300 description: this.i18n('Show back content from that instance for you.'), 317 description: this.i18n('Show back content from that instance for you.'),
301 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, 318 isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
302 handler: ({ account }) => this.unblockServerByUser(account.host) 319 handler: ({ account }) => this.unblockServerByUser(account.host)
320 },
321 {
322 label: this.i18n('Remove comments from your videos'),
323 description: this.i18n('Remove comments of this account from your videos.'),
324 handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'my-videos' })
303 } 325 }
304 ]) 326 ])
305 327
306 let instanceActions: DropdownAction<{ user: User, account: Account }>[] = [] 328 let instanceActions: DropdownAction<{ user: User, account: Account }>[] = []
307 329
308 // Instance actions 330 // Instance actions on account blocklists
309 if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { 331 if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) {
310 instanceActions = instanceActions.concat([ 332 instanceActions = instanceActions.concat([
311 { 333 {
@@ -323,7 +345,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
323 ]) 345 ])
324 } 346 }
325 347
326 // Instance actions 348 // Instance actions on server blocklists
327 if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { 349 if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) {
328 instanceActions = instanceActions.concat([ 350 instanceActions = instanceActions.concat([
329 { 351 {
@@ -341,6 +363,16 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
341 ]) 363 ])
342 } 364 }
343 365
366 if (authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) {
367 instanceActions = instanceActions.concat([
368 {
369 label: this.i18n('Remove comments from your instance'),
370 description: this.i18n('Remove comments of this account from your instance.'),
371 handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'instance' })
372 }
373 ])
374 }
375
344 if (instanceActions.length !== 0) { 376 if (instanceActions.length !== 0) {
345 this.userActions.push(instanceActions) 377 this.userActions.push(instanceActions)
346 } 378 }
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 01735c187..813f76672 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -1,32 +1,30 @@
1import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
2import { SharedModule as PrimeSharedModule } from 'primeng/api'
3import { InputMaskModule } from 'primeng/inputmask'
4import { InputSwitchModule } from 'primeng/inputswitch'
5import { MultiSelectModule } from 'primeng/multiselect'
6import { ClipboardModule } from '@angular/cdk/clipboard'
1import { CommonModule } from '@angular/common' 7import { CommonModule } from '@angular/common'
2import { HttpClientModule } from '@angular/common/http' 8import { HttpClientModule } from '@angular/common/http'
3import { NgModule } from '@angular/core' 9import { NgModule } from '@angular/core'
4import { FormsModule, ReactiveFormsModule } from '@angular/forms' 10import { FormsModule, ReactiveFormsModule } from '@angular/forms'
5import { RouterModule } from '@angular/router' 11import { RouterModule } from '@angular/router'
6import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' 12import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service'
7import { HelpComponent } from '@app/shared/misc/help.component' 13import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
8import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' 14import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
9import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' 15import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
10import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
11import { SharedModule as PrimeSharedModule } from 'primeng/api'
12import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
13import { ButtonComponent } from './buttons/button.component'
14import { DeleteButtonComponent } from './buttons/delete-button.component'
15import { EditButtonComponent } from './buttons/edit-button.component'
16import { LoaderComponent } from './misc/loader.component'
17import { RestExtractor, RestService } from './rest'
18import { UserService } from './users'
19import { VideoAbuseService } from './video-abuse'
20import { VideoBlacklistService } from './video-blacklist'
21import { VideoOwnershipService } from './video-ownership'
22import { VideoMiniatureComponent } from './video/video-miniature.component'
23import { FeedComponent } from './video/feed.component'
24import { VideoThumbnailComponent } from './video/video-thumbnail.component'
25import { VideoService } from './video/video.service'
26import { AccountService } from '@app/shared/account/account.service' 16import { AccountService } from '@app/shared/account/account.service'
27import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' 17import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
28import { I18n } from '@ngx-translate/i18n-polyfill' 18import { HighlightPipe } from '@app/shared/angular/highlight.pipe'
29import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 19import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe'
20import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
21import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
22import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe'
23import { BlocklistService } from '@app/shared/blocklist'
24import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
25import { AvatarComponent } from '@app/shared/channel/avatar.component'
26import { ConfirmComponent } from '@app/shared/confirm/confirm.component'
27import { DateToggleComponent } from '@app/shared/date/date-toggle.component'
30import { 28import {
31 CustomConfigValidatorsService, 29 CustomConfigValidatorsService,
32 InstanceValidatorsService, 30 InstanceValidatorsService,
@@ -44,70 +42,72 @@ import {
44 VideoPlaylistValidatorsService, 42 VideoPlaylistValidatorsService,
45 VideoValidatorsService 43 VideoValidatorsService
46} from '@app/shared/forms' 44} from '@app/shared/forms'
47import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' 45import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
48import { InputMaskModule } from 'primeng/inputmask'
49import { ScreenService } from '@app/shared/misc/screen.service'
50import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service'
51import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' 46import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
52import { VideoCaptionService } from '@app/shared/video-caption' 47import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component'
48import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' 49import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
54import { VideoImportService } from '@app/shared/video-import/video-import.service' 50import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component'
55import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' 51import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar'
56import { 52import { GlobalIconComponent } from '@app/shared/images/global-icon.component'
57 NgbCollapseModule, 53import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component'
58 NgbDropdownModule, 54import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component'
59 NgbModalModule, 55import { FollowService } from '@app/shared/instance/follow.service'
60 NgbPopoverModule,
61 NgbNavModule,
62 NgbTooltipModule
63} from '@ng-bootstrap/ng-bootstrap'
64import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
65import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' 56import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
66import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component' 57import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component'
67import { OverviewService } from '@app/shared/overview' 58import { InstanceService } from '@app/shared/instance/instance.service'
59import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component'
60import { HelpComponent } from '@app/shared/misc/help.component'
61import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component'
62import { ScreenService } from '@app/shared/misc/screen.service'
63import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component'
64import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service'
68import { UserBanModalComponent } from '@app/shared/moderation' 65import { UserBanModalComponent } from '@app/shared/moderation'
69import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' 66import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
70import { BlocklistService } from '@app/shared/blocklist' 67import { OverviewService } from '@app/shared/overview'
71import { AvatarComponent } from '@app/shared/channel/avatar.component' 68import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer'
72import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' 69import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
73import { UserHistoryService } from '@app/shared/users/user-history.service' 70import { UserHistoryService } from '@app/shared/users/user-history.service'
74import { UserNotificationService } from '@app/shared/users/user-notification.service' 71import { UserNotificationService } from '@app/shared/users/user-notification.service'
75import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' 72import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component'
76import { InstanceService } from '@app/shared/instance/instance.service' 73import { VideoCaptionService } from '@app/shared/video-caption'
77import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' 74import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
78import { ConfirmComponent } from '@app/shared/confirm/confirm.component' 75import { VideoImportService } from '@app/shared/video-import/video-import.service'
79import { DateToggleComponent } from '@app/shared/date/date-toggle.component'
80import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component'
81import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
82import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component'
83import { GlobalIconComponent } from '@app/shared/images/global-icon.component'
84import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
85import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' 76import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
86import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component'
87import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' 77import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component'
88import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' 78import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
89import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' 79import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
90import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe' 80import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
91import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
92import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
93import { HighlightPipe } from '@app/shared/angular/highlight.pipe'
94import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
95import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component'
96import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' 81import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component'
97import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' 82import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
98import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' 83import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
99import { FollowService } from '@app/shared/instance/follow.service'
100import { MultiSelectModule } from 'primeng/multiselect'
101import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component'
102import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component'
103import { RedundancyService } from '@app/shared/video/redundancy.service' 84import { RedundancyService } from '@app/shared/video/redundancy.service'
104import { ClipboardModule } from '@angular/cdk/clipboard' 85import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component'
105import { InputSwitchModule } from 'primeng/inputswitch' 86import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component'
106 87import {
107import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' 88 NgbCollapseModule,
108import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' 89 NgbDropdownModule,
109import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' 90 NgbModalModule,
110import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' 91 NgbNavModule,
92 NgbPopoverModule,
93 NgbTooltipModule
94} from '@ng-bootstrap/ng-bootstrap'
95import { I18n } from '@ngx-translate/i18n-polyfill'
96import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
97import { BulkService } from './bulk/bulk.service'
98import { ButtonComponent } from './buttons/button.component'
99import { DeleteButtonComponent } from './buttons/delete-button.component'
100import { EditButtonComponent } from './buttons/edit-button.component'
101import { LoaderComponent } from './misc/loader.component'
102import { RestExtractor, RestService } from './rest'
103import { UserService } from './users'
104import { VideoAbuseService } from './video-abuse'
105import { VideoBlacklistService } from './video-blacklist'
106import { VideoOwnershipService } from './video-ownership'
107import { FeedComponent } from './video/feed.component'
108import { VideoMiniatureComponent } from './video/video-miniature.component'
109import { VideoThumbnailComponent } from './video/video-thumbnail.component'
110import { VideoService } from './video/video.service'
111 111
112@NgModule({ 112@NgModule({
113 imports: [ 113 imports: [
@@ -313,6 +313,7 @@ import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-d
313 BlocklistService, 313 BlocklistService,
314 UserHistoryService, 314 UserHistoryService,
315 InstanceService, 315 InstanceService,
316 BulkService,
316 317
317 MarkdownService, 318 MarkdownService,
318 LinkifierService, 319 LinkifierService,
diff --git a/shared/models/bulk/index.ts b/shared/models/bulk/index.ts
new file mode 100644
index 000000000..168c8cd48
--- /dev/null
+++ b/shared/models/bulk/index.ts
@@ -0,0 +1 @@
export * from './bulk-remove-comments-of-body.model'
diff --git a/shared/models/index.ts b/shared/models/index.ts
index 062533834..b562e04a3 100644
--- a/shared/models/index.ts
+++ b/shared/models/index.ts
@@ -2,6 +2,7 @@ export * from './activitypub'
2export * from './actors' 2export * from './actors'
3export * from './avatars' 3export * from './avatars'
4export * from './blocklist' 4export * from './blocklist'
5export * from './bulk'
5export * from './redundancy' 6export * from './redundancy'
6export * from './users' 7export * from './users'
7export * from './videos' 8export * from './videos'