From 923ff87da2761fd88a8ca269ac1ef403abb583d2 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 19 May 2020 10:24:36 +0200 Subject: [PATCH] Add bulk comment actions on account dropdown --- client/src/app/shared/bulk/bulk.service.ts | 24 +++ .../user-moderation-dropdown.component.ts | 38 ++++- client/src/app/shared/shared.module.ts | 155 +++++++++--------- shared/models/bulk/index.ts | 1 + shared/models/index.ts | 1 + 5 files changed, 139 insertions(+), 80 deletions(-) create mode 100644 client/src/app/shared/bulk/bulk.service.ts create mode 100644 shared/models/bulk/index.ts 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 @@ +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { environment } from '../../../environments/environment' +import { RestExtractor, RestService } from '../rest' +import { BulkRemoveCommentsOfBody } from '../../../../../shared' +import { catchError } from 'rxjs/operators' + +@Injectable() +export class BulkService { + static BASE_BULK_URL = environment.apiUrl + '/api/v1/bulk' + + constructor ( + private authHttp: HttpClient, + private restExtractor: RestExtractor, + private restService: RestService + ) { } + + removeCommentsOf (body: BulkRemoveCommentsOfBody) { + const url = BulkService.BASE_BULK_URL + '/remove-comments-of' + + return this.authHttp.post(url, body) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } +} 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' import { User, UserRight } from '../../../../../shared/models/users' import { Account } from '@app/shared/account/account.model' import { BlocklistService } from '@app/shared/blocklist' -import { ServerConfig } from '@shared/models' +import { ServerConfig, BulkRemoveCommentsOfBody } from '@shared/models' +import { BulkService } from '../bulk/bulk.service' @Component({ selector: 'my-user-moderation-dropdown', @@ -38,6 +39,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { private serverService: ServerService, private userService: UserService, private blocklistService: BlocklistService, + private bulkService: BulkService, private i18n: I18n ) { } @@ -229,6 +231,21 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { ) } + async bulkRemoveCommentsOf (body: BulkRemoveCommentsOfBody) { + const message = this.i18n('Are you sure you want to remove all the comments of this account?') + const res = await this.confirmService.confirm(message, this.i18n('Delete account comments')) + if (res === false) return + + this.bulkService.removeCommentsOf(body) + .subscribe( + () => { + this.notifier.success(this.i18n('Will remove comments of this account (may take several minutes).')) + }, + + err => this.notifier.error(err.message) + ) + } + getRouterUserEditLink (user: User) { return [ '/admin', 'users', 'update', user.id ] } @@ -300,12 +317,17 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { description: this.i18n('Show back content from that instance for you.'), isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, handler: ({ account }) => this.unblockServerByUser(account.host) + }, + { + label: this.i18n('Remove comments from your videos'), + description: this.i18n('Remove comments of this account from your videos.'), + handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'my-videos' }) } ]) let instanceActions: DropdownAction<{ user: User, account: Account }>[] = [] - // Instance actions + // Instance actions on account blocklists if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { instanceActions = instanceActions.concat([ { @@ -323,7 +345,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { ]) } - // Instance actions + // Instance actions on server blocklists if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { instanceActions = instanceActions.concat([ { @@ -341,6 +363,16 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { ]) } + if (authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) { + instanceActions = instanceActions.concat([ + { + label: this.i18n('Remove comments from your instance'), + description: this.i18n('Remove comments of this account from your instance.'), + handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'instance' }) + } + ]) + } + if (instanceActions.length !== 0) { this.userActions.push(instanceActions) } 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 @@ +import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' +import { SharedModule as PrimeSharedModule } from 'primeng/api' +import { InputMaskModule } from 'primeng/inputmask' +import { InputSwitchModule } from 'primeng/inputswitch' +import { MultiSelectModule } from 'primeng/multiselect' +import { ClipboardModule } from '@angular/cdk/clipboard' import { CommonModule } from '@angular/common' import { HttpClientModule } from '@angular/common/http' import { NgModule } from '@angular/core' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { RouterModule } from '@angular/router' -import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' -import { HelpComponent } from '@app/shared/misc/help.component' -import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' -import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' -import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' -import { SharedModule as PrimeSharedModule } from 'primeng/api' -import { AUTH_INTERCEPTOR_PROVIDER } from './auth' -import { ButtonComponent } from './buttons/button.component' -import { DeleteButtonComponent } from './buttons/delete-button.component' -import { EditButtonComponent } from './buttons/edit-button.component' -import { LoaderComponent } from './misc/loader.component' -import { RestExtractor, RestService } from './rest' -import { UserService } from './users' -import { VideoAbuseService } from './video-abuse' -import { VideoBlacklistService } from './video-blacklist' -import { VideoOwnershipService } from './video-ownership' -import { VideoMiniatureComponent } from './video/video-miniature.component' -import { FeedComponent } from './video/feed.component' -import { VideoThumbnailComponent } from './video/video-thumbnail.component' -import { VideoService } from './video/video.service' +import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' +import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' +import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' +import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' import { AccountService } from '@app/shared/account/account.service' -import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { FromNowPipe } from '@app/shared/angular/from-now.pipe' +import { HighlightPipe } from '@app/shared/angular/highlight.pipe' +import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' +import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe' +import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' +import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe' +import { BlocklistService } from '@app/shared/blocklist' +import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' +import { AvatarComponent } from '@app/shared/channel/avatar.component' +import { ConfirmComponent } from '@app/shared/confirm/confirm.component' +import { DateToggleComponent } from '@app/shared/date/date-toggle.component' import { CustomConfigValidatorsService, InstanceValidatorsService, @@ -44,70 +42,72 @@ import { VideoPlaylistValidatorsService, VideoValidatorsService } from '@app/shared/forms' -import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' -import { InputMaskModule } from 'primeng/inputmask' -import { ScreenService } from '@app/shared/misc/screen.service' -import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' -import { VideoCaptionService } from '@app/shared/video-caption' +import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' +import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' +import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' +import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' +import { GlobalIconComponent } from '@app/shared/images/global-icon.component' +import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component' +import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' +import { FollowService } from '@app/shared/instance/follow.service' +import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' +import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component' +import { InstanceService } from '@app/shared/instance/instance.service' +import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' +import { HelpComponent } from '@app/shared/misc/help.component' +import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' +import { ScreenService } from '@app/shared/misc/screen.service' +import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' +import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' +import { UserBanModalComponent } from '@app/shared/moderation' +import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' +import { OverviewService } from '@app/shared/overview' +import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' +import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' +import { UserHistoryService } from '@app/shared/users/user-history.service' +import { UserNotificationService } from '@app/shared/users/user-notification.service' +import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' +import { VideoCaptionService } from '@app/shared/video-caption' +import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { VideoImportService } from '@app/shared/video-import/video-import.service' -import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' +import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' +import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' +import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' +import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' +import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' +import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' +import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' +import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' +import { RedundancyService } from '@app/shared/video/redundancy.service' +import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component' +import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' import { NgbCollapseModule, NgbDropdownModule, NgbModalModule, - NgbPopoverModule, NgbNavModule, + NgbPopoverModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' -import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' -import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' -import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component' -import { OverviewService } from '@app/shared/overview' -import { UserBanModalComponent } from '@app/shared/moderation' -import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' -import { BlocklistService } from '@app/shared/blocklist' -import { AvatarComponent } from '@app/shared/channel/avatar.component' -import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' -import { UserHistoryService } from '@app/shared/users/user-history.service' -import { UserNotificationService } from '@app/shared/users/user-notification.service' -import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' -import { InstanceService } from '@app/shared/instance/instance.service' -import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' -import { ConfirmComponent } from '@app/shared/confirm/confirm.component' -import { DateToggleComponent } from '@app/shared/date/date-toggle.component' -import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' -import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' -import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component' -import { GlobalIconComponent } from '@app/shared/images/global-icon.component' -import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' -import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' -import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' -import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' -import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' -import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' -import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe' -import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe' -import { FromNowPipe } from '@app/shared/angular/from-now.pipe' -import { HighlightPipe } from '@app/shared/angular/highlight.pipe' -import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' -import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component' -import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' -import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' -import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' -import { FollowService } from '@app/shared/instance/follow.service' -import { MultiSelectModule } from 'primeng/multiselect' -import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' -import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' -import { RedundancyService } from '@app/shared/video/redundancy.service' -import { ClipboardModule } from '@angular/cdk/clipboard' -import { InputSwitchModule } from 'primeng/inputswitch' - -import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' -import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' -import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' -import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { AUTH_INTERCEPTOR_PROVIDER } from './auth' +import { BulkService } from './bulk/bulk.service' +import { ButtonComponent } from './buttons/button.component' +import { DeleteButtonComponent } from './buttons/delete-button.component' +import { EditButtonComponent } from './buttons/edit-button.component' +import { LoaderComponent } from './misc/loader.component' +import { RestExtractor, RestService } from './rest' +import { UserService } from './users' +import { VideoAbuseService } from './video-abuse' +import { VideoBlacklistService } from './video-blacklist' +import { VideoOwnershipService } from './video-ownership' +import { FeedComponent } from './video/feed.component' +import { VideoMiniatureComponent } from './video/video-miniature.component' +import { VideoThumbnailComponent } from './video/video-thumbnail.component' +import { VideoService } from './video/video.service' @NgModule({ imports: [ @@ -313,6 +313,7 @@ import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-d BlocklistService, UserHistoryService, InstanceService, + BulkService, MarkdownService, 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' export * from './actors' export * from './avatars' export * from './blocklist' +export * from './bulk' export * from './redundancy' export * from './users' export * from './videos'