X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2F%2Badmin%2Fmoderation%2Fvideo-abuse-list%2Fvideo-abuse-list.component.ts;h=39f619cc35f2169950d057e809ce4e73670df490;hb=86ab22921b3ea2660715d1db47fec7037b309d12;hp=7a219c846addead14cab409ad6ac3e9b678d74b4;hpb=198d764ff08047ec469b21b1e8c54d8d29ef2b8d;p=github%2FChocobozzz%2FPeerTube.git diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index 7a219c846..39f619cc3 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts @@ -1,63 +1,222 @@ -import { Component, OnInit, ViewChild } from '@angular/core' -import { Account } from '../../../shared/account/account.model' -import { NotificationsService } from 'angular2-notifications' -import { SortMeta } from 'primeng/components/common/sortmeta' +import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core' +import { Account } from '@app/shared/account/account.model' +import { Notifier } from '@app/core' +import { SortMeta } from 'primeng/api' import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' -import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' +import { RestPagination, RestTable, VideoAbuseService, VideoBlacklistService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' import { ConfirmService } from '../../../core/index' import { ModerationCommentModalComponent } from './moderation-comment-modal.component' import { Video } from '../../../shared/video/video.model' +import { MarkdownService } from '@app/shared/renderer' +import { Actor } from '@app/shared/actor/actor.model' +import { buildVideoLink, buildVideoEmbed } from 'src/assets/player/utils' +import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' +import { DomSanitizer } from '@angular/platform-browser' +import { BlocklistService } from '@app/shared/blocklist' +import { VideoService } from '@app/shared/video/video.service' +import { ActivatedRoute, Params, Router } from '@angular/router' +import { filter } from 'rxjs/operators' + +export type ProcessedVideoAbuse = VideoAbuse & { + moderationCommentHtml?: string, + reasonHtml?: string + embedHtml?: string + updatedAt?: Date + // override bare server-side definitions with rich client-side definitions + reporterAccount: Account + video: VideoAbuse['video'] & { + channel: VideoAbuse['video']['channel'] & { + ownerAccount: Account + } + } +} @Component({ selector: 'my-video-abuse-list', templateUrl: './video-abuse-list.component.html', - styleUrls: [ '../moderation.component.scss'] + styleUrls: [ '../moderation.component.scss', './video-abuse-list.component.scss' ] }) -export class VideoAbuseListComponent extends RestTable implements OnInit { - @ViewChild('moderationCommentModal') moderationCommentModal: ModerationCommentModalComponent +export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit { + @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent - videoAbuses: VideoAbuse[] = [] + videoAbuses: ProcessedVideoAbuse[] = [] totalRecords = 0 - rowsPerPage = 10 sort: SortMeta = { field: 'createdAt', order: 1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } - videoAbuseActions: DropdownAction[] = [] + videoAbuseActions: DropdownAction[][] = [] constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private videoAbuseService: VideoAbuseService, + private blocklistService: BlocklistService, + private videoService: VideoService, + private videoBlacklistService: VideoBlacklistService, private confirmService: ConfirmService, - private i18n: I18n + private i18n: I18n, + private markdownRenderer: MarkdownService, + private sanitizer: DomSanitizer, + private route: ActivatedRoute, + private router: Router ) { super() this.videoAbuseActions = [ - { - label: this.i18n('Delete this report'), - handler: videoAbuse => this.removeVideoAbuse(videoAbuse) - }, - { - label: this.i18n('Update moderation comment'), - handler: videoAbuse => this.openModerationCommentModal(videoAbuse) - }, - { - label: this.i18n('Mark as accepted'), - handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), - isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) - }, - { - label: this.i18n('Mark as rejected'), - handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), - isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) - } + [ + { + label: this.i18n('Internal actions'), + isHeader: true + }, + { + label: this.i18n('Delete report'), + handler: videoAbuse => this.removeVideoAbuse(videoAbuse) + }, + { + label: this.i18n('Add note'), + handler: videoAbuse => this.openModerationCommentModal(videoAbuse), + isDisplayed: videoAbuse => !videoAbuse.moderationComment + }, + { + label: this.i18n('Update note'), + handler: videoAbuse => this.openModerationCommentModal(videoAbuse), + isDisplayed: videoAbuse => !!videoAbuse.moderationComment + }, + { + label: this.i18n('Mark as accepted'), + handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), + isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) + }, + { + label: this.i18n('Mark as rejected'), + handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), + isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) + } + ], + [ + { + label: this.i18n('Actions for the video'), + isHeader: true, + isDisplayed: videoAbuse => !videoAbuse.video.deleted + }, + { + label: this.i18n('Blacklist video'), + isDisplayed: videoAbuse => !videoAbuse.video.deleted && !videoAbuse.video.blacklisted, + handler: videoAbuse => { + this.videoBlacklistService.blacklistVideo(videoAbuse.video.id, undefined, true) + .subscribe( + () => { + this.notifier.success(this.i18n('Video blacklisted.')) + + this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) + }, + + err => this.notifier.error(err.message) + ) + } + }, + { + label: this.i18n('Unblacklist video'), + isDisplayed: videoAbuse => !videoAbuse.video.deleted && videoAbuse.video.blacklisted, + handler: videoAbuse => { + this.videoBlacklistService.removeVideoFromBlacklist(videoAbuse.video.id) + .subscribe( + () => { + this.notifier.success(this.i18n('Video unblacklisted.')) + + this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) + }, + + err => this.notifier.error(err.message) + ) + } + }, + { + label: this.i18n('Delete video'), + isDisplayed: videoAbuse => !videoAbuse.video.deleted, + handler: async videoAbuse => { + const res = await this.confirmService.confirm( + this.i18n('Do you really want to delete this video?'), + this.i18n('Delete') + ) + if (res === false) return + + this.videoService.removeVideo(videoAbuse.video.id) + .subscribe( + () => { + this.notifier.success(this.i18n('Video deleted.')) + + this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) + }, + + err => this.notifier.error(err.message) + ) + } + } + ], + [ + { + label: this.i18n('Actions for the reporter'), + isHeader: true + }, + { + label: this.i18n('Mute reporter'), + handler: async videoAbuse => { + const account = videoAbuse.reporterAccount as Account + + this.blocklistService.blockAccountByInstance(account) + .subscribe( + () => { + this.notifier.success( + this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost }) + ) + + account.mutedByInstance = true + }, + + err => this.notifier.error(err.message) + ) + } + }, + { + label: this.i18n('Mute server'), + isDisplayed: videoAbuse => !videoAbuse.reporterAccount.userId, + handler: async videoAbuse => { + this.blocklistService.blockServerByInstance(videoAbuse.reporterAccount.host) + .subscribe( + () => { + this.notifier.success( + this.i18n('Server {{host}} muted by the instance.', { host: videoAbuse.reporterAccount.host }) + ) + }, + + err => this.notifier.error(err.message) + ) + } + } + ] ] } ngOnInit () { this.initialize() + + this.route.queryParams + .pipe(filter(params => params.search !== undefined && params.search !== null)) + .subscribe(params => { + this.search = params.search + this.setTableFilter(params.search) + this.loadData() + }) + } + + ngAfterViewInit () { + if (this.search) this.setTableFilter(this.search) + } + + getIdentifier () { + return 'VideoAbuseListComponent' } openModerationCommentModal (videoAbuse: VideoAbuse) { @@ -68,10 +227,25 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { this.loadData() } - createByString (account: Account) { - return Account.CREATE_BY_STRING(account.name, account.host) + /* Table filter functions */ + onAbuseSearch (event: Event) { + this.onSearch(event) + this.setQueryParams((event.target as HTMLInputElement).value) + } + + setQueryParams (search: string) { + const queryParams: Params = {} + if (search) Object.assign(queryParams, { search }) + this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams }) } + resetTableFilter () { + this.setTableFilter('') + this.setQueryParams('') + this.resetSearch() + } + /* END Table filter functions */ + isVideoAbuseAccepted (videoAbuse: VideoAbuse) { return videoAbuse.state.id === VideoAbuseState.ACCEPTED } @@ -84,20 +258,30 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { return Video.buildClientUrl(videoAbuse.video.uuid) } + getVideoEmbed (videoAbuse: VideoAbuse) { + const absoluteAPIUrl = getAbsoluteAPIUrl() + const embedUrl = buildVideoLink({ + baseUrl: absoluteAPIUrl + '/videos/embed/' + videoAbuse.video.uuid, + warningTitle: false + }) + return buildVideoEmbed(embedUrl) + } + + switchToDefaultAvatar ($event: Event) { + ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + } + async removeVideoAbuse (videoAbuse: VideoAbuse) { const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete')) if (res === false) return this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Abuse deleted.') - ) + this.notifier.success(this.i18n('Abuse deleted.')) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -106,20 +290,43 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { .subscribe( () => this.loadData(), - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } protected loadData () { - return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) - .subscribe( - resultList => { - this.videoAbuses = resultList.data - this.totalRecords = resultList.total - }, - - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) + return this.videoAbuseService.getVideoAbuses({ + pagination: this.pagination, + sort: this.sort, + search: this.search + }).subscribe( + async resultList => { + this.totalRecords = resultList.total + const videoAbuses = [] + + for (const abuse of resultList.data) { + Object.assign(abuse, { + reasonHtml: await this.toHtml(abuse.reason), + moderationCommentHtml: await this.toHtml(abuse.moderationComment), + embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), + reporterAccount: new Account(abuse.reporterAccount) + }) + + if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) + if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt + + videoAbuses.push(abuse as ProcessedVideoAbuse) + } + + this.videoAbuses = videoAbuses + }, + + err => this.notifier.error(err.message) + ) + } + + private toHtml (text: string) { + return this.markdownRenderer.textMarkdownToHTML(text) } }