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
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
(onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
@@ -128,6 +128,22 @@
+
+
+
+ {{ abuse.flaggedAccount.displayName }}
+
+ {{ abuse.flaggedAccount.nameWithHostForced }}
+
+ |
+
+
+ Account deleted
+ |
+
+
+
+
{{ abuse.createdAt | date: 'short' }} |
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts
index 1ea61ed37..74c5fe2b3 100644
--- a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts
@@ -1,3 +1,5 @@
+import * as debug from 'debug'
+import truncate from 'lodash-es/truncate'
import { SortMeta } from 'primeng/api'
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
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 { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
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 { Abuse, AbuseState } from '@shared/models'
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 & {
moderationCommentHtml?: string,
reasonHtml?: string
@@ -45,12 +51,13 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
sort: SortMeta = { field: 'createdAt', order: 1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
- abuseActions: DropdownAction[][] = []
+ abuseActions: DropdownAction[][] = []
constructor (
private notifier: Notifier,
private abuseService: AbuseService,
private blocklistService: BlocklistService,
+ private commentService: VideoCommentService,
private videoService: VideoService,
private videoBlocklistService: VideoBlockService,
private confirmService: ConfirmService,
@@ -63,140 +70,15 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
super()
this.abuseActions = [
- [
- {
- 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.buildInternalActions(),
- this.updateAbuseState(abuse, AbuseState.ACCEPTED)
- },
+ this.buildFlaggedAccountActions(),
- 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.buildCommentActions(),
- this.updateAbuseState(abuse, AbuseState.ACCEPTED)
- },
+ this.buildVideoActions(),
- 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)
- )
- }
- }
- ],
- [
- {
- 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)
- )
- }
- }
- ]
+ this.buildAccountActions()
]
}
@@ -207,6 +89,8 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
.subscribe(params => {
this.search = params.search || ''
+ logger('On URL change (search: %s).', this.search)
+
this.setTableFilter(this.search)
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
}
+ getAccountUrl (abuse: ProcessedAbuse) {
+ return '/accounts/' + abuse.flaggedAccount.nameWithHost
+ }
+
getVideoEmbed (abuse: Abuse) {
return buildVideoEmbed(
buildVideoLink({
@@ -304,6 +192,8 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
}
protected loadData () {
+ logger('Load data.')
+
return this.abuseService.getAbuses({
pagination: this.pagination,
sort: this.sort,
@@ -356,6 +246,208 @@ export class AbuseListComponent extends RestTable implements OnInit, AfterViewIn
)
}
+ private buildInternalActions (): DropdownAction[] {
+ 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[] {
+ 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[] {
+ 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[] {
+ 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[] {
+ 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) {
return this.markdownRenderer.textMarkdownToHTML(text)
}
diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss
index f73c71dc5..65fe94d39 100644
--- a/client/src/app/+admin/moderation/moderation.component.scss
+++ b/client/src/app/+admin/moderation/moderation.component.scss
@@ -96,7 +96,8 @@ my-action-dropdown.show {
top: 3px;
}
-.table-comment-link {
+.table-comment-link,
+.table-account-link {
@include disable-outline;
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;
color: var(--greyForegroundColor);
}
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts
index 79505c779..d79efbb49 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts
+++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts
@@ -4,10 +4,9 @@ import { Router } from '@angular/router'
import { Notifier, User } from '@app/core'
import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms'
import { Video } from '@app/shared/shared-main'
+import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { VideoCommentCreate } from '@shared/models'
-import { VideoComment } from './video-comment.model'
-import { VideoCommentService } from './video-comment.service'
@Component({
selector: 'my-video-comment-add',
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
index 2a4a6e737..6744a0954 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
@@ -3,11 +3,10 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild }
import { MarkdownService, Notifier, UserService } from '@app/core'
import { AuthService } from '@app/core/auth'
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 { User, UserRight } from '@shared/models'
-import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
-import { VideoComment } from './video-comment.model'
@Component({
selector: 'my-video-comment',
@@ -136,7 +135,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
this.comment.account = null
}
- if (this.isUserLoggedIn()) {
+ if (this.isUserLoggedIn() && this.authService.getUser().account.id !== this.comment.account.id) {
this.prependModerationActions = [
{
label: this.i18n('Report comment'),
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts
index df0018ec6..66494a20a 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts
+++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts
@@ -4,10 +4,8 @@ import { ActivatedRoute } from '@angular/router'
import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service'
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 { VideoCommentThreadTree } from './video-comment-thread-tree.model'
-import { VideoComment } from './video-comment.model'
-import { VideoCommentService } from './video-comment.service'
@Component({
selector: 'my-video-comments',
diff --git a/client/src/app/+videos/+video-watch/video-watch.module.ts b/client/src/app/+videos/+video-watch/video-watch.module.ts
index 421170d81..5821dc2b7 100644
--- a/client/src/app/+videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.module.ts
@@ -5,16 +5,17 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons'
import { SharedMainModule } from '@app/shared/shared-main'
import { SharedModerationModule } from '@app/shared/shared-moderation'
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 { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist'
-import { RecommendationsModule } from './recommendations/recommendations.module'
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 { VideoCommentComponent } from './comment/video-comment.component'
-import { VideoCommentService } from './comment/video-comment.service'
import { VideoCommentsComponent } from './comment/video-comments.component'
import { VideoShareComponent } from './modal/video-share.component'
import { VideoSupportComponent } from './modal/video-support.component'
+import { RecommendationsModule } from './recommendations/recommendations.module'
import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive'
import { VideoDurationPipe } from './video-duration-formatter.pipe'
import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
@@ -34,7 +35,8 @@ import { VideoWatchComponent } from './video-watch.component'
SharedVideoPlaylistModule,
SharedUserSubscriptionModule,
SharedModerationModule,
- SharedGlobalIconModule
+ SharedGlobalIconModule,
+ SharedVideoCommentModule
],
declarations: [
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts
index 1b35ad47d..e6328eddc 100644
--- a/client/src/app/core/rest/rest-table.ts
+++ b/client/src/app/core/rest/rest-table.ts
@@ -3,6 +3,9 @@ import { LazyLoadEvent, SortMeta } from 'primeng/api'
import { RestPagination } from './rest-pagination'
import { Subject } from 'rxjs'
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
+import * as debug from 'debug'
+
+const logger = debug('peertube:tables:RestTable')
export abstract class RestTable {
@@ -15,7 +18,7 @@ export abstract class RestTable {
rowsPerPage = this.rowsPerPageOptions[0]
expandedRows = {}
- private searchStream: Subject
+ protected searchStream: Subject
abstract getIdentifier (): string
@@ -37,6 +40,8 @@ export abstract class RestTable {
}
loadLazy (event: LazyLoadEvent) {
+ logger('Load lazy %o.', event)
+
this.sort = {
order: event.sortOrder,
field: event.sortField
@@ -65,6 +70,9 @@ export abstract class RestTable {
)
.subscribe(search => {
this.search = search
+
+ logger('On search %s.', this.search)
+
this.loadData()
})
}
@@ -75,14 +83,18 @@ export abstract class RestTable {
}
onPage (event: { first: number, rows: number }) {
+ logger('On page %o.', event)
+
if (this.rowsPerPage !== event.rows) {
this.rowsPerPage = event.rows
this.pagination = {
start: event.first,
count: this.rowsPerPage
}
+
this.loadData()
}
+
this.expandedRows = {}
}
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
index 9ec6dbab1..bda88bdee 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -14,6 +14,8 @@ export abstract class Actor implements ActorServer {
avatarUrl: string
+ isLocal: boolean
+
static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
if (actor?.avatar?.url) return actor.avatar.url
@@ -52,6 +54,10 @@ export abstract class Actor implements ActorServer {
this.avatar = hash.avatar
+ const absoluteAPIUrl = getAbsoluteAPIUrl()
+ const thisHost = new URL(absoluteAPIUrl).host
+ this.isLocal = this.host.trim() === thisHost
+
this.updateComputedAttributes()
}
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
index a137f8c62..61b48a806 100644
--- a/client/src/app/shared/shared-main/users/user-notification.model.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -34,7 +34,9 @@ export class UserNotification implements UserNotificationServer {
threadId: number
video: {
+ id: number
uuid: string
+ name: string
}
}
@@ -115,13 +117,15 @@ export class UserNotification implements UserNotificationServer {
case UserNotificationType.COMMENT_MENTION:
if (!this.comment) break
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
case UserNotificationType.NEW_ABUSE_FOR_MODERATORS:
this.abuseUrl = '/admin/moderation/abuses/list'
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
case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
@@ -190,6 +194,10 @@ export class UserNotification implements UserNotificationServer {
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 } }) {
actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
}
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html
index 2b341af2c..8127ae979 100644
--- a/client/src/app/shared/shared-main/users/user-notifications.component.html
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.html
@@ -45,9 +45,22 @@
-
+
+
+
+
+
+
+
+
diff --git a/client/src/app/shared/shared-moderation/comment-report.component.scss b/client/src/app/shared/shared-moderation/comment-report.component.scss
deleted file mode 100644
index 17a33d3a2..000000000
--- a/client/src/app/shared/shared-moderation/comment-report.component.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-@import 'variables';
-@import 'mixins';
-
-.information {
- margin-bottom: 20px;
-}
-
-textarea {
- @include peertube-textarea(100%, 100px);
-}
-
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts
index d6c4a10be..41c910ffe 100644
--- a/client/src/app/shared/shared-moderation/index.ts
+++ b/client/src/app/shared/shared-moderation/index.ts
@@ -1,3 +1,5 @@
+export * from './report-modals'
+
export * from './abuse.service'
export * from './account-block.model'
export * from './account-blocklist.component'
@@ -9,5 +11,4 @@ export * from './user-ban-modal.component'
export * from './user-moderation-dropdown.component'
export * from './video-block.component'
export * from './video-block.service'
-export * from './video-report.component'
export * from './shared-moderation.module'
diff --git a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
new file mode 100644
index 000000000..78ca934c7
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
@@ -0,0 +1,94 @@
+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
+ }
+}
diff --git a/client/src/app/shared/shared-moderation/comment-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
similarity index 86%
rename from client/src/app/shared/shared-moderation/comment-report.component.ts
rename to client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
index 5db4b2dc1..00d7b8d34 100644
--- a/client/src/app/shared/shared-moderation/comment-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
@@ -1,28 +1,27 @@
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 { VideoComment } from '@app/shared/shared-video-comment'
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'
+import { AbuseService } from '../abuse.service'
@Component({
selector: 'my-comment-report',
- templateUrl: './comment-report.component.html',
- styleUrls: [ './comment-report.component.scss' ]
+ templateUrl: './report.component.html',
+ styleUrls: [ './report.component.scss' ]
})
export class CommentReportComponent extends FormReactive implements OnInit {
@Input() comment: VideoComment = null
@ViewChild('modal', { static: true }) modal: NgbModal
+ modalTitle: string
error: string = null
predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
- embedHtml: SafeHtml
private openedModal: NgbModalRef
@@ -42,7 +41,7 @@ export class CommentReportComponent extends FormReactive implements OnInit {
}
get originHost () {
- if (this.isRemoteComment()) {
+ if (this.isRemote()) {
return this.comment.account.host
}
@@ -50,6 +49,8 @@ export class CommentReportComponent extends FormReactive implements OnInit {
}
ngOnInit () {
+ this.modalTitle = this.i18n('Report comment')
+
this.buildForm({
reason: this.abuseValidatorsService.ABUSE_REASON,
predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null)
@@ -87,7 +88,7 @@ export class CommentReportComponent extends FormReactive implements OnInit {
)
}
- isRemoteComment () {
+ isRemote () {
return !this.comment.isLocal
}
}
diff --git a/client/src/app/shared/shared-moderation/report-modals/index.ts b/client/src/app/shared/shared-moderation/report-modals/index.ts
new file mode 100644
index 000000000..f3c4058ae
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/report-modals/index.ts
@@ -0,0 +1,3 @@
+export * from './account-report.component'
+export * from './comment-report.component'
+export * from './video-report.component'
diff --git a/client/src/app/shared/shared-moderation/comment-report.component.html b/client/src/app/shared/shared-moderation/report-modals/report.component.html
similarity index 92%
rename from client/src/app/shared/shared-moderation/comment-report.component.html
rename to client/src/app/shared/shared-moderation/report-modals/report.component.html
index 1105b3788..bda62312f 100644
--- a/client/src/app/shared/shared-moderation/comment-report.component.html
+++ b/client/src/app/shared/shared-moderation/report-modals/report.component.html
@@ -1,6 +1,6 @@
@@ -34,7 +34,7 @@
- Your report will be sent to moderators of {{ currentHost }} and will be forwarded to the comment origin ({{ originHost }}) too.
+ Your report will be sent to moderators of {{ currentHost }} and will be forwarded to the comment origin ({{ originHost }}) too.
diff --git a/client/src/app/shared/shared-moderation/video-report.component.scss b/client/src/app/shared/shared-moderation/report-modals/report.component.scss
similarity index 100%
rename from client/src/app/shared/shared-moderation/video-report.component.scss
rename to client/src/app/shared/shared-moderation/report-modals/report.component.scss
diff --git a/client/src/app/shared/shared-moderation/video-report.component.html b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html
similarity index 96%
rename from client/src/app/shared/shared-moderation/video-report.component.html
rename to client/src/app/shared/shared-moderation/report-modals/video-report.component.html
index b724ecb18..4947088d1 100644
--- a/client/src/app/shared/shared-moderation/video-report.component.html
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html
@@ -72,7 +72,7 @@
- Your report will be sent to moderators of {{ currentHost }} and will be forwarded to the video origin ({{ originHost }}) too.
+ Your report will be sent to moderators of {{ currentHost }} and will be forwarded to the video origin ({{ originHost }}) too.
diff --git a/client/src/app/shared/shared-moderation/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
similarity index 94%
rename from client/src/app/shared/shared-moderation/video-report.component.ts
rename to client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
index 26e7b62ba..7d53ea3c9 100644
--- a/client/src/app/shared/shared-moderation/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
@@ -8,13 +8,13 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models'
-import { Video } from '../shared-main'
-import { AbuseService } from './abuse.service'
+import { Video } from '../../shared-main'
+import { AbuseService } from '../abuse.service'
@Component({
selector: 'my-video-report',
templateUrl: './video-report.component.html',
- styleUrls: [ './video-report.component.scss' ]
+ styleUrls: [ './report.component.scss' ]
})
export class VideoReportComponent extends FormReactive implements OnInit {
@Input() video: Video = null
@@ -44,7 +44,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
}
get originHost () {
- if (this.isRemoteVideo()) {
+ if (this.isRemote()) {
return this.video.account.host
}
@@ -116,7 +116,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
)
}
- isRemoteVideo () {
+ isRemote () {
return !this.video.isLocal
}
}
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
index ff4021a33..8fa9ee794 100644
--- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -3,22 +3,23 @@ import { NgModule } from '@angular/core'
import { SharedFormModule } from '../shared-forms/shared-form.module'
import { SharedGlobalIconModule } from '../shared-icons'
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 { BlocklistService } from './blocklist.service'
import { BulkService } from './bulk.service'
import { UserBanModalComponent } from './user-ban-modal.component'
import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
-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'
+import { VideoReportComponent, AccountReportComponent, CommentReportComponent } from './report-modals'
@NgModule({
imports: [
SharedMainModule,
SharedFormModule,
- SharedGlobalIconModule
+ SharedGlobalIconModule,
+ SharedVideoCommentModule
],
declarations: [
@@ -27,7 +28,8 @@ import { CommentReportComponent } from './comment-report.component'
VideoBlockComponent,
VideoReportComponent,
BatchDomainsModalComponent,
- CommentReportComponent
+ CommentReportComponent,
+ AccountReportComponent
],
exports: [
@@ -36,7 +38,8 @@ import { CommentReportComponent } from './comment-report.component'
VideoBlockComponent,
VideoReportComponent,
BatchDomainsModalComponent,
- CommentReportComponent
+ CommentReportComponent,
+ AccountReportComponent
],
providers: [
diff --git a/client/src/app/shared/shared-video-comment/index.ts b/client/src/app/shared/shared-video-comment/index.ts
new file mode 100644
index 000000000..b1195f232
--- /dev/null
+++ b/client/src/app/shared/shared-video-comment/index.ts
@@ -0,0 +1,5 @@
+export * from './video-comment.service'
+export * from './video-comment.model'
+export * from './video-comment-thread-tree.model'
+
+export * from './shared-video-comment.module'
diff --git a/client/src/app/shared/shared-video-comment/shared-video-comment.module.ts b/client/src/app/shared/shared-video-comment/shared-video-comment.module.ts
new file mode 100644
index 000000000..41b329861
--- /dev/null
+++ b/client/src/app/shared/shared-video-comment/shared-video-comment.module.ts
@@ -0,0 +1,19 @@
+
+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 { }
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts b/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts
similarity index 100%
rename from client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts
rename to client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts
similarity index 100%
rename from client/src/app/+videos/+video-watch/comment/video-comment.model.ts
rename to client/src/app/shared/shared-video-comment/video-comment.model.ts
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts
similarity index 98%
rename from client/src/app/+videos/+video-watch/comment/video-comment.service.ts
rename to client/src/app/shared/shared-video-comment/video-comment.service.ts
index a73fb9ca8..81c65aa38 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.service.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts
@@ -11,7 +11,7 @@ import {
VideoCommentCreate,
VideoCommentThreadTree as VideoCommentThreadTreeServerModel
} from '@shared/models'
-import { environment } from '../../../../environments/environment'
+import { environment } from '../../../environments/environment'
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
import { VideoComment } from './video-comment.model'
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 5a6f37bb9..d54eab966 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -311,7 +311,8 @@ class Emailer {
videoPublishedAt: new Date(video.publishedAt).toLocaleString(),
videoName: video.name,
reason: abuse.reason,
- videoChannel: video.VideoChannel,
+ videoChannel: abuse.video.channel,
+ reporter,
action
}
}
@@ -330,6 +331,7 @@ class Emailer {
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
reason: abuse.reason,
flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(),
+ reporter,
action
}
}
@@ -346,6 +348,7 @@ class Emailer {
accountDisplayName: account.getDisplayName(),
isLocal: account.isOwned(),
reason: abuse.reason,
+ reporter,
action
}
}
diff --git a/server/lib/emails/account-abuse-new/html.pug b/server/lib/emails/account-abuse-new/html.pug
index 06be8025b..f1aa2886e 100644
--- a/server/lib/emails/account-abuse-new/html.pug
+++ b/server/lib/emails/account-abuse-new/html.pug
@@ -6,8 +6,8 @@ block title
block content
p
- | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account "
- a(href=accountUrl) #{accountDisplayName}
+ | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account
+ a(href=accountUrl) #{accountDisplayName}
p The reporter, #{reporter}, cited the following reason(s):
blockquote #{reason}
diff --git a/server/lib/emails/video-comment-abuse-new/html.pug b/server/lib/emails/video-comment-abuse-new/html.pug
index fc1c3e4e7..e92d986b5 100644
--- a/server/lib/emails/video-comment-abuse-new/html.pug
+++ b/server/lib/emails/video-comment-abuse-new/html.pug
@@ -6,10 +6,10 @@ block title
block content
p
- | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment "
- a(href=commentUrl) on video #{videoName}
- | of #{flaggedAccount}
- | created on #{commentCreatedAt}
+ | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}
+ a(href=commentUrl) comment on video "#{videoName}"
+ | of #{flaggedAccount}
+ | created on #{commentCreatedAt}
p The reporter, #{reporter}, cited the following reason(s):
blockquote #{reason}
diff --git a/shared/models/moderation/abuse/abuse.model.ts b/shared/models/moderation/abuse/abuse.model.ts
index 74798ab2c..0a0c6bd35 100644
--- a/shared/models/moderation/abuse/abuse.model.ts
+++ b/shared/models/moderation/abuse/abuse.model.ts
@@ -62,9 +62,9 @@ export interface Abuse {
// FIXME: deprecated in 2.3, remove the following properties
// @deprecated
- startAt: null
+ startAt?: null
// @deprecated
- endAt: null
+ endAt?: null
// @deprecated
count?: number
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts
index 11d96fd50..5f7c33976 100644
--- a/shared/models/users/user-notification.model.ts
+++ b/shared/models/users/user-notification.model.ts
@@ -73,7 +73,9 @@ export interface UserNotification {
threadId: number
video: {
+ id: number
uuid: string
+ name: string
}
}
|