feat(mail): handle multiple mail identities

Fixes #768, fixes #4602
feature/mail-identities
Francis Lachapelle 2020-06-18 16:53:42 -04:00
parent 2651b5aa9d
commit f8aa338e64
24 changed files with 607 additions and 428 deletions

View File

@ -995,7 +995,7 @@ static NSString *userAgent = nil;
- (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail
{
NSDictionary *info, *attachment;
NSString *signature, *nl;
NSString *signature, *nl, *space;
SOGoUserDefaults *ud;
[sourceMail fetchCoreInfos];
@ -1031,8 +1031,9 @@ static NSString *userAgent = nil;
signature = [[self mailAccountFolder] signature];
if ([signature length])
{
nl = (isHTML ? @"<br/>" : @"\n");
[self setText: [NSString stringWithFormat: @"%@%@-- %@%@", nl, nl, nl, signature]];
nl = (isHTML ? @"<br />" : @"\n");
space = (isHTML ? @"&nbsp;" : @" ");
[self setText: [NSString stringWithFormat: @"%@%@--%@%@%@", nl, nl, space, nl, signature]];
}
attachment = [NSDictionary dictionaryWithObjectsAndKeys:
[sourceMail filenameForForward], @"filename",

View File

@ -1,5 +1,5 @@
/*
Copyright (C) 2009-2019 Inverse inc.
Copyright (C) 2009-2020 Inverse inc.
This file is part of SOGo.
@ -87,6 +87,7 @@ typedef enum {
forceActivation: (BOOL) forceActivation;
- (NSArray *) identities;
- (NSDictionary *) defaultIdentity;
- (NSString *) signature;
- (NSString *) encryption;

View File

@ -1,5 +1,5 @@
/*
Copyright (C) 2007-2019 Inverse inc.
Copyright (C) 2007-2020 Inverse inc.
This file is part of SOGo.
@ -667,13 +667,37 @@ static NSString *inboxFolderName = @"INBOX";
return identities;
}
- (NSDictionary *) defaultIdentity
{
NSDictionary *defaultIdentity, *currentIdentity;
unsigned int count, max;
defaultIdentity = nil;
[self identities];
max = [identities count];
for (count = 0; count < max; count++)
{
currentIdentity = [identities objectAtIndex: count];
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
{
defaultIdentity = currentIdentity;
break;
}
}
return defaultIdentity; // can be nil
}
- (NSString *) signature
{
NSDictionary *identity;
NSString *signature;
[self identities];
if ([identities count] > 0)
signature = [[identities objectAtIndex: 0] objectForKey: @"signature"];
identity = [self defaultIdentity];
if (identity)
signature = [identity objectForKey: @"signature"];
else
signature = nil;

View File

@ -234,14 +234,15 @@
- (NSString *) signature
{
NSString *signature, *mailSignature, *nl;
NSString *signature, *mailSignature, *nl, *space;
signature = [[sourceMail mailAccountFolder] signature];
if ([signature length])
{
nl = (htmlComposition ? @"<br/>" : @"\n");
mailSignature = [NSString stringWithFormat: @"-- %@%@", nl, signature];
nl = (htmlComposition ? @"<br />" : @"\n");
space = (htmlComposition ? @"&nbsp;" : @" ");
mailSignature = [NSString stringWithFormat: @"--%@%@%@", space, nl, signature];
}
else
mailSignature = @"";

View File

@ -342,18 +342,34 @@
- (NSMutableDictionary *) defaultIdentity
{
NSMutableDictionary *currentIdentity, *defaultIdentity;
NSEnumerator *identities;
NSDictionary *defaultAccount, *currentIdentity;
NSMutableDictionary *defaultIdentity;
NSArray *identities;
NSString *defaultEmail;
unsigned int count, max;
defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]];
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
defaultIdentity = nil;
identities = [[self allIdentities] objectEnumerator];
while (!defaultIdentity
&& (currentIdentity = [identities nextObject]))
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
defaultIdentity = currentIdentity;
identities = [defaultAccount objectForKey: @"identities"];
max = [identities count];
return defaultIdentity;
for (count = 0; count < max; count++)
{
currentIdentity = [identities objectAtIndex: count];
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
{
defaultIdentity = [NSMutableDictionary dictionaryWithDictionary: currentIdentity];
break;
}
else if ([[currentIdentity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
{
defaultIdentity = [NSMutableDictionary dictionaryWithDictionary: currentIdentity];
}
}
return defaultIdentity; // can be nil
}
- (SOGoDateFormatter *) dateFormatterInContext: (WOContext *) context
@ -626,19 +642,20 @@
- (void) _appendSystemMailAccountWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities
{
NSString *fullName, *replyTo, *imapLogin, *imapServer, *cImapServer, *signature,
*encryption, *scheme, *action, *query, *customEmail, *defaultEmail, *sieveServer;
NSString *fullName, *imapLogin, *imapServer, *cImapServer,
*encryption, *scheme, *action, *query, *customEmail, *sieveServer;
NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts, *security, *mailSettings;
NSNumber *port;
NSMutableArray *identities, *mails;
NSArray *delegators, *delegates;
NSURL *url, *cUrl;
unsigned int count, max, default_identity;
unsigned int count, max; //, default_identity;
NSInteger defaultPort;
NSUInteger index;
BOOL hasDefaultIdentity;
[self userDefaults];
[self userSettings];
[self userDefaults]; // set _defaults
[self userSettings]; // set _settings
mailSettings = [_settings objectForKey: @"Mail"];
mailAccount = [NSMutableDictionary new];
@ -715,89 +732,75 @@
}
// 5. Identities
defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]];
default_identity = 0;
identities = [NSMutableArray new];
[identities addObjectsFromArray: [_defaults mailIdentities]];
mails = [NSMutableArray arrayWithArray: [self allEmails]];
[mailAccount setObject: [mails objectAtIndex: 0] forKey: @"name"];
max = [identities count];
hasDefaultIdentity = NO;
fullName = [self cn];
if ([fullName length] == 0)
fullName = login;
replyTo = [_defaults mailReplyTo];
max = [mails count];
/* custom from */
if ([[self domainDefaults] mailCustomFromEnabled])
{
[self userDefaults];
customEmail = [_defaults mailCustomEmail];
fullName = [_defaults mailCustomFullName];
if ([customEmail length] > 0 || [fullName length] > 0)
{
if ([customEmail length] == 0)
customEmail = [mails objectAtIndex: 0];
else if ([fullName length] == 0)
{
// Custom email but default fullname; if the custom email is
// one of the user's emails, remove the duplicated entry
index = [mails indexOfObject: customEmail];
if (index != NSNotFound)
{
[mails removeObjectAtIndex: index];
max--;
}
}
if ([fullName length] == 0)
{
fullName = [self cn];
if ([fullName length] == 0)
fullName = login;
}
identity = [NSMutableDictionary new];
[identity setObject: customEmail forKey: @"email"];
[identity setObject: fullName forKey: @"fullName"];
if ([replyTo length] > 0)
[identity setObject: replyTo forKey: @"replyTo"];
signature = [_defaults mailSignature];
if (signature)
[identity setObject: signature forKey: @"signature"];
[identities addObject: identity];
if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
default_identity = [identities count]-1;
[identity release];
}
}
// Sanitize identities
for (count = 0; count < max; count++)
{
identity = [NSMutableDictionary new];
fullName = [self cn];
if (![fullName length])
fullName = login;
[identity setObject: fullName forKey: @"fullName"];
[identity setObject: [[mails objectAtIndex: count] stringByTrimmingSpaces]
forKey: @"email"];
if ([replyTo length] > 0)
[identity setObject: replyTo forKey: @"replyTo"];
signature = [_defaults mailSignature];
if (signature)
[identity setObject: signature forKey: @"signature"];
[identities addObject: identity];
if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
default_identity = [identities count]-1;
[identity release];
identity = [NSMutableDictionary dictionaryWithDictionary: [identities objectAtIndex: count]];
customEmail = [identity objectForKey: @"email"];
if ([customEmail length])
{
if (![[self domainDefaults] mailCustomFromEnabled])
{
// No custom from -- enforce a valid email
index = [mails indexOfObject: customEmail];
if (index == NSNotFound)
{
[identity setObject: [self systemEmail] forKey: @"email"];
}
}
}
else
{
// Email must be defined
[identity setObject: [self systemEmail] forKey: @"email"];
}
if (![[self domainDefaults] mailCustomFromEnabled])
{
// No custom from -- enforce a valid fullname and remove reply-to
[identity setObject: fullName forKey: @"fullName"];
[identity removeObjectForKey: @"replyTo"];
}
if (!appendDeletegatedIdentities)
{
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
}
if ([[identity objectForKey: @"isDefault"] boolValue])
{
if (hasDefaultIdentity || !appendDeletegatedIdentities)
[identity removeObjectForKey: @"isDefault"]; // only one possible default identity
else
hasDefaultIdentity = YES;
}
[identities replaceObjectAtIndex: count withObject: identity];
}
if (![identities count])
{
// Create a default identity
identity = [NSMutableDictionary dictionaryWithObjectsAndKeys:
fullName, @"fullName",
[self systemEmail], @"email", nil];
if (appendDeletegatedIdentities)
{
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"];
hasDefaultIdentity = YES;
}
else
{
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
}
[identities addObject: identity];
}
[[identities objectAtIndex: default_identity] setObject: [NSNumber numberWithBool: YES]
forKey: @"isDefault"];
/* identities from delegators */
if (appendDeletegatedIdentities)
@ -980,11 +983,19 @@
- (NSDictionary *) primaryIdentity
{
NSDictionary *defaultAccount;
NSArray *identities;
NSDictionary *defaultIdentity, *defaultAccount;
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
defaultIdentity = [self defaultIdentity];
return [[defaultAccount objectForKey: @"identities"] objectAtIndex: 0];
if (!defaultIdentity && [[self mailAccounts] count])
{
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
identities = [defaultAccount objectForKey: @"identities"];
defaultIdentity = [identities objectAtIndex: 0];
}
return defaultIdentity;
}
/* folders */

View File

@ -1,6 +1,6 @@
/* SOGoUserDefaults.h - this file is part of SOGo
*
* Copyright (C) 2011-2017 Inverse inc.
* Copyright (C) 2011-2020 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -157,21 +157,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
- (void) setMailReplyPlacement: (NSString *) newValue;
- (NSString *) mailReplyPlacement;
- (void) setMailSignature: (NSString *) newValue;
- (NSString *) mailSignature;
- (void) setMailSignaturePlacement: (NSString *) newValue;
- (NSString *) mailSignaturePlacement;
- (void) setMailCustomFullName: (NSString *) newValue;
- (NSString *) mailCustomFullName;
- (void) setMailCustomEmail: (NSString *) newValue;
- (NSString *) mailCustomEmail;
- (void) setMailReplyTo: (NSString *) newValue;
- (NSString *) mailReplyTo;
- (void) setAllowUserReceipt: (BOOL) allow;
- (BOOL) allowUserReceipt;
- (void) setUserReceiptNonRecipientAction: (NSString *) action;
@ -194,6 +182,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
- (void) setMailCertificateAlwaysEncrypt: (BOOL) newValue;
- (BOOL) mailCertificateAlwaysEncrypt;
- (void) setMailIdentities: (NSArray *) newIdentites;
- (NSArray *) mailIdentities;
- (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts;
- (NSArray *) auxiliaryMailAccounts;

View File

@ -20,6 +20,7 @@
#import <Foundation/NSDictionary.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <NGImap4/NSString+Imap4.h>
#import <NGObjWeb/WOApplication.h>
@ -114,33 +115,55 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return rc;
}
- (BOOL) _migrateSignature
- (BOOL) _migrateMailIdentities
{
BOOL rc;
NSString *signature;
NSArray *mailAccounts, *identities;
NSDictionary *identity;
NSArray *mailIdentities;
NSMutableDictionary *identity;
NSString *fullName, *email, *replyTo, *signature;
mailAccounts = [self arrayForKey: @"MailAccounts"];
if (mailAccounts)
mailIdentities = [self mailIdentities];
if (mailIdentities)
{
rc = YES;
if ([mailAccounts count] > 0)
{
identities = [[mailAccounts objectAtIndex: 0]
objectForKey: @"identifies"];
if ([identities count] > 0)
{
identity = [identities objectAtIndex: 0];
signature = [identity objectForKey: @"signature"];
if ([signature length])
[self setObject: signature forKey: @"MailSignature"];
}
}
[self removeObjectForKey: @"MailAccounts"];
rc = NO;
}
else
rc = NO;
{
identity = [NSMutableDictionary dictionaryWithCapacity: 4];
fullName = [self stringForKey: @"SOGoMailCustomFullName"];
email = [self stringForKey: @"SOGoMailCustomEmail"];
replyTo = [self stringForKey: @"SOGoMailReplyTo"];
signature = [self stringForKey: @"SOGoMailSignature"];
if ([fullName length])
[identity setObject: fullName forKey: @"fullName"];
if ([email length])
[identity setObject: email forKey: @"email"];
if ([replyTo length])
[identity setObject: replyTo forKey: @"replyTo"];
if ([signature length])
[identity setObject: signature forKey: @"signature"];
if ([identity count])
{
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"];
[self setMailIdentities: [NSArray arrayWithObject: identity]];
}
/**
* Keep old attributes for now because v2 doesn't handle identities
*
if (fullName)
[self removeObjectForKey: @"SOGoMailCustomFullName"];
if (email)
[self removeObjectForKey: @"SOGoMailCustomEmail"];
if (replyTo)
[self removeObjectForKey: @"SOGoMailReplyTo"];
if (signature)
[self removeObjectForKey: @"SOGoMailSignature"];
*/
rc = YES;
}
return rc;
}
@ -211,7 +234,8 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
/* we must not use a boolean operation, otherwise subsequent migrations will
not be invoked in the case where rc = YES. */
return ([self _migrateLastModule]
| [self _migrateSignature]
// | [self _migrateSignature]
| [self _migrateMailIdentities]
| [self _migrateCalendarCategories]
| [self migrateOldDefaultsWithDictionary: migratedKeys]
| [super migrate]);
@ -629,18 +653,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return [self stringForKey: @"SOGoMailReplyPlacement"];
}
- (void) setMailSignature: (NSString *) newValue
{
if ([newValue length] == 0)
newValue = nil;
[self setObject: newValue forKey: @"SOGoMailSignature"];
}
- (NSString *) mailSignature
{
return [self stringForKey: @"SOGoMailSignature"];
}
- (void) setMailSignaturePlacement: (NSString *) newValue
{
[self setObject: newValue forKey: @"SOGoMailSignaturePlacement"];
@ -660,45 +672,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return signaturePlacement;
}
- (void) setMailCustomFullName: (NSString *) newValue
{
if ([newValue length] == 0)
newValue = nil;
[self setObject: [newValue stringByTrimmingSpaces]
forKey: @"SOGoMailCustomFullName"];
}
- (NSString *) mailCustomFullName
{
return [self stringForKey: @"SOGoMailCustomFullName"];
}
- (void) setMailCustomEmail: (NSString *) newValue
{
if ([newValue length] == 0)
newValue = nil;
[self setObject: [newValue stringByTrimmingSpaces]
forKey: @"SOGoMailCustomEmail"];
}
- (NSString *) mailCustomEmail
{
return [self stringForKey: @"SOGoMailCustomEmail"];
}
- (void) setMailReplyTo: (NSString *) newValue
{
if ([newValue length] == 0)
newValue = nil;
[self setObject: [newValue stringByTrimmingSpaces]
forKey: @"SOGoMailReplyTo"];
}
- (NSString *) mailReplyTo
{
return [self stringForKey: @"SOGoMailReplyTo"];
}
- (void) setAllowUserReceipt: (BOOL) allow
{
[self setBool: allow forKey: @"SOGoMailReceiptAllow"];
@ -784,6 +757,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return [self boolForKey: @"SOGoMailCertificateAlwaysEncrypt"];
}
- (void) setMailIdentities: (NSArray *) newIdentites
{
[self setObject: newIdentites forKey: @"SOGoMailIdentities"];
}
- (NSArray *) mailIdentities
{
return [self arrayForKey: @"SOGoMailIdentities"];
}
- (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts
{
[self setObject: newAccounts forKey: @"AuxiliaryMailAccounts"];

View File

@ -674,18 +674,18 @@
return [NSString stringWithFormat: @"urn:uuid:%@", nameInContainer];
}
- (NSException *) setDavSignature: (NSString *) newSignature
{
SOGoUserDefaults *ud;
SOGoUser *user;
// - (NSException *) setDavSignature: (NSString *) newSignature
// {
// SOGoUserDefaults *ud;
// SOGoUser *user;
user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
ud = [user userDefaults];
[ud setMailSignature: newSignature];
[ud synchronize];
// user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
// ud = [user userDefaults];
// [ud setMailSignature: newSignature];
// [ud synchronize];
return nil;
}
// return nil;
// }
#warning unused stub
- (BOOL) collectionDavKey: (NSString *) key

View File

@ -104,7 +104,7 @@
"To" = "To";
"Cc" = "Cc";
"Bcc" = "Bcc";
"Reply-To" = "Reply-To";
"Reply-To" = "Reply-To";
"Add address" = "Add address";
"Body" = "Body";
"Open" = "Open";
@ -121,6 +121,7 @@
"Edit Draft..." = "Edit Draft...";
"Load Images" = "Load Images";
"Return Receipt" = "Return Receipt";
"Choose which identity to send this message from" = "Choose which identity to send this message from";
"The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?" = "The sender of this message has asked to be notified when you read this message. Do you wish to notify the sender?";
"Return Receipt (displayed) - %@"= "Return Receipt (displayed) - %@";
"This is a Return Receipt for the mail that you sent to %@.\n\nNote: This Return Receipt only acknowledges that the message was displayed on the recipient's computer. There is no guarantee that the recipient has read or understood the message contents." = "This is a Return Receipt for the mail that you sent to %@.\n\nNote: This Return Receipt only acknowledges that the message was displayed on the recipient's computer. There is no guarantee that the recipient has read or understood the message contents.";

View File

@ -100,7 +100,7 @@
- (WOResponse *) composeAction
{
NSString *value, *signature, *nl;
NSString *value, *signature, *nl, *space;
SOGoDraftObject *newDraftMessage;
NSMutableDictionary *headers;
NSDictionary *data;
@ -108,7 +108,7 @@
SOGoDraftsFolder *drafts;
SOGoUserDefaults *ud;
id mailTo;
BOOL save;
BOOL save, isHTML;
drafts = [[self clientObject] draftsFolderInContext: context];
newDraftMessage = [drafts newDraft];
@ -142,9 +142,11 @@
{
ud = [[context activeUser] userDefaults];
[newDraftMessage setIsHTML: [[ud mailComposeMessageType] isEqualToString: @"html"]];
nl = ([newDraftMessage isHTML] ? @"<br/>" : @"\n");
isHTML = [newDraftMessage isHTML];
nl = (isHTML? @"<br />" : @"\n");
space = (isHTML ? @"&nbsp;" : @" ");
[newDraftMessage setText: [NSString stringWithFormat: @"%@%@-- %@%@", nl, nl, nl, signature]];
[newDraftMessage setText: [NSString stringWithFormat: @"%@%@--%@%@%@", nl, nl, space, nl, signature]];
save = YES;
}
if (save)

View File

@ -280,7 +280,7 @@ static NSArray *infoKeys = nil;
}
if (!from)
{
from = [self _emailFromIdentity: [identities objectAtIndex: 0]];
from = [self _emailFromIdentity: [[context activeUser] defaultIdentity]];
[from retain];
}
}
@ -301,19 +301,11 @@ static NSArray *infoKeys = nil;
//
if ([[[[self clientObject] mailAccountFolder] nameInContainer] intValue] == 0)
{
SOGoUserDefaults *ud;
ud = [[context activeUser] userDefaults];
value = [ud mailReplyTo];
value = [[[context activeUser] defaultIdentity] objectForKey: @"replyTo"];
}
else
{
NSArray *identities;
identities = [[[self clientObject] mailAccountFolder] identities];
if ([identities count])
value = [[identities objectAtIndex: 0] objectForKey: @"replyTo"];
value = [[[[self clientObject] mailAccountFolder] defaultIdentity] objectForKey: @"replyTo"];
}
return value;
@ -757,11 +749,12 @@ static NSArray *infoKeys = nil;
[self setSourceFolder: [co sourceFolder]];
data = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[self from], @"from",
[self localeCode], @"locale",
[NSNumber numberWithBool: [self isHTML]], @"isHTML",
text, @"text",
nil];
if ((value = [self from]))
[data setObject: value forKey: @"from"];
if ((value = [self replyTo]))
[data setObject: value forKey: @"replyTo"];
if ((value = [self to]))

View File

@ -212,6 +212,9 @@
"Email" = "Email";
"Reply To Email" = "Reply To Email";
"Signature" = "Signature";
"Identities" = "Identities";
"Default Identity" = "Default Identity";
"New Identity" = "New Identity";
"(Click to create)" = "(Click to create)";
"Please enter your signature below" = "Please enter your signature below";
"Please specify a valid sender address." = "Please specify a valid sender address.";

View File

@ -426,10 +426,13 @@ static SoProduct *preferencesProduct = nil;
}
if (account)
[accounts insertObject: account atIndex: 0];
[values setObject: accounts forKey: @"AuxiliaryMailAccounts"];
// Ignore parameters as they are injected in the system mail account ([SOGoUser mailAccounts])
[values removeObjectForKey: @"SOGoMailIdentities"];
[values removeObjectForKey: @"SOGoMailCertificate"];
[values removeObjectForKey: @"SOGoMailCertificateAlwaysSign"];
[values removeObjectForKey: @"SOGoMailCertificateAlwaysEncrypt"];
[values setObject: accounts forKey: @"AuxiliaryMailAccounts"];
// Add the domain's default vacation subject if user has not specified a custom subject
vacationOptions = [defaults vacationOptions];

View File

@ -1110,63 +1110,6 @@ static NSArray *reminderValues = nil;
return [[user domainDefaults] mailAuxiliaryUserAccountsEnabled];
}
//
// Used internally
//
- (void) _extractMainIdentity: (NSDictionary *) identity
inDictionary: (NSMutableDictionary *) target
{
/* We perform some validation here as we have no guaranty on the input
validity. */
NSString *value;
if ([identity isKindOfClass: [NSDictionary class]])
{
value = [identity objectForKey: @"signature"];
if (value)
[target setObject: value forKey: @"SOGoMailSignature"];
else
[target removeObjectForKey: @"SOGoMailSignature"];
if (mailCustomFromEnabled)
{
value = [[identity objectForKey: @"email"]
stringByTrimmingSpaces];
/* We make sure that the "custom" value is different from the system email */
if ([value length] == 0
|| [[user systemEmail] isEqualToString: value])
value = nil;
if (value)
[target setObject: value forKey: @"SOGoMailCustomEmail"];
else
[target removeObjectForKey: @"SOGoMailCustomEmail"];
value = [[identity objectForKey: @"fullName"]
stringByTrimmingSpaces];
if ([value length] == 0
|| [[user cn] isEqualToString: value])
value = nil;
if (value)
[target setObject: value forKey: @"SOGoMailCustomFullName"];
else
[target removeObjectForKey: @"SOGoMailCustomFullName"];
}
value = [[identity objectForKey: @"replyTo"]
stringByTrimmingSpaces];
if (value && [value length] > 0)
[target setObject: value forKey: @"SOGoMailReplyTo"];
else
[target removeObjectForKey: @"SOGoMailReplyTo"];
}
}
//
// Used internally
//
@ -1229,20 +1172,6 @@ static NSArray *reminderValues = nil;
}
}
//
// Used internally
//
- (void) _extractMainCustomFrom: (NSDictionary *) account
{
}
//
// Used internally
//
- (void) _extractMainReplyTo: (NSDictionary *) account
{
}
//
// Used internally
//
@ -1259,7 +1188,7 @@ static NSArray *reminderValues = nil;
if (!knownKeys)
{
knownKeys = [NSArray arrayWithObjects: @"fullName", @"email",
@"signature", @"replyTo", nil];
@"signature", @"replyTo", @"isDefault", nil];
[knownKeys retain];
}
@ -1367,9 +1296,8 @@ static NSArray *reminderValues = nil;
if ([account isKindOfClass: [NSDictionary class]])
{
identities = [account objectForKey: @"identities"];
if ([identities isKindOfClass: [NSArray class]]
&& [identities count] > 0)
[self _extractMainIdentity: [identities objectAtIndex: 0] inDictionary: target];
if ([self _validateAccountIdentities: identities])
[target setObject: identities forKey: @"SOGoMailIdentities"];
[self _extractMainReceiptsPreferences: [account objectForKey: @"receipts"] inDictionary: target];
[self _extractMainSecurityPreferences: [account objectForKey: @"security"] inDictionary: target];
}
@ -1575,10 +1503,6 @@ static NSArray *reminderValues = nil;
if ([accounts count] > 0)
{
// The first account is the main system account. The following mapping is required:
// - identities[0].signature => SOGoMailSignature
// - identities[0].email => SOGoMailCustomEmail
// - identities[0].fullName => SOGoMailCustomFullName
// - identities[0].replyTo => SOGoMailReplyTo
// - receipts.receiptAction => SOGoMailReceiptAllow
// - receipts.receiptNonRecipientAction => SOGoMailReceiptNonRecipientAction
// - receipts.receiptOutsideDomainAction => SOGoMailReceiptOutsideDomainAction

View File

@ -27,10 +27,6 @@
protectedBy = "View";
pageName = "UIxFilterEditor";
};
identities = {
protectedBy = "View";
pageName = "UIxIdentities";
};
activeExternalSieveScripts = {
protectedBy = "View";
pageName = "UIxJSONPreferences";

View File

@ -28,14 +28,35 @@
<sg-avatar-image class="hide-xs"
sg-email="editor.message.editable.from"
size="32">person</sg-avatar-image>
<md-input-container class="sg-no-wrap">
<label><var:string label:value="From"/></label>
<md-select name="from"
ng-model="editor.message.editable.from">
<md-option ng-value="identity" ng-repeat="identity in editor.identities track by $index">{{identity}}</md-option>
</md-select>
</md-input-container>
<div flex="flex"><!-- spacer --></div>
<md-autocomplete
class="md-margin md-default-theme md-bg md-hue-1" flex="flex"
required="required"
md-input-name="from"
md-require-match="true"
md-selected-item="editor.fromIdentity"
md-search-text="editor.identitySearchText"
md-selected-item-change="editor.setFromIdentity(identity)"
md-items="identity in editor.identitySearch(editor.identitySearchText)"
md-item-text="identity.full"
md-min-length="0"
md-escape-options="clear"
label:placeholder="Choose which identity to send this message from"
md-menu-class="md-2-line">
<md-item-template>
<div class="sg-tile-content">
<div class="sg-md-subhead">
<span md-highlight-text="editor.identitySearchText"
md-highlight-flags="gi">{{ identity.full }}</span>
</div>
<div class="sg-md-body">
<i ng-bind-html="editor.account.getTextSignature($index)"><!-- signature --></i>
</div>
</div>
</md-item-template>
<div ng-messages="messageForm.from.$error">
<div ng-message="required">This field is required</div>
</div>
</md-autocomplete>
<md-button class="sg-icon-button" ng-click="editor.send()"
ng-disabled="!(editor.message.editable.to.length > 0 || editor.message.editable.cc.length > 0 || editor.message.editable.bcc.length > 0) || editor.uploader.isUploading || messageForm.$invalid || messageForm.$submitted"
sg-ripple-click="mailEditor">

View File

@ -94,45 +94,100 @@
</md-input-container>
</div>
<md-input-container class="md-block md-flex">
<label><var:string label:value="Full Name"/></label>
<input type="text" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="$AccountDialogController.account.identities[0].fullName"/>
</md-input-container>
<div layout="row">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="Email"/></label>
<input type="email" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="$AccountDialogController.account.identities[0].email"/>
</md-input-container>
<md-input-container class="md-block" flex="50"
ng-hide="$AccountDialogController.customFromIsReadonly()">
<label><var:string label:value="Reply To Email"/></label>
<input type="email"
autocomplete="off"
ng-model="$AccountDialogController.account.identities[0].replyTo"/>
</md-input-container>
<!-- identities -->
<div class="pseudo-input-container" ng-if="$AccountDialogController.hasIdentities()">
<label class="pseudo-input-label"><var:string label:value="Identities"/></label>
<md-card ng-repeat="identity in $AccountDialogController.account.identities | filter:$AccountDialogController.isEditableIdentity"
class="sg-collapsed"
ng-class="{ 'sg-expanded': $index == $AccountDialogController.selectedIdentity }">
<a class="md-flex md-button" ng-click="$AccountDialogController.selectIdentity($index)">
<div layout="row" layout-align="start center">
<div class="card-picture">
<sg-avatar-image class="md-avatar"
sg-email="identity.email"
size="40">person</sg-avatar-image>
</div>
<div class="sg-tile-content">
<div class="sg-md-subhead">
<div>
<span ng-bind="identity.fullName"><!-- fullName --></span>
<span ng-show="identity.email">
<var:entity const:name="nbsp"/>
<var:entity const:name="lt"/><span ng-bind="identity.email"><!-- email --></span><var:entity const:name="gt"/>
</span>
</div>
</div>
<div class="sg-md-body">
<i ng-bind-html="$AccountDialogController.account.getTextSignature($index)"><!-- signature --></i>
</div>
</div>
<md-button class="sg-icon-button" type="button"
ng-click="$AccountDialogController.removeIdentity($index)"
ng-show="$AccountDialogController.canRemoveIdentity($index)">
<md-icon>delete</md-icon>
</md-button>
<md-button class="sg-icon-button"
label:aria-label="Default Identity"
ng-click="$AccountDialogController.setDefaultIdentity($event, $index)">
<md-icon ng-class="{ 'md-warn md-hue-2': identity.isDefault }">{{ identity.isDefault ? 'favorite' : 'favorite_border' }}</md-icon>
</md-button>
</div>
</a>
<md-card-content ng-show="$index == $AccountDialogController.selectedIdentity">
<md-input-container class="md-block md-flex">
<label><var:string label:value="Full Name"/></label>
<input type="text" required="required"
ng-disabled="$AccountDialogController.customFromIsReadonly()"
ng-model="identity.fullName"/>
</md-input-container>
<md-autocomplete
class="md-block" required="required"
md-no-cache="true"
md-search-text="identity.email"
md-items="address in $AccountDialogController.filterEmailAddresses(identity.email)"
md-escape-options="clear"
md-require-match="$AccountDialogController.customFromIsReadonly()"
md-min-length="0"
label:md-floating-label="Email">
<md-item-template>
<span md-highlight-text="identity.email"
md-highlight-flags="gi">{{ address }}</span>
</md-item-template>
</md-autocomplete>
<md-input-container class="md-block"
ng-hide="$AccountDialogController.customFromIsReadonly()">
<label><var:string label:value="Reply To Email"/></label>
<input type="email"
autocomplete="off"
ng-model="identity.replyTo"/>
</md-input-container>
<md-input-container
class="md-block md-flex"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
<label><var:string label:value="Signature"/></label>
<textarea ng-model="identity.signature"><!-- signature --></textarea>
</md-input-container>
<div class="pseudo-input-container"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
<sg-ckeditor
class="ng-cloak"
config="$AccountDialogController.ckConfig"
ck-margin="8px"
ng-model="identity.signature"><!-- HTML editor --></sg-ckeditor>
</div>
</md-card-content>
</md-card>
<div layout="row" layout-align="end center">
<md-button type="button"
ng-click="$AccountDialogController.addIdentity()"
label:aria-label="New Identity">
<var:string label:value="New Identity"/>
</md-button>
</div>
</div>
<!-- To switch between a simple text editor and the CK/HTML editor, we use a ng-if and not
a ng-class as it doesn't get initialized by the ckEditor class directive -->
<md-input-container class="md-block md-flex"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
<label><var:string label:value="Signature"/></label>
<textarea
ng-model="$AccountDialogController.account.identities[0].signature"><!-- plain text editor --></textarea>
</md-input-container>
<div class="pseudo-input-container"
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
<sg-ckeditor
config="$AccountDialogController.ckConfig"
ck-margin="8px"
ng-model="$AccountDialogController.account.identities[0].signature"><!-- HTML editor --></sg-ckeditor>
</div>
<div class="sg-padded--bottom"><!-- spacer --></div>
<md-input-container class="md-block md-input-has-value">
<label><var:string label:value="When I receive a request for a return receipt"/></label>

@ -1 +1 @@
Subproject commit bf5aa3511ef8f849d54818ce1e2ac37f179f6f23
Subproject commit 3746148548a304f664c09fcad16ab48a42e7810f

View File

@ -323,6 +323,7 @@
}
this.$onDestroy = function () {
editorElement.classList.add('ng-cloak');
editor.destroy();
}
@ -349,12 +350,19 @@
});
}
// vm.ngModelCtrl.$render();
editorElement.classList.remove('ng-cloak');
vm.ngModelCtrl.$render();
}
function onEditorChange () {
var html = editor.getData();
var text = editor.document.getBody().getText();
var body = editor.document.getBody();
var text;
if (_.isEmpty(body))
return;
else
text = body.getText();
if (text === '\n') {
text = '';

View File

@ -13,10 +13,16 @@
if (typeof futureAccountData.then !== 'function') {
angular.extend(this, futureAccountData);
_.forEach(this.identities, function(identity) {
if (identity.fullName)
if (identity.fullName && identity.email)
identity.full = identity.fullName + ' <' + identity.email + '>';
else
else if (identity.email)
identity.full = '<' + identity.email + '>';
else
identity.full = '';
if (identity.signature) {
var element = angular.element('<div>' + identity.signature + '</div>');
identity.textSignature = _.map(element.contents(), 'textContent').join(' ').trim();
}
});
Account.$log.debug('Account: ' + JSON.stringify(futureAccountData, undefined, 2));
}
@ -315,6 +321,27 @@
});
};
/**
* @function getTextSignature
* @memberof Account.prototype
* @desc Create a plain text representation of the signature for the specified identity index.
* @returns a plain text version of the signature
*/
Account.prototype.getTextSignature = function(index) {
if (index < this.identities.length) {
var identity = this.identities[index];
if (identity.signature) {
var element = angular.element('<div>' + identity.signature + '</div>');
identity.textSignature = _.map(element.contents(), 'textContent').join(' ').trim();
} else {
identity.textSignature = '';
}
return identity.textSignature;
} else {
throw Error('Index of identity is out of range');
}
};
/**
* @function $certificate
* @memberof Account.prototype
@ -451,4 +478,28 @@
});
};
/**
* @function $omit
* @memberof Account.prototype
* @desc Return a sanitized object used to send to the server.
* @return an object literal copy of the Account instance
*/
Account.prototype.$omit = function () {
var account = {}, identities = [];
angular.forEach(this, function(value, key) {
if (key != 'constructor' && key !='identities' && key[0] != '$') {
account[key] = angular.copy(value);
}
});
_.forEach(this.identities, function (identity) {
if (!identity.isReadOnly)
identities.push(_.pick(identity, ['email', 'fullName', 'replyTo', 'signature', 'isDefault']));
});
account.identities = identities;
return account;
};
})();

View File

@ -12,7 +12,7 @@
this.$onInit = function() {
$scope.isPopup = stateParent.isPopup;
this.addRecipient = addRecipient;
this.account = stateAccount;
this.autocomplete = {to: {}, cc: {}, bcc: {}};
this.autosave = null;
this.autosaveDrafts = autosaveDrafts;
@ -21,7 +21,9 @@
this.isFullscreen = false;
this.hideBcc = (stateMessage.editable.bcc.length === 0);
this.hideCc = (stateMessage.editable.cc.length === 0);
this.identities = _.uniq(_.map(stateAccount.identities, 'full'));
this.identities = stateAccount.identities;
this.fromIdentity = stateMessage.editable.from;
this.identitySearchText = '';
this.message = stateMessage;
this.recipientSeparatorKeys = [
$mdConstant.KEY_CODE.ENTER,
@ -283,11 +285,11 @@
});
}
function addRecipient(contact, field) {
this.addRecipient = function (contact, field) {
var recipients, recipient, list, i, address;
var emailRE = /([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)/i;
recipients = vm.message.editable[field];
recipients = this.message.editable[field];
if (angular.isString(contact)) {
// Examples that are handled:
@ -351,11 +353,66 @@
return recipient;
else
return null;
}
};
this.setFromIdentity = function (identity) {
var node, children, nl, space, signature, previousIdentity;
if (identity)
this.message.editable.from = identity.full;
if (this.composeType == "html") {
nl = '<br />';
space = '&nbsp;';
} else {
nl = '\n';
space = ' ';
}
if (identity && identity.signature)
signature = nl + nl + '--' + space + nl + identity.signature;
else
signature = '';
previousIdentity = _.find(this.identities, function (currentIdentity, index) {
if (currentIdentity.signature) {
var currentSignature = new RegExp(nl + ' ?' + nl + '--' + space + nl + currentIdentity.signature);
if (vm.message.editable.text.search(currentSignature) >= 0) {
vm.message.editable.text = vm.message.editable.text.replace(currentSignature, signature);
return true;
}
}
return false;
});
if (!previousIdentity && signature.length > 0) {
// Must place signature at proper place
if (!this.isNew() && this.replyPlacement == 'above') {
var quotedMessageIndex = this.message.editable.text.search(new RegExp(nl + '.+?:( ?' + nl + '){2}(> |<blockquote type="cite")'));
if (quotedMessageIndex >= 0) {
this.message.editable.text =
this.message.editable.text.slice(0, quotedMessageIndex) +
signature +
this.message.editable.text.slice(quotedMessageIndex);
} else {
this.message.editable.text = signature + this.message.editable.text;
}
} else {
this.message.editable.text += signature;
}
}
};
this.identitySearch = function (query) {
var q = query ? query : '';
return _.filter(stateAccount.identities, function(identity) {
return identity.full.toLowerCase().indexOf(q.toLowerCase()) >= 0;
});
};
this.expandGroup = function(contact, field) {
var recipients, i, j;
recipients = vm.message.editable[field];
recipients = this.message.editable[field];
i = recipients.indexOf(contact);
recipients.splice(i, 1);
for (j = 0; j < contact.members.length; j++) {
@ -391,8 +448,7 @@
if (this.firstFocus) {
onCompletePromise().then(function(element) {
var textContent = angular.element(textArea).val(),
hasSignature = (Preferences.defaults.SOGoMailSignature &&
Preferences.defaults.SOGoMailSignature.length > 0),
hasSignature = /\n-- \n/.test(textContent),
signatureLength = 0,
sigLimit,
caretPosition;
@ -402,8 +458,9 @@
element.find('md-dialog-content')[0].scrollTop = 0;
}
else {
// Search for signature starting from bottom
if (hasSignature) {
sigLimit = textContent.lastIndexOf("--");
sigLimit = textContent.lastIndexOf("-- ");
if (sigLimit > -1)
signatureLength = (textContent.length - sigLimit);
}
@ -447,7 +504,7 @@
if (x === null) {
break;
}
if (x.getText() == '--') {
if (/--(%20|%A0|%C2%A0)/.test(encodeURI(x.getText()))) {
node = x.getPrevious().getPrevious();
break;
}

View File

@ -7,23 +7,23 @@
/**
* @ngInject
*/
AccountDialogController.$inject = ['$timeout', '$mdDialog', 'FileUploader', 'Dialog', 'sgSettings', 'Account', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled'];
function AccountDialogController($timeout, $mdDialog, FileUploader, Dialog, Settings, Account, defaults, account, accountId, mailCustomFromEnabled) {
var vm = this,
accountObject = new Account({ id: accountId, security: account.security });
AccountDialogController.$inject = ['$timeout', '$window', '$mdConstant', '$mdDialog', 'FileUploader', 'Dialog', 'sgSettings', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled'];
function AccountDialogController($timeout, $window, $mdConstant, $mdDialog, FileUploader, Dialog, Settings, defaults, account, accountId, mailCustomFromEnabled) {
var vm = this;
vm.defaultPort = 143;
vm.defaults = defaults;
vm.account = account;
vm.accountId = accountId;
vm.customFromIsReadonly = customFromIsReadonly;
vm.onBeforeUploadCertificate = onBeforeUploadCertificate;
vm.removeCertificate = removeCertificate;
vm.importCertificate = importCertificate;
vm.cancel = cancel;
vm.save = save;
vm.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./;
vm.ckConfig = {
this.defaultPort = 143;
this.defaults = defaults;
this.account = account;
this.accountId = accountId;
this.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./;
this.addressesSearchText = '';
this.emailSeparatorKeys = [
$mdConstant.KEY_CODE.ENTER,
$mdConstant.KEY_CODE.TAB,
$mdConstant.KEY_CODE.COMMA,
$mdConstant.KEY_CODE.SEMICOLON
];
this.ckConfig = {
'autoGrow_minHeight': 70,
'toolbar': [['Bold', 'Italic', '-', 'Link',
'Font','FontSize','-','TextColor',
@ -31,14 +31,14 @@
language: defaults.LocaleCode
};
if (!vm.account.encryption)
vm.account.encryption = "none";
else if (vm.account.encryption == "ssl")
vm.defaultPort = 993;
if (!this.account.encryption)
this.account.encryption = "none";
else if (this.account.encryption == "ssl")
this.defaultPort = 993;
_loadCertificate();
vm.uploader = new FileUploader({
this.uploader = new FileUploader({
url: [Settings.activeUser('folderURL') + 'Mail', accountId, 'importCertificate'].join('/'),
autoUpload: false,
queueLimit: 1,
@ -58,9 +58,65 @@
}
});
this.hasIdentities = function () {
return _.filter(this.account.identities, vm.isEditableIdentity).length > 0;
};
this.isEditableIdentity = function (identity) {
return !identity.isReadOnly;
};
this.selectIdentity = function (index) {
if (this.selectedIdentity == index) {
this.selectedIdentity = null;
} else {
this.selectedIdentity = index;
}
};
this.setDefaultIdentity = function ($event, $index) {
_.forEach(this.account.identities, function(identity, i) {
if (i == $index)
identity.isDefault = !identity.isDefault;
else
delete identity.isDefault;
});
$event.stopPropagation();
return false;
};
this.canRemoveIdentity = function (index) {
return (index == this.selectedIdentity) && (this.account.identities.length > 1);
};
this.removeIdentity = function (index) {
this.account.identities.splice(index, 1);
this.selectedIdentity = null;
};
this.addIdentity = function () {
var firstReadonlyIndex = _.findIndex(this.account.identities, { isReadOnly: 1 });
var identity = {};
if (this.customFromIsReadonly())
identity.fullName = this.account.identities[0].fullName;
this.account.identities.splice(Math.max(firstReadonlyIndex, 0), 0, identity);
this.selectedIdentity = firstReadonlyIndex;
};
this.showCkEditor = function ($index) {
return this.selectedIdentity == $index && this.defaults.SOGoMailComposeMessageType == 'html';
};
this.filterEmailAddresses = function ($query) {
return _.filter($window.defaultEmailAddresses, function (address) {
return address.toLowerCase().indexOf($query.toLowerCase()) >= 0;
});
};
function _loadCertificate() {
if (vm.account.security && vm.account.security.hasCertificate)
accountObject.$certificate().then(function(crt) {
vm.account.$certificate().then(function(crt) {
vm.certificate = crt;
}, function() {
delete vm.account.security.hasCertificate;
@ -73,35 +129,33 @@
return isP12File;
}
function customFromIsReadonly() {
this.customFromIsReadonly = function () {
if (accountId > 0)
return false;
return !mailCustomFromEnabled;
}
};
function importCertificate() {
vm.uploader.queue[0].formData = [{ password: vm.certificatePassword }];
vm.uploader.uploadItem(0);
}
this.importCertificate = function () {
this.uploader.queue[0].formData = [{ password: this.certificatePassword }];
this.uploader.uploadItem(0);
};
function onBeforeUploadCertificate(form) {
vm.form = form;
vm.uploader.clearQueue();
}
this.onBeforeUploadCertificate = function (form) {
this.form = form;
this.uploader.clearQueue();
};
function removeCertificate() {
accountObject.$removeCertificate().then(function() {
delete vm.account.security.hasCertificate;
});
}
this.removeCertificate = function () {
this.account.$removeCertificate();
};
function cancel() {
this.cancel = function () {
$mdDialog.cancel();
}
};
function save() {
this.save = function () {
$mdDialog.hide();
}
};
}
angular

View File

@ -98,28 +98,25 @@
};
this.addMailAccount = function(ev, form) {
var account;
var account, index;
this.preferences.defaults.AuxiliaryMailAccounts.push({});
account = _.last(this.preferences.defaults.AuxiliaryMailAccounts);
angular.extend(account,
{
isNew: true,
name: "",
identities: [
{
fullName: "",
email: ""
}
],
receipts: {
receiptAction: "ignore",
receiptNonRecipientAction: "ignore",
receiptOutsideDomainAction: "ignore",
receiptAnyAction: "ignore"
}
});
account = new Account({
isNew: true,
name: "",
identities: [
{
fullName: "",
email: ""
}
],
receipts: {
receiptAction: "ignore",
receiptNonRecipientAction: "ignore",
receiptOutsideDomainAction: "ignore",
receiptAnyAction: "ignore"
}
});
index = this.preferences.defaults.AuxiliaryMailAccounts.length;
$mdDialog.show({
controller: 'AccountDialogController',
@ -133,14 +130,13 @@
mailCustomFromEnabled: $window.mailCustomFromEnabled
}
}).then(function() {
vm.preferences.defaults.AuxiliaryMailAccounts.push(account.$omit());
form.$setDirty();
}).catch(function() {
vm.preferences.defaults.AuxiliaryMailAccounts.pop();
});
};
this.editMailAccount = function(event, index, form) {
var account = this.preferences.defaults.AuxiliaryMailAccounts[index];
var account = new Account(this.preferences.defaults.AuxiliaryMailAccounts[index]);
$mdDialog.show({
controller: 'AccountDialogController',
controllerAs: '$AccountDialogController',
@ -153,10 +149,8 @@
mailCustomFromEnabled: $window.mailCustomFromEnabled
}
}).then(function() {
vm.preferences.defaults.AuxiliaryMailAccounts[index] = account;
vm.preferences.defaults.AuxiliaryMailAccounts[index] = account.$omit();
form.$setDirty();
}, function() {
// Cancel
});
};

View File

@ -80,6 +80,11 @@ $list-item-dense-line-height: 1.05 !default;
}
}
md-autocomplete .ng-invalid:not(.ng-empty) {
text-decoration: underline;
color: $colorRedA700 !important;
}
@media (max-width: $layout-breakpoint-xs) {
// Enlarge the autocompletion menu on small devices to fit the entire screen
.md-autocomplete-suggestions-container {