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..0449436a2 100644 --- a/src/app/account-app/account-receipt.component.ts +++ b/src/app/account-app/account-receipt.component.ts @@ -20,7 +20,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { RunboxMe, RunboxWebmailAPI } from '../rmmapi/rbwebmail'; -import { AsyncSubject } from 'rxjs'; +import { AsyncSubject, firstValueFrom } from 'rxjs'; import { take } from 'rxjs/operators'; @Component({ @@ -45,12 +45,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.pipe(take(1))); 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..fe705f4fb 100644 --- a/src/app/account-app/cart.service.spec.ts +++ b/src/app/account-app/cart.service.spec.ts @@ -21,7 +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 { firstValueFrom, of } from 'rxjs'; import { take } from 'rxjs/operators'; import { Decimal } from 'decimal.js-light'; @@ -49,7 +49,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..8230390ff 100644 --- a/src/app/account-app/cart.service.ts +++ b/src/app/account-app/cart.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 { ProductOrder } from './product-order'; @@ -46,7 +46,7 @@ export class CartService { } async add(p: ProductOrder): Promise { - const items = await this.items.pipe(take(1)).toPromise(); + const items = await firstValueFrom(this.items.pipe(take(1))); for (const i of items) { // Cannot order multiples of subscription products @@ -70,7 +70,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.pipe(take(1))); for (const p of items) { if (p.pid === pid && p.apid === apid) { return true; @@ -80,7 +80,7 @@ export class CartService { } async remove(order: ProductOrder): Promise { - const items = await this.items.pipe(take(1)).toPromise(); + const items = await firstValueFrom(this.items.pipe(take(1))); // 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/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/common/preferences.service.ts b/src/app/common/preferences.service.ts index 7ed303938..b1a358a11 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.pipe(take(1))); 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.pipe(take(1))); 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.pipe(take(1))); 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..f3621b0b7 100644 --- a/src/app/contacts-app/contact-details/contact-details.component.ts +++ b/src/app/contacts-app/contact-details/contact-details.component.ts @@ -31,6 +31,7 @@ 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.pipe(take(1))); 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..c6a96fc9e 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.pipe(take(1))); 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..4c5882bf6 100644 --- a/src/app/http/progress.service.ts +++ b/src/app/http/progress.service.ts @@ -1,26 +1,30 @@ // --------- 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 ---------- import {Injectable} from '@angular/core'; -import {Subject, BehaviorSubject} from 'rxjs'; +import {Subject, BehaviorSubject, Observable} from 'rxjs'; +import {delay} from 'rxjs/operators'; @Injectable() export class ProgressService { httpRequestInProgress: Subject = new BehaviorSubject(false); + + // Delayed version to avoid ExpressionChangedAfterItHasBeenCheckedError in templates + httpRequestInProgress$: Observable = this.httpRequestInProgress.pipe(delay(0)); } 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..76b23948a 100644 --- a/src/app/mailviewer/avatar-bar.component.ts +++ b/src/app/mailviewer/avatar-bar.component.ts @@ -17,8 +17,8 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { Component, Input, OnInit } from '@angular/core'; -import { ReplaySubject} from 'rxjs'; +import { Component, Input, OnInit, OnChanges } from '@angular/core'; +import { firstValueFrom, ReplaySubject} from 'rxjs'; import { take } from 'rxjs/operators'; import { ProfileService } from '../profiles/profile.service'; import { ContactsService } from '../contacts-app/contacts.service'; @@ -53,7 +53,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 +82,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.pipe(take(1))); 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..7c58e14d4 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.pipe(take(1))), }; 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..53faca1dd 100644 --- a/src/app/start/startdesk.component.ts +++ b/src/app/start/startdesk.component.ts @@ -27,7 +27,7 @@ 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 { 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.pipe(take(1))); 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/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%;