Merge branch 'VM/RED-6774'

This commit is contained in:
Valentin Mihai 2023-06-21 00:12:16 +03:00
commit cabc3fb15b
30 changed files with 1145 additions and 78 deletions

View File

@ -10,6 +10,7 @@ export class AnnotationPermissions {
canMarkAsFalsePositive = true;
canRemoveOrSuggestToRemoveOnlyHere = true;
canRemoveOrSuggestToRemoveFromDictionary = true;
canRemoveOrSuggestToRemoveRedaction = true;
canAcceptSuggestion = true;
canRejectSuggestion = true;
canForceRedaction = true;
@ -63,6 +64,12 @@ export class AnnotationPermissions {
!annotation.pending &&
!annotation.hasBeenResized;
permissions.canRemoveOrSuggestToRemoveRedaction =
annotations.length === 1 &&
(permissions.canRemoveOrSuggestToRemoveOnlyHere ||
permissions.canRemoveOrSuggestToRemoveFromDictionary ||
permissions.canMarkAsFalsePositive);
permissions.canChangeLegalBasis = canAddOrRequestRedaction && annotation.isRedacted && !annotation.pending;
permissions.canRecategorizeImage =

View File

@ -2,6 +2,8 @@ import { IManualRedactionEntry } from '@red/domain';
export const ManualRedactionEntryTypes = {
DICTIONARY: 'DICTIONARY',
REDACT: 'REDACT',
HINT: 'HINT',
REDACTION: 'REDACTION',
FALSE_POSITIVE: 'FALSE_POSITIVE',
} as const;

View File

@ -150,29 +150,38 @@
icon="red:visibility"
></iqser-circle-button>
<iqser-circle-button
(action)="removeOrSuggestRemoveAnnotation(true)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
[type]="buttonType"
icon="red:remove-from-dict"
></iqser-circle-button>
<!-- <iqser-circle-button-->
<!-- (action)="removeOrSuggestRemoveAnnotation(true)"-->
<!-- *ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"-->
<!-- [tooltipPosition]="tooltipPosition"-->
<!-- [tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"-->
<!-- [type]="buttonType"-->
<!-- icon="red:remove-from-dict"-->
<!-- ></iqser-circle-button>-->
<!-- <iqser-circle-button-->
<!-- (action)="markAsFalsePositive()"-->
<!-- *ngIf="annotationPermissions.canMarkAsFalsePositive"-->
<!-- [tooltipPosition]="tooltipPosition"-->
<!-- [tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"-->
<!-- [type]="buttonType"-->
<!-- icon="red:thumb-down"-->
<!-- ></iqser-circle-button>-->
<!-- <iqser-circle-button-->
<!-- (action)="removeOrSuggestRemoveAnnotation(false)"-->
<!-- *ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"-->
<!-- [tooltipPosition]="tooltipPosition"-->
<!-- [tooltip]="'annotation-actions.remove-annotation.only-here' | translate"-->
<!-- [type]="buttonType"-->
<!-- icon="iqser:trash"-->
<!-- ></iqser-circle-button>-->
<iqser-circle-button
(action)="markAsFalsePositive()"
*ngIf="annotationPermissions.canMarkAsFalsePositive"
(action)="removeOrSuggestRemoveRedaction()"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveRedaction"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"
[type]="buttonType"
icon="red:thumb-down"
></iqser-circle-button>
<iqser-circle-button
(action)="removeOrSuggestRemoveAnnotation(false)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"
[tooltip]="'annotation-actions.remove-annotation.remove-redaction' | translate"
[type]="buttonType"
icon="iqser:trash"
></iqser-circle-button>

View File

@ -10,6 +10,7 @@ import { HelpModeService, IqserPermissionsService } from '@iqser/common-ui';
import { ViewModeService } from '../../services/view-mode.service';
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
import { Roles } from '@users/roles';
import { RemoveRedactionPermissions } from '../../dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
export const AnnotationButtonTypes = {
dark: 'dark',
@ -78,6 +79,10 @@ export class AnnotationActionsComponent implements OnChanges {
this.annotationActionsService.removeOrSuggestRemoveAnnotation(this.annotations, removeFromDict);
}
removeOrSuggestRemoveRedaction() {
this.annotationActionsService.removeOrSuggestRemoveRedaction(this.annotations[0], this.annotationPermissions);
}
markAsFalsePositive() {
this.annotationActionsService.markAsFalsePositive(this.annotations);
}

View File

@ -0,0 +1,91 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.selected-text'"></label>
{{ form.get('selectedText').value }}
</div>
<iqser-details-radio
[options]="options"
(extraOptionChanged)="extraOptionChanged($event)"
formControlName="option"
></iqser-details-radio>
<ng-container *deny="roles.getRss; if: !dictionaryRequest && type !== 'HINT'">
<div class="iqser-input-group required w-450">
<label [translate]="'redact-text.dialog.content.reason'"></label>
<mat-form-field>
<mat-select
[placeholder]="'redact-text.dialog.content.reason-placeholder' | translate"
class="full-width"
formControlName="reason"
>
<mat-option
*ngFor="let option of legalOptions"
[matTooltip]="option.description"
[value]="option"
matTooltipPosition="after"
>
{{ option.label }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.legal-basis'"></label>
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
</div>
</ng-container>
<ng-container *deny="roles.getRss; if: dictionaryRequest || type === 'HINT'">
<div class="iqser-input-group required w-450">
<label [translate]="'redact-text.dialog.content.type'"></label>
<mat-form-field>
<mat-select formControlName="dictionary" [placeholder]="'redact-text.dialog.content.type-placeholder' | translate">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option
*ngFor="let dictionary of dictionaries"
[matTooltip]="dictionary.description"
[value]="dictionary.type"
matTooltipPosition="after"
>
<span> {{ dictionary.label }} </span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-container>
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.comment'"></label>
<textarea
formControlName="comment"
iqserHasScrollbar
name="comment"
rows="4"
type="text"
[placeholder]="'redact-text.dialog.content.comment-placeholder' | translate"
></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[label]="'redact-text.dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
[disabled]="disabled"
>
</iqser-icon-button>
<div class="all-caps-label cancel" mat-dialog-close [translate]="'redact-text.dialog.actions.cancel'"></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,8 @@
label {
font-weight: bold;
padding-bottom: 8px;
}
iqser-details-radio {
padding-top: 20px;
}

View File

@ -0,0 +1,225 @@
import { Component, OnInit } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
import { Dictionary, Dossier, DossierTemplate, File, IAddRedactionRequest, IManualRedactionEntry, SuperTypes } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Roles } from '@users/roles';
import { firstValueFrom } from 'rxjs';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import {
ManualRedactionEntryType,
ManualRedactionEntryTypes,
ManualRedactionEntryWrapper,
} from '@models/file/manual-redaction-entry.wrapper';
import { redactTextTranslations } from '@translations/redact-text-translations';
import { RedactTextOption, RedactTextOptions } from './redact-text-options';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
const PIN_ICON = 'red:push-pin';
const FOLDER_ICON = 'red:folder';
interface RedactTextData {
manualRedactionEntryWrapper: ManualRedactionEntryWrapper;
dossierId: string;
file: File;
}
interface DialogResult {
redaction: IManualRedactionEntry;
dictionary: Dictionary;
applyToAllDossiers: boolean | null;
}
@Component({
templateUrl: './redact-text-dialog.component.html',
styleUrls: ['./redact-text-dialog.component.scss'],
})
export class RedactTextDialogComponent
extends IqserDialogComponent<RedactTextDialogComponent, RedactTextData, DialogResult>
implements OnInit
{
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RedactTextOption>[];
readonly type: ManualRedactionEntryType;
dictionaryRequest = false;
legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = [];
form!: UntypedFormGroup;
#manualRedactionTypeExists = true;
#applyToAllDossiers = true;
readonly #translations = redactTextTranslations;
readonly #dossier: Dossier;
readonly #hint: boolean;
constructor(
private readonly _justificationsService: JustificationsService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dictionaryService: DictionaryService,
private readonly _iqserPermissionsService: IqserPermissionsService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _formBuilder: FormBuilder,
) {
super();
this.#dossier = _activeDossiersService.find(this.data.dossierId);
this.type = this.data.manualRedactionEntryWrapper.type;
this.#hint = this.type === ManualRedactionEntryTypes.HINT;
this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
this.options = this.#options();
this.form = this.#getForm();
this.form
.get('option')
.valueChanges.pipe(
tap((option: DetailsRadioOption<RedactTextOption>) => {
this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER;
this.#resetValues();
}),
takeUntilDestroyed(),
)
.subscribe();
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
get disabled() {
if (this.dictionaryRequest || this.#hint) {
return !this.form.get('dictionary').value;
}
return !this.form.get('reason').value;
}
async ngOnInit(): Promise<void> {
this.dictionaries = this._dictionaryService.getPossibleDictionaries(this.#dossier.dossierTemplateId, this.#hint);
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
legalBasis: lbm.reason,
description: lbm.description,
label: lbm.name,
}));
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
this.#selectReason();
this.#formatSelectedTextValue();
}
extraOptionChanged(option: DetailsRadioOption<RedactTextOption>): void {
this.#applyToAllDossiers = option.extraOption.checked;
}
save(): void {
this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
const redaction = this.data.manualRedactionEntryWrapper.manualRedactionEntry;
this.dialogRef.close({
redaction,
dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value),
applyToAllDossiers: this.dictionaryRequest ? this.#applyToAllDossiers : null,
});
}
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value,
reason: [null],
comment: [null],
dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null],
classification: ['non-readable content'],
section: [null],
option: [this.options[0]],
});
}
#formatSelectedTextValue(): void {
this.data.manualRedactionEntryWrapper.manualRedactionEntry.value =
this.data.manualRedactionEntryWrapper.manualRedactionEntry.value.replace(
// eslint-disable-next-line no-control-regex,max-len
/([^\s\d-]{2,})[-\u00AD]\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]/gi,
'$1',
);
}
#selectReason() {
if (this.legalOptions.length === 1) {
this.form.get('reason').setValue(this.legalOptions[0]);
}
}
#enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
const legalOption: LegalBasisOption = this.form.get('reason').value;
addRedactionRequest.type = this.form.get('dictionary').value;
if (legalOption) {
addRedactionRequest.reason = legalOption.description;
addRedactionRequest.legalBasis = legalOption.legalBasis;
}
if (this._iqserPermissionsService.has(Roles.getRss)) {
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
} else {
addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
addRedactionRequest.addToDossierDictionary = this.dictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
}
if (!addRedactionRequest.reason) {
addRedactionRequest.reason = 'Dictionary Request';
}
const commentValue = this.form.get('comment').value;
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
addRedactionRequest.section = this.form.get('section').value;
addRedactionRequest.value = addRedactionRequest.rectangle
? this.form.get('classification').value
: this.form.get('selectedText').value;
}
#options() {
const options: DetailsRadioOption<RedactTextOption>[] = [
{
label: this.#translations[this.type].onlyHere.label,
description: this.#translations[this.type].onlyHere.description,
icon: PIN_ICON,
value: RedactTextOptions.ONLY_HERE,
},
];
if (!this._iqserPermissionsService.has(Roles.getRss)) {
options.push({
label: this.#translations[this.type].inDossier.label,
description: this.#translations[this.type].inDossier.description,
descriptionParams: { dossierName: this.#dossier.dossierName },
icon: FOLDER_ICON,
value: RedactTextOptions.IN_DOSSIER,
extraOption: {
label: this.#translations[this.type].inDossier.extraOptionLabel,
checked: true,
},
});
}
return options;
}
#resetValues() {
this.#applyToAllDossiers = true;
this.options[1].extraOption.checked = true;
if (this.dictionaryRequest) {
this.form.get('reason').setValue(null);
this.form.get('dictionary').setValue(null);
return;
}
this.form.get('dictionary').setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
}
}

View File

@ -0,0 +1,6 @@
export const RedactTextOptions = {
ONLY_HERE: 'ONLY_HERE',
IN_DOSSIER: 'IN_DOSSIER',
} as const;
export type RedactTextOption = keyof typeof RedactTextOptions;

View File

@ -0,0 +1,123 @@
import { Component } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes } from '@iqser/common-ui';
import { RemoveRedactionOption, RemoveRedactionOptions } from './remove-redaction-options';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Dossier } from '@red/domain';
import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive';
import { PermissionsService } from '@services/permissions.service';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
const PIN_ICON = 'red:push-pin';
const FOLDER_ICON = 'red:folder';
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
export interface RemoveRedactionPermissions {
canRemoveOrSuggestToRemoveOnlyHere: boolean;
canRemoveOrSuggestToRemoveFromDictionary: boolean;
canMarkAsFalsePositive: boolean;
}
export interface RemoveRedactionData {
redaction: AnnotationWrapper;
dossier: Dossier;
falsePositiveContext: string;
permissions: RemoveRedactionPermissions;
}
export interface RemoveRedactionResult {
comment: string;
option: DetailsRadioOption<RemoveRedactionOption>;
applyToAllDossiers: boolean | null;
}
@Component({
templateUrl: './remove-redaction-dialog.html',
styleUrls: ['./remove-redaction-dialog.component.scss'],
})
export class RemoveRedactionDialogComponent extends IqserDialogComponent<
RemoveRedactionDialogComponent,
RemoveRedactionData,
RemoveRedactionResult
> {
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RemoveRedactionOption>[];
form!: UntypedFormGroup;
readonly #redaction: AnnotationWrapper;
readonly #permissions: RemoveRedactionPermissions;
readonly #translations = removeRedactionTranslations;
constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) {
super();
this.#redaction = this.data.redaction;
this.#permissions = this.data.permissions;
this.options = this.#options();
this.form = this.#getForm();
this.form
.get('option')
.valueChanges.pipe(
tap(() => {
this.options[1].extraOption.checked = true;
this.options[2].extraOption.checked = true;
}),
takeUntilDestroyed(),
)
.subscribe();
}
save(): void {
this.dialogRef.close(this.form.getRawValue());
}
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
comment: [null],
option: [this.options[0]],
});
}
#options() {
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
if (this.#permissions.canRemoveOrSuggestToRemoveOnlyHere) {
options.push({
label: this.#translations.ONLY_HERE.label,
description: this.#translations.ONLY_HERE.description,
descriptionParams: { value: this.#redaction.value },
icon: PIN_ICON,
value: RemoveRedactionOptions.ONLY_HERE,
});
}
if (this.#permissions.canRemoveOrSuggestToRemoveFromDictionary) {
options.push({
label: this.#translations.IN_DOSSIER.label,
description: this.#translations.IN_DOSSIER.description,
descriptionParams: { value: this.#redaction.value },
icon: FOLDER_ICON,
value: RemoveRedactionOptions.IN_DOSSIER,
extraOption: {
label: this.#translations.IN_DOSSIER.extraOptionLabel,
checked: true,
},
});
}
if (this.#permissions.canMarkAsFalsePositive) {
options.push({
label: this.#translations.FALSE_POSITIVE.label,
description: this.#translations.FALSE_POSITIVE.description,
descriptionParams: { value: this.#redaction.value, type: this.#redaction.type, context: this.data.falsePositiveContext },
icon: REMOVE_FROM_DICT_ICON,
value: RemoveRedactionOptions.FALSE_POSITIVE,
extraOption: {
label: this.#translations.FALSE_POSITIVE.extraOptionLabel,
checked: true,
},
});
}
return options;
}
}

View File

@ -0,0 +1,34 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'remove-redaction.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
<div class="iqser-input-group w-450">
<label [translate]="'remove-redaction.dialog.content.comment'"></label>
<textarea
formControlName="comment"
iqserHasScrollbar
name="comment"
rows="4"
type="text"
[placeholder]="'remove-redaction.dialog.content.comment-placeholder' | translate"
></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[label]="'remove-redaction.dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
>
</iqser-icon-button>
<div class="all-caps-label cancel" mat-dialog-close [translate]="'remove-redaction.dialog.actions.cancel'"></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,7 @@
export const RemoveRedactionOptions = {
ONLY_HERE: 'ONLY_HERE',
IN_DOSSIER: 'IN_DOSSIER',
FALSE_POSITIVE: 'FALSE_POSITIVE',
} as const;
export type RemoveRedactionOption = keyof typeof RemoveRedactionOptions;

View File

@ -39,7 +39,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { Dictionary, File, ViewModes } from '@red/domain';
import { Dictionary, File, IManualRedactionEntry, ViewModes } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { combineLatest, firstValueFrom, of, pairwise } from 'rxjs';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
@ -74,6 +74,9 @@ import { ConfigService } from '@services/config.service';
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
import { Roles } from '@users/roles';
import { SuggestionsService } from './services/suggestions.service';
import { IqserDialog } from '../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
@ -124,6 +127,7 @@ export class FilePreviewScreenComponent
private readonly _changeRef: ChangeDetectorRef,
private readonly _tenantsService: TenantsService,
private readonly _dialogService: FilePreviewDialogService,
private readonly _iqserDialog: IqserDialog,
private readonly _pageRotationService: PageRotationService,
private readonly _viewerHeaderService: ViewerHeaderService,
private readonly _annotationDrawService: AnnotationDrawService,
@ -137,6 +141,7 @@ export class FilePreviewScreenComponent
private readonly _readableRedactionsService: ReadableRedactionsService,
private readonly _helpModeService: HelpModeService,
private readonly _suggestionsService: SuggestionsService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _dialog: MatDialog,
) {
super();
@ -366,6 +371,34 @@ export class FilePreviewScreenComponent
);
}
async openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
const file = this.state.file();
const result = await this._iqserDialog
.openDefault(RedactTextDialogComponent, {
data: { manualRedactionEntryWrapper, dossierId: this.dossierId, file },
})
.result();
if (result) {
const add$ = this._manualRedactionService.addAnnotation(
[result.redaction],
this.dossierId,
this.fileId,
result.dictionary?.label,
);
if (result.applyToAllDossiers !== null) {
const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId);
const { ...body } = dossierTemplate;
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
await this._dossierTemplatesService.createOrUpdate(body);
}
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
}
}
toggleFullScreen() {
this.fullScreen = !this.fullScreen;
if (this.fullScreen) {
@ -623,6 +656,10 @@ export class FilePreviewScreenComponent
this.openManualAnnotationDialog($event);
});
this.addActiveScreenSubscription = this.pdfProxyService.redactTextRequested$.subscribe($event => {
this.openRedactTextDialog($event);
});
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
this._ngZone.run(() => this.#updateQueryParamsPage(page)),
);

View File

@ -65,6 +65,8 @@ import { PagesComponent } from './components/pages/pages.component';
import { SharedModule } from '@shared/shared.module';
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
import { FalsePositiveDialogComponent } from './dialogs/false-positive-dialog/false-positive-dialog.component';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
const routes: IqserRoutes = [
{
@ -89,6 +91,8 @@ const dialogs = [
ImportRedactionsDialogComponent,
RssDialogComponent,
FalsePositiveDialogComponent,
RedactTextDialogComponent,
RemoveRedactionDialogComponent,
];
const components = [

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { ManualRedactionService } from './manual-redaction.service';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { firstValueFrom, Observable } from 'rxjs';
import { firstValueFrom, Observable, of } from 'rxjs';
import { getFirstRelevantTextPart } from '../../../utils';
import { Core } from '@pdftron/webviewer';
import {
@ -30,6 +30,14 @@ import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { SkippedService } from './skipped.service';
import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service';
import {
RemoveRedactionDialogComponent,
RemoveRedactionPermissions,
RemoveRedactionResult,
} from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
import { RemoveRedactionOptions } from '../dialogs/remove-redaction-dialog/remove-redaction-options';
import { IqserDialog } from '../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
@Injectable()
export class AnnotationActionsService {
@ -37,6 +45,7 @@ export class AnnotationActionsService {
private readonly _manualRedactionService: ManualRedactionService,
private readonly _dialogService: FilePreviewDialogService,
private readonly _dialog: MatDialog,
private readonly _iqserDialog: IqserDialog,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
private readonly _annotationManager: REDAnnotationManager,
@ -44,6 +53,7 @@ export class AnnotationActionsService {
private readonly _state: FilePreviewStateService,
private readonly _fileDataService: FileDataService,
private readonly _skippedService: SkippedService,
private readonly _dossierTemplatesService: DossierTemplatesService,
) {}
acceptSuggestion(annotations: AnnotationWrapper[]) {
@ -129,6 +139,41 @@ export class AnnotationActionsService {
});
}
async removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper, permissions) {
const removePermissions: RemoveRedactionPermissions = {
canRemoveOrSuggestToRemoveOnlyHere: permissions.canRemoveOrSuggestToRemoveOnlyHere,
canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary,
canMarkAsFalsePositive: permissions.canMarkAsFalsePositive,
};
const result: RemoveRedactionResult = await this._iqserDialog
.openDefault(RemoveRedactionDialogComponent, {
data: {
redaction,
dossier: this._state.dossier(),
falsePositiveContext: this._getFalsePositiveText(redaction),
permissions: removePermissions,
},
})
.result();
if (result) {
if (result.option.value === RemoveRedactionOptions.FALSE_POSITIVE) {
this.#setAsFalsePositive(redaction, result.comment);
} else {
const removeFromDictionary = result.option.value === RemoveRedactionOptions.IN_DOSSIER;
this.#removeRedaction(redaction, result.comment, removeFromDictionary);
}
}
if (result.option.extraOption) {
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
const { ...body } = dossierTemplate;
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
await this._dossierTemplatesService.createOrUpdate(body);
}
}
recategorizeImages(annotations: AnnotationWrapper[]) {
const data = { annotations, dossier: this._state.dossier() };
const { dossierId, fileId } = this._state;
@ -375,4 +420,34 @@ export class AnnotationActionsService {
}
return words;
}
#setAsFalsePositive(redaction: AnnotationWrapper, comment: string) {
const request = {
sourceId: redaction.id,
value: this._getFalsePositiveText(redaction),
type: redaction.type,
positions: redaction.positions,
addToDictionary: true,
reason: 'False Positive',
dictionaryEntryType: redaction.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,
comment: comment ? { text: comment } : null,
};
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(this._manualRedactionService.addAnnotation([request], dossierId, fileId)).then();
}
#removeRedaction(redaction: AnnotationWrapper, comment: string, removeFromDictionary: boolean) {
const body = {
annotationId: redaction.id,
comment: comment,
removeFromDictionary,
};
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(
this._manualRedactionService.removeOrSuggestRemove([body], dossierId, fileId, removeFromDictionary, redaction.isHint),
).then();
}
}

View File

@ -62,14 +62,14 @@ export class PdfAnnotationActionsService {
availableActions.push(recategorizeButton);
}
if (permissions.canRemoveOrSuggestToRemoveFromDictionary) {
const removeFromDictButton = this.#getButton(
'remove-from-dict',
_('annotation-actions.remove-annotation.remove-from-dict'),
() => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true),
);
availableActions.push(removeFromDictButton);
}
// if (permissions.canRemoveOrSuggestToRemoveFromDictionary) {
// const removeFromDictButton = this.#getButton(
// 'remove-from-dict',
// _('annotation-actions.remove-annotation.remove-from-dict'),
// () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true),
// );
// availableActions.push(removeFromDictButton);
// }
if (permissions.canAcceptRecommendation) {
const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () =>
@ -92,12 +92,12 @@ export class PdfAnnotationActionsService {
availableActions.push(undoButton);
}
if (permissions.canMarkAsFalsePositive) {
const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () =>
this.#annotationActionsService.markAsFalsePositive(annotations),
);
availableActions.push(markAsFalsePositiveButton);
}
// if (permissions.canMarkAsFalsePositive) {
// const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () =>
// this.#annotationActionsService.markAsFalsePositive(annotations),
// );
// availableActions.push(markAsFalsePositiveButton);
// }
if (permissions.canForceRedaction) {
const forceRedactionButton = this.#getButton('thumb-up', _('annotation-actions.force-redaction.label'), () =>
@ -120,13 +120,20 @@ export class PdfAnnotationActionsService {
availableActions.push(rejectSuggestionButton);
}
if (permissions.canRemoveOrSuggestToRemoveOnlyHere) {
const removeOrSuggestToRemoveOnlyHereButton = this.#getButton(
'trash',
_('annotation-actions.remove-annotation.only-here'),
() => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false),
// if (permissions.canRemoveOrSuggestToRemoveOnlyHere) {
// const removeOrSuggestToRemoveOnlyHereButton = this.#getButton(
// 'trash',
// _('annotation-actions.remove-annotation.only-here'),
// () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false),
// );
// availableActions.push(removeOrSuggestToRemoveOnlyHereButton);
// }
if (permissions.canRemoveOrSuggestToRemoveRedaction) {
const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () =>
this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0], permissions),
);
availableActions.push(removeOrSuggestToRemoveOnlyHereButton);
availableActions.push(removeOrSuggestToRemoveButton);
}
return availableActions;
@ -164,6 +171,7 @@ export class PdfAnnotationActionsService {
canForceHint: permissions.reduce((acc, next) => acc && next.canForceHint, true),
canRejectSuggestion: permissions.reduce((acc, next) => acc && next.canRejectSuggestion, true),
canRemoveOrSuggestToRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveOnlyHere, true),
canRemoveOrSuggestToRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveRedaction, true),
};
}
}

View File

@ -42,6 +42,7 @@ import Quad = Core.Math.Quad;
export class PdfProxyService {
readonly annotationSelected$ = this.#annotationSelected$;
readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>();
readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>();
readonly pageChanged$ = this._pdf.pageChanged$.pipe(
tap(() => this._handleCustomActions()),
tap(() => this._pdf.resetAnnotationActions()),
@ -60,6 +61,7 @@ export class PdfProxyService {
? this._convertPath('/assets/icons/general/pdftron-action-add-component.svg')
: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg');
readonly #addHintIcon = this._convertPath('/assets/icons/general/pdftron-action-add-hint.svg');
constructor(
private readonly _translateService: TranslateService,
@ -159,21 +161,34 @@ export class PdfProxyService {
if (this._iqserPermissionsService.has(Roles.redactions.write) || this._iqserPermissionsService.has(Roles.redactions.request)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_REDACTION,
dataElement: TextPopups.REDACT_TEXT,
img: this.#addRedactionIcon,
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)),
onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.REDACT)),
});
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_HINT,
img: this.#addHintIcon,
onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.HINT)),
});
if (!this._iqserPermissionsService.has(Roles.getRss)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_DICTIONARY,
img: this.#addDictIcon,
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY)),
});
}
// popups.push({
// type: 'actionButton',
// dataElement: TextPopups.ADD_REDACTION,
// img: this.#addRedactionIcon,
// title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
// onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)),
// });
//
// if (!this._iqserPermissionsService.has(Roles.getRss)) {
// popups.push({
// type: 'actionButton',
// dataElement: TextPopups.ADD_DICTIONARY,
// img: this.#addDictIcon,
// title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
// onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY)),
// });
// }
}
}
@ -189,6 +204,13 @@ export class PdfProxyService {
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
}
private _redactText(type: ManualRedactionEntryType) {
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
const text = this._documentViewer.selectedText();
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
this.redactTextRequested$.next({ manualRedactionEntry, type });
}
private _handleCustomActions() {
const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage());

View File

@ -29,6 +29,8 @@ export const HeaderElements = {
export type HeaderElementType = ValuesOf<typeof HeaderElements>;
export const TextPopups = {
REDACT_TEXT: 'redact-text',
ADD_HINT: 'add-hint',
ADD_REDACTION: 'add-redaction',
ADD_DICTIONARY: 'add-dictionary',
ADD_RECTANGLE: 'add-rectangle',

View File

@ -63,6 +63,7 @@ export class IconsModule {
'padding-top-bottom',
'page',
'preview',
'push-pin',
'put-back',
'read-only',
'ready-for-approval',

View File

@ -145,6 +145,24 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual);
}
getPossibleDictionaries(dossierTemplateId: string, hintTypes: boolean): Dictionary[] {
const possibleDictionaries: Dictionary[] = [];
this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => {
if (!hintTypes) {
if (!d.hint) {
possibleDictionaries.push(d);
}
} else if (d.hint) {
possibleDictionaries.push(d);
}
});
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
return possibleDictionaries;
}
getRedactionTypes(dossierTemplateId: string): Dictionary[] {
const possibleDictionaries: Dictionary[] = [];

View File

@ -0,0 +1,32 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
interface Option {
label: string;
description: string;
extraOptionLabel?: string;
}
export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' | 'inDossier', Option>> = {
REDACT: {
onlyHere: {
label: _('redact-text.dialog.content.options.redact.only-here.label'),
description: _('redact-text.dialog.content.options.redact.only-here.description'),
},
inDossier: {
label: _('redact-text.dialog.content.options.redact.in-dossier.label'),
description: _('redact-text.dialog.content.options.redact.in-dossier.description'),
extraOptionLabel: _('redact-text.dialog.content.options.redact.in-dossier.extraOptionLabel'),
},
},
HINT: {
onlyHere: {
label: _('redact-text.dialog.content.options.hint.only-here.label'),
description: _('redact-text.dialog.content.options.hint.only-here.description'),
},
inDossier: {
label: _('redact-text.dialog.content.options.hint.in-dossier.label'),
description: _('redact-text.dialog.content.options.hint.in-dossier.description'),
extraOptionLabel: _('redact-text.dialog.content.options.hint.in-dossier.extraOptionLabel'),
},
},
} as const;

View File

@ -0,0 +1,25 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { RemoveRedactionOption } from '../modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options';
interface Option {
label: string;
description: string;
extraOptionLabel?: string;
}
export const removeRedactionTranslations: { [key in RemoveRedactionOption]: Option } = {
ONLY_HERE: {
label: _('remove-redaction.dialog.content.options.only-here.label'),
description: _('remove-redaction.dialog.content.options.only-here.description'),
},
IN_DOSSIER: {
label: _('remove-redaction.dialog.content.options.in-dossier.label'),
description: _('remove-redaction.dialog.content.options.in-dossier.description'),
extraOptionLabel: _('remove-redaction.dialog.content.options.redact.in-dossier.extraOptionLabel'),
},
FALSE_POSITIVE: {
label: _('remove-redaction.dialog.content.options.false-positive.label'),
description: _('remove-redaction.dialog.content.options.false-positive.description'),
extraOptionLabel: _('remove-redaction.dialog.content.options.redact.false-positive.extraOptionLabel'),
},
};

View File

@ -43,6 +43,10 @@
"generic": "Fehler beim Erstellen der Dossiervorlage."
},
"form": {
"apply-updates-default": {
"description": "",
"heading": ""
},
"description": "Beschreibung",
"description-placeholder": "Beschreibung eingeben",
"hidden-text": {
@ -61,11 +65,7 @@
"title": ""
},
"valid-from": "Gültig ab",
"valid-to": "Gültig bis",
"apply-updates-default": {
"heading": "",
"description": ""
}
"valid-to": "Gültig bis"
},
"save": "Dossier-Vorlage speichern",
"title": "{type, select, edit{Dossier-Vorlage {name} bearbeiten} create{Dossier-Vorlage erstellen} clone{} other{}}"
@ -330,9 +330,7 @@
"recategorize-image": "neu kategorisieren",
"reject-suggestion": "Vorschlag ablehnen",
"remove-annotation": {
"false-positive": "Falsch positiv",
"only-here": "nur hier entfernen",
"remove-from-dict": "Aus dem Wörterbuch entfernen"
"remove-redaction": ""
},
"remove-highlights": {
"label": ""
@ -1904,6 +1902,49 @@
},
"header": "Bildtypen bearbeiten"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"comment-placeholder": "",
"legal-basis": "",
"options": {
"hint": {
"in-dossier": {
"description": "",
"extraOptionLabel": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
},
"redact": {
"in-dossier": {
"description": "",
"extraOptionLabel": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
}
},
"reason": "",
"reason-placeholder": "",
"selected-text": "",
"type": "",
"type-placeholder": ""
},
"title": ""
}
},
"redaction-abbreviation": "R",
"references": "",
"remove-annotations-dialog": {
@ -1921,6 +1962,41 @@
},
"value": "Wert"
},
"remove-redaction": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"comment-placeholder": "",
"options": {
"false-positive": {
"description": "",
"label": ""
},
"in-dossier": {
"description": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
},
"redact": {
"false-positive": {
"extraOptionLabel": ""
},
"in-dossier": {
"extraOptionLabel": ""
}
}
}
},
"title": ""
}
},
"report-type": {
"label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}"
},

View File

@ -43,6 +43,10 @@
"generic": "Failed to create dossier template."
},
"form": {
"apply-updates-default": {
"description": "Apply dictionary updates to all dossiers by default",
"heading": "Entity configuration"
},
"description": "Description",
"description-placeholder": "Enter Description",
"hidden-text": {
@ -61,11 +65,7 @@
"title": "Keep overlapping elements"
},
"valid-from": "Valid from",
"valid-to": "Valid to",
"apply-updates-default": {
"heading": "Entity configuration",
"description": "Apply dictionary updates to all dossiers by default"
}
"valid-to": "Valid to"
},
"save": "Save Dossier Template",
"title": "{type, select, edit{Edit {name}} create{Create} clone{Clone} other{}} Dossier Template"
@ -330,9 +330,7 @@
"recategorize-image": "Recategorize",
"reject-suggestion": "Reject Suggestion",
"remove-annotation": {
"false-positive": "False Positive",
"only-here": "Remove only here",
"remove-from-dict": "Remove from dictionary"
"remove-redaction": "Remove"
},
"remove-highlights": {
"label": "Remove Selected Earmarks"
@ -1904,6 +1902,49 @@
},
"header": "Edit Image Type"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"legal-basis": "Legal Basis",
"options": {
"hint": {
"in-dossier": {
"description": "Add hint in every document in {dossierName}.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Add hint in dossier"
},
"only-here": {
"description": "Add hint only at this position in this document.",
"label": "Add hint only here"
}
},
"redact": {
"in-dossier": {
"description": "Add redaction in every document in {dossierName}.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Redact in dossier"
},
"only-here": {
"description": "Add redaction only at this position in this document.",
"label": "Redact only here"
}
}
},
"reason": "Reason",
"reason-placeholder": "Select a reason ...",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type ..."
},
"title": "Redact text"
}
},
"redaction-abbreviation": "R",
"references": "{count} {count, plural, one{reference} other{references}}",
"remove-annotations-dialog": {
@ -1921,6 +1962,41 @@
},
"value": "Value"
},
"remove-redaction": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"options": {
"false-positive": {
"description": "\"{value}\" is not a {type} in this context: \"{context}\".",
"label": "False positive"
},
"in-dossier": {
"description": "Do not redact \"{value}\" in any document of the current dossier.",
"label": "Remove in dossier"
},
"only-here": {
"description": "Do not redact \"{value}\" at this position in the current document.",
"label": "Remove here"
},
"redact": {
"false-positive": {
"extraOptionLabel": "Apply to all dossiers"
},
"in-dossier": {
"extraOptionLabel": "Apply to all dossiers"
}
}
}
},
"title": "Remove redaction"
}
},
"report-type": {
"label": "{length} report {length, plural, one{type} other{types}}"
},

View File

@ -43,6 +43,10 @@
"generic": "Fehler beim Erstellen der Dossiervorlage."
},
"form": {
"apply-updates-default": {
"description": "",
"heading": ""
},
"description": "Beschreibung",
"description-placeholder": "Beschreibung eingeben",
"hidden-text": {
@ -326,9 +330,7 @@
"recategorize-image": "neu kategorisieren",
"reject-suggestion": "Vorschlag ablehnen",
"remove-annotation": {
"false-positive": "Falsch positiv",
"only-here": "nur hier entfernen",
"remove-from-dict": "Aus dem Wörterbuch entfernen"
"remove-redaction": ""
},
"remove-highlights": {
"label": ""
@ -1900,6 +1902,49 @@
},
"header": "Bildtypen bearbeiten"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"comment-placeholder": "",
"legal-basis": "",
"options": {
"hint": {
"in-dossier": {
"description": "",
"extraOptionLabel": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
},
"redact": {
"in-dossier": {
"description": "",
"extraOptionLabel": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
}
},
"reason": "",
"reason-placeholder": "",
"selected-text": "",
"type": "",
"type-placeholder": ""
},
"title": ""
}
},
"redaction-abbreviation": "C",
"references": "",
"remove-annotations-dialog": {
@ -1917,6 +1962,41 @@
},
"value": "Wert"
},
"remove-redaction": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"comment-placeholder": "",
"options": {
"false-positive": {
"description": "",
"label": ""
},
"in-dossier": {
"description": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
},
"redact": {
"false-positive": {
"extraOptionLabel": ""
},
"in-dossier": {
"extraOptionLabel": ""
}
}
}
},
"title": ""
}
},
"report-type": {
"label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}"
},

View File

@ -43,6 +43,10 @@
"generic": "Failed to create dossier template."
},
"form": {
"apply-updates-default": {
"description": "",
"heading": ""
},
"description": "Description",
"description-placeholder": "Enter Description",
"hidden-text": {
@ -326,9 +330,7 @@
"recategorize-image": "Recategorize",
"reject-suggestion": "Reject Suggestion",
"remove-annotation": {
"false-positive": "False Positive",
"only-here": "Remove only here",
"remove-from-dict": "Remove from dictionary"
"remove-redaction": "Remove"
},
"remove-highlights": {
"label": "Remove Selected Earmarks"
@ -1900,6 +1902,49 @@
},
"header": "Edit Image Type"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"legal-basis": "Legal Basis",
"options": {
"hint": {
"in-dossier": {
"description": "Add hint in every document in {dossierName}.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Add hint in dossier"
},
"only-here": {
"description": "Add hint only at this position in this document.",
"label": "Add hint only here"
}
},
"redact": {
"in-dossier": {
"description": "Add redaction in every document in {dossierName}.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Redact in dossier"
},
"only-here": {
"description": "Add redaction only at this position in this document.",
"label": "Redact only here"
}
}
},
"reason": "Reason",
"reason-placeholder": "Select a reason ...",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type ..."
},
"title": "Redact text"
}
},
"redaction-abbreviation": "C",
"references": "{count} {count, plural, one{reference} other{references}}",
"remove-annotations-dialog": {
@ -1917,6 +1962,41 @@
},
"value": "Value"
},
"remove-redaction": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"options": {
"false-positive": {
"description": "\"{value}\" is not a {type} in this context: \"{context}\".",
"label": "False positive"
},
"in-dossier": {
"description": "Do not redact \"{value}\" in any document of the current dossier.",
"label": "Remove in dossier"
},
"only-here": {
"description": "Do not redact \"{value}\" at this position in the current document.",
"label": "Remove here"
},
"redact": {
"false-positive": {
"extraOptionLabel": "Apply to all dossiers"
},
"in-dossier": {
"extraOptionLabel": "Apply to all dossiers"
}
}
}
},
"title": "Remove redaction"
}
},
"report-type": {
"label": "{length} report {length, plural, one{type} other{types}}"
},

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd" id="Redacted" stroke="none" stroke-width="1">
<circle fill="#94989f" cx="8" cy="8" r="8" />
<text fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="500"
id="H">
<tspan x="4" y="12">H</tspan>
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@ -3,7 +3,7 @@
xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd" id="Redacted" stroke="none" stroke-width="1">
<rect fill="#283241" height="16" id="Rectangle" width="16" x="0" y="0"></rect>
<text fill="#FFFFFF" font-family="Inter-SemiBold, Inter" font-size="11" font-weight="500"
<text fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="500"
id="H">
<tspan x="4.421875" y="12">R</tspan>
</text>

Before

Width:  |  Height:  |  Size: 519 B

After

Width:  |  Height:  |  Size: 526 B

View File

@ -0,0 +1,3 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.5002 10L79.4998 55H85V65H55V100H45V65H15V55H20.5002L24.4998 10H20V0H80V10H75.5002ZM34.4995 10L30.4998 55H69.4991L65.4995 10H34.4995Z" fill="#283241"/>
</svg>

After

Width:  |  Height:  |  Size: 311 B

@ -1 +1 @@
Subproject commit 2fe65233bc4186ba67c924efae4d82cfb8b89fe7
Subproject commit 6b45d4aa70554b5f41e59d689094d6dad8291c8e