diff --git a/.eslintrc.json b/.eslintrc.json index 0c3f077cf..6eb370fcb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,14 +24,23 @@ "plugin:@typescript-eslint/recommended" ], "rules": { + "@typescript-eslint/semi":"error", "@typescript-eslint/no-namespace": "warn", "@typescript-eslint/no-empty-function": "warn", "@typescript-eslint/no-useless-constructor": "off", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/quotes": ["error", "single"], "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/member-ordering": "warn", + "@typescript-eslint/member-ordering": "off", "@typescript-eslint/no-shadow": "error", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], "@angular-eslint/component-selector": [ "error", { diff --git a/package-lock.json b/package-lock.json index 673ae650c..73cf4479b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,6 @@ "otpauth": "^9.1.1", "rrule": "^2.7.2", "rxjs": "^7.8.0", - "rxjs-compat": "^6.6.7", "tinymce": "^6.8.3", "ts-md5": "^1.3.1", "zone.js": "^0.13.3" @@ -20500,12 +20499,6 @@ "tslib": "^2.1.0" } }, - "node_modules/rxjs-compat": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.6.7.tgz", - "integrity": "sha512-szN4fK+TqBPOFBcBcsR0g2cmTTUF/vaFEOZNuSdfU8/pGFnNmmn2u8SystYXG1QMrjOPBc6XTKHMVfENDf6hHw==", - "license": "Apache-2.0" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index 04a3a6d52..5a2cf1c08 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "otpauth": "^9.1.1", "rrule": "^2.7.2", "rxjs": "^7.8.0", - "rxjs-compat": "^6.6.7", "tinymce": "^6.8.3", "ts-md5": "^1.3.1", "zone.js": "^0.13.3" diff --git a/src/app/account-app/account-receipt.component.ts b/src/app/account-app/account-receipt.component.ts index 0fd2b8451..1f63f721d 100644 --- a/src/app/account-app/account-receipt.component.ts +++ b/src/app/account-app/account-receipt.component.ts @@ -20,8 +20,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { RunboxMe, RunboxWebmailAPI } from '../rmmapi/rbwebmail'; -import { AsyncSubject } from 'rxjs'; -import { take } from 'rxjs/operators'; +import { AsyncSubject, firstValueFrom } from 'rxjs'; @Component({ selector: 'app-account-receipt-component', @@ -45,12 +44,12 @@ export class AccountReceiptComponent implements OnInit { } async ngOnInit() { - this.me = await this.rmmapi.me.toPromise(); + this.me = await firstValueFrom(this.rmmapi.me); - const params = await this.route.params.pipe(take(1)).toPromise(); + const params = await firstValueFrom(this.route.params); const receiptID = params.id; - this.receipt = await this.rmmapi.getReceipt(receiptID).toPromise(); + this.receipt = await firstValueFrom(this.rmmapi.getReceipt(receiptID)); this.receipt.time = this.receipt.time.replace('T', ' '); if (this.receipt.method === 'giro') { diff --git a/src/app/account-app/account-renewals.component.ts b/src/app/account-app/account-renewals.component.ts index d3d0110ea..cad14cff2 100644 --- a/src/app/account-app/account-renewals.component.ts +++ b/src/app/account-app/account-renewals.component.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; @@ -232,7 +232,7 @@ Warning: You are close to your quotas for this product `, }) -export class AccountRenewalsRenewNowButtonComponent { +export class AccountRenewalsRenewNowButtonComponent implements OnInit { @Input() p: ActiveProduct; @Input() usage: DataUsageInterface; @Output() clicked: EventEmitter = new EventEmitter(); diff --git a/src/app/account-app/cart.service.spec.ts b/src/app/account-app/cart.service.spec.ts index 1389d2699..5900f395b 100644 --- a/src/app/account-app/cart.service.spec.ts +++ b/src/app/account-app/cart.service.spec.ts @@ -21,8 +21,7 @@ import { CartService } from './cart.service'; import { ProductOrder } from './product-order'; import { StorageService } from '../storage.service'; import { RunboxWebmailAPI } from '../rmmapi/rbwebmail'; -import { of } from 'rxjs'; -import { take } from 'rxjs/operators'; +import { firstValueFrom, of } from 'rxjs'; import { Decimal } from 'decimal.js-light'; Decimal.set({ precision: 2, rounding: Decimal.ROUND_HALF_EVEN }); @@ -49,7 +48,7 @@ describe('CartService', () => { const order = new ProductOrder(401,'subscription', new Decimal(3), 402); await cart.add(order); - console.log(await cart.items.pipe(take(1)).toPromise()); + console.log(await firstValueFrom(cart.items)); expect(await cart.contains(401, 402)).toBe(true, 'cart contains the renewal product'); expect(await cart.contains(401)).toBe(false, 'cart does not contain the new product'); }); diff --git a/src/app/account-app/cart.service.ts b/src/app/account-app/cart.service.ts index afd1c2f23..5c9a9c376 100644 --- a/src/app/account-app/cart.service.ts +++ b/src/app/account-app/cart.service.ts @@ -18,8 +18,7 @@ // ---------- END RUNBOX LICENSE ---------- import { Injectable } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; -import { take } from 'rxjs/operators'; +import { firstValueFrom, ReplaySubject } from 'rxjs'; import { ProductOrder } from './product-order'; import { StorageService } from '../storage.service'; @@ -46,7 +45,7 @@ export class CartService { } async add(p: ProductOrder): Promise { - const items = await this.items.pipe(take(1)).toPromise(); + const items = await firstValueFrom(this.items); for (const i of items) { // Cannot order multiples of subscription products @@ -70,7 +69,7 @@ export class CartService { } async contains(pid: number, apid?: number): Promise { - const items = await this.items.pipe(take(1)).toPromise(); + const items = await firstValueFrom(this.items); for (const p of items) { if (p.pid === pid && p.apid === apid) { return true; @@ -80,7 +79,7 @@ export class CartService { } async remove(order: ProductOrder): Promise { - const items = await this.items.pipe(take(1)).toPromise(); + const items = await firstValueFrom(this.items); // check if it's enough to just reduce the quantity on existing product for (const i of items) { if (i.isSameProduct(order)) { diff --git a/src/app/account-app/credit-cards/stripe-add-card-dialog.component.ts b/src/app/account-app/credit-cards/stripe-add-card-dialog.component.ts index 1cd98ee22..fbe56a54c 100644 --- a/src/app/account-app/credit-cards/stripe-add-card-dialog.component.ts +++ b/src/app/account-app/credit-cards/stripe-add-card-dialog.component.ts @@ -64,8 +64,8 @@ export class StripeAddCardDialogComponent implements AfterViewInit { } async ngAfterViewInit() { - await stripeLoader.toPromise(); - const stripePubkey = await this.paymentsservice.stripePubkey.toPromise(); + await firstValueFrom(stripeLoader); + const stripePubkey = await firstValueFrom(this.paymentsservice.stripePubkey); const customerSession = await firstValueFrom(this.paymentsservice.customerSession()); this.stripe = Stripe(stripePubkey); diff --git a/src/app/account-app/no-products-for-subaccounts.guard.ts b/src/app/account-app/no-products-for-subaccounts.guard.ts index 0e54f849c..d155f854f 100644 --- a/src/app/account-app/no-products-for-subaccounts.guard.ts +++ b/src/app/account-app/no-products-for-subaccounts.guard.ts @@ -19,7 +19,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { RunboxWebmailAPI } from '../rmmapi/rbwebmail'; import { RMMAuthGuardService } from '../rmmapi/rmmauthguard.service'; @@ -54,7 +54,7 @@ export class NoProductsForSubaccountsGuard { (success) => { if (typeof(success) === 'boolean' && success) { if (restricted) { - return this.rmmapi.me.toPromise().then(me => { + return firstValueFrom(this.rmmapi.me).then(me => { if (me.owner) { return this.router.parseUrl('/account/not-for-subaccounts'); } else { diff --git a/src/app/account-app/shopping-cart.component.ts b/src/app/account-app/shopping-cart.component.ts index 7349cb8fe..a31b8c06e 100644 --- a/src/app/account-app/shopping-cart.component.ts +++ b/src/app/account-app/shopping-cart.component.ts @@ -20,7 +20,7 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog as MatDialog, MatDialogRef as MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subject } from 'rxjs'; +import { firstValueFrom, Subject } from 'rxjs'; import { CartService } from './cart.service'; import { BitpayPaymentDialogComponent } from './bitpay-payment-dialog.component'; @@ -177,7 +177,7 @@ export class ShoppingCartComponent implements OnInit { // maybe there is a more elegant way to do this, but it's concise and works :) const cartItems = JSON.parse(JSON.stringify(items)); - let products = await this.paymentsservice.products.toPromise(); + let products = await firstValueFrom(this.paymentsservice.products); // Check if all the products in the cart had their details in the paymentservice. // This may not be true if they're coming from the URL for instance, @@ -192,7 +192,7 @@ export class ShoppingCartComponent implements OnInit { } const neededPids = Array.from(neededPidsSet.values()); if (neededPids.length > 0) { - const extras = await this.rmmapi.getProducts(neededPids).toPromise(); + const extras = await firstValueFrom(this.rmmapi.getProducts(neededPids)); if (extras.length !== neededPids.length) { console.warn(`Failed to load products ${neededPids.join(',')} (got: ${JSON.stringify(extras)})`); } @@ -213,7 +213,7 @@ export class ShoppingCartComponent implements OnInit { } async checkIfLegal(items: CartItem[]) { - const me = await this.rmmapi.me.toPromise(); + const me = await firstValueFrom(this.rmmapi.me); this.orderError = undefined; // unless we find something else :) diff --git a/src/app/account-app/stripe-payment-dialog.component.ts b/src/app/account-app/stripe-payment-dialog.component.ts index 9046f46fa..75bbd3c69 100644 --- a/src/app/account-app/stripe-payment-dialog.component.ts +++ b/src/app/account-app/stripe-payment-dialog.component.ts @@ -87,7 +87,7 @@ export class StripePaymentDialogComponent implements AfterViewInit { } async ngAfterViewInit() { - await stripeLoader.toPromise(); + await firstValueFrom(stripeLoader); const stripePubkey = await firstValueFrom(this.paymentsservice.stripePubkey); const customerSession = await firstValueFrom(this.paymentsservice.customerSession()); @@ -173,7 +173,7 @@ export class StripePaymentDialogComponent implements AfterViewInit { this.state = 'failure'; return; } - + } handleConfirmationToken(cId: string) { diff --git a/src/app/account-details/account-settings.component.ts b/src/app/account-details/account-settings.component.ts index 7097ecd60..422e837cc 100644 --- a/src/app/account-details/account-settings.component.ts +++ b/src/app/account-details/account-settings.component.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { AsyncSubject } from 'rxjs'; import { share, timeout } from 'rxjs/operators'; import { RMM } from '../rmm'; @@ -28,7 +28,7 @@ import { AccountSettingsInterface } from '../rmm/account-settings'; templateUrl: './account-settings.component.html', styleUrls: ['account-details.component.scss'], }) -export class AccountSettingsComponent { +export class AccountSettingsComponent implements OnInit { displayedColumns: string[] = ['description', 'status']; settings = new AsyncSubject(); settingsArray = []; diff --git a/src/app/account-details/personal-details.component.ts b/src/app/account-details/personal-details.component.ts index 24b2b4a94..75416d191 100644 --- a/src/app/account-details/personal-details.component.ts +++ b/src/app/account-details/personal-details.component.ts @@ -18,10 +18,10 @@ // ---------- END RUNBOX LICENSE ---------- import { HttpClient, HttpResponse } from '@angular/common/http'; -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; -import { Subject } from 'rxjs'; +import { firstValueFrom, Subject } from 'rxjs'; import { RMM } from '../rmm'; import { map } from 'rxjs/operators'; import { AccountDetailsInterface } from '../rmm/account-details'; @@ -39,7 +39,7 @@ interface CountryAndTimezone { templateUrl: './personal-details.component.html', styleUrls: ['account-details.component.scss'], }) -export class PersonalDetailsComponent { +export class PersonalDetailsComponent implements OnInit { hide = true; myControl = new UntypedFormControl(); countriesAndTimezones: CountryAndTimezone[] = []; @@ -109,10 +109,9 @@ export class PersonalDetailsComponent { } loadTimezones() { - this.http + firstValueFrom(this.http .get('/rest/v1/timezones') - .pipe(map((res: HttpResponse) => res['result'])) - .toPromise() + .pipe(map((res: HttpResponse) => res['result']))) .then((data) => this.timezones = data.timezones); } @@ -126,10 +125,9 @@ export class PersonalDetailsComponent { } private loadSelectFields() { - this.http + firstValueFrom(this.http .get('/rest/v1/account/details') - .pipe(map((res: HttpResponse) => res['result'])) - .toPromise() + .pipe(map((res: HttpResponse) => res['result']))) .then((data) => { this.selectedCountry = data.country; this.selectedTimezone = data.timezone; diff --git a/src/app/account-details/storage-data.component.ts b/src/app/account-details/storage-data.component.ts index 5dfb34dfb..abcad5a5c 100644 --- a/src/app/account-details/storage-data.component.ts +++ b/src/app/account-details/storage-data.component.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { AsyncSubject } from 'rxjs'; import { RMM } from '../rmm'; @@ -34,7 +34,7 @@ export interface DataUsage { templateUrl: './storage-data.component.html', styleUrls: ['account-details.component.scss'], }) -export class StorageDataComponent { +export class StorageDataComponent implements OnInit { dataUsage = new AsyncSubject(); displayedColumns: string[] = ['type', 'quota', 'usage', 'percentage_used']; diff --git a/src/app/account-security/sessions.component.ts b/src/app/account-security/sessions.component.ts index ccc26f8c0..a95d29f94 100644 --- a/src/app/account-security/sessions.component.ts +++ b/src/app/account-security/sessions.component.ts @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component, Output, EventEmitter, ViewChild } from '@angular/core'; +import { Component, Output, EventEmitter, ViewChild, OnInit } from '@angular/core'; import { MatLegacyPaginator as MatPaginator } from '@angular/material/legacy-paginator'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; @@ -28,7 +28,7 @@ import { RMM } from '../rmm'; styleUrls: ['account.security.component.scss'], templateUrl: 'sessions.component.html', }) -export class SessionsComponent { +export class SessionsComponent implements OnInit { panelOpenState = false; @ViewChild(MatPaginator, { static: false }) paginator: MatPaginator; @Output() Close: EventEmitter = new EventEmitter(); diff --git a/src/app/account-security/two-factor-authentication.component.html b/src/app/account-security/two-factor-authentication.component.html index 9a5b925e8..47056cc25 100644 --- a/src/app/account-security/two-factor-authentication.component.html +++ b/src/app/account-security/two-factor-authentication.component.html @@ -10,8 +10,9 @@

Two-Factor Authentication

This is the Two-Factor Authentication (2FA) configuration screen, where you can configure the various options for enabling and using 2FA.

-

We recommended that you create an Unlock Code when enabling 2FA. This special code can - be used to disable 2FA if you have problems logging in with your 2FA codes.

+

Unlock Code: An unlock code is required so that if you lose access to your TOTP or OTP codes, you can still get access to your account. + Using the unlock code will turn off 2FA for your account. + You can easily re-instate 2FA on this page once you are successfully logged in again.

Note: For security reasons, enabling 2FA will disable your account password for non-web services. These include IMAP, POP, SMTP, FTP, CalDAV, and CardDAV services that may be used with progams or apps on your device. Therefore you will need to set up App diff --git a/src/app/aliases/aliases.lister.spec.ts b/src/app/aliases/aliases.lister.spec.ts index 6effe5bf4..8a3ffb893 100644 --- a/src/app/aliases/aliases.lister.spec.ts +++ b/src/app/aliases/aliases.lister.spec.ts @@ -70,7 +70,7 @@ describe('AliasesListerComponent', () => { return of({ status: 'error', result: 'unimplemented' - }) + }); } } } }, @@ -94,11 +94,11 @@ describe('AliasesListerComponent', () => { }); fixture = TestBed.createComponent(AliasesListerComponent); component = fixture.componentInstance; - }) + }); it('loads aliases through RMM', () => { expect(component.aliases).toEqual(ALIASES); - }) + }); it('sets the default email to current user email', () => { expect(component.defaultEmail).toBe(DEFAULT_EMAIL); diff --git a/src/app/aliases/aliases.lister.ts b/src/app/aliases/aliases.lister.ts index a8781079d..847a1fe0e 100644 --- a/src/app/aliases/aliases.lister.ts +++ b/src/app/aliases/aliases.lister.ts @@ -58,7 +58,7 @@ export class AliasesListerComponent { if (result !== undefined) { this.aliases.push(result); } - }) + }); } edit (item: object) { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b5a152f8f..055e4c6ae 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -47,7 +47,7 @@ import { map, take, skip, mergeMap, filter, tap, throttleTime, debounceTime, dis import { WebSocketSearchService } from './websocketsearch/websocketsearch.service'; import { WebSocketSearchMailList } from './websocketsearch/websocketsearchmaillist'; -import { from, Observable } from 'rxjs'; +import { from, Observable, lastValueFrom } from 'rxjs'; import { xapianLoadedSubject } from './xapian/xapianwebloader'; import { SwPush } from '@angular/service-worker'; import { exportKeysFromJWK } from './webpush/vapid.tools'; @@ -396,7 +396,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis && prev.every((f, index) => objectEqualWithKeys(f, curr[index], [ 'folderId', 'totalMessages', 'newMessages', 'folderName' - ])) + ])); })) .pipe(map((folders: FolderListEntry[]) => folders.filter(f => f.folderPath.indexOf('Drafts') !== 0)) ); @@ -616,11 +616,11 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis async emptySpam(spamFolderName) { console.log('found spam folder with name', spamFolderName); - const messageLists = await this.rmmapi.listAllMessages( + const messageLists = await lastValueFrom(this.rmmapi.listAllMessages( 0, 0, 0, RunboxWebmailAPI.LIST_ALL_MESSAGES_CHUNK_SIZE, true, spamFolderName - ).toPromise(); + )); const messageIds = messageLists.map(msg => msg.id); this.messageActionsHandler.updateMessages({ @@ -929,7 +929,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis } public rowSelected(rowIndex: number, columnIndex: number, multiSelect?: boolean) { - const isSelect = (columnIndex === 0) || multiSelect + const isSelect = (columnIndex === 0) || multiSelect; if ((this.selectedFolder === this.messagelistservice.templateFolderName) && !isSelect) { this.draftDeskService.newTemplateDraft( @@ -1452,14 +1452,14 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis } async enableNotification() { - console.log('request notification permission') + console.log('request notification permission'); try { - const result = await Notification.requestPermission() - console.log('Notification.permission', Notification.permission) + const result = await Notification.requestPermission(); + console.log('Notification.permission', Notification.permission); if (result === 'granted') this.subscribeToNotifications(); } catch (error) { - console.error(error) + console.error(error); } } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index bced66edb..72427892d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -202,7 +202,7 @@ const routes: Routes = [ { provide: HTTP_INTERCEPTORS, useClass: RMMHttpInterceptorService, multi: true }, { provide: ErrorHandler, useClass: SentryErrorHandler }, { provide: SwRegistrationOptions, - useFactory: () => ({ registrationStrategy: 'registerWithDelay:5000' }) } + useFactory: () => ({ registrationStrategy: 'registerWhenStable:30000' }) } ], bootstrap: [MainContainerComponent] }) diff --git a/src/app/changelog/changelog.component.scss b/src/app/changelog/changelog.component.scss index 33643b198..ac11df38c 100644 --- a/src/app/changelog/changelog.component.scss +++ b/src/app/changelog/changelog.component.scss @@ -6,7 +6,8 @@ h4 { margin: 0.75em 0; } -.mat-list-base[dense] .mat-list-item, .mat-list-base[dense] .mat-list-option { +.mat-list-base[dense] .mat-list-item, +.mat-list-base[dense] .mat-list-option { height: 30px; } diff --git a/src/app/changelog/changes.ts b/src/app/changelog/changes.ts index 125deeb28..d19af7eaa 100644 --- a/src/app/changelog/changes.ts +++ b/src/app/changelog/changes.ts @@ -1,81 +1,535 @@ -// --------- BEGIN RUNBOX LICENSE --------- -// Copyright (C) 2016-2019 Runbox Solutions AS (runbox.com). -// -// This file is part of Runbox 7. -// -// Runbox 7 is free software: You can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the -// Free Software Foundation, either version 3 of the License, or (at your -// option) any later version. -// -// Runbox 7 is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Runbox 7. If not, see . -// ---------- END RUNBOX LICENSE ---------- - -import moment from 'moment'; - -export enum EntryType { - BUILD, - CI, - DOCS, - FEAT, - FIX, - PERF, - REFACTOR, - STYLE, - TEST -} - -const typeMapping = { - 'build': EntryType.BUILD, - 'ci': EntryType.CI, - 'docs': EntryType.DOCS, - 'feat': EntryType.FEAT, - 'feature': EntryType.FEAT, - 'fix': EntryType.FIX, - 'perf': EntryType.PERF, - 'refactor': EntryType.REFACTOR, - 'style': EntryType.STYLE, - 'test': EntryType.TEST, - // known typos - 'refator': EntryType.REFACTOR, -}; - -export class ChangelogEntry { - public datetime: moment.Moment; - - constructor( - public hash: string, - public epoch: number, - public type: EntryType, - public component: string, - public description: string, - ) { - this.datetime = moment(this.epoch * 1000); // in ms - } - - get url(): string { - return `https://github.com/runbox/runbox7/commit/${this.hash}`; - } -} - - -// These entries are auto-generated from build-changelog.js. -// Manual edits will be overwritten. -/* eslint-disable @typescript-eslint/quotes */ -// BEGIN:AUTOGENERATED -const changes = [ - [ - "2b05c475", - "1747382829", +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2019 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import moment from 'moment'; + +export enum EntryType { + BUILD, + CI, + DOCS, + FEAT, + FIX, + PERF, + REFACTOR, + STYLE, + TEST +} + +const typeMapping = { + 'build': EntryType.BUILD, + 'ci': EntryType.CI, + 'docs': EntryType.DOCS, + 'feat': EntryType.FEAT, + 'feature': EntryType.FEAT, + 'fix': EntryType.FIX, + 'perf': EntryType.PERF, + 'refactor': EntryType.REFACTOR, + 'style': EntryType.STYLE, + 'test': EntryType.TEST, + // known typos + 'refator': EntryType.REFACTOR, +}; + +export class ChangelogEntry { + public datetime: moment.Moment; + + constructor( + public hash: string, + public epoch: number, + public type: EntryType, + public component: string, + public description: string, + ) { + this.datetime = moment(this.epoch * 1000); // in ms + } + + get url(): string { + return `https://github.com/runbox/runbox7/commit/${this.hash}`; + } +} + + +// These entries are auto-generated from build-changelog.js. +// Manual edits will be overwritten. +/* eslint-disable @typescript-eslint/quotes */ +// BEGIN:AUTOGENERATED const changes = [ + [ + "137575af", + "1769569113", + "style", + "cleanup", + "remove obsolete CSS styles" + ], + [ + "dac39ac9", + "1769569036", + "refactor", + "cleanup", + "remove unused code and constants" + ], + [ + "b383a4e0", + "1769568964", + "fix", + "stability", + "gate service worker registration" + ], + [ + "84c8a297", + "1769566090", + "feat", + "migration", + "migrate to RxJS 7" + ], + [ + "15d5b4f1", + "1769272608", + "style", + "account-upgrades", + "amend text regarding sub-accounts" + ], + [ + "9f4c2fb3", + "1769009210", + "refactor", + "2fa", + "Change 2FA unlock code description" + ], + [ + "f2d51985", + "1768489312", + "fix", + "cart", + "Fix add/remove cart item" + ], + [ + "3bc39da0", + "1768480595", + "feat", + "upgrades", + "Show recommended plans with addons if needed to downgrade" + ], + [ + "eaf16ee2", + "1768388642", + "refactor", + "upgrades", + "Ensure currency calculations are correct" + ], + [ + "1b76a7d4", + "1767483281", + "fix", + "mailviewer", + "intercept `mailto:` links and open a compose window" + ], + [ + "b52c415a", + "1765455095", + "fix", + "notifications", + "Check Notifcation API exists when showing button" + ], + [ + "ad665a71", + "1764772951", + "fix", + "plans", + "Enable downgrade to Micro including Email Hosting" + ], + [ + "840522fb", + "1764245620", + "style", + "payment", + "Change \"pending\" to \"incomplete\"." + ], + [ + "9ada126e", + "1764175688", + "fix", + "stripe", + "Fail early if stripe submission errors" + ], + [ + "757de0ed", + "1762962071", + "fix", + "stripe", + "Improve on-page status updates for Stripe payments" + ], + [ + "35cb13aa", + "1761061904", + "fix", + "notification", + "notification permission on click" + ], + [ + "524e9134", + "1760020202", + "style", + "payment", + "Correct image source URLs." + ], + [ + "42c6b9d2", + "1759989960", + "style", + "payment", + "Move shopping cart style back to main stylesheet for it to work." + ], + [ + "2154f5a8", + "1759989960", + "style", + "payment", + "Include missing SCSS file." + ], + [ + "07f415a6", + "1759989960", + "style", + "payment", + "Add Apple Pay logo and imrpove formatting." + ], + [ + "09968dc8", + "1759916636", + "feat", + "dkim", + "Display all users domains" + ], + [ + "a6316817", + "1759692383", + "style", + "payment", + "Minor textual correction." + ], + [ + "89bf44f6", + "1759692157", + "style", + "payment", + "Add cryptocurrency logos." + ], + [ + "2cc33c58", + "1759689457", + "style", + "payment", + "Bundle Stripe and PayPal, and improve layout." + ], + [ + "89de7403", + "1759202407", + "style", + "payment", + "Add note about number of email aliases on sub-accounts." + ], + [ + "a04b4700", + "1759144876", + "fix", + "mailviewer", + "Show images with ngsw bypass" + ], + [ + "0eb7556f", + "1757937300", + "fix", + "2fa", + "Show QR codes for 2FA generation" + ], + [ + "440fbdfc", + "1756825896", + "fix", + "sentry", + "Downgrade client to make it work with sentry server" + ], + [ + "10e135be", + "1756794090", + "style", + "payment", + "Fix typo." + ], + [ + "7ebeca44", + "1756335277", + "style", + "settings", + "Moved and updated link to documentation." + ], + [ + "a1faab26", + "1756305250", + "fix", + "paymentcards", + "Make add card dialog fit on screen" + ], + [ + "50b8e5db", + "1755783796", "feat", "worker", - "Add sentry to xapian worker scope" + "Improve Sentry integration" + ], + [ + "db8ccdef", + "1754909871", + "fix", + "stripe", + "Allow dialog to scroll (make Submit reachable)" + ], + [ + "4bdcff06", + "1751829714", + "style", + "dkim", + "Add note about format." + ], + [ + "7d6ddd10", + "1751477343", + "fix", + "cart", + "Renewing a subscription now sets a quantity of 1" + ], + [ + "1eebaa8a", + "1751367740", + "fix", + "commit-lint", + "allow hyphen in commit subject" + ], + [ + "67c0a118", + "1751367409", + "fix", + "login", + "make login inputs required" + ], + [ + "29eb8869", + "1751367360", + "fix", + "side-menu", + "make buttons keyboard navigable" + ], + [ + "8d22da9f", + "1751363648", + "fix", + "upgrades", + "Highlight correct \"current sub\" in plans table" + ], + [ + "d40892f9", + "1751296588", + "build", + "lint", + "Remove typos from commit-lint tags" + ], + [ + "e7be3886", + "1751296588", + "fix", + "upgrades", + "Indicate which plans would put the user over quota" + ], + [ + "ea501342", + "1751291013", + "fix", + "subscriptions", + "Only warn about close-quota for Disk/File quotas" + ], + [ + "1a198352", + "1751277492", + "fix", + "upgrades", + "Prevent two subscriptions being bought at once" + ], + [ + "9e4d014c", + "1750951971", + "fix", + "mailviewer", + "Text/HTML views blank when attachment has odd types" + ], + [ + "290ebe7e", + "1750694678", + "fix", + "upgrades", + "Remove ability to buy plans when over usage" + ], + [ + "8b4ad27a", + "1750691421", + "fix", + "upgrades", + "Checks quota usage for plan upgrades table" + ], + [ + "77657d08", + "1750691117", + "build", + "lint", + "Fix allowed commit-message tags" + ], + [ + "19ee3d5f", + "1750410342", + "style", + "upgrades", + "Improve formatting of recommended plans and other fixes." + ], + [ + "e53115a7", + "1750354119", + "fix", + "upgrades", + "Compare user's quotas when displaying plans" + ], + [ + "6d14aa2b", + "1750088797", + "fix", + "renewals", + "Removes confusing Yes/No for renewal checkboxes" + ], + [ + "b2d769b7", + "1750084083", + "fix", + "mailviewer", + "Fixes attachment display with HTML (again)" + ], + [ + "4a4e08e2", + "1749740369", + "fix", + "mailviewer", + "Only show attachments that are not inline in HTML" + ], + [ + "1beabc35", + "1749571399", + "fix", + "folderlist", + "place dir expander button above the anchor overlay" + ], + [ + "1e5cee80", + "1749544633", + "fix", + "github", + "make checkout of fork work in commit lint workflow" + ], + [ + "8d0d3cde", + "1749227244", + "style", + "mail", + "Make Allow and Block Sender descriptions consistent." + ], + [ + "652e3748", + "1749031846", + "build", + "ci", + "Remove redundant cache action" + ], + [ + "9a0189bb", + "1749031810", + "build", + "ci", + "prevent improper commit formatting from being merged into master" + ], + [ + "6cfc7dd4", + "1748872985", + "fix", + "details", + "show alternative email resend on email change" + ], + [ + "0036ce78", + "1748517484", + "fix", + "payments", + "Handle post stripe 3d secure correctly" + ], + [ + "ea2c77ca", + "1747908332", + "fix", + "build", + "Remove unused dependencies (revert)" + ], + [ + "2ef0626f", + "1747734901", + "build", + "deps", + "Remove unused dependencies" + ], + [ + "183d42ad", + "1747729699", + "build", + "deps", + "Remove unused dependencies" + ], + [ + "90d46835", + "1747328794", + "fix", + "payments", + "Renew or create products after Stripe payment" + ], + [ + "9b2580c3", + "1747302470", + "fix", + "folderlist", + "Make folder buttons keyboard navigable" + ], + [ + "51cb8dfe", + "1747297115", + "feat", + "login", + "Add autcomplete attrs in accordance with WCAG guidelines" + ], + [ + "403f7367", + "1747296965", + "fix", + "login", + "Fix user not sent with login req even when field is filled" ], [ "111120bd", @@ -10718,14 +11172,14 @@ const changes = [ "make sure folders are handled correctly regadless of order" ] ]; -// END:AUTOGENERATED - -export const changelog: ChangelogEntry[] = changes.map(entry => { - const type = typeMapping[entry[2]]; - if (type === undefined) { - throw new Error(`Invalid change type "${entry[2]}" in ${entry}`); - } - return new ChangelogEntry( - entry[0], parseInt(entry[1], 10), type, entry[3], entry[4] - ); -}); +// END:AUTOGENERATED + +export const changelog: ChangelogEntry[] = changes.map(entry => { + const type = typeMapping[entry[2]]; + if (type === undefined) { + throw new Error(`Invalid change type "${entry[2]}" in ${entry}`); + } + return new ChangelogEntry( + entry[0], parseInt(entry[1], 10), type, entry[3], entry[4] + ); +}); diff --git a/src/app/common/preferences.service.ts b/src/app/common/preferences.service.ts index 7ed303938..6d602823a 100644 --- a/src/app/common/preferences.service.ts +++ b/src/app/common/preferences.service.ts @@ -18,7 +18,7 @@ // ---------- END RUNBOX LICENSE ---------- import { Injectable } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; +import { firstValueFrom, ReplaySubject } from 'rxjs'; import { take } from 'rxjs/operators'; import { StorageService } from '../storage.service'; @@ -151,7 +151,7 @@ export class PreferencesService { }; } - const allPrefs = await this.preferences.pipe(take(1)).toPromise(); + const allPrefs = await firstValueFrom(this.preferences); Object.keys(prefsdata[DefaultPrefGroups.Global]['entries']).forEach((key) => { allPrefs.set(`${DefaultPrefGroups.Global}:${key}`, prefsdata[DefaultPrefGroups.Global]['entries'][key]); }); @@ -179,7 +179,7 @@ export class PreferencesService { } private async uploadPreferenceData(level: string) { - const prefs = await this.preferences.pipe(take(1)).toPromise(); + const prefs = await firstValueFrom(this.preferences); const entriesObj = {}; prefs.forEach((value, key) => { // for (const [key, value] of prefs) { @@ -219,7 +219,7 @@ export class PreferencesService { if (this.loadedOldStyle) { return; } - let prefs = await this.preferences.pipe(take(1)).toPromise(); + let prefs = await firstValueFrom(this.preferences); if (!prefs) { // Already set / imported prefs = new Map(); @@ -235,7 +235,7 @@ export class PreferencesService { const showImagesDecisionKey = 'rmm7showimagesdecision'; const resizerPercentageKey = 'rmm7resizerpercentage'; - const uid = await this.storage.uid.toPromise(); + const uid = await firstValueFrom(this.storage.uid); if (level === DefaultPrefGroups.Global) { if (localStorage.getItem('rmm7experimentalFeatureEnabled') !== null) { diff --git a/src/app/common/util.spec.ts b/src/app/common/util.spec.ts index b50d532ea..3fa34121f 100644 --- a/src/app/common/util.spec.ts +++ b/src/app/common/util.spec.ts @@ -104,53 +104,53 @@ describe('withKeys', () => { }); it('can fetch nested items', () => { - expect(withKeys(o, ['c'])).toEqual({c: [3, 4]}) + expect(withKeys(o, ['c'])).toEqual({c: [3, 4]}); }); it('returns an empty object if all keys dont exist', () => { - expect(withKeys(o, ['x', 'y', 'z'])).toEqual({}) + expect(withKeys(o, ['x', 'y', 'z'])).toEqual({}); }); it('returns an empty object if no keys are provided', () => { expect(withKeys(o, [])).toEqual({}); }); -}) +}); describe('objectEqualWithKeys', () => { const o = {a: 1, b: 2, c: [3, 4], d: {e: 5}}; // since they are both empty objects it('matches when no keys are specified', () => { - expect(objectEqualWithKeys(o, o, [])).toBeTrue() - expect(objectEqualWithKeys(o, {}, [])).toBeTrue() - expect(objectEqualWithKeys({}, {}, [])).toBeTrue() + expect(objectEqualWithKeys(o, o, [])).toBeTrue(); + expect(objectEqualWithKeys(o, {}, [])).toBeTrue(); + expect(objectEqualWithKeys({}, {}, [])).toBeTrue(); }); it('matches when provided keys that exist', () => { - expect(objectEqualWithKeys(o, o, ['a', 'b'])).toBeTrue() - expect(objectEqualWithKeys(o, o, ['a', 'b', 'c', 'd'])).toBeTrue() + expect(objectEqualWithKeys(o, o, ['a', 'b'])).toBeTrue(); + expect(objectEqualWithKeys(o, o, ['a', 'b', 'c', 'd'])).toBeTrue(); }); // again, they are both empty objects it('matches when provided keys that dont exist', () => { - expect(objectEqualWithKeys(o, o, ['f', 'g', 'h'])).toBeTrue() + expect(objectEqualWithKeys(o, o, ['f', 'g', 'h'])).toBeTrue(); }); it('matches only by keys, ignoring other values', () => { - expect(objectEqualWithKeys(o, {a: 1, b: 2, c: 32}, ['a', 'b'])).toBeTrue() + expect(objectEqualWithKeys(o, {a: 1, b: 2, c: 32}, ['a', 'b'])).toBeTrue(); }); it('matches nested objects and arrays', () => { - expect(objectEqualWithKeys(o, {a: 25, c: [3, 4], d: {e: 5}}, ['c', 'd'])).toBeTrue() + expect(objectEqualWithKeys(o, {a: 25, c: [3, 4], d: {e: 5}}, ['c', 'd'])).toBeTrue(); }); it('fails to match if values are different', () => { - expect(objectEqualWithKeys(o, {a: 222, b: 123}, ['a', 'b'])).toBeFalse() - expect(objectEqualWithKeys(o, {c: [4, 5]}, ['c'])).toBeFalse() - expect(objectEqualWithKeys(o, {d: {e: 6}}, ['d'])).toBeFalse() + expect(objectEqualWithKeys(o, {a: 222, b: 123}, ['a', 'b'])).toBeFalse(); + expect(objectEqualWithKeys(o, {c: [4, 5]}, ['c'])).toBeFalse(); + expect(objectEqualWithKeys(o, {d: {e: 6}}, ['d'])).toBeFalse(); }); it('fails to match if comparison object is empty', () => { - expect(objectEqualWithKeys(o, {}, ['a', 'b'])).toBeFalse() + expect(objectEqualWithKeys(o, {}, ['a', 'b'])).toBeFalse(); }); }); \ No newline at end of file diff --git a/src/app/common/util.ts b/src/app/common/util.ts index fd94fe97f..3c8f045ff 100644 --- a/src/app/common/util.ts +++ b/src/app/common/util.ts @@ -73,7 +73,7 @@ export function withKeys(o: object, keys: any[]): object { return keys.reduce((acc, k) => { if (k in o) { acc[k] = o[k]; } return acc; - }, {}) + }, {}); } /** diff --git a/src/app/compose/compose.component.ts b/src/app/compose/compose.component.ts index 7d52848c8..e1aa9ecf2 100644 --- a/src/app/compose/compose.component.ts +++ b/src/app/compose/compose.component.ts @@ -46,7 +46,7 @@ declare const MailParser; const LOCAL_STORAGE_SHOW_POPULAR_RECIPIENTS = 'showPopularRecipients'; const LOCAL_STORAGE_DEFAULT_HTML_COMPOSE = 'composeInHTMLByDefault'; -const DOWNLOAD_DRAFT_URL = '/ajax/download_draft_attachment?filename=' +const DOWNLOAD_DRAFT_URL = '/ajax/download_draft_attachment?filename='; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -216,7 +216,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit { // have autosave until it was saved as a draft. if (this.model.tid) return; - this.submit(false) + this.submit(false); }); this.formGroup.controls.from.valueChanges @@ -471,8 +471,8 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit { public loadDraft(msgObj) { if (msgObj.errors) { - this.snackBar.open(msgObj.errors[0], 'Ok') - throw msgObj + this.snackBar.open(msgObj.errors[0], 'Ok'); + throw msgObj; } const model = new DraftFormModel(); @@ -869,7 +869,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit { } public saveTemplate() { - this.isTemplate = true + this.isTemplate = true; this.submit(false); } diff --git a/src/app/compose/draftdesk.service.ts b/src/app/compose/draftdesk.service.ts index 0c1d02731..82e6f452b 100644 --- a/src/app/compose/draftdesk.service.ts +++ b/src/app/compose/draftdesk.service.ts @@ -26,7 +26,7 @@ import { MailAddressInfo } from '../common/mailaddressinfo'; import { MessageListService } from '../rmmapi/messagelist.service'; import { MessageTableRowTool} from '../messagetable/messagetablerow'; import { Identity, ProfileService } from '../profiles/profile.service'; -import { from, of, BehaviorSubject } from 'rxjs'; +import { from, of, BehaviorSubject, firstValueFrom } from 'rxjs'; import { map, mergeMap, bufferCount, take, distinctUntilChanged } from 'rxjs/operators'; import moment from 'moment'; @@ -264,7 +264,7 @@ export class DraftDeskService { && prev.every((f, index) => objectEqualWithKeys(f, curr[index], [ 'folderId', 'totalMessages', 'newMessages' - ])) + ])); })) .subscribe((folders) => { this.refreshDrafts(); @@ -317,8 +317,8 @@ export class DraftDeskService { this.rmmapi.getMessageContents(messageId).subscribe((contents) => { const res: any = Object.assign({}, contents); - const {subject} = res.headers - let { to } = res.headers + const {subject} = res.headers; + let { to } = res.headers; if (to) { to = new MailAddressInfo(to.value.name, to.value.address).nameAndAddress; @@ -329,14 +329,14 @@ export class DraftDeskService { this.mainIdentity(), to, subject - ) + ); draftFormModel.tid = messageId; draftFormModel.msg_body = contents.text.text; draftFormModel.html = contents.text.html; return this.newDraft(draftFormModel); - }) + }); } public async newBugReport( @@ -353,9 +353,9 @@ export class DraftDeskService { '"Runbox 7 Bug Reports" ', 'Runbox 7 Bug Report' ); - const template = await this.http.get('assets/templates/bug_report.txt', - {responseType: 'text'}).toPromise(); - const me = await this.rmmapi.me.toPromise(); + const template = await firstValueFrom(this.http.get('assets/templates/bug_report.txt', + {responseType: 'text'})); + const me = await firstValueFrom(this.rmmapi.me); let body = `${template}` ; @@ -382,8 +382,8 @@ export class DraftDeskService { } public async newVideoCallInvite(to: string, url: URL) { - const template = await this.http.get('assets/templates/video_call.txt', - {responseType: 'text'}).toPromise(); + const template = await firstValueFrom(this.http.get('assets/templates/video_call.txt', + {responseType: 'text'})); const draftObj = DraftFormModel.create( -1, this.mainIdentity(), diff --git a/src/app/compose/recipients.service.spec.ts b/src/app/compose/recipients.service.spec.ts index ab2c353ba..cace40396 100644 --- a/src/app/compose/recipients.service.spec.ts +++ b/src/app/compose/recipients.service.spec.ts @@ -1,18 +1,18 @@ // --------- BEGIN RUNBOX LICENSE --------- // Copyright (C) 2016-2020 Runbox Solutions AS (runbox.com). -// +// // This file is part of Runbox 7. -// +// // Runbox 7 is free software: You can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // Runbox 7 is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- @@ -22,7 +22,7 @@ import { RecipientsService } from './recipients.service'; import { ContactsService } from '../contacts-app/contacts.service'; import { SearchService, XAPIAN_GLASS_WR } from '../xapian/searchservice'; import { StorageService } from '../storage.service'; -import { AsyncSubject, of, Subject } from 'rxjs'; +import { AsyncSubject, of, Subject, firstValueFrom, lastValueFrom } from 'rxjs'; import { take } from 'rxjs/operators'; import { Contact } from '../contacts-app/contact'; import { RunboxWebmailAPI } from '../rmmapi/rbwebmail'; @@ -109,7 +109,7 @@ describe('RecipientsService', () => { const recipientsService = TestBed.inject(RecipientsService); // Take 2 as searchindex+contacts service are separate updates - const recipients = await recipientsService.recipients.pipe(take(2)).toPromise(); + const recipients = await lastValueFrom(recipientsService.recipients.pipe(take(2))); console.log(recipients); expect(window['termlistresult'].length).toBe(5); @@ -136,7 +136,7 @@ describe('RecipientsService', () => { }; const recipientsService: RecipientsService = TestBed.inject(RecipientsService); - const suggested = await recipientsService.recentlyUsed.pipe(take(1)).toPromise(); + const suggested = await firstValueFrom(recipientsService.recentlyUsed); expect(suggested.length).toBe(2); }); diff --git a/src/app/compose/recipients.service.ts b/src/app/compose/recipients.service.ts index 436e34aad..f0886a1cf 100644 --- a/src/app/compose/recipients.service.ts +++ b/src/app/compose/recipients.service.ts @@ -120,8 +120,8 @@ export class RecipientsService { ).then((updateGroups) => { this.updateRecipients(updateGroups); }).catch((error) => { - console.error(error) - return this.recipients.next([]) + console.error(error); + return this.recipients.next([]); }); }); @@ -198,8 +198,8 @@ export class RecipientsService { } } ).catch((error) => { - console.error(error) - return null + console.error(error); + return null; }) ); } else if (m.email) { diff --git a/src/app/contacts-app/contact-details/contact-details.component.ts b/src/app/contacts-app/contact-details/contact-details.component.ts index b342d67d1..68b26ad6e 100644 --- a/src/app/contacts-app/contact-details/contact-details.component.ts +++ b/src/app/contacts-app/contact-details/contact-details.component.ts @@ -24,13 +24,14 @@ import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack import { Router, ActivatedRoute } from '@angular/router'; import { Contact, ContactKind, AddressDetails, Address, GroupMember } from '../contact'; import { ErrorDialog, ConfirmDialog, SimpleInputDialog, SimpleInputDialogParams } from '../../dialog/dialog.module'; -import { filter, take } from 'rxjs/operators'; +import { filter } from 'rxjs/operators'; import { ContactsService } from '../contacts.service'; import { MobileQueryService } from '../../mobile-query.service'; import { ContactPickerDialogComponent } from '../contact-picker-dialog.component'; import { AppSettings } from '../../app-settings'; import { DraftDeskService } from '../../compose/draftdesk.service'; +import { firstValueFrom } from 'rxjs'; import { RunboxWebmailAPI } from '../../rmmapi/rbwebmail'; import { OnscreenComponent } from '../../onscreen/onscreen.component'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; @@ -329,7 +330,7 @@ export class ContactDetailsComponent { } async askForMoreMembers(): Promise { - let contacts = await this.contactsservice.contactsSubject.pipe(take(1)).toPromise(); + let contacts = await firstValueFrom(this.contactsservice.contactsSubject); contacts = contacts.filter(c => { if (c.kind !== ContactKind.INVIDIDUAL) { return false; @@ -408,14 +409,14 @@ export class ContactDetailsComponent { } async newVideoCall(email: string) { - const name = await this.dialog.open(SimpleInputDialog, { + const name = await firstValueFrom(this.dialog.open(SimpleInputDialog, { data: new SimpleInputDialogParams( 'Meeting invitation', 'Pick a name for your meeting', 'Meeting name (optional)', ) - }).afterClosed().toPromise(); - const me = await this.rmmapi.me.toPromise(); + }).afterClosed()); + const me = await firstValueFrom(this.rmmapi.me); const meetingCode = OnscreenComponent.generateMeetingName(name, me.uid.toString()); const meetingUrl = new URL(this.location.prepareExternalUrl(`/onscreen/${meetingCode}`), window.location.origin); this.draftDeskService.newVideoCallInvite(email, meetingUrl).then(() => { diff --git a/src/app/contacts-app/contact-list.component.ts b/src/app/contacts-app/contact-list.component.ts index 53e02aed7..393988461 100644 --- a/src/app/contacts-app/contact-list.component.ts +++ b/src/app/contacts-app/contact-list.component.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component, Input, EventEmitter, Output } from '@angular/core'; +import { Component, Input, EventEmitter, Output, OnChanges } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; import { Contact } from './contact'; @@ -28,7 +28,7 @@ type SelectionEvent = Set; styleUrls: ['contacts-app.component.scss'], templateUrl: './contact-list.component.html' }) -export class ContactListComponent { +export class ContactListComponent implements OnChanges { @Input() contacts: Contact[]; @Input() categories: string[] = []; diff --git a/src/app/contacts-app/contacts-app.component.ts b/src/app/contacts-app/contacts-app.component.ts index d55a4054b..7e7ba2d10 100644 --- a/src/app/contacts-app/contacts-app.component.ts +++ b/src/app/contacts-app/contacts-app.component.ts @@ -258,7 +258,7 @@ export class ContactsAppComponent { try { contacts = Contact.fromVcf(vcf); } catch (e) { - console.error(e) + console.error(e); if (warning) { // we predicted this: this.showError('Only .vcf contacts files are supported, this does not look like one'); diff --git a/src/app/folder/folderlist.component.spec.ts b/src/app/folder/folderlist.component.spec.ts index ecaf28f44..c7d05637d 100644 --- a/src/app/folder/folderlist.component.spec.ts +++ b/src/app/folder/folderlist.component.spec.ts @@ -20,7 +20,7 @@ import { FolderListComponent, DropPosition, CreateFolderEvent, MoveFolderEvent } from './folderlist.component'; import { RunboxWebmailAPI } from '../rmmapi/rbwebmail'; import { FolderListEntry } from '../common/folderlistentry'; -import { BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject, firstValueFrom, of } from 'rxjs'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { TestBed } from '@angular/core/testing'; import { take } from 'rxjs/operators'; @@ -76,19 +76,19 @@ describe('FolderListComponent', () => { console.log('move folder with id 6 above 5'); await comp.folderReorderingDrop(6, 5, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 2, 3, 4, 6, 5, 7]); expect(rearrangedFolders.map(f => f.folderLevel)).toEqual([0, 0, 1, 2, 2, 2, 0]); console.log('move folder with id 6 above 5 - should not cause any changes'); await comp.folderReorderingDrop(6, 5, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 2, 3, 4, 6, 5, 7]); expect(rearrangedFolders.map(f => f.folderLevel)).toEqual([0, 0, 1, 2, 2, 2, 0]); console.log('move folder with id 6 below 5'); comp.folderReorderingDrop(6, 5, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(moveEvent.order).toEqual([1, 2, 3, 4, 5, 6, 7]); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 2, 3, 4, 5, 6, 7]); @@ -96,7 +96,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 5 below 7'); comp.folderReorderingDrop(5, 7, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(moveEvent.order).toEqual([1, 2, 3, 4, 6, 7, 5]); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 2, 3, 4, 6, 7, 5]); @@ -105,7 +105,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 5 inside 7'); comp.folderReorderingDrop(5, 7, DropPosition.INSIDE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 2, 3, 4, 6, 7, 5]); expect(rearrangedFolders[6].folderPath).toBe('folder3.subsubfolder2'); @@ -113,7 +113,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 7 above 1'); comp.folderReorderingDrop(7, 1, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([7, 5, 1, 2, 3, 4, 6]); expect(rearrangedFolders[0].folderPath).toBe('folder3'); @@ -122,7 +122,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 7 below 1'); comp.folderReorderingDrop(7, 1, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 7, 5, 2, 3, 4, 6]); expect(rearrangedFolders[1].folderPath).toBe('folder3'); @@ -131,7 +131,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 4 above 7'); comp.folderReorderingDrop(4, 7, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 4, 7, 5, 2, 3, 6]); expect(rearrangedFolders[1].folderPath).toBe('subsubfolder'); @@ -141,7 +141,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 5 below 7'); comp.folderReorderingDrop(5, 7, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders[2].folderPath).toBe('folder3'); expect(rearrangedFolders[3].folderPath).toBe('subsubfolder2'); @@ -150,7 +150,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 6 below 3'); comp.folderReorderingDrop(6, 3, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 4, 7, 5, 2, 3, 6]); @@ -158,7 +158,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 6 inside 7'); comp.folderReorderingDrop(6, 7, DropPosition.INSIDE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 4, 7, 6, 5, 2, 3]); @@ -166,7 +166,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 3 above 7'); comp.folderReorderingDrop(3, 7, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 4, 3, 7, 6, 5, 2]); @@ -174,7 +174,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 6 above 4'); comp.folderReorderingDrop(6, 4, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 6, 4, 3, 7, 5, 2]); @@ -182,7 +182,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 6 inside 4'); comp.folderReorderingDrop(6, 4, DropPosition.INSIDE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 4, 6, 3, 7, 5, 2]); @@ -190,7 +190,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 3 inside 5'); comp.folderReorderingDrop(3, 5, DropPosition.INSIDE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 4, 6, 7, 5, 3, 2]); @@ -198,7 +198,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 4 above 5'); comp.folderReorderingDrop(4, 5, DropPosition.ABOVE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 7, 4, 6, 5, 3, 2]); @@ -206,7 +206,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 6 below 7'); comp.folderReorderingDrop(6, 7, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 7, 6, 4, 5, 3, 2]); @@ -214,7 +214,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 2 inside 3'); comp.folderReorderingDrop(2, 3, DropPosition.INSIDE); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 7, 6, 4, 5, 3, 2]); @@ -222,7 +222,7 @@ describe('FolderListComponent', () => { console.log('move folder with id 4 below 3'); comp.folderReorderingDrop(4, 3, DropPosition.BELOW); - rearrangedFolders = await comp.folders.pipe(take(1)).toPromise(); + rearrangedFolders = await firstValueFrom(comp.folders); console.log(rearrangedFolders.map(f => f.folderId)); expect(rearrangedFolders.map(f => f.folderId)).toEqual([1, 7, 6, 5, 3, 2, 4]); @@ -244,7 +244,7 @@ describe('FolderListComponent', () => { foldersSubject.next([...folders, new FolderListEntry(3, 50, 40, 'user', ev.name, 'folder2', 0)]) ); comp.addFolder(); - const newListOfFolders = await comp.folders.pipe(take(1)).toPromise(); + const newListOfFolders = await firstValueFrom(comp.folders); console.log(newListOfFolders); diff --git a/src/app/folder/folderlist.component.ts b/src/app/folder/folderlist.component.ts index e0524080e..831826457 100644 --- a/src/app/folder/folderlist.component.ts +++ b/src/app/folder/folderlist.component.ts @@ -25,7 +25,7 @@ import { FolderListEntry } from '../common/folderlistentry'; import { FolderMessageCountMap } from '../rmmapi/messagelist.service'; import { SimpleInputDialog, SimpleInputDialogParams } from '../dialog/simpleinput.dialog'; -import { Observable } from 'rxjs'; +import { Observable, firstValueFrom } from 'rxjs'; import { first, map, filter, take } from 'rxjs/operators'; import { FlatTreeControl } from '@angular/cdk/tree'; import {ExtendedKeyboardEvent, Hotkey, HotkeysService} from 'angular2-hotkeys'; @@ -117,7 +117,7 @@ export class FolderListComponent implements OnChanges { } } catch (e) { /* we don't care why it failed, it just means that we'll show all folders as collapsed */ - console.error(e) + console.error(e); } this.treeControl = new FlatTreeControl(this._getLevel, this._isExpandable); @@ -255,8 +255,8 @@ export class FolderListComponent implements OnChanges { } onFolderClick($event, folder) { - $event.preventDefault() - this.selectFolder(folder) + $event.preventDefault(); + this.selectFolder(folder); } selectFolder(folder: string): void { @@ -366,7 +366,7 @@ export class FolderListComponent implements OnChanges { return; } - const folders = await this.folders.pipe(take(1)).toPromise(); + const folders = await firstValueFrom(this.folders); let sourceIndex = folders.findIndex(fld => fld.folderId === sourceFolderId); let destinationIndex = folders.findIndex(folder => folder.folderId === destinationFolderId); diff --git a/src/app/http/progress.service.spec.ts b/src/app/http/progress.service.spec.ts index 8bb91f129..4576c8eb4 100644 --- a/src/app/http/progress.service.spec.ts +++ b/src/app/http/progress.service.spec.ts @@ -1,18 +1,18 @@ // --------- BEGIN RUNBOX LICENSE --------- // Copyright (C) 2016-2018 Runbox Solutions AS (runbox.com). -// +// // This file is part of Runbox 7. -// +// // Runbox 7 is free software: You can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // Runbox 7 is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- @@ -27,6 +27,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { RMMHttpInterceptorService } from '../rmmapi/rmmhttpinterceptor.service'; import { ProgressService } from './progress.service'; import { RouterTestingModule } from '@angular/router/testing'; +import { firstValueFrom } from 'rxjs'; import { filter, take } from 'rxjs/operators'; import { RMMAuthGuardService } from '../rmmapi/rmmauthguard.service'; import { RMMOfflineService } from '../rmmapi/rmmoffline.service'; @@ -72,14 +73,12 @@ describe('ProgressService', () => { const last_on_req = httpMock.expectOne('/rest/v1/last_on'); last_on_req.flush(200); - const me = await rmmapiservice.me.toPromise(); + const me = await firstValueFrom(rmmapiservice.me); expect(me.last_name).toBe('testuser'); expect(httpProgressSeen).toBeTruthy(); - const hasCurrentHttpActivity = await progressService.httpRequestInProgress.pipe( - take(1) - ).toPromise(); + const hasCurrentHttpActivity = await firstValueFrom(progressService.httpRequestInProgress); expect(hasCurrentHttpActivity).toBeFalsy(); expect(rmmapiservice.last_on_interval).toBeTruthy(); diff --git a/src/app/http/progress.service.ts b/src/app/http/progress.service.ts index 647df750b..8beb8c662 100644 --- a/src/app/http/progress.service.ts +++ b/src/app/http/progress.service.ts @@ -1,18 +1,18 @@ // --------- BEGIN RUNBOX LICENSE --------- // Copyright (C) 2016-2018 Runbox Solutions AS (runbox.com). -// +// // This file is part of Runbox 7. -// +// // Runbox 7 is free software: You can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // Runbox 7 is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index d73e32a74..60e5c0e46 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -93,7 +93,7 @@ export class LoginComponent implements OnInit { } public onSubmit(ngForm: NgForm) { - const { value } = ngForm + const { value } = ngForm; this.login_errors_reset(); diff --git a/src/app/mailviewer/avatar-bar.component.ts b/src/app/mailviewer/avatar-bar.component.ts index 24bb623d7..cbe6aa201 100644 --- a/src/app/mailviewer/avatar-bar.component.ts +++ b/src/app/mailviewer/avatar-bar.component.ts @@ -17,9 +17,8 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component, Input, OnInit } from '@angular/core'; -import { ReplaySubject} from 'rxjs'; -import { take } from 'rxjs/operators'; +import { Component, Input, OnInit, OnChanges } from '@angular/core'; +import { firstValueFrom, ReplaySubject} from 'rxjs'; import { ProfileService } from '../profiles/profile.service'; import { ContactsService } from '../contacts-app/contacts.service'; import { PreferencesService } from '../common/preferences.service'; @@ -53,7 +52,7 @@ import { PreferencesService } from '../common/preferences.service';

`, }) -export class AvatarBarComponent implements OnInit { +export class AvatarBarComponent implements OnInit, OnChanges { @Input() email: { from: string[], to: string[], @@ -82,7 +81,7 @@ export class AvatarBarComponent implements OnInit { async ngOnChanges() { this.emails = []; // so that we don't display the old, wrong ones while we're loading new ones - const own = await this.ownAddresses.pipe(take(1)).toPromise(); + const own = await firstValueFrom(this.ownAddresses); const emails: string[] = [].concat.apply( [], [this.email.from, this.email.to, this.email.cc, this.email.bcc] diff --git a/src/app/mailviewer/singlemailviewer.component.ts b/src/app/mailviewer/singlemailviewer.component.ts index 0c632d961..1bb81b032 100644 --- a/src/app/mailviewer/singlemailviewer.component.ts +++ b/src/app/mailviewer/singlemailviewer.component.ts @@ -1,18 +1,18 @@ // --------- BEGIN RUNBOX LICENSE --------- // Copyright (C) 2016-2018 Runbox Solutions AS (runbox.com). -// +// // This file is part of Runbox 7. -// +// // Runbox 7 is free software: You can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // Runbox 7 is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- @@ -40,7 +40,7 @@ import { MobileQueryService } from '../mobile-query.service'; import { HorizResizerDirective } from '../directives/horizresizer.directive'; import { MessageContents, RunboxWebmailAPI } from '../rmmapi/rbwebmail'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; import { Router } from '@angular/router'; import { MessageListService } from '../rmmapi/messagelist.service'; import { loadLocalMailParser } from './mailparser'; @@ -564,7 +564,7 @@ export class SingleMailViewerComponent implements OnInit, DoCheck, AfterViewInit res.date.setMinutes(res.date.getMinutes() - res.date.getTimezoneOffset()); res.sanitized_html = this.expandAttachmentData(res.attachments, res.sanitized_html); - res.visible_attachment_count = res.attachments.filter((att) => !att.internal).length; + res.visible_attachment_count = res.attachments.filter((att) => !att.internal).length; res.sanitized_html_without_images = this.expandAttachmentData(res.attachments, res.sanitized_html_without_images); @@ -689,7 +689,7 @@ export class SingleMailViewerComponent implements OnInit, DoCheck, AfterViewInit } print() { - // Can't access print view inside iFrame, so we need to + // Can't access print view inside iFrame, so we need to // temporary hide buttons while the view is rendering const messageContents = document.getElementById('messageContents'); const buttons = messageContents.getElementsByTagName('button'); @@ -732,7 +732,7 @@ export class SingleMailViewerComponent implements OnInit, DoCheck, AfterViewInit /** * EXPERIMENTAL, decrypt attachment (encrypted.asc) by sending it to pgpapp.no - * @param attachmentIndex + * @param attachmentIndex */ public decryptAttachment(attachmentIndex: number) { this.http.get('/rest/v1/email/' + this.messageId + '/attachment/' + attachmentIndex, @@ -746,7 +746,7 @@ export class SingleMailViewerComponent implements OnInit, DoCheck, AfterViewInit window.removeEventListener('message', pgpapplistener); pgpapp.close(); - const parseMail = await loadLocalMailParser().toPromise(); + const parseMail = await firstValueFrom(loadLocalMailParser()); const parsed = await parseMail(msg.data.decryptedContent); this.mailObj.text = parsed.text; this.mailObj.subject = parsed.subject; diff --git a/src/app/menu/headertoolbar.component.ts b/src/app/menu/headertoolbar.component.ts index 2a6964181..ae584a1f3 100644 --- a/src/app/menu/headertoolbar.component.ts +++ b/src/app/menu/headertoolbar.component.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { RunboxWebmailAPI } from '../rmmapi/rbwebmail'; import { RMMOfflineService } from '../rmmapi/rmmoffline.service'; import { Router } from '@angular/router'; @@ -29,7 +29,7 @@ import { RunboxMe } from '../rmmapi/rbwebmail'; selector: 'rmm-headertoolbar', templateUrl: 'headertoolbar.component.html' }) -export class HeaderToolbarComponent { +export class HeaderToolbarComponent implements OnInit { rmm6tooltip = 'This area isn\'t upgraded to Runbox 7 yet and will open in a new tab'; user_is_trial = false; diff --git a/src/app/onscreen/onscreen.component.ts b/src/app/onscreen/onscreen.component.ts index a9d5d5eaf..8997dc4c8 100644 --- a/src/app/onscreen/onscreen.component.ts +++ b/src/app/onscreen/onscreen.component.ts @@ -20,7 +20,7 @@ import { Component, OnDestroy } from '@angular/core'; import { Location } from '@angular/common'; import { MobileQueryService } from '../mobile-query.service'; -import { AsyncSubject } from 'rxjs'; +import { AsyncSubject, firstValueFrom } from 'rxjs'; import { RunboxWebmailAPI, RunboxMe } from '../rmmapi/rbwebmail'; import { ContactsService } from '../contacts-app/contacts.service'; import { ActivatedRoute } from '@angular/router'; @@ -115,7 +115,7 @@ export class OnscreenComponent implements OnDestroy { } async createMeeting() { - await jitsiLoader.toPromise(); + await firstValueFrom(jitsiLoader); const name = await this.encodeMeetingName(this.form.meetingName); this.joinMeeting(name).then(() => { @@ -132,7 +132,7 @@ export class OnscreenComponent implements OnDestroy { } async joinMeeting(code: string) { - await jitsiLoader.toPromise(); + await firstValueFrom(jitsiLoader); this.jitsiAPI = new JitsiMeetExternalAPI('video.runbox.com', { roomName: code, @@ -188,7 +188,7 @@ export class OnscreenComponent implements OnDestroy { } async encodeMeetingName(name: string): Promise { - const me = await this.me.toPromise(); + const me = await firstValueFrom(this.me); return Promise.resolve(OnscreenComponent.generateMeetingName(name, me.uid.toString())); } diff --git a/src/app/profiles/profile.service.spec.ts b/src/app/profiles/profile.service.spec.ts index de80daf6c..5be6dcaad 100644 --- a/src/app/profiles/profile.service.spec.ts +++ b/src/app/profiles/profile.service.spec.ts @@ -157,36 +157,36 @@ describe('ProfileService', () => { }).compileComponents(); })); - beforeEach(() => { service = service = TestBed.inject(ProfileService) }); + beforeEach(() => { service = service = TestBed.inject(ProfileService); }); it('loads valid profile subsets', (done) => { service.validProfiles.subscribe(profiles => { expect(profiles.length).toBe(4); done(); }); - }) + }); it('loads all profiles', (done) => { service.profiles.subscribe(profiles => { expect(profiles.length).toBe(PROFILES.length); done(); }); - }) + }); it('loads alias profile subsets', (done) => { service.aliases.subscribe(profiles => { expect(profiles.length).toBe(PROFILES.filter(p => p.reference_type === 'aliases').length); done(); }); - }) + }); it('loads non alias profile subsets', (done) => { service.otherProfiles.subscribe(profiles => { expect(profiles.length).toBe(2); done(); }); - }) + }); it('loads a compose profile', () => { expect(service.composeProfile).toBeDefined(); expect(service.composeProfile.email).toEqual('a2@example.com'); - }) + }); it('adds a new profile on create', (done) => { service.create({ name: 'New Profile Name', diff --git a/src/app/rmm/account-security.ts b/src/app/rmm/account-security.ts index 748072da8..33a505202 100644 --- a/src/app/rmm/account-security.ts +++ b/src/app/rmm/account-security.ts @@ -80,7 +80,7 @@ export class AccountSecurity { + ' \\____\\/ \\_____/\\ \n' + ' \\____\\/ \n'; return txt; - } + }; txt_footer: any = () => { const dt = new Date(); @@ -93,7 +93,7 @@ export class AccountSecurity { ' \n' + ' https://runbox.com Generated by Runbox at ' + dt_str + ' \n'; return txt; - } + }; check_password(password): Observable { this.is_busy = true; diff --git a/src/app/rmm6/rmm6.module.ts b/src/app/rmm6/rmm6.module.ts index ccbb4ffb6..52abc3aa8 100644 --- a/src/app/rmm6/rmm6.module.ts +++ b/src/app/rmm6/rmm6.module.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { NgModule, ApplicationRef, ComponentFactoryResolver, Injector, NgZone } from '@angular/core'; +import { NgModule, ApplicationRef, ComponentFactoryResolver, Injector, NgZone, DoBootstrap } from '@angular/core'; import { RMM6AngularGateway } from './rmm6angulargateway'; import { MailViewerModule } from '../mailviewer/mailviewer.module'; import { DomainRegisterModule } from '../domainregister/domainregister.module'; @@ -78,7 +78,7 @@ import { SearchExpressionBuilderModule } from '../xapian/search-expression-build ], providers: [ProgressService] }) -export class RMM6Module { +export class RMM6Module implements DoBootstrap { rmmAngularGW: RMM6AngularGateway; diff --git a/src/app/rmmapi/rblocale.ts b/src/app/rmmapi/rblocale.ts index cdbcdb0c5..738eb3c0f 100644 --- a/src/app/rmmapi/rblocale.ts +++ b/src/app/rmmapi/rblocale.ts @@ -45,7 +45,7 @@ export class RunboxLocale { try { translated = window['getLocale'](key.split('.')); } catch (ex) { - console.error(ex) + console.error(ex); console.log('locale translations not found'); } return translated; diff --git a/src/app/saved-searches/saved-searches.service.ts b/src/app/saved-searches/saved-searches.service.ts index 21763d5a6..0b2419f4e 100644 --- a/src/app/saved-searches/saved-searches.service.ts +++ b/src/app/saved-searches/saved-searches.service.ts @@ -18,7 +18,7 @@ // ---------- END RUNBOX LICENSE ---------- import { Injectable } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; +import { firstValueFrom, ReplaySubject } from 'rxjs'; import { take } from 'rxjs/operators'; import { StorageService } from '../storage.service'; @@ -83,7 +83,7 @@ export class SavedSearchesService { private async uploadSeachData() { const data: SavedSearchStorage = { version: this.version, - entries: await this.searches.pipe(take(1)).toPromise(), + entries: await firstValueFrom(this.searches), }; this.rmmapi.setSavedSearches(data).subscribe( newData => this.applySyncedData(newData) diff --git a/src/app/sentry-error-handler.ts b/src/app/sentry-error-handler.ts index d005c9f4b..af3d56711 100644 --- a/src/app/sentry-error-handler.ts +++ b/src/app/sentry-error-handler.ts @@ -22,6 +22,7 @@ import * as Sentry from '@sentry/browser'; import './sentry'; import { RMMAuthGuardService } from './rmmapi/rmmauthguard.service'; +import { firstValueFrom } from 'rxjs'; import { RunboxWebmailAPI } from './rmmapi/rbwebmail'; @Injectable() @@ -40,10 +41,10 @@ export class SentryErrorHandler implements ErrorHandler { return; } const authguard = this.injector.get(RMMAuthGuardService); - const isLoggedIn = await authguard.isLoggedIn().toPromise(); + const isLoggedIn = await firstValueFrom(authguard.isLoggedIn()); if (isLoggedIn) { const rmmapi = this.injector.get(RunboxWebmailAPI); - const me = await rmmapi.me.toPromise(); + const me = await firstValueFrom(rmmapi.me); Sentry.setUser({ uid: me.uid, username: me.username, diff --git a/src/app/sentry.ts b/src/app/sentry.ts index d0980ca07..93677fad9 100644 --- a/src/app/sentry.ts +++ b/src/app/sentry.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { environment } from '../environments/environment' +import { environment } from '../environments/environment'; import * as Sentry from '@sentry/browser'; Sentry.init({ diff --git a/src/app/start/startdesk.component.ts b/src/app/start/startdesk.component.ts index 26cf8c34f..69d62d7be 100644 --- a/src/app/start/startdesk.component.ts +++ b/src/app/start/startdesk.component.ts @@ -26,8 +26,8 @@ import moment from 'moment'; import { Contact } from '../contacts-app/contact'; import { SearchService, SearchIndexDocumentData } from '../xapian/searchservice'; import { isValidEmail } from '../compose/emailvalidator'; -import { filter, take } from 'rxjs/operators'; -import { ReplaySubject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { firstValueFrom, ReplaySubject } from 'rxjs'; import { ProfileService } from '../profiles/profile.service'; import { UsageReportsService } from '../common/usage-reports.service'; @@ -267,7 +267,7 @@ export class StartDeskComponent implements OnInit { private async extractMailingLists(messages: SearchIndexDocumentData[]): Promise> { const possibleMailingLists = new Map(); - const ownAddresses = await this.ownAddresses.pipe(take(1)).toPromise(); + const ownAddresses = await firstValueFrom(this.ownAddresses); for (const message of messages) { if (!message.recipients.find(r => ownAddresses.has(r.toLowerCase()))) { diff --git a/src/app/storage.service.ts b/src/app/storage.service.ts index 3af77f90f..31e57e397 100644 --- a/src/app/storage.service.ts +++ b/src/app/storage.service.ts @@ -19,7 +19,7 @@ import { RunboxWebmailAPI } from './rmmapi/rbwebmail'; import { Injectable } from '@angular/core'; -import { AsyncSubject, ReplaySubject } from 'rxjs'; +import { AsyncSubject, firstValueFrom, ReplaySubject } from 'rxjs'; @Injectable() export class StorageService { @@ -36,7 +36,7 @@ export class StorageService { } private async userKey(key: string): Promise { - const uid = await this.uid.toPromise(); + const uid = await firstValueFrom(this.uid); return `${uid}:${key}`; } diff --git a/src/app/welcome/welcomedesk.component.ts b/src/app/welcome/welcomedesk.component.ts index 0a1ab40a4..1b1580248 100644 --- a/src/app/welcome/welcomedesk.component.ts +++ b/src/app/welcome/welcomedesk.component.ts @@ -37,11 +37,11 @@ export class WelcomeDeskComponent implements OnInit { this.rmmapi.me.subscribe(me => this.me = me); } - public postSignup = '' + public postSignup = ''; ngOnInit() { this.activatedRoute.queryParams.subscribe(params => { this.postSignup = params['postSignup']; - }) + }); } } diff --git a/src/app/xapian/index.worker.ts b/src/app/xapian/index.worker.ts index 378cbdc51..a1cfb24bd 100644 --- a/src/app/xapian/index.worker.ts +++ b/src/app/xapian/index.worker.ts @@ -21,7 +21,7 @@ import '../sentry'; -import { Observer, Observable, of, from, AsyncSubject } from 'rxjs'; +import { Observer, Observable, of, from, AsyncSubject, firstValueFrom, lastValueFrom } from 'rxjs'; import { mergeMap, map, filter, catchError, tap, take, bufferCount } from 'rxjs/operators'; import { XapianAPI } from '@runboxcom/runbox-searchindex'; @@ -147,7 +147,7 @@ class SearchIndexService { db.close(); }; } catch (e) { - console.error(e) + console.error(e); console.log('Worker: Unable to open local xapian index', (e ? e.message : '')); db.close(); // console.log('Worker: Req failed'); @@ -237,7 +237,7 @@ class SearchIndexService { } }); } catch (e) { - console.error(e) + console.error(e); } } @@ -273,7 +273,7 @@ class SearchIndexService { try { FS.unlink('xapianglass'); } catch (e) { - console.error(e) + console.error(e); } @@ -535,8 +535,8 @@ not matching with rest api counts for current folder`); (result) => console.log(result.result.result.msg) ).catch( (err) => { - console.error(err) - console.log('Error updating folder counts: ' + err.errors.join(',')) + console.error(err); + console.log('Error updating folder counts: ' + err.errors.join(',')); } ); } @@ -556,7 +556,7 @@ not matching with rest api counts for current folder`); try { this.api.deleteDocumentByUniqueTerm(uniqueIdTerm); } catch (e) { - console.error(e) + console.error(e); console.error('Worker: Unable to delete message from index', msgid); } }) @@ -681,7 +681,7 @@ not matching with rest api counts for current folder`); this.numberOfMessagesSyncedLastTime = searchIndexDocumentUpdates.length; if (searchIndexDocumentUpdates.length > 0) { - await this.postMessagesToXapianWorker(searchIndexDocumentUpdates).toPromise(); + await lastValueFrom(this.postMessagesToXapianWorker(searchIndexDocumentUpdates)); } // Look up messages with missing body text term and add the missing text to the index @@ -713,7 +713,7 @@ not matching with rest api counts for current folder`); console.error('Worker: Failed to add text to document', messageId, e); } }); - })).toPromise(); + })); } } else { // localsearchactivated is off @@ -861,7 +861,7 @@ not matching with rest api counts for current folder`); if (this.persistIndexInProgressSubject) { // Wait for persistence of index to finish before doing more work on the index - await this.persistIndexInProgressSubject.toPromise(); + await firstValueFrom(this.persistIndexInProgressSubject); } setTimeout(() => processMessage(), 1); @@ -872,7 +872,7 @@ not matching with rest api counts for current folder`); this.indexNotPersisted = true; } this.api.commitXapianUpdates(); - await this.persistIndex().toPromise(); + await lastValueFrom(this.persistIndex()); if (hasProgressSnackBar) { ctx.postMessage({'action': PostMessageAction.closeProgressSnackBar}); diff --git a/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.spec.ts b/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.spec.ts index dd9d0deed..a17304417 100644 --- a/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.spec.ts +++ b/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.spec.ts @@ -1,18 +1,18 @@ // --------- BEGIN RUNBOX LICENSE --------- // Copyright (C) 2016-2019 Runbox Solutions AS (runbox.com). -// +// // This file is part of Runbox 7. -// +// // Runbox 7 is free software: You can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // Runbox 7 is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- @@ -28,6 +28,7 @@ import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { firstValueFrom } from 'rxjs'; import { take } from 'rxjs/operators'; describe('MultipleSearchFieldsInputComponent', () => { @@ -63,7 +64,7 @@ describe('MultipleSearchFieldsInputComponent', () => { component.currentFolder = 'Testfolder'; component.formGroup.get('currentfolderonly').setValue(true); - const searchExpressionPromise = component.searchexpression.pipe(take(1)).toPromise(); + const searchExpressionPromise = firstValueFrom(component.searchexpression); component.formGroup.get('subject').setValue('testsubject'); const searchExpression = await searchExpressionPromise; diff --git a/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.ts b/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.ts index 029e45252..d5854fcaf 100644 --- a/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.ts +++ b/src/app/xapian/multiple-search-fields-input/multiple-search-fields-input.component.ts @@ -54,7 +54,7 @@ export class MultipleSearchFieldsInputComponent implements OnChanges { .pipe(distinctUntilChanged()) .subscribe(() => { this.enableDisableUnread(); - this.buildSearchExpression() + this.buildSearchExpression(); }); } diff --git a/src/app/xapian/searchservice.spec.ts b/src/app/xapian/searchservice.spec.ts index b7e9277f2..6252a1654 100644 --- a/src/app/xapian/searchservice.spec.ts +++ b/src/app/xapian/searchservice.spec.ts @@ -1,18 +1,18 @@ // --------- BEGIN RUNBOX LICENSE --------- // Copyright (C) 2016-2018 Runbox Solutions AS (runbox.com). -// +// // This file is part of Runbox 7. -// +// // Runbox 7 is free software: You can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. -// +// // Runbox 7 is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- @@ -28,6 +28,7 @@ import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/ import { MessageListService } from '../rmmapi/messagelist.service'; import { XapianAPI } from '@runboxcom/runbox-searchindex/rmmxapianapi'; +import { firstValueFrom } from 'rxjs'; import { xapianLoadedSubject } from './xapianwebloader'; import { PostMessageAction } from './messageactions'; @@ -123,12 +124,12 @@ describe('SearchService', () => { xit('should load searchservice, but no local index', async () => { const searchService = TestBed.inject(SearchService); - await xapianLoadedSubject.toPromise(); + await firstValueFrom(xapianLoadedSubject); const req = httpMock.expectOne('/rest/v1/email_folder/list'); req.flush(listEmailFoldersResponse); - expect(await searchService.initSubject.toPromise()).toBeFalsy(); + expect(await firstValueFrom(searchService.initSubject)).toBeFalsy(); expect(searchService.localSearchActivated).toBeFalsy(); httpMock.verify(); @@ -193,7 +194,7 @@ describe('SearchService', () => { const testuserid = 444; const localdir = 'rmmsearchservice' + testuserid; - await xapianLoadedSubject.toPromise(); + await firstValueFrom(xapianLoadedSubject); FS.mkdir(localdir); FS.mount(IDBFS, {}, '/' + localdir); @@ -246,7 +247,7 @@ describe('SearchService', () => { req.flush(listEmailFoldersResponse); - expect(await searchService.initSubject.toPromise()).toBeTruthy(); + expect(await firstValueFrom(searchService.initSubject)).toBeTruthy(); console.log('search service initialised'); expect(searchService.localSearchActivated).toBeTruthy(); expect(localdir).toEqual(searchService.localdir); diff --git a/src/app/xapian/searchservice.ts b/src/app/xapian/searchservice.ts index efe5c9420..b83c992c4 100644 --- a/src/app/xapian/searchservice.ts +++ b/src/app/xapian/searchservice.ts @@ -223,7 +223,7 @@ export class SearchService { db.close(); }; } catch (e) { - console.error(e) + console.error(e); console.log('Unable to open local xapian index', (e ? e.message : '')); db.close(); observer.next(false); @@ -366,7 +366,7 @@ export class SearchService { this.indexLastUpdateTime = new Date().getTime(); } } catch (e) { - console.error(e) + console.error(e); if (!this.updateIndexLastUpdateTime()) { // Corrupt xapian index - delete it and subscribe to changes (fallback to websocket search) // Deal with this on the non-worker side, then tell it to @@ -386,7 +386,7 @@ export class SearchService { } catch (e) { - console.error(e) + console.error(e); console.log('No xapian db'); this.initSubject.next(false); } @@ -411,7 +411,7 @@ export class SearchService { } }); } catch (e) { - console.error(e) + console.error(e); } } @@ -560,7 +560,7 @@ export class SearchService { try { FS.stat(XAPIAN_GLASS_WR); } catch (e) { - console.error(e) + console.error(e); FS.mkdir(XAPIAN_GLASS_WR); } @@ -787,7 +787,7 @@ export class SearchService { try { FS.stat(`${this.partitionsdir}/${dirname}`); } catch (e) { - console.error(e) + console.error(e); FS.mkdir(`${this.partitionsdir}/${dirname}`); } diff --git a/src/build/gen-env.js b/src/build/gen-env.js index 903816a2d..8db3a977b 100644 --- a/src/build/gen-env.js +++ b/src/build/gen-env.js @@ -49,6 +49,6 @@ export default ${JSON.stringify( return acc }, {}) -, null, 2)}`); +, null, 2)};`); debug(`Wrote env file to ${target}.`) diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index cf62cf7e2..5bec9c6ac 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -17,9 +17,9 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import env from './env' +import env from './env'; export const environment = { ...env, production: true -} +}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 3d2dfca83..1e1e66b03 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import env from './env' +import env from './env'; export const environment = { ...env, diff --git a/src/styles.scss b/src/styles.scss index fb10e6bf9..7ff559a45 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -32,7 +32,7 @@ $rmm-darker-background: #01001c; $rmm-gray: #dddddd; $rmm-gray-light: #eeeeee; $rmm-gray-lighter: #f3f3f3; - + $rmm-default-theme: mat.define-light-theme($rmm-default-primary, $rmm-default-accent, $rmm-default-warn); $rmm-default-lighter-gray: #eeeeee; @@ -42,24 +42,6 @@ $rmm-default-black: #444444; @include mat.all-legacy-component-themes($rmm-default-theme); -// GTA 13.06.2018: Load custom fonts - -@font-face { - font-family: "Avenir Next Pro Regular"; - src: url("assets/AvenirNextLTPro-Regular.otf"); - src: url("assets/Avenir-Next-LT-Pro.ttf"); - font-style: normal; - font-weight: normal; -} - -@font-face { - font-family: "Avenir Next Pro Medium"; - src: url("assets/AvenirNextLTPro-Medium.otf"); - src: url("assets/AvenirNextLTPro-Medium.ttf"); - font-style: normal; - font-weight: normal; -} - // GTA 13.06.2018: Override default fonts as per https://material.angular.io/guide/typography $custom-typography: mat.define-legacy-typography-config( @@ -159,7 +141,7 @@ a[mat-list-item] .mat-list-item-content { min-height: 24px !important; } -.mat-list[dense] .mat-list-item .mat-list-text, +.mat-list[dense] .mat-list-item .mat-list-text, .mat-nav-list[dense] .mat-list-item .mat-list-text>*, mat-list-item .mat-list-text, a[mat-list-item] .mat-list-text { @@ -313,21 +295,21 @@ mat-grid-tile.tableTitle { height: 16px; width: 42px; } - + .mat-slide-toggle.mat-checked .mat-slide-toggle-thumb-container { top: -5px; transform: translate3d(20px, 0, 0); } - + .mat-slide-toggle.mat-checked .mat-slide-toggle-thumb { height: 24px; width: 24px; } - + .mat-slide-toggle-label { font-size: 16px; } - + .mat-slide-toggle-content { margin-left: 2px; } @@ -394,12 +376,12 @@ mat-grid-tile.tableTitle { /*** Main ***/ #main { - position: fixed; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - width: 100%; + position: fixed; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + width: 100%; height: 100%; min-height: 100%; display: flex; @@ -439,7 +421,7 @@ mat-grid-tile.tableTitle { display: none; margin: 0; } - + #logo { margin: 0; width: 300px; @@ -518,7 +500,7 @@ div.loginScreen { mat-form-field { width: 200px; } - } + } #loginOptions { display: flex; margin: 0.5em; @@ -724,8 +706,8 @@ rmm-headertoolbar { /* Sidenav pane */ mat-sidenav-container { - position: absolute !important; - bottom: 0px !important; + position: absolute !important; + bottom: 0px !important; left: 0px !important; right: 0px !important; width: 100% !important; @@ -782,7 +764,7 @@ mat-sidenav-container { .mat-mini-fab .mat-button-wrapper { line-height: 18px; } - + a { width: 30%; } @@ -936,7 +918,7 @@ rmm-folderlist { } .foldersidebarcount { - font-size: 10px; + font-size: 10px; } .draftsFolder { @@ -982,7 +964,7 @@ app-saved-searches .mat-list-base[dense] .mat-list-item { flex-grow: 1; overflow: hidden; } - + .messageListActionButtonsRight button { width: 30px; // Remember to also update TOOLBAR_LIST_BUTTON_WIDTH in app.component.ts in order to show the correct number of menu items } @@ -1008,7 +990,7 @@ app-saved-searches .mat-list-base[dense] .mat-list-item { button { margin-right: 10px; - + @media(max-width: 540px) { margin-right: 2px; height: 30px; @@ -1022,7 +1004,7 @@ app-saved-searches .mat-list-base[dense] .mat-list-item { #offerLocalIndex .mat-list-item-content { padding: 0 5px; -} +} #searchField { flex-grow: 10; @@ -1097,7 +1079,7 @@ app-saved-searches .mat-list-base[dense] .mat-list-item { .mat-icon, .mat-icon-button { color: mat.get-color-from-palette($rmm-default-primary); } - + @media (max-width: 540px) { #threadedCheckbox { display: none; @@ -1172,7 +1154,7 @@ app-saved-searches .mat-list-base[dense] .mat-list-item { .mat-radio-label-content { padding: 0 !important; } - + button, .mat-radio-button, .mat-checkbox { margin-left: 5px; } @@ -1195,7 +1177,7 @@ app-saved-searches .mat-list-base[dense] .mat-list-item { .mat-icon { margin: 0 3px !important; } - + mat-flat-button, .mat-flat-button, mat-raised-button, .mat-raised-button { min-width: 30px !important; width: 30px !important; @@ -1307,7 +1289,7 @@ compose #fieldFrom .mat-form-field-wrapper, compose .fieldRecipient .mat-form-fi .recipientSuggestionContainer { max-height: 90px; overflow: auto; - + span { font-size: 12.5px; } @@ -1347,7 +1329,7 @@ compose #fieldFrom .mat-form-field-wrapper, compose .fieldRecipient .mat-form-fi .mat-nav-list[dense], .mat-list-item, .mat-list-text, .mat-form-field { height: 48px !important; } -} +} .contactList .mat-form-field-infix { font-size: 16px; @@ -1370,7 +1352,7 @@ compose #fieldFrom .mat-form-field-wrapper, compose .fieldRecipient .mat-form-fi .mat-form-field-appearance-legacy .mat-form-field-infix { padding-top: 0 !important; } - + .mat-form-field-appearance-legacy .mat-form-field-label { top: 0.75em; } @@ -1519,7 +1501,7 @@ app-calendar-event-editor-dialog p { .productGrid mat-card.recommended { border: 1px solid mat.get-color-from-palette($rmm-default-primary); -} +} #pricePlans td { /* border-right: 1px solid $rmm-dark-background !important; */ @@ -1573,7 +1555,7 @@ app-payment-method { cursor: pointer; } } - + #otherPaymentMethods { margin: 20px 0 50px 0; max-width: 90%; @@ -1594,7 +1576,7 @@ app-payment-method { color: #0F0; } -.dev.runbox-components .nice_green_timer .timeunit { +.dev.runbox-components .nice_green_timer .timeunit { border: 1px solid #0F0; width: 40px; height: 40px; @@ -1609,22 +1591,22 @@ app-payment-method { .dev.runbox-components .nice_green_timer .timeunit.hours { color: #0f0; } -.dev.runbox-components .nice_green_timer .timeunit.years::after { +.dev.runbox-components .nice_green_timer .timeunit.years::after { content: "y"; } -.dev.runbox-components .nice_green_timer .timeunit.months::after { +.dev.runbox-components .nice_green_timer .timeunit.months::after { content: "m"; } -.dev.runbox-components .nice_green_timer .timeunit.days::after { +.dev.runbox-components .nice_green_timer .timeunit.days::after { content: "d"; } -.dev.runbox-components .nice_green_timer .timeunit.hours::after { +.dev.runbox-components .nice_green_timer .timeunit.hours::after { content: "h"; } -.dev.runbox-components .nice_green_timer .timeunit.minutes::after { +.dev.runbox-components .nice_green_timer .timeunit.minutes::after { content: "m"; } -.dev.runbox-components .nice_green_timer .timeunit.seconds::after { +.dev.runbox-components .nice_green_timer .timeunit.seconds::after { content: "s"; } @@ -1640,22 +1622,22 @@ app-payment-method { align-items: center; } -.dev.runbox-components .nice_blue_timer .timeunit.years::after { +.dev.runbox-components .nice_blue_timer .timeunit.years::after { content: " years"; } -.dev.runbox-components .nice_blue_timer .timeunit.months::after { +.dev.runbox-components .nice_blue_timer .timeunit.months::after { content: " months"; } -.dev.runbox-components .nice_blue_timer .timeunit.days::after { +.dev.runbox-components .nice_blue_timer .timeunit.days::after { content: " days"; } -.dev.runbox-components .nice_blue_timer .timeunit.hours::after { +.dev.runbox-components .nice_blue_timer .timeunit.hours::after { content: " hours"; } -.dev.runbox-components .nice_blue_timer .timeunit.minutes::after { +.dev.runbox-components .nice_blue_timer .timeunit.minutes::after { content: " mins"; } -.dev.runbox-components .nice_blue_timer .timeunit.seconds::after { +.dev.runbox-components .nice_blue_timer .timeunit.seconds::after { content: " secs"; } @@ -1743,7 +1725,7 @@ td.mat-cell.cdk-column-renewal_name.mat-column-renewal_name { table.renewalsTable td, table.paymentsTable td { padding: 5px 10px 0px 10px !important; -} +} table.detailsTable { width: 100%;