diff options
author | Chocobozzz <me@florianbigard.com> | 2020-07-27 11:40:30 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-07-31 11:35:19 +0200 |
commit | 94148c9028829b5576a5dcbfba2c7fb9cf6443d3 (patch) | |
tree | 2774f272329111abd03e8441ff936da11fb1a3f3 /client/src/app/shared/shared-moderation | |
parent | 441e453ae53e491b09c9b09b00b041788176ce64 (diff) | |
download | PeerTube-94148c9028829b5576a5dcbfba2c7fb9cf6443d3.tar.gz PeerTube-94148c9028829b5576a5dcbfba2c7fb9cf6443d3.tar.zst PeerTube-94148c9028829b5576a5dcbfba2c7fb9cf6443d3.zip |
Add abuse messages management in my account
Diffstat (limited to 'client/src/app/shared/shared-moderation')
8 files changed, 129 insertions, 247 deletions
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 deleted file mode 100644 index 67c6a3081..000000000 --- a/client/src/app/shared/shared-moderation/abuse-message-modal.component.html +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
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 deleted file mode 100644 index 89d6b88c1..000000000 --- a/client/src/app/shared/shared-moderation/abuse-message-modal.component.scss +++ /dev/null | |||
@@ -1,57 +0,0 @@ | |||
1 | @import 'variables'; | ||
2 | @import 'mixins'; | ||
3 | |||
4 | form { | ||
5 | margin: 20px 20px 0 0; | ||
6 | } | ||
7 | |||
8 | textarea { | ||
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 deleted file mode 100644 index 5822dfe1d..000000000 --- a/client/src/app/shared/shared-moderation/abuse-message-modal.component.ts +++ /dev/null | |||
@@ -1,115 +0,0 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Output, ViewChild, OnInit } from '@angular/core' | ||
2 | import { Notifier, AuthService } from '@app/core' | ||
3 | import { FormReactive, FormValidatorService, AbuseValidatorsService } from '@app/shared/shared-forms' | ||
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | import { AbuseMessage, UserAbuse } from '@shared/models' | ||
8 | import { 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 | }) | ||
15 | export 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 652d8370f..c1aa62023 100644 --- a/client/src/app/shared/shared-moderation/abuse.service.ts +++ b/client/src/app/shared/shared-moderation/abuse.service.ts | |||
@@ -5,13 +5,24 @@ import { catchError, map } from 'rxjs/operators' | |||
5 | import { HttpClient, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { Injectable } from '@angular/core' | 6 | import { Injectable } from '@angular/core' |
7 | import { RestExtractor, RestPagination, RestService } from '@app/core' | 7 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
8 | import { AdminAbuse, AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, ResultList, UserAbuse, AbuseMessage } from '@shared/models' | ||
9 | import { environment } from '../../../environments/environment' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
9 | import { | ||
10 | AbuseCreate, | ||
11 | AbuseFilter, | ||
12 | AbuseMessage, | ||
13 | AbusePredefinedReasonsString, | ||
14 | AbuseState, | ||
15 | AbuseUpdate, | ||
16 | AdminAbuse, | ||
17 | ResultList, | ||
18 | UserAbuse | ||
19 | } from '@shared/models' | ||
20 | import { environment } from '../../../environments/environment' | ||
11 | 21 | ||
12 | @Injectable() | 22 | @Injectable() |
13 | export class AbuseService { | 23 | export class AbuseService { |
14 | private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses' | 24 | private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses' |
25 | private static BASE_MY_ABUSE_URL = environment.apiUrl + '/api/v1/users/me/abuses' | ||
15 | 26 | ||
16 | constructor ( | 27 | constructor ( |
17 | private i18n: I18n, | 28 | private i18n: I18n, |
@@ -32,33 +43,7 @@ export class AbuseService { | |||
32 | params = this.restService.addRestGetParams(params, pagination, sort) | 43 | params = this.restService.addRestGetParams(params, pagination, sort) |
33 | 44 | ||
34 | if (search) { | 45 | if (search) { |
35 | const filters = this.restService.parseQueryStringFilter(search, { | 46 | params = this.buildParamsFromSearch(search, params) |
36 | id: { prefix: '#' }, | ||
37 | state: { | ||
38 | prefix: 'state:', | ||
39 | handler: v => { | ||
40 | if (v === 'accepted') return AbuseState.ACCEPTED | ||
41 | if (v === 'pending') return AbuseState.PENDING | ||
42 | if (v === 'rejected') return AbuseState.REJECTED | ||
43 | |||
44 | return undefined | ||
45 | } | ||
46 | }, | ||
47 | videoIs: { | ||
48 | prefix: 'videoIs:', | ||
49 | handler: v => { | ||
50 | if (v === 'deleted') return v | ||
51 | if (v === 'blacklisted') return v | ||
52 | |||
53 | return undefined | ||
54 | } | ||
55 | }, | ||
56 | searchReporter: { prefix: 'reporter:' }, | ||
57 | searchReportee: { prefix: 'reportee:' }, | ||
58 | predefinedReason: { prefix: 'tag:' } | ||
59 | }) | ||
60 | |||
61 | params = this.restService.addObjectParams(params, filters) | ||
62 | } | 47 | } |
63 | 48 | ||
64 | return this.authHttp.get<ResultList<AdminAbuse>>(url, { params }) | 49 | return this.authHttp.get<ResultList<AdminAbuse>>(url, { params }) |
@@ -67,6 +52,27 @@ export class AbuseService { | |||
67 | ) | 52 | ) |
68 | } | 53 | } |
69 | 54 | ||
55 | getUserAbuses (options: { | ||
56 | pagination: RestPagination, | ||
57 | sort: SortMeta, | ||
58 | search?: string | ||
59 | }): Observable<ResultList<UserAbuse>> { | ||
60 | const { pagination, sort, search } = options | ||
61 | const url = AbuseService.BASE_MY_ABUSE_URL | ||
62 | |||
63 | let params = new HttpParams() | ||
64 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
65 | |||
66 | if (search) { | ||
67 | params = this.buildParamsFromSearch(search, params) | ||
68 | } | ||
69 | |||
70 | return this.authHttp.get<ResultList<UserAbuse>>(url, { params }) | ||
71 | .pipe( | ||
72 | catchError(res => this.restExtractor.handleError(res)) | ||
73 | ) | ||
74 | } | ||
75 | |||
70 | reportVideo (parameters: AbuseCreate) { | 76 | reportVideo (parameters: AbuseCreate) { |
71 | const url = AbuseService.BASE_ABUSE_URL | 77 | const url = AbuseService.BASE_ABUSE_URL |
72 | 78 | ||
@@ -180,4 +186,33 @@ export class AbuseService { | |||
180 | return reasons | 186 | return reasons |
181 | } | 187 | } |
182 | 188 | ||
189 | private buildParamsFromSearch (search: string, params: HttpParams) { | ||
190 | const filters = this.restService.parseQueryStringFilter(search, { | ||
191 | id: { prefix: '#' }, | ||
192 | state: { | ||
193 | prefix: 'state:', | ||
194 | handler: v => { | ||
195 | if (v === 'accepted') return AbuseState.ACCEPTED | ||
196 | if (v === 'pending') return AbuseState.PENDING | ||
197 | if (v === 'rejected') return AbuseState.REJECTED | ||
198 | |||
199 | return undefined | ||
200 | } | ||
201 | }, | ||
202 | videoIs: { | ||
203 | prefix: 'videoIs:', | ||
204 | handler: v => { | ||
205 | if (v === 'deleted') return v | ||
206 | if (v === 'blacklisted') return v | ||
207 | |||
208 | return undefined | ||
209 | } | ||
210 | }, | ||
211 | searchReporter: { prefix: 'reporter:' }, | ||
212 | searchReportee: { prefix: 'reportee:' }, | ||
213 | predefinedReason: { prefix: 'tag:' } | ||
214 | }) | ||
215 | |||
216 | return this.restService.addObjectParams(params, filters) | ||
217 | } | ||
183 | } | 218 | } |
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts index c8082d4b3..41c910ffe 100644 --- a/client/src/app/shared/shared-moderation/index.ts +++ b/client/src/app/shared/shared-moderation/index.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | export * from './report-modals' | 1 | export * from './report-modals' |
2 | 2 | ||
3 | export * from './abuse-message-modal.component' | ||
4 | export * from './abuse.service' | 3 | export * from './abuse.service' |
5 | export * from './account-block.model' | 4 | export * from './account-block.model' |
6 | export * from './account-blocklist.component' | 5 | export * from './account-blocklist.component' |
diff --git a/client/src/app/shared/shared-moderation/moderation.scss b/client/src/app/shared/shared-moderation/moderation.scss new file mode 100644 index 000000000..260346dc5 --- /dev/null +++ b/client/src/app/shared/shared-moderation/moderation.scss | |||
@@ -0,0 +1,50 @@ | |||
1 | @import 'variables'; | ||
2 | @import 'mixins'; | ||
3 | @import 'miniature'; | ||
4 | |||
5 | .moderation-expanded { | ||
6 | font-size: 90%; | ||
7 | |||
8 | .moderation-expanded-label { | ||
9 | font-weight: $font-semibold; | ||
10 | display: inline-block; | ||
11 | vertical-align: top; | ||
12 | text-align: right; | ||
13 | } | ||
14 | |||
15 | .moderation-expanded-text { | ||
16 | display: inline-flex; | ||
17 | word-wrap: break-word; | ||
18 | |||
19 | ::ng-deep p:last-child { | ||
20 | margin-bottom: 0px !important; | ||
21 | } | ||
22 | } | ||
23 | } | ||
24 | |||
25 | .input-group { | ||
26 | @include peertube-input-group(300px); | ||
27 | |||
28 | .dropdown-toggle::after { | ||
29 | margin-left: 0; | ||
30 | } | ||
31 | } | ||
32 | |||
33 | .chip { | ||
34 | @include chip; | ||
35 | } | ||
36 | |||
37 | .caption { | ||
38 | justify-content: flex-end; | ||
39 | |||
40 | input { | ||
41 | @include peertube-input-text(250px); | ||
42 | flex-grow: 1; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | my-action-dropdown.show { | ||
47 | ::ng-deep .dropdown-root { | ||
48 | display: block !important; | ||
49 | } | ||
50 | } | ||
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.scss b/client/src/app/shared/shared-moderation/server-blocklist.component.scss index 9ddb76850..31db4d92b 100644 --- a/client/src/app/shared/shared-moderation/server-blocklist.component.scss +++ b/client/src/app/shared/shared-moderation/server-blocklist.component.scss | |||
@@ -32,3 +32,16 @@ a { | |||
32 | .block-button { | 32 | .block-button { |
33 | @include create-button; | 33 | @include create-button; |
34 | } | 34 | } |
35 | |||
36 | .caption { | ||
37 | justify-content: flex-end; | ||
38 | |||
39 | input { | ||
40 | @include peertube-input-text(250px); | ||
41 | flex-grow: 1; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | .chip { | ||
46 | @include chip; | ||
47 | } | ||
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 b5b6daf27..b1b98f8d0 100644 --- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts +++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts | |||
@@ -4,7 +4,6 @@ import { SharedFormModule } from '../shared-forms/shared-form.module' | |||
4 | import { SharedGlobalIconModule } from '../shared-icons' | 4 | import { SharedGlobalIconModule } from '../shared-icons' |
5 | import { SharedMainModule } from '../shared-main/shared-main.module' | 5 | import { SharedMainModule } from '../shared-main/shared-main.module' |
6 | import { SharedVideoCommentModule } from '../shared-video-comment' | 6 | import { SharedVideoCommentModule } from '../shared-video-comment' |
7 | import { AbuseMessageModalComponent } from './abuse-message-modal.component' | ||
8 | import { AbuseService } from './abuse.service' | 7 | import { AbuseService } from './abuse.service' |
9 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' | 8 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' |
10 | import { BlocklistService } from './blocklist.service' | 9 | import { BlocklistService } from './blocklist.service' |
@@ -30,8 +29,7 @@ import { VideoBlockService } from './video-block.service' | |||
30 | VideoReportComponent, | 29 | VideoReportComponent, |
31 | BatchDomainsModalComponent, | 30 | BatchDomainsModalComponent, |
32 | CommentReportComponent, | 31 | CommentReportComponent, |
33 | AccountReportComponent, | 32 | AccountReportComponent |
34 | AbuseMessageModalComponent | ||
35 | ], | 33 | ], |
36 | 34 | ||
37 | exports: [ | 35 | exports: [ |
@@ -41,8 +39,7 @@ import { VideoBlockService } from './video-block.service' | |||
41 | VideoReportComponent, | 39 | VideoReportComponent, |
42 | BatchDomainsModalComponent, | 40 | BatchDomainsModalComponent, |
43 | CommentReportComponent, | 41 | CommentReportComponent, |
44 | AccountReportComponent, | 42 | AccountReportComponent |
45 | AbuseMessageModalComponent | ||
46 | ], | 43 | ], |
47 | 44 | ||
48 | providers: [ | 45 | providers: [ |