diff --git a/e2e/cypress/integration/account-access.ts b/e2e/cypress/integration/account-access.ts
index c25120028..c072124cc 100644
--- a/e2e/cypress/integration/account-access.ts
+++ b/e2e/cypress/integration/account-access.ts
@@ -3,13 +3,13 @@
describe('Account access control', () => {
function becomeSubaccount() {
cy.intercept('/rest/v1/me', (req) => {
- req.reply((res) => {
+ req.continue((res) => {
const payload = JSON.parse(res.body);
payload.result.owner = {
uid: 666,
username: 'mastermind@runbox.com',
};
- res.body = JSON.stringify(payload);
+ res.send(JSON.stringify(payload));
});
});
}
diff --git a/e2e/cypress/integration/compose.ts b/e2e/cypress/integration/compose.ts
index 135a6d01e..53632dbd6 100644
--- a/e2e/cypress/integration/compose.ts
+++ b/e2e/cypress/integration/compose.ts
@@ -1,10 +1,14 @@
///
describe('Composing emails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
localStorage.setItem('221:Desktop:localSearchPromptDisplayed', JSON.stringify('true'));
localStorage.setItem('221:Mobile:localSearchPromptDisplayed', JSON.stringify('true'));
localStorage.setItem('221:preference_keys', '["Desktop:localSearchPromptDisplayed","Mobile:localSearchPromptDisplayed"]');
+
+ (await indexedDB.databases())
+ .filter(db => db.name && /messageCache/.test(db.name))
+ .forEach(db => indexedDB.deleteDatabase(db.name!));
});
Cypress.config('requestTimeout', 100000);
@@ -55,10 +59,7 @@ describe('Composing emails', () => {
cy.get('mailrecipient-input mat-error').should('not.exist');
});
- it('should open reply draft with HTML editor', async () => {
- (await indexedDB.databases())
- .filter(db => db.name && /messageCache/.test(db.name))
- .forEach(db => indexedDB.deleteDatabase(db.name!));
+ it('should open reply draft with HTML editor', () => {
// cy.visit('/');
// cy.wait(1000);
cy.intercept('/rest/v1/email/1').as('message1requested');
diff --git a/e2e/cypress/integration/message-caching.ts b/e2e/cypress/integration/message-caching.ts
index a3ff7f388..e97f88f42 100644
--- a/e2e/cypress/integration/message-caching.ts
+++ b/e2e/cypress/integration/message-caching.ts
@@ -1,16 +1,16 @@
///
describe('Message caching', () => {
- beforeEach(() => {
+ beforeEach(async () => {
localStorage.setItem('Desktop:localSearchPromptDisplayed', 'true');
localStorage.setItem('Global:messageSubjectDragTipShown', 'true');
- });
-
- it('should cache all messages on first time page load', async () => {
(await indexedDB.databases())
.filter(db => db.name && /messageCache/.test(db.name))
.forEach(db => indexedDB.deleteDatabase(db.name!));
+ });
+
+ it('should cache all messages on first time page load', () => {
cy.intercept('/rest/v1/email/12').as('message12requested');
cy.visit('/');
@@ -19,6 +19,13 @@ describe('Message caching', () => {
});
it('should not re-request messages after a page reload', () => {
+ cy.intercept('/rest/v1/email/12').as('message12requested');
+
+ cy.visit('/');
+ cy.wait('@message12requested', {'timeout':10000});
+ // This should have fetched/cached the message
+
+ // Now don't fetch it again:
cy.visit('/#Inbox:12');
let called = false;
cy.intercept('/rest/v1/email/12', (_req) => {
diff --git a/e2e/cypress/integration/profile.ts b/e2e/cypress/integration/profile.ts
new file mode 100644
index 000000000..0e948aab8
--- /dev/null
+++ b/e2e/cypress/integration/profile.ts
@@ -0,0 +1,45 @@
+///
+
+describe('Profiles settings page', () => {
+
+ const ALLOWED_DOMAINS = ['runbox.com', 'example.com'];
+
+ it('lists currently existing profiles', () => {
+ cy.intercept('GET', '/rest/v1/profiles').as('getProfiles');
+ cy.intercept('GET', '/rest/v1/aliases/limits').as('getAliasLimits');
+ cy.visit('/account/identities');
+ cy.wait('@getProfiles');
+
+ // 1 compose profile, 5 valid ones:
+ cy.get('mat-card-content.profile-content').should('have.length', 6);
+ });
+
+ it('can create new profiles', () => {
+ cy.intercept('GET', '/rest/v1/profiles').as('getProfiles');
+ cy.intercept('GET', '/rest/v1/aliases/limits').as('getAliasLimits');
+ cy.visit('/account/identities');
+ cy.wait('@getAliasLimits');
+
+ cy.get('#add-identity').click();
+
+ // open dialog, fill in fields, submit
+ cy.get('app-profiles-edit').should('be.visible');
+ cy.get('input[name="email"]').type('newprof@runbox.com');
+ cy.get('input[name="from"]').type('My Name');
+ cy.get('input[name="name"]').type('My Profile');
+ cy.get('textarea[name="signature"]').type('My Sig');
+
+ cy.intercept('POST', '/rest/v1/profile', {
+ statusCode: 200,
+ body: {
+ status: 'success',
+ result: {id: 1}
+ }
+ }).as('postProfile');
+ cy.get('button#save').click();
+ cy.wait('@postProfile');
+
+ cy.get('app-profiles-edit').should('not.exist');
+ });
+
+});
diff --git a/e2e/cypress/support/index.js b/e2e/cypress/support/index.js
index f65e7f7cd..5d5415faf 100644
--- a/e2e/cypress/support/index.js
+++ b/e2e/cypress/support/index.js
@@ -1 +1,2 @@
-import './commands'
\ No newline at end of file
+import './commands';
+require('cypress-terminal-report/src/installLogsCollector')();
diff --git a/e2e/mockserver/mockserver.ts b/e2e/mockserver/mockserver.ts
index 8440e5266..76212a1c0 100644
--- a/e2e/mockserver/mockserver.ts
+++ b/e2e/mockserver/mockserver.ts
@@ -311,6 +311,9 @@ END:VCALENDAR
}
}, 1000);
break;
+ case '/rest/v1/aliases/limits':
+ response.end(JSON.stringify({ "total": 10, "current": 4}));
+ break;
case '/rest/v1/profiles':
response.end(JSON.stringify(this.profiles_verified()));
break;
@@ -345,12 +348,6 @@ END:VCALENDAR
case '/rest/v1/calendar/events_raw':
this.handleEvents(request, response);
break;
- case '/ajax/from_address':
- response.end(JSON.stringify(this.from_address()));
- break;
- case '/ajax/aliases':
- response.end(JSON.stringify({ 'status': 'success', 'aliases': [] }));
- break;
case '/rest/v1/email_folder/create':
this.createFolder(request, response);
break;
@@ -381,6 +378,18 @@ END:VCALENDAR
}
));
break;
+ case '/rest/v1/webmail/preferences':
+ response.end(JSON.stringify({
+ 'Global': {'version': 1, 'entries': {} },
+ 'Desktop': {'version': 1, 'entries': {} },
+ 'Mobile': {'version': 1, 'entries': {} }
+ }));
+ break;
+ case '/rest/v1/webmail/saved_searches':
+ response.end(JSON.stringify({
+ 'version': 1, 'entries': []
+ }));
+ break;
case '/_ics/Europe/Oslo.ics':
response.end(this.vtimezone_oslo);
break;
@@ -796,34 +805,32 @@ END:VCALENDAR
profiles_verified() {
return {
- 'result': {
- 'aliases': [{
- 'profile': {
- 'smtp_username': null,
- 'email': 'a2@example.com',
- 'reference_type': 'aliases',
- 'id': 16455,
- 'smtp_port': null,
- 'smtp_address': null,
- 'is_smtp_enabled': 0,
- 'signature': null,
- 'reference': {},
- 'reply_to': 'a2@example.com',
- 'name': 'a2@example.com',
- 'smtp_password': null,
- 'from_name': 'Hallucinogen',
- 'type': 'aliases'
- }
- }, {
- 'profile': {
- 'id': 16456,
- 'email': 'aa1@example.com',
- 'reference_type': 'aliases',
- 'smtp_username': null,
- 'from_name': 'Astrix',
- 'smtp_password': null,
- 'type': 'aliases',
- 'reference': {
+ 'results':
+ [{
+ 'smtp_username': null,
+ 'email': 'a2@example.com',
+ 'reference_type': 'aliases',
+ 'id': 16455,
+ 'smtp_port': null,
+ 'smtp_address': null,
+ 'is_smtp_enabled': 0,
+ 'signature': null,
+ 'reference': {},
+ 'reply_to': 'a2@example.com',
+ 'name': 'a2@example.com',
+ 'smtp_password': null,
+ 'from_name': 'Hallucinogen',
+ 'type': 'main'
+ },
+ {
+ 'id': 16456,
+ 'email': 'aa1@example.com',
+ 'reference_type': 'aliases',
+ 'smtp_username': null,
+ 'from_name': 'Astrix',
+ 'smtp_password': null,
+ 'type': 'aliases',
+ 'reference': {
'domainid': null,
'id': 278,
'localpart': 'aa1',
@@ -833,92 +840,87 @@ END:VCALENDAR
'status': 6,
'id': 16,
'name': 'example.com'
- }
- }
- }
- }, {
- 'profile': {
- 'smtp_username': null,
- 'email': 'testmail@testmail.com',
- 'reference_type': 'aliases',
- 'id': 16457,
- 'smtp_port': null,
- 'smtp_address': null,
- 'is_smtp_enabled': 0,
- 'signature': null,
- 'reference': {},
- 'name': 'John Doe',
- 'smtp_password': null,
- 'from_name': 'John Doe',
- 'type': 'aliases'
- }
- }],
- 'others': [{
- 'profile': {
- 'smtp_password': null,
- 'from_name': 'Electric Universe',
- 'type': 'external_email',
- 'name': 'Electric Universe',
- 'reference': {
- 'save_sent': 'n',
- 'signature': 'xxx',
- 'use_sig_for_reply': 'NO',
- 'reply_to': 'admin@runbox.com',
- 'name': 'Electric Universe',
- 'default_bcc': '',
- 'email': 'admin@runbox.com',
- 'msg_per_page': 0,
- 'folder': 'Encoding Test',
- 'sig_above': 'NO',
- 'charset': null,
- 'comp_new_window': null,
- 'status': 0
},
- 'reply_to': 'admin@runbox.com',
- 'smtp_address': null,
- 'is_smtp_enabled': 0,
- 'signature': 'xxx',
- 'smtp_port': null,
- 'id': 16448,
- 'email': 'admin@runbox.com',
- 'reference_type': 'preference',
- 'smtp_username': null
- }
- }, {
- 'profile': {
- 'smtp_address': null,
- 'signature': '
ą
\r\neex
',
- 'is_smtp_enabled': 0,
- 'smtp_port': null,
- 'from_name': 'folder1',
- 'smtp_password': null,
- 'type': 'external_email',
- 'reply_to': 'admin@runbox.com',
- 'reference': {
- 'comp_new_window': null,
- 'status': 0,
- 'charset': null,
- 'folder': 'LALA',
- 'sig_above': 'NO',
- 'email': 'admin@runbox.com',
- 'msg_per_page': 0,
- 'name': 'folder1',
- 'reply_to': 'admin@runbox.com',
- 'default_bcc': '',
- 'use_sig_for_reply': 'YES',
- 'signature': 'ą
\r\neex
',
- 'save_sent': 'n'
- },
- 'name': 'folder1',
- 'email': 'admin@runbox.com',
- 'reference_type': 'preference',
- 'smtp_username': null,
- 'id': 16450
- }
- }],
- 'main': []
- }
- };
+ },
+ },
+ {
+ 'smtp_username': null,
+ 'email': 'testmail@testmail.com',
+ 'reference_type': 'aliases',
+ 'id': 16457,
+ 'smtp_port': null,
+ 'smtp_address': null,
+ 'is_smtp_enabled': 0,
+ 'signature': null,
+ 'reference': {},
+ 'name': 'John Doe',
+ 'smtp_password': null,
+ 'from_name': 'John Doe',
+ 'type': 'aliases'
+ },
+ {
+ 'smtp_password': null,
+ 'from_name': 'Electric Universe',
+ 'type': 'external_email',
+ 'name': 'Electric Universe',
+ 'reference': {
+ 'save_sent': 'n',
+ 'signature': 'xxx',
+ 'use_sig_for_reply': 'NO',
+ 'reply_to': 'admin@runbox.com',
+ 'name': 'Electric Universe',
+ 'default_bcc': '',
+ 'email': 'admin@runbox.com',
+ 'msg_per_page': 0,
+ 'folder': 'Encoding Test',
+ 'sig_above': 'NO',
+ 'charset': null,
+ 'comp_new_window': null,
+ 'status': 0
+ },
+ 'reply_to': 'admin@runbox.com',
+ 'smtp_address': null,
+ 'is_smtp_enabled': 0,
+ 'signature': 'xxx',
+ 'smtp_port': null,
+ 'id': 16448,
+ 'email': 'admin@runbox.com',
+ 'reference_type': 'preference',
+ 'smtp_username': null
+ },
+ {
+
+ 'smtp_address': null,
+ 'signature': 'ą
\r\neex
',
+ 'is_smtp_enabled': 0,
+ 'smtp_port': null,
+ 'from_name': 'folder1',
+ 'smtp_password': null,
+ 'type': 'external_email',
+ 'reply_to': 'admin@runbox.com',
+ 'reference': {
+ 'comp_new_window': null,
+ 'status': 0,
+ 'charset': null,
+ 'folder': 'LALA',
+ 'sig_above': 'NO',
+ 'email': 'admin@runbox.com',
+ 'msg_per_page': 0,
+ 'name': 'folder1',
+ 'reply_to': 'admin@runbox.com',
+ 'default_bcc': '',
+ 'use_sig_for_reply': 'YES',
+ 'signature': 'ą
\r\neex
',
+ 'save_sent': 'n'
+ },
+ 'name': 'folder1',
+ 'email': 'admin@runbox.com',
+ 'reference_type': 'preference',
+ 'smtp_username': null,
+ 'id': 16450
+ }
+ ]
+ };
}
contacts(): any[] {
diff --git a/package-lock.json b/package-lock.json
index 3f99366d7..f5f676c92 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -63,7 +63,7 @@
"@types/node": "^14.14.31",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
- "cypress": "^12.10.0",
+ "cypress": "^12.17.4",
"cypress-terminal-report": "^5.1.1",
"eslint": "^8.38.0",
"jasmine-core": "~4.6.0",
@@ -3783,9 +3783,9 @@
}
},
"node_modules/@cypress/request": {
- "version": "2.88.11",
- "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz",
- "integrity": "sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==",
+ "version": "2.88.12",
+ "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz",
+ "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==",
"dev": true,
"dependencies": {
"aws-sign2": "~0.7.0",
@@ -3803,7 +3803,7 @@
"performance-now": "^2.1.0",
"qs": "~6.10.3",
"safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
+ "tough-cookie": "^4.1.3",
"tunnel-agent": "^0.6.0",
"uuid": "^8.3.2"
},
@@ -3861,6 +3861,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/@cypress/request/node_modules/tough-cookie": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+ "dev": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@cypress/request/node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/@cypress/request/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -8884,15 +8908,15 @@
"dev": true
},
"node_modules/cypress": {
- "version": "12.10.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.10.0.tgz",
- "integrity": "sha512-Y0wPc221xKKW1/4iAFCphkrG2jNR4MjOne3iGn4mcuCaE7Y5EtXL83N8BzRsAht7GYfWVjJ/UeTqEdDKHz39HQ==",
+ "version": "12.17.4",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.4.tgz",
+ "integrity": "sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
- "@cypress/request": "^2.88.10",
+ "@cypress/request": "2.88.12",
"@cypress/xvfb": "^1.2.4",
- "@types/node": "^14.14.31",
+ "@types/node": "^16.18.39",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.2.0",
@@ -8925,9 +8949,10 @@
"minimist": "^1.2.8",
"ospath": "^1.2.2",
"pretty-bytes": "^5.6.0",
+ "process": "^0.11.10",
"proxy-from-env": "1.0.0",
"request-progress": "^3.0.0",
- "semver": "^7.3.2",
+ "semver": "^7.5.3",
"supports-color": "^8.1.1",
"tmp": "~0.2.1",
"untildify": "^4.0.0",
@@ -9054,6 +9079,12 @@
"node": ">=8"
}
},
+ "node_modules/cypress/node_modules/@types/node": {
+ "version": "16.18.59",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.59.tgz",
+ "integrity": "sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ==",
+ "dev": true
+ },
"node_modules/cypress/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -9216,9 +9247,9 @@
}
},
"node_modules/cypress/node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -18720,6 +18751,15 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -18983,6 +19023,12 @@
"node": ">=0.6"
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -21572,6 +21618,16 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/package.json b/package.json
index 692322fcc..b07d7db3e 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
"@types/node": "^14.14.31",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
- "cypress": "^12.10.0",
+ "cypress": "^12.17.4",
"cypress-terminal-report": "^5.1.1",
"eslint": "^8.38.0",
"jasmine-core": "~4.6.0",
diff --git a/src/app/compose/compose.component.ts b/src/app/compose/compose.component.ts
index eac5f44ef..633774865 100644
--- a/src/app/compose/compose.component.ts
+++ b/src/app/compose/compose.component.ts
@@ -24,7 +24,6 @@ import { Location } from '@angular/common';
import { Router } from '@angular/router';
import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
-import { FromAddress } from '../rmmapi/from_address';
import { Observable, Subscription } from 'rxjs';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
import { DraftDeskService, DraftFormModel } from './draftdesk.service';
@@ -37,6 +36,7 @@ import { TinyMCEPlugin } from '../rmm/plugin/tinymce.plugin';
import { RecipientsService } from './recipients.service';
import { isValidEmailArray } from './emailvalidator';
import { MailAddressInfo } from '../common/mailaddressinfo';
+import { Identity } from '../profiles/profile.service';
import { MessageTableRowTool} from '../messagetable/messagetablerow';
import { DefaultPrefGroups, PreferencesService } from '../common/preferences.service';
import { objectEqualWithKeys } from '../common/util';
@@ -124,7 +124,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
if (this.model.isUnsaved()) {
this.editing = true;
this.isUnsaved = true;
- const from: FromAddress = this.draftDeskservice.fromsSubject.value.find((f) =>
+ const from: Identity = this.draftDeskservice.fromsSubject.value.find((f) =>
f.nameAndAddress === this.model.from || f.email === this.model.from);
this.has_pasted_signature = false;
@@ -206,7 +206,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
this.formGroup.controls.from.valueChanges
.pipe(debounceTime(1000))
.subscribe((selected_from_address) => {
- const from: FromAddress = this.draftDeskservice.fromsSubject.value.find((f) =>
+ const from: Identity = this.draftDeskservice.fromsSubject.value.find((f) =>
f.nameAndAddress === selected_from_address);
if ( this.formGroup.controls.msg_body.pristine ) {
if ( this.signature && from.signature ) {
diff --git a/src/app/compose/draftdesk.component.ts b/src/app/compose/draftdesk.component.ts
index d1991cac7..0c64d6597 100644
--- a/src/app/compose/draftdesk.component.ts
+++ b/src/app/compose/draftdesk.component.ts
@@ -101,6 +101,8 @@ export class DraftDeskComponent implements OnInit {
}
});
this.draftModelsInView.splice(0, 0, ...newEntries);
+ this.draftModelsInView.sort((a,b) => a.mid < b.mid ? -1 : 1);
+ this.draftModelsInView = this.draftModelsInView.slice(0, this.currentMaxDraftsInView);
deletedEntries.forEach(
(dMsgId) => this.draftModelsInView.splice(this.draftModelsInView.findIndex((dMsg) => dMsg.mid === dMsgId), 1));
} else {
diff --git a/src/app/compose/draftdesk.service.spec.ts b/src/app/compose/draftdesk.service.spec.ts
index e60d8b570..d460fe1fa 100644
--- a/src/app/compose/draftdesk.service.spec.ts
+++ b/src/app/compose/draftdesk.service.spec.ts
@@ -17,8 +17,8 @@
// along with Runbox 7. If not, see .
// ---------- END RUNBOX LICENSE ----------
-import {DraftFormModel} from './draftdesk.service';
-import { FromAddress } from '../rmmapi/from_address';
+import { DraftFormModel } from './draftdesk.service';
+import { Identity } from '../profiles/profile.service';
import { MailAddressInfo } from '../common/mailaddressinfo';
@@ -45,7 +45,7 @@ describe('DraftDesk', () => {
rawtext: 'blabla\nabcde',
html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
false);
expect(draft.subject).toBe('Fwd: Test subject');
@@ -84,7 +84,7 @@ blabla\nabcde`);
html: 'blabla
abcde
',
sanitized_html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
true);
expect(draft.subject).toBe('Fwd: Test subject');
@@ -137,7 +137,7 @@ Subject: Test subject
rawtext: 'blabla\nabcde',
html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
false, false);
expect(draft.subject).toBe('Re: Test subject');
@@ -167,7 +167,7 @@ Subject: Test subject
rawtext: 'blabla\nabcde',
html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
false, false);
expect(draft.subject).toBe('Re: Test subject');
@@ -204,7 +204,7 @@ Subject: Test subject
rawtext: 'blabla\nabcde',
html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
false, false);
expect(draft.subject).toBe('Re: Test subject');
@@ -245,7 +245,7 @@ Subject: Test subject
rawtext: 'blabla\nabcde',
html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
true, false);
expect(draft.subject).toBe('Re: Test subject');
@@ -276,7 +276,7 @@ Subject: Test subject
rawtext: 'blabla\nabcde',
html: 'blabla
abcde
'
},
- [ FromAddress.fromEmailAddress('to@runbox.com')],
+ [ Identity.fromObject({'email':'to@runbox.com'})],
true, false);
expect(draft.subject).toBe('Re: Test subject');
@@ -304,7 +304,7 @@ Subject: Test subject
text: 'blabla\nabcde',
rawtext: 'blabla\nabcde'
},
- [ FromAddress.fromEmailAddress('to@runbox.com') ],
+ [ Identity.fromObject({'email':'to@runbox.com'}) ],
true, false);
expect(draft.subject).toBe('Re: Test subject');
@@ -332,7 +332,7 @@ Subject: Test subject
text: 'blabla\nabcde',
rawtext: 'blabla\nabcde'
},
- [ FromAddress.fromEmailAddress('to@runbox.com') ],
+ [ Identity.fromObject({'email':'to@runbox.com'}) ],
true, false);
const replydraft = DraftFormModel.reply({
@@ -351,7 +351,7 @@ Subject: Test subject
text: draft.msg_body,
rawtext: draft.msg_body
},
- [ FromAddress.fromEmailAddress('from@runbox.com') ],
+ [ Identity.fromObject({'email':'from@runbox.com'}) ],
false, false);
expect(replydraft.subject).toBe('Re: Test subject');
@@ -370,7 +370,7 @@ Subject: Test subject
// compose?new=true
let draft = DraftFormModel.create(
-1,
- FromAddress.fromEmailAddress('to@runbox.com'),
+ Identity.fromObject({'email':'to@runbox.com'}),
null,
'');
expect(draft.isUnsaved()).toBe(true);
@@ -378,7 +378,7 @@ Subject: Test subject
// Link on contact page:
draft = DraftFormModel.create(
-1,
- FromAddress.fromEmailAddress('to@runbox.com'),
+ Identity.fromObject({'email':'to@runbox.com'}),
'"Test Runbox" ',
'');
expect(draft.isUnsaved()).toBe(true);
@@ -386,7 +386,7 @@ Subject: Test subject
// refreshDrafts
draft = DraftFormModel.create(
12345,
- FromAddress.fromEmailAddress('to@runbox.com'),
+ Identity.fromObject({'email':'to@runbox.com'}),
'"Test Runbox" ',
'Some blahblah');
expect(draft.isUnsaved()).toBe(false);
diff --git a/src/app/compose/draftdesk.service.ts b/src/app/compose/draftdesk.service.ts
index b53d4e236..d0bcede21 100644
--- a/src/app/compose/draftdesk.service.ts
+++ b/src/app/compose/draftdesk.service.ts
@@ -21,12 +21,11 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
import { FolderListEntry } from '../common/folderlistentry';
-import { FromAddress } from '../rmmapi/from_address';
import { MessageInfo } from '../common/messageinfo';
import { MailAddressInfo } from '../common/mailaddressinfo';
import { MessageListService } from '../rmmapi/messagelist.service';
import { MessageTableRowTool} from '../messagetable/messagetablerow';
-import { RMM } from '../rmm';
+import { Identity, ProfileService } from '../profiles/profile.service';
import { from, of, BehaviorSubject } from 'rxjs';
import { map, mergeMap, bufferCount, take, distinctUntilChanged } from 'rxjs/operators';
@@ -68,7 +67,7 @@ export class DraftFormModel {
message_date = null;
public static create(draftId: number,
- fromAddress: FromAddress,
+ fromAddress: Identity,
to: string, subject: string,
preview?: string,
message_date?: Date): DraftFormModel {
@@ -87,7 +86,7 @@ export class DraftFormModel {
return ret;
}
- public static reply(mailObj, froms: FromAddress[], all: boolean, useHTML: boolean): DraftFormModel {
+ public static reply(mailObj, froms: Identity[], all: boolean, useHTML: boolean): DraftFormModel {
const ret = new DraftFormModel();
ret.reply_to_id = mailObj.mid;
ret.in_reply_to = mailObj.headers['message-id'];
@@ -165,7 +164,7 @@ export class DraftFormModel {
return ret;
}
- public static forward(mailObj, froms: FromAddress[], useHTML: boolean): DraftFormModel {
+ public static forward(mailObj, froms: Identity[], useHTML: boolean): DraftFormModel {
const ret = new DraftFormModel();
ret.setFromForResponse(mailObj, froms);
@@ -214,7 +213,7 @@ ${mailObj.sanitized_html}`;
return false;
}
- private setFromForResponse(mailObj, froms: FromAddress[]): void {
+ private setFromForResponse(mailObj, froms: Identity[]): void {
if (froms.length > 0) {
this.from = (
[].concat(mailObj.to || []).concat(mailObj.cc || []).find(
@@ -234,21 +233,20 @@ ${mailObj.sanitized_html}`;
@Injectable()
export class DraftDeskService {
draftModels: BehaviorSubject = new BehaviorSubject([]);
- fromsSubject: BehaviorSubject = new BehaviorSubject([]);
+ fromsSubject: BehaviorSubject = new BehaviorSubject([]);
isEditing = -1;
composingNewDraft: DraftFormModel;
shouldReturnToPreviousPage = false;
constructor(public rmmapi: RunboxWebmailAPI,
private messagelistservice: MessageListService,
- private http: HttpClient,
- private rmm: RMM) {
- this.rmm.profile.profiles.subscribe((_) => {
- this.refreshFroms();
+ private profileService: ProfileService,
+ private http: HttpClient
+ ) {
+ this.profileService.validProfiles.subscribe((profiles) => {
+ this.fromsSubject.next(profiles);
});
- // run these at least once (rmm.blah is not a service!)
- this.refreshFroms();
// Recreate drafts when froms(identities) change
this.fromsSubject
.subscribe(froms => {
@@ -272,36 +270,8 @@ export class DraftDeskService {
}
// default identity for creating an email
- public mainIdentity(): FromAddress {
- return this.fromsSubject.value[0];
- }
-
- public refreshFroms() {
- this.rmmapi.getFromAddress().pipe(
- map((froms) => {
- froms.sort((a, b) => {
- if (a.type === 'main') {
- return -1;
- } else if (b.type === 'main') {
- return 1;
- } else if (a.type === 'aliases') {
- return -1;
- } else if (b.type === 'aliases') {
- return 1;
- } else {
- return 0;
- }
- });
- froms.sort((a, b) => {
- return a.priority - b.priority;
- });
- return froms;
- })).subscribe(
- froms => this.fromsSubject.next(froms),
- err => {
- console.error(err);
- }
- );
+ public mainIdentity(): Identity {
+ return this.profileService.composeProfile;
}
private refreshDrafts() {
@@ -319,7 +289,7 @@ export class DraftDeskService {
newDrafts.push(
DraftFormModel.create(
msgInfo.id,
- this.fromsSubject.value[0],
+ this.mainIdentity(),
msgInfo.to.map((addr) => addr.name === null || addr.address.indexOf(addr.name + '@') === 0 ?
addr.address : addr.name + '<' + addr.address + '>').join(','),
msgInfo.subject, null, msgInfo.messageDate)
@@ -349,7 +319,7 @@ export class DraftDeskService {
) {
const draftObj = DraftFormModel.create(
-1,
- this.fromsSubject.value[0],
+ this.mainIdentity(),
'"Runbox 7 Bug Reports" ',
'Runbox 7 Bug Report'
);
@@ -386,7 +356,7 @@ export class DraftDeskService {
{responseType: 'text'}).toPromise();
const draftObj = DraftFormModel.create(
-1,
- this.fromsSubject.value[0],
+ this.mainIdentity(),
to,
"Let's have a video call"
);
diff --git a/src/app/compose/recipients.service.spec.ts b/src/app/compose/recipients.service.spec.ts
index 8cb1dc2a1..13251ff1a 100644
--- a/src/app/compose/recipients.service.spec.ts
+++ b/src/app/compose/recipients.service.spec.ts
@@ -126,7 +126,7 @@ export class ContactsServiceMock {
export class RunboxWebMailAPIMock {
public me = of({ uid: 33 });
- public getFromAddress = () => of(['testuser@runbox.com']);
+ public getProfiles = () => of([{ 'email':'testuser@runbox.com'}]);
}
describe('RecipientsService', () => {
diff --git a/src/app/compose/recipients.service.ts b/src/app/compose/recipients.service.ts
index 28dd53fef..825d3c300 100644
--- a/src/app/compose/recipients.service.ts
+++ b/src/app/compose/recipients.service.ts
@@ -25,7 +25,7 @@ import { ContactKind, Contact } from '../contacts-app/contact';
import { isValidEmail } from './emailvalidator';
import { MailAddressInfo } from '../common/mailaddressinfo';
import { Recipient } from './recipient';
-import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
+import { ProfileService } from '../profiles/profile.service';
import moment from 'moment';
enum RecipientOrigin {
@@ -45,9 +45,9 @@ export class RecipientsService {
constructor(
private searchService: SearchService,
private contactsService: ContactsService,
- rmmapi: RunboxWebmailAPI,
+ profileService: ProfileService
) {
- rmmapi.getFromAddress().subscribe(
+ profileService.validProfiles.subscribe(
froms => this.ownAddresses.next(new Set(froms.map(f => f.email))),
_err => this.ownAddresses.next(new Set([])),
);
diff --git a/src/app/mailviewer/avatar-bar.component.ts b/src/app/mailviewer/avatar-bar.component.ts
index e58a981db..24bb623d7 100644
--- a/src/app/mailviewer/avatar-bar.component.ts
+++ b/src/app/mailviewer/avatar-bar.component.ts
@@ -20,7 +20,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { ReplaySubject} from 'rxjs';
import { take } from 'rxjs/operators';
-import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
+import { ProfileService } from '../profiles/profile.service';
import { ContactsService } from '../contacts-app/contacts.service';
import { PreferencesService } from '../common/preferences.service';
@@ -67,13 +67,13 @@ export class AvatarBarComponent implements OnInit {
constructor(
preferenceService: PreferencesService,
private contactsservice: ContactsService,
- private rmmapi: RunboxWebmailAPI,
+ private profileService: ProfileService
) {
preferenceService.preferences.subscribe(_ => this.ngOnChanges());
}
ngOnInit() {
- this.rmmapi.getFromAddress().subscribe(
+ this.profileService.validProfiles.subscribe(
froms => this.ownAddresses.next(new Set(froms.map(f => f.email.toLowerCase()))),
_err => this.ownAddresses.next(new Set([])),
);
diff --git a/src/app/mailviewer/singlemailviewer.component.spec.ts b/src/app/mailviewer/singlemailviewer.component.spec.ts
index b4766bf69..54a40c99c 100644
--- a/src/app/mailviewer/singlemailviewer.component.spec.ts
+++ b/src/app/mailviewer/singlemailviewer.component.spec.ts
@@ -129,7 +129,7 @@ describe('SingleMailViewerComponent', () => {
} },
{ provide: RunboxWebmailAPI, useValue: {
me: of({ uid: 9876 }),
- getFromAddress() { return of([]); },
+ getProfiles() { return of([]); },
getMessageContents(messageId: number): Observable {
console.log('Get message contents for', messageId);
return of(Object.assign(new MessageContents(), {
diff --git a/src/app/profiles/profile.service.spec.ts b/src/app/profiles/profile.service.spec.ts
new file mode 100644
index 000000000..c5fa4841a
--- /dev/null
+++ b/src/app/profiles/profile.service.spec.ts
@@ -0,0 +1,203 @@
+// --------- BEGIN RUNBOX LICENSE ---------
+// Copyright (C) 2016-2023 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 { Identity, ProfileService } from "./profile.service";
+import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { TestBed, waitForAsync } from '@angular/core/testing';
+import { of } from 'rxjs';
+
+describe('Identity', () => {
+ it('Should create an identity with just email', () => {
+ const ident = Identity.fromObject({ 'email': 'test@example.com' });
+ expect(ident.nameAndAddress).toEqual('test@example.com');
+ });
+
+ it('Should create an identity with name and email', () => {
+ const ident = Identity.fromObject({
+ 'email': 'test@example.com',
+ 'name': 'Fred Bloggs',
+ });
+ expect(ident.nameAndAddress).toEqual('Fred Bloggs ');
+ });
+});
+
+describe('ProfileService', () => {
+ let service: ProfileService;
+
+ const DEFAULT_EMAIL = 'a2@example.com';
+ let PROFILES = [{
+ 'email': 'a2@example.com',
+ 'reference_type': 'aliases',
+ 'id': 16455,
+ 'signature': null,
+ 'reference': {},
+ 'reply_to': 'a2@example.com',
+ 'name': 'a2@example.com',
+ 'smtp_password': null,
+ 'from_name': 'Hallucinogen',
+ 'type': 'main'
+ },
+ {
+ 'id': 16456,
+ 'email': 'aa1@example.com',
+ 'reference_type': 'aliases',
+ 'from_name': 'Astrix',
+ 'type': 'aliases',
+ 'reference': {
+ 'domainid': null,
+ 'id': 278,
+ 'localpart': 'aa1',
+ 'virtual_domainid': 16,
+ 'virtual_domain': {
+ 'catch_all': '',
+ 'status': 6,
+ 'id': 16,
+ 'name': 'example.com'
+ },
+ },
+ },
+ {
+ 'email': 'testmail@testmail.com',
+ 'reference_type': 'aliases',
+ 'id': 16457,
+ 'signature': null,
+ 'reference': {},
+ 'name': 'John Doe',
+ 'from_name': 'John Doe',
+ 'type': 'aliases'
+ },
+ {
+ 'from_name': 'Electric Universe',
+ 'type': 'external_email',
+ 'name': 'Electric Universe',
+ 'reference': {
+ 'save_sent': 'n',
+ 'signature': 'xxx',
+ 'use_sig_for_reply': 'NO',
+ 'reply_to': 'admin@runbox.com',
+ 'name': 'Electric Universe',
+ 'default_bcc': '',
+ 'email': 'admin@runbox.com',
+ 'msg_per_page': 0,
+ 'folder': 'Encoding Test',
+ 'sig_above': 'NO',
+ 'charset': null,
+ 'comp_new_window': null,
+ 'status': 0
+ },
+ 'reply_to': 'admin@runbox.com',
+ 'signature': 'xxx',
+ 'id': 16448,
+ 'email': 'admin@runbox.com',
+ 'reference_type': 'preference',
+ },
+ {
+ 'signature': 'ą
\r\neex
',
+ 'from_name': 'folder1',
+ 'type': 'external_email',
+ 'reply_to': 'admin@runbox.com',
+ 'reference': {
+ 'comp_new_window': null,
+ 'status': 0,
+ 'charset': null,
+ 'folder': 'LALA',
+ 'sig_above': 'NO',
+ 'email': 'admin@runbox.com',
+ 'msg_per_page': 0,
+ 'name': 'folder1',
+ 'reply_to': 'admin@runbox.com',
+ 'default_bcc': '',
+ 'use_sig_for_reply': 'YES',
+ 'signature': 'ą
\r\neex
',
+ 'save_sent': 'n'
+ },
+ 'name': 'folder1',
+ 'email': 'admin@runbox.com',
+ 'reference_type': 'preference',
+ 'id': 16450
+ }];
+
+ const ALLOWED_DOMAINS = ['runbox.com', 'example.com'];
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ HttpClientTestingModule,
+ ],
+ providers: [
+ { provide: RunboxWebmailAPI, useValue: {
+ me: of({first_name: 'Test', last_name: 'User'}),
+ getProfiles: () => of(PROFILES),
+ createProfile: (newprofile) => {
+ newprofile['reference_type'] = 'aliases';
+ PROFILES.unshift(newprofile);
+ return of(PROFILES.length);
+ },
+
+ } },
+ ProfileService
+ ],
+ }).compileComponents();
+ }));
+
+ 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.nonAliases.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',
+ email: 'newp@runbox.com',
+ from_name: 'New Profile',
+ signature: 'My sig'})
+ .subscribe((res) => {
+ expect(res).toBeTruthy();
+ expect(PROFILES.length).toBe(PROFILES.length);
+ done();
+ });
+
+ });
+});
diff --git a/src/app/profiles/profile.service.ts b/src/app/profiles/profile.service.ts
new file mode 100644
index 000000000..47a28be68
--- /dev/null
+++ b/src/app/profiles/profile.service.ts
@@ -0,0 +1,137 @@
+// --------- 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 { map } from 'rxjs/operators';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { RunboxMe, RunboxWebmailAPI } from '../rmmapi/rbwebmail';
+
+export interface FromPriority {
+ from_priority: number;
+ id: number;
+}
+
+export class Identity {
+ email: string;
+ from_name: string;
+ from_priority: number;
+ id: number;
+ is_signature_html: boolean;
+ is_smtp_enabled: boolean;
+ name: string;
+ reference_type: string;
+ reply_to: string;
+ signature: string;
+ type: string;
+ smtp_address: string;
+ smtp_password: string;
+ smtp_port: string;
+ smtp_username: string;
+ is_verified: boolean;
+ reference: { status: number };
+ preferred_runbox_domain: string;
+ // FIXME: Legacy rubbish for send-folder-options
+ folder: string;
+
+ public nameAndAddress: string;
+
+ public static fromObject(obj: any): Identity {
+ const ret = Object.assign(new Identity(), obj);
+ ret.resolveNameAndAddress();
+ return ret;
+ }
+
+ resolveNameAndAddress() {
+ this.nameAndAddress = this.name ? `${this.name} <${this.email}>` : this.email;
+ }
+}
+
+@Injectable({ providedIn: 'root' })
+export class ProfileService {
+ public profiles: BehaviorSubject = new BehaviorSubject([]);
+ public aliases: BehaviorSubject = new BehaviorSubject([]);
+ public nonAliases: BehaviorSubject = new BehaviorSubject([]);
+ public validProfiles: BehaviorSubject = new BehaviorSubject([]);
+ public composeProfile: Identity;
+ public me: RunboxMe;
+ constructor(
+ public rmmapi: RunboxWebmailAPI
+ ) {
+ this.refresh();
+ this.rmmapi.me.subscribe(me => this.me = me);
+ }
+
+ refresh() {
+ this.rmmapi.getProfiles().subscribe(
+ (res: Identity[]) => {
+ this.validProfiles.next(res.filter(p => p.type === 'aliases' || (p.reference_type === 'preference' && p.reference.status === 0)));
+ this.aliases.next(res.filter(p => p.reference_type === 'aliases'));
+ this.nonAliases.next(res.filter(p => p.reference_type !== 'aliases'));
+ this.composeProfile = res.find(p => p.from_priority === 0);
+ if (!this.composeProfile) {
+ this.composeProfile = res.find(p => p.type === 'main');
+ }
+ this.profiles.next(res);
+ }
+ );
+ }
+
+ create(values): Observable {
+ return this.rmmapi.createProfile(values).pipe(
+ map((res: boolean) => {
+ this.refresh();
+ return res;
+ })
+ );
+ }
+ delete(id): Observable {
+ return this.rmmapi.deleteProfile(id).pipe(
+ map((res: boolean) => {
+ this.refresh();
+ return res;
+ })
+ );
+ }
+
+ update(id, values): Observable {
+ return this.rmmapi.updateProfile(id, values).pipe(
+ map((res: boolean) => {
+ this.refresh();
+ return res;
+ })
+ );
+ }
+ reValidate(id) {
+ this.rmmapi.resendValidationEmail(id).subscribe(
+ reply => {
+ this.refresh();
+ if ( !reply ) {
+ // snackbar error msg?
+ }
+ },
+ );
+ }
+ updateFromPriorities(values: FromPriority[]) {
+ this.rmmapi.updateFromPriorities(values).subscribe(
+ (reply) => {
+ this.refresh();
+ }
+ );
+ }
+
+}
diff --git a/src/app/profiles/profile.ts b/src/app/profiles/profile.ts
deleted file mode 100644
index 7500c478d..000000000
--- a/src/app/profiles/profile.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-// --------- 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 ----------
-export class Profile {
- id: number;
- profile: string;
- name: string;
- from: string;
- reply_to: string;
- signature: string;
-
- constructor(properties: any) {
- const self = this;
- properties.forEach( key => self[key] = properties[key] );
- }
-}
diff --git a/src/app/profiles/profiles.component.html b/src/app/profiles/profiles.component.html
index d36793a2f..ca9894b03 100644
--- a/src/app/profiles/profiles.component.html
+++ b/src/app/profiles/profiles.component.html
@@ -1,8 +1,8 @@
Identities
-
+
Default Identity
@@ -12,16 +12,19 @@ Default Identity
-
+ 0"
+ [validProfiles] = profileService.validProfiles.value
+ [selectedProfile] = profileService.composeProfile
+ >
-
Select the email you want to use as your Default Identity:
+
Select the identity you want to use as your Default Identity:
+ *ngIf="profileService.aliases.value.length > 0"
+ [profiles]="profileService.aliases.value">
Identities for Aliases
@@ -29,7 +32,7 @@ Identities for Aliases
Aliases are extra email addresses that deliver to your account, just as your main username/email address does. Below are identities you can use if you want to send messages from your aliases.
These identities can be customised by adding a different From Name, Signature or Reply-to address. You can also change the Runbox domain an alias uses.
You can't delete these identities as they are tied to your aliases.
-
+
= alias_limits?.total">
You have reached the maximum allowed number of Runbox aliases.
To manage your aliases, please visit Email Aliases .
@@ -38,23 +41,20 @@ Identities for Aliases
+ *ngIf="profileService.nonAliases.value.length > 0"
+ [profiles]="profileService.nonAliases.value">
Other identities
-
Other Identities can be used when you want to send from addresses that are not part of your Runbox
- account. Adding this kind of address (e.g. a work email address) will require verification via
- email that you have access to that account. You can also use Other Identities if you need
- additional identities for you Runbox email addresses.
-
Added identities require verification via an email sent to the given address.
+
Other Identities can be used when you want to send from addresses that are not part of your Runbox account. Adding this kind of address (e.g. a work email address) will require verification via email that you have access to that account. You can also use Other Identities if you need additional identities for you Runbox email addresses.
+
Adding new identities require verification via an email sent to the given address.
-
+
Add Identity
- {{profiles.others.length}} identities created
+ {{profileService.nonAliases.value.length}} identities created
diff --git a/src/app/profiles/profiles.component.ts b/src/app/profiles/profiles.component.ts
index 333721762..8729895b0 100644
--- a/src/app/profiles/profiles.component.ts
+++ b/src/app/profiles/profiles.component.ts
@@ -16,12 +16,12 @@
// 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, OnInit } from '@angular/core';
+import { Component, Output, EventEmitter, ViewChild } from '@angular/core';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { MatLegacyPaginator as MatPaginator } from '@angular/material/legacy-paginator';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
import { ProfilesEditorModalComponent } from './profiles.editor.modal';
-import { RMM, AllIdentities } from '../rmm';
+import { ProfileService } from './profile.service';
@Component({
moduleId: 'angular2/app/profiles/',
@@ -29,60 +29,41 @@ import { RMM, AllIdentities } from '../rmm';
templateUrl: 'profiles.component.html'
})
-export class ProfilesComponent implements OnInit {
+export class ProfilesComponent {
panelOpenState = false;
@ViewChild(MatPaginator) paginator: MatPaginator;
@Output() Close: EventEmitter = new EventEmitter();
domain;
- profiles: AllIdentities;
- aliases = [];
- aliases_counter = {};
- aliases_unique = [];
+// profiles: Identity[];
+ alias_limits;
dialog_ref: any;
- ngOnInit() {
- this.rmm.profile.profiles.subscribe(profiles => this.profiles = profiles);
- }
-
- ev_reload_emiter (ev) {
- this.load_aliases();
- this.load_profiles();
- }
-
constructor(
public snackBar: MatSnackBar,
public dialog: MatDialog,
- public rmm: RMM,
+ public profileService: ProfileService,
) {
- this.rmm.runbox_domain.load();
- this.load_profiles();
- this.load_aliases();
- }
-
- load_aliases () {
- this.rmm.alias.load();
- }
- load_profiles () {
- this.rmm.profile.load();
+ // FIXME: Need to refresh this if/when we make more aliases
+ this.profileService.rmmapi.getAliasLimits().subscribe(
+ res => this.alias_limits = res
+ );
}
-
+
show_error (message, action) {
this.snackBar.open(message, action, {
duration: 2000,
});
}
- add_profile (type): void {
- let item = {type: type};
+ add_profile (): void {
+ let item = {};
this.dialog_ref = this.dialog.open(ProfilesEditorModalComponent, {
width: '600px',
data: item,
});
- this.dialog_ref.componentInstance.aliases_unique = this.aliases_unique;
this.dialog_ref.componentInstance.is_create = true;
this.dialog_ref.afterClosed().subscribe(result => {
- this.load_profiles();
item = result;
});
}
diff --git a/src/app/profiles/profiles.default.html b/src/app/profiles/profiles.default.html
index 6074fab54..ef0e7f69b 100644
--- a/src/app/profiles/profiles.default.html
+++ b/src/app/profiles/profiles.default.html
@@ -3,8 +3,8 @@
-
-
+
+
{{profile.nameAndAddress}}
diff --git a/src/app/profiles/profiles.default.ts b/src/app/profiles/profiles.default.ts
index e6fa65aaa..65c13b0de 100644
--- a/src/app/profiles/profiles.default.ts
+++ b/src/app/profiles/profiles.default.ts
@@ -17,86 +17,49 @@
// along with Runbox 7. If not, see .
// ---------- END RUNBOX LICENSE ----------
-import { Component } from '@angular/core';
-import { RMM } from '../rmm';
+import { Component, Input } from '@angular/core';
+import { Identity, FromPriority, ProfileService } from './profile.service';
import { RunboxWebmailAPI } from '../rmmapi/rbwebmail';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
-export interface Profile {
- email: string;
- id: number;
- name: string;
- nameAndAddress: string;
- priority: number;
- reply_to: string;
- signature: string;
- type: string;
-}
-
@Component({
- selector: 'app-profiles-default',
- styleUrls: ['profiles.default.scss'],
- templateUrl: 'profiles.default.html',
+ selector: 'app-profiles-default',
+ styleUrls: ['profiles.default.scss'],
+ templateUrl: 'profiles.default.html',
})
export class DefaultProfileComponent {
field_errors: any;
- profiles: Profile[];
- selected: any;
-
- constructor(public rmm: RMM, public rmmapi: RunboxWebmailAPI, private snackBar: MatSnackBar) {
- this.selectCurrentDefault();
- }
- async fetchProfiles() {
- const froms = await this.rmmapi.getFromAddress().toPromise();
- // Sort emails alphabetically
- this.profiles = froms.sort((a, b) => (a.email < b.email ? -1 : 1));
- }
+ @Input() validProfiles: Identity[];
+ @Input() selectedProfile: Identity;
- async selectCurrentDefault() {
- await this.fetchProfiles();
- const defaultProfiles = [];
- for (const profile of this.profiles) {
- if (profile.priority === 0) {
- defaultProfiles.push(profile);
- }
- }
- if (defaultProfiles.length === 1) {
- this.selected = defaultProfiles[0];
- } else {
- this.selected = this.profiles.find(p => p.type === 'main');
- }
+ constructor(
+ public profileService: ProfileService,
+ public rmmapi: RunboxWebmailAPI,
+ private snackBar: MatSnackBar
+ ) {
+ this.profileService.profiles.subscribe((_) => {
+ this.selectedProfile = this.profileService.composeProfile;
+ this.validProfiles = this.profileService.validProfiles.value;
+ });
}
updateDefaultProfile() {
- const priorities: any[] = new Array();
- const priority_data = { from_priorities: priorities };
- const type_data = { type: 'main' };
- for (const profile of this.profiles) {
- if (profile === this.selected) {
- const values = { id: profile.id, from_priority: 0 };
- priorities.push(values);
- this.updateType(profile.id, type_data, this.field_errors);
+ const priorities: FromPriority[] = new Array();
+ let p_value = 1;
+ for (const profile of this.profileService.profiles.value) {
+ let from_p: FromPriority = {"from_priority": -1, "id": profile.id };
+ if (profile.id === this.selectedProfile.id) {
+ from_p.from_priority = 0;
+ profile.from_priority = 0;
+ priorities.push(from_p);
} else {
- const values = { id: profile.id, from_priority: 1 };
- priorities.push(values);
+ from_p.from_priority = p_value++;
+ profile.from_priority = from_p.from_priority;
+ priorities.push(from_p);
}
}
- this.rmm.profile.updateFromPriorities(priority_data);
- }
-
- updateType(id: number, values: { type: string }, field_errors: any) {
- const req = this.rmm.profile.update(id, values, field_errors);
- req.subscribe(
- (reply) => {
- if (reply['status'] === 'success') {
- this.rmm.profile.load();
- } else if (reply['status'] === 'error') {
- this.showNotification('Could not update Identity Type');
- return;
- }
- }
- );
+ this.profileService.updateFromPriorities(priorities);
}
showNotification(message: string, action = 'Dismiss'): void {
diff --git a/src/app/profiles/profiles.editor.modal.html b/src/app/profiles/profiles.editor.modal.html
index d2b5cee1e..040fb5385 100644
--- a/src/app/profiles/profiles.editor.modal.html
+++ b/src/app/profiles/profiles.editor.modal.html
@@ -1,19 +1,26 @@