diff --git a/apps/red-ui/src/app/app-routing.module.ts b/apps/red-ui/src/app/app-routing.module.ts index 92d9eae7a..b6ec3c6fd 100644 --- a/apps/red-ui/src/app/app-routing.module.ts +++ b/apps/red-ui/src/app/app-routing.module.ts @@ -7,7 +7,6 @@ import { RouteReuseStrategy, RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component'; import { AppStateGuard } from '@state/app-state.guard'; -import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component'; const routes = [ { @@ -21,18 +20,9 @@ const routes = [ canActivate: [AuthGuard], }, { - path: 'main/my-profile', + path: 'main/account', component: BaseScreenComponent, - children: [ - { - path: '', - component: UserProfileScreenComponent, - }, - ], - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard], - }, + loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule), }, { path: 'main/admin', diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index 67dfac44c..32387e151 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -20,7 +20,6 @@ import { DownloadsListScreenComponent } from '@components/downloads-list-screen/ import { AppRoutingModule } from './app-routing.module'; import { SharedModule } from '@shared/shared.module'; import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module'; -import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component'; import { PlatformLocation } from '@angular/common'; import { BASE_HREF } from './tokens'; import { MONACO_PATH, MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; @@ -51,7 +50,7 @@ function cleanupBaseUrl(baseUrl: string) { } } -const screens = [BaseScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent]; +const screens = [BaseScreenComponent, DownloadsListScreenComponent]; const components = [AppComponent, AuthErrorComponent, NotificationsComponent, SpotlightSearchComponent, ...screens]; diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts index 80e31da53..20cd1506a 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts @@ -32,9 +32,10 @@ export class BaseScreenComponent { readonly currentUser = this.userService.currentUser; readonly userMenuItems: readonly MenuItem[] = [ { - name: _('top-bar.navigation-items.my-account.children.my-profile'), - routerLink: '/main/my-profile', - show: true, + name: _('top-bar.navigation-items.my-account.children.account'), + routerLink: '/main/account', + show: this.currentUser.isUser, + action: this.appStateService.reset, showDot: () => false, }, { diff --git a/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.html b/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.html deleted file mode 100644 index c643dc6b2..000000000 --- a/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.html +++ /dev/null @@ -1,50 +0,0 @@ -
-
-
-
-
-
-
-
-
-
-
- - -
- -
- - -
- -
- - -
-
- - - - {{ translations[language] | translate }} - - -
-
-
- -
- - {{ 'user-profile.actions.change-password' | translate }} -
-
-
-
-
diff --git a/apps/red-ui/src/app/modules/account/account-routing.module.ts b/apps/red-ui/src/app/modules/account/account-routing.module.ts new file mode 100644 index 000000000..777b82c0c --- /dev/null +++ b/apps/red-ui/src/app/modules/account/account-routing.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { CompositeRouteGuard } from '@iqser/common-ui'; +import { AuthGuard } from '../auth/auth.guard'; +import { RedRoleGuard } from '../auth/red-role.guard'; +import { AppStateGuard } from '../../state/app-state.guard'; +import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component'; + +const routes = [ + { path: '', redirectTo: 'user-profile', pathMatch: 'full' }, + { + path: 'user-profile', + component: BaseAccountScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard], + requiredRoles: ['RED_USER'], + }, + loadChildren: () => import('./screens/user-profile/user-profile.module').then(m => m.UserProfileModule), + }, + { + path: 'notifications', + component: BaseAccountScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard], + }, + loadChildren: () => import('./screens/notifications/notifications.module').then(m => m.NotificationsModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class AccountRoutingModule {} diff --git a/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.html b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.html new file mode 100644 index 000000000..8ebc033dc --- /dev/null +++ b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.html @@ -0,0 +1,7 @@ + + +
+ {{ item.label | translate }} +
+
+
diff --git a/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.scss b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.scss new file mode 100644 index 000000000..15c54cccf --- /dev/null +++ b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.scss @@ -0,0 +1,7 @@ +:host { + height: calc(100vh - 61px); + + &.dossier-templates { + height: calc(100vh - 111px); + } +} diff --git a/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts new file mode 100644 index 000000000..f4cf007ae --- /dev/null +++ b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts @@ -0,0 +1,26 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +interface NavItem { + readonly label: string; + readonly screen: string; +} + +@Component({ + selector: 'redaction-account-side-nav', + templateUrl: './account-side-nav.component.html', + styleUrls: ['./account-side-nav.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AccountSideNavComponent { + readonly items: NavItem[] = [ + { + screen: 'user-profile', + label: _('user-profile'), + }, + { + screen: 'notifications', + label: _('notifications'), + }, + ]; +} diff --git a/apps/red-ui/src/app/modules/account/account.module.ts b/apps/red-ui/src/app/modules/account/account.module.ts new file mode 100644 index 000000000..28b2ee431 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/account.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../shared/shared.module'; +import { AccountRoutingModule } from './account-routing.module'; +import { AccountSideNavComponent } from './account-side-nav/account-side-nav.component'; +import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component'; +import { NotificationPreferencesService } from './services/notification-preferences.service'; + +@NgModule({ + declarations: [AccountSideNavComponent, BaseAccountScreenComponent], + imports: [CommonModule, SharedModule, AccountRoutingModule], + providers: [NotificationPreferencesService], +}) +export class AccountModule {} diff --git a/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html new file mode 100644 index 000000000..1597a1874 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html @@ -0,0 +1,20 @@ +
+
+ + + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
diff --git a/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.scss b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.scss similarity index 64% rename from apps/red-ui/src/app/components/user-profile/user-profile-screen.component.scss rename to apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.scss index 677b2c3d2..fd508d3c9 100644 --- a/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.scss +++ b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.scss @@ -1,11 +1,16 @@ -@use 'variables'; -@use 'common-mixins'; +@use 'apps/red-ui/src/assets/styles/variables'; +@use 'libs/common-ui/src/assets/styles/common-mixins'; .content-container { background-color: variables.$grey-2; justify-content: center; @include common-mixins.scroll-bar; overflow: auto; + + .dialog { + width: var(--width); + min-height: unset; + } } .full-height { @@ -17,11 +22,3 @@ height: calc(100% + 50px); z-index: 1; } - -iframe { - background: white; - width: 500px; - height: 500px; - position: absolute; - z-index: 100; -} diff --git a/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.ts b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.ts new file mode 100644 index 000000000..c87363a82 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core'; +import { Router } from '@angular/router'; +import { notificationsTranslations } from '../translations/notifications-translations'; + +@Component({ + selector: 'redaction-base-account-screen', + templateUrl: './base-account-screen-component.html', + styleUrls: ['./base-account-screen-component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BaseAccountScreenComponent implements OnInit { + readonly translations = notificationsTranslations; + path: string; + + constructor(private readonly _router: Router, private readonly _hostRef: ViewContainerRef) { + this.path = this._router.url.split('/').pop(); + } + + ngOnInit(): void { + this._setDialogWidth(); + } + + private _setDialogWidth() { + const element = this._hostRef.element.nativeElement as HTMLElement; + element.style.setProperty('--width', this.path === 'user-profile' ? 'unset' : '100%'); + } +} diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/constants.ts b/apps/red-ui/src/app/modules/account/screens/notifications/constants.ts new file mode 100644 index 000000000..3c98f33b1 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/notifications/constants.ts @@ -0,0 +1,38 @@ +export const NotificationCategories = { + inAppNotifications: 'inAppNotifications', + emailNotifications: 'emailNotifications', +} as const; + +export const NotificationCategoriesValues = Object.values(NotificationCategories); + +export const OwnDossiersNotificationsTypes = { + dossierStatusChanges: 'dossierStatusChanges', + requestToJoinTheDossier: 'requestToJoinTheDossier', + documentStatusChanges: 'documentStatusChanges', + documentIsSentForApproval: 'documentIsSentForApproval', +} as const; + +export const OwnDossiersNotificationsTypesValues = Object.values(OwnDossiersNotificationsTypes); + +export const ReviewerOnDossiersNotificationsTypes = { + whenIAmAssignedOnADocument: 'whenIAmAssignedOnADocument', + whenIAmUnassignedFromADocument: 'whenIAmUnassignedFromADocument', + whenADocumentIsApproved: 'whenADocumentIsApproved', +} as const; + +export const ReviewerOnDossiersNotificationsTypesValues = Object.values(ReviewerOnDossiersNotificationsTypes); + +export const ApproverOnDossiersNotificationsTypes = { + whenADocumentIsSentForApproval: 'whenADocumentIsSentForApproval', + whenADocumentIsAssignedToAReviewer: 'whenADocumentIsAssignedToAReviewer', + whenAReviewerIsUnassignedFromADocument: 'whenAReviewerIsUnassignedFromADocument', +} as const; + +export const ApproverOnDossiersNotificationsTypesValues = Object.values(ApproverOnDossiersNotificationsTypes); + +export const NotificationGroupsKeys = ['own', 'reviewer', 'approver'] as const; +export const NotificationGroupsValues = [ + OwnDossiersNotificationsTypesValues, + ReviewerOnDossiersNotificationsTypesValues, + ApproverOnDossiersNotificationsTypesValues, +] as const; diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html new file mode 100644 index 000000000..8c76795dc --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html @@ -0,0 +1,43 @@ +
+
+
+
+ {{ + translations[category] | translate + }} +
+ +
+
+
+ + + {{ translations[type.toLocaleLowerCase()] | translate }} +
+
+ +
+ +
+
+
+ + {{ translations[preference] | translate }} + +
+
+
+
+
+ +
+ +
+
diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.scss b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.scss new file mode 100644 index 000000000..82f2afa68 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.scss @@ -0,0 +1,51 @@ +@use 'variables'; +@use 'libs/common-ui/src/assets/styles/common-mixins'; + +.dialog-content { + flex-direction: column; + + .header { + grid-column: span 2; + padding: 10px 10px; + margin-bottom: -1px; + border-top: 1px solid variables.$separator; + border-bottom: 1px solid variables.$separator; + } + + .options-content { + padding: 10px 48px; + + .statement { + opacity: 0.7; + color: variables.$grey-1; + font-weight: 500; + padding: 10px 0; + } + + .radio-container { + display: flex; + padding: 10px 0 10px; + .radio-button { + display: flex; + align-items: center; + padding-right: 30px; + iqser-round-checkbox { + margin-right: 8px; + } + } + } + + .group { + padding: 10px 0; + + .group-title { + color: variables.$grey-1; + font-weight: 600; + } + + .iqser-input-group { + margin-top: 5px; + } + } + } +} diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts new file mode 100644 index 000000000..e6b8f4a2a --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts @@ -0,0 +1,89 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { notificationsTranslations } from '../../../translations/notifications-translations'; +import { NotificationPreferencesService } from '../../../services/notification-preferences.service'; +import { LoadingService, Toaster } from '@iqser/common-ui'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { NotificationCategoriesValues, NotificationGroupsKeys, NotificationGroupsValues } from '../constants'; +import { EmailNotificationScheduleTypesValues } from '@red/domain'; + +@Component({ + selector: 'redaction-notifications-screen', + templateUrl: './notifications-screen.component.html', + styleUrls: ['./notifications-screen.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NotificationsScreenComponent implements OnInit { + readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues; + readonly notificationCategories = NotificationCategoriesValues; + readonly notificationGroupsKeys = NotificationGroupsKeys; + readonly notificationGroupsValues = NotificationGroupsValues; + readonly translations = notificationsTranslations; + + formGroup: FormGroup; + + constructor( + private readonly _toaster: Toaster, + private readonly _formBuilder: FormBuilder, + private readonly _loadingService: LoadingService, + private readonly _notificationPreferencesService: NotificationPreferencesService, + ) { + this.formGroup = this._formBuilder.group({ + inAppNotificationsEnabled: [undefined], + emailNotificationsEnabled: [undefined], + emailNotificationType: [undefined], + emailNotifications: [undefined], + inAppNotifications: [undefined], + }); + } + + async ngOnInit(): Promise { + await this._initializeForm(); + } + + isCategoryActive(category: string) { + return this.formGroup.get(`${category}Enabled`).value; + } + + setEmailNotificationType(type: string) { + this.formGroup.get('emailNotificationType').setValue(type); + } + + getEmailNotificationType() { + return this.formGroup.get('emailNotificationType').value; + } + + isPreferenceChecked(category: string, preference: string) { + return this.formGroup.get(category).value.includes(preference); + } + + addRemovePreference(checked: boolean, category: string, preference: string) { + const preferences = this.formGroup.get(category).value; + if (checked) { + preferences.push(preference); + } else { + const indexOfPreference = preferences.indexOf(preference); + preferences.splice(indexOfPreference, 1); + } + this.formGroup.get(category).setValue(preferences); + } + + async save() { + this._loadingService.start(); + try { + await this._notificationPreferencesService.update(this.formGroup.value).toPromise(); + } catch (e) { + this._toaster.error(_('notifications-screen.error.generic')); + } + this._loadingService.stop(); + } + + private async _initializeForm() { + this._loadingService.start(); + + const notificationPreferences = await this._notificationPreferencesService.get().toPromise(); + this.formGroup.patchValue(notificationPreferences); + + this._loadingService.stop(); + } +} diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts b/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts new file mode 100644 index 000000000..bc6d3f30a --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../../../shared/shared.module'; +import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component'; + +const routes = [{ path: '', component: NotificationsScreenComponent }]; + +@NgModule({ + declarations: [NotificationsScreenComponent], + imports: [RouterModule.forChild(routes), CommonModule, SharedModule], +}) +export class NotificationsModule {} diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html new file mode 100644 index 000000000..25d5a5a44 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html @@ -0,0 +1,37 @@ +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + + {{ translations[language] | translate }} + + +
+ +
+
+ +
+ +
+
diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.scss b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.scss new file mode 100644 index 000000000..e1573c569 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.scss @@ -0,0 +1,3 @@ +a { + color: black; +} diff --git a/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts similarity index 87% rename from apps/red-ui/src/app/components/user-profile/user-profile-screen.component.ts rename to apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts index 1601afc46..f1a23936a 100644 --- a/apps/red-ui/src/app/components/user-profile/user-profile-screen.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts @@ -1,19 +1,20 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { UserService } from '@services/user.service'; -import { PermissionsService } from '@services/permissions.service'; -import { LanguageService } from '@i18n/language.service'; -import { TranslateService } from '@ngx-translate/core'; -import { ConfigService } from '@services/config.service'; import { DomSanitizer } from '@angular/platform-browser'; -import { languagesTranslations } from '../../translations/languages-translations'; +import { TranslateService } from '@ngx-translate/core'; import { LoadingService } from '@iqser/common-ui'; import { IProfile } from '@red/domain'; +import { languagesTranslations } from '../../../translations/languages-translations'; +import { PermissionsService } from '../../../../../services/permissions.service'; +import { UserService } from '../../../../../services/user.service'; +import { ConfigService } from '../../../../../services/config.service'; +import { LanguageService } from '../../../../../i18n/language.service'; @Component({ selector: 'redaction-user-profile-screen', templateUrl: './user-profile-screen.component.html', styleUrls: ['./user-profile-screen.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class UserProfileScreenComponent implements OnInit { formGroup: FormGroup; diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts new file mode 100644 index 000000000..928581695 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../../../shared/shared.module'; +import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component'; + +const routes = [{ path: '', component: UserProfileScreenComponent }]; + +@NgModule({ + declarations: [UserProfileScreenComponent], + imports: [RouterModule.forChild(routes), CommonModule, SharedModule], +}) +export class UserProfileModule {} diff --git a/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts b/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts new file mode 100644 index 000000000..acd23a718 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts @@ -0,0 +1,31 @@ +import { Injectable, Injector } from '@angular/core'; +import { GenericService } from '@iqser/common-ui'; +import { Observable, of } from 'rxjs'; +import { UserService } from '../../../services/user.service'; +import { EmailNotificationScheduleTypes, INotificationPreferences } from '@red/domain'; +import { catchError } from 'rxjs/operators'; + +@Injectable() +export class NotificationPreferencesService extends GenericService { + constructor(protected readonly _injector: Injector, private readonly _userService: UserService) { + super(_injector, 'notification-preferences'); + } + + get(): Observable { + return super.get().pipe(catchError(() => of(this._defaultPreferences))); + } + + update(notificationPreferences: INotificationPreferences): Observable { + return super._post(notificationPreferences); + } + + private get _defaultPreferences(): INotificationPreferences { + return { + emailNotificationType: EmailNotificationScheduleTypes.INSTANT, + emailNotifications: [], + emailNotificationsEnabled: false, + inAppNotifications: [], + inAppNotificationsEnabled: true, + }; + } +} diff --git a/apps/red-ui/src/app/translations/languages-translations.ts b/apps/red-ui/src/app/modules/account/translations/languages-translations.ts similarity index 100% rename from apps/red-ui/src/app/translations/languages-translations.ts rename to apps/red-ui/src/app/modules/account/translations/languages-translations.ts diff --git a/apps/red-ui/src/app/modules/account/translations/notifications-translations.ts b/apps/red-ui/src/app/modules/account/translations/notifications-translations.ts new file mode 100644 index 000000000..c85d03104 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/translations/notifications-translations.ts @@ -0,0 +1,24 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +export const notificationsTranslations: { [key: string]: string } = { + daily: _('notifications-screen.schedule.instant'), + daily_summary: _('notifications-screen.schedule.daily'), + weekly_summary: _('notifications-screen.schedule.weekly'), + inAppNotifications: _('notifications-screen.category.in-app-notifications'), + emailNotifications: _('notifications-screen.category.email-notifications'), + documentIsSentForApproval: _('notifications-screen.options.document-is-sent-for-approval'), + documentStatusChanges: _('notifications-screen.options.document-status-changes'), + dossierStatusChanges: _('notifications-screen.options.dossier-status-changes'), + requestToJoinTheDossier: _('notifications-screen.options.request-to-join-the-dossier'), + whenADocumentIsApproved: _('notifications-screen.options.when-a-document-is-approved'), + whenADocumentIsAssignedToAReviewer: _('notifications-screen.options.when-a-document-is-assigned-to-a-reviewer'), + whenADocumentIsSentForApproval: _('notifications-screen.options.when-a-document-is-sent-for-approval'), + whenAReviewerIsUnassignedFromADocument: _('notifications-screen.options.when-a-reviewer-is-unassigned-from-a-document'), + whenIAmAssignedOnADocument: _('notifications-screen.options.when-i-am-assigned-on-a-document'), + whenIAmUnassignedFromADocument: _('notifications-screen.options.when-i-am-unassigned-from-a-document'), + approver: _('notifications-screen.groups.approver'), + own: _('notifications-screen.groups.own'), + reviewer: _('notifications-screen.groups.reviewer'), + notifications: _('notifications-screen.title'), + 'user-profile': _('user-profile-screen.title'), +} as const; diff --git a/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-dialog.service.ts b/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-dialog.service.ts index 5a226efb9..a4ee07326 100644 --- a/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-dialog.service.ts +++ b/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-dialog.service.ts @@ -5,7 +5,6 @@ import { ConfirmationDialogInput, DialogConfig, DialogService, - ListingService, LoadingService, TitleColors, } from '@iqser/common-ui'; diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index c82e4bd4d..fdd8aaea6 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1,4 +1,5 @@ { + "account-settings": "Account Settings", "actions": { "all": "All", "none": "None" @@ -1298,9 +1299,39 @@ "user-promoted-to-approver": "{user} promoted to approver in dossier: {dossierName}!", "user-removed-as-dossier-member": "{user} removed as a member of: {dossierName} !" }, - "notifications": { - "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}", - "no-data": "You have no notifications." + "notifications": "Notifications", + "notifications-screen": { + "category": { + "email-notifications": "Email Notifications", + "in-app-notifications": "In-App Notifications" + }, + "error": { + "generic": "Something went wrong... Preferences update failed!" + }, + "groups": { + "approver": "Dossiers you are approver on", + "own": "Dossiers you own", + "reviewer": "Dossiers you are reviewer on" + }, + "options-title": "Choose on which category you want to be notified", + "options": { + "document-is-sent-for-approval": "Document is sent for approval", + "document-status-changes": "Document status changes", + "dossier-status-changes": "Dossier status changes", + "request-to-join-the-dossier": "Request to join the dossier", + "when-a-document-is-approved": "When a document is approved", + "when-a-document-is-assigned-to-a-reviewer": "When a document is assigned to a reviewer", + "when-a-document-is-sent-for-approval": "When a document is sent for approval", + "when-a-reviewer-is-unassigned-from-a-document": "When a reviewer is unassigned from a document", + "when-i-am-assigned-on-a-document": "When I am assigned on a document", + "when-i-am-unassigned-from-a-document": "When I am unassigned from a document" + }, + "schedule": { + "daily": "Daily Summary", + "instant": "Instant", + "weekly": "Weekly Summary" + }, + "title": "Notifications Preferences" }, "overwrite-files-dialog": { "options": { @@ -1477,6 +1508,7 @@ "dossiers": "Active Dossiers", "my-account": { "children": { + "account": "Account", "admin": "Settings", "downloads": "My Downloads", "language": { @@ -1485,7 +1517,6 @@ "label": "Language" }, "logout": "Logout", - "my-profile": "My Profile", "trash": "Trash" } } @@ -1557,17 +1588,18 @@ } }, "user-management": "User Management", - "user-profile": { + "user-profile": "My Profile", + "user-profile-screen": { "actions": { "change-password": "Change Password", - "save": "Save profile" + "save": "Save Changes" }, "form": { "email": "Email", "first-name": "First name", "last-name": "Last name" }, - "title": "My profile" + "title": "Edit Profile" }, "user-stats": { "chart": { diff --git a/libs/red-domain/.eslintrc.json b/libs/red-domain/.eslintrc.json index c3a38e5bf..a4e2637cd 100644 --- a/libs/red-domain/.eslintrc.json +++ b/libs/red-domain/.eslintrc.json @@ -1,15 +1,9 @@ { - "extends": [ - "../../.eslintrc.json" - ], - "ignorePatterns": [ - "!**/*" - ], + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], "overrides": [ { - "files": [ - "*.ts" - ], + "files": ["*.ts"], "extends": [ "plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates", @@ -20,9 +14,7 @@ "plugin:@angular-eslint/recommended--extra" ], "parserOptions": { - "project": [ - "libs/red-domain/tsconfig.*?.json" - ] + "project": ["libs/red-domain/tsconfig.*?.json"] }, "rules": { "@angular-eslint/directive-selector": [ @@ -55,33 +47,20 @@ "error", { "selector": "memberLike", - "modifiers": [ - "private" - ], - "format": [ - "camelCase" - ], + "modifiers": ["private"], + "format": ["camelCase"], "leadingUnderscore": "require" }, { "selector": "memberLike", - "modifiers": [ - "protected" - ], - "format": [ - "camelCase" - ], + "modifiers": ["protected"], + "format": ["camelCase"], "leadingUnderscore": "require" }, { "selector": "memberLike", - "modifiers": [ - "private" - ], - "format": [ - "UPPER_CASE", - "camelCase" - ], + "modifiers": ["private"], + "format": ["UPPER_CASE", "camelCase"], "leadingUnderscore": "require" } ], @@ -94,22 +73,12 @@ "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/restrict-template-expressions": "off" }, - "plugins": [ - "@angular-eslint/eslint-plugin", - "@typescript-eslint" - ] + "plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"] }, { - "files": [ - "*.html" - ], - "extends": [ - "plugin:@nrwl/nx/angular-template", - "plugin:@angular-eslint/template/recommended" - ], - "plugins": [ - "prettier" - ], + "files": ["*.html"], + "extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"], + "plugins": ["prettier"], "rules": {} } ] diff --git a/libs/red-domain/src/index.ts b/libs/red-domain/src/index.ts index 5365dabd6..3cd713b40 100644 --- a/libs/red-domain/src/index.ts +++ b/libs/red-domain/src/index.ts @@ -6,6 +6,7 @@ export * from './lib/users'; export * from './lib/pages'; export * from './lib/audit'; export * from './lib/notifications'; +export * from './lib/notifications-preferences'; export * from './lib/dossier-templates'; export * from './lib/dictionaries'; export * from './lib/redaction-log'; diff --git a/libs/red-domain/src/lib/notifications-preferences/index.ts b/libs/red-domain/src/lib/notifications-preferences/index.ts new file mode 100644 index 000000000..f2fedd47f --- /dev/null +++ b/libs/red-domain/src/lib/notifications-preferences/index.ts @@ -0,0 +1,2 @@ +export * from './notification-preferences'; +export * from './types'; diff --git a/libs/red-domain/src/lib/notifications-preferences/notification-preferences.ts b/libs/red-domain/src/lib/notifications-preferences/notification-preferences.ts new file mode 100644 index 000000000..394af0193 --- /dev/null +++ b/libs/red-domain/src/lib/notifications-preferences/notification-preferences.ts @@ -0,0 +1,9 @@ +import { EmailNotificationScheduleType } from './types'; + +export interface INotificationPreferences { + emailNotificationType: EmailNotificationScheduleType; + emailNotifications: string[]; + emailNotificationsEnabled: boolean; + inAppNotifications: string[]; + inAppNotificationsEnabled: boolean; +} diff --git a/libs/red-domain/src/lib/notifications-preferences/types.ts b/libs/red-domain/src/lib/notifications-preferences/types.ts new file mode 100644 index 000000000..31c6061e1 --- /dev/null +++ b/libs/red-domain/src/lib/notifications-preferences/types.ts @@ -0,0 +1,8 @@ +export const EmailNotificationScheduleTypes = { + INSTANT: 'DAILY', + DAILY: 'DAILY_SUMMARY', + WEEKLY: 'WEEKLY_SUMMARY', +} as const; + +export type EmailNotificationScheduleType = keyof typeof EmailNotificationScheduleTypes; +export const EmailNotificationScheduleTypesValues = Object.values(EmailNotificationScheduleTypes); diff --git a/paligo-theme/paligo-styles/redacto-theme.css b/paligo-theme/paligo-styles/redacto-theme.css index a6388498e..0c1b353c2 100644 --- a/paligo-theme/paligo-styles/redacto-theme.css +++ b/paligo-theme/paligo-styles/redacto-theme.css @@ -1,507 +1,507 @@ @charset "UTF-8"; @font-face { - font-family: "OpenSans"; - font-weight: 300; - src: url("../fonts/OpenSans-VariableFont.ttf") format("truetype"); + font-family: 'OpenSans'; + font-weight: 300; + src: url('../fonts/OpenSans-VariableFont.ttf') format('truetype'); } @font-face { - font-family: "OpenSans"; - font-weight: 400; - src: url("../fonts/OpenSans-VariableFont.ttf") format("truetype"); + font-family: 'OpenSans'; + font-weight: 400; + src: url('../fonts/OpenSans-VariableFont.ttf') format('truetype'); } @font-face { - font-family: "OpenSans"; - font-weight: 500; - src: url("../fonts/OpenSans-VariableFont.ttf") format("truetype"); + font-family: 'OpenSans'; + font-weight: 500; + src: url('../fonts/OpenSans-VariableFont.ttf') format('truetype'); } @font-face { - font-family: "OpenSans"; - font-weight: 600; - src: url("../fonts/OpenSans-VariableFont.ttf") format("truetype"); + font-family: 'OpenSans'; + font-weight: 600; + src: url('../fonts/OpenSans-VariableFont.ttf') format('truetype'); } @font-face { - font-family: "OpenSans"; - font-weight: 700; - src: url("../font/OpenSans-VariableFont.ttf") format("truetype"); + font-family: 'OpenSans'; + font-weight: 700; + src: url('../font/OpenSans-VariableFont.ttf') format('truetype'); } .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 .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: 26px; - font-weight: 300; - line-height: 36px; - margin: 0; + font-size: 26px; + 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; } .search-container h2 { - font-size: 42px; - font-weight: 300; - line-height: 57px; + font-size: 42px; + font-weight: 300; + line-height: 57px; } .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: 26px; - font-weight: 300; - line-height: 36px; - color: #283241; + font-size: 26px; + 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-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-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; } .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; - font-size: 32px; - font-weight: 300; - line-height: 43px; + margin: 0 0 24px; + font-size: 32px; + font-weight: 300; + line-height: 43px; } .image-viewport { - margin: auto; + margin: auto; } .image-viewport img { - margin: 16px auto; - box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14); + margin: 16px auto; + box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14); } .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, @@ -509,152 +509,152 @@ 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; + margin-top: 32px; + margin-bottom: 32px; + padding: 16px 24px 16px 68px; + background-color: #fff; + border-left: 4px solid #dd4d50; } .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: 24px 0; + margin: 24px 0; } .mediaobject { - margin-top: 20px; + margin-top: 20px; } .mediaobject img { - border-radius: 4px; - margin: 0; + border-radius: 4px; + margin: 0; } .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: 26px; - font-weight: 300; - line-height: 36px; - margin: 0; + font-size: 26px; + 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; } .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: "OpenSans", 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: 'OpenSans', sans-serif; + scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2); + scrollbar-width: thin; + /* Track */ + /* Handle */ } body h1, body h2, @@ -664,31 +664,31 @@ body h5, body h6, body p, body pre { - margin: 0; - font-family: "OpenSans", sans-serif; + margin: 0; + font-family: 'OpenSans', 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 h3 { - font-size: 32px; - font-weight: 300; - line-height: 43px; + font-size: 32px; + font-weight: 300; + line-height: 43px; } 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; }