RED-5546: fix escaped html
This commit is contained in:
parent
048fc24b6e
commit
32a2607e23
@ -1,12 +1,5 @@
|
||||
<section class="dialog">
|
||||
<div
|
||||
[translateParams]="{
|
||||
type: dossierTemplate ? (data.clone ? 'clone' : 'edit') : 'create',
|
||||
name: dossierTemplate?.name
|
||||
}"
|
||||
[translate]="'add-edit-clone-dossier-template.title'"
|
||||
class="dialog-header heading-l"
|
||||
></div>
|
||||
<div [innerHTML]="'add-edit-clone-dossier-template.title' | translate : translateParams" class="dialog-header heading-l"></div>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<div class="dialog-content">
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { AbstractControl, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
||||
import { downloadTypesTranslations } from '@translations/download-types-translations';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplate, DownloadFileType, IDossierTemplate } from '@red/domain';
|
||||
import { DossierTemplate, IDossierTemplate } from '@red/domain';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { ROLES } from '@users/roles';
|
||||
|
||||
@ -17,6 +16,11 @@ interface EditCloneTemplateData {
|
||||
clone?: boolean;
|
||||
}
|
||||
|
||||
const downloadTypes = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map(type => ({
|
||||
key: type,
|
||||
label: downloadTypesTranslations[type],
|
||||
}));
|
||||
|
||||
@Component({
|
||||
templateUrl: './add-edit-clone-dossier-template-dialog.component.html',
|
||||
styleUrls: ['./add-edit-clone-dossier-template-dialog.component.scss'],
|
||||
@ -25,11 +29,7 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
|
||||
readonly roles = ROLES;
|
||||
hasValidFrom: boolean;
|
||||
hasValidTo: boolean;
|
||||
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'];
|
||||
downloadTypes: { key: DownloadFileType; label: string }[] = this.downloadTypesEnum.map(type => ({
|
||||
key: type,
|
||||
label: downloadTypesTranslations[type],
|
||||
}));
|
||||
readonly downloadTypes = downloadTypes;
|
||||
readonly dossierTemplate: DossierTemplate;
|
||||
private _previousValidFrom: Dayjs;
|
||||
private _previousValidTo: Dayjs;
|
||||
@ -59,6 +59,13 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
|
||||
return !this.valid;
|
||||
}
|
||||
|
||||
get translateParams() {
|
||||
return {
|
||||
type: this.dossierTemplate ? (this.data.clone ? 'clone' : 'edit') : 'create',
|
||||
name: this.dossierTemplate?.name,
|
||||
};
|
||||
}
|
||||
|
||||
toggleHasValid(extremity: string) {
|
||||
if (extremity === 'from') {
|
||||
this.hasValidFrom = !this.hasValidFrom;
|
||||
@ -72,20 +79,21 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
const dossierTemplate = {
|
||||
dossierTemplateId: this.dossierTemplate?.dossierTemplateId,
|
||||
...this.form.getRawValue(),
|
||||
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
||||
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
||||
} as IDossierTemplate;
|
||||
|
||||
try {
|
||||
const dossierTemplate = {
|
||||
dossierTemplateId: this.dossierTemplate?.dossierTemplateId,
|
||||
...this.form.getRawValue(),
|
||||
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
||||
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
||||
} as IDossierTemplate;
|
||||
if (this.data?.clone) {
|
||||
await firstValueFrom(this._dossierTemplatesService.clone(this.dossierTemplate.id, dossierTemplate));
|
||||
await this._dossierTemplatesService.clone(this.dossierTemplate.id, dossierTemplate);
|
||||
} else {
|
||||
await firstValueFrom(this._dossierTemplatesService.createOrUpdate(dossierTemplate));
|
||||
await this._dossierTemplatesService.createOrUpdate(dossierTemplate);
|
||||
}
|
||||
this._dialogRef.close(true);
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
const message =
|
||||
error.status === HttpStatusCode.Conflict
|
||||
? _('add-edit-clone-dossier-template.error.conflict')
|
||||
@ -105,7 +113,7 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
|
||||
this._lastValidTo = this._previousValidTo || this._lastValidTo;
|
||||
}
|
||||
|
||||
private _getForm(): UntypedFormGroup {
|
||||
private _getForm() {
|
||||
return this._formBuilder.group({
|
||||
name: [this._getCloneName(), Validators.required],
|
||||
description: [this.dossierTemplate?.description],
|
||||
@ -122,29 +130,28 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
|
||||
}
|
||||
|
||||
private _getCloneName(): string {
|
||||
if (this.data?.clone) {
|
||||
const templateName = this.dossierTemplate.name.trim();
|
||||
|
||||
let nameOfClonedTemplate: string = templateName.split('Copy of ').filter(n => n)[0];
|
||||
nameOfClonedTemplate = nameOfClonedTemplate.split(/\(\s*\d+\s*\)$/)[0].trim();
|
||||
|
||||
const allTemplatesNames = this._dossierTemplatesService.all.map(t => t.name);
|
||||
|
||||
let clonesCount = 0;
|
||||
for (const name of allTemplatesNames) {
|
||||
const splitName = name.split(nameOfClonedTemplate);
|
||||
const suffixRegExp = new RegExp(/^\(\s*\d+\s*\)$/);
|
||||
if (splitName[0] === 'Copy of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) {
|
||||
clonesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (clonesCount >= 1) {
|
||||
return `Copy of ${nameOfClonedTemplate} (${clonesCount})`;
|
||||
}
|
||||
return `Copy of ${nameOfClonedTemplate}`;
|
||||
if (!this.data?.clone) {
|
||||
return this.dossierTemplate?.name;
|
||||
}
|
||||
return this.dossierTemplate?.name;
|
||||
|
||||
const templateName = this.dossierTemplate.name.trim();
|
||||
let nameOfClonedTemplate: string = templateName.split('Copy of ').filter(n => n)[0];
|
||||
nameOfClonedTemplate = nameOfClonedTemplate.split(/\(\s*\d+\s*\)$/)[0].trim();
|
||||
const allTemplatesNames = this._dossierTemplatesService.all.map(t => t.name);
|
||||
|
||||
let clonesCount = 0;
|
||||
for (const name of allTemplatesNames) {
|
||||
const splitName = name.split(nameOfClonedTemplate);
|
||||
const suffixRegExp = new RegExp(/^\(\s*\d+\s*\)$/);
|
||||
if (splitName[0] === 'Copy of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) {
|
||||
clonesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (clonesCount >= 1) {
|
||||
return `Copy of ${nameOfClonedTemplate} (${clonesCount})`;
|
||||
}
|
||||
return `Copy of ${nameOfClonedTemplate}`;
|
||||
}
|
||||
|
||||
private _requiredIfValidator(predicate) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div
|
||||
*ngIf="(changes$ | async).length && ((isSelected$ | async) === false || (multiSelectService.inactive$ | async))"
|
||||
[matTooltip]="changesTooltip$ | async"
|
||||
*ngIf="(noSelection$ | async) && changesTooltip$ | async as changesTooltip"
|
||||
[matTooltip]="changesTooltip"
|
||||
class="chip"
|
||||
matTooltipClass="multiline"
|
||||
matTooltipPosition="above"
|
||||
@ -8,34 +8,22 @@
|
||||
<mat-icon [svgIcon]="'red:redaction-changes'"></mat-icon>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="engines$ | async as engines">
|
||||
<ng-container *ngIf="engines.length && ((isSelected$ | async) === false || (multiSelectService.inactive$ | async))">
|
||||
<div
|
||||
#trigger="cdkOverlayOrigin"
|
||||
(mouseout)="isPopoverOpen = false"
|
||||
(mouseover)="isPopoverOpen = true"
|
||||
cdkOverlayOrigin
|
||||
class="chip"
|
||||
>
|
||||
<ng-container *ngFor="let engine of engines">
|
||||
<mat-icon *ngIf="engine.show" [svgIcon]="engine.icon"></mat-icon>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="(noSelection$ | async) && engines$ | async as engines">
|
||||
<div #trigger="cdkOverlayOrigin" (mouseout)="isPopoverOpen = false" (mouseover)="isPopoverOpen = true" cdkOverlayOrigin class="chip">
|
||||
<mat-icon *ngFor="let engine of engines" [svgIcon]="engine.icon"></mat-icon>
|
||||
</div>
|
||||
|
||||
<ng-template
|
||||
[cdkConnectedOverlayOffsetY]="-8"
|
||||
[cdkConnectedOverlayOpen]="isPopoverOpen"
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
cdkConnectedOverlay
|
||||
>
|
||||
<div class="popover">
|
||||
<ng-container *ngFor="let engine of engines">
|
||||
<div *ngIf="engine.show" class="flex-align-items-center">
|
||||
<mat-icon [svgIcon]="engine.icon"></mat-icon>
|
||||
<span>{{ engine.description | translate: engine.translateParams }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template
|
||||
[cdkConnectedOverlayOffsetY]="-8"
|
||||
[cdkConnectedOverlayOpen]="isPopoverOpen"
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
cdkConnectedOverlay
|
||||
>
|
||||
<div class="popover">
|
||||
<div *ngFor="let engine of engines" class="flex-align-items-center">
|
||||
<mat-icon [svgIcon]="engine.icon"></mat-icon>
|
||||
<span [innerHTML]="engine.description | translate : engine.translateParams"></span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { annotationChangesTranslations } from '@translations/annotation-changes-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { KeysOf } from '@iqser/common-ui';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { filter, map, switchMap } from 'rxjs/operators';
|
||||
import { KeysOf, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BehaviorSubject, combineLatest, filter, map, Observable, switchMap } from 'rxjs';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
|
||||
interface Engine {
|
||||
@ -24,94 +23,92 @@ const Engines = {
|
||||
|
||||
type EngineName = keyof typeof Engines;
|
||||
|
||||
function isBasedOn(annotation: AnnotationWrapper, engineName: EngineName) {
|
||||
return !!annotation.engines?.includes(engineName);
|
||||
}
|
||||
|
||||
const changesProperties: KeysOf<AnnotationWrapper>[] = [
|
||||
'hasBeenResized',
|
||||
'hasBeenRecategorized',
|
||||
'hasLegalBasisChanged',
|
||||
'hasBeenRemovedByManualOverride',
|
||||
'hasBeenForcedRedaction',
|
||||
'hasBeenForcedHint',
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-details [annotation]',
|
||||
templateUrl: './annotation-details.component.html',
|
||||
styleUrls: ['./annotation-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AnnotationDetailsComponent implements OnChanges {
|
||||
@Input() annotation: AnnotationWrapper;
|
||||
readonly isSelected$: Observable<boolean>;
|
||||
readonly noSelection$: Observable<boolean>;
|
||||
isPopoverOpen = false;
|
||||
|
||||
readonly engines$: Observable<Engine[]>;
|
||||
readonly changes$: Observable<string[]>;
|
||||
readonly changesTooltip$: Observable<string>;
|
||||
private readonly _annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(undefined);
|
||||
readonly #annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(undefined);
|
||||
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
) {
|
||||
this.isSelected$ = this._annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation)));
|
||||
const isSelected$ = this.#annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation)));
|
||||
this.noSelection$ = combineLatest([isSelected$, multiSelectService.inactive$]).pipe(
|
||||
map(([isSelected, inactive]) => !isSelected || inactive),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
this.engines$ = this.#engines$;
|
||||
this.changes$ = this.#changes$;
|
||||
this.changesTooltip$ = this.#changesTooltip;
|
||||
}
|
||||
|
||||
get #engines$(): Observable<Engine[]> {
|
||||
return this._annotationChanged$.pipe(
|
||||
return this.#annotationChanged$.pipe(
|
||||
filter(annotation => !!annotation),
|
||||
map(annotation =>
|
||||
[
|
||||
{
|
||||
icon: 'red:dictionary',
|
||||
description: _('annotation-engines.dictionary'),
|
||||
show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.DICTIONARY),
|
||||
translateParams: { isHint: this.annotation.hint },
|
||||
},
|
||||
{
|
||||
icon: 'red:ai',
|
||||
description: _('annotation-engines.ner'),
|
||||
show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.NER),
|
||||
},
|
||||
{
|
||||
icon: 'red:rule',
|
||||
description: _('annotation-engines.rule'),
|
||||
show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.RULE),
|
||||
translateParams: { rule: this.annotation.legalBasisValue || '' },
|
||||
},
|
||||
].filter(engine => engine.show),
|
||||
),
|
||||
map(annotation => this.#extractEngines(annotation).filter(engine => engine.show)),
|
||||
);
|
||||
}
|
||||
|
||||
get #changes$(): Observable<string[]> {
|
||||
return this._annotationChanged$.pipe(
|
||||
get #changesTooltip(): Observable<string | undefined> {
|
||||
return this.#annotationChanged$.pipe(
|
||||
filter(annotation => !!annotation),
|
||||
map(annotation => {
|
||||
const changesProperties: KeysOf<AnnotationWrapper>[] = [
|
||||
'hasBeenResized',
|
||||
'hasBeenRecategorized',
|
||||
'hasLegalBasisChanged',
|
||||
'hasBeenRemovedByManualOverride',
|
||||
'hasBeenForcedRedaction',
|
||||
'hasBeenForcedHint',
|
||||
];
|
||||
return changesProperties.filter(key => annotation[key]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
get #changesTooltip(): Observable<string> {
|
||||
return this.changes$.pipe(
|
||||
map(annotation => changesProperties.filter(key => annotation[key])),
|
||||
map(changes => {
|
||||
if (!changes.length) {
|
||||
return;
|
||||
}
|
||||
const header = this._translateService.instant(_('annotation-changes.header'));
|
||||
const details = changes
|
||||
.map(change => this._translateService.instant(annotationChangesTranslations[change]))
|
||||
.map(change => `• ${change}`);
|
||||
return [header, ...details].join('\n');
|
||||
const details = changes.map(change => this._translateService.instant(annotationChangesTranslations[change]));
|
||||
return [header, ...details.map(change => `• ${change}`)].join('\n');
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private static _isBasedOn(annotation: AnnotationWrapper, engineName: EngineName) {
|
||||
return !!annotation.engines?.includes(engineName);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this._annotationChanged$.next(this.annotation);
|
||||
this.#annotationChanged$.next(this.annotation);
|
||||
}
|
||||
|
||||
#extractEngines(annotation: AnnotationWrapper): Engine[] {
|
||||
return [
|
||||
{
|
||||
icon: 'red:dictionary',
|
||||
description: _('annotation-engines.dictionary'),
|
||||
show: isBasedOn(annotation, Engines.DICTIONARY),
|
||||
translateParams: { isHint: annotation.hint },
|
||||
},
|
||||
{
|
||||
icon: 'red:ai',
|
||||
description: _('annotation-engines.ner'),
|
||||
show: isBasedOn(annotation, Engines.NER),
|
||||
},
|
||||
{
|
||||
icon: 'red:rule',
|
||||
description: _('annotation-engines.rule'),
|
||||
show: isBasedOn(annotation, Engines.RULE),
|
||||
translateParams: { rule: annotation.legalBasisValue || '' },
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
<section *ngIf="dossier$ | async as dossier" class="dialog">
|
||||
<div class="dialog-header heading-l" id="editDossierHeader">
|
||||
{{ 'edit-dossier-dialog.header' | translate : { dossierName: dossier.dossierName } }}
|
||||
</div>
|
||||
<div
|
||||
[innerHTML]="'edit-dossier-dialog.header' | translate : { dossierName: dossier.dossierName }"
|
||||
class="dialog-header heading-l"
|
||||
id="editDossierHeader"
|
||||
></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<iqser-side-nav [title]="'edit-dossier-dialog.side-nav-title' | translate">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { DossierTemplate, IDossierTemplate } from '@red/domain';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { firstValueFrom, forkJoin, Observable, of } from 'rxjs';
|
||||
import { FileAttributesService } from '../entity-services/file-attributes.service';
|
||||
import { catchError, map, mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { DossierTemplateStatsService } from '../entity-services/dossier-template-stats.service';
|
||||
@ -73,12 +73,14 @@ export class DossierTemplatesService extends EntitiesService<IDossierTemplate, D
|
||||
}
|
||||
|
||||
@Validate()
|
||||
createOrUpdate(@RequiredParam() body: IDossierTemplate) {
|
||||
return this._post(body).pipe(switchMap(() => this.loadAll()));
|
||||
async createOrUpdate(@RequiredParam() body: IDossierTemplate) {
|
||||
await firstValueFrom(this._post(body));
|
||||
return await firstValueFrom(this.loadAll());
|
||||
}
|
||||
|
||||
clone(dossierTemplateId: string, body: IDossierTemplate) {
|
||||
return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}/clone`).pipe(switchMap(() => this.loadAll()));
|
||||
async clone(dossierTemplateId: string, body: IDossierTemplate) {
|
||||
await firstValueFrom(this._post(body, `${this._defaultModelPath}/${dossierTemplateId}/clone`));
|
||||
return await firstValueFrom(this.loadAll());
|
||||
}
|
||||
|
||||
refreshDossierTemplate(dossierTemplateId: string): Observable<any> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user