diff options
33 files changed, 515 insertions, 215 deletions
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index af80337ce..31c8e3a8e 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -22,6 +22,7 @@ | |||
22 | <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> | 22 | <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> |
23 | 23 | ||
24 | <my-user-moderation-dropdown | 24 | <my-user-moderation-dropdown |
25 | [prependActions]="prependModerationActions" | ||
25 | buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto" | 26 | buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto" |
26 | (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" | 27 | (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" |
27 | ></my-user-moderation-dropdown> | 28 | ></my-user-moderation-dropdown> |
@@ -50,3 +51,7 @@ | |||
50 | <router-outlet></router-outlet> | 51 | <router-outlet></router-outlet> |
51 | </div> | 52 | </div> |
52 | </div> | 53 | </div> |
54 | |||
55 | <ng-container *ngIf="prependModerationActions"> | ||
56 | <my-account-report #accountReportModal [account]="account"></my-account-report> | ||
57 | </ng-container> | ||
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index 01911cac2..9288fcb42 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { Subscription } from 'rxjs' | 1 | import { Subscription } from 'rxjs' |
2 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' | 2 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' |
3 | import { Component, OnDestroy, OnInit } from '@angular/core' | 3 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute } from '@angular/router' | 4 | import { ActivatedRoute } from '@angular/router' |
5 | import { AuthService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core' | 5 | import { AuthService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core' |
6 | import { Account, AccountService, ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 6 | import { Account, AccountService, DropdownAction, ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main' |
7 | import { AccountReportComponent } from '@app/shared/shared-moderation' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { User, UserRight } from '@shared/models' | 9 | import { User, UserRight } from '@shared/models' |
9 | 10 | ||
@@ -12,6 +13,8 @@ import { User, UserRight } from '@shared/models' | |||
12 | styleUrls: [ './accounts.component.scss' ] | 13 | styleUrls: [ './accounts.component.scss' ] |
13 | }) | 14 | }) |
14 | export class AccountsComponent implements OnInit, OnDestroy { | 15 | export class AccountsComponent implements OnInit, OnDestroy { |
16 | @ViewChild('accountReportModal') accountReportModal: AccountReportComponent | ||
17 | |||
15 | account: Account | 18 | account: Account |
16 | accountUser: User | 19 | accountUser: User |
17 | videoChannels: VideoChannel[] = [] | 20 | videoChannels: VideoChannel[] = [] |
@@ -20,6 +23,8 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
20 | isAccountManageable = false | 23 | isAccountManageable = false |
21 | accountFollowerTitle = '' | 24 | accountFollowerTitle = '' |
22 | 25 | ||
26 | prependModerationActions: DropdownAction<any>[] | ||
27 | |||
23 | private routeSub: Subscription | 28 | private routeSub: Subscription |
24 | 29 | ||
25 | constructor ( | 30 | constructor ( |
@@ -42,24 +47,7 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
42 | map(params => params[ 'accountId' ]), | 47 | map(params => params[ 'accountId' ]), |
43 | distinctUntilChanged(), | 48 | distinctUntilChanged(), |
44 | switchMap(accountId => this.accountService.getAccount(accountId)), | 49 | switchMap(accountId => this.accountService.getAccount(accountId)), |
45 | tap(account => { | 50 | tap(account => this.onAccount(account)), |
46 | this.account = account | ||
47 | |||
48 | if (this.authService.isLoggedIn()) { | ||
49 | this.authService.userInformationLoaded.subscribe( | ||
50 | () => { | ||
51 | this.isAccountManageable = this.account.userId && this.account.userId === this.authService.getUser().id | ||
52 | |||
53 | this.accountFollowerTitle = this.i18n( | ||
54 | '{{followers}} direct account followers', | ||
55 | { followers: this.subscribersDisplayFor(account.followersCount) } | ||
56 | ) | ||
57 | } | ||
58 | ) | ||
59 | } | ||
60 | |||
61 | this.getUserIfNeeded(account) | ||
62 | }), | ||
63 | switchMap(account => this.videoChannelService.listAccountVideoChannels(account)), | 51 | switchMap(account => this.videoChannelService.listAccountVideoChannels(account)), |
64 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) | 52 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) |
65 | ) | 53 | ) |
@@ -107,6 +95,41 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
107 | return this.i18n('{count, plural, =1 {1 subscriber} other {{{count}} subscribers}}', { count }) | 95 | return this.i18n('{count, plural, =1 {1 subscriber} other {{{count}} subscribers}}', { count }) |
108 | } | 96 | } |
109 | 97 | ||
98 | private onAccount (account: Account) { | ||
99 | this.prependModerationActions = undefined | ||
100 | |||
101 | this.account = account | ||
102 | |||
103 | if (this.authService.isLoggedIn()) { | ||
104 | this.authService.userInformationLoaded.subscribe( | ||
105 | () => { | ||
106 | this.isAccountManageable = this.account.userId && this.account.userId === this.authService.getUser().id | ||
107 | |||
108 | this.accountFollowerTitle = this.i18n( | ||
109 | '{{followers}} direct account followers', | ||
110 | { followers: this.subscribersDisplayFor(account.followersCount) } | ||
111 | ) | ||
112 | |||
113 | // It's not our account, we can report it | ||
114 | if (!this.isAccountManageable) { | ||
115 | this.prependModerationActions = [ | ||
116 | { | ||
117 | label: this.i18n('Report account'), | ||
118 | handler: () => this.showReportModal() | ||
119 | } | ||
120 | ] | ||
121 | } | ||
122 | } | ||
123 | ) | ||
124 | } | ||
125 | |||
126 | this.getUserIfNeeded(account) | ||
127 | } | ||
128 | |||
129 | private showReportModal () { | ||
130 | this.accountReportModal.show() | ||
131 | } | ||
132 | |||
110 | private getUserIfNeeded (account: Account) { | 133 | private getUserIfNeeded (account: Account) { |
111 | if (!account.userId || !this.authService.isLoggedIn()) return | 134 | if (!account.userId || !this.authService.isLoggedIn()) return |
112 | 135 | ||
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 1ad73e38a..99502304d 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 | |||
@@ -1,6 +1,6 @@ | |||
1 | <p-table | 1 | <p-table |
2 | [value]="abuses" [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" [lazyLoadOnInit]="false" |
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" |
6 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" | 6 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
@@ -128,6 +128,22 @@ | |||
128 | </td> | 128 | </td> |
129 | </ng-container> | 129 | </ng-container> |
130 | 130 | ||
131 | <ng-container *ngIf="!abuse.comment && !abuse.video"> | ||
132 | <td *ngIf="abuse.flaggedAccount"> | ||
133 | <a [href]="getAccountUrl(abuse)" class="table-account-link" target="_blank" rel="noopener noreferrer"> | ||
134 | <span>{{ abuse.flaggedAccount.displayName }}</span> | ||
135 | |||
136 | <span class="account-flagged-handle">{{ abuse.flaggedAccount.nameWithHostForced }}</span> | ||
137 | </a> | ||
138 | </td> | ||
139 | |||
140 | <td i18n *ngIf="!abuse.flaggedAccount"> | ||
141 | Account deleted | ||
142 | </td> | ||
143 | |||
144 | </ng-container> | ||
145 | |||
146 | |||
131 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> | 147 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> |
132 | 148 | ||
133 | <td class="c-hand abuse-states" [pRowToggler]="abuse"> | 149 | <td class="c-hand abuse-states" [pRowToggler]="abuse"> |
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 1ea61ed37..74c5fe2b3 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 | |||
@@ -1,3 +1,5 @@ | |||
1 | import * as debug from 'debug' | ||
2 | import truncate from 'lodash-es/truncate' | ||
1 | import { SortMeta } from 'primeng/api' | 3 | import { SortMeta } from 'primeng/api' |
2 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' | 4 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' |
3 | import { environment } from 'src/environments/environment' | 5 | import { environment } from 'src/environments/environment' |
@@ -7,11 +9,15 @@ import { ActivatedRoute, Params, Router } from '@angular/router' | |||
7 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' | 9 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' |
8 | import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 10 | import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
9 | import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' | 11 | import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' |
12 | import { VideoCommentService } from '@app/shared/shared-video-comment' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 13 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { Abuse, AbuseState } from '@shared/models' | 14 | import { Abuse, AbuseState } from '@shared/models' |
12 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 15 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
13 | import truncate from 'lodash-es/truncate' | ||
14 | 16 | ||
17 | const logger = debug('peertube:moderation:AbuseListComponent') | ||
18 | |||
19 | // Don't use an abuse model because we need external services to compute some properties | ||
20 | // And this model is only used in this component | ||
15 | export type ProcessedAbuse = Abuse & { | 21 | export type ProcessedAbuse = Abuse & { |
16 | moderationCommentHtml?: string, | 22 | moderationCommentHtml?: string, |
17 | reasonHtml?: string | 23 | reasonHtml?: string |
@@ -45,12 +51,13 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
45 | sort: SortMeta = { field: 'createdAt', order: 1 } | 51 | sort: SortMeta = { field: 'createdAt', order: 1 } |
46 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 52 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
47 | 53 | ||
48 | abuseActions: DropdownAction<Abuse>[][] = [] | 54 | abuseActions: DropdownAction<ProcessedAbuse>[][] = [] |
49 | 55 | ||
50 | constructor ( | 56 | constructor ( |
51 | private notifier: Notifier, | 57 | private notifier: Notifier, |
52 | private abuseService: AbuseService, | 58 | private abuseService: AbuseService, |
53 | private blocklistService: BlocklistService, | 59 | private blocklistService: BlocklistService, |
60 | private commentService: VideoCommentService, | ||
54 | private videoService: VideoService, | 61 | private videoService: VideoService, |
55 | private videoBlocklistService: VideoBlockService, | 62 | private videoBlocklistService: VideoBlockService, |
56 | private confirmService: ConfirmService, | 63 | private confirmService: ConfirmService, |
@@ -63,140 +70,15 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
63 | super() | 70 | super() |
64 | 71 | ||
65 | this.abuseActions = [ | 72 | this.abuseActions = [ |
66 | [ | 73 | this.buildInternalActions(), |
67 | { | ||
68 | label: this.i18n('Internal actions'), | ||
69 | isHeader: true | ||
70 | }, | ||
71 | { | ||
72 | label: this.i18n('Delete report'), | ||
73 | handler: abuse => this.removeAbuse(abuse) | ||
74 | }, | ||
75 | { | ||
76 | label: this.i18n('Add note'), | ||
77 | handler: abuse => this.openModerationCommentModal(abuse), | ||
78 | isDisplayed: abuse => !abuse.moderationComment | ||
79 | }, | ||
80 | { | ||
81 | label: this.i18n('Update note'), | ||
82 | handler: abuse => this.openModerationCommentModal(abuse), | ||
83 | isDisplayed: abuse => !!abuse.moderationComment | ||
84 | }, | ||
85 | { | ||
86 | label: this.i18n('Mark as accepted'), | ||
87 | handler: abuse => this.updateAbuseState(abuse, AbuseState.ACCEPTED), | ||
88 | isDisplayed: abuse => !this.isAbuseAccepted(abuse) | ||
89 | }, | ||
90 | { | ||
91 | label: this.i18n('Mark as rejected'), | ||
92 | handler: abuse => this.updateAbuseState(abuse, AbuseState.REJECTED), | ||
93 | isDisplayed: abuse => !this.isAbuseRejected(abuse) | ||
94 | } | ||
95 | ], | ||
96 | [ | ||
97 | { | ||
98 | label: this.i18n('Actions for the video'), | ||
99 | isHeader: true, | ||
100 | isDisplayed: abuse => abuse.video && !abuse.video.deleted | ||
101 | }, | ||
102 | { | ||
103 | label: this.i18n('Block video'), | ||
104 | isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted, | ||
105 | handler: abuse => { | ||
106 | this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true) | ||
107 | .subscribe( | ||
108 | () => { | ||
109 | this.notifier.success(this.i18n('Video blocked.')) | ||
110 | |||
111 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | ||
112 | }, | ||
113 | |||
114 | err => this.notifier.error(err.message) | ||
115 | ) | ||
116 | } | ||
117 | }, | ||
118 | { | ||
119 | label: this.i18n('Unblock video'), | ||
120 | isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted, | ||
121 | handler: abuse => { | ||
122 | this.videoBlocklistService.unblockVideo(abuse.video.id) | ||
123 | .subscribe( | ||
124 | () => { | ||
125 | this.notifier.success(this.i18n('Video unblocked.')) | ||
126 | |||
127 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | ||
128 | }, | ||
129 | |||
130 | err => this.notifier.error(err.message) | ||
131 | ) | ||
132 | } | ||
133 | }, | ||
134 | { | ||
135 | label: this.i18n('Delete video'), | ||
136 | isDisplayed: abuse => abuse.video && !abuse.video.deleted, | ||
137 | handler: async abuse => { | ||
138 | const res = await this.confirmService.confirm( | ||
139 | this.i18n('Do you really want to delete this video?'), | ||
140 | this.i18n('Delete') | ||
141 | ) | ||
142 | if (res === false) return | ||
143 | 74 | ||
144 | this.videoService.removeVideo(abuse.video.id) | 75 | this.buildFlaggedAccountActions(), |
145 | .subscribe( | ||
146 | () => { | ||
147 | this.notifier.success(this.i18n('Video deleted.')) | ||
148 | 76 | ||
149 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | 77 | this.buildCommentActions(), |
150 | }, | ||
151 | 78 | ||
152 | err => this.notifier.error(err.message) | 79 | this.buildVideoActions(), |
153 | ) | 80 | |
154 | } | 81 | this.buildAccountActions() |
155 | } | ||
156 | ], | ||
157 | [ | ||
158 | { | ||
159 | label: this.i18n('Actions for the reporter'), | ||
160 | isHeader: true, | ||
161 | isDisplayed: abuse => !!abuse.reporterAccount | ||
162 | }, | ||
163 | { | ||
164 | label: this.i18n('Mute reporter'), | ||
165 | isDisplayed: abuse => !!abuse.reporterAccount, | ||
166 | handler: async abuse => { | ||
167 | const account = abuse.reporterAccount as Account | ||
168 | |||
169 | this.blocklistService.blockAccountByInstance(account) | ||
170 | .subscribe( | ||
171 | () => { | ||
172 | this.notifier.success( | ||
173 | this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost }) | ||
174 | ) | ||
175 | |||
176 | account.mutedByInstance = true | ||
177 | }, | ||
178 | |||
179 | err => this.notifier.error(err.message) | ||
180 | ) | ||
181 | } | ||
182 | }, | ||
183 | { | ||
184 | label: this.i18n('Mute server'), | ||
185 | isDisplayed: abuse => abuse.reporterAccount && !abuse.reporterAccount.userId, | ||
186 | handler: async abuse => { | ||
187 | this.blocklistService.blockServerByInstance(abuse.reporterAccount.host) | ||
188 | .subscribe( | ||
189 | () => { | ||
190 | this.notifier.success( | ||
191 | this.i18n('Server {{host}} muted by the instance.', { host: abuse.reporterAccount.host }) | ||
192 | ) | ||
193 | }, | ||
194 | |||
195 | err => this.notifier.error(err.message) | ||
196 | ) | ||
197 | } | ||
198 | } | ||
199 | ] | ||
200 | ] | 82 | ] |
201 | } | 83 | } |
202 | 84 | ||
@@ -207,6 +89,8 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
207 | .subscribe(params => { | 89 | .subscribe(params => { |
208 | this.search = params.search || '' | 90 | this.search = params.search || '' |
209 | 91 | ||
92 | logger('On URL change (search: %s).', this.search) | ||
93 | |||
210 | this.setTableFilter(this.search) | 94 | this.setTableFilter(this.search) |
211 | this.loadData() | 95 | this.loadData() |
212 | }) | 96 | }) |
@@ -264,6 +148,10 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
264 | return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId | 148 | return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId |
265 | } | 149 | } |
266 | 150 | ||
151 | getAccountUrl (abuse: ProcessedAbuse) { | ||
152 | return '/accounts/' + abuse.flaggedAccount.nameWithHost | ||
153 | } | ||
154 | |||
267 | getVideoEmbed (abuse: Abuse) { | 155 | getVideoEmbed (abuse: Abuse) { |
268 | return buildVideoEmbed( | 156 | return buildVideoEmbed( |
269 | buildVideoLink({ | 157 | buildVideoLink({ |
@@ -304,6 +192,8 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
304 | } | 192 | } |
305 | 193 | ||
306 | protected loadData () { | 194 | protected loadData () { |
195 | logger('Load data.') | ||
196 | |||
307 | return this.abuseService.getAbuses({ | 197 | return this.abuseService.getAbuses({ |
308 | pagination: this.pagination, | 198 | pagination: this.pagination, |
309 | sort: this.sort, | 199 | sort: this.sort, |
@@ -356,6 +246,208 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn | |||
356 | ) | 246 | ) |
357 | } | 247 | } |
358 | 248 | ||
249 | private buildInternalActions (): DropdownAction<ProcessedAbuse>[] { | ||
250 | return [ | ||
251 | { | ||
252 | label: this.i18n('Internal actions'), | ||
253 | isHeader: true | ||
254 | }, | ||
255 | { | ||
256 | label: this.i18n('Delete report'), | ||
257 | handler: abuse => this.removeAbuse(abuse) | ||
258 | }, | ||
259 | { | ||
260 | label: this.i18n('Add note'), | ||
261 | handler: abuse => this.openModerationCommentModal(abuse), | ||
262 | isDisplayed: abuse => !abuse.moderationComment | ||
263 | }, | ||
264 | { | ||
265 | label: this.i18n('Update note'), | ||
266 | handler: abuse => this.openModerationCommentModal(abuse), | ||
267 | isDisplayed: abuse => !!abuse.moderationComment | ||
268 | }, | ||
269 | { | ||
270 | label: this.i18n('Mark as accepted'), | ||
271 | handler: abuse => this.updateAbuseState(abuse, AbuseState.ACCEPTED), | ||
272 | isDisplayed: abuse => !this.isAbuseAccepted(abuse) | ||
273 | }, | ||
274 | { | ||
275 | label: this.i18n('Mark as rejected'), | ||
276 | handler: abuse => this.updateAbuseState(abuse, AbuseState.REJECTED), | ||
277 | isDisplayed: abuse => !this.isAbuseRejected(abuse) | ||
278 | } | ||
279 | ] | ||
280 | } | ||
281 | |||
282 | private buildFlaggedAccountActions (): DropdownAction<ProcessedAbuse>[] { | ||
283 | return [ | ||
284 | { | ||
285 | label: this.i18n('Actions for the flagged account'), | ||
286 | isHeader: true, | ||
287 | isDisplayed: abuse => abuse.flaggedAccount && !abuse.comment && !abuse.video | ||
288 | }, | ||
289 | |||
290 | { | ||
291 | label: this.i18n('Mute account'), | ||
292 | isDisplayed: abuse => abuse.flaggedAccount && !abuse.comment && !abuse.video, | ||
293 | handler: abuse => this.muteAccountHelper(abuse.flaggedAccount) | ||
294 | }, | ||
295 | |||
296 | { | ||
297 | label: this.i18n('Mute server account'), | ||
298 | isDisplayed: abuse => abuse.flaggedAccount && !abuse.comment && !abuse.video, | ||
299 | handler: abuse => this.muteServerHelper(abuse.flaggedAccount.host) | ||
300 | } | ||
301 | ] | ||
302 | } | ||
303 | |||
304 | private buildAccountActions (): DropdownAction<ProcessedAbuse>[] { | ||
305 | return [ | ||
306 | { | ||
307 | label: this.i18n('Actions for the reporter'), | ||
308 | isHeader: true, | ||
309 | isDisplayed: abuse => !!abuse.reporterAccount | ||
310 | }, | ||
311 | |||
312 | { | ||
313 | label: this.i18n('Mute reporter'), | ||
314 | isDisplayed: abuse => !!abuse.reporterAccount, | ||
315 | handler: abuse => this.muteAccountHelper(abuse.reporterAccount) | ||
316 | }, | ||
317 | |||
318 | { | ||
319 | label: this.i18n('Mute server'), | ||
320 | isDisplayed: abuse => abuse.reporterAccount && !abuse.reporterAccount.userId, | ||
321 | handler: abuse => this.muteServerHelper(abuse.reporterAccount.host) | ||
322 | } | ||
323 | ] | ||
324 | } | ||
325 | |||
326 | private buildVideoActions (): DropdownAction<ProcessedAbuse>[] { | ||
327 | return [ | ||
328 | { | ||
329 | label: this.i18n('Actions for the video'), | ||
330 | isHeader: true, | ||
331 | isDisplayed: abuse => abuse.video && !abuse.video.deleted | ||
332 | }, | ||
333 | { | ||
334 | label: this.i18n('Block video'), | ||
335 | isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted, | ||
336 | handler: abuse => { | ||
337 | this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true) | ||
338 | .subscribe( | ||
339 | () => { | ||
340 | this.notifier.success(this.i18n('Video blocked.')) | ||
341 | |||
342 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | ||
343 | }, | ||
344 | |||
345 | err => this.notifier.error(err.message) | ||
346 | ) | ||
347 | } | ||
348 | }, | ||
349 | { | ||
350 | label: this.i18n('Unblock video'), | ||
351 | isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted, | ||
352 | handler: abuse => { | ||
353 | this.videoBlocklistService.unblockVideo(abuse.video.id) | ||
354 | .subscribe( | ||
355 | () => { | ||
356 | this.notifier.success(this.i18n('Video unblocked.')) | ||
357 | |||
358 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | ||
359 | }, | ||
360 | |||
361 | err => this.notifier.error(err.message) | ||
362 | ) | ||
363 | } | ||
364 | }, | ||
365 | { | ||
366 | label: this.i18n('Delete video'), | ||
367 | isDisplayed: abuse => abuse.video && !abuse.video.deleted, | ||
368 | handler: async abuse => { | ||
369 | const res = await this.confirmService.confirm( | ||
370 | this.i18n('Do you really want to delete this video?'), | ||
371 | this.i18n('Delete') | ||
372 | ) | ||
373 | if (res === false) return | ||
374 | |||
375 | this.videoService.removeVideo(abuse.video.id) | ||
376 | .subscribe( | ||
377 | () => { | ||
378 | this.notifier.success(this.i18n('Video deleted.')) | ||
379 | |||
380 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | ||
381 | }, | ||
382 | |||
383 | err => this.notifier.error(err.message) | ||
384 | ) | ||
385 | } | ||
386 | } | ||
387 | ] | ||
388 | } | ||
389 | |||
390 | private buildCommentActions (): DropdownAction<ProcessedAbuse>[] { | ||
391 | return [ | ||
392 | { | ||
393 | label: this.i18n('Actions for the comment'), | ||
394 | isHeader: true, | ||
395 | isDisplayed: abuse => abuse.comment && !abuse.comment.deleted | ||
396 | }, | ||
397 | |||
398 | { | ||
399 | label: this.i18n('Delete comment'), | ||
400 | isDisplayed: abuse => abuse.comment && !abuse.comment.deleted, | ||
401 | handler: async abuse => { | ||
402 | const res = await this.confirmService.confirm( | ||
403 | this.i18n('Do you really want to delete this comment?'), | ||
404 | this.i18n('Delete') | ||
405 | ) | ||
406 | if (res === false) return | ||
407 | |||
408 | this.commentService.deleteVideoComment(abuse.comment.video.id, abuse.comment.id) | ||
409 | .subscribe( | ||
410 | () => { | ||
411 | this.notifier.success(this.i18n('Comment deleted.')) | ||
412 | |||
413 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) | ||
414 | }, | ||
415 | |||
416 | err => this.notifier.error(err.message) | ||
417 | ) | ||
418 | } | ||
419 | } | ||
420 | ] | ||
421 | } | ||
422 | |||
423 | private muteAccountHelper (account: Account) { | ||
424 | this.blocklistService.blockAccountByInstance(account) | ||
425 | .subscribe( | ||
426 | () => { | ||
427 | this.notifier.success( | ||
428 | this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost }) | ||
429 | ) | ||
430 | |||
431 | account.mutedByInstance = true | ||
432 | }, | ||
433 | |||
434 | err => this.notifier.error(err.message) | ||
435 | ) | ||
436 | } | ||
437 | |||
438 | private muteServerHelper (host: string) { | ||
439 | this.blocklistService.blockServerByInstance(host) | ||
440 | .subscribe( | ||
441 | () => { | ||
442 | this.notifier.success( | ||
443 | this.i18n('Server {{host}} muted by the instance.', { host: host }) | ||
444 | ) | ||
445 | }, | ||
446 | |||
447 | err => this.notifier.error(err.message) | ||
448 | ) | ||
449 | } | ||
450 | |||
359 | private toHtml (text: string) { | 451 | private toHtml (text: string) { |
360 | return this.markdownRenderer.textMarkdownToHTML(text) | 452 | return this.markdownRenderer.textMarkdownToHTML(text) |
361 | } | 453 | } |
diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index f73c71dc5..65fe94d39 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss | |||
@@ -96,7 +96,8 @@ my-action-dropdown.show { | |||
96 | top: 3px; | 96 | top: 3px; |
97 | } | 97 | } |
98 | 98 | ||
99 | .table-comment-link { | 99 | .table-comment-link, |
100 | .table-account-link { | ||
100 | @include disable-outline; | 101 | @include disable-outline; |
101 | 102 | ||
102 | color: var(--mainForegroundColor); | 103 | color: var(--mainForegroundColor); |
@@ -106,7 +107,13 @@ my-action-dropdown.show { | |||
106 | } | 107 | } |
107 | } | 108 | } |
108 | 109 | ||
109 | .comment-flagged-account { | 110 | .table-account-link { |
111 | display: flex; | ||
112 | flex-direction: column; | ||
113 | } | ||
114 | |||
115 | .comment-flagged-account, | ||
116 | .account-flagged-handle { | ||
110 | font-size: 11px; | 117 | font-size: 11px; |
111 | color: var(--greyForegroundColor); | 118 | color: var(--greyForegroundColor); |
112 | } | 119 | } |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts index 79505c779..d79efbb49 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts | |||
@@ -4,10 +4,9 @@ import { Router } from '@angular/router' | |||
4 | import { Notifier, User } from '@app/core' | 4 | import { Notifier, User } from '@app/core' |
5 | import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms' |
6 | import { Video } from '@app/shared/shared-main' | 6 | import { Video } from '@app/shared/shared-main' |
7 | import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment' | ||
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 8 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { VideoCommentCreate } from '@shared/models' | 9 | import { VideoCommentCreate } from '@shared/models' |
9 | import { VideoComment } from './video-comment.model' | ||
10 | import { VideoCommentService } from './video-comment.service' | ||
11 | 10 | ||
12 | @Component({ | 11 | @Component({ |
13 | selector: 'my-video-comment-add', | 12 | selector: 'my-video-comment-add', |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts index 2a4a6e737..6744a0954 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts | |||
@@ -3,11 +3,10 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } | |||
3 | import { MarkdownService, Notifier, UserService } from '@app/core' | 3 | import { MarkdownService, Notifier, UserService } from '@app/core' |
4 | import { AuthService } from '@app/core/auth' | 4 | import { AuthService } from '@app/core/auth' |
5 | import { Account, Actor, DropdownAction, Video } from '@app/shared/shared-main' | 5 | import { Account, Actor, DropdownAction, Video } from '@app/shared/shared-main' |
6 | import { CommentReportComponent } from '@app/shared/shared-moderation/comment-report.component' | 6 | import { CommentReportComponent } from '@app/shared/shared-moderation/report-modals/comment-report.component' |
7 | import { VideoComment, VideoCommentThreadTree } from '@app/shared/shared-video-comment' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { User, UserRight } from '@shared/models' | 9 | import { User, UserRight } from '@shared/models' |
9 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' | ||
10 | import { VideoComment } from './video-comment.model' | ||
11 | 10 | ||
12 | @Component({ | 11 | @Component({ |
13 | selector: 'my-video-comment', | 12 | selector: 'my-video-comment', |
@@ -136,7 +135,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
136 | this.comment.account = null | 135 | this.comment.account = null |
137 | } | 136 | } |
138 | 137 | ||
139 | if (this.isUserLoggedIn()) { | 138 | if (this.isUserLoggedIn() && this.authService.getUser().account.id !== this.comment.account.id) { |
140 | this.prependModerationActions = [ | 139 | this.prependModerationActions = [ |
141 | { | 140 | { |
142 | label: this.i18n('Report comment'), | 141 | label: this.i18n('Report comment'), |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts index df0018ec6..66494a20a 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts | |||
@@ -4,10 +4,8 @@ import { ActivatedRoute } from '@angular/router' | |||
4 | import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core' | 4 | import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { Syndication, VideoDetails } from '@app/shared/shared-main' | 6 | import { Syndication, VideoDetails } from '@app/shared/shared-main' |
7 | import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' | ||
9 | import { VideoComment } from './video-comment.model' | ||
10 | import { VideoCommentService } from './video-comment.service' | ||
11 | 9 | ||
12 | @Component({ | 10 | @Component({ |
13 | selector: 'my-video-comments', | 11 | selector: 'my-video-comments', |
diff --git a/client/src/app/+videos/+video-watch/video-watch.module.ts b/client/src/app/+videos/+video-watch/video-watch.module.ts index 421170d81..5821dc2b7 100644 --- a/client/src/app/+videos/+video-watch/video-watch.module.ts +++ b/client/src/app/+videos/+video-watch/video-watch.module.ts | |||
@@ -5,16 +5,17 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons' | |||
5 | import { SharedMainModule } from '@app/shared/shared-main' | 5 | import { SharedMainModule } from '@app/shared/shared-main' |
6 | import { SharedModerationModule } from '@app/shared/shared-moderation' | 6 | import { SharedModerationModule } from '@app/shared/shared-moderation' |
7 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' | 7 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' |
8 | import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' | ||
8 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' | 9 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' |
9 | import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' | 10 | import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' |
10 | import { RecommendationsModule } from './recommendations/recommendations.module' | ||
11 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 11 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
12 | import { VideoCommentService } from '../../shared/shared-video-comment/video-comment.service' | ||
12 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' | 13 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' |
13 | import { VideoCommentComponent } from './comment/video-comment.component' | 14 | import { VideoCommentComponent } from './comment/video-comment.component' |
14 | import { VideoCommentService } from './comment/video-comment.service' | ||
15 | import { VideoCommentsComponent } from './comment/video-comments.component' | 15 | import { VideoCommentsComponent } from './comment/video-comments.component' |
16 | import { VideoShareComponent } from './modal/video-share.component' | 16 | import { VideoShareComponent } from './modal/video-share.component' |
17 | import { VideoSupportComponent } from './modal/video-support.component' | 17 | import { VideoSupportComponent } from './modal/video-support.component' |
18 | import { RecommendationsModule } from './recommendations/recommendations.module' | ||
18 | import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive' | 19 | import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive' |
19 | import { VideoDurationPipe } from './video-duration-formatter.pipe' | 20 | import { VideoDurationPipe } from './video-duration-formatter.pipe' |
20 | import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' | 21 | import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' |
@@ -34,7 +35,8 @@ import { VideoWatchComponent } from './video-watch.component' | |||
34 | SharedVideoPlaylistModule, | 35 | SharedVideoPlaylistModule, |
35 | SharedUserSubscriptionModule, | 36 | SharedUserSubscriptionModule, |
36 | SharedModerationModule, | 37 | SharedModerationModule, |
37 | SharedGlobalIconModule | 38 | SharedGlobalIconModule, |
39 | SharedVideoCommentModule | ||
38 | ], | 40 | ], |
39 | 41 | ||
40 | declarations: [ | 42 | declarations: [ |
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts index 1b35ad47d..e6328eddc 100644 --- a/client/src/app/core/rest/rest-table.ts +++ b/client/src/app/core/rest/rest-table.ts | |||
@@ -3,6 +3,9 @@ import { LazyLoadEvent, SortMeta } from 'primeng/api' | |||
3 | import { RestPagination } from './rest-pagination' | 3 | import { RestPagination } from './rest-pagination' |
4 | import { Subject } from 'rxjs' | 4 | import { Subject } from 'rxjs' |
5 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | 5 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' |
6 | import * as debug from 'debug' | ||
7 | |||
8 | const logger = debug('peertube:tables:RestTable') | ||
6 | 9 | ||
7 | export abstract class RestTable { | 10 | export abstract class RestTable { |
8 | 11 | ||
@@ -15,7 +18,7 @@ export abstract class RestTable { | |||
15 | rowsPerPage = this.rowsPerPageOptions[0] | 18 | rowsPerPage = this.rowsPerPageOptions[0] |
16 | expandedRows = {} | 19 | expandedRows = {} |
17 | 20 | ||
18 | private searchStream: Subject<string> | 21 | protected searchStream: Subject<string> |
19 | 22 | ||
20 | abstract getIdentifier (): string | 23 | abstract getIdentifier (): string |
21 | 24 | ||
@@ -37,6 +40,8 @@ export abstract class RestTable { | |||
37 | } | 40 | } |
38 | 41 | ||
39 | loadLazy (event: LazyLoadEvent) { | 42 | loadLazy (event: LazyLoadEvent) { |
43 | logger('Load lazy %o.', event) | ||
44 | |||
40 | this.sort = { | 45 | this.sort = { |
41 | order: event.sortOrder, | 46 | order: event.sortOrder, |
42 | field: event.sortField | 47 | field: event.sortField |
@@ -65,6 +70,9 @@ export abstract class RestTable { | |||
65 | ) | 70 | ) |
66 | .subscribe(search => { | 71 | .subscribe(search => { |
67 | this.search = search | 72 | this.search = search |
73 | |||
74 | logger('On search %s.', this.search) | ||
75 | |||
68 | this.loadData() | 76 | this.loadData() |
69 | }) | 77 | }) |
70 | } | 78 | } |
@@ -75,14 +83,18 @@ export abstract class RestTable { | |||
75 | } | 83 | } |
76 | 84 | ||
77 | onPage (event: { first: number, rows: number }) { | 85 | onPage (event: { first: number, rows: number }) { |
86 | logger('On page %o.', event) | ||
87 | |||
78 | if (this.rowsPerPage !== event.rows) { | 88 | if (this.rowsPerPage !== event.rows) { |
79 | this.rowsPerPage = event.rows | 89 | this.rowsPerPage = event.rows |
80 | this.pagination = { | 90 | this.pagination = { |
81 | start: event.first, | 91 | start: event.first, |
82 | count: this.rowsPerPage | 92 | count: this.rowsPerPage |
83 | } | 93 | } |
94 | |||
84 | this.loadData() | 95 | this.loadData() |
85 | } | 96 | } |
97 | |||
86 | this.expandedRows = {} | 98 | this.expandedRows = {} |
87 | } | 99 | } |
88 | 100 | ||
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 9ec6dbab1..bda88bdee 100644 --- a/client/src/app/shared/shared-main/account/actor.model.ts +++ b/client/src/app/shared/shared-main/account/actor.model.ts | |||
@@ -14,6 +14,8 @@ export abstract class Actor implements ActorServer { | |||
14 | 14 | ||
15 | avatarUrl: string | 15 | avatarUrl: string |
16 | 16 | ||
17 | isLocal: boolean | ||
18 | |||
17 | static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { | 19 | static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { |
18 | if (actor?.avatar?.url) return actor.avatar.url | 20 | if (actor?.avatar?.url) return actor.avatar.url |
19 | 21 | ||
@@ -52,6 +54,10 @@ export abstract class Actor implements ActorServer { | |||
52 | 54 | ||
53 | this.avatar = hash.avatar | 55 | this.avatar = hash.avatar |
54 | 56 | ||
57 | const absoluteAPIUrl = getAbsoluteAPIUrl() | ||
58 | const thisHost = new URL(absoluteAPIUrl).host | ||
59 | this.isLocal = this.host.trim() === thisHost | ||
60 | |||
55 | this.updateComputedAttributes() | 61 | this.updateComputedAttributes() |
56 | } | 62 | } |
57 | 63 | ||
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 a137f8c62..61b48a806 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 | |||
@@ -34,7 +34,9 @@ export class UserNotification implements UserNotificationServer { | |||
34 | threadId: number | 34 | threadId: number |
35 | 35 | ||
36 | video: { | 36 | video: { |
37 | id: number | ||
37 | uuid: string | 38 | uuid: string |
39 | name: string | ||
38 | } | 40 | } |
39 | } | 41 | } |
40 | 42 | ||
@@ -115,13 +117,15 @@ export class UserNotification implements UserNotificationServer { | |||
115 | case UserNotificationType.COMMENT_MENTION: | 117 | case UserNotificationType.COMMENT_MENTION: |
116 | if (!this.comment) break | 118 | if (!this.comment) break |
117 | this.accountUrl = this.buildAccountUrl(this.comment.account) | 119 | this.accountUrl = this.buildAccountUrl(this.comment.account) |
118 | this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ] | 120 | this.commentUrl = this.buildCommentUrl(this.comment) |
119 | break | 121 | break |
120 | 122 | ||
121 | case UserNotificationType.NEW_ABUSE_FOR_MODERATORS: | 123 | case UserNotificationType.NEW_ABUSE_FOR_MODERATORS: |
122 | this.abuseUrl = '/admin/moderation/abuses/list' | 124 | this.abuseUrl = '/admin/moderation/abuses/list' |
123 | 125 | ||
124 | if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video) | 126 | if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video) |
127 | else if (this.abuse.comment) this.commentUrl = this.buildCommentUrl(this.abuse.comment) | ||
128 | else if (this.abuse.account) this.accountUrl = this.buildAccountUrl(this.abuse.account) | ||
125 | break | 129 | break |
126 | 130 | ||
127 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: | 131 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: |
@@ -190,6 +194,10 @@ export class UserNotification implements UserNotificationServer { | |||
190 | return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName | 194 | return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName |
191 | } | 195 | } |
192 | 196 | ||
197 | private buildCommentUrl (comment: { video: { uuid: string }, threadId: number }) { | ||
198 | return [ this.buildVideoUrl(comment.video), { threadId: comment.threadId } ] | ||
199 | } | ||
200 | |||
193 | private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { | 201 | private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { |
194 | actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) | 202 | actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) |
195 | } | 203 | } |
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 2b341af2c..8127ae979 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 | |||
@@ -45,9 +45,22 @@ | |||
45 | <ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS"> | 45 | <ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS"> |
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" *ngIf="notification.videoUrl" i18n> |
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> | 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 | |||
52 | <div class="message" *ngIf="notification.commentUrl" i18n> | ||
53 | <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new comment abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.abuse.comment.video.name }}</a> | ||
54 | </div> | ||
55 | |||
56 | <div class="message" *ngIf="notification.accountUrl" i18n> | ||
57 | <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new account abuse</a> has been created on account <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.abuse.account.displayName }}</a> | ||
58 | </div> | ||
59 | |||
60 | <!-- Deleted entity associated to the abuse --> | ||
61 | <div class="message" *ngIf="!notification.videoUrl && !notification.commentUrl && !notification.accountUrl" i18n> | ||
62 | <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new abuse</a> has been created | ||
63 | </div> | ||
51 | </ng-container> | 64 | </ng-container> |
52 | 65 | ||
53 | <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS"> | 66 | <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS"> |
diff --git a/client/src/app/shared/shared-moderation/comment-report.component.scss b/client/src/app/shared/shared-moderation/comment-report.component.scss deleted file mode 100644 index 17a33d3a2..000000000 --- a/client/src/app/shared/shared-moderation/comment-report.component.scss +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | @import 'variables'; | ||
2 | @import 'mixins'; | ||
3 | |||
4 | .information { | ||
5 | margin-bottom: 20px; | ||
6 | } | ||
7 | |||
8 | textarea { | ||
9 | @include peertube-textarea(100%, 100px); | ||
10 | } | ||
11 | |||
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts index d6c4a10be..41c910ffe 100644 --- a/client/src/app/shared/shared-moderation/index.ts +++ b/client/src/app/shared/shared-moderation/index.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | export * from './report-modals' | ||
2 | |||
1 | export * from './abuse.service' | 3 | export * from './abuse.service' |
2 | export * from './account-block.model' | 4 | export * from './account-block.model' |
3 | export * from './account-blocklist.component' | 5 | export * from './account-blocklist.component' |
@@ -9,5 +11,4 @@ export * from './user-ban-modal.component' | |||
9 | export * from './user-moderation-dropdown.component' | 11 | export * from './user-moderation-dropdown.component' |
10 | export * from './video-block.component' | 12 | export * from './video-block.component' |
11 | export * from './video-block.service' | 13 | export * from './video-block.service' |
12 | export * from './video-report.component' | ||
13 | export * from './shared-moderation.module' | 14 | export * from './shared-moderation.module' |
diff --git a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts new file mode 100644 index 000000000..78ca934c7 --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts | |||
@@ -0,0 +1,94 @@ | |||
1 | import { mapValues, pickBy } from 'lodash-es' | ||
2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | ||
3 | import { Notifier } from '@app/core' | ||
4 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { Account } from '@app/shared/shared-main' | ||
6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
9 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' | ||
10 | import { AbuseService } from '../abuse.service' | ||
11 | |||
12 | @Component({ | ||
13 | selector: 'my-account-report', | ||
14 | templateUrl: './report.component.html', | ||
15 | styleUrls: [ './report.component.scss' ] | ||
16 | }) | ||
17 | export class AccountReportComponent extends FormReactive implements OnInit { | ||
18 | @Input() account: Account = null | ||
19 | |||
20 | @ViewChild('modal', { static: true }) modal: NgbModal | ||
21 | |||
22 | error: string = null | ||
23 | predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] | ||
24 | modalTitle: string | ||
25 | |||
26 | private openedModal: NgbModalRef | ||
27 | |||
28 | constructor ( | ||
29 | protected formValidatorService: FormValidatorService, | ||
30 | private modalService: NgbModal, | ||
31 | private abuseValidatorsService: AbuseValidatorsService, | ||
32 | private abuseService: AbuseService, | ||
33 | private notifier: Notifier, | ||
34 | private i18n: I18n | ||
35 | ) { | ||
36 | super() | ||
37 | } | ||
38 | |||
39 | get currentHost () { | ||
40 | return window.location.host | ||
41 | } | ||
42 | |||
43 | get originHost () { | ||
44 | if (this.isRemote()) { | ||
45 | return this.account.host | ||
46 | } | ||
47 | |||
48 | return '' | ||
49 | } | ||
50 | |||
51 | ngOnInit () { | ||
52 | this.modalTitle = this.i18n('Report {{displayName}}', { displayName: this.account.displayName }) | ||
53 | |||
54 | this.buildForm({ | ||
55 | reason: this.abuseValidatorsService.ABUSE_REASON, | ||
56 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) | ||
57 | }) | ||
58 | |||
59 | this.predefinedReasons = this.abuseService.getPrefefinedReasons('account') | ||
60 | } | ||
61 | |||
62 | show () { | ||
63 | this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false, size: 'lg' }) | ||
64 | } | ||
65 | |||
66 | hide () { | ||
67 | this.openedModal.close() | ||
68 | this.openedModal = null | ||
69 | } | ||
70 | |||
71 | report () { | ||
72 | const reason = this.form.get('reason').value | ||
73 | const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as AbusePredefinedReasonsString[] | ||
74 | |||
75 | this.abuseService.reportVideo({ | ||
76 | reason, | ||
77 | predefinedReasons, | ||
78 | account: { | ||
79 | id: this.account.id | ||
80 | } | ||
81 | }).subscribe( | ||
82 | () => { | ||
83 | this.notifier.success(this.i18n('Account reported.')) | ||
84 | this.hide() | ||
85 | }, | ||
86 | |||
87 | err => this.notifier.error(err.message) | ||
88 | ) | ||
89 | } | ||
90 | |||
91 | isRemote () { | ||
92 | return !this.account.isLocal | ||
93 | } | ||
94 | } | ||
diff --git a/client/src/app/shared/shared-moderation/comment-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts index 5db4b2dc1..00d7b8d34 100644 --- a/client/src/app/shared/shared-moderation/comment-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts | |||
@@ -1,28 +1,27 @@ | |||
1 | import { mapValues, pickBy } from 'lodash-es' | 1 | import { mapValues, pickBy } from 'lodash-es' |
2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
3 | import { SafeHtml } from '@angular/platform-browser' | ||
4 | import { VideoComment } from '@app/+videos/+video-watch/comment/video-comment.model' | ||
5 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
6 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 4 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
5 | import { VideoComment } from '@app/shared/shared-video-comment' | ||
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' | 9 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' |
11 | import { AbuseService } from './abuse.service' | 10 | import { AbuseService } from '../abuse.service' |
12 | 11 | ||
13 | @Component({ | 12 | @Component({ |
14 | selector: 'my-comment-report', | 13 | selector: 'my-comment-report', |
15 | templateUrl: './comment-report.component.html', | 14 | templateUrl: './report.component.html', |
16 | styleUrls: [ './comment-report.component.scss' ] | 15 | styleUrls: [ './report.component.scss' ] |
17 | }) | 16 | }) |
18 | export class CommentReportComponent extends FormReactive implements OnInit { | 17 | export class CommentReportComponent extends FormReactive implements OnInit { |
19 | @Input() comment: VideoComment = null | 18 | @Input() comment: VideoComment = null |
20 | 19 | ||
21 | @ViewChild('modal', { static: true }) modal: NgbModal | 20 | @ViewChild('modal', { static: true }) modal: NgbModal |
22 | 21 | ||
22 | modalTitle: string | ||
23 | error: string = null | 23 | error: string = null |
24 | predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] | 24 | predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] |
25 | embedHtml: SafeHtml | ||
26 | 25 | ||
27 | private openedModal: NgbModalRef | 26 | private openedModal: NgbModalRef |
28 | 27 | ||
@@ -42,7 +41,7 @@ export class CommentReportComponent extends FormReactive implements OnInit { | |||
42 | } | 41 | } |
43 | 42 | ||
44 | get originHost () { | 43 | get originHost () { |
45 | if (this.isRemoteComment()) { | 44 | if (this.isRemote()) { |
46 | return this.comment.account.host | 45 | return this.comment.account.host |
47 | } | 46 | } |
48 | 47 | ||
@@ -50,6 +49,8 @@ export class CommentReportComponent extends FormReactive implements OnInit { | |||
50 | } | 49 | } |
51 | 50 | ||
52 | ngOnInit () { | 51 | ngOnInit () { |
52 | this.modalTitle = this.i18n('Report comment') | ||
53 | |||
53 | this.buildForm({ | 54 | this.buildForm({ |
54 | reason: this.abuseValidatorsService.ABUSE_REASON, | 55 | reason: this.abuseValidatorsService.ABUSE_REASON, |
55 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) | 56 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) |
@@ -87,7 +88,7 @@ export class CommentReportComponent extends FormReactive implements OnInit { | |||
87 | ) | 88 | ) |
88 | } | 89 | } |
89 | 90 | ||
90 | isRemoteComment () { | 91 | isRemote () { |
91 | return !this.comment.isLocal | 92 | return !this.comment.isLocal |
92 | } | 93 | } |
93 | } | 94 | } |
diff --git a/client/src/app/shared/shared-moderation/report-modals/index.ts b/client/src/app/shared/shared-moderation/report-modals/index.ts new file mode 100644 index 000000000..f3c4058ae --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './account-report.component' | ||
2 | export * from './comment-report.component' | ||
3 | export * from './video-report.component' | ||
diff --git a/client/src/app/shared/shared-moderation/comment-report.component.html b/client/src/app/shared/shared-moderation/report-modals/report.component.html index 1105b3788..bda62312f 100644 --- a/client/src/app/shared/shared-moderation/comment-report.component.html +++ b/client/src/app/shared/shared-moderation/report-modals/report.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Report comment</h4> | 3 | <h4 class="modal-title">{{ modalTitle }}</h4> |
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
@@ -34,7 +34,7 @@ | |||
34 | 34 | ||
35 | <div class="col-7"> | 35 | <div class="col-7"> |
36 | <div i18n class="information"> | 36 | <div i18n class="information"> |
37 | Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemoteComment()"> and will be forwarded to the comment origin ({{ originHost }}) too</ng-container>. | 37 | Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemote()"> and will be forwarded to the comment origin ({{ originHost }}) too</ng-container>. |
38 | </div> | 38 | </div> |
39 | 39 | ||
40 | <div class="form-group"> | 40 | <div class="form-group"> |
diff --git a/client/src/app/shared/shared-moderation/video-report.component.scss b/client/src/app/shared/shared-moderation/report-modals/report.component.scss index b2606cbd8..b2606cbd8 100644 --- a/client/src/app/shared/shared-moderation/video-report.component.scss +++ b/client/src/app/shared/shared-moderation/report-modals/report.component.scss | |||
diff --git a/client/src/app/shared/shared-moderation/video-report.component.html b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html index b724ecb18..4947088d1 100644 --- a/client/src/app/shared/shared-moderation/video-report.component.html +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html | |||
@@ -72,7 +72,7 @@ | |||
72 | </div> | 72 | </div> |
73 | 73 | ||
74 | <div i18n class="information"> | 74 | <div i18n class="information"> |
75 | Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemoteVideo()"> and will be forwarded to the video origin ({{ originHost }}) too</ng-container>. | 75 | Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemote()"> and will be forwarded to the video origin ({{ originHost }}) too</ng-container>. |
76 | </div> | 76 | </div> |
77 | 77 | ||
78 | <div class="form-group"> | 78 | <div class="form-group"> |
diff --git a/client/src/app/shared/shared-moderation/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts index 26e7b62ba..7d53ea3c9 100644 --- a/client/src/app/shared/shared-moderation/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts | |||
@@ -8,13 +8,13 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | |||
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' | 10 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' |
11 | import { Video } from '../shared-main' | 11 | import { Video } from '../../shared-main' |
12 | import { AbuseService } from './abuse.service' | 12 | import { AbuseService } from '../abuse.service' |
13 | 13 | ||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-video-report', | 15 | selector: 'my-video-report', |
16 | templateUrl: './video-report.component.html', | 16 | templateUrl: './video-report.component.html', |
17 | styleUrls: [ './video-report.component.scss' ] | 17 | styleUrls: [ './report.component.scss' ] |
18 | }) | 18 | }) |
19 | export class VideoReportComponent extends FormReactive implements OnInit { | 19 | export class VideoReportComponent extends FormReactive implements OnInit { |
20 | @Input() video: Video = null | 20 | @Input() video: Video = null |
@@ -44,7 +44,7 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
44 | } | 44 | } |
45 | 45 | ||
46 | get originHost () { | 46 | get originHost () { |
47 | if (this.isRemoteVideo()) { | 47 | if (this.isRemote()) { |
48 | return this.video.account.host | 48 | return this.video.account.host |
49 | } | 49 | } |
50 | 50 | ||
@@ -116,7 +116,7 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
116 | ) | 116 | ) |
117 | } | 117 | } |
118 | 118 | ||
119 | isRemoteVideo () { | 119 | isRemote () { |
120 | return !this.video.isLocal | 120 | return !this.video.isLocal |
121 | } | 121 | } |
122 | } | 122 | } |
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 ff4021a33..8fa9ee794 100644 --- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts +++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts | |||
@@ -3,22 +3,23 @@ import { NgModule } from '@angular/core' | |||
3 | import { SharedFormModule } from '../shared-forms/shared-form.module' | 3 | import { SharedFormModule } from '../shared-forms/shared-form.module' |
4 | import { SharedGlobalIconModule } from '../shared-icons' | 4 | import { SharedGlobalIconModule } from '../shared-icons' |
5 | import { SharedMainModule } from '../shared-main/shared-main.module' | 5 | import { SharedMainModule } from '../shared-main/shared-main.module' |
6 | import { SharedVideoCommentModule } from '../shared-video-comment' | ||
7 | import { AbuseService } from './abuse.service' | ||
6 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' | 8 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' |
7 | import { BlocklistService } from './blocklist.service' | 9 | import { BlocklistService } from './blocklist.service' |
8 | import { BulkService } from './bulk.service' | 10 | import { BulkService } from './bulk.service' |
9 | import { UserBanModalComponent } from './user-ban-modal.component' | 11 | import { UserBanModalComponent } from './user-ban-modal.component' |
10 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' | 12 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' |
11 | import { AbuseService } from './abuse.service' | ||
12 | import { VideoBlockComponent } from './video-block.component' | 13 | import { VideoBlockComponent } from './video-block.component' |
13 | import { VideoBlockService } from './video-block.service' | 14 | import { VideoBlockService } from './video-block.service' |
14 | import { VideoReportComponent } from './video-report.component' | 15 | import { VideoReportComponent, AccountReportComponent, CommentReportComponent } from './report-modals' |
15 | import { CommentReportComponent } from './comment-report.component' | ||
16 | 16 | ||
17 | @NgModule({ | 17 | @NgModule({ |
18 | imports: [ | 18 | imports: [ |
19 | SharedMainModule, | 19 | SharedMainModule, |
20 | SharedFormModule, | 20 | SharedFormModule, |
21 | SharedGlobalIconModule | 21 | SharedGlobalIconModule, |
22 | SharedVideoCommentModule | ||
22 | ], | 23 | ], |
23 | 24 | ||
24 | declarations: [ | 25 | declarations: [ |
@@ -27,7 +28,8 @@ import { CommentReportComponent } from './comment-report.component' | |||
27 | VideoBlockComponent, | 28 | VideoBlockComponent, |
28 | VideoReportComponent, | 29 | VideoReportComponent, |
29 | BatchDomainsModalComponent, | 30 | BatchDomainsModalComponent, |
30 | CommentReportComponent | 31 | CommentReportComponent, |
32 | AccountReportComponent | ||
31 | ], | 33 | ], |
32 | 34 | ||
33 | exports: [ | 35 | exports: [ |
@@ -36,7 +38,8 @@ import { CommentReportComponent } from './comment-report.component' | |||
36 | VideoBlockComponent, | 38 | VideoBlockComponent, |
37 | VideoReportComponent, | 39 | VideoReportComponent, |
38 | BatchDomainsModalComponent, | 40 | BatchDomainsModalComponent, |
39 | CommentReportComponent | 41 | CommentReportComponent, |
42 | AccountReportComponent | ||
40 | ], | 43 | ], |
41 | 44 | ||
42 | providers: [ | 45 | providers: [ |
diff --git a/client/src/app/shared/shared-video-comment/index.ts b/client/src/app/shared/shared-video-comment/index.ts new file mode 100644 index 000000000..b1195f232 --- /dev/null +++ b/client/src/app/shared/shared-video-comment/index.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export * from './video-comment.service' | ||
2 | export * from './video-comment.model' | ||
3 | export * from './video-comment-thread-tree.model' | ||
4 | |||
5 | export * from './shared-video-comment.module' | ||
diff --git a/client/src/app/shared/shared-video-comment/shared-video-comment.module.ts b/client/src/app/shared/shared-video-comment/shared-video-comment.module.ts new file mode 100644 index 000000000..41b329861 --- /dev/null +++ b/client/src/app/shared/shared-video-comment/shared-video-comment.module.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
4 | import { VideoCommentService } from './video-comment.service' | ||
5 | |||
6 | @NgModule({ | ||
7 | imports: [ | ||
8 | SharedMainModule | ||
9 | ], | ||
10 | |||
11 | declarations: [ ], | ||
12 | |||
13 | exports: [ ], | ||
14 | |||
15 | providers: [ | ||
16 | VideoCommentService | ||
17 | ] | ||
18 | }) | ||
19 | export class SharedVideoCommentModule { } | ||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts b/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts index 7c2aaeadd..7c2aaeadd 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts +++ b/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts | |||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts index e85443196..e85443196 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.model.ts +++ b/client/src/app/shared/shared-video-comment/video-comment.model.ts | |||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts index a73fb9ca8..81c65aa38 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts | |||
@@ -11,7 +11,7 @@ import { | |||
11 | VideoCommentCreate, | 11 | VideoCommentCreate, |
12 | VideoCommentThreadTree as VideoCommentThreadTreeServerModel | 12 | VideoCommentThreadTree as VideoCommentThreadTreeServerModel |
13 | } from '@shared/models' | 13 | } from '@shared/models' |
14 | import { environment } from '../../../../environments/environment' | 14 | import { environment } from '../../../environments/environment' |
15 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' | 15 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' |
16 | import { VideoComment } from './video-comment.model' | 16 | import { VideoComment } from './video-comment.model' |
17 | 17 | ||
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 5a6f37bb9..d54eab966 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -311,7 +311,8 @@ class Emailer { | |||
311 | videoPublishedAt: new Date(video.publishedAt).toLocaleString(), | 311 | videoPublishedAt: new Date(video.publishedAt).toLocaleString(), |
312 | videoName: video.name, | 312 | videoName: video.name, |
313 | reason: abuse.reason, | 313 | reason: abuse.reason, |
314 | videoChannel: video.VideoChannel, | 314 | videoChannel: abuse.video.channel, |
315 | reporter, | ||
315 | action | 316 | action |
316 | } | 317 | } |
317 | } | 318 | } |
@@ -330,6 +331,7 @@ class Emailer { | |||
330 | commentCreatedAt: new Date(comment.createdAt).toLocaleString(), | 331 | commentCreatedAt: new Date(comment.createdAt).toLocaleString(), |
331 | reason: abuse.reason, | 332 | reason: abuse.reason, |
332 | flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(), | 333 | flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(), |
334 | reporter, | ||
333 | action | 335 | action |
334 | } | 336 | } |
335 | } | 337 | } |
@@ -346,6 +348,7 @@ class Emailer { | |||
346 | accountDisplayName: account.getDisplayName(), | 348 | accountDisplayName: account.getDisplayName(), |
347 | isLocal: account.isOwned(), | 349 | isLocal: account.isOwned(), |
348 | reason: abuse.reason, | 350 | reason: abuse.reason, |
351 | reporter, | ||
349 | action | 352 | action |
350 | } | 353 | } |
351 | } | 354 | } |
diff --git a/server/lib/emails/account-abuse-new/html.pug b/server/lib/emails/account-abuse-new/html.pug index 06be8025b..f1aa2886e 100644 --- a/server/lib/emails/account-abuse-new/html.pug +++ b/server/lib/emails/account-abuse-new/html.pug | |||
@@ -6,8 +6,8 @@ block title | |||
6 | 6 | ||
7 | block content | 7 | block content |
8 | p | 8 | p |
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account " | 9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account |
10 | a(href=accountUrl) #{accountDisplayName} | 10 | a(href=accountUrl) #{accountDisplayName} |
11 | 11 | ||
12 | p The reporter, #{reporter}, cited the following reason(s): | 12 | p The reporter, #{reporter}, cited the following reason(s): |
13 | blockquote #{reason} | 13 | blockquote #{reason} |
diff --git a/server/lib/emails/video-comment-abuse-new/html.pug b/server/lib/emails/video-comment-abuse-new/html.pug index fc1c3e4e7..e92d986b5 100644 --- a/server/lib/emails/video-comment-abuse-new/html.pug +++ b/server/lib/emails/video-comment-abuse-new/html.pug | |||
@@ -6,10 +6,10 @@ block title | |||
6 | 6 | ||
7 | block content | 7 | block content |
8 | p | 8 | p |
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment " | 9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '} |
10 | a(href=commentUrl) on video #{videoName} | 10 | a(href=commentUrl) comment on video "#{videoName}" |
11 | | of #{flaggedAccount} | 11 | | of #{flaggedAccount} |
12 | | created on #{commentCreatedAt} | 12 | | created on #{commentCreatedAt} |
13 | 13 | ||
14 | p The reporter, #{reporter}, cited the following reason(s): | 14 | p The reporter, #{reporter}, cited the following reason(s): |
15 | blockquote #{reason} | 15 | blockquote #{reason} |
diff --git a/shared/models/moderation/abuse/abuse.model.ts b/shared/models/moderation/abuse/abuse.model.ts index 74798ab2c..0a0c6bd35 100644 --- a/shared/models/moderation/abuse/abuse.model.ts +++ b/shared/models/moderation/abuse/abuse.model.ts | |||
@@ -62,9 +62,9 @@ export interface Abuse { | |||
62 | // FIXME: deprecated in 2.3, remove the following properties | 62 | // FIXME: deprecated in 2.3, remove the following properties |
63 | 63 | ||
64 | // @deprecated | 64 | // @deprecated |
65 | startAt: null | 65 | startAt?: null |
66 | // @deprecated | 66 | // @deprecated |
67 | endAt: null | 67 | endAt?: null |
68 | 68 | ||
69 | // @deprecated | 69 | // @deprecated |
70 | count?: number | 70 | count?: number |
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts index 11d96fd50..5f7c33976 100644 --- a/shared/models/users/user-notification.model.ts +++ b/shared/models/users/user-notification.model.ts | |||
@@ -73,7 +73,9 @@ export interface UserNotification { | |||
73 | threadId: number | 73 | threadId: number |
74 | 74 | ||
75 | video: { | 75 | video: { |
76 | id: number | ||
76 | uuid: string | 77 | uuid: string |
78 | name: string | ||
77 | } | 79 | } |
78 | } | 80 | } |
79 | 81 | ||