aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-07-24 17:21:25 +0200
committerChocobozzz <chocobozzz@cpy.re>2020-07-31 11:35:19 +0200
commit441e453ae53e491b09c9b09b00b041788176ce64 (patch)
tree6104afc6b8344b39ec95211ed236ed784895d65d /client/src/app/shared
parentedbc9325462ddf4536775871ebc25e06f46612d1 (diff)
downloadPeerTube-441e453ae53e491b09c9b09b00b041788176ce64.tar.gz
PeerTube-441e453ae53e491b09c9b09b00b041788176ce64.tar.zst
PeerTube-441e453ae53e491b09c9b09b00b041788176ce64.zip
Add abuse message management in admin
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts10
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.ts3
-rw-r--r--client/src/app/shared/shared-moderation/abuse-message-modal.component.html40
-rw-r--r--client/src/app/shared/shared-moderation/abuse-message-modal.component.scss57
-rw-r--r--client/src/app/shared/shared-moderation/abuse-message-modal.component.ts115
-rw-r--r--client/src/app/shared/shared-moderation/abuse.service.ts41
-rw-r--r--client/src/app/shared/shared-moderation/index.ts1
-rw-r--r--client/src/app/shared/shared-moderation/shared-moderation.module.ts9
8 files changed, 265 insertions, 11 deletions
diff --git a/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts
index 739115e19..5f15963f3 100644
--- a/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts
@@ -7,6 +7,7 @@ import { BuildFormValidator } from './form-validator.service'
7export class AbuseValidatorsService { 7export class AbuseValidatorsService {
8 readonly ABUSE_REASON: BuildFormValidator 8 readonly ABUSE_REASON: BuildFormValidator
9 readonly ABUSE_MODERATION_COMMENT: BuildFormValidator 9 readonly ABUSE_MODERATION_COMMENT: BuildFormValidator
10 readonly ABUSE_MESSAGE: BuildFormValidator
10 11
11 constructor (private i18n: I18n) { 12 constructor (private i18n: I18n) {
12 this.ABUSE_REASON = { 13 this.ABUSE_REASON = {
@@ -26,5 +27,14 @@ export class AbuseValidatorsService {
26 'maxlength': this.i18n('Moderation comment cannot be more than 3000 characters long.') 27 'maxlength': this.i18n('Moderation comment cannot be more than 3000 characters long.')
27 } 28 }
28 } 29 }
30
31 this.ABUSE_MESSAGE = {
32 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
33 MESSAGES: {
34 'required': this.i18n('Abuse message is required.'),
35 'minlength': this.i18n('Abuse message must be at least 2 characters long.'),
36 'maxlength': this.i18n('Abuse message cannot be more than 3000 characters long.')
37 }
38 }
29 } 39 }
30} 40}
diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts
index c58ef29fa..409681702 100644
--- a/client/src/app/shared/shared-icons/global-icon.component.ts
+++ b/client/src/app/shared/shared-icons/global-icon.component.ts
@@ -64,8 +64,7 @@ const icons = {
64 'go': require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default, 64 'go': require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default,
65 'cross': require('!!raw-loader?!../../../assets/images/feather/x.svg').default, 65 'cross': require('!!raw-loader?!../../../assets/images/feather/x.svg').default,
66 'tick': require('!!raw-loader?!../../../assets/images/feather/check.svg').default, 66 'tick': require('!!raw-loader?!../../../assets/images/feather/check.svg').default,
67 'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default, 67 'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default
68 'columns': require('!!raw-loader?!../../../assets/images/feather/columns.svg').default
69} 68}
70 69
71export type GlobalIconName = keyof typeof icons 70export type GlobalIconName = keyof typeof icons
diff --git a/client/src/app/shared/shared-moderation/abuse-message-modal.component.html b/client/src/app/shared/shared-moderation/abuse-message-modal.component.html
new file mode 100644
index 000000000..67c6a3081
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/abuse-message-modal.component.html
@@ -0,0 +1,40 @@
1<ng-template #modal>
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Messages</h4>
4
5 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
6 </div>
7
8 <div class="modal-body">
9 <div class="messages" #messagesBlock>
10 <div
11 *ngFor="let message of abuseMessages"
12 class="message-block" [ngClass]="{ 'by-moderator': message.byModerator, 'by-me': isMessageByMe(message) }"
13 >
14
15 <div class="author">{{ message.account.name }}</div>
16
17 <div class="bubble">
18 <div class="content">{{ message.message }}</div>
19 <div class="date">{{ message.createdAt | date }}</div>
20 </div>
21 </div>
22 </div>
23
24 <form novalidate [formGroup]="form" (ngSubmit)="addMessage()">
25 <div class="form-group">
26 <textarea formControlName="message" ngbAutofocus [ngClass]="{ 'input-error': formErrors['message'] }" class="form-control"></textarea>
27
28 <div *ngIf="formErrors.message" class="form-error">
29 {{ formErrors.message }}
30 </div>
31 </div>
32
33 <div class="form-group inputs">
34 <input type="submit" i18n-value value="Add message" class="action-button-submit" [disabled]="!form.valid || sendingMessage">
35 </div>
36 </form>
37
38 </div>
39
40</ng-template>
diff --git a/client/src/app/shared/shared-moderation/abuse-message-modal.component.scss b/client/src/app/shared/shared-moderation/abuse-message-modal.component.scss
new file mode 100644
index 000000000..89d6b88c1
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/abuse-message-modal.component.scss
@@ -0,0 +1,57 @@
1@import 'variables';
2@import 'mixins';
3
4form {
5 margin: 20px 20px 0 0;
6}
7
8textarea {
9 @include peertube-textarea(100%, 70px);
10
11 margin-top: 20px;
12}
13
14.messages {
15 display: flex;
16 flex-direction: column;
17 overflow-y: scroll;
18 margin-right: 5px;
19}
20
21.message-block {
22 margin-bottom: 10px;
23 max-width: 60%;
24
25 .author {
26 color: var(--greyForegroundColor);
27 font-size: 14px;
28 }
29
30 .bubble {
31 color: var(--mainForegroundColor);
32 background-color: var(--greyBackgroundColor);
33 border-radius: 10px;
34 padding: 5px 10px;
35
36 &.by-me {
37 color: var(--mainForegroundColor);
38 background-color: var(--secondaryColor);
39 }
40
41 &.by-moderator {
42 color: #fff;
43 background-color: var(--mainColor);
44
45 align-self: flex-end;
46 }
47
48 .content {
49 font-size: 15px;
50 }
51
52 .date {
53 font-size: 13px;
54 color: var(--greyForegroundColor);
55 }
56 }
57}
diff --git a/client/src/app/shared/shared-moderation/abuse-message-modal.component.ts b/client/src/app/shared/shared-moderation/abuse-message-modal.component.ts
new file mode 100644
index 000000000..5822dfe1d
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/abuse-message-modal.component.ts
@@ -0,0 +1,115 @@
1import { Component, ElementRef, EventEmitter, Output, ViewChild, OnInit } from '@angular/core'
2import { Notifier, AuthService } from '@app/core'
3import { FormReactive, FormValidatorService, AbuseValidatorsService } from '@app/shared/shared-forms'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { AbuseMessage, UserAbuse } from '@shared/models'
8import { AbuseService } from './abuse.service'
9
10@Component({
11 selector: 'my-abuse-message-modal',
12 templateUrl: './abuse-message-modal.component.html',
13 styleUrls: [ './abuse-message-modal.component.scss' ]
14})
15export class AbuseMessageModalComponent extends FormReactive implements OnInit {
16 @ViewChild('modal', { static: true }) modal: NgbModal
17 @ViewChild('messagesBlock', { static: false }) messagesBlock: ElementRef
18
19 @Output() countMessagesUpdated = new EventEmitter<{ abuseId: number, countMessages: number }>()
20
21 abuseMessages: AbuseMessage[] = []
22 textareaMessage: string
23 sendingMessage = false
24
25 private openedModal: NgbModalRef
26 private abuse: UserAbuse
27
28 constructor (
29 protected formValidatorService: FormValidatorService,
30 private abuseValidatorsService: AbuseValidatorsService,
31 private modalService: NgbModal,
32 private auth: AuthService,
33 private notifier: Notifier,
34 private i18n: I18n,
35 private abuseService: AbuseService
36 ) {
37 super()
38 }
39
40 ngOnInit () {
41 this.buildForm({
42 message: this.abuseValidatorsService.ABUSE_MESSAGE
43 })
44 }
45
46 openModal (abuse: UserAbuse) {
47 this.abuse = abuse
48
49 this.openedModal = this.modalService.open(this.modal, { centered: true })
50
51 this.loadMessages()
52 }
53
54 hide () {
55 this.abuseMessages = []
56 this.openedModal.close()
57 }
58
59 addMessage () {
60 this.sendingMessage = true
61
62 this.abuseService.addAbuseMessage(this.abuse, this.form.value['message'])
63 .subscribe(
64 () => {
65 this.form.reset()
66 this.sendingMessage = false
67 this.countMessagesUpdated.emit({ abuseId: this.abuse.id, countMessages: this.abuseMessages.length + 1 })
68
69 this.loadMessages()
70 },
71
72 err => {
73 this.sendingMessage = false
74 console.error(err)
75 this.notifier.error('Sorry but you cannot send this message. Please retry later')
76 }
77 )
78 }
79
80 deleteMessage (abuseMessage: AbuseMessage) {
81 this.abuseService.deleteAbuseMessage(this.abuse, abuseMessage)
82 .subscribe(
83 () => {
84 this.countMessagesUpdated.emit({ abuseId: this.abuse.id, countMessages: this.abuseMessages.length - 1 })
85
86 this.abuseMessages = this.abuseMessages.filter(m => m.id !== abuseMessage.id)
87 },
88
89 err => this.notifier.error(err.message)
90 )
91 }
92
93 isMessageByMe (abuseMessage: AbuseMessage) {
94 return this.auth.getUser().account.id === abuseMessage.account.id
95 }
96
97 private loadMessages () {
98 this.abuseService.listAbuseMessages(this.abuse)
99 .subscribe(
100 res => {
101 this.abuseMessages = res.data
102
103 setTimeout(() => {
104 if (!this.messagesBlock) return
105
106 const element = this.messagesBlock.nativeElement as HTMLElement
107 element.scrollIntoView({ block: 'end', inline: 'nearest' })
108 })
109 },
110
111 err => this.notifier.error(err.message)
112 )
113 }
114
115}
diff --git a/client/src/app/shared/shared-moderation/abuse.service.ts b/client/src/app/shared/shared-moderation/abuse.service.ts
index 95ac16955..652d8370f 100644
--- a/client/src/app/shared/shared-moderation/abuse.service.ts
+++ b/client/src/app/shared/shared-moderation/abuse.service.ts
@@ -5,7 +5,7 @@ import { catchError, map } from 'rxjs/operators'
5import { HttpClient, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpParams } from '@angular/common/http'
6import { Injectable } from '@angular/core' 6import { Injectable } from '@angular/core'
7import { RestExtractor, RestPagination, RestService } from '@app/core' 7import { RestExtractor, RestPagination, RestService } from '@app/core'
8import { Abuse, AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, ResultList } from '@shared/models' 8import { AdminAbuse, AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, ResultList, UserAbuse, AbuseMessage } from '@shared/models'
9import { environment } from '../../../environments/environment' 9import { environment } from '../../../environments/environment'
10import { I18n } from '@ngx-translate/i18n-polyfill' 10import { I18n } from '@ngx-translate/i18n-polyfill'
11 11
@@ -20,11 +20,11 @@ export class AbuseService {
20 private restExtractor: RestExtractor 20 private restExtractor: RestExtractor
21 ) { } 21 ) { }
22 22
23 getAbuses (options: { 23 getAdminAbuses (options: {
24 pagination: RestPagination, 24 pagination: RestPagination,
25 sort: SortMeta, 25 sort: SortMeta,
26 search?: string 26 search?: string
27 }): Observable<ResultList<Abuse>> { 27 }): Observable<ResultList<AdminAbuse>> {
28 const { pagination, sort, search } = options 28 const { pagination, sort, search } = options
29 const url = AbuseService.BASE_ABUSE_URL 29 const url = AbuseService.BASE_ABUSE_URL
30 30
@@ -61,7 +61,7 @@ export class AbuseService {
61 params = this.restService.addObjectParams(params, filters) 61 params = this.restService.addObjectParams(params, filters)
62 } 62 }
63 63
64 return this.authHttp.get<ResultList<Abuse>>(url, { params }) 64 return this.authHttp.get<ResultList<AdminAbuse>>(url, { params })
65 .pipe( 65 .pipe(
66 catchError(res => this.restExtractor.handleError(res)) 66 catchError(res => this.restExtractor.handleError(res))
67 ) 67 )
@@ -79,7 +79,7 @@ export class AbuseService {
79 ) 79 )
80 } 80 }
81 81
82 updateAbuse (abuse: Abuse, abuseUpdate: AbuseUpdate) { 82 updateAbuse (abuse: AdminAbuse, abuseUpdate: AbuseUpdate) {
83 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id 83 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id
84 84
85 return this.authHttp.put(url, abuseUpdate) 85 return this.authHttp.put(url, abuseUpdate)
@@ -89,7 +89,7 @@ export class AbuseService {
89 ) 89 )
90 } 90 }
91 91
92 removeAbuse (abuse: Abuse) { 92 removeAbuse (abuse: AdminAbuse) {
93 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id 93 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id
94 94
95 return this.authHttp.delete(url) 95 return this.authHttp.delete(url)
@@ -99,6 +99,35 @@ export class AbuseService {
99 ) 99 )
100 } 100 }
101 101
102 addAbuseMessage (abuse: UserAbuse, message: string) {
103 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id + '/messages'
104
105 return this.authHttp.post(url, { message })
106 .pipe(
107 map(this.restExtractor.extractDataBool),
108 catchError(res => this.restExtractor.handleError(res))
109 )
110 }
111
112 listAbuseMessages (abuse: UserAbuse) {
113 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id + '/messages'
114
115 return this.authHttp.get<ResultList<AbuseMessage>>(url)
116 .pipe(
117 catchError(res => this.restExtractor.handleError(res))
118 )
119 }
120
121 deleteAbuseMessage (abuse: UserAbuse, abuseMessage: AbuseMessage) {
122 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id + '/messages/' + abuseMessage.id
123
124 return this.authHttp.delete(url)
125 .pipe(
126 map(this.restExtractor.extractDataBool),
127 catchError(res => this.restExtractor.handleError(res))
128 )
129 }
130
102 getPrefefinedReasons (type: AbuseFilter) { 131 getPrefefinedReasons (type: AbuseFilter) {
103 let reasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [ 132 let reasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [
104 { 133 {
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts
index 41c910ffe..c8082d4b3 100644
--- a/client/src/app/shared/shared-moderation/index.ts
+++ b/client/src/app/shared/shared-moderation/index.ts
@@ -1,5 +1,6 @@
1export * from './report-modals' 1export * from './report-modals'
2 2
3export * from './abuse-message-modal.component'
3export * from './abuse.service' 4export * from './abuse.service'
4export * from './account-block.model' 5export * from './account-block.model'
5export * from './account-blocklist.component' 6export * from './account-blocklist.component'
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 8fa9ee794..b5b6daf27 100644
--- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -4,15 +4,16 @@ import { SharedFormModule } from '../shared-forms/shared-form.module'
4import { SharedGlobalIconModule } from '../shared-icons' 4import { SharedGlobalIconModule } from '../shared-icons'
5import { SharedMainModule } from '../shared-main/shared-main.module' 5import { SharedMainModule } from '../shared-main/shared-main.module'
6import { SharedVideoCommentModule } from '../shared-video-comment' 6import { SharedVideoCommentModule } from '../shared-video-comment'
7import { AbuseMessageModalComponent } from './abuse-message-modal.component'
7import { AbuseService } from './abuse.service' 8import { AbuseService } from './abuse.service'
8import { BatchDomainsModalComponent } from './batch-domains-modal.component' 9import { BatchDomainsModalComponent } from './batch-domains-modal.component'
9import { BlocklistService } from './blocklist.service' 10import { BlocklistService } from './blocklist.service'
10import { BulkService } from './bulk.service' 11import { BulkService } from './bulk.service'
12import { AccountReportComponent, CommentReportComponent, VideoReportComponent } from './report-modals'
11import { UserBanModalComponent } from './user-ban-modal.component' 13import { UserBanModalComponent } from './user-ban-modal.component'
12import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' 14import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
13import { VideoBlockComponent } from './video-block.component' 15import { VideoBlockComponent } from './video-block.component'
14import { VideoBlockService } from './video-block.service' 16import { VideoBlockService } from './video-block.service'
15import { VideoReportComponent, AccountReportComponent, CommentReportComponent } from './report-modals'
16 17
17@NgModule({ 18@NgModule({
18 imports: [ 19 imports: [
@@ -29,7 +30,8 @@ import { VideoReportComponent, AccountReportComponent, CommentReportComponent }
29 VideoReportComponent, 30 VideoReportComponent,
30 BatchDomainsModalComponent, 31 BatchDomainsModalComponent,
31 CommentReportComponent, 32 CommentReportComponent,
32 AccountReportComponent 33 AccountReportComponent,
34 AbuseMessageModalComponent
33 ], 35 ],
34 36
35 exports: [ 37 exports: [
@@ -39,7 +41,8 @@ import { VideoReportComponent, AccountReportComponent, CommentReportComponent }
39 VideoReportComponent, 41 VideoReportComponent,
40 BatchDomainsModalComponent, 42 BatchDomainsModalComponent,
41 CommentReportComponent, 43 CommentReportComponent,
42 AccountReportComponent 44 AccountReportComponent,
45 AbuseMessageModalComponent
43 ], 46 ],
44 47
45 providers: [ 48 providers: [