Add ability to delete and update abuse on client
This commit is contained in:
parent
7f7680641b
commit
efc9e8450a
|
@ -11,7 +11,7 @@ import { JobsComponent } from './jobs/job.component'
|
|||
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 { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses'
|
||||
import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses'
|
||||
import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
|
||||
import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
|
||||
|
||||
|
@ -41,6 +41,7 @@ import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-moda
|
|||
|
||||
VideoAbusesComponent,
|
||||
VideoAbuseListComponent,
|
||||
ModerationCommentModalComponent,
|
||||
|
||||
JobsComponent,
|
||||
JobsListComponent,
|
||||
|
|
|
@ -5,12 +5,6 @@
|
|||
@include create-button('../../../../assets/images/global/add.svg');
|
||||
}
|
||||
|
||||
my-action-dropdown /deep/ .icon {
|
||||
&.icon-ban {
|
||||
background-image: url('../../../../assets/images/global/edit-black.svg');
|
||||
}
|
||||
}
|
||||
|
||||
tr.banned {
|
||||
color: red;
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './video-abuse-list.component'
|
||||
export * from './moderation-comment-modal.component'
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<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>
|
|
@ -0,0 +1,6 @@
|
|||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(100%, 100px);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -4,31 +4,63 @@
|
|||
|
||||
<p-table
|
||||
[value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
|
||||
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
|
||||
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
|
||||
>
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 40px"></th>
|
||||
<th i18n style="width: 80px;">State</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-videoAbuse>
|
||||
<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]="videoAbuse.video.url" 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>
|
|
@ -1,6 +1,6 @@
|
|||
/deep/ a {
|
||||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
&, &:hover, &:active, &:focus {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
.moderation-comment-label {
|
||||
font-weight: $font-semibold;
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
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 } from '../../../../../../shared'
|
||||
|
||||
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'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-abuse-list',
|
||||
|
@ -13,28 +15,97 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
|||
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
|
||||
}
|
||||
|
||||
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(
|
||||
|
|
|
@ -6,6 +6,7 @@ import { BuildFormValidator } from '@app/shared'
|
|||
@Injectable()
|
||||
export class VideoAbuseValidatorsService {
|
||||
readonly VIDEO_ABUSE_REASON: BuildFormValidator
|
||||
readonly VIDEO_ABUSE_MODERATION_COMMENT: BuildFormValidator
|
||||
|
||||
constructor (private i18n: I18n) {
|
||||
this.VIDEO_ABUSE_REASON = {
|
||||
|
@ -16,5 +17,14 @@ export class VideoAbuseValidatorsService {
|
|||
'maxlength': this.i18n('Report reason cannot be more than 300 characters long.')
|
||||
}
|
||||
}
|
||||
|
||||
this.VIDEO_ABUSE_MODERATION_COMMENT = {
|
||||
VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
|
||||
MESSAGES: {
|
||||
'required': this.i18n('Moderation comment is required.'),
|
||||
'minlength': this.i18n('Moderation comment must be at least 2 characters long.'),
|
||||
'maxlength': this.i18n('Moderation comment cannot be more than 300 characters long.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
|
|||
import { Injectable } from '@angular/core'
|
||||
import { SortMeta } from 'primeng/components/common/sortmeta'
|
||||
import { Observable } from 'rxjs'
|
||||
import { ResultList, VideoAbuse } from '../../../../../shared'
|
||||
import { ResultList, VideoAbuse, VideoAbuseUpdate } from '../../../../../shared'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { RestExtractor, RestPagination, RestService } from '../rest'
|
||||
|
||||
|
@ -42,4 +42,23 @@ export class VideoAbuseService {
|
|||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
updateVideoAbuse (videoAbuse: VideoAbuse, abuseUpdate: VideoAbuseUpdate) {
|
||||
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id
|
||||
|
||||
return this.authHttp.put(url, abuseUpdate)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
removeVideoAbuse (videoAbuse: VideoAbuse) {
|
||||
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id
|
||||
|
||||
return this.authHttp.delete(url)
|
||||
.pipe(
|
||||
map(this.restExtractor.extractDataBool),
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
@import 'mixins';
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(100%, 60px);
|
||||
@include peertube-textarea(100%, 100px);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ export * from './user-video-rate.type'
|
|||
export * from './video-abuse-state.model'
|
||||
export * from './video-abuse-create.model'
|
||||
export * from './video-abuse.model'
|
||||
export * from './video-abuse-update.model'
|
||||
export * from './video-blacklist.model'
|
||||
export * from './video-channel-create.model'
|
||||
export * from './video-channel-update.model'
|
||||
|
|
Loading…
Reference in New Issue