Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

predefined report reasons & improved reporter UI #2842

Merged
merged 9 commits into from
Jun 22, 2020
14 changes: 14 additions & 0 deletions client/src/app/+admin/moderation/moderation.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@
}
}

p-calendar {
display: block;

::ng-deep {
.ui-widget-content {
min-width: 400px;
}

input {
@include peertube-input-text(100%);
}
}
}

.screenratio {
div {
@include miniature-thumbnail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@
<span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.reasonHtml"></span>
</div>

<div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
<span class="col-3"></span>
<span class="col-9">
<a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" *ngFor="let reason of getPredefinedReasons()">
<div>{{ reason.label }}</div>
</a>
</span>
</div>

<div *ngIf="videoAbuse.startAt" class="mt-2 d-flex">
<span class="col-3 moderation-expanded-label" i18n>Reported part</span>
<span class="col-9">
{{ startAt }}<ng-container *ngIf="videoAbuse.endAt"> - {{ endAt }}</ng-container>
</span>
</div>

<div class="mt-3 d-flex" *ngIf="videoAbuse.moderationComment">
<span class="col-3 moderation-expanded-label" i18n>Note</span>
<span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.moderationCommentHtml"></span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component, Input } from '@angular/core'
import { Account } from '@app/shared/account/account.model'
import { Actor } from '@app/shared/actor/actor.model'
import { VideoAbusePredefinedReasonsString } from '../../../../../../shared/models/videos/abuse/video-abuse-reason.model'
import { ProcessedVideoAbuse } from './video-abuse-list.component'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { durationToString } from '@app/shared/misc/utils'

@Component({
selector: 'my-video-abuse-details',
Expand All @@ -11,6 +13,39 @@ import { ProcessedVideoAbuse } from './video-abuse-list.component'
export class VideoAbuseDetailsComponent {
@Input() videoAbuse: ProcessedVideoAbuse

private predefinedReasonsTranslations: { [key in VideoAbusePredefinedReasonsString]: string }

constructor (
private i18n: I18n
) {
this.predefinedReasonsTranslations = {
violentOrRepulsive: this.i18n('Violent or Repulsive'),
hatefulOrAbusive: this.i18n('Hateful or Abusive'),
spamOrMisleading: this.i18n('Spam or Misleading'),
privacy: this.i18n('Privacy'),
rights: this.i18n('Rights'),
serverRules: this.i18n('Server rules'),
thumbnails: this.i18n('Thumbnails'),
captions: this.i18n('Captions')
}
}

get startAt () {
return durationToString(this.videoAbuse.startAt)
}

get endAt () {
return durationToString(this.videoAbuse.endAt)
}

getPredefinedReasons () {
if (!this.videoAbuse.predefinedReasons) return []
return this.videoAbuse.predefinedReasons.map(r => ({
id: r,
label: this.predefinedReasonsTranslations[r]
}))
}

switchToDefaultAvatar ($event: Event) {
($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { ModerationCommentModalComponent } from './moderation-comment-modal.comp
import { Video } from '../../../shared/video/video.model'
import { MarkdownService } from '@app/shared/renderer'
import { Actor } from '@app/shared/actor/actor.model'
import { buildVideoLink, buildVideoEmbed } from 'src/assets/player/utils'
import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
import { DomSanitizer } from '@angular/platform-browser'
import { BlocklistService } from '@app/shared/blocklist'
import { VideoService } from '@app/shared/video/video.service'
import { ActivatedRoute, Params, Router } from '@angular/router'
import { filter } from 'rxjs/operators'
import { environment } from 'src/environments/environment'

export type ProcessedVideoAbuse = VideoAbuse & {
moderationCommentHtml?: string,
Expand Down Expand Up @@ -259,12 +259,15 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
}

getVideoEmbed (videoAbuse: VideoAbuse) {
const absoluteAPIUrl = getAbsoluteAPIUrl()
const embedUrl = buildVideoLink({
baseUrl: absoluteAPIUrl + '/videos/embed/' + videoAbuse.video.uuid,
warningTitle: false
rigelk marked this conversation as resolved.
Show resolved Hide resolved
})
return buildVideoEmbed(embedUrl)
return buildVideoEmbed(
buildVideoLink({
baseUrl: `${environment.embedUrl}/videos/embed/${videoAbuse.video.uuid}`,
title: false,
warningTitle: false,
startTime: videoAbuse.startAt,
stopTime: videoAbuse.endAt
})
)
}

switchToDefaultAvatar ($event: Event) {
Expand Down
6 changes: 3 additions & 3 deletions client/src/app/shared/rest/rest.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class RestService {
addObjectParams (params: HttpParams, object: { [ name: string ]: any }) {
for (const name of Object.keys(object)) {
const value = object[name]
if (!value) continue
if (value === undefined || value === null) continue

if (Array.isArray(value) && value.length !== 0) {
for (const v of value) params = params.append(name, v)
Expand Down Expand Up @@ -93,7 +93,7 @@ export class RestService {

return t
})
.filter(t => !!t)
.filter(t => !!t || t === 0)

if (matchedTokens.length === 0) continue

Expand All @@ -103,7 +103,7 @@ export class RestService {
}

return {
search: searchTokens.join(' '),
search: searchTokens.join(' ') || undefined,

...additionalFilters
}
Expand Down
13 changes: 8 additions & 5 deletions client/src/app/shared/video-abuse/video-abuse.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { SortMeta } from 'primeng/api'
import { Observable } from 'rxjs'
import { ResultList, VideoAbuse, VideoAbuseUpdate, VideoAbuseState } from '../../../../../shared'
import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '../../../../../shared'
import { environment } from '../../../environments/environment'
import { RestExtractor, RestPagination, RestService } from '../rest'
import { omit } from 'lodash-es'

@Injectable()
export class VideoAbuseService {
Expand Down Expand Up @@ -51,7 +52,8 @@ export class VideoAbuseService {
}
},
searchReporter: { prefix: 'reporter:' },
searchReportee: { prefix: 'reportee:' }
searchReportee: { prefix: 'reportee:' },
predefinedReason: { prefix: 'tag:' }
})

params = this.restService.addObjectParams(params, filters)
Expand All @@ -63,9 +65,10 @@ export class VideoAbuseService {
)
}

reportVideo (id: number, reason: string) {
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
const body = { reason }
reportVideo (parameters: { id: number } & VideoAbuseCreate) {
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + parameters.id + '/abuse'

const body = omit(parameters, [ 'id' ])

return this.authHttp.post(url, body)
.pipe(
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/shared/video/modals/video-block.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<ng-template #modal>
<div class="modal-header">
<h4 i18n class="modal-title">Blocklist video</h4>
<h4 i18n class="modal-title">Block video "{{ video.name }}"</h4>
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
</div>

Expand All @@ -9,7 +9,7 @@ <h4 i18n class="modal-title">Blocklist video</h4>
<form novalidate [formGroup]="form" (ngSubmit)="block()">
<div class="form-group">
<textarea
i18n-placeholder placeholder="Reason..." formControlName="reason"
i18n-placeholder placeholder="Please describe the reason..." formControlName="reason"
[ngClass]="{ 'input-error': formErrors['reason'] }" class="form-control"
></textarea>
<div *ngIf="formErrors.reason" class="form-error">
Expand Down
105 changes: 82 additions & 23 deletions client/src/app/shared/video/modals/video-report.component.html
Original file line number Diff line number Diff line change
@@ -1,38 +1,97 @@
<ng-template #modal>
<div class="modal-header">
<h4 i18n class="modal-title">Report video</h4>
<h4 i18n class="modal-title">Report video "{{ video.name }}"</h4>
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
</div>

<div class="modal-body">
<form novalidate [formGroup]="form" (ngSubmit)="report()">

<div i18n class="information">
Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemoteVideo()"> and will be forwarded to the video origin ({{ originHost }}) too</ng-container>.
</div>
<div class="row">
<div class="col-5 form-group">

<label i18n for="reportPredefinedReasons">What is the issue?</label>

<div class="ml-2 mt-2 d-flex flex-column">
<ng-container formGroupName="predefinedReasons">
<div class="form-group" *ngFor="let reason of predefinedReasons">
<my-peertube-checkbox formControlName="{{reason.id}}" labelText="{{reason.label}}">
<ng-template *ngIf="reason.help" ptTemplate="help">
<div [innerHTML]="reason.help"></div>
</ng-template>
<ng-container *ngIf="reason.description" ngProjectAs="description">
<div [innerHTML]="reason.description"></div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</div>

<form novalidate [formGroup]="form" (ngSubmit)="report()">
<div class="form-group">
<textarea
i18n-placeholder placeholder="Reason..." formControlName="reason"
[ngClass]="{ 'input-error': formErrors['reason'] }" class="form-control"
></textarea>
<div *ngIf="formErrors.reason" class="form-error">
{{ formErrors.reason }}
</div>
</div>

<div class="form-group inputs">
<input
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
(click)="hide()" (key.enter)="hide()"
>
<div class="col-7">
<div class="row justify-content-center">
<div class="col-12 col-lg-9 mb-2">
<div class="screenratio">
<div [innerHTML]="embedHtml"></div>
</div>
</div>
</div>

<div class="mb-1 start-at" formGroupName="timestamp">
<my-peertube-checkbox
formControlName="hasStart"
i18n-labelText labelText="Start at"
></my-peertube-checkbox>

<my-timestamp-input
[timestamp]="timestamp.startAt"
[maxTimestamp]="video.duration"
formControlName="startAt"
inputName="startAt"
>
</my-timestamp-input>
</div>

<div class="mb-3 stop-at" formGroupName="timestamp" *ngIf="timestamp.hasStart">
<my-peertube-checkbox
formControlName="hasEnd"
i18n-labelText labelText="Stop at"
></my-peertube-checkbox>

<input
type="submit" i18n-value value="Submit" class="action-button-submit"
[disabled]="!form.valid"
>
<my-timestamp-input
[timestamp]="timestamp.endAt"
[maxTimestamp]="video.duration"
formControlName="endAt"
inputName="endAt"
>
</my-timestamp-input>
</div>

<div i18n class="information">
Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemoteVideo()"> and will be forwarded to the video origin ({{ originHost }}) too</ng-container>.
</div>

<div class="form-group">
<textarea
i18n-placeholder placeholder="Please describe the issue..." formControlName="reason" ngbAutofocus
[ngClass]="{ 'input-error': formErrors['reason'] }" class="form-control"
></textarea>
<div *ngIf="formErrors.reason" class="form-error">
{{ formErrors.reason }}
</div>
</div>
</div>
</form>
</div>

<div class="form-group inputs">
<input
type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
(click)="hide()" (key.enter)="hide()"
>
<input type="submit" i18n-value value="Submit" class="action-button-submit" [disabled]="!form.valid">
</div>

</form>
</div>
</ng-template>
17 changes: 17 additions & 0 deletions client/src/app/shared/video/modals/video-report.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,20 @@
textarea {
@include peertube-textarea(100%, 100px);
}

.start-at,
.stop-at {
width: 300px;
display: flex;
align-items: center;

my-timestamp-input {
margin-left: 10px;
}
}

.screenratio {
@include large-screen-ratio($selector: 'div, ::ng-deep iframe') {
left: 0;
};
}
Loading