Add ability to report comments in front end
This commit is contained in:
parent
310b5219b3
commit
8ca56654a1
22 changed files with 488 additions and 163 deletions
|
@ -47,8 +47,8 @@ export class AdminComponent implements OnInit {
|
|||
|
||||
if (this.hasAbusesRight()) {
|
||||
moderationItems.children.push({
|
||||
label: this.i18n('Video reports'),
|
||||
routerLink: '/admin/moderation/video-abuses/list',
|
||||
label: this.i18n('Reports'),
|
||||
routerLink: '/admin/moderation/abuses/list',
|
||||
iconName: 'flag'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
<div class="col-8">
|
||||
|
||||
<!-- report metadata -->
|
||||
<div class="d-flex">
|
||||
<div class="d-flex" *ngIf="abuse.reporterAccount">
|
||||
<span class="col-3 moderation-expanded-label" i18n>Reporter</span>
|
||||
|
||||
<span class="col-9 moderation-expanded-text">
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" class="chip">
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
|
||||
class="chip"
|
||||
>
|
||||
<img
|
||||
class="avatar"
|
||||
[src]="abuse.reporterAccount.avatar?.path"
|
||||
|
@ -17,27 +20,35 @@
|
|||
<span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n>
|
||||
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
|
||||
class="ml-auto text-muted abuse-details-links" i18n
|
||||
>
|
||||
{abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<div class="d-flex" *ngIf="abuse.flaggedAccount">
|
||||
<span class="col-3 moderation-expanded-label" i18n>Reportee</span>
|
||||
<span class="col-9 moderation-expanded-text">
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.video.channel.ownerAccount.displayName + '"' }" class="chip">
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
|
||||
class="chip"
|
||||
>
|
||||
<img
|
||||
class="avatar"
|
||||
[src]="abuse.video.channel.ownerAccount?.avatar?.path"
|
||||
[src]="abuse.flaggedAccount?.avatar?.path"
|
||||
(error)="switchToDefaultAvatar($event)"
|
||||
alt="Avatar"
|
||||
>
|
||||
<div>
|
||||
<span class="text-muted">{{ abuse.video.channel.ownerAccount ? abuse.video.channel.ownerAccount.nameWithHost : '' }}</span>
|
||||
<span class="text-muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.video.channel.ownerAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n>
|
||||
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
|
||||
class="ml-auto text-muted abuse-details-links" i18n
|
||||
>
|
||||
{abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
|
||||
</a>
|
||||
</span>
|
||||
|
@ -45,7 +56,7 @@
|
|||
|
||||
<div class="d-flex" *ngIf="abuse.updatedAt">
|
||||
<span class="col-3 moderation-expanded-label" i18n>Updated</span>
|
||||
<time class="col-9 moderation-expanded-text video-details-date-updated">{{ abuse.updatedAt | date: 'medium' }}</time>
|
||||
<time class="col-9 moderation-expanded-text abuse-details-date-updated">{{ abuse.updatedAt | date: 'medium' }}</time>
|
||||
</div>
|
||||
|
||||
<!-- report text -->
|
||||
|
@ -60,34 +71,45 @@
|
|||
<div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
|
||||
<span class="col-3"></span>
|
||||
<span class="col-9">
|
||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" *ngFor="let reason of getPredefinedReasons()">
|
||||
<a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ '/admin/moderation/abuses/list' ]"
|
||||
[queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light"
|
||||
>
|
||||
<div>{{ reason.label }}</div>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="abuse.startAt" class="mt-2 d-flex">
|
||||
<div *ngIf="abuse.video?.startAt" class="mt-2 d-flex">
|
||||
<span class="col-3 moderation-expanded-label" i18n>Reported part</span>
|
||||
<span class="col-9">
|
||||
{{ startAt }}<ng-container *ngIf="abuse.endAt"> - {{ endAt }}</ng-container>
|
||||
{{ startAt }}<ng-container *ngIf="abuse.video.endAt"> - {{ endAt }}</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex" *ngIf="abuse.moderationComment">
|
||||
<span class="col-3 moderation-expanded-label" i18n>Note</span>
|
||||
<span class="col-9 moderation-expanded-text" [innerHTML]="abuse.moderationCommentHtml"></span>
|
||||
<span class="col-9 moderation-expanded-text d-block" [innerHTML]="abuse.moderationCommentHtml"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- report right part (video details) -->
|
||||
<!-- report right part (video/comment details) -->
|
||||
<div class="col-4">
|
||||
<div class="screenratio">
|
||||
<div *ngIf="abuse.video.deleted || abuse.video.blacklisted">
|
||||
<div *ngIf="abuse.video" class="screenratio">
|
||||
<div>
|
||||
<span i18n *ngIf="abuse.video.deleted">The video was deleted</span>
|
||||
<span i18n *ngIf="!abuse.video.deleted">The video was blocked</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!abuse.video.deleted && !abuse.video.blacklisted" [innerHTML]="abuse.embedHtml"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="abuse.comment" class="comment-html">
|
||||
<div>
|
||||
<strong i18n>Comment:</strong>
|
||||
</div>
|
||||
|
||||
<div [innerHTML]="abuse.commentHtml"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,15 +31,16 @@ export class AbuseDetailsComponent {
|
|||
}
|
||||
|
||||
get startAt () {
|
||||
return durationToString(this.abuse.startAt)
|
||||
return durationToString(this.abuse.video.startAt)
|
||||
}
|
||||
|
||||
get endAt () {
|
||||
return durationToString(this.abuse.endAt)
|
||||
return durationToString(this.abuse.video.endAt)
|
||||
}
|
||||
|
||||
getPredefinedReasons () {
|
||||
if (!this.abuse.predefinedReasons) return []
|
||||
|
||||
return this.abuse.predefinedReasons.map(r => ({
|
||||
id: r,
|
||||
label: this.predefinedReasonsTranslations[r]
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<tr> <!-- header -->
|
||||
<th style="width: 40px;"></th>
|
||||
<th style="width: 20%;" pResizableColumn i18n>Reporter</th>
|
||||
<th i18n>Video</th>
|
||||
<th i18n>Video/Comment/Account</th>
|
||||
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||
<th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
|
||||
<th style="width: 150px;"></th>
|
||||
|
@ -54,7 +54,7 @@
|
|||
</td>
|
||||
|
||||
<td>
|
||||
<a [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
|
||||
<a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
|
||||
<div class="chip two-lines">
|
||||
<img
|
||||
class="avatar"
|
||||
|
@ -64,54 +64,73 @@
|
|||
>
|
||||
<div>
|
||||
{{ abuse.reporterAccount.displayName }}
|
||||
<span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span>
|
||||
<span>{{ abuse.reporterAccount.nameWithHost }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<span i18n *ngIf="!abuse.reporterAccount">
|
||||
Deleted account
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td *ngIf="!abuse.video.deleted">
|
||||
<a [href]="getVideoUrl(abuse)" class="video-table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer">
|
||||
<div class="video-table-video">
|
||||
<div class="video-table-video-image">
|
||||
<img [src]="abuse.video.thumbnailPath">
|
||||
<span
|
||||
class="video-table-video-image-label" *ngIf="abuse.count > 1"
|
||||
i18n-title title="This video has been reported multiple times."
|
||||
>
|
||||
{{ abuse.nth }}/{{ abuse.count }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="video-table-video-text">
|
||||
<div>
|
||||
<span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span>
|
||||
<span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
|
||||
{{ abuse.video.name }}
|
||||
<ng-container *ngIf="abuse.video">
|
||||
|
||||
<td *ngIf="!abuse.video.deleted">
|
||||
<a [href]="getVideoUrl(abuse)" class="table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer">
|
||||
<div class="table-video">
|
||||
<div class="table-video-image">
|
||||
<img [src]="abuse.video.thumbnailPath">
|
||||
<span
|
||||
class="table-video-image-label" *ngIf="abuse.count > 1"
|
||||
i18n-title title="This video has been reported multiple times."
|
||||
>
|
||||
{{ abuse.nth }}/{{ abuse.count }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
|
||||
<div class="video-table-video" i18n-title title="Video was deleted">
|
||||
<div class="video-table-video-image">
|
||||
<span i18n>Deleted</span>
|
||||
</div>
|
||||
<div class="video-table-video-text">
|
||||
<div>
|
||||
{{ abuse.video.name }}
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
<div class="table-video-text">
|
||||
<div>
|
||||
<span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span>
|
||||
<span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
|
||||
{{ abuse.video.name }}
|
||||
</div>
|
||||
<div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
|
||||
<div class="table-video" i18n-title title="Video was deleted">
|
||||
<div class="table-video-image">
|
||||
<span i18n>Deleted</span>
|
||||
</div>
|
||||
|
||||
<div class="table-video-text">
|
||||
<div>
|
||||
{{ abuse.video.name }}
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</div>
|
||||
<div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
|
||||
</div>
|
||||
<div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="abuse.comment">
|
||||
<td>
|
||||
<a [href]="getCommentUrl(abuse)" [innerHTML]="abuse.truncatedCommentHtml" class="table-comment-link"
|
||||
[title]="abuse.comment.video.name" target="_blank" rel="noopener noreferrer"
|
||||
></a>
|
||||
|
||||
<div class="comment-flagged-account" *ngIf="abuse.flaggedAccount">by {{ abuse.flaggedAccount.displayName }}</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td>
|
||||
|
||||
<td class="c-hand video-abuse-states" [pRowToggler]="abuse">
|
||||
<td class="c-hand abuse-states" [pRowToggler]="abuse">
|
||||
<span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span>
|
||||
<span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span>
|
||||
<span *ngIf="abuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment" class="glyphicon glyphicon-comment"></span>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
@include disable-default-a-behaviour;
|
||||
}
|
||||
|
||||
.video-abuse-states .glyphicon-comment {
|
||||
.abuse-states .glyphicon-comment {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api'
|
|||
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
|
||||
import { DomSanitizer } from '@angular/platform-browser'
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router'
|
||||
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||
|
@ -10,15 +10,20 @@ import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/s
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { Abuse, AbuseState } from '@shared/models'
|
||||
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
||||
import truncate from 'lodash-es/truncate'
|
||||
|
||||
export type ProcessedAbuse = Abuse & {
|
||||
moderationCommentHtml?: string,
|
||||
reasonHtml?: string
|
||||
embedHtml?: string
|
||||
embedHtml?: SafeHtml
|
||||
updatedAt?: Date
|
||||
|
||||
// override bare server-side definitions with rich client-side definitions
|
||||
reporterAccount: Account
|
||||
reporterAccount?: Account
|
||||
flaggedAccount?: Account
|
||||
|
||||
truncatedCommentHtml?: string
|
||||
commentHtml?: string
|
||||
|
||||
video: Abuse['video'] & {
|
||||
channel: Abuse['video']['channel'] & {
|
||||
|
@ -92,11 +97,11 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
{
|
||||
label: this.i18n('Actions for the video'),
|
||||
isHeader: true,
|
||||
isDisplayed: abuse => !abuse.video.deleted
|
||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted
|
||||
},
|
||||
{
|
||||
label: this.i18n('Block video'),
|
||||
isDisplayed: abuse => !abuse.video.deleted && !abuse.video.blacklisted,
|
||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted,
|
||||
handler: abuse => {
|
||||
this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true)
|
||||
.subscribe(
|
||||
|
@ -112,7 +117,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
},
|
||||
{
|
||||
label: this.i18n('Unblock video'),
|
||||
isDisplayed: abuse => !abuse.video.deleted && abuse.video.blacklisted,
|
||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted,
|
||||
handler: abuse => {
|
||||
this.videoBlocklistService.unblockVideo(abuse.video.id)
|
||||
.subscribe(
|
||||
|
@ -128,7 +133,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
},
|
||||
{
|
||||
label: this.i18n('Delete video'),
|
||||
isDisplayed: abuse => !abuse.video.deleted,
|
||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted,
|
||||
handler: async abuse => {
|
||||
const res = await this.confirmService.confirm(
|
||||
this.i18n('Do you really want to delete this video?'),
|
||||
|
@ -152,10 +157,12 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
[
|
||||
{
|
||||
label: this.i18n('Actions for the reporter'),
|
||||
isHeader: true
|
||||
isHeader: true,
|
||||
isDisplayed: abuse => !!abuse.reporterAccount
|
||||
},
|
||||
{
|
||||
label: this.i18n('Mute reporter'),
|
||||
isDisplayed: abuse => !!abuse.reporterAccount,
|
||||
handler: async abuse => {
|
||||
const account = abuse.reporterAccount as Account
|
||||
|
||||
|
@ -175,7 +182,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
},
|
||||
{
|
||||
label: this.i18n('Mute server'),
|
||||
isDisplayed: abuse => !abuse.reporterAccount.userId,
|
||||
isDisplayed: abuse => abuse.reporterAccount && !abuse.reporterAccount.userId,
|
||||
handler: async abuse => {
|
||||
this.blocklistService.blockServerByInstance(abuse.reporterAccount.host)
|
||||
.subscribe(
|
||||
|
@ -231,7 +238,7 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
const queryParams: Params = {}
|
||||
if (search) Object.assign(queryParams, { search })
|
||||
|
||||
this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams })
|
||||
this.router.navigate([ '/admin/moderation/abuses/list' ], { queryParams })
|
||||
}
|
||||
|
||||
resetTableFilter () {
|
||||
|
@ -253,6 +260,10 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
return Video.buildClientUrl(abuse.video.uuid)
|
||||
}
|
||||
|
||||
getCommentUrl (abuse: Abuse) {
|
||||
return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId
|
||||
}
|
||||
|
||||
getVideoEmbed (abuse: Abuse) {
|
||||
return buildVideoEmbed(
|
||||
buildVideoLink({
|
||||
|
@ -300,23 +311,45 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
|||
}).subscribe(
|
||||
async resultList => {
|
||||
this.totalRecords = resultList.total
|
||||
const abuses = []
|
||||
|
||||
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)
|
||||
})
|
||||
this.abuses = []
|
||||
|
||||
for (const a of resultList.data) {
|
||||
const abuse = a as ProcessedAbuse
|
||||
|
||||
abuse.reasonHtml = await this.toHtml(abuse.reason)
|
||||
abuse.moderationCommentHtml = await this.toHtml(abuse.moderationComment)
|
||||
|
||||
if (abuse.video) {
|
||||
abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse))
|
||||
|
||||
if (abuse.video.channel?.ownerAccount) {
|
||||
abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
|
||||
}
|
||||
}
|
||||
|
||||
if (abuse.comment) {
|
||||
if (abuse.comment.deleted) {
|
||||
abuse.truncatedCommentHtml = abuse.commentHtml = this.i18n('Deleted comment')
|
||||
} else {
|
||||
const truncated = truncate(abuse.comment.text, { length: 100 })
|
||||
abuse.truncatedCommentHtml = await this.markdownRenderer.textMarkdownToHTML(truncated, true)
|
||||
abuse.commentHtml = await this.markdownRenderer.textMarkdownToHTML(abuse.comment.text, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (abuse.reporterAccount) {
|
||||
abuse.reporterAccount = new Account(abuse.reporterAccount)
|
||||
}
|
||||
|
||||
if (abuse.flaggedAccount) {
|
||||
abuse.flaggedAccount = new Account(abuse.flaggedAccount)
|
||||
}
|
||||
|
||||
if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
|
||||
if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt
|
||||
|
||||
abuses.push(abuse as ProcessedAbuse)
|
||||
this.abuses.push(abuse)
|
||||
}
|
||||
|
||||
this.abuses = abuses
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
|
|
|
@ -25,18 +25,18 @@
|
|||
vertical-align: top;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.moderation-expanded-text {
|
||||
display: inline-flex;
|
||||
word-wrap: break-word;
|
||||
|
||||
|
||||
::ng-deep p:last-child {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-table-states {
|
||||
.table-states {
|
||||
& > :not(:first-child) {
|
||||
margin-left: .4rem;
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ p-calendar {
|
|||
.screenratio {
|
||||
div {
|
||||
@include miniature-thumbnail;
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -72,6 +73,11 @@ p-calendar {
|
|||
};
|
||||
}
|
||||
|
||||
.comment-html {
|
||||
background-color: #ececec;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.chip {
|
||||
@include chip;
|
||||
}
|
||||
|
@ -83,16 +89,32 @@ my-action-dropdown.show {
|
|||
}
|
||||
|
||||
|
||||
.video-table-video-link {
|
||||
.table-video-link {
|
||||
@include disable-outline;
|
||||
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.video-table-video {
|
||||
.table-comment-link {
|
||||
@include disable-outline;
|
||||
|
||||
color: var(--mainForegroundColor);
|
||||
|
||||
::ng-deep p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-flagged-account {
|
||||
font-size: 11px;
|
||||
color: var(--greyForegroundColor);
|
||||
}
|
||||
|
||||
.table-video {
|
||||
display: inline-flex;
|
||||
|
||||
.video-table-video-image {
|
||||
.table-video-image {
|
||||
@include miniature-thumbnail;
|
||||
|
||||
$image-height: 45px;
|
||||
|
@ -118,7 +140,7 @@ my-action-dropdown.show {
|
|||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
|
||||
.video-table-video-image-label {
|
||||
.table-video-image-label {
|
||||
@include static-thumbnail-overlay;
|
||||
position: absolute;
|
||||
border-radius: 3px;
|
||||
|
@ -130,7 +152,7 @@ my-action-dropdown.show {
|
|||
}
|
||||
}
|
||||
|
||||
.video-table-video-text {
|
||||
.table-video-text {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
@ -145,7 +167,8 @@ my-action-dropdown.show {
|
|||
}
|
||||
|
||||
div + div {
|
||||
font-size: 80%;
|
||||
color: var(--greyForegroundColor);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export const ModerationRoutes: Routes = [
|
|||
data: {
|
||||
userRight: UserRight.MANAGE_ABUSES,
|
||||
meta: {
|
||||
title: 'Video reports'
|
||||
title: 'Reports'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<div *ngIf="isRemovableByUser()" (click)="onWantToDelete()" class="comment-action-delete" i18n>Delete</div>
|
||||
|
||||
<my-user-moderation-dropdown
|
||||
[prependActions]="prependModerationActions"
|
||||
buttonSize="small" [account]="commentAccount" [user]="commentUser" i18n-label label="Options" placement="bottom-left auto"
|
||||
></my-user-moderation-dropdown>
|
||||
</div>
|
||||
|
@ -93,3 +94,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="prependModerationActions">
|
||||
<my-comment-report #commentReportModal [comment]="comment"></my-comment-report>
|
||||
</ng-container>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import { MarkdownService, Notifier, UserService } from '@app/core'
|
||||
import { AuthService } from '@app/core/auth'
|
||||
import { Account, Actor, Video } from '@app/shared/shared-main'
|
||||
import { Account, Actor, DropdownAction, Video } from '@app/shared/shared-main'
|
||||
import { CommentReportComponent } from '@app/shared/shared-moderation/comment-report.component'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { User, UserRight } from '@shared/models'
|
||||
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
|
||||
import { VideoComment } from './video-comment.model'
|
||||
|
@ -12,6 +15,8 @@ import { VideoComment } from './video-comment.model'
|
|||
styleUrls: ['./video-comment.component.scss']
|
||||
})
|
||||
export class VideoCommentComponent implements OnInit, OnChanges {
|
||||
@ViewChild('commentReportModal') commentReportModal: CommentReportComponent
|
||||
|
||||
@Input() video: Video
|
||||
@Input() comment: VideoComment
|
||||
@Input() parentComments: VideoComment[] = []
|
||||
|
@ -26,6 +31,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
|||
@Output() resetReply = new EventEmitter()
|
||||
@Output() timestampClicked = new EventEmitter<number>()
|
||||
|
||||
prependModerationActions: DropdownAction<any>[]
|
||||
|
||||
sanitizedCommentHTML = ''
|
||||
newParentComments: VideoComment[] = []
|
||||
|
||||
|
@ -33,6 +40,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
|||
commentUser: User
|
||||
|
||||
constructor (
|
||||
private i18n: I18n,
|
||||
private markdownService: MarkdownService,
|
||||
private authService: AuthService,
|
||||
private userService: UserService,
|
||||
|
@ -127,5 +135,20 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
|||
} else {
|
||||
this.comment.account = null
|
||||
}
|
||||
|
||||
if (this.isUserLoggedIn()) {
|
||||
this.prependModerationActions = [
|
||||
{
|
||||
label: this.i18n('Report comment'),
|
||||
handler: () => this.showReportModal()
|
||||
}
|
||||
]
|
||||
} else {
|
||||
this.prependModerationActions = undefined
|
||||
}
|
||||
}
|
||||
|
||||
private showReportModal () {
|
||||
this.commentReportModal.show()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,8 +46,10 @@ export abstract class Actor implements ActorServer {
|
|||
this.host = hash.host
|
||||
this.followingCount = hash.followingCount
|
||||
this.followersCount = hash.followersCount
|
||||
this.createdAt = new Date(hash.createdAt.toString())
|
||||
this.updatedAt = new Date(hash.updatedAt.toString())
|
||||
|
||||
if (hash.createdAt) this.createdAt = new Date(hash.createdAt.toString())
|
||||
if (hash.updatedAt) this.updatedAt = new Date(hash.updatedAt.toString())
|
||||
|
||||
this.avatar = hash.avatar
|
||||
|
||||
this.updateComputedAttributes()
|
||||
|
|
|
@ -5,18 +5,20 @@ import { catchError, map } from 'rxjs/operators'
|
|||
import { HttpClient, HttpParams } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { RestExtractor, RestPagination, RestService } from '@app/core'
|
||||
import { AbuseUpdate, ResultList, Abuse, AbuseCreate, AbuseState } from '@shared/models'
|
||||
import { Abuse, AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, ResultList } from '@shared/models'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Injectable()
|
||||
export class AbuseService {
|
||||
private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses'
|
||||
|
||||
constructor (
|
||||
private i18n: I18n,
|
||||
private authHttp: HttpClient,
|
||||
private restService: RestService,
|
||||
private restExtractor: RestExtractor
|
||||
) {}
|
||||
) { }
|
||||
|
||||
getAbuses (options: {
|
||||
pagination: RestPagination,
|
||||
|
@ -24,7 +26,7 @@ export class AbuseService {
|
|||
search?: string
|
||||
}): Observable<ResultList<Abuse>> {
|
||||
const { pagination, sort, search } = options
|
||||
const url = AbuseService.BASE_ABUSE_URL + 'abuse'
|
||||
const url = AbuseService.BASE_ABUSE_URL
|
||||
|
||||
let params = new HttpParams()
|
||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||
|
@ -60,39 +62,93 @@ export class AbuseService {
|
|||
}
|
||||
|
||||
return this.authHttp.get<ResultList<Abuse>>(url, { params })
|
||||
.pipe(
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
.pipe(
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
reportVideo (parameters: AbuseCreate) {
|
||||
const url = AbuseService.BASE_ABUSE_URL
|
||||
|
||||
const body = omit(parameters, [ 'id' ])
|
||||
const body = omit(parameters, ['id'])
|
||||
|
||||
return this.authHttp.post(url, body)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
updateAbuse (abuse: Abuse, abuseUpdate: AbuseUpdate) {
|
||||
const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id
|
||||
|
||||
return this.authHttp.put(url, abuseUpdate)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
removeAbuse (abuse: Abuse) {
|
||||
const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id
|
||||
|
||||
return this.authHttp.delete(url)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}}
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
getPrefefinedReasons (type: AbuseFilter) {
|
||||
let reasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [
|
||||
{
|
||||
id: 'violentOrRepulsive',
|
||||
label: this.i18n('Violent or repulsive'),
|
||||
help: this.i18n('Contains offensive, violent, or coarse language or iconography.')
|
||||
},
|
||||
{
|
||||
id: 'hatefulOrAbusive',
|
||||
label: this.i18n('Hateful or abusive'),
|
||||
help: this.i18n('Contains abusive, racist or sexist language or iconography.')
|
||||
},
|
||||
{
|
||||
id: 'spamOrMisleading',
|
||||
label: this.i18n('Spam, ad or false news'),
|
||||
help: this.i18n('Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.')
|
||||
},
|
||||
{
|
||||
id: 'privacy',
|
||||
label: this.i18n('Privacy breach or doxxing'),
|
||||
help: this.i18n('Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).')
|
||||
},
|
||||
{
|
||||
id: 'rights',
|
||||
label: this.i18n('Intellectual property violation'),
|
||||
help: this.i18n('Infringes my intellectual property or copyright, wrt. the regional rules with which the server must comply.')
|
||||
},
|
||||
{
|
||||
id: 'serverRules',
|
||||
label: this.i18n('Breaks server rules'),
|
||||
description: this.i18n('Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.')
|
||||
}
|
||||
]
|
||||
|
||||
if (type === 'video') {
|
||||
reasons = reasons.concat([
|
||||
{
|
||||
id: 'thumbnails',
|
||||
label: this.i18n('Thumbnails'),
|
||||
help: this.i18n('The above can only be seen in thumbnails.')
|
||||
},
|
||||
{
|
||||
id: 'captions',
|
||||
label: this.i18n('Captions'),
|
||||
help: this.i18n('The above can only be seen in captions (please describe which).')
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
return reasons
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<ng-template #modal>
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Report comment</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="report()">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-5 form-group">
|
||||
|
||||
<label i18n for="reportPredefinedReasons">What is the issue?</label>
|
||||
|
||||
<div class="ml-2 mt-2 d-flex flex-column">
|
||||
<ng-container formGroupName="predefinedReasons">
|
||||
|
||||
<div class="form-group" *ngFor="let reason of predefinedReasons">
|
||||
<my-peertube-checkbox [inputName]="reason.id" [formControlName]="reason.id" [labelText]="reason.label">
|
||||
<ng-template *ngIf="reason.help" ptTemplate="help">
|
||||
<div [innerHTML]="reason.help"></div>
|
||||
</ng-template>
|
||||
|
||||
<ng-container *ngIf="reason.description" ngProjectAs="description">
|
||||
<div [innerHTML]="reason.description"></div>
|
||||
</ng-container>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-7">
|
||||
<div i18n class="information">
|
||||
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>.
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<textarea
|
||||
i18n-placeholder placeholder="Please describe the issue..." formControlName="reason" ngbAutofocus
|
||||
[ngClass]="{ 'input-error': formErrors['reason'] }" class="form-control"
|
||||
></textarea>
|
||||
<div *ngIf="formErrors.reason" class="form-error">
|
||||
{{ formErrors.reason }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group inputs">
|
||||
<input
|
||||
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
|
||||
(click)="hide()" (key.enter)="hide()"
|
||||
>
|
||||
<input type="submit" i18n-value value="Submit" class="action-button-submit" [disabled]="!form.valid">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
|
@ -0,0 +1,11 @@
|
|||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.information {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(100%, 100px);
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import { mapValues, pickBy } from 'lodash-es'
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||
import { SafeHtml } from '@angular/platform-browser'
|
||||
import { VideoComment } from '@app/+videos/+video-watch/comment/video-comment.model'
|
||||
import { Notifier } from '@app/core'
|
||||
import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
|
||||
import { AbuseService } from './abuse.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-comment-report',
|
||||
templateUrl: './comment-report.component.html',
|
||||
styleUrls: [ './comment-report.component.scss' ]
|
||||
})
|
||||
export class CommentReportComponent extends FormReactive implements OnInit {
|
||||
@Input() comment: VideoComment = null
|
||||
|
||||
@ViewChild('modal', { static: true }) modal: NgbModal
|
||||
|
||||
error: string = null
|
||||
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
|
||||
embedHtml: SafeHtml
|
||||
|
||||
private openedModal: NgbModalRef
|
||||
|
||||
constructor (
|
||||
protected formValidatorService: FormValidatorService,
|
||||
private modalService: NgbModal,
|
||||
private abuseValidatorsService: AbuseValidatorsService,
|
||||
private abuseService: AbuseService,
|
||||
private notifier: Notifier,
|
||||
private i18n: I18n
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
get currentHost () {
|
||||
return window.location.host
|
||||
}
|
||||
|
||||
get originHost () {
|
||||
if (this.isRemoteComment()) {
|
||||
return this.comment.account.host
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.buildForm({
|
||||
reason: this.abuseValidatorsService.ABUSE_REASON,
|
||||
predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null)
|
||||
})
|
||||
|
||||
this.predefinedReasons = this.abuseService.getPrefefinedReasons('comment')
|
||||
}
|
||||
|
||||
show () {
|
||||
this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false, size: 'lg' })
|
||||
}
|
||||
|
||||
hide () {
|
||||
this.openedModal.close()
|
||||
this.openedModal = null
|
||||
}
|
||||
|
||||
report () {
|
||||
const reason = this.form.get('reason').value
|
||||
const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as AbusePredefinedReasonsString[]
|
||||
|
||||
this.abuseService.reportVideo({
|
||||
reason,
|
||||
predefinedReasons,
|
||||
comment: {
|
||||
id: this.comment.id
|
||||
}
|
||||
}).subscribe(
|
||||
() => {
|
||||
this.notifier.success(this.i18n('Comment reported.'))
|
||||
this.hide()
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
|
||||
isRemoteComment () {
|
||||
return !this.comment.isLocal
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import { AbuseService } from './abuse.service'
|
|||
import { VideoBlockComponent } from './video-block.component'
|
||||
import { VideoBlockService } from './video-block.service'
|
||||
import { VideoReportComponent } from './video-report.component'
|
||||
import { CommentReportComponent } from './comment-report.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -25,7 +26,8 @@ import { VideoReportComponent } from './video-report.component'
|
|||
UserModerationDropdownComponent,
|
||||
VideoBlockComponent,
|
||||
VideoReportComponent,
|
||||
BatchDomainsModalComponent
|
||||
BatchDomainsModalComponent,
|
||||
CommentReportComponent
|
||||
],
|
||||
|
||||
exports: [
|
||||
|
@ -33,7 +35,8 @@ import { VideoReportComponent } from './video-report.component'
|
|||
UserModerationDropdownComponent,
|
||||
VideoBlockComponent,
|
||||
VideoReportComponent,
|
||||
BatchDomainsModalComponent
|
||||
BatchDomainsModalComponent,
|
||||
CommentReportComponent
|
||||
],
|
||||
|
||||
providers: [
|
||||
|
|
|
@ -16,6 +16,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
|
|||
|
||||
@Input() user: User
|
||||
@Input() account: Account
|
||||
@Input() prependActions: DropdownAction<{ user: User, account: Account }>[]
|
||||
|
||||
@Input() buttonSize: 'normal' | 'small' = 'normal'
|
||||
@Input() placement = 'left-top left-bottom auto'
|
||||
|
@ -250,6 +251,12 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
|
|||
private buildActions () {
|
||||
this.userActions = []
|
||||
|
||||
if (this.prependActions) {
|
||||
this.userActions = [
|
||||
this.prependActions
|
||||
]
|
||||
}
|
||||
|
||||
if (this.authService.isLoggedIn()) {
|
||||
const authUser = this.authService.getUser()
|
||||
|
||||
|
|
|
@ -14,16 +14,19 @@
|
|||
|
||||
<div class="ml-2 mt-2 d-flex flex-column">
|
||||
<ng-container formGroupName="predefinedReasons">
|
||||
|
||||
<div class="form-group" *ngFor="let reason of predefinedReasons">
|
||||
<my-peertube-checkbox formControlName="{{reason.id}}" labelText="{{reason.label}}">
|
||||
<my-peertube-checkbox [inputName]="reason.id" [formControlName]="reason.id" [labelText]="reason.label">
|
||||
<ng-template *ngIf="reason.help" ptTemplate="help">
|
||||
<div [innerHTML]="reason.help"></div>
|
||||
</ng-template>
|
||||
|
||||
<ng-container *ngIf="reason.description" ngProjectAs="description">
|
||||
<div [innerHTML]="reason.description"></div>
|
||||
</ng-container>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
@ -73,7 +76,7 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<textarea
|
||||
<textarea
|
||||
i18n-placeholder placeholder="Please describe the issue..." formControlName="reason" ngbAutofocus
|
||||
[ngClass]="{ 'input-error': formErrors['reason'] }" class="form-control"
|
||||
></textarea>
|
||||
|
|
|
@ -79,48 +79,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
|||
}
|
||||
})
|
||||
|
||||
this.predefinedReasons = [
|
||||
{
|
||||
id: 'violentOrRepulsive',
|
||||
label: this.i18n('Violent or repulsive'),
|
||||
help: this.i18n('Contains offensive, violent, or coarse language or iconography.')
|
||||
},
|
||||
{
|
||||
id: 'hatefulOrAbusive',
|
||||
label: this.i18n('Hateful or abusive'),
|
||||
help: this.i18n('Contains abusive, racist or sexist language or iconography.')
|
||||
},
|
||||
{
|
||||
id: 'spamOrMisleading',
|
||||
label: this.i18n('Spam, ad or false news'),
|
||||
help: this.i18n('Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.')
|
||||
},
|
||||
{
|
||||
id: 'privacy',
|
||||
label: this.i18n('Privacy breach or doxxing'),
|
||||
help: this.i18n('Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).')
|
||||
},
|
||||
{
|
||||
id: 'rights',
|
||||
label: this.i18n('Intellectual property violation'),
|
||||
help: this.i18n('Infringes my intellectual property or copyright, wrt. the regional rules with which the server must comply.')
|
||||
},
|
||||
{
|
||||
id: 'serverRules',
|
||||
label: this.i18n('Breaks server rules'),
|
||||
description: this.i18n('Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.')
|
||||
},
|
||||
{
|
||||
id: 'thumbnails',
|
||||
label: this.i18n('Thumbnails'),
|
||||
help: this.i18n('The above can only be seen in thumbnails.')
|
||||
},
|
||||
{
|
||||
id: 'captions',
|
||||
label: this.i18n('Captions'),
|
||||
help: this.i18n('The above can only be seen in captions (please describe which).')
|
||||
}
|
||||
]
|
||||
this.predefinedReasons = this.abuseService.getPrefefinedReasons('video')
|
||||
|
||||
this.embedHtml = this.getVideoEmbed()
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ export enum ScopeNames {
|
|||
model: VideoModel.unscoped(),
|
||||
include: [
|
||||
{
|
||||
attributes: [ 'filename', 'fileUrl' ],
|
||||
attributes: [ 'filename', 'fileUrl', 'type' ],
|
||||
model: ThumbnailModel
|
||||
},
|
||||
{
|
||||
|
@ -395,6 +395,8 @@ export class AbuseModel extends Model<AbuseModel> {
|
|||
|
||||
comment = {
|
||||
id: entity.id,
|
||||
threadId: entity.getThreadId(),
|
||||
|
||||
text: entity.text ?? '',
|
||||
|
||||
deleted: entity.isDeleted(),
|
||||
|
|
|
@ -655,7 +655,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
|||
id: this.id,
|
||||
url: this.url,
|
||||
text: this.text,
|
||||
threadId: this.originCommentId || this.id,
|
||||
threadId: this.getThreadId(),
|
||||
inReplyToCommentId: this.inReplyToCommentId || null,
|
||||
videoId: this.videoId,
|
||||
createdAt: this.createdAt,
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface VideoAbuse {
|
|||
|
||||
export interface VideoCommentAbuse {
|
||||
id: number
|
||||
threadId: number
|
||||
|
||||
video: {
|
||||
id: number
|
||||
|
|
Loading…
Reference in a new issue