diff options
author | Chocobozzz <me@florianbigard.com> | 2020-07-09 11:58:46 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-07-10 14:02:41 +0200 |
commit | 8ca56654a176ee8f350d31282c6cac4a59f58499 (patch) | |
tree | 6e52ed0d8410abfceb62bcb6230b8ed50bd6c574 /client/src/app/+admin | |
parent | 310b5219b38427f0c2c7ba57225afdd8f3064380 (diff) | |
download | PeerTube-8ca56654a176ee8f350d31282c6cac4a59f58499.tar.gz PeerTube-8ca56654a176ee8f350d31282c6cac4a59f58499.tar.zst PeerTube-8ca56654a176ee8f350d31282c6cac4a59f58499.zip |
Add ability to report comments in front end
Diffstat (limited to 'client/src/app/+admin')
8 files changed, 186 insertions, 88 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index 87ed33a45..4345d1945 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts | |||
@@ -47,8 +47,8 @@ export class AdminComponent implements OnInit { | |||
47 | 47 | ||
48 | if (this.hasAbusesRight()) { | 48 | if (this.hasAbusesRight()) { |
49 | moderationItems.children.push({ | 49 | moderationItems.children.push({ |
50 | label: this.i18n('Video reports'), | 50 | label: this.i18n('Reports'), |
51 | routerLink: '/admin/moderation/video-abuses/list', | 51 | routerLink: '/admin/moderation/abuses/list', |
52 | iconName: 'flag' | 52 | iconName: 'flag' |
53 | }) | 53 | }) |
54 | } | 54 | } |
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html index d031ea8ed..cba9cfb73 100644 --- a/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html +++ b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html | |||
@@ -3,10 +3,13 @@ | |||
3 | <div class="col-8"> | 3 | <div class="col-8"> |
4 | 4 | ||
5 | <!-- report metadata --> | 5 | <!-- report metadata --> |
6 | <div class="d-flex"> | 6 | <div class="d-flex" *ngIf="abuse.reporterAccount"> |
7 | <span class="col-3 moderation-expanded-label" i18n>Reporter</span> | 7 | <span class="col-3 moderation-expanded-label" i18n>Reporter</span> |
8 | |||
8 | <span class="col-9 moderation-expanded-text"> | 9 | <span class="col-9 moderation-expanded-text"> |
9 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" class="chip"> | 10 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" |
11 | class="chip" | ||
12 | > | ||
10 | <img | 13 | <img |
11 | class="avatar" | 14 | class="avatar" |
12 | [src]="abuse.reporterAccount.avatar?.path" | 15 | [src]="abuse.reporterAccount.avatar?.path" |
@@ -17,27 +20,35 @@ | |||
17 | <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span> | 20 | <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span> |
18 | </div> | 21 | </div> |
19 | </a> | 22 | </a> |
20 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | 23 | |
24 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" | ||
25 | class="ml-auto text-muted abuse-details-links" i18n | ||
26 | > | ||
21 | {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> | 27 | {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> |
22 | </a> | 28 | </a> |
23 | </span> | 29 | </span> |
24 | </div> | 30 | </div> |
25 | 31 | ||
26 | <div class="d-flex"> | 32 | <div class="d-flex" *ngIf="abuse.flaggedAccount"> |
27 | <span class="col-3 moderation-expanded-label" i18n>Reportee</span> | 33 | <span class="col-3 moderation-expanded-label" i18n>Reportee</span> |
28 | <span class="col-9 moderation-expanded-text"> | 34 | <span class="col-9 moderation-expanded-text"> |
29 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.video.channel.ownerAccount.displayName + '"' }" class="chip"> | 35 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" |
36 | class="chip" | ||
37 | > | ||
30 | <img | 38 | <img |
31 | class="avatar" | 39 | class="avatar" |
32 | [src]="abuse.video.channel.ownerAccount?.avatar?.path" | 40 | [src]="abuse.flaggedAccount?.avatar?.path" |
33 | (error)="switchToDefaultAvatar($event)" | 41 | (error)="switchToDefaultAvatar($event)" |
34 | alt="Avatar" | 42 | alt="Avatar" |
35 | > | 43 | > |
36 | <div> | 44 | <div> |
37 | <span class="text-muted">{{ abuse.video.channel.ownerAccount ? abuse.video.channel.ownerAccount.nameWithHost : '' }}</span> | 45 | <span class="text-muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span> |
38 | </div> | 46 | </div> |
39 | </a> | 47 | </a> |
40 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.video.channel.ownerAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | 48 | |
49 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" | ||
50 | class="ml-auto text-muted abuse-details-links" i18n | ||
51 | > | ||
41 | {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> | 52 | {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> |
42 | </a> | 53 | </a> |
43 | </span> | 54 | </span> |
@@ -45,7 +56,7 @@ | |||
45 | 56 | ||
46 | <div class="d-flex" *ngIf="abuse.updatedAt"> | 57 | <div class="d-flex" *ngIf="abuse.updatedAt"> |
47 | <span class="col-3 moderation-expanded-label" i18n>Updated</span> | 58 | <span class="col-3 moderation-expanded-label" i18n>Updated</span> |
48 | <time class="col-9 moderation-expanded-text video-details-date-updated">{{ abuse.updatedAt | date: 'medium' }}</time> | 59 | <time class="col-9 moderation-expanded-text abuse-details-date-updated">{{ abuse.updatedAt | date: 'medium' }}</time> |
49 | </div> | 60 | </div> |
50 | 61 | ||
51 | <!-- report text --> | 62 | <!-- report text --> |
@@ -60,34 +71,45 @@ | |||
60 | <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex"> | 71 | <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex"> |
61 | <span class="col-3"></span> | 72 | <span class="col-3"></span> |
62 | <span class="col-9"> | 73 | <span class="col-9"> |
63 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" *ngFor="let reason of getPredefinedReasons()"> | 74 | <a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ '/admin/moderation/abuses/list' ]" |
75 | [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" | ||
76 | > | ||
64 | <div>{{ reason.label }}</div> | 77 | <div>{{ reason.label }}</div> |
65 | </a> | 78 | </a> |
66 | </span> | 79 | </span> |
67 | </div> | 80 | </div> |
68 | 81 | ||
69 | <div *ngIf="abuse.startAt" class="mt-2 d-flex"> | 82 | <div *ngIf="abuse.video?.startAt" class="mt-2 d-flex"> |
70 | <span class="col-3 moderation-expanded-label" i18n>Reported part</span> | 83 | <span class="col-3 moderation-expanded-label" i18n>Reported part</span> |
71 | <span class="col-9"> | 84 | <span class="col-9"> |
72 | {{ startAt }}<ng-container *ngIf="abuse.endAt"> - {{ endAt }}</ng-container> | 85 | {{ startAt }}<ng-container *ngIf="abuse.video.endAt"> - {{ endAt }}</ng-container> |
73 | </span> | 86 | </span> |
74 | </div> | 87 | </div> |
75 | 88 | ||
76 | <div class="mt-3 d-flex" *ngIf="abuse.moderationComment"> | 89 | <div class="mt-3 d-flex" *ngIf="abuse.moderationComment"> |
77 | <span class="col-3 moderation-expanded-label" i18n>Note</span> | 90 | <span class="col-3 moderation-expanded-label" i18n>Note</span> |
78 | <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.moderationCommentHtml"></span> | 91 | <span class="col-9 moderation-expanded-text d-block" [innerHTML]="abuse.moderationCommentHtml"></span> |
79 | </div> | 92 | </div> |
80 | 93 | ||
81 | </div> | 94 | </div> |
82 | 95 | ||
83 | <!-- report right part (video details) --> | 96 | <!-- report right part (video/comment details) --> |
84 | <div class="col-4"> | 97 | <div class="col-4"> |
85 | <div class="screenratio"> | 98 | <div *ngIf="abuse.video" class="screenratio"> |
86 | <div *ngIf="abuse.video.deleted || abuse.video.blacklisted"> | 99 | <div> |
87 | <span i18n *ngIf="abuse.video.deleted">The video was deleted</span> | 100 | <span i18n *ngIf="abuse.video.deleted">The video was deleted</span> |
88 | <span i18n *ngIf="!abuse.video.deleted">The video was blocked</span> | 101 | <span i18n *ngIf="!abuse.video.deleted">The video was blocked</span> |
89 | </div> | 102 | </div> |
103 | |||
90 | <div *ngIf="!abuse.video.deleted && !abuse.video.blacklisted" [innerHTML]="abuse.embedHtml"></div> | 104 | <div *ngIf="!abuse.video.deleted && !abuse.video.blacklisted" [innerHTML]="abuse.embedHtml"></div> |
91 | </div> | 105 | </div> |
106 | |||
107 | <div *ngIf="abuse.comment" class="comment-html"> | ||
108 | <div> | ||
109 | <strong i18n>Comment:</strong> | ||
110 | </div> | ||
111 | |||
112 | <div [innerHTML]="abuse.commentHtml"></div> | ||
113 | </div> | ||
92 | </div> | 114 | </div> |
93 | </div> | 115 | </div> |
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts index 8f87630b8..fb0f65764 100644 --- a/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts +++ b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts | |||
@@ -31,15 +31,16 @@ export class AbuseDetailsComponent { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | get startAt () { | 33 | get startAt () { |
34 | return durationToString(this.abuse.startAt) | 34 | return durationToString(this.abuse.video.startAt) |
35 | } | 35 | } |
36 | 36 | ||
37 | get endAt () { | 37 | get endAt () { |
38 | return durationToString(this.abuse.endAt) | 38 | return durationToString(this.abuse.video.endAt) |
39 | } | 39 | } |
40 | 40 | ||
41 | getPredefinedReasons () { | 41 | getPredefinedReasons () { |
42 | if (!this.abuse.predefinedReasons) return [] | 42 | if (!this.abuse.predefinedReasons) return [] |
43 | |||
43 | return this.abuse.predefinedReasons.map(r => ({ | 44 | return this.abuse.predefinedReasons.map(r => ({ |
44 | id: r, | 45 | id: r, |
45 | label: this.predefinedReasonsTranslations[r] | 46 | label: this.predefinedReasonsTranslations[r] |
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html index 333438269..1ad73e38a 100644 --- a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html +++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html | |||
@@ -38,7 +38,7 @@ | |||
38 | <tr> <!-- header --> | 38 | <tr> <!-- header --> |
39 | <th style="width: 40px;"></th> | 39 | <th style="width: 40px;"></th> |
40 | <th style="width: 20%;" pResizableColumn i18n>Reporter</th> | 40 | <th style="width: 20%;" pResizableColumn i18n>Reporter</th> |
41 | <th i18n>Video</th> | 41 | <th i18n>Video/Comment/Account</th> |
42 | <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> | 42 | <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> |
43 | <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th> | 43 | <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th> |
44 | <th style="width: 150px;"></th> | 44 | <th style="width: 150px;"></th> |
@@ -54,7 +54,7 @@ | |||
54 | </td> | 54 | </td> |
55 | 55 | ||
56 | <td> | 56 | <td> |
57 | <a [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> | 57 | <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> |
58 | <div class="chip two-lines"> | 58 | <div class="chip two-lines"> |
59 | <img | 59 | <img |
60 | class="avatar" | 60 | class="avatar" |
@@ -64,54 +64,73 @@ | |||
64 | > | 64 | > |
65 | <div> | 65 | <div> |
66 | {{ abuse.reporterAccount.displayName }} | 66 | {{ abuse.reporterAccount.displayName }} |
67 | <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span> | 67 | <span>{{ abuse.reporterAccount.nameWithHost }}</span> |
68 | </div> | 68 | </div> |
69 | </div> | 69 | </div> |
70 | </a> | 70 | </a> |
71 | |||
72 | <span i18n *ngIf="!abuse.reporterAccount"> | ||
73 | Deleted account | ||
74 | </span> | ||
71 | </td> | 75 | </td> |
72 | 76 | ||
73 | <td *ngIf="!abuse.video.deleted"> | 77 | <ng-container *ngIf="abuse.video"> |
74 | <a [href]="getVideoUrl(abuse)" class="video-table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer"> | 78 | |
75 | <div class="video-table-video"> | 79 | <td *ngIf="!abuse.video.deleted"> |
76 | <div class="video-table-video-image"> | 80 | <a [href]="getVideoUrl(abuse)" class="table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer"> |
77 | <img [src]="abuse.video.thumbnailPath"> | 81 | <div class="table-video"> |
78 | <span | 82 | <div class="table-video-image"> |
79 | class="video-table-video-image-label" *ngIf="abuse.count > 1" | 83 | <img [src]="abuse.video.thumbnailPath"> |
80 | i18n-title title="This video has been reported multiple times." | 84 | <span |
81 | > | 85 | class="table-video-image-label" *ngIf="abuse.count > 1" |
82 | {{ abuse.nth }}/{{ abuse.count }} | 86 | i18n-title title="This video has been reported multiple times." |
83 | </span> | 87 | > |
88 | {{ abuse.nth }}/{{ abuse.count }} | ||
89 | </span> | ||
90 | </div> | ||
91 | |||
92 | <div class="table-video-text"> | ||
93 | <div> | ||
94 | <span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span> | ||
95 | <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span> | ||
96 | {{ abuse.video.name }} | ||
97 | </div> | ||
98 | <div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> | ||
99 | </div> | ||
84 | </div> | 100 | </div> |
85 | <div class="video-table-video-text"> | 101 | </a> |
102 | </td> | ||
103 | |||
104 | <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse"> | ||
105 | <div class="table-video" i18n-title title="Video was deleted"> | ||
106 | <div class="table-video-image"> | ||
107 | <span i18n>Deleted</span> | ||
108 | </div> | ||
109 | |||
110 | <div class="table-video-text"> | ||
86 | <div> | 111 | <div> |
87 | <span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span> | ||
88 | <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span> | ||
89 | {{ abuse.video.name }} | 112 | {{ abuse.video.name }} |
113 | <span class="glyphicon glyphicon-trash"></span> | ||
90 | </div> | 114 | </div> |
91 | <div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> | 115 | <div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> |
92 | </div> | 116 | </div> |
93 | </div> | 117 | </div> |
94 | </a> | 118 | </td> |
95 | </td> | 119 | </ng-container> |
96 | 120 | ||
97 | <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse"> | 121 | <ng-container *ngIf="abuse.comment"> |
98 | <div class="video-table-video" i18n-title title="Video was deleted"> | 122 | <td> |
99 | <div class="video-table-video-image"> | 123 | <a [href]="getCommentUrl(abuse)" [innerHTML]="abuse.truncatedCommentHtml" class="table-comment-link" |
100 | <span i18n>Deleted</span> | 124 | [title]="abuse.comment.video.name" target="_blank" rel="noopener noreferrer" |
101 | </div> | 125 | ></a> |
102 | <div class="video-table-video-text"> | 126 | |
103 | <div> | 127 | <div class="comment-flagged-account" *ngIf="abuse.flaggedAccount">by {{ abuse.flaggedAccount.displayName }}</div> |
104 | {{ abuse.video.name }} | 128 | </td> |
105 | <span class="glyphicon glyphicon-trash"></span> | 129 | </ng-container> |
106 | </div> | ||
107 | <div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> | ||
108 | </div> | ||
109 | </div> | ||
110 | </td> | ||
111 | 130 | ||
112 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> | 131 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> |
113 | 132 | ||
114 | <td class="c-hand video-abuse-states" [pRowToggler]="abuse"> | 133 | <td class="c-hand abuse-states" [pRowToggler]="abuse"> |
115 | <span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span> | 134 | <span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span> |
116 | <span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span> | 135 | <span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span> |
117 | <span *ngIf="abuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment" class="glyphicon glyphicon-comment"></span> | 136 | <span *ngIf="abuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment" class="glyphicon glyphicon-comment"></span> |
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss index 8eee15b64..c22f98c47 100644 --- a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss +++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss | |||
@@ -10,7 +10,7 @@ | |||
10 | @include disable-default-a-behaviour; | 10 | @include disable-default-a-behaviour; |
11 | } | 11 | } |
12 | 12 | ||
13 | .video-abuse-states .glyphicon-comment { | 13 | .abuse-states .glyphicon-comment { |
14 | margin-left: 0.5rem; | 14 | margin-left: 0.5rem; |
15 | } | 15 | } |
16 | 16 | ||
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts index 427ec4d5d..1ea61ed37 100644 --- a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts +++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts | |||
@@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api' | |||
2 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' | 2 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' |
3 | import { environment } from 'src/environments/environment' | 3 | import { environment } from 'src/environments/environment' |
4 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' | 4 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' |
5 | import { DomSanitizer } from '@angular/platform-browser' | 5 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
6 | import { ActivatedRoute, Params, Router } from '@angular/router' | 6 | import { ActivatedRoute, Params, Router } from '@angular/router' |
7 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' | 7 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' |
8 | import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 8 | import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
@@ -10,15 +10,20 @@ import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/s | |||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { Abuse, AbuseState } from '@shared/models' | 11 | import { Abuse, AbuseState } from '@shared/models' |
12 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 12 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
13 | import truncate from 'lodash-es/truncate' | ||
13 | 14 | ||
14 | export type ProcessedAbuse = Abuse & { | 15 | export type ProcessedAbuse = Abuse & { |
15 | moderationCommentHtml?: string, | 16 | moderationCommentHtml?: string, |
16 | reasonHtml?: string | 17 | reasonHtml?: string |
17 | embedHtml?: string | 18 | embedHtml?: SafeHtml |
18 | updatedAt?: Date | 19 | updatedAt?: Date |
19 | 20 | ||
20 | // override bare server-side definitions with rich client-side definitions | 21 | // override bare server-side definitions with rich client-side definitions |
21 | reporterAccount: Account | 22 | reporterAccount?: Account |
23 | flaggedAccount?: Account | ||
24 | |||
25 | truncatedCommentHtml?: string | ||
26 | commentHtml?: string | ||
22 | 27 | ||
23 | video: Abuse['video'] & { | 28 | video: Abuse['video'] & { |
24 | channel: Abuse['video']['channel'] & { | 29 | channel: Abuse['video']['channel'] & { |
@@ -92,11 +97,11 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
92 | { | 97 | { |
93 | label: this.i18n('Actions for the video'), | 98 | label: this.i18n('Actions for the video'), |
94 | isHeader: true, | 99 | isHeader: true, |
95 | isDisplayed: abuse => !abuse.video.deleted | 100 | isDisplayed: abuse => abuse.video && !abuse.video.deleted |
96 | }, | 101 | }, |
97 | { | 102 | { |
98 | label: this.i18n('Block video'), | 103 | label: this.i18n('Block video'), |
99 | isDisplayed: abuse => !abuse.video.deleted && !abuse.video.blacklisted, | 104 | isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted, |
100 | handler: abuse => { | 105 | handler: abuse => { |
101 | this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true) | 106 | this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true) |
102 | .subscribe( | 107 | .subscribe( |
@@ -112,7 +117,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
112 | }, | 117 | }, |
113 | { | 118 | { |
114 | label: this.i18n('Unblock video'), | 119 | label: this.i18n('Unblock video'), |
115 | isDisplayed: abuse => !abuse.video.deleted && abuse.video.blacklisted, | 120 | isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted, |
116 | handler: abuse => { | 121 | handler: abuse => { |
117 | this.videoBlocklistService.unblockVideo(abuse.video.id) | 122 | this.videoBlocklistService.unblockVideo(abuse.video.id) |
118 | .subscribe( | 123 | .subscribe( |
@@ -128,7 +133,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
128 | }, | 133 | }, |
129 | { | 134 | { |
130 | label: this.i18n('Delete video'), | 135 | label: this.i18n('Delete video'), |
131 | isDisplayed: abuse => !abuse.video.deleted, | 136 | isDisplayed: abuse => abuse.video && !abuse.video.deleted, |
132 | handler: async abuse => { | 137 | handler: async abuse => { |
133 | const res = await this.confirmService.confirm( | 138 | const res = await this.confirmService.confirm( |
134 | this.i18n('Do you really want to delete this video?'), | 139 | this.i18n('Do you really want to delete this video?'), |
@@ -152,10 +157,12 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
152 | [ | 157 | [ |
153 | { | 158 | { |
154 | label: this.i18n('Actions for the reporter'), | 159 | label: this.i18n('Actions for the reporter'), |
155 | isHeader: true | 160 | isHeader: true, |
161 | isDisplayed: abuse => !!abuse.reporterAccount | ||
156 | }, | 162 | }, |
157 | { | 163 | { |
158 | label: this.i18n('Mute reporter'), | 164 | label: this.i18n('Mute reporter'), |
165 | isDisplayed: abuse => !!abuse.reporterAccount, | ||
159 | handler: async abuse => { | 166 | handler: async abuse => { |
160 | const account = abuse.reporterAccount as Account | 167 | const account = abuse.reporterAccount as Account |
161 | 168 | ||
@@ -175,7 +182,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
175 | }, | 182 | }, |
176 | { | 183 | { |
177 | label: this.i18n('Mute server'), | 184 | label: this.i18n('Mute server'), |
178 | isDisplayed: abuse => !abuse.reporterAccount.userId, | 185 | isDisplayed: abuse => abuse.reporterAccount && !abuse.reporterAccount.userId, |
179 | handler: async abuse => { | 186 | handler: async abuse => { |
180 | this.blocklistService.blockServerByInstance(abuse.reporterAccount.host) | 187 | this.blocklistService.blockServerByInstance(abuse.reporterAccount.host) |
181 | .subscribe( | 188 | .subscribe( |
@@ -231,7 +238,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
231 | const queryParams: Params = {} | 238 | const queryParams: Params = {} |
232 | if (search) Object.assign(queryParams, { search }) | 239 | if (search) Object.assign(queryParams, { search }) |
233 | 240 | ||
234 | this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams }) | 241 | this.router.navigate([ '/admin/moderation/abuses/list' ], { queryParams }) |
235 | } | 242 | } |
236 | 243 | ||
237 | resetTableFilter () { | 244 | resetTableFilter () { |
@@ -253,6 +260,10 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
253 | return Video.buildClientUrl(abuse.video.uuid) | 260 | return Video.buildClientUrl(abuse.video.uuid) |
254 | } | 261 | } |
255 | 262 | ||
263 | getCommentUrl (abuse: Abuse) { | ||
264 | return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId | ||
265 | } | ||
266 | |||
256 | getVideoEmbed (abuse: Abuse) { | 267 | getVideoEmbed (abuse: Abuse) { |
257 | return buildVideoEmbed( | 268 | return buildVideoEmbed( |
258 | buildVideoLink({ | 269 | buildVideoLink({ |
@@ -300,23 +311,45 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
300 | }).subscribe( | 311 | }).subscribe( |
301 | async resultList => { | 312 | async resultList => { |
302 | this.totalRecords = resultList.total | 313 | this.totalRecords = resultList.total |
303 | const abuses = [] | ||
304 | 314 | ||
305 | for (const abuse of resultList.data) { | 315 | this.abuses = [] |
306 | Object.assign(abuse, { | 316 | |
307 | reasonHtml: await this.toHtml(abuse.reason), | 317 | for (const a of resultList.data) { |
308 | moderationCommentHtml: await this.toHtml(abuse.moderationComment), | 318 | const abuse = a as ProcessedAbuse |
309 | embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), | 319 | |
310 | reporterAccount: new Account(abuse.reporterAccount) | 320 | abuse.reasonHtml = await this.toHtml(abuse.reason) |
311 | }) | 321 | abuse.moderationCommentHtml = await this.toHtml(abuse.moderationComment) |
322 | |||
323 | if (abuse.video) { | ||
324 | abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)) | ||
325 | |||
326 | if (abuse.video.channel?.ownerAccount) { | ||
327 | abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) | ||
328 | } | ||
329 | } | ||
330 | |||
331 | if (abuse.comment) { | ||
332 | if (abuse.comment.deleted) { | ||
333 | abuse.truncatedCommentHtml = abuse.commentHtml = this.i18n('Deleted comment') | ||
334 | } else { | ||
335 | const truncated = truncate(abuse.comment.text, { length: 100 }) | ||
336 | abuse.truncatedCommentHtml = await this.markdownRenderer.textMarkdownToHTML(truncated, true) | ||
337 | abuse.commentHtml = await this.markdownRenderer.textMarkdownToHTML(abuse.comment.text, true) | ||
338 | } | ||
339 | } | ||
340 | |||
341 | if (abuse.reporterAccount) { | ||
342 | abuse.reporterAccount = new Account(abuse.reporterAccount) | ||
343 | } | ||
344 | |||
345 | if (abuse.flaggedAccount) { | ||
346 | abuse.flaggedAccount = new Account(abuse.flaggedAccount) | ||
347 | } | ||
312 | 348 | ||
313 | if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) | ||
314 | if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt | 349 | if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt |
315 | 350 | ||
316 | abuses.push(abuse as ProcessedAbuse) | 351 | this.abuses.push(abuse) |
317 | } | 352 | } |
318 | |||
319 | this.abuses = abuses | ||
320 | }, | 353 | }, |
321 | 354 | ||
322 | err => this.notifier.error(err.message) | 355 | err => this.notifier.error(err.message) |
diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index 0ec420af9..f73c71dc5 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss | |||
@@ -25,18 +25,18 @@ | |||
25 | vertical-align: top; | 25 | vertical-align: top; |
26 | text-align: right; | 26 | text-align: right; |
27 | } | 27 | } |
28 | 28 | ||
29 | .moderation-expanded-text { | 29 | .moderation-expanded-text { |
30 | display: inline-flex; | 30 | display: inline-flex; |
31 | word-wrap: break-word; | 31 | word-wrap: break-word; |
32 | 32 | ||
33 | ::ng-deep p:last-child { | 33 | ::ng-deep p:last-child { |
34 | margin-bottom: 0px !important; | 34 | margin-bottom: 0px !important; |
35 | } | 35 | } |
36 | } | 36 | } |
37 | } | 37 | } |
38 | 38 | ||
39 | .video-table-states { | 39 | .table-states { |
40 | & > :not(:first-child) { | 40 | & > :not(:first-child) { |
41 | margin-left: .4rem; | 41 | margin-left: .4rem; |
42 | } | 42 | } |
@@ -59,6 +59,7 @@ p-calendar { | |||
59 | .screenratio { | 59 | .screenratio { |
60 | div { | 60 | div { |
61 | @include miniature-thumbnail; | 61 | @include miniature-thumbnail; |
62 | |||
62 | display: inline-flex; | 63 | display: inline-flex; |
63 | justify-content: center; | 64 | justify-content: center; |
64 | align-items: center; | 65 | align-items: center; |
@@ -72,6 +73,11 @@ p-calendar { | |||
72 | }; | 73 | }; |
73 | } | 74 | } |
74 | 75 | ||
76 | .comment-html { | ||
77 | background-color: #ececec; | ||
78 | padding: 10px; | ||
79 | } | ||
80 | |||
75 | .chip { | 81 | .chip { |
76 | @include chip; | 82 | @include chip; |
77 | } | 83 | } |
@@ -83,16 +89,32 @@ my-action-dropdown.show { | |||
83 | } | 89 | } |
84 | 90 | ||
85 | 91 | ||
86 | .video-table-video-link { | 92 | .table-video-link { |
87 | @include disable-outline; | 93 | @include disable-outline; |
94 | |||
88 | position: relative; | 95 | position: relative; |
89 | top: 3px; | 96 | top: 3px; |
90 | } | 97 | } |
91 | 98 | ||
92 | .video-table-video { | 99 | .table-comment-link { |
100 | @include disable-outline; | ||
101 | |||
102 | color: var(--mainForegroundColor); | ||
103 | |||
104 | ::ng-deep p:last-child { | ||
105 | margin: 0; | ||
106 | } | ||
107 | } | ||
108 | |||
109 | .comment-flagged-account { | ||
110 | font-size: 11px; | ||
111 | color: var(--greyForegroundColor); | ||
112 | } | ||
113 | |||
114 | .table-video { | ||
93 | display: inline-flex; | 115 | display: inline-flex; |
94 | 116 | ||
95 | .video-table-video-image { | 117 | .table-video-image { |
96 | @include miniature-thumbnail; | 118 | @include miniature-thumbnail; |
97 | 119 | ||
98 | $image-height: 45px; | 120 | $image-height: 45px; |
@@ -118,7 +140,7 @@ my-action-dropdown.show { | |||
118 | color: pvar(--inputPlaceholderColor); | 140 | color: pvar(--inputPlaceholderColor); |
119 | } | 141 | } |
120 | 142 | ||
121 | .video-table-video-image-label { | 143 | .table-video-image-label { |
122 | @include static-thumbnail-overlay; | 144 | @include static-thumbnail-overlay; |
123 | position: absolute; | 145 | position: absolute; |
124 | border-radius: 3px; | 146 | border-radius: 3px; |
@@ -130,7 +152,7 @@ my-action-dropdown.show { | |||
130 | } | 152 | } |
131 | } | 153 | } |
132 | 154 | ||
133 | .video-table-video-text { | 155 | .table-video-text { |
134 | display: inline-flex; | 156 | display: inline-flex; |
135 | flex-direction: column; | 157 | flex-direction: column; |
136 | justify-content: center; | 158 | justify-content: center; |
@@ -145,7 +167,8 @@ my-action-dropdown.show { | |||
145 | } | 167 | } |
146 | 168 | ||
147 | div + div { | 169 | div + div { |
148 | font-size: 80%; | 170 | color: var(--greyForegroundColor); |
171 | font-size: 11px; | ||
149 | } | 172 | } |
150 | } | 173 | } |
151 | } | 174 | } |
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index 1e207e5e8..8a31a54dc 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts | |||
@@ -33,7 +33,7 @@ export const ModerationRoutes: Routes = [ | |||
33 | data: { | 33 | data: { |
34 | userRight: UserRight.MANAGE_ABUSES, | 34 | userRight: UserRight.MANAGE_ABUSES, |
35 | meta: { | 35 | meta: { |
36 | title: 'Video reports' | 36 | title: 'Reports' |
37 | } | 37 | } |
38 | } | 38 | } |
39 | }, | 39 | }, |