diff --git a/apps/red-ui/src/app/app.component.html b/apps/red-ui/src/app/app.component.html index ff64625af..20a8fecb2 100644 --- a/apps/red-ui/src/app/app.component.html +++ b/apps/red-ui/src/app/app.component.html @@ -1,3 +1,4 @@ + diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts index b691bd9c5..9e4809505 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { Debounce, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { TranslateService } from '@ngx-translate/core'; @@ -38,6 +38,7 @@ export class RulesScreenComponent extends ComponentHasChanges implements OnInit constructor( readonly permissionsService: PermissionsService, private readonly _rulesService: RulesService, + private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _toaster: Toaster, protected readonly _translateService: TranslateService, @@ -74,6 +75,7 @@ export class RulesScreenComponent extends ComponentHasChanges implements OnInit }, }); (window as any).monaco.editor.setTheme('redaction'); + this._changeDetectorRef.detectChanges(); } @Debounce() @@ -106,6 +108,7 @@ export class RulesScreenComponent extends ComponentHasChanges implements OnInit revert(): void { this.currentLines = this.initialLines; this._decorations = this._codeEditor?.deltaDecorations(this._decorations, []) || []; + this._changeDetectorRef.detectChanges(); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html index 726110f51..96a77da55 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html @@ -100,7 +100,7 @@ - + diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts index bf8865bdd..19e8e7e71 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts @@ -15,19 +15,19 @@ import { FileDropOverlayService } from '@upload-download/services/file-drop-over import { FileUploadModel } from '@upload-download/model/file-upload.model'; import { FileUploadService } from '@upload-download/services/file-upload.service'; import { StatusOverlayService } from '@upload-download/services/status-overlay.service'; -import * as moment from 'moment'; -import { Observable, timer } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { filter, skip, switchMap, tap } from 'rxjs/operators'; import { convertFiles, Files, handleFileDrop } from '@utils/index'; import { CircleButtonTypes, + CustomError, DefaultListingServices, + ErrorService, ListingComponent, ListingModes, LoadingService, NestedFilter, OnAttach, - OnDetach, TableColumnConfig, TableComponent, WorkflowConfig, @@ -45,7 +45,7 @@ import { LongPressEvent } from '@shared/directives/long-press.directive'; import { UserPreferenceService } from '@services/user-preference.service'; import { FilesMapService } from '@services/entity-services/files-map.service'; import { FilesService } from '@services/entity-services/files.service'; -import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; +import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -53,7 +53,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DossierOverviewScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnDetach, OnAttach { +export class DossierOverviewScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnAttach { readonly listingModes = ListingModes; readonly circleButtonTypes = CircleButtonTypes; readonly tableHeaderLabel = _('dossier-overview.table-header.title'); @@ -74,6 +74,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple @ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef; @ViewChild(TableComponent) private readonly _tableComponent: TableComponent; private _fileAttributeConfigs: IFileAttributeConfig[]; + private readonly _removableSubscriptions = new Subscription(); constructor( protected readonly _injector: Injector, @@ -81,6 +82,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple readonly permissionsService: PermissionsService, private readonly _loadingService: LoadingService, private readonly _dossiersService: DossiersService, + private readonly _dossierStatsService: DossierStatsService, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _appConfigService: AppConfigService, private readonly _fileUploadService: FileUploadService, @@ -92,6 +94,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple readonly configService: ConfigService, private readonly _userPreferenceService: UserPreferenceService, private readonly _fileMapService: FilesMapService, + private readonly _errorService: ErrorService, activatedRoute: ActivatedRoute, ) { super(_injector); @@ -128,18 +131,17 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this._fileDropOverlayService.initFileDropHandling(this.dossierId); - this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) - .pipe( - switchMap(() => this._filesService.hasChanges$(this.dossierId)), - filter(changed => changed), - switchMap(() => this._reloadFiles()), - ) - .subscribe(); - this.addSubscription = this.configService.listingMode$.subscribe(() => { this._computeAllFilters(); }); + this.addSubscription = this._dossiersService.dossierFileChanges$ + .pipe( + filter(dossierId => dossierId === this.dossierId), + switchMap(dossierId => this._filesService.loadAll(dossierId)), + ) + .subscribe(); + this.addSubscription = this._dossierTemplatesService .getEntityChanged$(this.currentDossier.dossierTemplateId) .pipe( @@ -166,11 +168,10 @@ export class DossierOverviewScreenComponent extends ListingComponent imple ngOnAttach() { this._fileDropOverlayService.initFileDropHandling(this.dossierId); + this._setRemovableSubscriptions(); this._tableComponent?.scrollToLastIndex(); } - ngOnDetach() {} - forceReanalysisAction($event: LongPressEvent) { this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled; } @@ -192,8 +193,20 @@ export class DossierOverviewScreenComponent extends ListingComponent imple (this._fileInput as any).nativeElement.value = null; } - recentlyModifiedChecker = (file: File) => - moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment()); + private _setRemovableSubscriptions(): void { + this._removableSubscriptions.add( + this._dossiersService + .getEntityDeleted$(this.dossierId) + .pipe(tap(() => this._handleDeletedDossier())) + .subscribe(), + ); + } + + private _handleDeletedDossier(): void { + this._errorService.set( + new CustomError(_('error.deleted-entity.dossier.label'), _('error.deleted-entity.dossier.action'), 'iqser:expand'), + ); + } private _updateFileAttributes(): void { this._fileAttributeConfigs = @@ -204,11 +217,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this._computeAllFilters(); } - private async _reloadFiles() { - await this._filesService.loadAll(this.dossierId).toPromise(); - this._computeAllFilters(); - } - private _loadEntitiesFromState() { this.currentDossier = this._dossiersService.find(this.dossierId); this._computeAllFilters(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html index 6dec76bb6..c37bd2a84 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html @@ -12,7 +12,7 @@ *ngIf="currentUser.isUser" [tooltip]="(currentUser.isManager ? 'dossier-listing.edit.action' : 'dossier-listing.dossier-info.action') | translate" [type]="circleButtonTypes.dark" - [icon]="currentUser.isManager ? 'iqser:edit' : 'iqser:dossier-info'" + [icon]="currentUser.isManager ? 'iqser:edit' : 'red:info'" iqserHelpMode="edit-dossier-from-list" > diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html index 7f8d42a1e..c12eabe47 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html @@ -9,28 +9,28 @@ -
-
+
+
{{ dossierStats.numberOfFiles }}
-
+
{{ dossierStats.numberOfPages }}
-
+
{{ dossier.memberIds.length }}
-
+
{{ dossier.date | date: 'mediumDate' }}
-
+
{{ dossier.dueDate | date: 'mediumDate' }}
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts index 5a270d9fc..bbc7be107 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts @@ -3,6 +3,9 @@ import { Dossier, DossierStats } from '@red/domain'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; +import * as moment from 'moment'; + +const DUE_DATE_WARN_DAYS = 14; @Component({ selector: 'redaction-dossiers-listing-dossier-name', @@ -20,6 +23,18 @@ export class DossiersListingDossierNameComponent { private readonly _dossiersService: DossiersService, ) {} + get approachingDueDate(): boolean { + return this._dueDateDaysDiff >= 0 && this._dueDateDaysDiff <= DUE_DATE_WARN_DAYS; + } + + get passedDueDate(): boolean { + return this._dueDateDaysDiff < 0; + } + + private get _dueDateDaysDiff(): number { + return moment(this.dossier.dueDate).diff(moment().startOf('day'), 'days'); + } + getDossierTemplateNameFor(dossierTemplateId: string): string { return this._dossierTemplatesService.find(dossierTemplateId).name; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts index c7ba3f10b..d70a2ae00 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts @@ -1,19 +1,17 @@ -import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Dossier } from '@red/domain'; import { UserService } from '@services/user.service'; import { PermissionsService } from '@services/permissions.service'; import { TranslateChartService } from '@services/translate-chart.service'; -import { timer } from 'rxjs'; import { Router } from '@angular/router'; import { DossiersDialogService } from '../../../services/dossiers-dialog.service'; -import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, OnDetach, TableComponent } from '@iqser/common-ui'; +import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ConfigService } from '../config.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { FilesService } from '@services/entity-services/files.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { switchMap, tap } from 'rxjs/operators'; -import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; +import { tap } from 'rxjs/operators'; @Component({ templateUrl: './dossiers-listing-screen.component.html', @@ -25,7 +23,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { +export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnAttach { readonly currentUser = this._userService.currentUser; readonly tableColumnConfigs = this._configService.tableConfig; readonly tableHeaderLabel = _('dossier-listing.table-header.title'); @@ -57,22 +55,13 @@ export class DossiersListingScreenComponent extends ListingComponent im } ngOnInit(): void { - this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) - .pipe(switchMap(() => this._dossiersService.loadAllIfChanged())) - .subscribe(); - this.addSubscription = this._dossiersService.all$.pipe(tap(() => this._computeAllFilters())).subscribe(); } ngOnAttach(): void { - this.ngOnInit(); this._tableComponent?.scrollToLastIndex(); } - ngOnDetach(): void { - this.ngOnDestroy(); - } - openAddDossierDialog(): void { this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => { await this._router.navigate([addResponse.dossier.routerLink]); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index 445d22bbd..4ff4c076a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -5,7 +5,9 @@ import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component import { AutoUnsubscribe, CircleButtonTypes, + CustomError, Debounce, + ErrorService, FilterService, LoadingService, OnAttach, @@ -43,19 +45,12 @@ import { ViewModeService } from './services/view-mode.service'; import { MultiSelectService } from './services/multi-select.service'; import { DocumentInfoService } from './services/document-info.service'; import { ReanalysisService } from '../../../../services/reanalysis.service'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import Annotation = Core.Annotations.Annotation; import PDFNet = Core.PDFNet; const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown']; -function diff(first: readonly T[], second: readonly T[]): T[] { - // symmetrical difference between two arrays - const a = new Set(first); - const b = new Set(second); - - return [...first.filter(x => !b.has(x)), ...second.filter(x => !a.has(x))]; -} - @Component({ templateUrl: './file-preview-screen.component.html', styleUrls: ['./file-preview-screen.component.scss'], @@ -108,6 +103,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private readonly _filesMapService: FilesMapService, private readonly _dossiersService: DossiersService, private readonly _reanalysisService: ReanalysisService, + private readonly _errorService: ErrorService, readonly excludedPagesService: ExcludedPagesService, readonly viewModeService: ViewModeService, readonly multiSelectService: MultiSelectService, @@ -190,7 +186,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni ngOnDetach(): void { this.displayPdfViewer = false; - super.ngOnDestroy(); + super.ngOnDetach(); this._changeDetectorRef.markForCheck(); } @@ -529,10 +525,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } private _subscribeToFileUpdates(): void { - this.addSubscription = timer(0, 5000) + this.addActiveScreenSubscription = timer(0, 5000) .pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId))) .subscribe(); - this.addSubscription = this._filesMapService.fileReanalysed$ + this.addActiveScreenSubscription = this._filesMapService.fileReanalysed$ .pipe(filter(file => file.fileId === this.fileId)) .subscribe(async file => { if (file.lastProcessed !== this.fileData?.file.lastProcessed) { @@ -541,6 +537,28 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } this._loadingService.stop(); }); + + this.addActiveScreenSubscription = this._dossiersService + .getEntityDeleted$(this.dossierId) + .pipe(tap(() => this._handleDeletedDossier())) + .subscribe(); + + this.addActiveScreenSubscription = this._filesMapService + .watchDeleted$(this.fileId) + .pipe(tap(() => this._handleDeletedFile())) + .subscribe(); + } + + private _handleDeletedDossier(): void { + this._errorService.set( + new CustomError(_('error.deleted-entity.file-dossier.label'), _('error.deleted-entity.file-dossier.action'), 'iqser:expand'), + ); + } + + private _handleDeletedFile(): void { + this._errorService.set( + new CustomError(_('error.deleted-entity.file.label'), _('error.deleted-entity.file.action'), 'iqser:expand'), + ); } private async _loadFileData(file: File): Promise { diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts index 150f44c55..95a6427cd 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, EventEmitter, HostBinding, @@ -96,6 +97,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, private readonly _userPreferenceService: UserPreferenceService, private readonly _reanalysisService: ReanalysisService, private readonly _router: Router, + private readonly _changeRef: ChangeDetectorRef, ) { super(); } @@ -224,7 +226,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, } ngOnInit() { - this._dossiersService.getEntityChanged$(this.file.dossierId).pipe(tap(() => this._setup())); + this.addSubscription = this._dossiersService + .getEntityChanged$(this.file.dossierId) + .pipe(tap(() => this._setup())) + .subscribe(); } ngOnChanges() { @@ -353,6 +358,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced; this.buttons = this._buttons; + + this._changeRef.markForCheck(); } private async _setFileApproved() { diff --git a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts index 7f4bbdc68..5d14236fe 100644 --- a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts @@ -2,16 +2,27 @@ import { Injectable, Injector } from '@angular/core'; import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui'; import { Dossier, IDossier, IDossierRequest } from '@red/domain'; import { catchError, filter, map, mapTo, switchMap, tap } from 'rxjs/operators'; -import { combineLatest, iif, Observable, of, throwError } from 'rxjs'; +import { combineLatest, Observable, of, Subject, throwError, timer } from 'rxjs'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; +import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; export interface IDossiersStats { totalPeople: number; totalAnalyzedPages: number; } +interface ChangesDetails { + dossierChanges: [ + { + dossierChanges: boolean; + dossierId: string; + fileChanges: boolean; + }, + ]; +} + const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists'); const GENERIC_MGS = _('add-dossier-dialog.errors.generic'); @@ -20,6 +31,7 @@ const GENERIC_MGS = _('add-dossier-dialog.errors.generic'); }) export class DossiersService extends EntitiesService { readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities))); + readonly dossierFileChanges$ = new Subject(); constructor( private readonly _toaster: Toaster, @@ -27,6 +39,13 @@ export class DossiersService extends EntitiesService { private readonly _dossierStatsService: DossierStatsService, ) { super(_injector, Dossier, 'dossier'); + + timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) + .pipe( + switchMap(() => this.loadAllIfChanged()), + tap(changes => this._emitFileChanges(changes)), + ) + .subscribe(); } loadAll(): Observable { @@ -39,8 +58,15 @@ export class DossiersService extends EntitiesService { ); } - loadAllIfChanged(): Observable { - return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll()).pipe(mapTo(changed)))); + loadAllIfChanged(): Observable { + return this.hasChangesDetails$().pipe(switchMap(changes => this.loadAll().pipe(mapTo(changes)))); + } + + hasChangesDetails$(): Observable { + const body = { value: this._lastCheckedForChanges.get('root') ?? '0' }; + return this._post(body, `${this._defaultModelPath}/changes/details`).pipe( + filter(changes => changes.dossierChanges.length > 0), + ); } @Validate() @@ -82,6 +108,10 @@ export class DossiersService extends EntitiesService { return super.delete(body, 'deleted-dossiers/hard-delete', body).toPromise(); } + private _emitFileChanges(changes: ChangesDetails): void { + changes.dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId)); + } + private _computeStats(entities: List): IDossiersStats { let totalAnalyzedPages = 0; const totalPeople = new Set(); diff --git a/apps/red-ui/src/app/services/entity-services/files-map.service.ts b/apps/red-ui/src/app/services/entity-services/files-map.service.ts index b861090da..9d04a9d99 100644 --- a/apps/red-ui/src/app/services/entity-services/files-map.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files-map.service.ts @@ -7,6 +7,7 @@ import { filter, startWith } from 'rxjs/operators'; export class FilesMapService { readonly fileReanalysed$ = new Subject(); private readonly _entityChanged$ = new Subject(); + private readonly _entityDeleted$ = new Subject(); private readonly _map = new Map>(); get$(dossierId: string) { @@ -39,6 +40,7 @@ export class FilesMapService { const reanalysedEntities = []; const changedEntities = []; + const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id)); // Keep old object references for unchanged entities const newEntities = entities.map(newEntity => { @@ -67,6 +69,10 @@ export class FilesMapService { for (const file of changedEntities) { this._entityChanged$.next(file); } + + for (const file of deletedEntities) { + this._entityDeleted$.next(file); + } } replace(entity: File) { @@ -83,4 +89,8 @@ export class FilesMapService { startWith(this.get(key, entityId)), ); } + + watchDeleted$(entityId: string): Observable { + return this._entityDeleted$.pipe(filter(entity => entity.id === entityId)); + } } diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index db23cec37..ed5fadd09 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -944,6 +944,20 @@ "side-nav-title": "Configurations" }, "error": { + "deleted-entity": { + "dossier": { + "action": "Back to overview", + "label": "This dossier has been deleted!" + }, + "file-dossier": { + "action": "Back to overview", + "label": "The dossier of this file has been deleted!" + }, + "file": { + "action": "Back to dossier", + "label": "This file has been deleted!" + } + }, "http": { "generic": "Action failed with code {status}" }, diff --git a/libs/common-ui b/libs/common-ui index afed41403..f7004520a 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit afed414030f936da36aa993a1945591b5c451861 +Subproject commit f7004520a71b36a00109115a5f4e860f918dcca9 diff --git a/package.json b/package.json index 3fb6881a0..1cf394330 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redaction", - "version": "3.143.0", + "version": "3.148.0", "private": true, "license": "MIT", "scripts": { diff --git a/paligo-theme.tar.gz b/paligo-theme.tar.gz index e07249f0a..3bb6a8861 100644 Binary files a/paligo-theme.tar.gz and b/paligo-theme.tar.gz differ diff --git a/paligo-theme/paligo-styles/redacto-theme.css b/paligo-theme/paligo-styles/redacto-theme.css index 5ceef3777..f5622f730 100644 --- a/paligo-theme/paligo-styles/redacto-theme.css +++ b/paligo-theme/paligo-styles/redacto-theme.css @@ -1,481 +1,482 @@ @charset "UTF-8"; -@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap'); .portal-header { - z-index: 1; - height: 450px; + z-index: 1; + height: 450px; } .portal-header::before { - background-color: #283241; + background-color: #283241; } .portal-header h1 { - font-size: 64px; - font-weight: 300; - line-height: 87px; - margin-top: 68px; - margin-bottom: 40px; + font-size: 64px; + font-weight: 300; + line-height: 87px; + margin-top: 68px; + margin-bottom: 40px; } .portal-header .portal-search { - max-width: 600px; - margin: auto; - position: relative; + max-width: 600px; + margin: auto; + position: relative; } .portal-header .portal-search .search-field { - width: 100%; - border: 1px solid #d3d5da; - border-radius: 8px; - background-color: #fff; + width: 100%; + border: 1px solid #d3d5da; + border-radius: 8px; + background-color: #fff; } .portal-header .portal-search .search-field::placeholder { - opacity: 0.7; + opacity: 0.7; } .portal-header .portal-search .search-field, .portal-header .portal-search .search-field::placeholder { - color: #283241; - font-size: 14px; - line-height: 18px; + color: #283241; + font-size: 14px; + line-height: 18px; } .portal-header .portal-search .search-field { - padding: 12px 17px; + padding: 12px 17px; } .portal-header .portal-search .btn { - position: absolute; - right: 0; - padding: 11px 18px; - background-color: transparent; - color: #283241; - cursor: pointer; - border-radius: 0 8px 8px 0; + position: absolute; + right: 0; + padding: 11px 18px; + background-color: transparent; + color: #283241; + cursor: pointer; + border-radius: 0 8px 8px 0; } .portal-header .portal-search .btn:hover { - background-color: #dd4d50; + background-color: #dd4d50; } @media only screen and (max-width: 768px) { - .portal-header h1 { - font-size: 42px; - font-weight: 300; - line-height: 57px; - } + .portal-header h1 { + font-size: 42px; + font-weight: 300; + line-height: 57px; + } } .featured-content-label { - display: none; + display: none; } .featured-content { - display: none; + display: none; } .portal-single-publication { - background-color: transparent; + background-color: transparent; } .portal-single-publication > a { - border-radius: 4px; + border-radius: 4px; } .portal-single-publication .publication-icon { - background-color: #dd4d50; + background-color: #dd4d50; } .portal-contents { - margin-top: 100px; - margin-bottom: 0; + margin-top: 100px; + margin-bottom: 0; } .portal-contents .inner { - margin: 0; - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 24px; + margin: 0; + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 24px; } .portal-contents .inner::before { - content: none; + content: none; } .publication-contents { - padding: 24px 40px; - border: 1px solid #e2e4e9; - width: 100%; - margin: 0; - background-color: #fff; - border-radius: 4px; + padding: 24px 40px; + border: 1px solid #e2e4e9; + width: 100%; + margin: 0; + background-color: #fff; + border-radius: 4px; } .publication-contents:first-child { - grid-column: 1/span 2; + grid-column: 1 / span 2; } .publication-contents h4.featured-title, .publication-contents .section-toc-title { - font-size: 28px; - font-weight: 300; - line-height: 36px; - margin: 0; + font-size: 28px; + font-weight: 300; + line-height: 36px; + margin: 0; } .publication-contents h4.featured-title a, .publication-contents .section-toc-title a { - color: #283241; + color: #283241; } .publication-contents h4.featured-title a:hover, .publication-contents .section-toc-title a:hover { - color: #283241; - text-decoration: underline; + color: #283241; + text-decoration: underline; } .publication-contents ul { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } .publication-contents li { - margin: 4px 0; + margin: 4px 0; } .publication-contents li:first-child { - margin-top: 20px; + margin-top: 20px; } .publication-contents li:last-child { - margin-bottom: 40px; + margin-bottom: 40px; } .publication-contents li a { - color: #dd4d50; - font-size: 16px; - line-height: 24px; + color: #dd4d50; + font-size: 16px; + line-height: 24px; } .publication-contents li a:hover { - color: #dd4d50; - text-decoration: underline; + color: #dd4d50; + text-decoration: underline; } .publication-contents h4 span, .publication-contents li::before { - display: none; + display: none; } @media only screen and (max-width: 768px) { - .portal-contents .inner { - grid-template-columns: 1fr; - } + .portal-contents .inner { + grid-template-columns: 1fr; + } - .publication-contents:first-child { - grid-column: auto; - } + .publication-contents:first-child { + grid-column: auto; + } } /* Einleitung */ .cat-panel-1:before { - content: "\f277"; + content: '\f277'; } /* Workflow */ .cat-panel-2:before { - content: "\f0c1"; + content: '\f0c1'; } /* Voraussetzungen */ .cat-panel-3:before { - content: "\f109"; + content: '\f109'; } /* Benutzermenü und -profil */ .cat-panel-4:before { - content: "\f007"; + content: '\f007'; } /* Benachrichtigungen */ .cat-panel-5:before { - content: "\f0f3"; + content: '\f0f3'; } /* Suchfunktion */ .cat-panel-6:before { - content: "\f002"; + content: '\f002'; } /* Ebenen in der Benutzeroberfläche des RedactManagers */ .cat-panel-7:before { - content: "\f248"; + content: '\f248'; } /* Rollen und Berechtigungen */ .cat-panel-8:before { - content: "\f084"; + content: '\f084'; } /* Dossier erstellen und verwalten */ .cat-panel-9:before { - content: "\f07c"; + content: '\f07c'; } /* Dokumente bearbeiten im Editor */ .cat-panel-10:before { - content: "\f15c"; + content: '\f15c'; } /* Dossier abschließen und herunterladen */ .cat-panel-11:before { - content: "\f019"; + content: '\f019'; } /* Funktionsübersicht */ .cat-panel-12:before { - content: "\f03a"; + content: '\f03a'; } /* Glossar */ .cat-panel-13:before { - content: "\f02d"; + content: '\f02d'; } /* FAQ’s (häufige Fragen) */ .cat-panel-14:before { - content: "\f059"; + content: '\f059'; } .portal-search-result { - background-color: #f5f5f7; + background-color: #f5f5f7; } .search-container { - padding-bottom: 100px; + padding-bottom: 100px; } .portal-search-result { - padding: 80px 0 0 0; + padding: 80px 0 0 0; } ul.searchresults { - border: 1px solid #e2e4e9; - background-color: #fff; - border-radius: 4px; - margin-top: 32px; + border: 1px solid #e2e4e9; + background-color: #fff; + border-radius: 4px; + margin-top: 32px; } ul.searchresults .search-highlight { - font-style: normal; + font-style: normal; } li.searchresultitem { - margin: 0 32px; - border-bottom: 1px solid #e2e4e9; - padding: 32px 8px; + margin: 0 32px; + border-bottom: 1px solid #e2e4e9; + padding: 32px 8px; } .searchresultitem.selected-searchresultitem { - background-color: transparent; - border-radius: 0; + background-color: transparent; + border-radius: 0; } .searchresulttitle { - font-size: 28px; - font-weight: 300; - line-height: 36px; - color: #283241; + font-size: 28px; + font-weight: 300; + line-height: 36px; + color: #283241; } .searchresultsnippet { - margin: 16px 0; - color: #283241; + margin: 16px 0; + color: #283241; } .search-result-breadcrumbs { - color: #dd4d50; + color: #dd4d50; } .portal-footer, .site-footer { - border-top: 1px solid #e2e4e9; - padding: 0; + border-top: 1px solid #e2e4e9; + padding: 0; } .portal-footer.portal-footer, .site-footer.portal-footer { - margin-top: 100px; + margin-top: 100px; } .portal-footer .inner, .site-footer .inner { - margin: 0; - padding: 8px 0 64px 0; - font-size: 16px; - line-height: 24px; + margin: 0; + padding: 8px 0 64px 0; + font-size: 16px; + line-height: 24px; } .portal-footer .inner > *, .site-footer .inner > * { - padding: 0; + padding: 0; } .portal-footer .inner .copyright, .site-footer .inner .copyright { - width: 50%; + width: 50%; } :root { - --iqser-primary: lightblue; - --iqser-primary-rgb: 220, 230, 234; - --iqser-primary-2: orange; - --iqser-accent: blue; - --iqser-accent-rgb: 123, 234, 111; - --iqser-disabled: #9398a0; - --iqser-not-disabled-table-item: #f9fafb; - --iqser-btn-bg-hover: #e2e4e9; - --iqser-btn-bg: #f0f1f4; - --iqser-warn: #fdbd00; - --iqser-white: white; - --iqser-black: black; - --iqser-light: white; - --iqser-separator: rgba(226, 228, 233, 0.9); - --iqser-quick-filter-border: #d3d5da; - --iqser-grey-1: #283241; - --iqser-grey-2: #f4f5f7; - --iqser-grey-3: #aaacb3; - --iqser-grey-4: #e2e4e9; - --iqser-grey-5: #d3d5da; - --iqser-grey-6: #f0f1f4; - --iqser-grey-7: #9398a0; - --iqser-grey-8: #f9fafb; - --iqser-green-1: #00ff00; - --iqser-green-2: #5ce594; - --iqser-yellow-1: #ffb83b; - --iqser-yellow-2: #fdbd00; - --iqser-red-1: #dd4d50; - --iqser-blue-5: #c5d3eb; - --iqser-helpmode-primary: green; + --iqser-primary: lightblue; + --iqser-primary-rgb: 220, 230, 234; + --iqser-primary-2: orange; + --iqser-accent: blue; + --iqser-accent-rgb: 123, 234, 111; + --iqser-disabled: #9398a0; + --iqser-not-disabled-table-item: #f9fafb; + --iqser-btn-bg-hover: #e2e4e9; + --iqser-btn-bg: #f0f1f4; + --iqser-warn: #fdbd00; + --iqser-white: white; + --iqser-black: black; + --iqser-light: white; + --iqser-separator: rgba(226, 228, 233, 0.9); + --iqser-quick-filter-border: #d3d5da; + --iqser-grey-1: #283241; + --iqser-grey-2: #f4f5f7; + --iqser-grey-3: #aaacb3; + --iqser-grey-4: #e2e4e9; + --iqser-grey-5: #d3d5da; + --iqser-grey-6: #f0f1f4; + --iqser-grey-7: #9398a0; + --iqser-grey-8: #f9fafb; + --iqser-green-1: #00ff00; + --iqser-green-2: #5ce594; + --iqser-yellow-1: #ffb83b; + --iqser-yellow-2: #fdbd00; + --iqser-yellow-rgb: 253, 189, 0; + --iqser-red-1: #dd4d50; + --iqser-blue-5: #c5d3eb; + --iqser-helpmode-primary: green; } .site-sidebar { - background-color: #283241; - scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2); - scrollbar-width: thin; - /* Track */ - /* Handle */ + background-color: #283241; + scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2); + scrollbar-width: thin; + /* Track */ + /* Handle */ } .site-sidebar .logo { - padding: 24px 0 30px 0 !important; + padding: 24px 0 30px 0 !important; } .site-sidebar::-webkit-scrollbar { - width: 11px; + width: 11px; } .site-sidebar::-webkit-scrollbar-track { - background: var(--iqser-grey-2); + background: var(--iqser-grey-2); } .site-sidebar::-webkit-scrollbar-thumb { - background: var(--iqser-quick-filter-border); + background: var(--iqser-quick-filter-border); } .site-sidebar-search { - padding: 0 24px; + padding: 0 24px; } .site-sidebar-search .search-field { - width: 100%; - border: 1px solid #d3d5da; - border-radius: 8px; - background-color: #fff; + width: 100%; + border: 1px solid #d3d5da; + border-radius: 8px; + background-color: #fff; } .site-sidebar-search .search-field::placeholder { - opacity: 0.7; + opacity: 0.7; } .site-sidebar-search .search-field, .site-sidebar-search .search-field::placeholder { - color: #283241; - font-size: 14px; - line-height: 18px; + color: #283241; + font-size: 14px; + line-height: 18px; } .site-sidebar-search .search-field { - padding: 7px 13px; + padding: 7px 13px; } .nav-site-sidebar { - margin-top: 16px; + margin-top: 16px; } .nav-site-sidebar .topic-link { - padding-top: 11px; - padding-bottom: 11px; - font-size: 14px; - line-height: 18px; - color: #d3d5da; + padding-top: 11px; + padding-bottom: 11px; + font-size: 14px; + line-height: 18px; + color: #d3d5da; } .nav-site-sidebar .topic-link:hover { - background-color: #313d4e; + background-color: #313d4e; } .nav-site-sidebar .active > .topic-link { - background-color: #313d4e; + background-color: #313d4e; } .nav-site-sidebar .active > a { - color: #fff; - font-weight: 600; + color: #fff; + font-weight: 600; } .nav-site-sidebar > li > a { - padding-left: 24px; + padding-left: 24px; } .nav-site-sidebar > li > ul > li > a { - padding-left: 32px; + padding-left: 32px; } .nav-site-sidebar > li > ul > li > ul > li > a { - padding-left: 40px; + padding-left: 40px; } .toc .glyphicon { - top: 5px; + top: 5px; } .toc > li > .topic-link > .glyphicon { - margin-top: 4px; + margin-top: 4px; } .toolbar { - box-shadow: none; - padding: 21px 24px; - margin-bottom: 50px; + box-shadow: none; + padding: 21px 24px; + margin-bottom: 50px; } .topic-content .breadcrumb-container { - margin-top: 40px; + margin-top: 40px; } .topic-content .breadcrumb { - font-size: 14px; - line-height: 18px; - font-weight: 600; + font-size: 14px; + line-height: 18px; + font-weight: 600; } .topic-content .breadcrumb a { - color: #283241; + color: #283241; } .topic-content .breadcrumb a:hover { - color: #dd4d50; - text-decoration: underline; + color: #dd4d50; + text-decoration: underline; } .topic-content .breadcrumb .breadcrumb-node { - color: #dd4d50; + color: #dd4d50; } main article { - margin-bottom: 0; - padding: 0; + margin-bottom: 0; + padding: 0; } section > .titlepage .title { - margin: 24px 0 16px 0; + margin: 24px 0 16px 0; } #topic-content > section > .titlepage h2.title { - margin: 0 0 24px; + margin: 0 0 24px; } .image-viewport { - margin: auto; + margin: auto; } .image-viewport img { - margin: 16px auto; + margin: 16px auto; } .pager { - margin-top: 30px; - margin-bottom: 30px; - padding: 0; + margin-top: 30px; + margin-bottom: 30px; + padding: 0; } .pager li > a, .pager li > span { - color: #dd4d50; - font-size: 14px; - font-weight: 600; - line-height: 19px; - text-transform: uppercase; - padding: 0; - background-color: transparent; - border: none; - border-radius: 0; + color: #dd4d50; + font-size: 14px; + font-weight: 600; + line-height: 19px; + text-transform: uppercase; + padding: 0; + background-color: transparent; + border: none; + border-radius: 0; } .pager li > a:hover, .pager li > span:hover { - text-decoration: underline; - background-color: transparent; - color: #dd4d50; + text-decoration: underline; + background-color: transparent; + color: #dd4d50; } .warning, @@ -483,230 +484,241 @@ section > .titlepage .title { .important, .caution, .tip { - margin-top: 32px; - margin-bottom: 32px; - padding: 16px 24px 16px 68px; - background-color: #fff; - border-left: 4px solid #dd4d50; - border-radius: 4px; + margin-top: 32px; + margin-bottom: 32px; + padding: 16px 24px 16px 68px; + background-color: #fff; + border-left: 4px solid #dd4d50; + border-radius: 4px; } .warning:before, .note:before, .important:before, .caution:before, .tip:before { - color: #dd4d50; - width: 20px; - height: 20px; - text-align: center; - left: 24px; - top: calc(50% - 15px); + color: #dd4d50; + width: 20px; + height: 20px; + text-align: center; + left: 24px; + top: calc(50% - 15px); } .warning h3, .note h3, .important h3, .caution h3, .tip h3 { - padding: 0; - font-size: 18px; - font-weight: 600; - line-height: 24px; - margin-bottom: 8px; + padding: 0; + font-size: 18px; + font-weight: 600; + line-height: 24px; + margin-bottom: 8px; } .warning p, .note p, .important p, .caution p, .tip p { - line-height: 20px; + line-height: 20px; } .topic-content > section > p { - margin: 12px 0; + margin: 12px 0; } .panel { - padding: 12px 0; - border-radius: 4px; - border: none; + padding: 12px 0; + border-radius: 4px; + border: none; } .panel .panel-body > p { - margin-bottom: 12px; + margin-bottom: 12px; } .panel .panel-body > p:not(:first-of-type) { - margin-top: 18px; + margin-top: 18px; } .mediaobject { - margin-top: 20px; + margin-top: 20px; } .mediaobject img { - border-radius: 4px; - margin: 0; - box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14); + border-radius: 4px; + margin: 0; + box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14); } .mediaobject .caption > p { - font-size: 14px; - text-align: center; - font-style: italic; - margin: 0; + font-size: 14px; + text-align: center; + font-style: italic; + margin: 0; } .inlinemediaobject { - vertical-align: unset; + vertical-align: unset; } main ol, main ul { - margin: 0 0 24px; + margin: 0 0 24px; } .section-toc { - padding: 24px 40px; - border: 1px solid #e2e4e9; - width: 100%; - margin: 0; - background-color: #fff; - border-radius: 4px; + padding: 24px 40px; + border: 1px solid #e2e4e9; + width: 100%; + margin: 0; + background-color: #fff; + border-radius: 4px; } .section-toc:first-child { - grid-column: 1/span 2; + grid-column: 1 / span 2; } .section-toc h4.featured-title, .section-toc .section-toc-title { - font-size: 28px; - font-weight: 300; - line-height: 36px; - margin: 0; + font-size: 28px; + font-weight: 300; + line-height: 36px; + margin: 0; } .section-toc h4.featured-title a, .section-toc .section-toc-title a { - color: #283241; + color: #283241; } .section-toc h4.featured-title a:hover, .section-toc .section-toc-title a:hover { - color: #283241; - text-decoration: underline; + color: #283241; + text-decoration: underline; } .section-toc ul { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } .section-toc li { - margin: 4px 0; + margin: 4px 0; } .section-toc li:first-child { - margin-top: 20px; + margin-top: 20px; } .section-toc li:last-child { - margin-bottom: 40px; + margin-bottom: 40px; } .section-toc li a { - color: #dd4d50; - font-size: 16px; - line-height: 24px; + color: #dd4d50; + font-size: 16px; + line-height: 24px; } .section-toc li a:hover { - color: #dd4d50; - text-decoration: underline; + color: #dd4d50; + text-decoration: underline; } .section-toc h4 span, .section-toc li::before { - display: none; + display: none; } .section-toc li:first-child { - margin-top: 16px; + margin-top: 16px; } .section-toc li:last-child { - margin-bottom: 8px; + margin-bottom: 8px; } .procedure > li.step::before { - background-color: transparent; - border: 1px solid #dd4d50; - color: #dd4d50; - line-height: 23px; + background-color: transparent; + border: 1px solid #dd4d50; + color: #dd4d50; + line-height: 23px; } .question { - font-weight: 600; + font-weight: 600; } .question > td > p { - margin: 32px 0 18px 0; + margin: 32px 0 18px 0; } .question > td:first-child { - padding-right: 4px; + padding-right: 4px; } .fixed-toolbar article.topic :target.question:before { - content: none; + content: none; } body { - color: #283241; - background-color: #f5f5f7; - font-family: "Open Sans", sans-serif; - scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2); - scrollbar-width: thin; - /* Track */ - /* Handle */ + color: #283241; + background-color: #f5f5f7; + font-family: 'Open Sans', sans-serif; + scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2); + scrollbar-width: thin; + /* Track */ + /* Handle */ } -body h1, body .h1, -body h2, body .h2, -body h3, body .h3, -body h4, body .h4, -body h5, body .h5, -body h6, body .h6, +body h1, +body .h1, +body h2, +body .h2, +body h3, +body .h3, +body h4, +body .h4, +body h5, +body .h5, +body h6, +body .h6, body p, body pre { - margin: 0; - font-family: "Open Sans", sans-serif; + margin: 0; + font-family: 'Open Sans', sans-serif; } body::-webkit-scrollbar { - width: 11px; + width: 11px; } body::-webkit-scrollbar-track { - background: var(--iqser-grey-2); + background: var(--iqser-grey-2); } body::-webkit-scrollbar-thumb { - background: var(--iqser-quick-filter-border); + background: var(--iqser-quick-filter-border); } -body h1, body .h1 { - font-size: 64px; - font-weight: 300; - line-height: 87px; +body h1, +body .h1 { + font-size: 64px; + font-weight: 300; + line-height: 87px; } -body h2, body .h2 { - font-size: 42px; - font-weight: 300; - line-height: 57px; +body h2, +body .h2 { + font-size: 42px; + font-weight: 300; + line-height: 57px; } -body h3, body .h3 { - font-size: 32px; - font-weight: 300; - line-height: 43px; +body h3, +body .h3 { + font-size: 32px; + font-weight: 300; + line-height: 43px; } -body h4, body .h4 { - font-size: 28px; - font-weight: 300; - line-height: 36px; +body h4, +body .h4 { + font-size: 28px; + font-weight: 300; + line-height: 36px; } -body h5, body .h5 { - font-size: 18px; - font-weight: 600; - line-height: 24px; +body h5, +body .h5 { + font-size: 18px; + font-weight: 600; + line-height: 24px; } body p { - font-size: 16px; - line-height: 24px; + font-size: 16px; + line-height: 24px; } body a { - color: #dd4d50; + color: #dd4d50; } body a:hover { - text-decoration: underline; - color: #dd4d50; + text-decoration: underline; + color: #dd4d50; } body a:focus { - color: #dd4d50; + color: #dd4d50; }