import { FollowsRoutes } from './follows'
import { JobsRoutes } from './jobs/job.routes'
import { UsersRoutes } from './users'
-import { VideoAbusesRoutes } from './video-abuses'
-import { VideoBlacklistRoutes } from './video-blacklist'
+import { ModerationRoutes } from '@app/+admin/moderation/moderation.routes'
const adminRoutes: Routes = [
{
},
...FollowsRoutes,
...UsersRoutes,
- ...VideoAbusesRoutes,
- ...VideoBlacklistRoutes,
+ ...ModerationRoutes,
...JobsRoutes,
...ConfigRoutes
]
Manage follows
</a>
- <a i18n *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active" class="title-page">
- Video abuses
- </a>
-
- <a i18n *ngIf="hasVideoBlacklistRight()" routerLink="/admin/video-blacklist" routerLinkActive="active" class="title-page">
- Video blacklist
+ <a i18n *ngIf="hasVideoAbusesRight() || hasVideoBlacklistRight()" routerLink="/admin/moderation" routerLinkActive="active" class="title-page">
+ Moderation
</a>
<a i18n *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page">
import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
import { JobService } from './jobs/shared/job.service'
import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users'
-import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses'
-import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
+import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation'
import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
+import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
@NgModule({
imports: [
UserListComponent,
UserBanModalComponent,
- VideoBlacklistComponent,
+ ModerationComponent,
VideoBlacklistListComponent,
-
- VideoAbusesComponent,
VideoAbuseListComponent,
ModerationCommentModalComponent,
--- /dev/null
+export * from './video-abuse-list'
+export * from './video-blacklist-list'
+export * from './moderation.component'
+export * from './moderation.routes'
--- /dev/null
+<div class="admin-sub-header">
+ <div i18n class="form-sub-title">Moderation</div>
+
+ <div class="admin-sub-nav">
+ <a *ngIf="hasVideoAbusesRight()" i18n routerLink="video-abuses/list" routerLinkActive="active">Video abuses</a>
+
+ <a *ngIf="hasVideoBlacklistRight()" i18n routerLink="video-blacklist/list" routerLinkActive="active">Blacklisted videos</a>
+ </div>
+</div>
+
+<router-outlet></router-outlet>
\ No newline at end of file
--- /dev/null
+.form-sub-title {
+ flex-grow: 0;
+ margin-right: 30px;
+}
--- /dev/null
+import { Component } from '@angular/core'
+import { UserRight } from '../../../../../shared'
+import { AuthService } from '@app/core/auth/auth.service'
+
+@Component({
+ templateUrl: './moderation.component.html',
+ styleUrls: [ './moderation.component.scss' ]
+})
+export class ModerationComponent {
+ constructor (private auth: AuthService) {}
+
+ hasVideoAbusesRight () {
+ return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
+ }
+
+ hasVideoBlacklistRight () {
+ return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
+ }
+}
--- /dev/null
+import { Routes } from '@angular/router'
+import { UserRight } from '../../../../../shared'
+import { UserRightGuard } from '@app/core'
+import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list'
+import { VideoBlacklistListComponent } from '@app/+admin/moderation/video-blacklist-list'
+import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
+
+export const ModerationRoutes: Routes = [
+ {
+ path: 'moderation',
+ component: ModerationComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'video-abuses/list',
+ pathMatch: 'full'
+ },
+ {
+ path: 'video-abuses/list',
+ component: VideoAbuseListComponent,
+ canActivate: [ UserRightGuard ],
+ data: {
+ userRight: UserRight.MANAGE_VIDEO_ABUSES,
+ meta: {
+ title: 'Video abuses list'
+ }
+ }
+ },
+ {
+ path: 'video-blacklist/list',
+ component: VideoBlacklistListComponent,
+ canActivate: [ UserRightGuard ],
+ data: {
+ userRight: UserRight.MANAGE_VIDEO_BLACKLIST,
+ meta: {
+ title: 'Blacklisted videos'
+ }
+ }
+ }
+ ]
+ }
+]
--- /dev/null
+export * from './video-abuse-list.component'
+export * from './moderation-comment-modal.component'
--- /dev/null
+<ng-template #modal>
+ <div class="modal-header">
+ <h4 i18n class="modal-title">Moderation comment</h4>
+ <span class="close" aria-hidden="true" (click)="hideModerationCommentModal()"></span>
+ </div>
+
+ <div class="modal-body">
+ <form novalidate [formGroup]="form" (ngSubmit)="banUser()">
+ <div class="form-group">
+ <textarea formControlName="moderationComment" [ngClass]="{ 'input-error': formErrors['moderationComment'] }">
+ </textarea>
+ <div *ngIf="formErrors.moderationComment" class="form-error">
+ {{ formErrors.moderationComment }}
+ </div>
+ </div>
+
+ <div i18n>
+ This comment can only be seen by you or the other moderators.
+ </div>
+
+ <div class="form-group inputs">
+ <span i18n class="action-button action-button-cancel" (click)="hideModerationCommentModal()">Cancel</span>
+
+ <input
+ type="submit" i18n-value value="Update this comment" class="action-button-submit"
+ [disabled]="!form.valid"
+ >
+ </div>
+ </form>
+ </div>
+
+</ng-template>
\ No newline at end of file
--- /dev/null
+@import 'variables';
+@import 'mixins';
+
+textarea {
+ @include peertube-textarea(100%, 100px);
+}
--- /dev/null
+import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
+import { FormValidatorService } from '../../../shared/forms/form-validators/form-validator.service'
+import { VideoAbuse } from '../../../../../../shared/models/videos'
+
+@Component({
+ selector: 'my-moderation-comment-modal',
+ templateUrl: './moderation-comment-modal.component.html',
+ styleUrls: [ './moderation-comment-modal.component.scss' ]
+})
+export class ModerationCommentModalComponent extends FormReactive implements OnInit {
+ @ViewChild('modal') modal: NgbModal
+ @Output() commentUpdated = new EventEmitter<string>()
+
+ private abuseToComment: VideoAbuse
+ private openedModal: NgbModalRef
+
+ constructor (
+ protected formValidatorService: FormValidatorService,
+ private modalService: NgbModal,
+ private notificationsService: NotificationsService,
+ private videoAbuseService: VideoAbuseService,
+ private videoAbuseValidatorsService: VideoAbuseValidatorsService,
+ private i18n: I18n
+ ) {
+ super()
+ }
+
+ ngOnInit () {
+ this.buildForm({
+ moderationComment: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON
+ })
+ }
+
+ openModal (abuseToComment: VideoAbuse) {
+ this.abuseToComment = abuseToComment
+ this.openedModal = this.modalService.open(this.modal)
+
+ this.form.patchValue({
+ moderationComment: this.abuseToComment.moderationComment
+ })
+ }
+
+ hideModerationCommentModal () {
+ this.abuseToComment = undefined
+ this.openedModal.close()
+ this.form.reset()
+ }
+
+ async banUser () {
+ const moderationComment: string = this.form.value['moderationComment']
+
+ this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment })
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Comment updated.')
+ )
+
+ this.commentUpdated.emit(moderationComment)
+ this.hideModerationCommentModal()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
+}
--- /dev/null
+<p-table
+ [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+ [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
+>
+ <ng-template pTemplate="header">
+ <tr>
+ <th style="width: 40px"></th>
+ <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
+ <th i18n>Reason</th>
+ <th i18n>Reporter</th>
+ <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
+ <th i18n>Video</th>
+ <th style="width: 50px;"></th>
+ </tr>
+ </ng-template>
+
+ <ng-template pTemplate="body" let-expanded="expanded" let-videoAbuse>
+ <tr>
+ <td>
+ <span *ngIf="videoAbuse.moderationComment" class="expander" [pRowToggler]="videoAbuse">
+ <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
+ </span>
+ </td>
+
+ <td>
+ <span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span>
+ <span *ngIf="isVideoAbuseRejected(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-remove"></span>
+ </td>
+
+ <td>{{ videoAbuse.reason }}</td>
+
+ <td>
+ <a [href]="videoAbuse.reporterAccount.url" i18n-title title="Go to the account" target="_blank" rel="noopener noreferrer">
+ {{ createByString(videoAbuse.reporterAccount) }}
+ </a>
+ </td>
+
+ <td>{{ videoAbuse.createdAt }}</td>
+
+ <td>
+ <a [href]="getVideoUrl(videoAbuse)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
+ {{ videoAbuse.video.name }}
+ </a>
+ </td>
+
+ <td class="action-cell">
+ <my-action-dropdown i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown>
+ </td>
+ </tr>
+ </ng-template>
+
+ <ng-template pTemplate="rowexpansion" let-videoAbuse>
+ <tr class="moderation-comment">
+ <td colspan="7">
+ <span i18n class="moderation-comment-label">Moderation comment:</span>
+ {{ videoAbuse.moderationComment }}
+ </td>
+ </tr>
+ </ng-template>
+</p-table>
+
+<my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal>
\ No newline at end of file
--- /dev/null
+@import 'variables';
+@import 'mixins';
+
+.moderation-comment-label {
+ font-weight: $font-semibold;
+}
\ No newline at end of file
--- /dev/null
+import { Component, OnInit, ViewChild } from '@angular/core'
+import { Account } from '../../../shared/account/account.model'
+import { NotificationsService } from 'angular2-notifications'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { VideoAbuse, VideoAbuseState } from '../../../../../../shared'
+import { RestPagination, RestTable, VideoAbuseService } from '../../../shared'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { DropdownAction } from '../../../shared/buttons/action-dropdown.component'
+import { ConfirmService } from '../../../core/index'
+import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
+import { Video } from '../../../shared/video/video.model'
+
+@Component({
+ selector: 'my-video-abuse-list',
+ templateUrl: './video-abuse-list.component.html',
+ styleUrls: [ './video-abuse-list.component.scss']
+})
+export class VideoAbuseListComponent extends RestTable implements OnInit {
+ @ViewChild('moderationCommentModal') moderationCommentModal: ModerationCommentModalComponent
+
+ videoAbuses: VideoAbuse[] = []
+ totalRecords = 0
+ rowsPerPage = 10
+ sort: SortMeta = { field: 'createdAt', order: 1 }
+ pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+
+ videoAbuseActions: DropdownAction<VideoAbuse>[] = []
+
+ constructor (
+ private notificationsService: NotificationsService,
+ private videoAbuseService: VideoAbuseService,
+ private confirmService: ConfirmService,
+ private i18n: I18n
+ ) {
+ super()
+
+ this.videoAbuseActions = [
+ {
+ label: this.i18n('Delete'),
+ handler: videoAbuse => this.removeVideoAbuse(videoAbuse)
+ },
+ {
+ label: this.i18n('Update moderation comment'),
+ handler: videoAbuse => this.openModerationCommentModal(videoAbuse)
+ },
+ {
+ label: this.i18n('Mark as accepted'),
+ handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED),
+ isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse)
+ },
+ {
+ label: this.i18n('Mark as rejected'),
+ handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED),
+ isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse)
+ }
+ ]
+ }
+
+ ngOnInit () {
+ this.loadSort()
+ }
+
+ openModerationCommentModal (videoAbuse: VideoAbuse) {
+ this.moderationCommentModal.openModal(videoAbuse)
+ }
+
+ onModerationCommentUpdated () {
+ this.loadData()
+ }
+
+ createByString (account: Account) {
+ return Account.CREATE_BY_STRING(account.name, account.host)
+ }
+
+ isVideoAbuseAccepted (videoAbuse: VideoAbuse) {
+ return videoAbuse.state.id === VideoAbuseState.ACCEPTED
+ }
+
+ isVideoAbuseRejected (videoAbuse: VideoAbuse) {
+ return videoAbuse.state.id === VideoAbuseState.REJECTED
+ }
+
+ getVideoUrl (videoAbuse: VideoAbuse) {
+ return Video.buildClientUrl(videoAbuse.video.uuid)
+ }
+
+ async removeVideoAbuse (videoAbuse: VideoAbuse) {
+ const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete'))
+ if (res === false) return
+
+ this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Abuse deleted.')
+ )
+ this.loadData()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
+ updateVideoAbuseState (videoAbuse: VideoAbuse, state: VideoAbuseState) {
+ this.videoAbuseService.updateVideoAbuse(videoAbuse, { state })
+ .subscribe(
+ () => this.loadData(),
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+
+ }
+
+ protected loadData () {
+ return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort)
+ .subscribe(
+ resultList => {
+ this.videoAbuses = resultList.data
+ this.totalRecords = resultList.total
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+}
--- /dev/null
+export * from './video-blacklist-list.component'
--- /dev/null
+<p-table
+ [value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+ [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
+>
+ <ng-template pTemplate="header">
+ <tr>
+ <th style="width: 40px"></th>
+ <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
+ <th i18n>Sensitive</th>
+ <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
+ <th style="width: 50px;"></th>
+ </tr>
+ </ng-template>
+
+ <ng-template pTemplate="body" let-videoBlacklist let-expanded="expanded">
+ <tr>
+ <td>
+ <span *ngIf="videoBlacklist.reason" class="expander" [pRowToggler]="videoBlacklist">
+ <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
+ </span>
+ </td>
+
+ <td>
+ <a [href]="getVideoUrl(videoBlacklist)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
+ {{ videoBlacklist.video.name }}
+ </a>
+ </td>
+
+ <td>{{ videoBlacklist.video.nsfw }}</td>
+ <td>{{ videoBlacklist.createdAt }}</td>
+
+ <td class="action-cell">
+ <my-action-dropdown i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown>
+ </td>
+ </tr>
+ </ng-template>
+
+ <ng-template pTemplate="rowexpansion" let-videoBlacklist>
+ <tr class="blacklist-reason">
+ <td colspan="7">
+ <span i18n class="blacklist-reason-label">Blacklist reason:</span>
+ {{ videoBlacklist.reason }}
+ </td>
+ </tr>
+ </ng-template>
+</p-table>
+
--- /dev/null
+@import 'variables';
+@import 'mixins';
+
+.blacklist-reason-label {
+ font-weight: $font-semibold;
+}
\ No newline at end of file
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { NotificationsService } from 'angular2-notifications'
+import { ConfirmService } from '../../../core'
+import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
+import { VideoBlacklist } from '../../../../../../shared'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { DropdownAction } from '../../../shared/buttons/action-dropdown.component'
+import { Video } from '../../../shared/video/video.model'
+
+@Component({
+ selector: 'my-video-blacklist-list',
+ templateUrl: './video-blacklist-list.component.html',
+ styleUrls: [ './video-blacklist-list.component.scss' ]
+})
+export class VideoBlacklistListComponent extends RestTable implements OnInit {
+ blacklist: VideoBlacklist[] = []
+ totalRecords = 0
+ rowsPerPage = 10
+ sort: SortMeta = { field: 'createdAt', order: 1 }
+ pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+
+ videoBlacklistActions: DropdownAction<VideoBlacklist>[] = []
+
+ constructor (
+ private notificationsService: NotificationsService,
+ private confirmService: ConfirmService,
+ private videoBlacklistService: VideoBlacklistService,
+ private i18n: I18n
+ ) {
+ super()
+
+ this.videoBlacklistActions = [
+ {
+ label: this.i18n('Unblacklist'),
+ handler: videoBlacklist => this.removeVideoFromBlacklist(videoBlacklist)
+ }
+ ]
+ }
+
+ ngOnInit () {
+ this.loadSort()
+ }
+
+ getVideoUrl (videoBlacklist: VideoBlacklist) {
+ return Video.buildClientUrl(videoBlacklist.video.uuid)
+ }
+
+ async removeVideoFromBlacklist (entry: VideoBlacklist) {
+ const confirmMessage = this.i18n(
+ 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.'
+ )
+
+ const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist'))
+ if (res === false) return
+
+ this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })
+ )
+ this.loadData()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
+ protected loadData () {
+ this.videoBlacklistService.listBlacklist(this.pagination, this.sort)
+ .subscribe(
+ resultList => {
+ this.blacklist = resultList.data
+ this.totalRecords = resultList.total
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+}
+++ /dev/null
-export * from './video-abuse-list'
-export * from './video-abuses.component'
-export * from './video-abuses.routes'
+++ /dev/null
-export * from './video-abuse-list.component'
-export * from './moderation-comment-modal.component'
+++ /dev/null
-<ng-template #modal>
- <div class="modal-header">
- <h4 i18n class="modal-title">Moderation comment</h4>
- <span class="close" aria-hidden="true" (click)="hideModerationCommentModal()"></span>
- </div>
-
- <div class="modal-body">
- <form novalidate [formGroup]="form" (ngSubmit)="banUser()">
- <div class="form-group">
- <textarea formControlName="moderationComment" [ngClass]="{ 'input-error': formErrors['moderationComment'] }">
- </textarea>
- <div *ngIf="formErrors.moderationComment" class="form-error">
- {{ formErrors.moderationComment }}
- </div>
- </div>
-
- <div i18n>
- This comment can only be seen by you or the other moderators.
- </div>
-
- <div class="form-group inputs">
- <span i18n class="action-button action-button-cancel" (click)="hideModerationCommentModal()">Cancel</span>
-
- <input
- type="submit" i18n-value value="Update this comment" class="action-button-submit"
- [disabled]="!form.valid"
- >
- </div>
- </form>
- </div>
-
-</ng-template>
\ No newline at end of file
+++ /dev/null
-@import 'variables';
-@import 'mixins';
-
-textarea {
- @include peertube-textarea(100%, 100px);
-}
+++ /dev/null
-import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
-import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared'
-import { I18n } from '@ngx-translate/i18n-polyfill'
-import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
-import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
-import { VideoAbuse } from '../../../../../../shared/models/videos'
-
-@Component({
- selector: 'my-moderation-comment-modal',
- templateUrl: './moderation-comment-modal.component.html',
- styleUrls: [ './moderation-comment-modal.component.scss' ]
-})
-export class ModerationCommentModalComponent extends FormReactive implements OnInit {
- @ViewChild('modal') modal: NgbModal
- @Output() commentUpdated = new EventEmitter<string>()
-
- private abuseToComment: VideoAbuse
- private openedModal: NgbModalRef
-
- constructor (
- protected formValidatorService: FormValidatorService,
- private modalService: NgbModal,
- private notificationsService: NotificationsService,
- private videoAbuseService: VideoAbuseService,
- private videoAbuseValidatorsService: VideoAbuseValidatorsService,
- private i18n: I18n
- ) {
- super()
- }
-
- ngOnInit () {
- this.buildForm({
- moderationComment: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON
- })
- }
-
- openModal (abuseToComment: VideoAbuse) {
- this.abuseToComment = abuseToComment
- this.openedModal = this.modalService.open(this.modal)
-
- this.form.patchValue({
- moderationComment: this.abuseToComment.moderationComment
- })
- }
-
- hideModerationCommentModal () {
- this.abuseToComment = undefined
- this.openedModal.close()
- this.form.reset()
- }
-
- async banUser () {
- const moderationComment: string = this.form.value['moderationComment']
-
- this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment })
- .subscribe(
- () => {
- this.notificationsService.success(
- this.i18n('Success'),
- this.i18n('Comment updated.')
- )
-
- this.commentUpdated.emit(moderationComment)
- this.hideModerationCommentModal()
- },
-
- err => this.notificationsService.error(this.i18n('Error'), err.message)
- )
- }
-
-}
+++ /dev/null
-<div class="admin-sub-header">
- <div i18n class="form-sub-title">Video abuses list</div>
-</div>
-
-<p-table
- [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
- [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
->
- <ng-template pTemplate="header">
- <tr>
- <th style="width: 40px"></th>
- <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
- <th i18n>Reason</th>
- <th i18n>Reporter</th>
- <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
- <th i18n>Video</th>
- <th style="width: 50px;"></th>
- </tr>
- </ng-template>
-
- <ng-template pTemplate="body" let-expanded="expanded" let-videoAbuse>
- <tr>
- <td>
- <span *ngIf="videoAbuse.moderationComment" class="expander" [pRowToggler]="videoAbuse">
- <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
- </span>
- </td>
-
- <td>
- <span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span>
- <span *ngIf="isVideoAbuseRejected(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-remove"></span>
- </td>
-
- <td>{{ videoAbuse.reason }}</td>
-
- <td>
- <a [href]="videoAbuse.reporterAccount.url" i18n-title title="Go to the account" target="_blank" rel="noopener noreferrer">
- {{ createByString(videoAbuse.reporterAccount) }}
- </a>
- </td>
-
- <td>{{ videoAbuse.createdAt }}</td>
-
- <td>
- <a [href]="getVideoUrl(videoAbuse)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
- {{ videoAbuse.video.name }}
- </a>
- </td>
-
- <td class="action-cell">
- <my-action-dropdown i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown>
- </td>
- </tr>
- </ng-template>
-
- <ng-template pTemplate="rowexpansion" let-videoAbuse>
- <tr class="moderation-comment">
- <td colspan="7">
- <span i18n class="moderation-comment-label">Moderation comment:</span>
- {{ videoAbuse.moderationComment }}
- </td>
- </tr>
- </ng-template>
-</p-table>
-
-<my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal>
\ No newline at end of file
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.moderation-comment-label {
- font-weight: $font-semibold;
-}
\ No newline at end of file
+++ /dev/null
-import { Component, OnInit, ViewChild } from '@angular/core'
-import { Account } from '@app/shared/account/account.model'
-import { NotificationsService } from 'angular2-notifications'
-import { SortMeta } from 'primeng/components/common/sortmeta'
-import { VideoAbuse, VideoAbuseState } from '../../../../../../shared'
-import { RestPagination, RestTable, VideoAbuseService } from '../../../shared'
-import { I18n } from '@ngx-translate/i18n-polyfill'
-import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
-import { ConfirmService } from '@app/core'
-import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
-import { Video } from '@app/shared/video/video.model'
-
-@Component({
- selector: 'my-video-abuse-list',
- templateUrl: './video-abuse-list.component.html',
- styleUrls: [ './video-abuse-list.component.scss']
-})
-export class VideoAbuseListComponent extends RestTable implements OnInit {
- @ViewChild('moderationCommentModal') moderationCommentModal: ModerationCommentModalComponent
-
- videoAbuses: VideoAbuse[] = []
- totalRecords = 0
- rowsPerPage = 10
- sort: SortMeta = { field: 'createdAt', order: 1 }
- pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
-
- videoAbuseActions: DropdownAction<VideoAbuse>[] = []
-
- constructor (
- private notificationsService: NotificationsService,
- private videoAbuseService: VideoAbuseService,
- private confirmService: ConfirmService,
- private i18n: I18n
- ) {
- super()
-
- this.videoAbuseActions = [
- {
- label: this.i18n('Delete'),
- handler: videoAbuse => this.removeVideoAbuse(videoAbuse)
- },
- {
- label: this.i18n('Update moderation comment'),
- handler: videoAbuse => this.openModerationCommentModal(videoAbuse)
- },
- {
- label: this.i18n('Mark as accepted'),
- handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED),
- isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse)
- },
- {
- label: this.i18n('Mark as rejected'),
- handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED),
- isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse)
- }
- ]
- }
-
- ngOnInit () {
- this.loadSort()
- }
-
- openModerationCommentModal (videoAbuse: VideoAbuse) {
- this.moderationCommentModal.openModal(videoAbuse)
- }
-
- onModerationCommentUpdated () {
- this.loadData()
- }
-
- createByString (account: Account) {
- return Account.CREATE_BY_STRING(account.name, account.host)
- }
-
- isVideoAbuseAccepted (videoAbuse: VideoAbuse) {
- return videoAbuse.state.id === VideoAbuseState.ACCEPTED
- }
-
- isVideoAbuseRejected (videoAbuse: VideoAbuse) {
- return videoAbuse.state.id === VideoAbuseState.REJECTED
- }
-
- getVideoUrl (videoAbuse: VideoAbuse) {
- return Video.buildClientUrl(videoAbuse.video.uuid)
- }
-
- async removeVideoAbuse (videoAbuse: VideoAbuse) {
- const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete'))
- if (res === false) return
-
- this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe(
- () => {
- this.notificationsService.success(
- this.i18n('Success'),
- this.i18n('Abuse deleted.')
- )
- this.loadData()
- },
-
- err => this.notificationsService.error(this.i18n('Error'), err.message)
- )
- }
-
- updateVideoAbuseState (videoAbuse: VideoAbuse, state: VideoAbuseState) {
- this.videoAbuseService.updateVideoAbuse(videoAbuse, { state })
- .subscribe(
- () => this.loadData(),
-
- err => this.notificationsService.error(this.i18n('Error'), err.message)
- )
-
- }
-
- protected loadData () {
- return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort)
- .subscribe(
- resultList => {
- this.videoAbuses = resultList.data
- this.totalRecords = resultList.total
- },
-
- err => this.notificationsService.error(this.i18n('Error'), err.message)
- )
- }
-}
+++ /dev/null
-import { Component } from '@angular/core'
-
-@Component({
- template: '<router-outlet></router-outlet>'
-})
-
-export class VideoAbusesComponent {
-}
+++ /dev/null
-import { Routes } from '@angular/router'
-
-import { UserRightGuard } from '../../core'
-import { UserRight } from '../../../../../shared'
-import { VideoAbusesComponent } from './video-abuses.component'
-import { VideoAbuseListComponent } from './video-abuse-list'
-
-export const VideoAbusesRoutes: Routes = [
- {
- path: 'video-abuses',
- component: VideoAbusesComponent,
- canActivate: [ UserRightGuard ],
- data: {
- userRight: UserRight.MANAGE_VIDEO_ABUSES
- },
- children: [
- {
- path: '',
- redirectTo: 'list',
- pathMatch: 'full'
- },
- {
- path: 'list',
- component: VideoAbuseListComponent,
- data: {
- meta: {
- title: 'Video abuses list'
- }
- }
- }
- ]
- }
-]
+++ /dev/null
-export * from './video-blacklist-list'
-export * from './video-blacklist.component'
-export * from './video-blacklist.routes'
+++ /dev/null
-export * from './video-blacklist-list.component'
+++ /dev/null
-<div class="admin-sub-header">
- <div i18n class="form-sub-title">Blacklisted videos</div>
-</div>
-
-<p-table
- [value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
- [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
->
- <ng-template pTemplate="header">
- <tr>
- <th style="width: 40px"></th>
- <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
- <th i18n>Sensitive</th>
- <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
- <th style="width: 50px;"></th>
- </tr>
- </ng-template>
-
- <ng-template pTemplate="body" let-videoBlacklist let-expanded="expanded">
- <tr>
- <td>
- <span *ngIf="videoBlacklist.reason" class="expander" [pRowToggler]="videoBlacklist">
- <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
- </span>
- </td>
-
- <td>
- <a [href]="getVideoUrl(videoBlacklist)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
- {{ videoBlacklist.video.name }}
- </a>
- </td>
-
- <td>{{ videoBlacklist.video.nsfw }}</td>
- <td>{{ videoBlacklist.createdAt }}</td>
-
- <td class="action-cell">
- <my-action-dropdown i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown>
- </td>
- </tr>
- </ng-template>
-
- <ng-template pTemplate="rowexpansion" let-videoBlacklist>
- <tr class="blacklist-reason">
- <td colspan="6">
- <span i18n class="blacklist-reason-label">Blacklist reason:</span>
- {{ videoBlacklist.reason }}
- </td>
- </tr>
- </ng-template>
-</p-table>
-
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.blacklist-reason-label {
- font-weight: $font-semibold;
-}
\ No newline at end of file
+++ /dev/null
-import { Component, OnInit } from '@angular/core'
-import { SortMeta } from 'primeng/components/common/sortmeta'
-import { NotificationsService } from 'angular2-notifications'
-import { ConfirmService } from '../../../core'
-import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
-import { VideoBlacklist } from '../../../../../../shared'
-import { I18n } from '@ngx-translate/i18n-polyfill'
-import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
-import { Video } from '@app/shared/video/video.model'
-
-@Component({
- selector: 'my-video-blacklist-list',
- templateUrl: './video-blacklist-list.component.html',
- styleUrls: [ './video-blacklist-list.component.scss' ]
-})
-export class VideoBlacklistListComponent extends RestTable implements OnInit {
- blacklist: VideoBlacklist[] = []
- totalRecords = 0
- rowsPerPage = 10
- sort: SortMeta = { field: 'createdAt', order: 1 }
- pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
-
- videoBlacklistActions: DropdownAction<VideoBlacklist>[] = []
-
- constructor (
- private notificationsService: NotificationsService,
- private confirmService: ConfirmService,
- private videoBlacklistService: VideoBlacklistService,
- private i18n: I18n
- ) {
- super()
-
- this.videoBlacklistActions = [
- {
- label: this.i18n('Unblacklist'),
- handler: videoBlacklist => this.removeVideoFromBlacklist(videoBlacklist)
- }
- ]
- }
-
- ngOnInit () {
- this.loadSort()
- }
-
- getVideoUrl (videoBlacklist: VideoBlacklist) {
- return Video.buildClientUrl(videoBlacklist.video.uuid)
- }
-
- async removeVideoFromBlacklist (entry: VideoBlacklist) {
- const confirmMessage = this.i18n(
- 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.'
- )
-
- const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist'))
- if (res === false) return
-
- this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe(
- () => {
- this.notificationsService.success(
- this.i18n('Success'),
- this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })
- )
- this.loadData()
- },
-
- err => this.notificationsService.error(this.i18n('Error'), err.message)
- )
- }
-
- protected loadData () {
- this.videoBlacklistService.listBlacklist(this.pagination, this.sort)
- .subscribe(
- resultList => {
- this.blacklist = resultList.data
- this.totalRecords = resultList.total
- },
-
- err => this.notificationsService.error(this.i18n('Error'), err.message)
- )
- }
-}
+++ /dev/null
-import { Component } from '@angular/core'
-
-@Component({
- template: '<router-outlet></router-outlet>'
-})
-export class VideoBlacklistComponent {
-}
+++ /dev/null
-import { Routes } from '@angular/router'
-
-import { UserRightGuard } from '../../core'
-import { UserRight } from '../../../../../shared'
-import { VideoBlacklistComponent } from './video-blacklist.component'
-import { VideoBlacklistListComponent } from './video-blacklist-list'
-
-export const VideoBlacklistRoutes: Routes = [
- {
- path: 'video-blacklist',
- component: VideoBlacklistComponent,
- canActivate: [ UserRightGuard ],
- data: {
- userRight: UserRight.MANAGE_VIDEO_BLACKLIST
- },
- children: [
- {
- path: '',
- redirectTo: 'list',
- pathMatch: 'full'
- },
- {
- path: 'list',
- component: VideoBlacklistListComponent,
- data: {
- meta: {
- title: 'Blacklisted videos'
- }
- }
- }
- ]
- }
-]