aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+admin/admin.component.ts2
-rw-r--r--client/src/app/+admin/admin.module.ts10
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-details.component.html93
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts (renamed from client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts)22
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-list.component.html (renamed from client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html)64
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss (renamed from client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss)0
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts (renamed from client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts)127
-rw-r--r--client/src/app/+admin/moderation/abuse-list/index.ts3
-rw-r--r--client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html (renamed from client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html)0
-rw-r--r--client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss (renamed from client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.scss)0
-rw-r--r--client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts (renamed from client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts)18
-rw-r--r--client/src/app/+admin/moderation/index.ts2
-rw-r--r--client/src/app/+admin/moderation/moderation.routes.ts15
-rw-r--r--client/src/app/+admin/moderation/video-abuse-list/index.ts2
-rw-r--r--client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html93
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts2
-rw-r--r--client/src/app/menu/menu.component.ts4
-rw-r--r--client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts (renamed from client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts)10
-rw-r--r--client/src/app/shared/shared-forms/form-validators/index.ts2
-rw-r--r--client/src/app/shared/shared-forms/shared-form.module.ts4
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts2
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts26
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.html8
-rw-r--r--client/src/app/shared/shared-moderation/abuse.service.ts (renamed from client/src/app/shared/shared-moderation/video-abuse.service.ts)32
-rw-r--r--client/src/app/shared/shared-moderation/index.ts2
-rw-r--r--client/src/app/shared/shared-moderation/shared-moderation.module.ts4
-rw-r--r--client/src/app/shared/shared-moderation/video-report.component.ts29
27 files changed, 300 insertions, 276 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index 6f340884f..1e137e63e 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -91,7 +91,7 @@ export class AdminComponent implements OnInit {
91 } 91 }
92 92
93 hasVideoAbusesRight () { 93 hasVideoAbusesRight () {
94 return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES) 94 return this.auth.getUser().hasRight(UserRight.MANAGE_ABUSES)
95 } 95 }
96 96
97 hasVideoBlocklistRight () { 97 hasVideoBlocklistRight () {
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 728227a84..c59bd2927 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -14,10 +14,10 @@ import { FollowersListComponent, FollowsComponent, VideoRedundanciesListComponen
14import { FollowingListComponent } from './follows/following-list/following-list.component' 14import { FollowingListComponent } from './follows/following-list/following-list.component'
15import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component' 15import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component'
16import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component' 16import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component'
17import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlockListComponent } from './moderation' 17import { ModerationCommentModalComponent, AbuseListComponent, VideoBlockListComponent } from './moderation'
18import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' 18import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
19import { ModerationComponent } from './moderation/moderation.component' 19import { ModerationComponent } from './moderation/moderation.component'
20import { VideoAbuseDetailsComponent } from './moderation/video-abuse-list/video-abuse-details.component' 20import { AbuseDetailsComponent } from './moderation/abuse-list/abuse-details.component'
21import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component' 21import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
22import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' 22import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
23import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' 23import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
@@ -60,8 +60,10 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
60 60
61 ModerationComponent, 61 ModerationComponent,
62 VideoBlockListComponent, 62 VideoBlockListComponent,
63 VideoAbuseListComponent, 63
64 VideoAbuseDetailsComponent, 64 AbuseListComponent,
65 AbuseDetailsComponent,
66
65 ModerationCommentModalComponent, 67 ModerationCommentModalComponent,
66 InstanceServerBlocklistComponent, 68 InstanceServerBlocklistComponent,
67 InstanceAccountBlocklistComponent, 69 InstanceAccountBlocklistComponent,
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
new file mode 100644
index 000000000..d031ea8ed
--- /dev/null
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html
@@ -0,0 +1,93 @@
1<div class="d-flex moderation-expanded">
2 <!-- report left part (report details) -->
3 <div class="col-8">
4
5 <!-- report metadata -->
6 <div class="d-flex">
7 <span class="col-3 moderation-expanded-label" i18n>Reporter</span>
8 <span class="col-9 moderation-expanded-text">
9 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }" class="chip">
10 <img
11 class="avatar"
12 [src]="abuse.reporterAccount.avatar?.path"
13 (error)="switchToDefaultAvatar($event)"
14 alt="Avatar"
15 >
16 <div>
17 <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span>
18 </div>
19 </a>
20 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }" class="ml-auto text-muted video-details-links" i18n>
21 {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
22 </a>
23 </span>
24 </div>
25
26 <div class="d-flex">
27 <span class="col-3 moderation-expanded-label" i18n>Reportee</span>
28 <span class="col-9 moderation-expanded-text">
29 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.video.channel.ownerAccount.displayName + '&quot;' }" class="chip">
30 <img
31 class="avatar"
32 [src]="abuse.video.channel.ownerAccount?.avatar?.path"
33 (error)="switchToDefaultAvatar($event)"
34 alt="Avatar"
35 >
36 <div>
37 <span class="text-muted">{{ abuse.video.channel.ownerAccount ? abuse.video.channel.ownerAccount.nameWithHost : '' }}</span>
38 </div>
39 </a>
40 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.video.channel.ownerAccount.displayName + '&quot;' }" class="ml-auto text-muted video-details-links" i18n>
41 {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
42 </a>
43 </span>
44 </div>
45
46 <div class="d-flex" *ngIf="abuse.updatedAt">
47 <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>
49 </div>
50
51 <!-- report text -->
52 <div class="mt-3 d-flex">
53 <span class="col-3 moderation-expanded-label">
54 <ng-container i18n>Report</ng-container>
55 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a>
56 </span>
57 <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span>
58 </div>
59
60 <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
61 <span class="col-3"></span>
62 <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()">
64 <div>{{ reason.label }}</div>
65 </a>
66 </span>
67 </div>
68
69 <div *ngIf="abuse.startAt" class="mt-2 d-flex">
70 <span class="col-3 moderation-expanded-label" i18n>Reported part</span>
71 <span class="col-9">
72 {{ startAt }}<ng-container *ngIf="abuse.endAt"> - {{ endAt }}</ng-container>
73 </span>
74 </div>
75
76 <div class="mt-3 d-flex" *ngIf="abuse.moderationComment">
77 <span class="col-3 moderation-expanded-label" i18n>Note</span>
78 <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.moderationCommentHtml"></span>
79 </div>
80
81 </div>
82
83 <!-- report right part (video details) -->
84 <div class="col-4">
85 <div class="screenratio">
86 <div *ngIf="abuse.video.deleted || abuse.video.blacklisted">
87 <span i18n *ngIf="abuse.video.deleted">The video was deleted</span>
88 <span i18n *ngIf="!abuse.video.deleted">The video was blocked</span>
89 </div>
90 <div *ngIf="!abuse.video.deleted && !abuse.video.blacklisted" [innerHTML]="abuse.embedHtml"></div>
91 </div>
92 </div>
93</div>
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts
index 5db2887fa..8f87630b8 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts
@@ -1,19 +1,19 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { Actor } from '@app/shared/shared-main' 2import { Actor } from '@app/shared/shared-main'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { VideoAbusePredefinedReasonsString } from '../../../../../../shared/models/videos/abuse/video-abuse-reason.model' 4import { AbusePredefinedReasonsString } from '@shared/models'
5import { ProcessedVideoAbuse } from './video-abuse-list.component' 5import { ProcessedAbuse } from './abuse-list.component'
6import { durationToString } from '@app/helpers' 6import { durationToString } from '@app/helpers'
7 7
8@Component({ 8@Component({
9 selector: 'my-video-abuse-details', 9 selector: 'my-abuse-details',
10 templateUrl: './video-abuse-details.component.html', 10 templateUrl: './abuse-details.component.html',
11 styleUrls: [ '../moderation.component.scss' ] 11 styleUrls: [ '../moderation.component.scss' ]
12}) 12})
13export class VideoAbuseDetailsComponent { 13export class AbuseDetailsComponent {
14 @Input() videoAbuse: ProcessedVideoAbuse 14 @Input() abuse: ProcessedAbuse
15 15
16 private predefinedReasonsTranslations: { [key in VideoAbusePredefinedReasonsString]: string } 16 private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string }
17 17
18 constructor ( 18 constructor (
19 private i18n: I18n 19 private i18n: I18n
@@ -31,16 +31,16 @@ export class VideoAbuseDetailsComponent {
31 } 31 }
32 32
33 get startAt () { 33 get startAt () {
34 return durationToString(this.videoAbuse.startAt) 34 return durationToString(this.abuse.startAt)
35 } 35 }
36 36
37 get endAt () { 37 get endAt () {
38 return durationToString(this.videoAbuse.endAt) 38 return durationToString(this.abuse.endAt)
39 } 39 }
40 40
41 getPredefinedReasons () { 41 getPredefinedReasons () {
42 if (!this.videoAbuse.predefinedReasons) return [] 42 if (!this.abuse.predefinedReasons) return []
43 return this.videoAbuse.predefinedReasons.map(r => ({ 43 return this.abuse.predefinedReasons.map(r => ({
44 id: r, 44 id: r,
45 label: this.predefinedReasonsTranslations[r] 45 label: this.predefinedReasonsTranslations[r]
46 })) 46 }))
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
index 64641b28a..167f32fe6 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
@@ -1,5 +1,5 @@
1<p-table 1<p-table
2 [value]="videoAbuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 2 [value]="abuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" 3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true"
4 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 4 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
5 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" 5 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
@@ -16,11 +16,11 @@
16 16
17 <div role="menu" ngbDropdownMenu> 17 <div role="menu" ngbDropdownMenu>
18 <h6 class="dropdown-header" i18n>Advanced report filters</h6> 18 <h6 class="dropdown-header" i18n>Advanced report filters</h6>
19 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a> 19 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
20 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a> 20 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
21 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a> 21 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
22 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a> 22 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a>
23 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a> 23 <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
24 </div> 24 </div>
25 </div> 25 </div>
26 <input 26 <input
@@ -45,91 +45,91 @@
45 </tr> 45 </tr>
46 </ng-template> 46 </ng-template>
47 47
48 <ng-template pTemplate="body" let-expanded="expanded" let-videoAbuse> 48 <ng-template pTemplate="body" let-expanded="expanded" let-abuse>
49 <tr> 49 <tr>
50 <td class="c-hand" [pRowToggler]="videoAbuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body"> 50 <td class="c-hand" [pRowToggler]="abuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
51 <span class="expander"> 51 <span class="expander">
52 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i> 52 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
53 </span> 53 </span>
54 </td> 54 </td>
55 55
56 <td> 56 <td>
57 <a [href]="videoAbuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 57 <a [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"
61 [src]="videoAbuse.reporterAccount.avatar?.path" 61 [src]="abuse.reporterAccount.avatar?.path"
62 (error)="switchToDefaultAvatar($event)" 62 (error)="switchToDefaultAvatar($event)"
63 alt="Avatar" 63 alt="Avatar"
64 > 64 >
65 <div> 65 <div>
66 {{ videoAbuse.reporterAccount.displayName }} 66 {{ abuse.reporterAccount.displayName }}
67 <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span> 67 <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span>
68 </div> 68 </div>
69 </div> 69 </div>
70 </a> 70 </a>
71 </td> 71 </td>
72 72
73 <td *ngIf="!videoAbuse.video.deleted"> 73 <td *ngIf="!abuse.video.deleted">
74 <a [href]="getVideoUrl(videoAbuse)" class="video-table-video-link" [title]="videoAbuse.video.name" target="_blank" rel="noopener noreferrer"> 74 <a [href]="getVideoUrl(abuse)" class="video-table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer">
75 <div class="video-table-video"> 75 <div class="video-table-video">
76 <div class="video-table-video-image"> 76 <div class="video-table-video-image">
77 <img [src]="videoAbuse.video.thumbnailPath"> 77 <img [src]="abuse.video.thumbnailPath">
78 <span 78 <span
79 class="video-table-video-image-label" *ngIf="videoAbuse.count > 1" 79 class="video-table-video-image-label" *ngIf="abuse.count > 1"
80 i18n-title title="This video has been reported multiple times." 80 i18n-title title="This video has been reported multiple times."
81 > 81 >
82 {{ videoAbuse.nth }}/{{ videoAbuse.count }} 82 {{ abuse.nth }}/{{ abuse.count }}
83 </span> 83 </span>
84 </div> 84 </div>
85 <div class="video-table-video-text"> 85 <div class="video-table-video-text">
86 <div> 86 <div>
87 <span *ngIf="!videoAbuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span> 87 <span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span>
88 <span *ngIf="videoAbuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span> 88 <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
89 {{ videoAbuse.video.name }} 89 {{ abuse.video.name }}
90 </div> 90 </div>
91 <div class="text-muted" i18n>by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div> 91 <div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
92 </div> 92 </div>
93 </div> 93 </div>
94 </a> 94 </a>
95 </td> 95 </td>
96 96
97 <td *ngIf="videoAbuse.video.deleted" class="c-hand" [pRowToggler]="videoAbuse"> 97 <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
98 <div class="video-table-video" i18n-title title="Video was deleted"> 98 <div class="video-table-video" i18n-title title="Video was deleted">
99 <div class="video-table-video-image"> 99 <div class="video-table-video-image">
100 <span i18n>Deleted</span> 100 <span i18n>Deleted</span>
101 </div> 101 </div>
102 <div class="video-table-video-text"> 102 <div class="video-table-video-text">
103 <div> 103 <div>
104 {{ videoAbuse.video.name }} 104 {{ abuse.video.name }}
105 <span class="glyphicon glyphicon-trash"></span> 105 <span class="glyphicon glyphicon-trash"></span>
106 </div> 106 </div>
107 <div class="text-muted" i18n>by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div> 107 <div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
108 </div> 108 </div>
109 </div> 109 </div>
110 </td> 110 </td>
111 111
112 <td class="c-hand" [pRowToggler]="videoAbuse">{{ videoAbuse.createdAt | date: 'short' }}</td> 112 <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td>
113 113
114 <td class="c-hand video-abuse-states" [pRowToggler]="videoAbuse"> 114 <td class="c-hand video-abuse-states" [pRowToggler]="abuse">
115 <span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span> 115 <span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span>
116 <span *ngIf="isVideoAbuseRejected(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-remove"></span> 116 <span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span>
117 <span *ngIf="videoAbuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="videoAbuse.moderationComment" class="glyphicon glyphicon-comment"></span> 117 <span *ngIf="abuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment" class="glyphicon glyphicon-comment"></span>
118 </td> 118 </td>
119 119
120 <td class="action-cell"> 120 <td class="action-cell">
121 <my-action-dropdown 121 <my-action-dropdown
122 [ngClass]="{ 'show': expanded }" placement="bottom-right top-right left auto" container="body" 122 [ngClass]="{ 'show': expanded }" placement="bottom-right top-right left auto" container="body"
123 i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse" 123 i18n-label label="Actions" [actions]="abuseActions" [entry]="abuse"
124 ></my-action-dropdown> 124 ></my-action-dropdown>
125 </td> 125 </td>
126 </tr> 126 </tr>
127 </ng-template> 127 </ng-template>
128 128
129 <ng-template pTemplate="rowexpansion" let-videoAbuse> 129 <ng-template pTemplate="rowexpansion" let-abuse>
130 <tr> 130 <tr>
131 <td class="expand-cell" colspan="6"> 131 <td class="expand-cell" colspan="6">
132 <my-video-abuse-details [videoAbuse]="videoAbuse"></my-video-abuse-details> 132 <my-abuse-details [abuse]="abuse"></my-abuse-details>
133 </td> 133 </td>
134 </tr> 134 </tr>
135 </ng-template> 135 </ng-template>
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss
index 8eee15b64..8eee15b64 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts
index 409dd42c7..427ec4d5d 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts
@@ -1,5 +1,4 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { filter } from 'rxjs/operators'
3import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' 2import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
4import { environment } from 'src/environments/environment' 3import { environment } from 'src/environments/environment'
5import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' 4import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
@@ -7,43 +6,45 @@ import { DomSanitizer } from '@angular/platform-browser'
7import { ActivatedRoute, Params, Router } from '@angular/router' 6import { ActivatedRoute, Params, Router } from '@angular/router'
8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' 7import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
9import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' 8import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
10import { BlocklistService, VideoAbuseService, VideoBlockService } from '@app/shared/shared-moderation' 9import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
11import { I18n } from '@ngx-translate/i18n-polyfill' 10import { I18n } from '@ngx-translate/i18n-polyfill'
12import { VideoAbuse, VideoAbuseState } from '@shared/models' 11import { Abuse, AbuseState } from '@shared/models'
13import { ModerationCommentModalComponent } from './moderation-comment-modal.component' 12import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
14 13
15export type ProcessedVideoAbuse = VideoAbuse & { 14export type ProcessedAbuse = Abuse & {
16 moderationCommentHtml?: string, 15 moderationCommentHtml?: string,
17 reasonHtml?: string 16 reasonHtml?: string
18 embedHtml?: string 17 embedHtml?: string
19 updatedAt?: Date 18 updatedAt?: Date
19
20 // override bare server-side definitions with rich client-side definitions 20 // override bare server-side definitions with rich client-side definitions
21 reporterAccount: Account 21 reporterAccount: Account
22 video: VideoAbuse['video'] & { 22
23 channel: VideoAbuse['video']['channel'] & { 23 video: Abuse['video'] & {
24 channel: Abuse['video']['channel'] & {
24 ownerAccount: Account 25 ownerAccount: Account
25 } 26 }
26 } 27 }
27} 28}
28 29
29@Component({ 30@Component({
30 selector: 'my-video-abuse-list', 31 selector: 'my-abuse-list',
31 templateUrl: './video-abuse-list.component.html', 32 templateUrl: './abuse-list.component.html',
32 styleUrls: [ '../moderation.component.scss', './video-abuse-list.component.scss' ] 33 styleUrls: [ '../moderation.component.scss', './abuse-list.component.scss' ]
33}) 34})
34export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit { 35export class AbuseListComponent extends RestTable implements OnInit, AfterViewInit {
35 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent 36 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
36 37
37 videoAbuses: ProcessedVideoAbuse[] = [] 38 abuses: ProcessedAbuse[] = []
38 totalRecords = 0 39 totalRecords = 0
39 sort: SortMeta = { field: 'createdAt', order: 1 } 40 sort: SortMeta = { field: 'createdAt', order: 1 }
40 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 41 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
41 42
42 videoAbuseActions: DropdownAction<VideoAbuse>[][] = [] 43 abuseActions: DropdownAction<Abuse>[][] = []
43 44
44 constructor ( 45 constructor (
45 private notifier: Notifier, 46 private notifier: Notifier,
46 private videoAbuseService: VideoAbuseService, 47 private abuseService: AbuseService,
47 private blocklistService: BlocklistService, 48 private blocklistService: BlocklistService,
48 private videoService: VideoService, 49 private videoService: VideoService,
49 private videoBlocklistService: VideoBlockService, 50 private videoBlocklistService: VideoBlockService,
@@ -56,7 +57,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
56 ) { 57 ) {
57 super() 58 super()
58 59
59 this.videoAbuseActions = [ 60 this.abuseActions = [
60 [ 61 [
61 { 62 {
62 label: this.i18n('Internal actions'), 63 label: this.i18n('Internal actions'),
@@ -64,45 +65,45 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
64 }, 65 },
65 { 66 {
66 label: this.i18n('Delete report'), 67 label: this.i18n('Delete report'),
67 handler: videoAbuse => this.removeVideoAbuse(videoAbuse) 68 handler: abuse => this.removeAbuse(abuse)
68 }, 69 },
69 { 70 {
70 label: this.i18n('Add note'), 71 label: this.i18n('Add note'),
71 handler: videoAbuse => this.openModerationCommentModal(videoAbuse), 72 handler: abuse => this.openModerationCommentModal(abuse),
72 isDisplayed: videoAbuse => !videoAbuse.moderationComment 73 isDisplayed: abuse => !abuse.moderationComment
73 }, 74 },
74 { 75 {
75 label: this.i18n('Update note'), 76 label: this.i18n('Update note'),
76 handler: videoAbuse => this.openModerationCommentModal(videoAbuse), 77 handler: abuse => this.openModerationCommentModal(abuse),
77 isDisplayed: videoAbuse => !!videoAbuse.moderationComment 78 isDisplayed: abuse => !!abuse.moderationComment
78 }, 79 },
79 { 80 {
80 label: this.i18n('Mark as accepted'), 81 label: this.i18n('Mark as accepted'),
81 handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), 82 handler: abuse => this.updateAbuseState(abuse, AbuseState.ACCEPTED),
82 isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) 83 isDisplayed: abuse => !this.isAbuseAccepted(abuse)
83 }, 84 },
84 { 85 {
85 label: this.i18n('Mark as rejected'), 86 label: this.i18n('Mark as rejected'),
86 handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), 87 handler: abuse => this.updateAbuseState(abuse, AbuseState.REJECTED),
87 isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) 88 isDisplayed: abuse => !this.isAbuseRejected(abuse)
88 } 89 }
89 ], 90 ],
90 [ 91 [
91 { 92 {
92 label: this.i18n('Actions for the video'), 93 label: this.i18n('Actions for the video'),
93 isHeader: true, 94 isHeader: true,
94 isDisplayed: videoAbuse => !videoAbuse.video.deleted 95 isDisplayed: abuse => !abuse.video.deleted
95 }, 96 },
96 { 97 {
97 label: this.i18n('Block video'), 98 label: this.i18n('Block video'),
98 isDisplayed: videoAbuse => !videoAbuse.video.deleted && !videoAbuse.video.blacklisted, 99 isDisplayed: abuse => !abuse.video.deleted && !abuse.video.blacklisted,
99 handler: videoAbuse => { 100 handler: abuse => {
100 this.videoBlocklistService.blockVideo(videoAbuse.video.id, undefined, true) 101 this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true)
101 .subscribe( 102 .subscribe(
102 () => { 103 () => {
103 this.notifier.success(this.i18n('Video blocked.')) 104 this.notifier.success(this.i18n('Video blocked.'))
104 105
105 this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) 106 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
106 }, 107 },
107 108
108 err => this.notifier.error(err.message) 109 err => this.notifier.error(err.message)
@@ -111,14 +112,14 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
111 }, 112 },
112 { 113 {
113 label: this.i18n('Unblock video'), 114 label: this.i18n('Unblock video'),
114 isDisplayed: videoAbuse => !videoAbuse.video.deleted && videoAbuse.video.blacklisted, 115 isDisplayed: abuse => !abuse.video.deleted && abuse.video.blacklisted,
115 handler: videoAbuse => { 116 handler: abuse => {
116 this.videoBlocklistService.unblockVideo(videoAbuse.video.id) 117 this.videoBlocklistService.unblockVideo(abuse.video.id)
117 .subscribe( 118 .subscribe(
118 () => { 119 () => {
119 this.notifier.success(this.i18n('Video unblocked.')) 120 this.notifier.success(this.i18n('Video unblocked.'))
120 121
121 this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) 122 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
122 }, 123 },
123 124
124 err => this.notifier.error(err.message) 125 err => this.notifier.error(err.message)
@@ -127,20 +128,20 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
127 }, 128 },
128 { 129 {
129 label: this.i18n('Delete video'), 130 label: this.i18n('Delete video'),
130 isDisplayed: videoAbuse => !videoAbuse.video.deleted, 131 isDisplayed: abuse => !abuse.video.deleted,
131 handler: async videoAbuse => { 132 handler: async abuse => {
132 const res = await this.confirmService.confirm( 133 const res = await this.confirmService.confirm(
133 this.i18n('Do you really want to delete this video?'), 134 this.i18n('Do you really want to delete this video?'),
134 this.i18n('Delete') 135 this.i18n('Delete')
135 ) 136 )
136 if (res === false) return 137 if (res === false) return
137 138
138 this.videoService.removeVideo(videoAbuse.video.id) 139 this.videoService.removeVideo(abuse.video.id)
139 .subscribe( 140 .subscribe(
140 () => { 141 () => {
141 this.notifier.success(this.i18n('Video deleted.')) 142 this.notifier.success(this.i18n('Video deleted.'))
142 143
143 this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) 144 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
144 }, 145 },
145 146
146 err => this.notifier.error(err.message) 147 err => this.notifier.error(err.message)
@@ -155,8 +156,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
155 }, 156 },
156 { 157 {
157 label: this.i18n('Mute reporter'), 158 label: this.i18n('Mute reporter'),
158 handler: async videoAbuse => { 159 handler: async abuse => {
159 const account = videoAbuse.reporterAccount as Account 160 const account = abuse.reporterAccount as Account
160 161
161 this.blocklistService.blockAccountByInstance(account) 162 this.blocklistService.blockAccountByInstance(account)
162 .subscribe( 163 .subscribe(
@@ -174,13 +175,13 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
174 }, 175 },
175 { 176 {
176 label: this.i18n('Mute server'), 177 label: this.i18n('Mute server'),
177 isDisplayed: videoAbuse => !videoAbuse.reporterAccount.userId, 178 isDisplayed: abuse => !abuse.reporterAccount.userId,
178 handler: async videoAbuse => { 179 handler: async abuse => {
179 this.blocklistService.blockServerByInstance(videoAbuse.reporterAccount.host) 180 this.blocklistService.blockServerByInstance(abuse.reporterAccount.host)
180 .subscribe( 181 .subscribe(
181 () => { 182 () => {
182 this.notifier.success( 183 this.notifier.success(
183 this.i18n('Server {{host}} muted by the instance.', { host: videoAbuse.reporterAccount.host }) 184 this.i18n('Server {{host}} muted by the instance.', { host: abuse.reporterAccount.host })
184 ) 185 )
185 }, 186 },
186 187
@@ -209,11 +210,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
209 } 210 }
210 211
211 getIdentifier () { 212 getIdentifier () {
212 return 'VideoAbuseListComponent' 213 return 'AbuseListComponent'
213 } 214 }
214 215
215 openModerationCommentModal (videoAbuse: VideoAbuse) { 216 openModerationCommentModal (abuse: Abuse) {
216 this.moderationCommentModal.openModal(videoAbuse) 217 this.moderationCommentModal.openModal(abuse)
217 } 218 }
218 219
219 onModerationCommentUpdated () { 220 onModerationCommentUpdated () {
@@ -240,26 +241,26 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
240 } 241 }
241 /* END Table filter functions */ 242 /* END Table filter functions */
242 243
243 isVideoAbuseAccepted (videoAbuse: VideoAbuse) { 244 isAbuseAccepted (abuse: Abuse) {
244 return videoAbuse.state.id === VideoAbuseState.ACCEPTED 245 return abuse.state.id === AbuseState.ACCEPTED
245 } 246 }
246 247
247 isVideoAbuseRejected (videoAbuse: VideoAbuse) { 248 isAbuseRejected (abuse: Abuse) {
248 return videoAbuse.state.id === VideoAbuseState.REJECTED 249 return abuse.state.id === AbuseState.REJECTED
249 } 250 }
250 251
251 getVideoUrl (videoAbuse: VideoAbuse) { 252 getVideoUrl (abuse: Abuse) {
252 return Video.buildClientUrl(videoAbuse.video.uuid) 253 return Video.buildClientUrl(abuse.video.uuid)
253 } 254 }
254 255
255 getVideoEmbed (videoAbuse: VideoAbuse) { 256 getVideoEmbed (abuse: Abuse) {
256 return buildVideoEmbed( 257 return buildVideoEmbed(
257 buildVideoLink({ 258 buildVideoLink({
258 baseUrl: `${environment.embedUrl}/videos/embed/${videoAbuse.video.uuid}`, 259 baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`,
259 title: false, 260 title: false,
260 warningTitle: false, 261 warningTitle: false,
261 startTime: videoAbuse.startAt, 262 startTime: abuse.startAt,
262 stopTime: videoAbuse.endAt 263 stopTime: abuse.endAt
263 }) 264 })
264 ) 265 )
265 } 266 }
@@ -268,11 +269,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
268 ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() 269 ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
269 } 270 }
270 271
271 async removeVideoAbuse (videoAbuse: VideoAbuse) { 272 async removeAbuse (abuse: Abuse) {
272 const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete')) 273 const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete'))
273 if (res === false) return 274 if (res === false) return
274 275
275 this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( 276 this.abuseService.removeAbuse(abuse).subscribe(
276 () => { 277 () => {
277 this.notifier.success(this.i18n('Abuse deleted.')) 278 this.notifier.success(this.i18n('Abuse deleted.'))
278 this.loadData() 279 this.loadData()
@@ -282,8 +283,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
282 ) 283 )
283 } 284 }
284 285
285 updateVideoAbuseState (videoAbuse: VideoAbuse, state: VideoAbuseState) { 286 updateAbuseState (abuse: Abuse, state: AbuseState) {
286 this.videoAbuseService.updateVideoAbuse(videoAbuse, { state }) 287 this.abuseService.updateAbuse(abuse, { state })
287 .subscribe( 288 .subscribe(
288 () => this.loadData(), 289 () => this.loadData(),
289 290
@@ -292,14 +293,14 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
292 } 293 }
293 294
294 protected loadData () { 295 protected loadData () {
295 return this.videoAbuseService.getVideoAbuses({ 296 return this.abuseService.getAbuses({
296 pagination: this.pagination, 297 pagination: this.pagination,
297 sort: this.sort, 298 sort: this.sort,
298 search: this.search 299 search: this.search
299 }).subscribe( 300 }).subscribe(
300 async resultList => { 301 async resultList => {
301 this.totalRecords = resultList.total 302 this.totalRecords = resultList.total
302 const videoAbuses = [] 303 const abuses = []
303 304
304 for (const abuse of resultList.data) { 305 for (const abuse of resultList.data) {
305 Object.assign(abuse, { 306 Object.assign(abuse, {
@@ -312,10 +313,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
312 if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) 313 if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
313 if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt 314 if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt
314 315
315 videoAbuses.push(abuse as ProcessedVideoAbuse) 316 abuses.push(abuse as ProcessedAbuse)
316 } 317 }
317 318
318 this.videoAbuses = videoAbuses 319 this.abuses = abuses
319 }, 320 },
320 321
321 err => this.notifier.error(err.message) 322 err => this.notifier.error(err.message)
diff --git a/client/src/app/+admin/moderation/abuse-list/index.ts b/client/src/app/+admin/moderation/abuse-list/index.ts
new file mode 100644
index 000000000..c6037dab4
--- /dev/null
+++ b/client/src/app/+admin/moderation/abuse-list/index.ts
@@ -0,0 +1,3 @@
1export * from './abuse-details.component'
2export * from './abuse-list.component'
3export * from './moderation-comment-modal.component'
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html
index 8082e93f4..8082e93f4 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html
+++ b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.scss b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss
index afcdb9a16..afcdb9a16 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.scss
+++ b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts
index 3cd763ca4..23738f9cd 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts
+++ b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts
@@ -1,11 +1,11 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { FormReactive, FormValidatorService, VideoAbuseValidatorsService } from '@app/shared/shared-forms' 3import { FormReactive, FormValidatorService, AbuseValidatorsService } from '@app/shared/shared-forms'
4import { VideoAbuseService } from '@app/shared/shared-moderation' 4import { AbuseService } from '@app/shared/shared-moderation'
5import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 5import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
6import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 6import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
7import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
8import { VideoAbuse } from '@shared/models' 8import { Abuse } from '@shared/models'
9 9
10@Component({ 10@Component({
11 selector: 'my-moderation-comment-modal', 11 selector: 'my-moderation-comment-modal',
@@ -16,15 +16,15 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
16 @ViewChild('modal', { static: true }) modal: NgbModal 16 @ViewChild('modal', { static: true }) modal: NgbModal
17 @Output() commentUpdated = new EventEmitter<string>() 17 @Output() commentUpdated = new EventEmitter<string>()
18 18
19 private abuseToComment: VideoAbuse 19 private abuseToComment: Abuse
20 private openedModal: NgbModalRef 20 private openedModal: NgbModalRef
21 21
22 constructor ( 22 constructor (
23 protected formValidatorService: FormValidatorService, 23 protected formValidatorService: FormValidatorService,
24 private modalService: NgbModal, 24 private modalService: NgbModal,
25 private notifier: Notifier, 25 private notifier: Notifier,
26 private videoAbuseService: VideoAbuseService, 26 private abuseService: AbuseService,
27 private videoAbuseValidatorsService: VideoAbuseValidatorsService, 27 private abuseValidatorsService: AbuseValidatorsService,
28 private i18n: I18n 28 private i18n: I18n
29 ) { 29 ) {
30 super() 30 super()
@@ -32,11 +32,11 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
32 32
33 ngOnInit () { 33 ngOnInit () {
34 this.buildForm({ 34 this.buildForm({
35 moderationComment: this.videoAbuseValidatorsService.VIDEO_ABUSE_MODERATION_COMMENT 35 moderationComment: this.abuseValidatorsService.ABUSE_MODERATION_COMMENT
36 }) 36 })
37 } 37 }
38 38
39 openModal (abuseToComment: VideoAbuse) { 39 openModal (abuseToComment: Abuse) {
40 this.abuseToComment = abuseToComment 40 this.abuseToComment = abuseToComment
41 this.openedModal = this.modalService.open(this.modal, { centered: true }) 41 this.openedModal = this.modalService.open(this.modal, { centered: true })
42 42
@@ -54,7 +54,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
54 async banUser () { 54 async banUser () {
55 const moderationComment: string = this.form.value[ 'moderationComment' ] 55 const moderationComment: string = this.form.value[ 'moderationComment' ]
56 56
57 this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) 57 this.abuseService.updateAbuse(this.abuseToComment, { moderationComment })
58 .subscribe( 58 .subscribe(
59 () => { 59 () => {
60 this.notifier.success(this.i18n('Comment updated.')) 60 this.notifier.success(this.i18n('Comment updated.'))
diff --git a/client/src/app/+admin/moderation/index.ts b/client/src/app/+admin/moderation/index.ts
index 16249236c..53e4bc991 100644
--- a/client/src/app/+admin/moderation/index.ts
+++ b/client/src/app/+admin/moderation/index.ts
@@ -1,5 +1,5 @@
1export * from './abuse-list'
1export * from './instance-blocklist' 2export * from './instance-blocklist'
2export * from './video-abuse-list'
3export * from './video-block-list' 3export * from './video-block-list'
4export * from './moderation.component' 4export * from './moderation.component'
5export * from './moderation.routes' 5export * from './moderation.routes'
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts
index cd837bcb9..1e207e5e8 100644
--- a/client/src/app/+admin/moderation/moderation.routes.ts
+++ b/client/src/app/+admin/moderation/moderation.routes.ts
@@ -1,7 +1,7 @@
1import { Routes } from '@angular/router' 1import { Routes } from '@angular/router'
2import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' 2import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
3import { ModerationComponent } from '@app/+admin/moderation/moderation.component' 3import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
4import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list' 4import { AbuseListComponent } from '@app/+admin/moderation/abuse-list'
5import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list' 5import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list'
6import { UserRightGuard } from '@app/core' 6import { UserRightGuard } from '@app/core'
7import { UserRight } from '@shared/models' 7import { UserRight } from '@shared/models'
@@ -13,20 +13,25 @@ export const ModerationRoutes: Routes = [
13 children: [ 13 children: [
14 { 14 {
15 path: '', 15 path: '',
16 redirectTo: 'video-abuses/list', 16 redirectTo: 'abuses/list',
17 pathMatch: 'full' 17 pathMatch: 'full'
18 }, 18 },
19 { 19 {
20 path: 'video-abuses', 20 path: 'video-abuses',
21 redirectTo: 'video-abuses/list', 21 redirectTo: 'abuses/list',
22 pathMatch: 'full' 22 pathMatch: 'full'
23 }, 23 },
24 { 24 {
25 path: 'video-abuses/list', 25 path: 'video-abuses/list',
26 component: VideoAbuseListComponent, 26 redirectTo: 'abuses/list',
27 pathMatch: 'full'
28 },
29 {
30 path: 'abuses/list',
31 component: AbuseListComponent,
27 canActivate: [ UserRightGuard ], 32 canActivate: [ UserRightGuard ],
28 data: { 33 data: {
29 userRight: UserRight.MANAGE_VIDEO_ABUSES, 34 userRight: UserRight.MANAGE_ABUSES,
30 meta: { 35 meta: {
31 title: 'Video reports' 36 title: 'Video reports'
32 } 37 }
diff --git a/client/src/app/+admin/moderation/video-abuse-list/index.ts b/client/src/app/+admin/moderation/video-abuse-list/index.ts
deleted file mode 100644
index da7176e52..000000000
--- a/client/src/app/+admin/moderation/video-abuse-list/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './video-abuse-list.component'
2export * from './moderation-comment-modal.component'
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
deleted file mode 100644
index ec808cdb8..000000000
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
+++ /dev/null
@@ -1,93 +0,0 @@
1<div class="d-flex moderation-expanded">
2 <!-- report left part (report details) -->
3 <div class="col-8">
4
5 <!-- report metadata -->
6 <div class="d-flex">
7 <span class="col-3 moderation-expanded-label" i18n>Reporter</span>
8 <span class="col-9 moderation-expanded-text">
9 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reporter:&quot;' + videoAbuse.reporterAccount.displayName + '&quot;' }" class="chip">
10 <img
11 class="avatar"
12 [src]="videoAbuse.reporterAccount.avatar?.path"
13 (error)="switchToDefaultAvatar($event)"
14 alt="Avatar"
15 >
16 <div>
17 <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span>
18 </div>
19 </a>
20 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reporter:&quot;' + videoAbuse.reporterAccount.displayName + '&quot;' }" class="ml-auto text-muted video-details-links" i18n>
21 {videoAbuse.countReportsForReporter, plural, =1 {1 report} other {{{ videoAbuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
22 </a>
23 </span>
24 </div>
25
26 <div class="d-flex">
27 <span class="col-3 moderation-expanded-label" i18n>Reportee</span>
28 <span class="col-9 moderation-expanded-text">
29 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:&quot;' +videoAbuse.video.channel.ownerAccount.displayName + '&quot;' }" class="chip">
30 <img
31 class="avatar"
32 [src]="videoAbuse.video.channel.ownerAccount?.avatar?.path"
33 (error)="switchToDefaultAvatar($event)"
34 alt="Avatar"
35 >
36 <div>
37 <span class="text-muted">{{ videoAbuse.video.channel.ownerAccount ? videoAbuse.video.channel.ownerAccount.nameWithHost : '' }}</span>
38 </div>
39 </a>
40 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:&quot;' +videoAbuse.video.channel.ownerAccount.displayName + '&quot;' }" class="ml-auto text-muted video-details-links" i18n>
41 {videoAbuse.countReportsForReportee, plural, =1 {1 report} other {{{ videoAbuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
42 </a>
43 </span>
44 </div>
45
46 <div class="d-flex" *ngIf="videoAbuse.updatedAt">
47 <span class="col-3 moderation-expanded-label" i18n>Updated</span>
48 <time class="col-9 moderation-expanded-text video-details-date-updated">{{ videoAbuse.updatedAt | date: 'medium' }}</time>
49 </div>
50
51 <!-- report text -->
52 <div class="mt-3 d-flex">
53 <span class="col-3 moderation-expanded-label">
54 <ng-container i18n>Report</ng-container>
55 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': '#' + videoAbuse.id }" class="ml-1 text-muted">#{{ videoAbuse.id }}</a>
56 </span>
57 <span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.reasonHtml"></span>
58 </div>
59
60 <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
61 <span class="col-3"></span>
62 <span class="col-9">
63 <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" *ngFor="let reason of getPredefinedReasons()">
64 <div>{{ reason.label }}</div>
65 </a>
66 </span>
67 </div>
68
69 <div *ngIf="videoAbuse.startAt" class="mt-2 d-flex">
70 <span class="col-3 moderation-expanded-label" i18n>Reported part</span>
71 <span class="col-9">
72 {{ startAt }}<ng-container *ngIf="videoAbuse.endAt"> - {{ endAt }}</ng-container>
73 </span>
74 </div>
75
76 <div class="mt-3 d-flex" *ngIf="videoAbuse.moderationComment">
77 <span class="col-3 moderation-expanded-label" i18n>Note</span>
78 <span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.moderationCommentHtml"></span>
79 </div>
80
81 </div>
82
83 <!-- report right part (video details) -->
84 <div class="col-4">
85 <div class="screenratio">
86 <div *ngIf="videoAbuse.video.deleted || videoAbuse.video.blacklisted">
87 <span i18n *ngIf="videoAbuse.video.deleted">The video was deleted</span>
88 <span i18n *ngIf="!videoAbuse.video.deleted">The video was blocked</span>
89 </div>
90 <div *ngIf="!videoAbuse.video.deleted && !videoAbuse.video.blacklisted" [innerHTML]="videoAbuse.embedHtml"></div>
91 </div>
92 </div>
93</div>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
index cfa514b26..adc18b587 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
@@ -47,7 +47,7 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
47 this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] 47 this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[]
48 48
49 this.rightNotifications = { 49 this.rightNotifications = {
50 videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, 50 videoAbuseAsModerator: UserRight.MANAGE_ABUSES,
51 videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, 51 videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST,
52 newUserRegistration: UserRight.MANAGE_USERS, 52 newUserRegistration: UserRight.MANAGE_USERS,
53 newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, 53 newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW,
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index 2dbe695c9..0ea251f1c 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -28,7 +28,7 @@ export class MenuComponent implements OnInit {
28 private routesPerRight: { [ role in UserRight ]?: string } = { 28 private routesPerRight: { [ role in UserRight ]?: string } = {
29 [UserRight.MANAGE_USERS]: '/admin/users', 29 [UserRight.MANAGE_USERS]: '/admin/users',
30 [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', 30 [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends',
31 [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/moderation/video-abuses', 31 [UserRight.MANAGE_ABUSES]: '/admin/moderation/abuses',
32 [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/moderation/video-blocks', 32 [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/moderation/video-blocks',
33 [UserRight.MANAGE_JOBS]: '/admin/jobs', 33 [UserRight.MANAGE_JOBS]: '/admin/jobs',
34 [UserRight.MANAGE_CONFIGURATION]: '/admin/config' 34 [UserRight.MANAGE_CONFIGURATION]: '/admin/config'
@@ -126,7 +126,7 @@ export class MenuComponent implements OnInit {
126 const adminRights = [ 126 const adminRights = [
127 UserRight.MANAGE_USERS, 127 UserRight.MANAGE_USERS,
128 UserRight.MANAGE_SERVER_FOLLOW, 128 UserRight.MANAGE_SERVER_FOLLOW,
129 UserRight.MANAGE_VIDEO_ABUSES, 129 UserRight.MANAGE_ABUSES,
130 UserRight.MANAGE_VIDEO_BLACKLIST, 130 UserRight.MANAGE_VIDEO_BLACKLIST,
131 UserRight.MANAGE_JOBS, 131 UserRight.MANAGE_JOBS,
132 UserRight.MANAGE_CONFIGURATION 132 UserRight.MANAGE_CONFIGURATION
diff --git a/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts
index aae56d607..739115e19 100644
--- a/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts
@@ -4,12 +4,12 @@ import { Injectable } from '@angular/core'
4import { BuildFormValidator } from './form-validator.service' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoAbuseValidatorsService { 7export class AbuseValidatorsService {
8 readonly VIDEO_ABUSE_REASON: BuildFormValidator 8 readonly ABUSE_REASON: BuildFormValidator
9 readonly VIDEO_ABUSE_MODERATION_COMMENT: BuildFormValidator 9 readonly ABUSE_MODERATION_COMMENT: BuildFormValidator
10 10
11 constructor (private i18n: I18n) { 11 constructor (private i18n: I18n) {
12 this.VIDEO_ABUSE_REASON = { 12 this.ABUSE_REASON = {
13 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], 13 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
14 MESSAGES: { 14 MESSAGES: {
15 'required': this.i18n('Report reason is required.'), 15 'required': this.i18n('Report reason is required.'),
@@ -18,7 +18,7 @@ export class VideoAbuseValidatorsService {
18 } 18 }
19 } 19 }
20 20
21 this.VIDEO_ABUSE_MODERATION_COMMENT = { 21 this.ABUSE_MODERATION_COMMENT = {
22 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], 22 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
23 MESSAGES: { 23 MESSAGES: {
24 'required': this.i18n('Moderation comment is required.'), 24 'required': this.i18n('Moderation comment is required.'),
diff --git a/client/src/app/shared/shared-forms/form-validators/index.ts b/client/src/app/shared/shared-forms/form-validators/index.ts
index 8b71841a9..b06a326ff 100644
--- a/client/src/app/shared/shared-forms/form-validators/index.ts
+++ b/client/src/app/shared/shared-forms/form-validators/index.ts
@@ -1,3 +1,4 @@
1export * from './abuse-validators.service'
1export * from './batch-domains-validators.service' 2export * from './batch-domains-validators.service'
2export * from './custom-config-validators.service' 3export * from './custom-config-validators.service'
3export * from './form-validator.service' 4export * from './form-validator.service'
@@ -6,7 +7,6 @@ export * from './instance-validators.service'
6export * from './login-validators.service' 7export * from './login-validators.service'
7export * from './reset-password-validators.service' 8export * from './reset-password-validators.service'
8export * from './user-validators.service' 9export * from './user-validators.service'
9export * from './video-abuse-validators.service'
10export * from './video-accept-ownership-validators.service' 10export * from './video-accept-ownership-validators.service'
11export * from './video-block-validators.service' 11export * from './video-block-validators.service'
12export * from './video-captions-validators.service' 12export * from './video-captions-validators.service'
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts
index e82fa97d4..ba33704cf 100644
--- a/client/src/app/shared/shared-forms/shared-form.module.ts
+++ b/client/src/app/shared/shared-forms/shared-form.module.ts
@@ -11,7 +11,7 @@ import {
11 LoginValidatorsService, 11 LoginValidatorsService,
12 ResetPasswordValidatorsService, 12 ResetPasswordValidatorsService,
13 UserValidatorsService, 13 UserValidatorsService,
14 VideoAbuseValidatorsService, 14 AbuseValidatorsService,
15 VideoAcceptOwnershipValidatorsService, 15 VideoAcceptOwnershipValidatorsService,
16 VideoBlockValidatorsService, 16 VideoBlockValidatorsService,
17 VideoCaptionsValidatorsService, 17 VideoCaptionsValidatorsService,
@@ -69,7 +69,7 @@ import { TimestampInputComponent } from './timestamp-input.component'
69 LoginValidatorsService, 69 LoginValidatorsService,
70 ResetPasswordValidatorsService, 70 ResetPasswordValidatorsService,
71 UserValidatorsService, 71 UserValidatorsService,
72 VideoAbuseValidatorsService, 72 AbuseValidatorsService,
73 VideoAcceptOwnershipValidatorsService, 73 VideoAcceptOwnershipValidatorsService,
74 VideoBlockValidatorsService, 74 VideoBlockValidatorsService,
75 VideoCaptionsValidatorsService, 75 VideoCaptionsValidatorsService,
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
index 5fc7989dd..0fa161ce6 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -14,7 +14,7 @@ export abstract class Actor implements ActorServer {
14 14
15 avatarUrl: string 15 avatarUrl: string
16 16
17 static GET_ACTOR_AVATAR_URL (actor: { avatar?: Avatar }) { 17 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
18 if (actor?.avatar?.url) return actor.avatar.url 18 if (actor?.avatar?.url) return actor.avatar.url
19 19
20 if (actor && actor.avatar) { 20 if (actor && actor.avatar) {
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
index de25d3ab9..389a242fd 100644
--- a/client/src/app/shared/shared-main/users/user-notification.model.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -25,9 +25,20 @@ export class UserNotification implements UserNotificationServer {
25 video: VideoInfo 25 video: VideoInfo
26 } 26 }
27 27
28 videoAbuse?: { 28 abuse?: {
29 id: number 29 id: number
30 video: VideoInfo 30
31 video?: VideoInfo
32
33 comment?: {
34 threadId: number
35
36 video: {
37 uuid: string
38 }
39 }
40
41 account?: ActorInfo
31 } 42 }
32 43
33 videoBlacklist?: { 44 videoBlacklist?: {
@@ -55,7 +66,7 @@ export class UserNotification implements UserNotificationServer {
55 // Additional fields 66 // Additional fields
56 videoUrl?: string 67 videoUrl?: string
57 commentUrl?: any[] 68 commentUrl?: any[]
58 videoAbuseUrl?: string 69 abuseUrl?: string
59 videoAutoBlacklistUrl?: string 70 videoAutoBlacklistUrl?: string
60 accountUrl?: string 71 accountUrl?: string
61 videoImportIdentifier?: string 72 videoImportIdentifier?: string
@@ -78,7 +89,7 @@ export class UserNotification implements UserNotificationServer {
78 this.comment = hash.comment 89 this.comment = hash.comment
79 if (this.comment) this.setAvatarUrl(this.comment.account) 90 if (this.comment) this.setAvatarUrl(this.comment.account)
80 91
81 this.videoAbuse = hash.videoAbuse 92 this.abuse = hash.abuse
82 93
83 this.videoBlacklist = hash.videoBlacklist 94 this.videoBlacklist = hash.videoBlacklist
84 95
@@ -108,8 +119,9 @@ export class UserNotification implements UserNotificationServer {
108 break 119 break
109 120
110 case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS: 121 case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS:
111 this.videoAbuseUrl = '/admin/moderation/video-abuses/list' 122 this.abuseUrl = '/admin/moderation/abuses/list'
112 this.videoUrl = this.buildVideoUrl(this.videoAbuse.video) 123
124 if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video)
113 break 125 break
114 126
115 case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: 127 case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
@@ -178,7 +190,7 @@ export class UserNotification implements UserNotificationServer {
178 return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName 190 return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName
179 } 191 }
180 192
181 private setAvatarUrl (actor: { avatarUrl?: string, avatar?: Avatar }) { 193 private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
182 actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) 194 actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
183 } 195 }
184} 196}
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html
index d5be1470e..8d31eab0d 100644
--- a/client/src/app/shared/shared-main/users/user-notifications.component.html
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.html
@@ -19,7 +19,7 @@
19 19
20 <ng-template #noVideo> 20 <ng-template #noVideo>
21 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> 21 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
22 22
23 <div class="message" i18n> 23 <div class="message" i18n>
24 The notification concerns a video now unavailable 24 The notification concerns a video now unavailable
25 </div> 25 </div>
@@ -46,7 +46,7 @@
46 <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> 46 <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
47 47
48 <div class="message" i18n> 48 <div class="message" i18n>
49 <a (click)="markAsRead(notification)" [routerLink]="notification.videoAbuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoAbuse.video.name }}</a> 49 <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.abuse.video.name }}</a>
50 </div> 50 </div>
51 </ng-container> 51 </ng-container>
52 52
@@ -65,7 +65,7 @@
65 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> 65 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
66 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> 66 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
67 </a> 67 </a>
68 68
69 <div class="message" i18n> 69 <div class="message" i18n>
70 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a> 70 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a>
71 </div> 71 </div>
@@ -73,7 +73,7 @@
73 73
74 <ng-template #noComment> 74 <ng-template #noComment>
75 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> 75 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
76 76
77 <div class="message" i18n> 77 <div class="message" i18n>
78 The notification concerns a comment now unavailable 78 The notification concerns a comment now unavailable
79 </div> 79 </div>
diff --git a/client/src/app/shared/shared-moderation/video-abuse.service.ts b/client/src/app/shared/shared-moderation/abuse.service.ts
index 44dea44a5..f45018d5c 100644
--- a/client/src/app/shared/shared-moderation/video-abuse.service.ts
+++ b/client/src/app/shared/shared-moderation/abuse.service.ts
@@ -5,12 +5,12 @@ 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 { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '@shared/models' 8import { AbuseUpdate, ResultList, Abuse, AbuseCreate, AbuseState } from '@shared/models'
9import { environment } from '../../../environments/environment' 9import { environment } from '../../../environments/environment'
10 10
11@Injectable() 11@Injectable()
12export class VideoAbuseService { 12export class AbuseService {
13 private static BASE_VIDEO_ABUSE_URL = environment.apiUrl + '/api/v1/videos/' 13 private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses'
14 14
15 constructor ( 15 constructor (
16 private authHttp: HttpClient, 16 private authHttp: HttpClient,
@@ -18,13 +18,13 @@ export class VideoAbuseService {
18 private restExtractor: RestExtractor 18 private restExtractor: RestExtractor
19 ) {} 19 ) {}
20 20
21 getVideoAbuses (options: { 21 getAbuses (options: {
22 pagination: RestPagination, 22 pagination: RestPagination,
23 sort: SortMeta, 23 sort: SortMeta,
24 search?: string 24 search?: string
25 }): Observable<ResultList<VideoAbuse>> { 25 }): Observable<ResultList<Abuse>> {
26 const { pagination, sort, search } = options 26 const { pagination, sort, search } = options
27 const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse' 27 const url = AbuseService.BASE_ABUSE_URL + 'abuse'
28 28
29 let params = new HttpParams() 29 let params = new HttpParams()
30 params = this.restService.addRestGetParams(params, pagination, sort) 30 params = this.restService.addRestGetParams(params, pagination, sort)
@@ -35,9 +35,9 @@ export class VideoAbuseService {
35 state: { 35 state: {
36 prefix: 'state:', 36 prefix: 'state:',
37 handler: v => { 37 handler: v => {
38 if (v === 'accepted') return VideoAbuseState.ACCEPTED 38 if (v === 'accepted') return AbuseState.ACCEPTED
39 if (v === 'pending') return VideoAbuseState.PENDING 39 if (v === 'pending') return AbuseState.PENDING
40 if (v === 'rejected') return VideoAbuseState.REJECTED 40 if (v === 'rejected') return AbuseState.REJECTED
41 41
42 return undefined 42 return undefined
43 } 43 }
@@ -59,14 +59,14 @@ export class VideoAbuseService {
59 params = this.restService.addObjectParams(params, filters) 59 params = this.restService.addObjectParams(params, filters)
60 } 60 }
61 61
62 return this.authHttp.get<ResultList<VideoAbuse>>(url, { params }) 62 return this.authHttp.get<ResultList<Abuse>>(url, { params })
63 .pipe( 63 .pipe(
64 catchError(res => this.restExtractor.handleError(res)) 64 catchError(res => this.restExtractor.handleError(res))
65 ) 65 )
66 } 66 }
67 67
68 reportVideo (parameters: { id: number } & VideoAbuseCreate) { 68 reportVideo (parameters: AbuseCreate) {
69 const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + parameters.id + '/abuse' 69 const url = AbuseService.BASE_ABUSE_URL
70 70
71 const body = omit(parameters, [ 'id' ]) 71 const body = omit(parameters, [ 'id' ])
72 72
@@ -77,8 +77,8 @@ export class VideoAbuseService {
77 ) 77 )
78 } 78 }
79 79
80 updateVideoAbuse (videoAbuse: VideoAbuse, abuseUpdate: VideoAbuseUpdate) { 80 updateAbuse (abuse: Abuse, abuseUpdate: AbuseUpdate) {
81 const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id 81 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id
82 82
83 return this.authHttp.put(url, abuseUpdate) 83 return this.authHttp.put(url, abuseUpdate)
84 .pipe( 84 .pipe(
@@ -87,8 +87,8 @@ export class VideoAbuseService {
87 ) 87 )
88 } 88 }
89 89
90 removeVideoAbuse (videoAbuse: VideoAbuse) { 90 removeAbuse (abuse: Abuse) {
91 const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id 91 const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id
92 92
93 return this.authHttp.delete(url) 93 return this.authHttp.delete(url)
94 .pipe( 94 .pipe(
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts
index 8e74254f6..d6c4a10be 100644
--- a/client/src/app/shared/shared-moderation/index.ts
+++ b/client/src/app/shared/shared-moderation/index.ts
@@ -1,3 +1,4 @@
1export * from './abuse.service'
1export * from './account-block.model' 2export * from './account-block.model'
2export * from './account-blocklist.component' 3export * from './account-blocklist.component'
3export * from './batch-domains-modal.component' 4export * from './batch-domains-modal.component'
@@ -6,7 +7,6 @@ export * from './bulk.service'
6export * from './server-blocklist.component' 7export * from './server-blocklist.component'
7export * from './user-ban-modal.component' 8export * from './user-ban-modal.component'
8export * from './user-moderation-dropdown.component' 9export * from './user-moderation-dropdown.component'
9export * from './video-abuse.service'
10export * from './video-block.component' 10export * from './video-block.component'
11export * from './video-block.service' 11export * from './video-block.service'
12export * from './video-report.component' 12export * from './video-report.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 f7e64dfa3..742193e58 100644
--- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -8,7 +8,7 @@ import { BlocklistService } from './blocklist.service'
8import { BulkService } from './bulk.service' 8import { BulkService } from './bulk.service'
9import { UserBanModalComponent } from './user-ban-modal.component' 9import { UserBanModalComponent } from './user-ban-modal.component'
10import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' 10import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
11import { VideoAbuseService } from './video-abuse.service' 11import { AbuseService } from './abuse.service'
12import { VideoBlockComponent } from './video-block.component' 12import { VideoBlockComponent } from './video-block.component'
13import { VideoBlockService } from './video-block.service' 13import { VideoBlockService } from './video-block.service'
14import { VideoReportComponent } from './video-report.component' 14import { VideoReportComponent } from './video-report.component'
@@ -39,7 +39,7 @@ import { VideoReportComponent } from './video-report.component'
39 providers: [ 39 providers: [
40 BlocklistService, 40 BlocklistService,
41 BulkService, 41 BulkService,
42 VideoAbuseService, 42 AbuseService,
43 VideoBlockService 43 VideoBlockService
44 ] 44 ]
45}) 45})
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 11c805636..b8d9f8d27 100644
--- a/client/src/app/shared/shared-moderation/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/video-report.component.ts
@@ -3,13 +3,13 @@ import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
3import { Component, Input, OnInit, ViewChild } from '@angular/core' 3import { Component, Input, OnInit, ViewChild } from '@angular/core'
4import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 4import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
5import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
6import { FormReactive, FormValidatorService, VideoAbuseValidatorsService } from '@app/shared/shared-forms' 6import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
7import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 7import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
9import { I18n } from '@ngx-translate/i18n-polyfill' 9import { I18n } from '@ngx-translate/i18n-polyfill'
10import { videoAbusePredefinedReasonsMap, VideoAbusePredefinedReasonsString } from '@shared/models/videos/abuse/video-abuse-reason.model' 10import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
11import { Video } from '../shared-main' 11import { Video } from '../shared-main'
12import { VideoAbuseService } from './video-abuse.service' 12import { AbuseService } from './abuse.service'
13 13
14@Component({ 14@Component({
15 selector: 'my-video-report', 15 selector: 'my-video-report',
@@ -22,7 +22,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
22 @ViewChild('modal', { static: true }) modal: NgbModal 22 @ViewChild('modal', { static: true }) modal: NgbModal
23 23
24 error: string = null 24 error: string = null
25 predefinedReasons: { id: VideoAbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] 25 predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
26 embedHtml: SafeHtml 26 embedHtml: SafeHtml
27 27
28 private openedModal: NgbModalRef 28 private openedModal: NgbModalRef
@@ -30,8 +30,8 @@ export class VideoReportComponent extends FormReactive implements OnInit {
30 constructor ( 30 constructor (
31 protected formValidatorService: FormValidatorService, 31 protected formValidatorService: FormValidatorService,
32 private modalService: NgbModal, 32 private modalService: NgbModal,
33 private videoAbuseValidatorsService: VideoAbuseValidatorsService, 33 private abuseValidatorsService: AbuseValidatorsService,
34 private videoAbuseService: VideoAbuseService, 34 private abuseService: AbuseService,
35 private notifier: Notifier, 35 private notifier: Notifier,
36 private sanitizer: DomSanitizer, 36 private sanitizer: DomSanitizer,
37 private i18n: I18n 37 private i18n: I18n
@@ -69,8 +69,8 @@ export class VideoReportComponent extends FormReactive implements OnInit {
69 69
70 ngOnInit () { 70 ngOnInit () {
71 this.buildForm({ 71 this.buildForm({
72 reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON, 72 reason: this.abuseValidatorsService.ABUSE_REASON,
73 predefinedReasons: mapValues(videoAbusePredefinedReasonsMap, r => null), 73 predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null),
74 timestamp: { 74 timestamp: {
75 hasStart: null, 75 hasStart: null,
76 startAt: null, 76 startAt: null,
@@ -136,15 +136,18 @@ export class VideoReportComponent extends FormReactive implements OnInit {
136 136
137 report () { 137 report () {
138 const reason = this.form.get('reason').value 138 const reason = this.form.get('reason').value
139 const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as VideoAbusePredefinedReasonsString[] 139 const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as AbusePredefinedReasonsString[]
140 const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value 140 const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value
141 141
142 this.videoAbuseService.reportVideo({ 142 this.abuseService.reportVideo({
143 id: this.video.id, 143 accountId: this.video.account.id,
144 reason, 144 reason,
145 predefinedReasons, 145 predefinedReasons,
146 startAt: hasStart && startAt ? startAt : undefined, 146 video: {
147 endAt: hasEnd && endAt ? endAt : undefined 147 id: this.video.id,
148 startAt: hasStart && startAt ? startAt : undefined,
149 endAt: hasEnd && endAt ? endAt : undefined
150 }
148 }).subscribe( 151 }).subscribe(
149 () => { 152 () => {
150 this.notifier.success(this.i18n('Video reported.')) 153 this.notifier.success(this.i18n('Video reported.'))