From 8ca56654a176ee8f350d31282c6cac4a59f58499 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 9 Jul 2020 11:58:46 +0200 Subject: Add ability to report comments in front end --- .../app/shared/shared-moderation/abuse.service.ts | 96 +++++++++++++++++----- .../comment-report.component.html | 62 ++++++++++++++ .../comment-report.component.scss | 11 +++ .../shared-moderation/comment-report.component.ts | 93 +++++++++++++++++++++ .../shared-moderation/shared-moderation.module.ts | 7 +- .../user-moderation-dropdown.component.ts | 7 ++ .../shared-moderation/video-report.component.html | 7 +- .../shared-moderation/video-report.component.ts | 43 +--------- 8 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 client/src/app/shared/shared-moderation/comment-report.component.html create mode 100644 client/src/app/shared/shared-moderation/comment-report.component.scss create mode 100644 client/src/app/shared/shared-moderation/comment-report.component.ts (limited to 'client/src/app/shared/shared-moderation') diff --git a/client/src/app/shared/shared-moderation/abuse.service.ts b/client/src/app/shared/shared-moderation/abuse.service.ts index f45018d5c..95ac16955 100644 --- a/client/src/app/shared/shared-moderation/abuse.service.ts +++ b/client/src/app/shared/shared-moderation/abuse.service.ts @@ -5,18 +5,20 @@ import { catchError, map } from 'rxjs/operators' import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { RestExtractor, RestPagination, RestService } from '@app/core' -import { AbuseUpdate, ResultList, Abuse, AbuseCreate, AbuseState } from '@shared/models' +import { Abuse, AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, ResultList } from '@shared/models' import { environment } from '../../../environments/environment' +import { I18n } from '@ngx-translate/i18n-polyfill' @Injectable() export class AbuseService { private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses' constructor ( + private i18n: I18n, private authHttp: HttpClient, private restService: RestService, private restExtractor: RestExtractor - ) {} + ) { } getAbuses (options: { pagination: RestPagination, @@ -24,7 +26,7 @@ export class AbuseService { search?: string }): Observable> { const { pagination, sort, search } = options - const url = AbuseService.BASE_ABUSE_URL + 'abuse' + const url = AbuseService.BASE_ABUSE_URL let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) @@ -60,39 +62,93 @@ export class AbuseService { } return this.authHttp.get>(url, { params }) - .pipe( - catchError(res => this.restExtractor.handleError(res)) - ) + .pipe( + catchError(res => this.restExtractor.handleError(res)) + ) } reportVideo (parameters: AbuseCreate) { const url = AbuseService.BASE_ABUSE_URL - const body = omit(parameters, [ 'id' ]) + const body = omit(parameters, ['id']) return this.authHttp.post(url, body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(res => this.restExtractor.handleError(res)) - ) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(res => this.restExtractor.handleError(res)) + ) } updateAbuse (abuse: Abuse, abuseUpdate: AbuseUpdate) { const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id return this.authHttp.put(url, abuseUpdate) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(res => this.restExtractor.handleError(res)) - ) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(res => this.restExtractor.handleError(res)) + ) } removeAbuse (abuse: Abuse) { const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id return this.authHttp.delete(url) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(res => this.restExtractor.handleError(res)) - ) - }} + .pipe( + map(this.restExtractor.extractDataBool), + catchError(res => this.restExtractor.handleError(res)) + ) + } + + getPrefefinedReasons (type: AbuseFilter) { + let reasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [ + { + id: 'violentOrRepulsive', + label: this.i18n('Violent or repulsive'), + help: this.i18n('Contains offensive, violent, or coarse language or iconography.') + }, + { + id: 'hatefulOrAbusive', + label: this.i18n('Hateful or abusive'), + help: this.i18n('Contains abusive, racist or sexist language or iconography.') + }, + { + id: 'spamOrMisleading', + label: this.i18n('Spam, ad or false news'), + help: this.i18n('Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.') + }, + { + id: 'privacy', + label: this.i18n('Privacy breach or doxxing'), + help: this.i18n('Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).') + }, + { + id: 'rights', + label: this.i18n('Intellectual property violation'), + help: this.i18n('Infringes my intellectual property or copyright, wrt. the regional rules with which the server must comply.') + }, + { + id: 'serverRules', + label: this.i18n('Breaks server rules'), + description: this.i18n('Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.') + } + ] + + if (type === 'video') { + reasons = reasons.concat([ + { + id: 'thumbnails', + label: this.i18n('Thumbnails'), + help: this.i18n('The above can only be seen in thumbnails.') + }, + { + id: 'captions', + label: this.i18n('Captions'), + help: this.i18n('The above can only be seen in captions (please describe which).') + } + ]) + } + + return reasons + } + +} diff --git a/client/src/app/shared/shared-moderation/comment-report.component.html b/client/src/app/shared/shared-moderation/comment-report.component.html new file mode 100644 index 000000000..1105b3788 --- /dev/null +++ b/client/src/app/shared/shared-moderation/comment-report.component.html @@ -0,0 +1,62 @@ + + + + + diff --git a/client/src/app/shared/shared-moderation/comment-report.component.scss b/client/src/app/shared/shared-moderation/comment-report.component.scss new file mode 100644 index 000000000..17a33d3a2 --- /dev/null +++ b/client/src/app/shared/shared-moderation/comment-report.component.scss @@ -0,0 +1,11 @@ +@import 'variables'; +@import 'mixins'; + +.information { + margin-bottom: 20px; +} + +textarea { + @include peertube-textarea(100%, 100px); +} + diff --git a/client/src/app/shared/shared-moderation/comment-report.component.ts b/client/src/app/shared/shared-moderation/comment-report.component.ts new file mode 100644 index 000000000..5db4b2dc1 --- /dev/null +++ b/client/src/app/shared/shared-moderation/comment-report.component.ts @@ -0,0 +1,93 @@ +import { mapValues, pickBy } from 'lodash-es' +import { Component, Input, OnInit, ViewChild } from '@angular/core' +import { SafeHtml } from '@angular/platform-browser' +import { VideoComment } from '@app/+videos/+video-watch/comment/video-comment.model' +import { Notifier } from '@app/core' +import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' +import { AbuseService } from './abuse.service' + +@Component({ + selector: 'my-comment-report', + templateUrl: './comment-report.component.html', + styleUrls: [ './comment-report.component.scss' ] +}) +export class CommentReportComponent extends FormReactive implements OnInit { + @Input() comment: VideoComment = null + + @ViewChild('modal', { static: true }) modal: NgbModal + + error: string = null + predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] + embedHtml: SafeHtml + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private abuseValidatorsService: AbuseValidatorsService, + private abuseService: AbuseService, + private notifier: Notifier, + private i18n: I18n + ) { + super() + } + + get currentHost () { + return window.location.host + } + + get originHost () { + if (this.isRemoteComment()) { + return this.comment.account.host + } + + return '' + } + + ngOnInit () { + this.buildForm({ + reason: this.abuseValidatorsService.ABUSE_REASON, + predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) + }) + + this.predefinedReasons = this.abuseService.getPrefefinedReasons('comment') + } + + show () { + this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false, size: 'lg' }) + } + + hide () { + this.openedModal.close() + this.openedModal = null + } + + report () { + const reason = this.form.get('reason').value + const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as AbusePredefinedReasonsString[] + + this.abuseService.reportVideo({ + reason, + predefinedReasons, + comment: { + id: this.comment.id + } + }).subscribe( + () => { + this.notifier.success(this.i18n('Comment reported.')) + this.hide() + }, + + err => this.notifier.error(err.message) + ) + } + + isRemoteComment () { + return !this.comment.isLocal + } +} diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts index 742193e58..ff4021a33 100644 --- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts +++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts @@ -12,6 +12,7 @@ import { AbuseService } from './abuse.service' import { VideoBlockComponent } from './video-block.component' import { VideoBlockService } from './video-block.service' import { VideoReportComponent } from './video-report.component' +import { CommentReportComponent } from './comment-report.component' @NgModule({ imports: [ @@ -25,7 +26,8 @@ import { VideoReportComponent } from './video-report.component' UserModerationDropdownComponent, VideoBlockComponent, VideoReportComponent, - BatchDomainsModalComponent + BatchDomainsModalComponent, + CommentReportComponent ], exports: [ @@ -33,7 +35,8 @@ import { VideoReportComponent } from './video-report.component' UserModerationDropdownComponent, VideoBlockComponent, VideoReportComponent, - BatchDomainsModalComponent + BatchDomainsModalComponent, + CommentReportComponent ], providers: [ 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 d3c37f082..78c2658df 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 @@ -16,6 +16,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { @Input() user: User @Input() account: Account + @Input() prependActions: DropdownAction<{ user: User, account: Account }>[] @Input() buttonSize: 'normal' | 'small' = 'normal' @Input() placement = 'left-top left-bottom auto' @@ -250,6 +251,12 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { private buildActions () { this.userActions = [] + if (this.prependActions) { + this.userActions = [ + this.prependActions + ] + } + if (this.authService.isLoggedIn()) { const authUser = this.authService.getUser() diff --git a/client/src/app/shared/shared-moderation/video-report.component.html b/client/src/app/shared/shared-moderation/video-report.component.html index d6beb6d2a..b724ecb18 100644 --- a/client/src/app/shared/shared-moderation/video-report.component.html +++ b/client/src/app/shared/shared-moderation/video-report.component.html @@ -14,16 +14,19 @@
+
- +
+
+
@@ -73,7 +76,7 @@
- diff --git a/client/src/app/shared/shared-moderation/video-report.component.ts b/client/src/app/shared/shared-moderation/video-report.component.ts index 7977e4cca..26e7b62ba 100644 --- a/client/src/app/shared/shared-moderation/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/video-report.component.ts @@ -79,48 +79,7 @@ export class VideoReportComponent extends FormReactive implements OnInit { } }) - this.predefinedReasons = [ - { - id: 'violentOrRepulsive', - label: this.i18n('Violent or repulsive'), - help: this.i18n('Contains offensive, violent, or coarse language or iconography.') - }, - { - id: 'hatefulOrAbusive', - label: this.i18n('Hateful or abusive'), - help: this.i18n('Contains abusive, racist or sexist language or iconography.') - }, - { - id: 'spamOrMisleading', - label: this.i18n('Spam, ad or false news'), - help: this.i18n('Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.') - }, - { - id: 'privacy', - label: this.i18n('Privacy breach or doxxing'), - help: this.i18n('Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).') - }, - { - id: 'rights', - label: this.i18n('Intellectual property violation'), - help: this.i18n('Infringes my intellectual property or copyright, wrt. the regional rules with which the server must comply.') - }, - { - id: 'serverRules', - label: this.i18n('Breaks server rules'), - description: this.i18n('Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.') - }, - { - id: 'thumbnails', - label: this.i18n('Thumbnails'), - help: this.i18n('The above can only be seen in thumbnails.') - }, - { - id: 'captions', - label: this.i18n('Captions'), - help: this.i18n('The above can only be seen in captions (please describe which).') - } - ] + this.predefinedReasons = this.abuseService.getPrefefinedReasons('video') this.embedHtml = this.getVideoEmbed() } -- cgit v1.2.3