Add ability to report account
This commit is contained in:
parent
8ca56654a1
commit
cfde28bac3
|
@ -22,6 +22,7 @@
|
||||||
<span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
|
<span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
|
||||||
|
|
||||||
<my-user-moderation-dropdown
|
<my-user-moderation-dropdown
|
||||||
|
[prependActions]="prependModerationActions"
|
||||||
buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto"
|
buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto"
|
||||||
(userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
|
(userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
|
||||||
></my-user-moderation-dropdown>
|
></my-user-moderation-dropdown>
|
||||||
|
@ -50,3 +51,7 @@
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="prependModerationActions">
|
||||||
|
<my-account-report #accountReportModal [account]="account"></my-account-report>
|
||||||
|
</ng-container>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'
|
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { AuthService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core'
|
import { AuthService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core'
|
||||||
import { Account, AccountService, ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main'
|
import { Account, AccountService, DropdownAction, ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main'
|
||||||
|
import { AccountReportComponent } from '@app/shared/shared-moderation'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { User, UserRight } from '@shared/models'
|
import { User, UserRight } from '@shared/models'
|
||||||
|
|
||||||
|
@ -12,6 +13,8 @@ import { User, UserRight } from '@shared/models'
|
||||||
styleUrls: [ './accounts.component.scss' ]
|
styleUrls: [ './accounts.component.scss' ]
|
||||||
})
|
})
|
||||||
export class AccountsComponent implements OnInit, OnDestroy {
|
export class AccountsComponent implements OnInit, OnDestroy {
|
||||||
|
@ViewChild('accountReportModal') accountReportModal: AccountReportComponent
|
||||||
|
|
||||||
account: Account
|
account: Account
|
||||||
accountUser: User
|
accountUser: User
|
||||||
videoChannels: VideoChannel[] = []
|
videoChannels: VideoChannel[] = []
|
||||||
|
@ -20,6 +23,8 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
||||||
isAccountManageable = false
|
isAccountManageable = false
|
||||||
accountFollowerTitle = ''
|
accountFollowerTitle = ''
|
||||||
|
|
||||||
|
prependModerationActions: DropdownAction<any>[]
|
||||||
|
|
||||||
private routeSub: Subscription
|
private routeSub: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
@ -42,24 +47,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
||||||
map(params => params[ 'accountId' ]),
|
map(params => params[ 'accountId' ]),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
switchMap(accountId => this.accountService.getAccount(accountId)),
|
switchMap(accountId => this.accountService.getAccount(accountId)),
|
||||||
tap(account => {
|
tap(account => this.onAccount(account)),
|
||||||
this.account = account
|
|
||||||
|
|
||||||
if (this.authService.isLoggedIn()) {
|
|
||||||
this.authService.userInformationLoaded.subscribe(
|
|
||||||
() => {
|
|
||||||
this.isAccountManageable = this.account.userId && this.account.userId === this.authService.getUser().id
|
|
||||||
|
|
||||||
this.accountFollowerTitle = this.i18n(
|
|
||||||
'{{followers}} direct account followers',
|
|
||||||
{ followers: this.subscribersDisplayFor(account.followersCount) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getUserIfNeeded(account)
|
|
||||||
}),
|
|
||||||
switchMap(account => this.videoChannelService.listAccountVideoChannels(account)),
|
switchMap(account => this.videoChannelService.listAccountVideoChannels(account)),
|
||||||
catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))
|
catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))
|
||||||
)
|
)
|
||||||
|
@ -107,6 +95,41 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
||||||
return this.i18n('{count, plural, =1 {1 subscriber} other {{{count}} subscribers}}', { count })
|
return this.i18n('{count, plural, =1 {1 subscriber} other {{{count}} subscribers}}', { count })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onAccount (account: Account) {
|
||||||
|
this.prependModerationActions = undefined
|
||||||
|
|
||||||
|
this.account = account
|
||||||
|
|
||||||
|
if (this.authService.isLoggedIn()) {
|
||||||
|
this.authService.userInformationLoaded.subscribe(
|
||||||
|
() => {
|
||||||
|
this.isAccountManageable = this.account.userId && this.account.userId === this.authService.getUser().id
|
||||||
|
|
||||||
|
this.accountFollowerTitle = this.i18n(
|
||||||
|
'{{followers}} direct account followers',
|
||||||
|
{ followers: this.subscribersDisplayFor(account.followersCount) }
|
||||||
|
)
|
||||||
|
|
||||||
|
// It's not our account, we can report it
|
||||||
|
if (!this.isAccountManageable) {
|
||||||
|
this.prependModerationActions = [
|
||||||
|
{
|
||||||
|
label: this.i18n('Report account'),
|
||||||
|
handler: () => this.showReportModal()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getUserIfNeeded(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
private showReportModal () {
|
||||||
|
this.accountReportModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
private getUserIfNeeded (account: Account) {
|
private getUserIfNeeded (account: Account) {
|
||||||
if (!account.userId || !this.authService.isLoggedIn()) return
|
if (!account.userId || !this.authService.isLoggedIn()) return
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p-table
|
<p-table
|
||||||
[value]="abuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
[value]="abuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true"
|
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" [lazyLoadOnInit]="false"
|
||||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
|
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
|
||||||
(onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
|
(onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
|
||||||
|
@ -128,6 +128,22 @@
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!abuse.comment && !abuse.video">
|
||||||
|
<td *ngIf="abuse.flaggedAccount">
|
||||||
|
<a [href]="getAccountUrl(abuse)" class="table-account-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<span>{{ abuse.flaggedAccount.displayName }}</span>
|
||||||
|
|
||||||
|
<span class="account-flagged-handle">{{ abuse.flaggedAccount.nameWithHostForced }}</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td i18n *ngIf="!abuse.flaggedAccount">
|
||||||
|
Account deleted
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
<td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td>
|
<td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td>
|
||||||
|
|
||||||
<td class="c-hand abuse-states" [pRowToggler]="abuse">
|
<td class="c-hand abuse-states" [pRowToggler]="abuse">
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import * as debug from 'debug'
|
||||||
|
import truncate from 'lodash-es/truncate'
|
||||||
import { SortMeta } from 'primeng/api'
|
import { SortMeta } from 'primeng/api'
|
||||||
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
|
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
|
@ -7,11 +9,15 @@ import { ActivatedRoute, Params, Router } from '@angular/router'
|
||||||
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
|
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||||
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
|
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
|
||||||
|
import { VideoCommentService } from '@app/shared/shared-video-comment'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { Abuse, AbuseState } from '@shared/models'
|
import { Abuse, AbuseState } from '@shared/models'
|
||||||
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
||||||
import truncate from 'lodash-es/truncate'
|
|
||||||
|
|
||||||
|
const logger = debug('peertube:moderation:AbuseListComponent')
|
||||||
|
|
||||||
|
// Don't use an abuse model because we need external services to compute some properties
|
||||||
|
// And this model is only used in this component
|
||||||
export type ProcessedAbuse = Abuse & {
|
export type ProcessedAbuse = Abuse & {
|
||||||
moderationCommentHtml?: string,
|
moderationCommentHtml?: string,
|
||||||
reasonHtml?: string
|
reasonHtml?: string
|
||||||
|
@ -45,12 +51,13 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
||||||
sort: SortMeta = { field: 'createdAt', order: 1 }
|
sort: SortMeta = { field: 'createdAt', order: 1 }
|
||||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||||
|
|
||||||
abuseActions: DropdownAction<Abuse>[][] = []
|
abuseActions: DropdownAction<ProcessedAbuse>[][] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private abuseService: AbuseService,
|
private abuseService: AbuseService,
|
||||||
private blocklistService: BlocklistService,
|
private blocklistService: BlocklistService,
|
||||||
|
private commentService: VideoCommentService,
|
||||||
private videoService: VideoService,
|
private videoService: VideoService,
|
||||||
private videoBlocklistService: VideoBlockService,
|
private videoBlocklistService: VideoBlockService,
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
|
@ -63,140 +70,15 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.abuseActions = [
|
this.abuseActions = [
|
||||||
[
|
this.buildInternalActions(),
|
||||||
{
|
|
||||||
label: this.i18n('Internal actions'),
|
|
||||||
isHeader: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Delete report'),
|
|
||||||
handler: abuse => this.removeAbuse(abuse)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Add note'),
|
|
||||||
handler: abuse => this.openModerationCommentModal(abuse),
|
|
||||||
isDisplayed: abuse => !abuse.moderationComment
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Update note'),
|
|
||||||
handler: abuse => this.openModerationCommentModal(abuse),
|
|
||||||
isDisplayed: abuse => !!abuse.moderationComment
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Mark as accepted'),
|
|
||||||
handler: abuse => this.updateAbuseState(abuse, AbuseState.ACCEPTED),
|
|
||||||
isDisplayed: abuse => !this.isAbuseAccepted(abuse)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Mark as rejected'),
|
|
||||||
handler: abuse => this.updateAbuseState(abuse, AbuseState.REJECTED),
|
|
||||||
isDisplayed: abuse => !this.isAbuseRejected(abuse)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: this.i18n('Actions for the video'),
|
|
||||||
isHeader: true,
|
|
||||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Block video'),
|
|
||||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted,
|
|
||||||
handler: abuse => {
|
|
||||||
this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true)
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.notifier.success(this.i18n('Video blocked.'))
|
|
||||||
|
|
||||||
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
this.buildFlaggedAccountActions(),
|
||||||
},
|
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
this.buildCommentActions(),
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Unblock video'),
|
|
||||||
isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted,
|
|
||||||
handler: abuse => {
|
|
||||||
this.videoBlocklistService.unblockVideo(abuse.video.id)
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.notifier.success(this.i18n('Video unblocked.'))
|
|
||||||
|
|
||||||
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
this.buildVideoActions(),
|
||||||
},
|
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
this.buildAccountActions()
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Delete video'),
|
|
||||||
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?'),
|
|
||||||
this.i18n('Delete')
|
|
||||||
)
|
|
||||||
if (res === false) return
|
|
||||||
|
|
||||||
this.videoService.removeVideo(abuse.video.id)
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.notifier.success(this.i18n('Video deleted.'))
|
|
||||||
|
|
||||||
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
|
||||||
},
|
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: this.i18n('Actions for the reporter'),
|
|
||||||
isHeader: true,
|
|
||||||
isDisplayed: abuse => !!abuse.reporterAccount
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: this.i18n('Mute reporter'),
|
|
||||||
isDisplayed: abuse => !!abuse.reporterAccount,
|
|
||||||
handler: async abuse => {
|
|
||||||
const account = abuse.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: abuse => abuse.reporterAccount && !abuse.reporterAccount.userId,
|
|
||||||
handler: async abuse => {
|
|
||||||
this.blocklistService.blockServerByInstance(abuse.reporterAccount.host)
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.notifier.success(
|
|
||||||
this.i18n('Server {{host}} muted by the instance.', { host: abuse.reporterAccount.host })
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +89,8 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
||||||
.subscribe(params => {
|
.subscribe(params => {
|
||||||
this.search = params.search || ''
|
this.search = params.search || ''
|
||||||
|
|
||||||
|
logger('On URL change (search: %s).', this.search)
|
||||||
|
|
||||||
this.setTableFilter(this.search)
|
this.setTableFilter(this.search)
|
||||||
this.loadData()
|
this.loadData()
|
||||||
})
|
})
|
||||||
|
@ -264,6 +148,10 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
||||||
return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId
|
return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccountUrl (abuse: ProcessedAbuse) {
|
||||||
|
return '/accounts/' + abuse.flaggedAccount.nameWithHost
|
||||||
|
}
|
||||||
|
|
||||||
getVideoEmbed (abuse: Abuse) {
|
getVideoEmbed (abuse: Abuse) {
|
||||||
return buildVideoEmbed(
|
return buildVideoEmbed(
|
||||||
buildVideoLink({
|
buildVideoLink({
|
||||||
|
@ -304,6 +192,8 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadData () {
|
protected loadData () {
|
||||||
|
logger('Load data.')
|
||||||
|
|
||||||
return this.abuseService.getAbuses({
|
return this.abuseService.getAbuses({
|
||||||
pagination: this.pagination,
|
pagination: this.pagination,
|
||||||
sort: this.sort,
|
sort: this.sort,
|
||||||
|
@ -356,6 +246,208 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildInternalActions (): DropdownAction<ProcessedAbuse>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.i18n('Internal actions'),
|
||||||
|
isHeader: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Delete report'),
|
||||||
|
handler: abuse => this.removeAbuse(abuse)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Add note'),
|
||||||
|
handler: abuse => this.openModerationCommentModal(abuse),
|
||||||
|
isDisplayed: abuse => !abuse.moderationComment
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Update note'),
|
||||||
|
handler: abuse => this.openModerationCommentModal(abuse),
|
||||||
|
isDisplayed: abuse => !!abuse.moderationComment
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Mark as accepted'),
|
||||||
|
handler: abuse => this.updateAbuseState(abuse, AbuseState.ACCEPTED),
|
||||||
|
isDisplayed: abuse => !this.isAbuseAccepted(abuse)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Mark as rejected'),
|
||||||
|
handler: abuse => this.updateAbuseState(abuse, AbuseState.REJECTED),
|
||||||
|
isDisplayed: abuse => !this.isAbuseRejected(abuse)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildFlaggedAccountActions (): DropdownAction<ProcessedAbuse>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.i18n('Actions for the flagged account'),
|
||||||
|
isHeader: true,
|
||||||
|
isDisplayed: abuse => abuse.flaggedAccount && !abuse.comment && !abuse.video
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: this.i18n('Mute account'),
|
||||||
|
isDisplayed: abuse => abuse.flaggedAccount && !abuse.comment && !abuse.video,
|
||||||
|
handler: abuse => this.muteAccountHelper(abuse.flaggedAccount)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: this.i18n('Mute server account'),
|
||||||
|
isDisplayed: abuse => abuse.flaggedAccount && !abuse.comment && !abuse.video,
|
||||||
|
handler: abuse => this.muteServerHelper(abuse.flaggedAccount.host)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildAccountActions (): DropdownAction<ProcessedAbuse>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.i18n('Actions for the reporter'),
|
||||||
|
isHeader: true,
|
||||||
|
isDisplayed: abuse => !!abuse.reporterAccount
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: this.i18n('Mute reporter'),
|
||||||
|
isDisplayed: abuse => !!abuse.reporterAccount,
|
||||||
|
handler: abuse => this.muteAccountHelper(abuse.reporterAccount)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: this.i18n('Mute server'),
|
||||||
|
isDisplayed: abuse => abuse.reporterAccount && !abuse.reporterAccount.userId,
|
||||||
|
handler: abuse => this.muteServerHelper(abuse.reporterAccount.host)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildVideoActions (): DropdownAction<ProcessedAbuse>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.i18n('Actions for the video'),
|
||||||
|
isHeader: true,
|
||||||
|
isDisplayed: abuse => abuse.video && !abuse.video.deleted
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Block video'),
|
||||||
|
isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted,
|
||||||
|
handler: abuse => {
|
||||||
|
this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('Video blocked.'))
|
||||||
|
|
||||||
|
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Unblock video'),
|
||||||
|
isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted,
|
||||||
|
handler: abuse => {
|
||||||
|
this.videoBlocklistService.unblockVideo(abuse.video.id)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('Video unblocked.'))
|
||||||
|
|
||||||
|
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.i18n('Delete video'),
|
||||||
|
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?'),
|
||||||
|
this.i18n('Delete')
|
||||||
|
)
|
||||||
|
if (res === false) return
|
||||||
|
|
||||||
|
this.videoService.removeVideo(abuse.video.id)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('Video deleted.'))
|
||||||
|
|
||||||
|
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildCommentActions (): DropdownAction<ProcessedAbuse>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.i18n('Actions for the comment'),
|
||||||
|
isHeader: true,
|
||||||
|
isDisplayed: abuse => abuse.comment && !abuse.comment.deleted
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: this.i18n('Delete comment'),
|
||||||
|
isDisplayed: abuse => abuse.comment && !abuse.comment.deleted,
|
||||||
|
handler: async abuse => {
|
||||||
|
const res = await this.confirmService.confirm(
|
||||||
|
this.i18n('Do you really want to delete this comment?'),
|
||||||
|
this.i18n('Delete')
|
||||||
|
)
|
||||||
|
if (res === false) return
|
||||||
|
|
||||||
|
this.commentService.deleteVideoComment(abuse.comment.video.id, abuse.comment.id)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('Comment deleted.'))
|
||||||
|
|
||||||
|
this.updateAbuseState(abuse, AbuseState.ACCEPTED)
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private muteAccountHelper (account: 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private muteServerHelper (host: string) {
|
||||||
|
this.blocklistService.blockServerByInstance(host)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(
|
||||||
|
this.i18n('Server {{host}} muted by the instance.', { host: host })
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private toHtml (text: string) {
|
private toHtml (text: string) {
|
||||||
return this.markdownRenderer.textMarkdownToHTML(text)
|
return this.markdownRenderer.textMarkdownToHTML(text)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,8 @@ my-action-dropdown.show {
|
||||||
top: 3px;
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-comment-link {
|
.table-comment-link,
|
||||||
|
.table-account-link {
|
||||||
@include disable-outline;
|
@include disable-outline;
|
||||||
|
|
||||||
color: var(--mainForegroundColor);
|
color: var(--mainForegroundColor);
|
||||||
|
@ -106,7 +107,13 @@ my-action-dropdown.show {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-flagged-account {
|
.table-account-link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-flagged-account,
|
||||||
|
.account-flagged-handle {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--greyForegroundColor);
|
color: var(--greyForegroundColor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,9 @@ import { Router } from '@angular/router'
|
||||||
import { Notifier, User } from '@app/core'
|
import { Notifier, User } from '@app/core'
|
||||||
import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms'
|
import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms'
|
||||||
import { Video } from '@app/shared/shared-main'
|
import { Video } from '@app/shared/shared-main'
|
||||||
|
import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { VideoCommentCreate } from '@shared/models'
|
import { VideoCommentCreate } from '@shared/models'
|
||||||
import { VideoComment } from './video-comment.model'
|
|
||||||
import { VideoCommentService } from './video-comment.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-comment-add',
|
selector: 'my-video-comment-add',
|
||||||
|
|
|
@ -3,11 +3,10 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild }
|
||||||
import { MarkdownService, Notifier, UserService } from '@app/core'
|
import { MarkdownService, Notifier, UserService } from '@app/core'
|
||||||
import { AuthService } from '@app/core/auth'
|
import { AuthService } from '@app/core/auth'
|
||||||
import { Account, Actor, DropdownAction, 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 { CommentReportComponent } from '@app/shared/shared-moderation/report-modals/comment-report.component'
|
||||||
|
import { VideoComment, VideoCommentThreadTree } from '@app/shared/shared-video-comment'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { User, UserRight } from '@shared/models'
|
import { User, UserRight } from '@shared/models'
|
||||||
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
|
|
||||||
import { VideoComment } from './video-comment.model'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-comment',
|
selector: 'my-video-comment',
|
||||||
|
@ -136,7 +135,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
||||||
this.comment.account = null
|
this.comment.account = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isUserLoggedIn()) {
|
if (this.isUserLoggedIn() && this.authService.getUser().account.id !== this.comment.account.id) {
|
||||||
this.prependModerationActions = [
|
this.prependModerationActions = [
|
||||||
{
|
{
|
||||||
label: this.i18n('Report comment'),
|
label: this.i18n('Report comment'),
|
||||||
|
|
|
@ -4,10 +4,8 @@ import { ActivatedRoute } from '@angular/router'
|
||||||
import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core'
|
import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core'
|
||||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
import { Syndication, VideoDetails } from '@app/shared/shared-main'
|
import { Syndication, VideoDetails } from '@app/shared/shared-main'
|
||||||
|
import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
|
|
||||||
import { VideoComment } from './video-comment.model'
|
|
||||||
import { VideoCommentService } from './video-comment.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-comments',
|
selector: 'my-video-comments',
|
||||||
|
|
|
@ -5,16 +5,17 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons'
|
||||||
import { SharedMainModule } from '@app/shared/shared-main'
|
import { SharedMainModule } from '@app/shared/shared-main'
|
||||||
import { SharedModerationModule } from '@app/shared/shared-moderation'
|
import { SharedModerationModule } from '@app/shared/shared-moderation'
|
||||||
import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription'
|
import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription'
|
||||||
|
import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
|
||||||
import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
|
import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
|
||||||
import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist'
|
import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist'
|
||||||
import { RecommendationsModule } from './recommendations/recommendations.module'
|
|
||||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { VideoCommentService } from '../../shared/shared-video-comment/video-comment.service'
|
||||||
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
|
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
|
||||||
import { VideoCommentComponent } from './comment/video-comment.component'
|
import { VideoCommentComponent } from './comment/video-comment.component'
|
||||||
import { VideoCommentService } from './comment/video-comment.service'
|
|
||||||
import { VideoCommentsComponent } from './comment/video-comments.component'
|
import { VideoCommentsComponent } from './comment/video-comments.component'
|
||||||
import { VideoShareComponent } from './modal/video-share.component'
|
import { VideoShareComponent } from './modal/video-share.component'
|
||||||
import { VideoSupportComponent } from './modal/video-support.component'
|
import { VideoSupportComponent } from './modal/video-support.component'
|
||||||
|
import { RecommendationsModule } from './recommendations/recommendations.module'
|
||||||
import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive'
|
import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive'
|
||||||
import { VideoDurationPipe } from './video-duration-formatter.pipe'
|
import { VideoDurationPipe } from './video-duration-formatter.pipe'
|
||||||
import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
|
import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
|
||||||
|
@ -34,7 +35,8 @@ import { VideoWatchComponent } from './video-watch.component'
|
||||||
SharedVideoPlaylistModule,
|
SharedVideoPlaylistModule,
|
||||||
SharedUserSubscriptionModule,
|
SharedUserSubscriptionModule,
|
||||||
SharedModerationModule,
|
SharedModerationModule,
|
||||||
SharedGlobalIconModule
|
SharedGlobalIconModule,
|
||||||
|
SharedVideoCommentModule
|
||||||
],
|
],
|
||||||
|
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { LazyLoadEvent, SortMeta } from 'primeng/api'
|
||||||
import { RestPagination } from './rest-pagination'
|
import { RestPagination } from './rest-pagination'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
|
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
|
||||||
|
import * as debug from 'debug'
|
||||||
|
|
||||||
|
const logger = debug('peertube:tables:RestTable')
|
||||||
|
|
||||||
export abstract class RestTable {
|
export abstract class RestTable {
|
||||||
|
|
||||||
|
@ -15,7 +18,7 @@ export abstract class RestTable {
|
||||||
rowsPerPage = this.rowsPerPageOptions[0]
|
rowsPerPage = this.rowsPerPageOptions[0]
|
||||||
expandedRows = {}
|
expandedRows = {}
|
||||||
|
|
||||||
private searchStream: Subject<string>
|
protected searchStream: Subject<string>
|
||||||
|
|
||||||
abstract getIdentifier (): string
|
abstract getIdentifier (): string
|
||||||
|
|
||||||
|
@ -37,6 +40,8 @@ export abstract class RestTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLazy (event: LazyLoadEvent) {
|
loadLazy (event: LazyLoadEvent) {
|
||||||
|
logger('Load lazy %o.', event)
|
||||||
|
|
||||||
this.sort = {
|
this.sort = {
|
||||||
order: event.sortOrder,
|
order: event.sortOrder,
|
||||||
field: event.sortField
|
field: event.sortField
|
||||||
|
@ -65,6 +70,9 @@ export abstract class RestTable {
|
||||||
)
|
)
|
||||||
.subscribe(search => {
|
.subscribe(search => {
|
||||||
this.search = search
|
this.search = search
|
||||||
|
|
||||||
|
logger('On search %s.', this.search)
|
||||||
|
|
||||||
this.loadData()
|
this.loadData()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -75,14 +83,18 @@ export abstract class RestTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPage (event: { first: number, rows: number }) {
|
onPage (event: { first: number, rows: number }) {
|
||||||
|
logger('On page %o.', event)
|
||||||
|
|
||||||
if (this.rowsPerPage !== event.rows) {
|
if (this.rowsPerPage !== event.rows) {
|
||||||
this.rowsPerPage = event.rows
|
this.rowsPerPage = event.rows
|
||||||
this.pagination = {
|
this.pagination = {
|
||||||
start: event.first,
|
start: event.first,
|
||||||
count: this.rowsPerPage
|
count: this.rowsPerPage
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.expandedRows = {}
|
this.expandedRows = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ export abstract class Actor implements ActorServer {
|
||||||
|
|
||||||
avatarUrl: string
|
avatarUrl: string
|
||||||
|
|
||||||
|
isLocal: boolean
|
||||||
|
|
||||||
static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
|
static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
|
||||||
if (actor?.avatar?.url) return actor.avatar.url
|
if (actor?.avatar?.url) return actor.avatar.url
|
||||||
|
|
||||||
|
@ -52,6 +54,10 @@ export abstract class Actor implements ActorServer {
|
||||||
|
|
||||||
this.avatar = hash.avatar
|
this.avatar = hash.avatar
|
||||||
|
|
||||||
|
const absoluteAPIUrl = getAbsoluteAPIUrl()
|
||||||
|
const thisHost = new URL(absoluteAPIUrl).host
|
||||||
|
this.isLocal = this.host.trim() === thisHost
|
||||||
|
|
||||||
this.updateComputedAttributes()
|
this.updateComputedAttributes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,9 @@ export class UserNotification implements UserNotificationServer {
|
||||||
threadId: number
|
threadId: number
|
||||||
|
|
||||||
video: {
|
video: {
|
||||||
|
id: number
|
||||||
uuid: string
|
uuid: string
|
||||||
|
name: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,13 +117,15 @@ export class UserNotification implements UserNotificationServer {
|
||||||
case UserNotificationType.COMMENT_MENTION:
|
case UserNotificationType.COMMENT_MENTION:
|
||||||
if (!this.comment) break
|
if (!this.comment) break
|
||||||
this.accountUrl = this.buildAccountUrl(this.comment.account)
|
this.accountUrl = this.buildAccountUrl(this.comment.account)
|
||||||
this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ]
|
this.commentUrl = this.buildCommentUrl(this.comment)
|
||||||
break
|
break
|
||||||
|
|
||||||
case UserNotificationType.NEW_ABUSE_FOR_MODERATORS:
|
case UserNotificationType.NEW_ABUSE_FOR_MODERATORS:
|
||||||
this.abuseUrl = '/admin/moderation/abuses/list'
|
this.abuseUrl = '/admin/moderation/abuses/list'
|
||||||
|
|
||||||
if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video)
|
if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video)
|
||||||
|
else if (this.abuse.comment) this.commentUrl = this.buildCommentUrl(this.abuse.comment)
|
||||||
|
else if (this.abuse.account) this.accountUrl = this.buildAccountUrl(this.abuse.account)
|
||||||
break
|
break
|
||||||
|
|
||||||
case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
|
case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
|
||||||
|
@ -190,6 +194,10 @@ export class UserNotification implements UserNotificationServer {
|
||||||
return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName
|
return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildCommentUrl (comment: { video: { uuid: string }, threadId: number }) {
|
||||||
|
return [ this.buildVideoUrl(comment.video), { threadId: comment.threadId } ]
|
||||||
|
}
|
||||||
|
|
||||||
private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
|
private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
|
||||||
actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
|
actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,9 +45,22 @@
|
||||||
<ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS">
|
<ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS">
|
||||||
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
|
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
|
||||||
|
|
||||||
<div class="message" i18n>
|
<div class="message" *ngIf="notification.videoUrl" i18n>
|
||||||
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="message" *ngIf="notification.commentUrl" i18n>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message" *ngIf="notification.accountUrl" i18n>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deleted entity associated to the abuse -->
|
||||||
|
<div class="message" *ngIf="!notification.videoUrl && !notification.commentUrl && !notification.accountUrl" i18n>
|
||||||
|
<a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new abuse</a> has been created
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS">
|
<ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS">
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
@import 'variables';
|
|
||||||
@import 'mixins';
|
|
||||||
|
|
||||||
.information {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
@include peertube-textarea(100%, 100px);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
export * from './report-modals'
|
||||||
|
|
||||||
export * from './abuse.service'
|
export * from './abuse.service'
|
||||||
export * from './account-block.model'
|
export * from './account-block.model'
|
||||||
export * from './account-blocklist.component'
|
export * from './account-blocklist.component'
|
||||||
|
@ -9,5 +11,4 @@ export * from './user-ban-modal.component'
|
||||||
export * from './user-moderation-dropdown.component'
|
export * from './user-moderation-dropdown.component'
|
||||||
export * from './video-block.component'
|
export * from './video-block.component'
|
||||||
export * from './video-block.service'
|
export * from './video-block.service'
|
||||||
export * from './video-report.component'
|
|
||||||
export * from './shared-moderation.module'
|
export * from './shared-moderation.module'
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { mapValues, pickBy } from 'lodash-es'
|
||||||
|
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||||
|
import { Notifier } from '@app/core'
|
||||||
|
import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||||
|
import { Account } from '@app/shared/shared-main'
|
||||||
|
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-account-report',
|
||||||
|
templateUrl: './report.component.html',
|
||||||
|
styleUrls: [ './report.component.scss' ]
|
||||||
|
})
|
||||||
|
export class AccountReportComponent extends FormReactive implements OnInit {
|
||||||
|
@Input() account: Account = null
|
||||||
|
|
||||||
|
@ViewChild('modal', { static: true }) modal: NgbModal
|
||||||
|
|
||||||
|
error: string = null
|
||||||
|
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
|
||||||
|
modalTitle: string
|
||||||
|
|
||||||
|
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.isRemote()) {
|
||||||
|
return this.account.host
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.modalTitle = this.i18n('Report {{displayName}}', { displayName: this.account.displayName })
|
||||||
|
|
||||||
|
this.buildForm({
|
||||||
|
reason: this.abuseValidatorsService.ABUSE_REASON,
|
||||||
|
predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.predefinedReasons = this.abuseService.getPrefefinedReasons('account')
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
account: {
|
||||||
|
id: this.account.id
|
||||||
|
}
|
||||||
|
}).subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('Account reported.'))
|
||||||
|
this.hide()
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
isRemote () {
|
||||||
|
return !this.account.isLocal
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,27 @@
|
||||||
import { mapValues, pickBy } from 'lodash-es'
|
import { mapValues, pickBy } from 'lodash-es'
|
||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
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 { Notifier } from '@app/core'
|
||||||
import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||||
|
import { VideoComment } from '@app/shared/shared-video-comment'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
|
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
|
||||||
import { AbuseService } from './abuse.service'
|
import { AbuseService } from '../abuse.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-comment-report',
|
selector: 'my-comment-report',
|
||||||
templateUrl: './comment-report.component.html',
|
templateUrl: './report.component.html',
|
||||||
styleUrls: [ './comment-report.component.scss' ]
|
styleUrls: [ './report.component.scss' ]
|
||||||
})
|
})
|
||||||
export class CommentReportComponent extends FormReactive implements OnInit {
|
export class CommentReportComponent extends FormReactive implements OnInit {
|
||||||
@Input() comment: VideoComment = null
|
@Input() comment: VideoComment = null
|
||||||
|
|
||||||
@ViewChild('modal', { static: true }) modal: NgbModal
|
@ViewChild('modal', { static: true }) modal: NgbModal
|
||||||
|
|
||||||
|
modalTitle: string
|
||||||
error: string = null
|
error: string = null
|
||||||
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
|
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
|
||||||
embedHtml: SafeHtml
|
|
||||||
|
|
||||||
private openedModal: NgbModalRef
|
private openedModal: NgbModalRef
|
||||||
|
|
||||||
|
@ -42,7 +41,7 @@ export class CommentReportComponent extends FormReactive implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
get originHost () {
|
get originHost () {
|
||||||
if (this.isRemoteComment()) {
|
if (this.isRemote()) {
|
||||||
return this.comment.account.host
|
return this.comment.account.host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +49,8 @@ export class CommentReportComponent extends FormReactive implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
this.modalTitle = this.i18n('Report comment')
|
||||||
|
|
||||||
this.buildForm({
|
this.buildForm({
|
||||||
reason: this.abuseValidatorsService.ABUSE_REASON,
|
reason: this.abuseValidatorsService.ABUSE_REASON,
|
||||||
predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null)
|
predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null)
|
||||||
|
@ -87,7 +88,7 @@ export class CommentReportComponent extends FormReactive implements OnInit {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isRemoteComment () {
|
isRemote () {
|
||||||
return !this.comment.isLocal
|
return !this.comment.isLocal
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './account-report.component'
|
||||||
|
export * from './comment-report.component'
|
||||||
|
export * from './video-report.component'
|
|
@ -1,6 +1,6 @@
|
||||||
<ng-template #modal>
|
<ng-template #modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Report comment</h4>
|
<h4 class="modal-title">{{ modalTitle }}</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<div class="col-7">
|
<div class="col-7">
|
||||||
<div i18n class="information">
|
<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>.
|
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>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
|
@ -72,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div i18n class="information">
|
<div i18n class="information">
|
||||||
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>.
|
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>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
|
@ -8,13 +8,13 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
|
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
|
||||||
import { Video } from '../shared-main'
|
import { Video } from '../../shared-main'
|
||||||
import { AbuseService } from './abuse.service'
|
import { AbuseService } from '../abuse.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-report',
|
selector: 'my-video-report',
|
||||||
templateUrl: './video-report.component.html',
|
templateUrl: './video-report.component.html',
|
||||||
styleUrls: [ './video-report.component.scss' ]
|
styleUrls: [ './report.component.scss' ]
|
||||||
})
|
})
|
||||||
export class VideoReportComponent extends FormReactive implements OnInit {
|
export class VideoReportComponent extends FormReactive implements OnInit {
|
||||||
@Input() video: Video = null
|
@Input() video: Video = null
|
||||||
|
@ -44,7 +44,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
get originHost () {
|
get originHost () {
|
||||||
if (this.isRemoteVideo()) {
|
if (this.isRemote()) {
|
||||||
return this.video.account.host
|
return this.video.account.host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isRemoteVideo () {
|
isRemote () {
|
||||||
return !this.video.isLocal
|
return !this.video.isLocal
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,22 +3,23 @@ import { NgModule } from '@angular/core'
|
||||||
import { SharedFormModule } from '../shared-forms/shared-form.module'
|
import { SharedFormModule } from '../shared-forms/shared-form.module'
|
||||||
import { SharedGlobalIconModule } from '../shared-icons'
|
import { SharedGlobalIconModule } from '../shared-icons'
|
||||||
import { SharedMainModule } from '../shared-main/shared-main.module'
|
import { SharedMainModule } from '../shared-main/shared-main.module'
|
||||||
|
import { SharedVideoCommentModule } from '../shared-video-comment'
|
||||||
|
import { AbuseService } from './abuse.service'
|
||||||
import { BatchDomainsModalComponent } from './batch-domains-modal.component'
|
import { BatchDomainsModalComponent } from './batch-domains-modal.component'
|
||||||
import { BlocklistService } from './blocklist.service'
|
import { BlocklistService } from './blocklist.service'
|
||||||
import { BulkService } from './bulk.service'
|
import { BulkService } from './bulk.service'
|
||||||
import { UserBanModalComponent } from './user-ban-modal.component'
|
import { UserBanModalComponent } from './user-ban-modal.component'
|
||||||
import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
|
import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
|
||||||
import { AbuseService } from './abuse.service'
|
|
||||||
import { VideoBlockComponent } from './video-block.component'
|
import { VideoBlockComponent } from './video-block.component'
|
||||||
import { VideoBlockService } from './video-block.service'
|
import { VideoBlockService } from './video-block.service'
|
||||||
import { VideoReportComponent } from './video-report.component'
|
import { VideoReportComponent, AccountReportComponent, CommentReportComponent } from './report-modals'
|
||||||
import { CommentReportComponent } from './comment-report.component'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedMainModule,
|
SharedMainModule,
|
||||||
SharedFormModule,
|
SharedFormModule,
|
||||||
SharedGlobalIconModule
|
SharedGlobalIconModule,
|
||||||
|
SharedVideoCommentModule
|
||||||
],
|
],
|
||||||
|
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -27,7 +28,8 @@ import { CommentReportComponent } from './comment-report.component'
|
||||||
VideoBlockComponent,
|
VideoBlockComponent,
|
||||||
VideoReportComponent,
|
VideoReportComponent,
|
||||||
BatchDomainsModalComponent,
|
BatchDomainsModalComponent,
|
||||||
CommentReportComponent
|
CommentReportComponent,
|
||||||
|
AccountReportComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
|
@ -36,7 +38,8 @@ import { CommentReportComponent } from './comment-report.component'
|
||||||
VideoBlockComponent,
|
VideoBlockComponent,
|
||||||
VideoReportComponent,
|
VideoReportComponent,
|
||||||
BatchDomainsModalComponent,
|
BatchDomainsModalComponent,
|
||||||
CommentReportComponent
|
CommentReportComponent,
|
||||||
|
AccountReportComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './video-comment.service'
|
||||||
|
export * from './video-comment.model'
|
||||||
|
export * from './video-comment-thread-tree.model'
|
||||||
|
|
||||||
|
export * from './shared-video-comment.module'
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { SharedMainModule } from '../shared-main/shared-main.module'
|
||||||
|
import { VideoCommentService } from './video-comment.service'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
SharedMainModule
|
||||||
|
],
|
||||||
|
|
||||||
|
declarations: [ ],
|
||||||
|
|
||||||
|
exports: [ ],
|
||||||
|
|
||||||
|
providers: [
|
||||||
|
VideoCommentService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SharedVideoCommentModule { }
|
|
@ -11,7 +11,7 @@ import {
|
||||||
VideoCommentCreate,
|
VideoCommentCreate,
|
||||||
VideoCommentThreadTree as VideoCommentThreadTreeServerModel
|
VideoCommentThreadTree as VideoCommentThreadTreeServerModel
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
import { environment } from '../../../../environments/environment'
|
import { environment } from '../../../environments/environment'
|
||||||
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
|
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
|
||||||
import { VideoComment } from './video-comment.model'
|
import { VideoComment } from './video-comment.model'
|
||||||
|
|
|
@ -311,7 +311,8 @@ class Emailer {
|
||||||
videoPublishedAt: new Date(video.publishedAt).toLocaleString(),
|
videoPublishedAt: new Date(video.publishedAt).toLocaleString(),
|
||||||
videoName: video.name,
|
videoName: video.name,
|
||||||
reason: abuse.reason,
|
reason: abuse.reason,
|
||||||
videoChannel: video.VideoChannel,
|
videoChannel: abuse.video.channel,
|
||||||
|
reporter,
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,6 +331,7 @@ class Emailer {
|
||||||
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
|
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
|
||||||
reason: abuse.reason,
|
reason: abuse.reason,
|
||||||
flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(),
|
flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(),
|
||||||
|
reporter,
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,6 +348,7 @@ class Emailer {
|
||||||
accountDisplayName: account.getDisplayName(),
|
accountDisplayName: account.getDisplayName(),
|
||||||
isLocal: account.isOwned(),
|
isLocal: account.isOwned(),
|
||||||
reason: abuse.reason,
|
reason: abuse.reason,
|
||||||
|
reporter,
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ block title
|
||||||
|
|
||||||
block content
|
block content
|
||||||
p
|
p
|
||||||
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account "
|
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account
|
||||||
a(href=accountUrl) #{accountDisplayName}
|
a(href=accountUrl) #{accountDisplayName}
|
||||||
|
|
||||||
p The reporter, #{reporter}, cited the following reason(s):
|
p The reporter, #{reporter}, cited the following reason(s):
|
||||||
blockquote #{reason}
|
blockquote #{reason}
|
||||||
|
|
|
@ -6,10 +6,10 @@ block title
|
||||||
|
|
||||||
block content
|
block content
|
||||||
p
|
p
|
||||||
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment "
|
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}
|
||||||
a(href=commentUrl) on video #{videoName}
|
a(href=commentUrl) comment on video "#{videoName}"
|
||||||
| of #{flaggedAccount}
|
| of #{flaggedAccount}
|
||||||
| created on #{commentCreatedAt}
|
| created on #{commentCreatedAt}
|
||||||
|
|
||||||
p The reporter, #{reporter}, cited the following reason(s):
|
p The reporter, #{reporter}, cited the following reason(s):
|
||||||
blockquote #{reason}
|
blockquote #{reason}
|
||||||
|
|
|
@ -62,9 +62,9 @@ export interface Abuse {
|
||||||
// FIXME: deprecated in 2.3, remove the following properties
|
// FIXME: deprecated in 2.3, remove the following properties
|
||||||
|
|
||||||
// @deprecated
|
// @deprecated
|
||||||
startAt: null
|
startAt?: null
|
||||||
// @deprecated
|
// @deprecated
|
||||||
endAt: null
|
endAt?: null
|
||||||
|
|
||||||
// @deprecated
|
// @deprecated
|
||||||
count?: number
|
count?: number
|
||||||
|
|
|
@ -73,7 +73,9 @@ export interface UserNotification {
|
||||||
threadId: number
|
threadId: number
|
||||||
|
|
||||||
video: {
|
video: {
|
||||||
|
id: number
|
||||||
uuid: string
|
uuid: string
|
||||||
|
name: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue