Merge branch 'master' into VM/TestsIds

This commit is contained in:
Valentin Mihai 2022-03-28 22:10:28 +03:00
commit bbdff8e525
10 changed files with 154 additions and 149 deletions

View File

@ -13,11 +13,11 @@
<div *ngFor="let group of groups; let first = first"> <div *ngFor="let group of groups; let first = first">
<div class="all-caps-label flex-align-items-center"> <div class="all-caps-label flex-align-items-center">
<div>{{ group.date }}</div> <div>{{ group.date }}</div>
<div *ngIf="(hasUnreadNotifications$ | async) && first" class="view-all" (click)="markRead($event)">View all</div> <div (click)="markRead($event)" *ngIf="(hasUnreadNotifications$ | async) && first" class="view-all">View all</div>
</div> </div>
<div <div
(click)="markRead($event, [notification.id])" (click)="markRead($event, [notification], true)"
*ngFor="let notification of group.notifications | sortBy: 'desc':'creationDate'" *ngFor="let notification of group.notifications | sortBy: 'desc':'creationDate'"
[class.unread]="!notification.readDate" [class.unread]="!notification.readDate"
class="notification" class="notification"
@ -29,7 +29,7 @@
<div class="small-label mt-2">{{ notification.time }}</div> <div class="small-label mt-2">{{ notification.time }}</div>
</div> </div>
<div <div
(click)="markRead($event, [notification.id], !notification.readDate)" (click)="markRead($event, [notification], !notification.readDate)"
class="dot" class="dot"
matTooltip="{{ 'notifications.mark-as' | translate: { type: notification.readDate ? 'unread' : 'read' } }}" matTooltip="{{ 'notifications.mark-as' | translate: { type: notification.readDate ? 'unread' : 'read' } }}"
matTooltipPosition="before" matTooltipPosition="before"

View File

@ -30,6 +30,7 @@
position: relative; position: relative;
line-height: 18px; line-height: 18px;
align-items: flex-start; align-items: flex-start;
cursor: default;
.notification-content { .notification-content {
margin-top: 4px; margin-top: 4px;
@ -50,6 +51,7 @@
background-color: variables.$grey-4; background-color: variables.$grey-4;
top: 8px; top: 8px;
right: 8px; right: 8px;
cursor: pointer;
} }
&:hover { &:hover {

View File

@ -1,16 +1,13 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@shared/pipes/date.pipe'; import { DatePipe } from '@shared/pipes/date.pipe';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { NotificationsService } from '@services/notifications.service'; import { NotificationsService } from '@services/notifications.service';
import { Notification } from '@red/domain'; import { Notification } from '@red/domain';
import { distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { BehaviorSubject, firstValueFrom, Observable, timer } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { AutoUnsubscribe, List, shareLast } from '@iqser/common-ui'; import { shareLast } from '@iqser/common-ui';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
const INCLUDE_SEEN = false;
interface NotificationsGroup { interface NotificationsGroup {
date: string; date: string;
@ -23,11 +20,9 @@ interface NotificationsGroup {
styleUrls: ['./notifications.component.scss'], styleUrls: ['./notifications.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class NotificationsComponent extends AutoUnsubscribe implements OnInit { export class NotificationsComponent {
notifications$: Observable<Notification[]>; readonly hasUnreadNotifications$: Observable<boolean>;
hasUnreadNotifications$: Observable<boolean>; readonly groupedNotifications$: Observable<NotificationsGroup[]>;
groupedNotifications$: Observable<NotificationsGroup[]>;
private _notifications$ = new BehaviorSubject<Notification[]>([]);
constructor( constructor(
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
@ -36,40 +31,30 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit {
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
private readonly _datePipe: DatePipe, private readonly _datePipe: DatePipe,
) { ) {
super(); this.groupedNotifications$ = this._notificationsService.all$.pipe(map(notifications => this._groupNotifications(notifications)));
this.notifications$ = this._notifications$.asObservable().pipe(shareLast());
this.groupedNotifications$ = this.notifications$.pipe(map(notifications => this._groupNotifications(notifications)));
this.hasUnreadNotifications$ = this._hasUnreadNotifications$; this.hasUnreadNotifications$ = this._hasUnreadNotifications$;
} }
private get _hasUnreadNotifications$(): Observable<boolean> { private get _hasUnreadNotifications$(): Observable<boolean> {
return this.notifications$.pipe( return this._notificationsService.all$.pipe(
map(notifications => notifications.filter(n => !n.readDate).length > 0), map(notifications => notifications.filter(n => !n.readDate).length > 0),
distinctUntilChanged(), distinctUntilChanged(),
shareLast(), shareLast(),
); );
} }
async ngOnInit(): Promise<void> { async markRead($event, notifications: Notification[] = this._notificationsService.all, isRead = true): Promise<void> {
await this._loadData();
this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
.pipe(
switchMap(() => this._notificationsService.getNotificationsIfChanged(INCLUDE_SEEN)),
tap(notifications => this._notifications$.next(notifications)),
)
.subscribe();
}
async markRead($event, notifications: List<string> = this._notifications$.getValue().map(n => n.id), isRead = true): Promise<void> {
$event.stopPropagation(); $event.stopPropagation();
await firstValueFrom(this._notificationsService.toggleNotificationRead(notifications, isRead)); if (!notifications.find(notification => !!notification.readDate !== isRead)) {
await this._loadData(); // If no notification changes status after the request, abort
} return;
}
private async _loadData(): Promise<void> { await firstValueFrom(
const notifications = await firstValueFrom(this._notificationsService.getNotifications(INCLUDE_SEEN)); this._notificationsService.toggleNotificationRead(
this._notifications$.next(notifications); notifications.map(n => n.id),
isRead,
),
);
} }
private _groupNotifications(notifications: Notification[]): NotificationsGroup[] { private _groupNotifications(notifications: Notification[]): NotificationsGroup[] {

View File

@ -22,7 +22,7 @@ export class AccountSideNavComponent {
}, },
{ {
screen: 'notifications', screen: 'notifications',
label: _('notifications'), label: _('notifications.label'),
hideIf: !this._userService.currentUser.isUser, hideIf: !this._userService.currentUser.isUser,
}, },
]; ];

View File

@ -1,12 +1,13 @@
import { Component, OnDestroy } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { lastIndexOfEnd } from '@utils/functions'; import { lastIndexOfEnd } from '@utils/functions';
import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { RouterHistoryService } from '@services/router-history.service'; import { RouterHistoryService } from '@services/router-history.service';
import { DigitalSignatureService } from '../../services/digital-signature.service'; import { DigitalSignatureService } from '../../services/digital-signature.service';
import { IDigitalSignature, IDigitalSignatureRequest } from '@red/domain'; import { IDigitalSignature } from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { HttpStatusCode } from '@angular/common/http'; import { HttpStatusCode } from '@angular/common/http';
@Component({ @Component({
@ -14,7 +15,7 @@ import { HttpStatusCode } from '@angular/common/http';
templateUrl: './digital-signature-screen.component.html', templateUrl: './digital-signature-screen.component.html',
styleUrls: ['./digital-signature-screen.component.scss'], styleUrls: ['./digital-signature-screen.component.scss'],
}) })
export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements OnDestroy { export class DigitalSignatureScreenComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly currentUser = this._userService.currentUser; readonly currentUser = this._userService.currentUser;
@ -30,16 +31,18 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
readonly routerHistoryService: RouterHistoryService, readonly routerHistoryService: RouterHistoryService,
private readonly _digitalSignatureService: DigitalSignatureService, private readonly _digitalSignatureService: DigitalSignatureService,
) { ) {}
super();
this.loadDigitalSignatureAndInitializeForm();
}
get hasDigitalSignatureSet() { get hasDigitalSignatureSet() {
return this.digitalSignatureExists || !!this.form.get('base64EncodedPrivateKey').value; return this.digitalSignatureExists || !!this.form.get('base64EncodedPrivateKey').value;
} }
saveDigitalSignature() { async ngOnInit(): Promise<void> {
await this.loadDigitalSignatureAndInitializeForm();
}
async saveDigitalSignature(): Promise<void> {
this._loadingService.start();
const formValue = this.form.getRawValue(); const formValue = this.form.getRawValue();
const digitalSignature: IDigitalSignature = { const digitalSignature: IDigitalSignature = {
...formValue, ...formValue,
@ -51,29 +54,32 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
? this._digitalSignatureService.update(digitalSignature) ? this._digitalSignatureService.update(digitalSignature)
: this._digitalSignatureService.save(digitalSignature); : this._digitalSignatureService.save(digitalSignature);
this.addSubscription = observable.subscribe({ try {
next: () => { await firstValueFrom(observable);
this.loadDigitalSignatureAndInitializeForm(); await this.loadDigitalSignatureAndInitializeForm();
this._toaster.success(_('digital-signature-screen.action.save-success')); this._toaster.success(_('digital-signature-screen.action.save-success'));
}, } catch (error) {
error: error => { console.error(error);
if (error.status === HttpStatusCode.BadRequest) { if (error.status === HttpStatusCode.BadRequest) {
this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error')); this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
} else { } else {
this._toaster.error(_('digital-signature-screen.action.save-error')); this._toaster.error(_('digital-signature-screen.action.save-error'));
} }
}, }
});
this._loadingService.stop();
} }
removeDigitalSignature() { async removeDigitalSignature(): Promise<void> {
this.addSubscription = this._digitalSignatureService.delete().subscribe( this._loadingService.start();
() => { try {
this.loadDigitalSignatureAndInitializeForm(); await firstValueFrom(this._digitalSignatureService.delete());
this._toaster.success(_('digital-signature-screen.action.delete-success')); await this.loadDigitalSignatureAndInitializeForm();
}, this._toaster.success(_('digital-signature-screen.action.delete-success'));
() => this._toaster.error(_('digital-signature-screen.action.delete-error')), } catch (error) {
); console.error(error);
this._toaster.error(_('digital-signature-screen.action.delete-error'));
}
} }
fileChanged(event, input: HTMLInputElement) { fileChanged(event, input: HTMLInputElement) {
@ -89,24 +95,19 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
fileReader.readAsDataURL(file as Blob); fileReader.readAsDataURL(file as Blob);
} }
loadDigitalSignatureAndInitializeForm() { async loadDigitalSignatureAndInitializeForm(): Promise<void> {
this._loadingService.start(); this._loadingService.start();
this._digitalSignatureService try {
.getSignature() const digitalSignature = await firstValueFrom(this._digitalSignatureService.getSignature());
.subscribe({ this.digitalSignatureExists = true;
next: digitalSignature => { this.digitalSignature = digitalSignature;
this.digitalSignatureExists = true; } catch (error) {
this.digitalSignature = digitalSignature; this.digitalSignatureExists = false;
}, this.digitalSignature = {};
error: () => { }
this.digitalSignatureExists = false;
this.digitalSignature = {}; this.form = this._getForm();
}, this._loadingService.stop();
})
.add(() => {
this.form = this._getForm();
this._loadingService.stop();
});
} }
private _getForm(): FormGroup { private _getForm(): FormGroup {

View File

@ -1,56 +1,65 @@
import { Injectable, Injector } from '@angular/core'; import { Inject, Injectable, Injector } from '@angular/core';
import { GenericService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { EMPTY, iif, Observable } from 'rxjs'; import { EMPTY, iif, Observable, of, timer } from 'rxjs';
import { INotification, Notification, NotificationTypes } from '@red/domain'; import { Dossier, INotification, Notification, NotificationTypes } from '@red/domain';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap, tap } from 'rxjs/operators';
import { notificationsTranslations } from '../translations/notifications-translations'; import { notificationsTranslations } from '../translations/notifications-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ActiveDossiersService } from './dossiers/active-dossiers.service'; import { ActiveDossiersService } from './dossiers/active-dossiers.service';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { BASE_HREF } from '../tokens';
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
const INCLUDE_SEEN = false;
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class NotificationsService extends GenericService<Notification> { export class NotificationsService extends EntitiesService<Notification, INotification> {
constructor( constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
protected readonly _injector: Injector, protected readonly _injector: Injector,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
private readonly _archivedDossiersService: ArchivedDossiersService,
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _filesMapService: FilesMapService,
) { ) {
super(_injector, 'notification'); super(_injector, Notification, 'notification');
timer(0, CHANGED_CHECK_INTERVAL)
.pipe(
switchMap(() => (this._activeDossiersService.all.length ? of(null) : this._activeDossiersService.loadAll())),
switchMap(() => (this._archivedDossiersService.all.length ? of(null) : this._archivedDossiersService.loadAll())),
switchMap(() => this.#loadNotificationsIfChanged()),
)
.subscribe();
} }
@Validate() @Validate()
getNotifications(@RequiredParam() includeSeen: boolean): Observable<Notification[]> { toggleNotificationRead(@RequiredParam() body: List, @RequiredParam() setRead: boolean): Observable<unknown> {
let queryParam: QueryParam;
if (includeSeen !== undefined && includeSeen !== null) {
queryParam = { key: 'includeSeen', value: includeSeen };
}
return this.getAll<{ notifications: Notification[] }>(this._defaultModelPath, [queryParam]).pipe(
map(response => response.notifications.filter(n => n.notificationType in NotificationTypes)),
mapEach(notification => this._new(notification)),
);
}
@Validate()
getNotificationsIfChanged(@RequiredParam() includeSeen: boolean): Observable<Notification[]> {
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.getNotifications(includeSeen), EMPTY)));
}
@Validate()
toggleNotificationRead(@RequiredParam() body: List, @RequiredParam() setRead: boolean) {
let queryParam: QueryParam; let queryParam: QueryParam;
if (setRead !== undefined && setRead !== null) { if (setRead !== undefined && setRead !== null) {
queryParam = { key: 'setRead', value: setRead }; queryParam = { key: 'setRead', value: setRead };
} }
return this._post(body, `${this._defaultModelPath}/toggle-read`, [queryParam]); return this._post(body, `${this._defaultModelPath}/toggle-read`, [queryParam]).pipe(switchMap(() => this.#loadAll()));
}
#loadAll(includeSeen = INCLUDE_SEEN): Observable<Notification[]> {
const queryParam: QueryParam = { key: 'includeSeen', value: includeSeen };
return this.getAll<{ notifications: Notification[] }>(this._defaultModelPath, [queryParam]).pipe(
map(response => response.notifications.filter(n => n.notificationType in NotificationTypes)),
mapEach(notification => this._new(notification)),
tap(notifications => this.setEntities(notifications)),
);
}
#loadNotificationsIfChanged(): Observable<Notification[]> {
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.#loadAll(), EMPTY)));
} }
private _new(notification: INotification) { private _new(notification: INotification) {
@ -66,25 +75,27 @@ export class NotificationsService extends GenericService<Notification> {
private _translate(notification: INotification, translation: string): string { private _translate(notification: INotification, translation: string): string {
const fileId = notification.target.fileId; const fileId = notification.target.fileId;
const dossierId = notification.target.dossierId; const dossierId = notification.target.dossierId;
const dossier = this._activeDossiersService.find(dossierId); const dossier = this._activeDossiersService.find(dossierId) || this._archivedDossiersService.find(dossierId);
const fileName = notification.target.fileName; const fileName = notification.target.fileName;
return this._translateService.instant(translation, { return this._translateService.instant(translation, {
fileHref: this._getFileHref(dossierId, fileId), fileHref: this._getFileHref(dossier, fileId),
dossierHref: this._getDossierHref(dossierId), dossierHref: this._getDossierHref(dossier),
dossierName: notification.target?.dossierName || dossier?.dossierName || this._translateService.instant(_('dossier')), dossierName:
notification.target?.dossierName ||
dossier?.dossierName ||
this._translateService.instant(_('notifications.deleted-dossier')),
fileName: fileName || this._translateService.instant(_('file')), fileName: fileName || this._translateService.instant(_('file')),
user: this._getUsername(notification.userId), user: this._getUsername(notification.userId),
}); });
} }
private _getFileHref(dossierId: string, fileId: string): string { private _getFileHref(dossier: Dossier, fileId: string): string {
const dossierHref = this._getDossierHref(dossierId); return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
return `${dossierHref}/file/${fileId}`;
} }
private _getDossierHref(dossierId: string): string { private _getDossierHref(dossier: Dossier): string {
return `/ui/main/dossiers/${dossierId}`; return dossier ? `${this._baseHref}${dossier.routerLink}` : null;
} }
private _getUsername(userId: string | undefined) { private _getUsername(userId: string | undefined) {

View File

@ -665,7 +665,6 @@
"save": "Dokumenteninformation speichern", "save": "Dokumenteninformation speichern",
"title": "Datei-Attribute anlegen" "title": "Datei-Attribute anlegen"
}, },
"dossier": "Dossier",
"dossier-attribute-types": { "dossier-attribute-types": {
"date": "Datum", "date": "Datum",
"image": "Bild", "image": "Bild",
@ -1530,21 +1529,20 @@
} }
}, },
"notification": { "notification": {
"assign-approver": "Sie wurden dem Dokument <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> im Dossier <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b> als Genehmiger zugewiesen!", "assign-approver": "Sie wurden dem Dokument <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}<b> als Genehmiger zugewiesen!",
"assign-reviewer": "Sie wurden dem Dokument <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> im Dossier <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b> als Reviewer zugewiesen!", "assign-reviewer": "Sie wurden dem Dokument <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}<b> als Reviewer zugewiesen!",
"document-approved": "<b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> wurde genehmigt!", "document-approved": "<b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> wurde genehmigt!",
"dossier-deleted": "Dossier: <b>{dossierName}</b> wurde gelöscht!", "dossier-deleted": "Dossier: <b>{dossierName}</b> wurde gelöscht!",
"dossier-owner-removed": "Der Dossier-Owner von <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> wurde entfernt!", "dossier-owner-removed": "Der Dossier-Owner von <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> wurde entfernt!",
"dossier-owner-set": "Eigentümer von <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> geändert zu <b>{user}</b>!", "dossier-owner-set": "Eigentümer von <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> geändert zu <b>{user}</b>!",
"download-ready": "Ihr <b><a href='/ui/main/downloads', target=\"_blank\">Download</a></b> ist fertig!", "download-ready": "Ihr <b><a href='/ui/main/downloads', target=\"_blank\">Download</a></b> ist fertig!",
"no-data": "Du hast aktuell keine Benachrichtigungen", "no-data": "Du hast aktuell keine Benachrichtigungen",
"unassigned-from-file": "Sie wurden vom Dokument <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> im Dossier <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b> entfernt!", "unassigned-from-file": "Sie wurden vom Dokument <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}<b> entfernt!",
"user-becomes-dossier-member": "<b>{user}</b> ist jetzt Mitglied des Dossiers <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!", "user-becomes-dossier-member": "<b>{user}</b> ist jetzt Mitglied des Dossiers <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"user-demoted-to-reviewer": "<b>{user}</b> wurde im Dossier <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> auf die Reviewer-Berechtigung heruntergestuft!", "user-demoted-to-reviewer": "<b>{user}</b> wurde im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> auf die Reviewer-Berechtigung heruntergestuft!",
"user-promoted-to-approver": "<b>{user}</b> wurde im Dossier <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> zum Genehmiger ernannt!", "user-promoted-to-approver": "<b>{user}</b> wurde im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> zum Genehmiger ernannt!",
"user-removed-as-dossier-member": "<b>{user}</b> wurde als Mitglied von: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> entfernt!" "user-removed-as-dossier-member": "<b>{user}</b> wurde als Mitglied von: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> entfernt!"
}, },
"notifications": "Benachrichtigungen",
"notifications-screen": { "notifications-screen": {
"category": { "category": {
"email-notifications": "E-Mail Benachrichtigungen", "email-notifications": "E-Mail Benachrichtigungen",
@ -1583,6 +1581,11 @@
}, },
"title": "Benachrichtigungseinstellungen" "title": "Benachrichtigungseinstellungen"
}, },
"notifications": {
"deleted-dossier": "",
"label": "Benachrichtigungen",
"mark-as": ""
},
"ocr": { "ocr": {
"confirmation-dialog": { "confirmation-dialog": {
"cancel": "", "cancel": "",

View File

@ -665,7 +665,6 @@
"save": "Save Document Info", "save": "Save Document Info",
"title": "Introduce File Attributes" "title": "Introduce File Attributes"
}, },
"dossier": "Dossier",
"dossier-attribute-types": { "dossier-attribute-types": {
"date": "Date", "date": "Date",
"image": "Image", "image": "Image",
@ -1530,21 +1529,20 @@
} }
}, },
"notification": { "notification": {
"assign-approver": "You have been assigned as approver for <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!", "assign-approver": "You have been assigned as approver for <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"assign-reviewer": "You have been assigned as reviewer for <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!", "assign-reviewer": "You have been assigned as reviewer for <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"document-approved": " <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> has been approved!", "document-approved": " <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> has been approved!",
"dossier-deleted": "Dossier: <b>{dossierName}</b> has been deleted!", "dossier-deleted": "Dossier: <b>{dossierName}</b> has been deleted!",
"dossier-owner-removed": "You have been removed as dossier owner from <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!", "dossier-owner-removed": "You have been removed as dossier owner from <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"dossier-owner-set": "You are now the dossier owner of <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!", "dossier-owner-set": "You are now the dossier owner of <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"download-ready": "Your <b><a href='/ui/main/downloads', target=\"_blank\">download</a></b> is ready!", "download-ready": "Your <b><a href='/ui/main/downloads', target=\"_blank\">download</a></b> is ready!",
"no-data": "You currently have no notifications", "no-data": "You currently have no notifications",
"unassigned-from-file": "You have been unassigned from <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!", "unassigned-from-file": "You have been unassigned from <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}}}</b>!",
"user-becomes-dossier-member": "You have been added to dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!", "user-becomes-dossier-member": "You have been added to dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"user-demoted-to-reviewer": "You have been demoted to reviewer in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!", "user-demoted-to-reviewer": "You have been demoted to reviewer in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"user-promoted-to-approver": "You have been promoted to approver in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!", "user-promoted-to-approver": "You have been promoted to approver in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"user-removed-as-dossier-member": "You have been removed as a member from dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> !" "user-removed-as-dossier-member": "You have been removed as a member from dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!"
}, },
"notifications": "Notifications",
"notifications-screen": { "notifications-screen": {
"category": { "category": {
"email-notifications": "Email Notifications", "email-notifications": "Email Notifications",
@ -1583,6 +1581,11 @@
}, },
"title": "Notifications Preferences" "title": "Notifications Preferences"
}, },
"notifications": {
"deleted-dossier": "Deleted Dossier",
"label": "Notifications",
"mark-as": "Mark as {type, select, read{read} unread{unread} other{}}"
},
"ocr": { "ocr": {
"confirmation-dialog": { "confirmation-dialog": {
"cancel": "Cancel", "cancel": "Cancel",

View File

@ -1,6 +1,6 @@
{ {
"name": "redaction", "name": "redaction",
"version": "3.376.0", "version": "3.377.0",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {

Binary file not shown.