From efc9e8450a8bbeeef9cd18e3ad6037abc0f815c3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 13 Aug 2018 11:54:11 +0200 Subject: [PATCH] Add ability to delete and update abuse on client --- client/src/app/+admin/admin.module.ts | 3 +- .../users/user-list/user-list.component.scss | 6 -- .../video-abuses/video-abuse-list/index.ts | 1 + .../moderation-comment-modal.component.html | 32 ++++++++ .../moderation-comment-modal.component.scss | 6 ++ .../moderation-comment-modal.component.ts | 73 ++++++++++++++++++ .../video-abuse-list.component.html | 36 ++++++++- .../video-abuse-list.component.scss | 10 +-- .../video-abuse-list.component.ts | 77 ++++++++++++++++++- .../video-abuse-validators.service.ts | 10 +++ .../shared/video-abuse/video-abuse.service.ts | 23 +++++- .../modal/video-report.component.scss | 2 +- shared/models/videos/index.ts | 1 + 13 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.html create mode 100644 client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.scss create mode 100644 client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.ts diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index 04b7ec5c1..23ca5a6b3 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts @@ -11,7 +11,7 @@ import { JobsComponent } from './jobs/job.component' import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' import { JobService } from './jobs/shared/job.service' import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' -import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' +import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist' import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component' @@ -41,6 +41,7 @@ import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-moda VideoAbusesComponent, VideoAbuseListComponent, + ModerationCommentModalComponent, JobsComponent, JobsListComponent, diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss index 2d11dd7a0..47291918d 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/users/user-list/user-list.component.scss @@ -5,12 +5,6 @@ @include create-button('../../../../assets/images/global/add.svg'); } -my-action-dropdown /deep/ .icon { - &.icon-ban { - background-image: url('../../../../assets/images/global/edit-black.svg'); - } -} - tr.banned { color: red; } diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/index.ts b/client/src/app/+admin/video-abuses/video-abuse-list/index.ts index 01c24d860..da7176e52 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/index.ts +++ b/client/src/app/+admin/video-abuses/video-abuse-list/index.ts @@ -1 +1,2 @@ export * from './video-abuse-list.component' +export * from './moderation-comment-modal.component' diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.html new file mode 100644 index 000000000..3a8424f68 --- /dev/null +++ b/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.html @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.scss b/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.scss new file mode 100644 index 000000000..afcdb9a16 --- /dev/null +++ b/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.scss @@ -0,0 +1,6 @@ +@import 'variables'; +@import 'mixins'; + +textarea { + @include peertube-textarea(100%, 100px); +} diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.ts new file mode 100644 index 000000000..7e8af6e5a --- /dev/null +++ b/client/src/app/+admin/video-abuses/video-abuse-list/moderation-comment-modal.component.ts @@ -0,0 +1,73 @@ +import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { VideoAbuse } from '../../../../../../shared/models/videos' + +@Component({ + selector: 'my-moderation-comment-modal', + templateUrl: './moderation-comment-modal.component.html', + styleUrls: [ './moderation-comment-modal.component.scss' ] +}) +export class ModerationCommentModalComponent extends FormReactive implements OnInit { + @ViewChild('modal') modal: NgbModal + @Output() commentUpdated = new EventEmitter() + + private abuseToComment: VideoAbuse + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private notificationsService: NotificationsService, + private videoAbuseService: VideoAbuseService, + private videoAbuseValidatorsService: VideoAbuseValidatorsService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + this.buildForm({ + moderationComment: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON + }) + } + + openModal (abuseToComment: VideoAbuse) { + this.abuseToComment = abuseToComment + this.openedModal = this.modalService.open(this.modal) + + this.form.patchValue({ + moderationComment: this.abuseToComment.moderationComment + }) + } + + hideModerationCommentModal () { + this.abuseToComment = undefined + this.openedModal.close() + this.form.reset() + } + + async banUser () { + const moderationComment: string = this.form.value['moderationComment'] + + this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Comment updated.') + ) + + this.commentUpdated.emit(moderationComment) + this.hideModerationCommentModal() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + +} diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html index 8111e5f73..08501d872 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html @@ -4,31 +4,63 @@ + + State Reason Reporter Created Video + - + + + + + + + + + + + + {{ videoAbuse.reason }} + {{ createByString(videoAbuse.reporterAccount) }} + {{ videoAbuse.createdAt }} + {{ videoAbuse.video.name }} + + + + + + + + + + + Moderation comment: + {{ videoAbuse.moderationComment }} + + + \ No newline at end of file diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss index 6a4762650..951a3fc92 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.scss @@ -1,6 +1,6 @@ -/deep/ a { +@import '_variables'; +@import '_mixins'; - &, &:hover, &:active, &:focus { - color: #000; - } -} +.moderation-comment-label { + font-weight: $font-semibold; +} \ No newline at end of file diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts index 6ddebff7e..a850c0ec2 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts @@ -1,11 +1,13 @@ -import { Component, OnInit } from '@angular/core' +import { Component, OnInit, ViewChild } from '@angular/core' import { Account } from '@app/shared/account/account.model' import { NotificationsService } from 'angular2-notifications' import { SortMeta } from 'primeng/components/common/sortmeta' -import { VideoAbuse } from '../../../../../../shared' - +import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' +import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' +import { ConfirmService } from '@app/core' +import { ModerationCommentModalComponent } from './moderation-comment-modal.component' @Component({ selector: 'my-video-abuse-list', @@ -13,28 +15,97 @@ import { I18n } from '@ngx-translate/i18n-polyfill' styleUrls: [ './video-abuse-list.component.scss'] }) export class VideoAbuseListComponent extends RestTable implements OnInit { + @ViewChild('moderationCommentModal') moderationCommentModal: ModerationCommentModalComponent + videoAbuses: VideoAbuse[] = [] totalRecords = 0 rowsPerPage = 10 sort: SortMeta = { field: 'createdAt', order: 1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + videoAbuseActions: DropdownAction[] = [] + constructor ( private notificationsService: NotificationsService, private videoAbuseService: VideoAbuseService, + private confirmService: ConfirmService, private i18n: I18n ) { super() + + this.videoAbuseActions = [ + { + label: this.i18n('Delete'), + handler: videoAbuse => this.removeVideoAbuse(videoAbuse) + }, + { + label: this.i18n('Update moderation comment'), + handler: videoAbuse => this.openModerationCommentModal(videoAbuse) + }, + { + label: this.i18n('Mark as accepted'), + handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), + isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) + }, + { + label: this.i18n('Mark as rejected'), + handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), + isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) + } + ] } ngOnInit () { this.loadSort() } + openModerationCommentModal (videoAbuse: VideoAbuse) { + this.moderationCommentModal.openModal(videoAbuse) + } + + onModerationCommentUpdated () { + this.loadData() + } + createByString (account: Account) { return Account.CREATE_BY_STRING(account.name, account.host) } + isVideoAbuseAccepted (videoAbuse: VideoAbuse) { + return videoAbuse.state.id === VideoAbuseState.ACCEPTED + } + + isVideoAbuseRejected (videoAbuse: VideoAbuse) { + return videoAbuse.state.id === VideoAbuseState.REJECTED + } + + async removeVideoAbuse (videoAbuse: VideoAbuse) { + const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete')) + if (res === false) return + + this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Abuse deleted.') + ) + this.loadData() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + updateVideoAbuseState (videoAbuse: VideoAbuse, state: VideoAbuseState) { + this.videoAbuseService.updateVideoAbuse(videoAbuse, { state }) + .subscribe( + () => this.loadData(), + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + + } + protected loadData () { return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) .subscribe( diff --git a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts b/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts index 774e6a488..6e9806611 100644 --- a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts @@ -6,6 +6,7 @@ import { BuildFormValidator } from '@app/shared' @Injectable() export class VideoAbuseValidatorsService { readonly VIDEO_ABUSE_REASON: BuildFormValidator + readonly VIDEO_ABUSE_MODERATION_COMMENT: BuildFormValidator constructor (private i18n: I18n) { this.VIDEO_ABUSE_REASON = { @@ -16,5 +17,14 @@ export class VideoAbuseValidatorsService { 'maxlength': this.i18n('Report reason cannot be more than 300 characters long.') } } + + this.VIDEO_ABUSE_MODERATION_COMMENT = { + VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ], + MESSAGES: { + 'required': this.i18n('Moderation comment is required.'), + 'minlength': this.i18n('Moderation comment must be at least 2 characters long.'), + 'maxlength': this.i18n('Moderation comment cannot be more than 300 characters long.') + } + } } } diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts index 6fab3ef43..61b7e1b98 100644 --- a/client/src/app/shared/video-abuse/video-abuse.service.ts +++ b/client/src/app/shared/video-abuse/video-abuse.service.ts @@ -3,7 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { SortMeta } from 'primeng/components/common/sortmeta' import { Observable } from 'rxjs' -import { ResultList, VideoAbuse } from '../../../../../shared' +import { ResultList, VideoAbuse, VideoAbuseUpdate } from '../../../../../shared' import { environment } from '../../../environments/environment' import { RestExtractor, RestPagination, RestService } from '../rest' @@ -42,4 +42,23 @@ export class VideoAbuseService { catchError(res => this.restExtractor.handleError(res)) ) } -} + + updateVideoAbuse (videoAbuse: VideoAbuse, abuseUpdate: VideoAbuseUpdate) { + const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id + + return this.authHttp.put(url, abuseUpdate) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(res => this.restExtractor.handleError(res)) + ) + } + + removeVideoAbuse (videoAbuse: VideoAbuse) { + const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id + + return this.authHttp.delete(url) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(res => this.restExtractor.handleError(res)) + ) + }} diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.scss b/client/src/app/videos/+video-watch/modal/video-report.component.scss index 84562f15c..afcdb9a16 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.scss +++ b/client/src/app/videos/+video-watch/modal/video-report.component.scss @@ -2,5 +2,5 @@ @import 'mixins'; textarea { - @include peertube-textarea(100%, 60px); + @include peertube-textarea(100%, 100px); } diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts index 2d7de2a0d..02bf2b842 100644 --- a/shared/models/videos/index.ts +++ b/shared/models/videos/index.ts @@ -4,6 +4,7 @@ export * from './user-video-rate.type' export * from './video-abuse-state.model' export * from './video-abuse-create.model' export * from './video-abuse.model' +export * from './video-abuse-update.model' export * from './video-blacklist.model' export * from './video-channel-create.model' export * from './video-channel-update.model'