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