From cfde28bac33c3644e1b6218eb471b675a37def60 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 9 Jul 2020 15:54:24 +0200 Subject: Add ability to report account --- .../comment-report.component.html | 62 ----------- .../comment-report.component.scss | 11 -- .../shared-moderation/comment-report.component.ts | 93 ---------------- client/src/app/shared/shared-moderation/index.ts | 3 +- .../report-modals/account-report.component.ts | 94 ++++++++++++++++ .../report-modals/comment-report.component.ts | 94 ++++++++++++++++ .../shared-moderation/report-modals/index.ts | 3 + .../report-modals/report.component.html | 62 +++++++++++ .../report-modals/report.component.scss | 27 +++++ .../report-modals/video-report.component.html | 100 +++++++++++++++++ .../report-modals/video-report.component.ts | 122 +++++++++++++++++++++ .../shared-moderation/shared-moderation.module.ts | 15 ++- .../shared-moderation/video-report.component.html | 100 ----------------- .../shared-moderation/video-report.component.scss | 27 ----- .../shared-moderation/video-report.component.ts | 122 --------------------- 15 files changed, 513 insertions(+), 422 deletions(-) delete mode 100644 client/src/app/shared/shared-moderation/comment-report.component.html delete mode 100644 client/src/app/shared/shared-moderation/comment-report.component.scss delete mode 100644 client/src/app/shared/shared-moderation/comment-report.component.ts create mode 100644 client/src/app/shared/shared-moderation/report-modals/account-report.component.ts create mode 100644 client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts create mode 100644 client/src/app/shared/shared-moderation/report-modals/index.ts create mode 100644 client/src/app/shared/shared-moderation/report-modals/report.component.html create mode 100644 client/src/app/shared/shared-moderation/report-modals/report.component.scss create mode 100644 client/src/app/shared/shared-moderation/report-modals/video-report.component.html create mode 100644 client/src/app/shared/shared-moderation/report-modals/video-report.component.ts delete mode 100644 client/src/app/shared/shared-moderation/video-report.component.html delete mode 100644 client/src/app/shared/shared-moderation/video-report.component.scss delete mode 100644 client/src/app/shared/shared-moderation/video-report.component.ts (limited to 'client/src/app/shared/shared-moderation') diff --git a/client/src/app/shared/shared-moderation/comment-report.component.html b/client/src/app/shared/shared-moderation/comment-report.component.html deleted file mode 100644 index 1105b3788..000000000 --- a/client/src/app/shared/shared-moderation/comment-report.component.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - 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/comment-report.component.ts b/client/src/app/shared/shared-moderation/comment-report.component.ts deleted file mode 100644 index 5db4b2dc1..000000000 --- a/client/src/app/shared/shared-moderation/comment-report.component.ts +++ /dev/null @@ -1,93 +0,0 @@ -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 { 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-comment-report', - templateUrl: './comment-report.component.html', - styleUrls: [ './comment-report.component.scss' ] -}) -export class CommentReportComponent extends FormReactive implements OnInit { - @Input() comment: VideoComment = null - - @ViewChild('modal', { static: true }) modal: NgbModal - - error: string = null - predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] - embedHtml: SafeHtml - - 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.isRemoteComment()) { - return this.comment.account.host - } - - return '' - } - - ngOnInit () { - this.buildForm({ - reason: this.abuseValidatorsService.ABUSE_REASON, - predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) - }) - - this.predefinedReasons = this.abuseService.getPrefefinedReasons('comment') - } - - 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, - comment: { - id: this.comment.id - } - }).subscribe( - () => { - this.notifier.success(this.i18n('Comment reported.')) - this.hide() - }, - - err => this.notifier.error(err.message) - ) - } - - isRemoteComment () { - return !this.comment.isLocal - } -} 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/report-modals/comment-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts new file mode 100644 index 000000000..00d7b8d34 --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/comment-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 { 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' + +@Component({ + selector: 'my-comment-report', + 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 }[] = [] + + 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.comment.account.host + } + + return '' + } + + ngOnInit () { + this.modalTitle = this.i18n('Report comment') + + this.buildForm({ + reason: this.abuseValidatorsService.ABUSE_REASON, + predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) + }) + + this.predefinedReasons = this.abuseService.getPrefefinedReasons('comment') + } + + 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, + comment: { + id: this.comment.id + } + }).subscribe( + () => { + this.notifier.success(this.i18n('Comment reported.')) + this.hide() + }, + + err => this.notifier.error(err.message) + ) + } + + 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/report-modals/report.component.html b/client/src/app/shared/shared-moderation/report-modals/report.component.html new file mode 100644 index 000000000..bda62312f --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/report.component.html @@ -0,0 +1,62 @@ + + + + + diff --git a/client/src/app/shared/shared-moderation/report-modals/report.component.scss b/client/src/app/shared/shared-moderation/report-modals/report.component.scss new file mode 100644 index 000000000..b2606cbd8 --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/report.component.scss @@ -0,0 +1,27 @@ +@import 'variables'; +@import 'mixins'; + +.information { + margin-bottom: 20px; +} + +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; + }; +} diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html new file mode 100644 index 000000000..4947088d1 --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html @@ -0,0 +1,100 @@ + + + + + diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts new file mode 100644 index 000000000..7d53ea3c9 --- /dev/null +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts @@ -0,0 +1,122 @@ +import { mapValues, pickBy } from 'lodash-es' +import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' +import { Component, Input, OnInit, ViewChild } from '@angular/core' +import { DomSanitizer, SafeHtml } from '@angular/platform-browser' +import { Notifier } from '@app/core' +import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' +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' + +@Component({ + selector: 'my-video-report', + templateUrl: './video-report.component.html', + styleUrls: [ './report.component.scss' ] +}) +export class VideoReportComponent extends FormReactive implements OnInit { + @Input() video: Video = null + + @ViewChild('modal', { static: true }) modal: NgbModal + + error: string = null + predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] + embedHtml: SafeHtml + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private abuseValidatorsService: AbuseValidatorsService, + private abuseService: AbuseService, + private notifier: Notifier, + private sanitizer: DomSanitizer, + private i18n: I18n + ) { + super() + } + + get currentHost () { + return window.location.host + } + + get originHost () { + if (this.isRemote()) { + return this.video.account.host + } + + return '' + } + + get timestamp () { + return this.form.get('timestamp').value + } + + getVideoEmbed () { + return this.sanitizer.bypassSecurityTrustHtml( + buildVideoEmbed( + buildVideoLink({ + baseUrl: this.video.embedUrl, + title: false, + warningTitle: false + }) + ) + ) + } + + ngOnInit () { + this.buildForm({ + reason: this.abuseValidatorsService.ABUSE_REASON, + predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null), + timestamp: { + hasStart: null, + startAt: null, + hasEnd: null, + endAt: null + } + }) + + this.predefinedReasons = this.abuseService.getPrefefinedReasons('video') + + this.embedHtml = this.getVideoEmbed() + } + + 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[] + const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value + + this.abuseService.reportVideo({ + reason, + predefinedReasons, + video: { + id: this.video.id, + startAt: hasStart && startAt ? startAt : undefined, + endAt: hasEnd && endAt ? endAt : undefined + } + }).subscribe( + () => { + this.notifier.success(this.i18n('Video reported.')) + this.hide() + }, + + err => this.notifier.error(err.message) + ) + } + + 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-moderation/video-report.component.html b/client/src/app/shared/shared-moderation/video-report.component.html deleted file mode 100644 index b724ecb18..000000000 --- a/client/src/app/shared/shared-moderation/video-report.component.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - diff --git a/client/src/app/shared/shared-moderation/video-report.component.scss b/client/src/app/shared/shared-moderation/video-report.component.scss deleted file mode 100644 index b2606cbd8..000000000 --- a/client/src/app/shared/shared-moderation/video-report.component.scss +++ /dev/null @@ -1,27 +0,0 @@ -@import 'variables'; -@import 'mixins'; - -.information { - margin-bottom: 20px; -} - -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; - }; -} diff --git a/client/src/app/shared/shared-moderation/video-report.component.ts b/client/src/app/shared/shared-moderation/video-report.component.ts deleted file mode 100644 index 26e7b62ba..000000000 --- a/client/src/app/shared/shared-moderation/video-report.component.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { mapValues, pickBy } from 'lodash-es' -import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' -import { Component, Input, OnInit, ViewChild } from '@angular/core' -import { DomSanitizer, SafeHtml } from '@angular/platform-browser' -import { Notifier } from '@app/core' -import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' -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' - -@Component({ - selector: 'my-video-report', - templateUrl: './video-report.component.html', - styleUrls: [ './video-report.component.scss' ] -}) -export class VideoReportComponent extends FormReactive implements OnInit { - @Input() video: Video = null - - @ViewChild('modal', { static: true }) modal: NgbModal - - error: string = null - predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] - embedHtml: SafeHtml - - private openedModal: NgbModalRef - - constructor ( - protected formValidatorService: FormValidatorService, - private modalService: NgbModal, - private abuseValidatorsService: AbuseValidatorsService, - private abuseService: AbuseService, - private notifier: Notifier, - private sanitizer: DomSanitizer, - private i18n: I18n - ) { - super() - } - - get currentHost () { - return window.location.host - } - - get originHost () { - if (this.isRemoteVideo()) { - return this.video.account.host - } - - return '' - } - - get timestamp () { - return this.form.get('timestamp').value - } - - getVideoEmbed () { - return this.sanitizer.bypassSecurityTrustHtml( - buildVideoEmbed( - buildVideoLink({ - baseUrl: this.video.embedUrl, - title: false, - warningTitle: false - }) - ) - ) - } - - ngOnInit () { - this.buildForm({ - reason: this.abuseValidatorsService.ABUSE_REASON, - predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null), - timestamp: { - hasStart: null, - startAt: null, - hasEnd: null, - endAt: null - } - }) - - this.predefinedReasons = this.abuseService.getPrefefinedReasons('video') - - this.embedHtml = this.getVideoEmbed() - } - - 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[] - const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value - - this.abuseService.reportVideo({ - reason, - predefinedReasons, - video: { - id: this.video.id, - startAt: hasStart && startAt ? startAt : undefined, - endAt: hasEnd && endAt ? endAt : undefined - } - }).subscribe( - () => { - this.notifier.success(this.i18n('Video reported.')) - this.hide() - }, - - err => this.notifier.error(err.message) - ) - } - - isRemoteVideo () { - return !this.video.isLocal - } -} -- cgit v1.2.3