Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions src/app/compose/compose.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
has_pasted_signature: boolean;
signature: string;
selector: string;
private defaultBccRecipients: MailAddressInfo[] = [];

saveErrorMessage: string;

Expand Down Expand Up @@ -196,6 +197,15 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {

this.formGroup = this.formBuilder.group(this.model);

if (this.model.isUnsaved()) {
this.updateDefaultBcc(this.formGroup.controls.from.value, false);
} else {
this.trackDefaultBcc(this.formGroup.controls.from.value);
}

this.formGroup.controls.from.valueChanges
.subscribe((selected_from_address) => this.updateDefaultBcc(selected_from_address));

// Mark not saved if changes
this.formGroup.valueChanges.subscribe(() => this.saved = null);

Expand All @@ -222,8 +232,10 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
this.formGroup.controls.from.valueChanges
.pipe(debounceTime(1000))
.subscribe((selected_from_address) => {
const from: Identity = this.draftDeskservice.fromsSubject.value.find((f) =>
f.nameAndAddress === selected_from_address);
const from: Identity = this.identityForAddress(selected_from_address);
if (!from) {
return;
}
if ( this.formGroup.controls.msg_body.pristine ) {
if ( this.signature && from.signature ) {
// replaces current signature with new one
Expand Down Expand Up @@ -525,6 +537,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
this.draftDeskservice.shouldReturnToPreviousPage = false;

this.formGroup.patchValue(this.model, { emitEvent: false });
this.trackDefaultBcc(this.formGroup.controls.from.value);

this.htmlToggled();
}
Expand Down Expand Up @@ -696,6 +709,8 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
this.savingInProgress = true;

if (send) {
this.updateDefaultBcc(this.formGroup.controls.from.value, false);

let validemails = false;
validemails = isValidEmailArray(this.model.to);
if (!validemails) {
Expand Down Expand Up @@ -736,8 +751,7 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
}

// this.model.from should have a value (cos it defaults)
const from = this.draftDeskservice.fromsSubject.value.find(
(f) => this.model.from === f.nameAndAddress || this.model.from === f.email);
const from = this.identityForAddress(this.model.from);
let draft_from = this.model.from;
if (!from) {
console.log(`Compose: Could not find ${this.model.from} in ${this.draftDeskservice.fromsSubject.value}`);
Expand Down Expand Up @@ -915,4 +929,50 @@ export class ComposeComponent implements AfterViewInit, OnDestroy, OnInit {
s => !currentrecipients.find(r => r.address === s.address)
).slice(0, 10);
}

private updateDefaultBcc(selectedFromAddress: string, emitEvent = true): void {
if (!this.formGroup || !this.formGroup.controls.bcc) {
return;
}

const nextDefaultBcc = DraftFormModel.defaultBccRecipients(this.identityForAddress(selectedFromAddress));
const currentBcc: MailAddressInfo[] = this.model.bcc || [];
const bccWithoutPreviousDefault = currentBcc.filter((recipient) =>
!this.defaultBccRecipients.some((defaultRecipient) => this.sameEmailAddress(recipient, defaultRecipient)));
const mergedBcc = bccWithoutPreviousDefault.slice();

for (const defaultRecipient of nextDefaultBcc) {
if (!mergedBcc.some((recipient) => this.sameEmailAddress(recipient, defaultRecipient))) {
mergedBcc.push(defaultRecipient);
}
}

this.defaultBccRecipients = nextDefaultBcc;
this.model.bcc = mergedBcc;
this.hasBCC = mergedBcc.length > 0;
this.formGroup.controls.bcc.setValue(mergedBcc, { emitEvent });
}

private trackDefaultBcc(selectedFromAddress: string): void {
this.defaultBccRecipients = DraftFormModel.defaultBccRecipients(this.identityForAddress(selectedFromAddress));
}

private identityForAddress(selectedFromAddress: string): Identity {
if (!selectedFromAddress) {
return null;
}

const selectedAddresses = MailAddressInfo.parse(selectedFromAddress)
.map((address) => address.address.toLowerCase())
.filter((address) => address);
return this.draftDeskservice.fromsSubject.value.find((identity) =>
identity.nameAndAddress === selectedFromAddress ||
identity.email === selectedFromAddress ||
selectedAddresses.includes(identity.email.toLowerCase())
);
}

private sameEmailAddress(left: MailAddressInfo, right: MailAddressInfo): boolean {
return (left?.address || '').toLowerCase() === (right?.address || '').toLowerCase();
}
}
72 changes: 72 additions & 0 deletions src/app/compose/draftdesk.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,78 @@ Subject: Test subject <br />
expect(draft.isUnsaved()).toBe(false);
done();
});

it('Create: applies identity default bcc only to new drafts', (done) => {
let draft = DraftFormModel.create(
-1,
Identity.fromObject({'email': 'sender@runbox.com', 'default_bcc': 'archive@runbox.com'}),
'recipient@runbox.com',
'Test subject');

expect(draft.bcc.length).toBe(1);
expect(draft.bcc[0].address).toBe('archive@runbox.com');

draft = DraftFormModel.create(
12345,
Identity.fromObject({'email': 'sender@runbox.com', 'default_bcc': 'archive@runbox.com'}),
'recipient@runbox.com',
'Saved subject');

expect(draft.bcc).toEqual([]);
done();
});

it('Reply: applies identity default bcc from the selected From identity', (done) => {
const replydraft = DraftFormModel.reply({
headers: {
'message-id': 'reply-default-bcc',
},
from: [
{address: 'sender@runbox.com', name: 'Sender'}
],
to: [
{address: 'identity@runbox.com', name: 'Identity'}
],
date: mailDate,
subject: 'Test subject',
rawtext: 'blabla',
sanitized_html: '<p>blabla</p>',
},
[ Identity.fromObject({'email': 'identity@runbox.com', 'default_bcc': 'archive@runbox.com'})],
false,
false);

expect(replydraft.from).toBe('identity@runbox.com');
expect(replydraft.bcc.length).toBe(1);
expect(replydraft.bcc[0].address).toBe('archive@runbox.com');
done();
});

it('Forward: applies identity default bcc from the selected From identity', (done) => {
const draft = DraftFormModel.forward({
headers: {
'message-id': 'forward-default-bcc',
},
from: [
{address: 'sender@runbox.com', name: 'Sender'}
],
to: [
{address: 'identity@runbox.com', name: 'Identity'}
],
attachments: [],
date: mailDate,
subject: 'Test subject',
rawtext: 'blabla',
sanitized_html: '<p>blabla</p>',
},
[ Identity.fromObject({'email': 'identity@runbox.com', 'default_bcc': 'archive@runbox.com'})],
false);

expect(draft.from).toBe('identity@runbox.com');
expect(draft.bcc.length).toBe(1);
expect(draft.bcc[0].address).toBe('archive@runbox.com');
done();
});
});

describe('DraftDeskService', () => {
Expand Down
35 changes: 26 additions & 9 deletions src/app/compose/draftdesk.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export class DraftFormModel {
ret.from = fromAddress.email;
ret.mid = draftId;
ret.to = to ? MailAddressInfo.parse(to) : [];
if (ret.isUnsaved()) {
ret.bcc = DraftFormModel.defaultBccRecipients(fromAddress);
}
ret.subject = subject;
if (preview) {
// We create an element here because we want the plain text
Expand Down Expand Up @@ -126,7 +129,8 @@ export class DraftFormModel {
});
}
}
ret.setFromForResponse(mailObj, froms);
const selectedFrom = ret.setFromForResponse(mailObj, froms);
ret.bcc = DraftFormModel.defaultBccRecipients(selectedFrom);
ret.setSubjectForResponse(mailObj, 'Re: ');

const localTZ = moment.tz.guess();
Expand Down Expand Up @@ -166,9 +170,18 @@ export class DraftFormModel {
return ret;
}

public static defaultBccRecipients(identity: Identity): MailAddressInfo[] {
if (!identity || !identity.default_bcc || !identity.default_bcc.trim()) {
return [];
}
return MailAddressInfo.parse(identity.default_bcc)
.filter(recipient => recipient.address);
}

public static forward(mailObj, froms: Identity[], useHTML: boolean): DraftFormModel {
const ret = new DraftFormModel();
ret.setFromForResponse(mailObj, froms);
const selectedFrom = ret.setFromForResponse(mailObj, froms);
ret.bcc = DraftFormModel.defaultBccRecipients(selectedFrom);

const fwdFromNameStr = mailObj.from[0].name
? `"${mailObj.from[0].name}" &lt;${mailObj.from[0].address}&gt;`
Expand Down Expand Up @@ -216,15 +229,19 @@ ${mailObj.sanitized_html}`;
return false;
}

private setFromForResponse(mailObj, froms: Identity[]): void {
if (froms.length > 0) {
this.from = (
[].concat(mailObj.to || []).concat(mailObj.cc || []).find(
addr => froms.find(fromObj => fromObj.email === addr.address.toLowerCase())
) || { address: froms[0].email }
).address.toLowerCase();
private setFromForResponse(mailObj, froms: Identity[]): Identity {
if (froms && froms.length > 0) {
const recipientAddress = [].concat(mailObj.to || []).concat(mailObj.cc || []).find(
addr => froms.find(fromObj => fromObj.email.toLowerCase() === addr.address.toLowerCase())
);
const selectedFrom = recipientAddress
? froms.find(fromObj => fromObj.email.toLowerCase() === recipientAddress.address.toLowerCase())
: froms[0];
this.from = selectedFrom.email.toLowerCase();
return selectedFrom;
} else {
console.error('DraftDesk: No froms passed to setFromForResponse');
return null;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/app/profiles/profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class Identity {
name: string;
reference_type: string;
reply_to: string;
default_bcc: string;
signature: string;
type: string;
smtp_address: string;
Expand Down
11 changes: 11 additions & 0 deletions src/app/profiles/profiles.editor.modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@
</div>
</mat-form-field>

<mat-form-field class="form-field form-item">
<input matInput placeholder="Bcc sent messages to" name="default_bcc" [(ngModel)]="identity.default_bcc"
(ngModelChange)="onchange_field('default_bcc')" />
<div *ngIf="field_errors && field_errors.default_bcc">
<mat-hint>ie. archive@runbox.com</mat-hint>
<mat-error *ngFor="let error of field_errors.default_bcc; let i = index;">
{{error}}
</mat-error>
</div>
</mat-form-field>

<mat-form-field class="signature form-item">
<textarea matInput placeholder="Signature" [id]="selector" name="signature"
[(ngModel)]="identity.signature" (ngModelChange)="onchange_field('signature')"></textarea>
Expand Down
2 changes: 2 additions & 0 deletions src/app/profiles/profiles.editor.modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export class ProfilesEditorModalComponent implements OnDestroy {
email: identity.email,
from_name: identity.from_name,
reply_to: identity.reply_to,
default_bcc: identity.default_bcc,
signature: identity.signature,
smtp_address: identity.smtp_address,
smtp_port: identity.smtp_port,
Expand All @@ -130,6 +131,7 @@ export class ProfilesEditorModalComponent implements OnDestroy {
email: identity.email,
from_name: identity.from_name,
reply_to: identity.reply_to,
default_bcc: identity.default_bcc,
signature: identity.signature,
smtp_address: identity.smtp_address,
smtp_port: identity.smtp_port,
Expand Down