Increase abuse length to 3000

And correctly handle new lines
pull/1567/head
Chocobozzz 2019-01-14 16:48:38 +01:00
parent 9a39392a7e
commit 1506307f2f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
28 changed files with 136 additions and 80 deletions

View File

@ -1,9 +1,9 @@
import { Component, OnInit, ViewChild } from '@angular/core'
import { Notifier, ServerService } from '@app/core'
import { MarkdownService } from '@app/videos/shared'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
import { InstanceService } from '@app/shared/instance/instance.service'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-about-instance',

View File

@ -1,9 +1,9 @@
import { Component, OnInit, OnDestroy } from '@angular/core'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { Account } from '@app/shared/account/account.model'
import { AccountService } from '@app/shared/account/account.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { Subscription } from 'rxjs'
import { MarkdownService } from '@app/videos/shared'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-account-about',

View File

@ -10,6 +10,7 @@
font-weight: $font-semibold;
min-width: 200px;
display: inline-block;
vertical-align: top;
}
.moderation-expanded-text {

View File

@ -51,11 +51,11 @@
<td class="moderation-expanded" colspan="6">
<div>
<span i18n class="moderation-expanded-label">Reason:</span>
<span class="moderation-expanded-text">{{ videoAbuse.reason }}</span>
<span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.reason)"></span>
</div>
<div *ngIf="videoAbuse.moderationComment">
<span i18n class="moderation-expanded-label">Moderation comment:</span>
<span class="moderation-expanded-text">{{ videoAbuse.moderationComment }}</span>
<span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.moderationComment)"></span>
</div>
</td>
</tr>

View File

@ -9,6 +9,7 @@ import { DropdownAction } from '../../../shared/buttons/action-dropdown.componen
import { ConfirmService } from '../../../core/index'
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
import { Video } from '../../../shared/video/video.model'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-video-abuse-list',
@ -30,7 +31,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
private notifier: Notifier,
private videoAbuseService: VideoAbuseService,
private confirmService: ConfirmService,
private i18n: I18n
private i18n: I18n,
private markdownRenderer: MarkdownService
) {
super()
@ -108,6 +110,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
}
toHtml (text: string) {
return this.markdownRenderer.textMarkdownToHTML(text)
}
protected loadData () {
return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort)
.subscribe(

View File

@ -41,7 +41,7 @@
<tr>
<td class="moderation-expanded" colspan="6">
<span i18n class="moderation-expanded-label">Blacklist reason:</span>
<span class="moderation-expanded-text">{{ videoBlacklist.reason }}</span>
<span class="moderation-expanded-text" [innerHTML]="toHtml(videoBlacklist.reason)"></span>
</td>
</tr>
</ng-template>

View File

@ -7,6 +7,7 @@ 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'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-video-blacklist-list',
@ -26,6 +27,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
private notifier: Notifier,
private confirmService: ConfirmService,
private videoBlacklistService: VideoBlacklistService,
private markdownRenderer: MarkdownService,
private i18n: I18n
) {
super()
@ -52,6 +54,10 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
return this.i18n('no')
}
toHtml (text: string) {
return this.markdownRenderer.textMarkdownToHTML(text)
}
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.'

View File

@ -3,7 +3,7 @@ import { VideoChannelService } from '@app/shared/video-channel/video-channel.ser
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { Subscription } from 'rxjs'
import { MarkdownService } from '@app/videos/shared'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-video-channel-about',

View File

@ -10,20 +10,20 @@ export class VideoAbuseValidatorsService {
constructor (private i18n: I18n) {
this.VIDEO_ABUSE_REASON = {
VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
MESSAGES: {
'required': this.i18n('Report reason is required.'),
'minlength': this.i18n('Report reason must be at least 2 characters long.'),
'maxlength': this.i18n('Report reason cannot be more than 300 characters long.')
'maxlength': this.i18n('Report reason cannot be more than 3000 characters long.')
}
}
this.VIDEO_ABUSE_MODERATION_COMMENT = {
VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
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.')
'maxlength': this.i18n('Moderation comment cannot be more than 3000 characters long.')
}
}
}

View File

@ -1,10 +1,10 @@
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { MarkdownService } from '@app/videos/shared'
import { Subject } from 'rxjs'
import truncate from 'lodash-es/truncate'
import { ScreenService } from '@app/shared/misc/screen.service'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-markdown-textarea',

View File

@ -1,6 +1,6 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core'
import { MarkdownService } from '@app/videos/shared'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-help',

View File

@ -102,12 +102,18 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) {
return fd
}
function lineFeedToHtml (obj: any, keyToNormalize: string) {
function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
return immutableAssign(obj, {
[keyToNormalize]: obj[keyToNormalize].replace(/\r?\n|\r/g, '<br />')
[keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
})
}
function lineFeedToHtml (text: string) {
if (!text) return text
return text.replace(/\r?\n|\r/g, '<br />')
}
function removeElementFromArray <T> (arr: T[], elem: T) {
const index = arr.indexOf(elem)
if (index !== -1) arr.splice(index, 1)
@ -131,6 +137,7 @@ function scrollToTop () {
export {
sortBy,
durationToString,
lineFeedToHtml,
objectToUrlEncoded,
getParameterByName,
populateAsyncUserVideoChannels,
@ -138,7 +145,7 @@ export {
dateToHuman,
immutableAssign,
objectToFormData,
lineFeedToHtml,
objectLineFeedToHtml,
removeElementFromArray,
scrollToTop
}

View File

@ -0,0 +1,35 @@
import { Injectable } from '@angular/core'
import { LinkifierService } from '@app/shared/renderer/linkifier.service'
import * as sanitizeHtml from 'sanitize-html'
@Injectable()
export class HtmlRendererService {
constructor (private linkifier: LinkifierService) {
}
toSafeHtml (text: string) {
// Convert possible markdown to html
const html = this.linkifier.linkify(text)
return sanitizeHtml(html, {
allowedTags: [ 'a', 'p', 'span', 'br' ],
allowedSchemes: [ 'http', 'https' ],
allowedAttributes: {
'a': [ 'href', 'class', 'target' ]
},
transformTags: {
a: (tagName, attribs) => {
return {
tagName,
attribs: Object.assign(attribs, {
target: '_blank',
rel: 'noopener noreferrer'
})
}
}
}
})
}
}

View File

@ -0,0 +1,3 @@
export * from './html-renderer.service'
export * from './linkifier.service'
export * from './markdown.service'

View File

@ -6,7 +6,6 @@ import { RouterModule } from '@angular/router'
import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
import { HelpComponent } from '@app/shared/misc/help.component'
import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
import { MarkdownService } from '@app/videos/shared'
import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
@ -34,10 +33,10 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import {
CustomConfigValidatorsService,
InstanceValidatorsService,
LoginValidatorsService,
ReactiveFileComponent,
ResetPasswordValidatorsService,
InstanceValidatorsService,
TextareaAutoResizeDirective,
UserValidatorsService,
VideoAbuseValidatorsService,
@ -67,6 +66,7 @@ import { UserHistoryService } from '@app/shared/users/user-history.service'
import { UserNotificationService } from '@app/shared/users/user-notification.service'
import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component'
import { InstanceService } from '@app/shared/instance/instance.service'
import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer'
@NgModule({
imports: [
@ -167,7 +167,6 @@ import { InstanceService } from '@app/shared/instance/instance.service'
UserService,
VideoService,
AccountService,
MarkdownService,
VideoChannelService,
VideoCaptionService,
VideoImportService,
@ -192,6 +191,10 @@ import { InstanceService } from '@app/shared/instance/instance.service'
UserHistoryService,
InstanceService,
MarkdownService,
LinkifierService,
HtmlRendererService,
I18nPrimengCalendarService,
ScreenService,

View File

@ -32,9 +32,7 @@ export class VideoAbuseService {
reportVideo (id: number, reason: string) {
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
const body = {
reason
}
const body = { reason }
return this.authHttp.post(url, body)
.pipe(

View File

@ -1,11 +1,10 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
import * as sanitizeHtml from 'sanitize-html'
import { UserRight } from '../../../../../../shared/models/users'
import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model'
import { AuthService } from '../../../core/auth'
import { Video } from '../../../shared/video/video.model'
import { VideoComment } from './video-comment.model'
import { HtmlRendererService } from '@app/shared/renderer'
@Component({
selector: 'my-video-comment',
@ -29,7 +28,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
newParentComments: VideoComment[] = []
constructor (
private linkifierService: LinkifierService,
private htmlRenderer: HtmlRendererService,
private authService: AuthService
) {}
@ -87,27 +86,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
}
private init () {
// Convert possible markdown to html
const html = this.linkifierService.linkify(this.comment.text)
this.sanitizedCommentHTML = sanitizeHtml(html, {
allowedTags: [ 'a', 'p', 'span', 'br' ],
allowedSchemes: [ 'http', 'https' ],
allowedAttributes: {
'a': [ 'href', 'class', 'target' ]
},
transformTags: {
a: (tagName, attribs) => {
return {
tagName,
attribs: Object.assign(attribs, {
target: '_blank',
rel: 'noopener noreferrer'
})
}
}
}
})
this.sanitizedCommentHTML = this.htmlRenderer.toSafeHtml(this.comment.text)
this.newParentComments = this.parentComments.concat([ this.comment ])
}

View File

@ -1,7 +1,7 @@
import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { lineFeedToHtml } from '@app/shared/misc/utils'
import { objectLineFeedToHtml } from '@app/shared/misc/utils'
import { Observable } from 'rxjs'
import { ResultList, FeedFormat } from '../../../../../../shared/models'
import {
@ -28,7 +28,7 @@ export class VideoCommentService {
addCommentThread (videoId: number | string, comment: VideoCommentCreate) {
const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads'
const normalizedComment = lineFeedToHtml(comment, 'text')
const normalizedComment = objectLineFeedToHtml(comment, 'text')
return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment)
.pipe(
@ -39,7 +39,7 @@ export class VideoCommentService {
addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) {
const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId
const normalizedComment = lineFeedToHtml(comment, 'text')
const normalizedComment = objectLineFeedToHtml(comment, 'text')
return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment)
.pipe(

View File

@ -1,8 +1,7 @@
import { Component, Input, ViewChild } from '@angular/core'
import { MarkdownService } from '@app/videos/shared'
import { VideoDetails } from '../../../shared/video/video-details.model'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-video-support',

View File

@ -19,7 +19,6 @@ import { AuthService, ConfirmService } from '../../core'
import { RestExtractor, VideoBlacklistService } from '../../shared'
import { VideoDetails } from '../../shared/video/video-details.model'
import { VideoService } from '../../shared/video/video.service'
import { MarkdownService } from '../shared'
import { VideoDownloadComponent } from './modal/video-download.component'
import { VideoReportComponent } from './modal/video-report.component'
import { VideoShareComponent } from './modal/video-share.component'
@ -30,6 +29,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
import { environment } from '../../../environments/environment'
import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
import { VideoCaptionService } from '@app/shared/video-caption'
import { MarkdownService } from '@app/shared/renderer'
@Component({
selector: 'my-video-watch',

View File

@ -1,9 +1,7 @@
import { NgModule } from '@angular/core'
import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
import { ClipboardModule } from 'ngx-clipboard'
import { SharedModule } from '../../shared'
import { MarkdownService } from '../shared'
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
import { VideoCommentComponent } from './comment/video-comment.component'
import { VideoCommentService } from './comment/video-comment.service'
@ -46,8 +44,6 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio
],
providers: [
MarkdownService,
LinkifierService,
VideoCommentService
]
})

View File

@ -1 +0,0 @@
export * from './markdown.service'

View File

@ -16,7 +16,7 @@ let config: IConfig = require('config')
// ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 320
const LAST_MIGRATION_VERSION = 325
// ---------------------------------------------------------------------------
@ -316,8 +316,8 @@ let CONSTRAINTS_FIELDS = {
BLOCKED_REASON: { min: 3, max: 250 } // Length
},
VIDEO_ABUSES: {
REASON: { min: 2, max: 300 }, // Length
MODERATION_COMMENT: { min: 2, max: 300 } // Length
REASON: { min: 2, max: 3000 }, // Length
MODERATION_COMMENT: { min: 2, max: 3000 } // Length
},
VIDEO_BLACKLIST: {
REASON: { min: 2, max: 300 } // Length

View File

@ -0,0 +1,37 @@
import * as Sequelize from 'sequelize'
async function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
{
const data = {
type: Sequelize.STRING(3000),
allowNull: false,
defaultValue: null
}
await utils.queryInterface.changeColumn('videoAbuse', 'reason', data)
}
{
const data = {
type: Sequelize.STRING(3000),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.changeColumn('videoAbuse', 'moderationComment', data)
}
}
function down (options) {
throw new Error('Not implemented.')
}
export {
up,
down
}

View File

@ -1,17 +1,4 @@
import {
AfterCreate,
AllowNull,
BelongsTo,
Column,
CreatedAt,
DataType,
Default,
ForeignKey,
Is,
Model,
Table,
UpdatedAt
} from 'sequelize-typescript'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
import { VideoAbuse } from '../../../shared/models/videos'
import {
@ -19,7 +6,6 @@ import {
isVideoAbuseReasonValid,
isVideoAbuseStateValid
} from '../../helpers/custom-validators/video-abuses'
import { Emailer } from '../../lib/emailer'
import { AccountModel } from '../account/account'
import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
@ -40,8 +26,9 @@ import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers'
export class VideoAbuseModel extends Model<VideoAbuseModel> {
@AllowNull(false)
@Default(null)
@Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason'))
@Column
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.REASON.max))
reason: string
@AllowNull(false)

View File

@ -113,8 +113,8 @@ describe('Test video abuses API validators', function () {
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a reason too big', async function () {
const fields = { reason: 'super'.repeat(61) }
it('Should fail with a too big reason', async function () {
const fields = { reason: 'super'.repeat(605) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
@ -154,7 +154,7 @@ describe('Test video abuses API validators', function () {
})
it('Should fail with a bad moderation comment', async function () {
const body = { moderationComment: 'b'.repeat(305) }
const body = { moderationComment: 'b'.repeat(3001) }
await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body, 400)
})