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 - (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail
{ {
NSDictionary *info, *attachment; NSDictionary *info, *attachment;
NSString *signature, *nl; NSString *signature, *nl, *space;
SOGoUserDefaults *ud; SOGoUserDefaults *ud;
[sourceMail fetchCoreInfos]; [sourceMail fetchCoreInfos];
@ -1031,8 +1031,9 @@ static NSString *userAgent = nil;
signature = [[self mailAccountFolder] signature]; signature = [[self mailAccountFolder] signature];
if ([signature length]) if ([signature length])
{ {
nl = (isHTML ? @"<br/>" : @"\n"); nl = (isHTML ? @"<br />" : @"\n");
[self setText: [NSString stringWithFormat: @"%@%@-- %@%@", nl, nl, nl, signature]]; space = (isHTML ? @"&nbsp;" : @" ");
[self setText: [NSString stringWithFormat: @"%@%@--%@%@%@", nl, nl, space, nl, signature]];
} }
attachment = [NSDictionary dictionaryWithObjectsAndKeys: attachment = [NSDictionary dictionaryWithObjectsAndKeys:
[sourceMail filenameForForward], @"filename", [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. This file is part of SOGo.
@ -87,6 +87,7 @@ typedef enum {
forceActivation: (BOOL) forceActivation; forceActivation: (BOOL) forceActivation;
- (NSArray *) identities; - (NSArray *) identities;
- (NSDictionary *) defaultIdentity;
- (NSString *) signature; - (NSString *) signature;
- (NSString *) encryption; - (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. This file is part of SOGo.
@ -667,13 +667,37 @@ static NSString *inboxFolderName = @"INBOX";
return identities; 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 - (NSString *) signature
{ {
NSDictionary *identity;
NSString *signature; NSString *signature;
[self identities]; identity = [self defaultIdentity];
if ([identities count] > 0)
signature = [[identities objectAtIndex: 0] objectForKey: @"signature"]; if (identity)
signature = [identity objectForKey: @"signature"];
else else
signature = nil; signature = nil;

View File

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

View File

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

View File

@ -1,6 +1,6 @@
/* SOGoUserDefaults.h - this file is part of SOGo /* 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 * 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 * it under the terms of the GNU General Public License as published by
@ -157,21 +157,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
- (void) setMailReplyPlacement: (NSString *) newValue; - (void) setMailReplyPlacement: (NSString *) newValue;
- (NSString *) mailReplyPlacement; - (NSString *) mailReplyPlacement;
- (void) setMailSignature: (NSString *) newValue;
- (NSString *) mailSignature;
- (void) setMailSignaturePlacement: (NSString *) newValue; - (void) setMailSignaturePlacement: (NSString *) newValue;
- (NSString *) mailSignaturePlacement; - (NSString *) mailSignaturePlacement;
- (void) setMailCustomFullName: (NSString *) newValue;
- (NSString *) mailCustomFullName;
- (void) setMailCustomEmail: (NSString *) newValue;
- (NSString *) mailCustomEmail;
- (void) setMailReplyTo: (NSString *) newValue;
- (NSString *) mailReplyTo;
- (void) setAllowUserReceipt: (BOOL) allow; - (void) setAllowUserReceipt: (BOOL) allow;
- (BOOL) allowUserReceipt; - (BOOL) allowUserReceipt;
- (void) setUserReceiptNonRecipientAction: (NSString *) action; - (void) setUserReceiptNonRecipientAction: (NSString *) action;
@ -194,6 +182,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
- (void) setMailCertificateAlwaysEncrypt: (BOOL) newValue; - (void) setMailCertificateAlwaysEncrypt: (BOOL) newValue;
- (BOOL) mailCertificateAlwaysEncrypt; - (BOOL) mailCertificateAlwaysEncrypt;
- (void) setMailIdentities: (NSArray *) newIdentites;
- (NSArray *) mailIdentities;
- (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts; - (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts;
- (NSArray *) auxiliaryMailAccounts; - (NSArray *) auxiliaryMailAccounts;

View File

@ -20,6 +20,7 @@
#import <Foundation/NSDictionary.h> #import <Foundation/NSDictionary.h>
#import <Foundation/NSTimeZone.h> #import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <NGImap4/NSString+Imap4.h> #import <NGImap4/NSString+Imap4.h>
#import <NGObjWeb/WOApplication.h> #import <NGObjWeb/WOApplication.h>
@ -114,33 +115,55 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return rc; return rc;
} }
- (BOOL) _migrateSignature - (BOOL) _migrateMailIdentities
{ {
BOOL rc; BOOL rc;
NSString *signature; NSArray *mailIdentities;
NSArray *mailAccounts, *identities; NSMutableDictionary *identity;
NSDictionary *identity; NSString *fullName, *email, *replyTo, *signature;
mailAccounts = [self arrayForKey: @"MailAccounts"]; mailIdentities = [self mailIdentities];
if (mailAccounts) if (mailIdentities)
{ {
rc = YES; rc = NO;
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"];
} }
else 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; return rc;
} }
@ -211,7 +234,8 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
/* we must not use a boolean operation, otherwise subsequent migrations will /* we must not use a boolean operation, otherwise subsequent migrations will
not be invoked in the case where rc = YES. */ not be invoked in the case where rc = YES. */
return ([self _migrateLastModule] return ([self _migrateLastModule]
| [self _migrateSignature] // | [self _migrateSignature]
| [self _migrateMailIdentities]
| [self _migrateCalendarCategories] | [self _migrateCalendarCategories]
| [self migrateOldDefaultsWithDictionary: migratedKeys] | [self migrateOldDefaultsWithDictionary: migratedKeys]
| [super migrate]); | [super migrate]);
@ -629,18 +653,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return [self stringForKey: @"SOGoMailReplyPlacement"]; 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 - (void) setMailSignaturePlacement: (NSString *) newValue
{ {
[self setObject: newValue forKey: @"SOGoMailSignaturePlacement"]; [self setObject: newValue forKey: @"SOGoMailSignaturePlacement"];
@ -660,45 +672,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return signaturePlacement; 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 - (void) setAllowUserReceipt: (BOOL) allow
{ {
[self setBool: allow forKey: @"SOGoMailReceiptAllow"]; [self setBool: allow forKey: @"SOGoMailReceiptAllow"];
@ -784,6 +757,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return [self boolForKey: @"SOGoMailCertificateAlwaysEncrypt"]; return [self boolForKey: @"SOGoMailCertificateAlwaysEncrypt"];
} }
- (void) setMailIdentities: (NSArray *) newIdentites
{
[self setObject: newIdentites forKey: @"SOGoMailIdentities"];
}
- (NSArray *) mailIdentities
{
return [self arrayForKey: @"SOGoMailIdentities"];
}
- (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts - (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts
{ {
[self setObject: newAccounts forKey: @"AuxiliaryMailAccounts"]; [self setObject: newAccounts forKey: @"AuxiliaryMailAccounts"];

View File

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

View File

@ -104,7 +104,7 @@
"To" = "To"; "To" = "To";
"Cc" = "Cc"; "Cc" = "Cc";
"Bcc" = "Bcc"; "Bcc" = "Bcc";
"Reply-To" = "Reply-To"; "Reply-To" = "Reply-To";
"Add address" = "Add address"; "Add address" = "Add address";
"Body" = "Body"; "Body" = "Body";
"Open" = "Open"; "Open" = "Open";
@ -121,6 +121,7 @@
"Edit Draft..." = "Edit Draft..."; "Edit Draft..." = "Edit Draft...";
"Load Images" = "Load Images"; "Load Images" = "Load Images";
"Return Receipt" = "Return Receipt"; "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?"; "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) - %@"; "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."; "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 - (WOResponse *) composeAction
{ {
NSString *value, *signature, *nl; NSString *value, *signature, *nl, *space;
SOGoDraftObject *newDraftMessage; SOGoDraftObject *newDraftMessage;
NSMutableDictionary *headers; NSMutableDictionary *headers;
NSDictionary *data; NSDictionary *data;
@ -108,7 +108,7 @@
SOGoDraftsFolder *drafts; SOGoDraftsFolder *drafts;
SOGoUserDefaults *ud; SOGoUserDefaults *ud;
id mailTo; id mailTo;
BOOL save; BOOL save, isHTML;
drafts = [[self clientObject] draftsFolderInContext: context]; drafts = [[self clientObject] draftsFolderInContext: context];
newDraftMessage = [drafts newDraft]; newDraftMessage = [drafts newDraft];
@ -142,9 +142,11 @@
{ {
ud = [[context activeUser] userDefaults]; ud = [[context activeUser] userDefaults];
[newDraftMessage setIsHTML: [[ud mailComposeMessageType] isEqualToString: @"html"]]; [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; save = YES;
} }
if (save) if (save)

View File

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

View File

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

View File

@ -426,10 +426,13 @@ static SoProduct *preferencesProduct = nil;
} }
if (account) if (account)
[accounts insertObject: account atIndex: 0]; [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: @"SOGoMailCertificate"];
[values removeObjectForKey: @"SOGoMailCertificateAlwaysSign"]; [values removeObjectForKey: @"SOGoMailCertificateAlwaysSign"];
[values removeObjectForKey: @"SOGoMailCertificateAlwaysEncrypt"]; [values removeObjectForKey: @"SOGoMailCertificateAlwaysEncrypt"];
[values setObject: accounts forKey: @"AuxiliaryMailAccounts"];
// Add the domain's default vacation subject if user has not specified a custom subject // Add the domain's default vacation subject if user has not specified a custom subject
vacationOptions = [defaults vacationOptions]; vacationOptions = [defaults vacationOptions];

View File

@ -1110,63 +1110,6 @@ static NSArray *reminderValues = nil;
return [[user domainDefaults] mailAuxiliaryUserAccountsEnabled]; 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 // Used internally
// //
@ -1229,20 +1172,6 @@ static NSArray *reminderValues = nil;
} }
} }
//
// Used internally
//
- (void) _extractMainCustomFrom: (NSDictionary *) account
{
}
//
// Used internally
//
- (void) _extractMainReplyTo: (NSDictionary *) account
{
}
// //
// Used internally // Used internally
// //
@ -1259,7 +1188,7 @@ static NSArray *reminderValues = nil;
if (!knownKeys) if (!knownKeys)
{ {
knownKeys = [NSArray arrayWithObjects: @"fullName", @"email", knownKeys = [NSArray arrayWithObjects: @"fullName", @"email",
@"signature", @"replyTo", nil]; @"signature", @"replyTo", @"isDefault", nil];
[knownKeys retain]; [knownKeys retain];
} }
@ -1367,9 +1296,8 @@ static NSArray *reminderValues = nil;
if ([account isKindOfClass: [NSDictionary class]]) if ([account isKindOfClass: [NSDictionary class]])
{ {
identities = [account objectForKey: @"identities"]; identities = [account objectForKey: @"identities"];
if ([identities isKindOfClass: [NSArray class]] if ([self _validateAccountIdentities: identities])
&& [identities count] > 0) [target setObject: identities forKey: @"SOGoMailIdentities"];
[self _extractMainIdentity: [identities objectAtIndex: 0] inDictionary: target];
[self _extractMainReceiptsPreferences: [account objectForKey: @"receipts"] inDictionary: target]; [self _extractMainReceiptsPreferences: [account objectForKey: @"receipts"] inDictionary: target];
[self _extractMainSecurityPreferences: [account objectForKey: @"security"] inDictionary: target]; [self _extractMainSecurityPreferences: [account objectForKey: @"security"] inDictionary: target];
} }
@ -1575,10 +1503,6 @@ static NSArray *reminderValues = nil;
if ([accounts count] > 0) if ([accounts count] > 0)
{ {
// The first account is the main system account. The following mapping is required: // 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.receiptAction => SOGoMailReceiptAllow
// - receipts.receiptNonRecipientAction => SOGoMailReceiptNonRecipientAction // - receipts.receiptNonRecipientAction => SOGoMailReceiptNonRecipientAction
// - receipts.receiptOutsideDomainAction => SOGoMailReceiptOutsideDomainAction // - receipts.receiptOutsideDomainAction => SOGoMailReceiptOutsideDomainAction

View File

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

View File

@ -28,14 +28,35 @@
<sg-avatar-image class="hide-xs" <sg-avatar-image class="hide-xs"
sg-email="editor.message.editable.from" sg-email="editor.message.editable.from"
size="32">person</sg-avatar-image> size="32">person</sg-avatar-image>
<md-input-container class="sg-no-wrap"> <md-autocomplete
<label><var:string label:value="From"/></label> class="md-margin md-default-theme md-bg md-hue-1" flex="flex"
<md-select name="from" required="required"
ng-model="editor.message.editable.from"> md-input-name="from"
<md-option ng-value="identity" ng-repeat="identity in editor.identities track by $index">{{identity}}</md-option> md-require-match="true"
</md-select> md-selected-item="editor.fromIdentity"
</md-input-container> md-search-text="editor.identitySearchText"
<div flex="flex"><!-- spacer --></div> 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()" <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" 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"> sg-ripple-click="mailEditor">

View File

@ -94,45 +94,100 @@
</md-input-container> </md-input-container>
</div> </div>
<md-input-container class="md-block md-flex"> <!-- identities -->
<label><var:string label:value="Full Name"/></label> <div class="pseudo-input-container" ng-if="$AccountDialogController.hasIdentities()">
<input type="text" required="required" <label class="pseudo-input-label"><var:string label:value="Identities"/></label>
ng-disabled="$AccountDialogController.customFromIsReadonly()" <md-card ng-repeat="identity in $AccountDialogController.account.identities | filter:$AccountDialogController.isEditableIdentity"
ng-model="$AccountDialogController.account.identities[0].fullName"/> class="sg-collapsed"
</md-input-container> ng-class="{ 'sg-expanded': $index == $AccountDialogController.selectedIdentity }">
<a class="md-flex md-button" ng-click="$AccountDialogController.selectIdentity($index)">
<div layout="row"> <div layout="row" layout-align="start center">
<md-input-container class="md-block" flex="50"> <div class="card-picture">
<label><var:string label:value="Email"/></label> <sg-avatar-image class="md-avatar"
<input type="email" required="required" sg-email="identity.email"
ng-disabled="$AccountDialogController.customFromIsReadonly()" size="40">person</sg-avatar-image>
ng-model="$AccountDialogController.account.identities[0].email"/> </div>
</md-input-container> <div class="sg-tile-content">
<md-input-container class="md-block" flex="50" <div class="sg-md-subhead">
ng-hide="$AccountDialogController.customFromIsReadonly()"> <div>
<label><var:string label:value="Reply To Email"/></label> <span ng-bind="identity.fullName"><!-- fullName --></span>
<input type="email" <span ng-show="identity.email">
autocomplete="off" <var:entity const:name="nbsp"/>
ng-model="$AccountDialogController.account.identities[0].replyTo"/> <var:entity const:name="lt"/><span ng-bind="identity.email"><!-- email --></span><var:entity const:name="gt"/>
</md-input-container> </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> </div>
<!-- To switch between a simple text editor and the CK/HTML editor, we use a ng-if and not <div class="sg-padded--bottom"><!-- spacer --></div>
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>
<md-input-container class="md-block md-input-has-value"> <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> <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 () { this.$onDestroy = function () {
editorElement.classList.add('ng-cloak');
editor.destroy(); editor.destroy();
} }
@ -349,12 +350,19 @@
}); });
} }
// vm.ngModelCtrl.$render(); editorElement.classList.remove('ng-cloak');
vm.ngModelCtrl.$render();
} }
function onEditorChange () { function onEditorChange () {
var html = editor.getData(); 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') { if (text === '\n') {
text = ''; text = '';

View File

@ -13,10 +13,16 @@
if (typeof futureAccountData.then !== 'function') { if (typeof futureAccountData.then !== 'function') {
angular.extend(this, futureAccountData); angular.extend(this, futureAccountData);
_.forEach(this.identities, function(identity) { _.forEach(this.identities, function(identity) {
if (identity.fullName) if (identity.fullName && identity.email)
identity.full = identity.fullName + ' <' + identity.email + '>'; identity.full = identity.fullName + ' <' + identity.email + '>';
else else if (identity.email)
identity.full = '<' + 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)); 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 * @function $certificate
* @memberof Account.prototype * @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() { this.$onInit = function() {
$scope.isPopup = stateParent.isPopup; $scope.isPopup = stateParent.isPopup;
this.addRecipient = addRecipient; this.account = stateAccount;
this.autocomplete = {to: {}, cc: {}, bcc: {}}; this.autocomplete = {to: {}, cc: {}, bcc: {}};
this.autosave = null; this.autosave = null;
this.autosaveDrafts = autosaveDrafts; this.autosaveDrafts = autosaveDrafts;
@ -21,7 +21,9 @@
this.isFullscreen = false; this.isFullscreen = false;
this.hideBcc = (stateMessage.editable.bcc.length === 0); this.hideBcc = (stateMessage.editable.bcc.length === 0);
this.hideCc = (stateMessage.editable.cc.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.message = stateMessage;
this.recipientSeparatorKeys = [ this.recipientSeparatorKeys = [
$mdConstant.KEY_CODE.ENTER, $mdConstant.KEY_CODE.ENTER,
@ -283,11 +285,11 @@
}); });
} }
function addRecipient(contact, field) { this.addRecipient = function (contact, field) {
var recipients, recipient, list, i, address; 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; 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)) { if (angular.isString(contact)) {
// Examples that are handled: // Examples that are handled:
@ -351,11 +353,66 @@
return recipient; return recipient;
else else
return null; 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) { this.expandGroup = function(contact, field) {
var recipients, i, j; var recipients, i, j;
recipients = vm.message.editable[field]; recipients = this.message.editable[field];
i = recipients.indexOf(contact); i = recipients.indexOf(contact);
recipients.splice(i, 1); recipients.splice(i, 1);
for (j = 0; j < contact.members.length; j++) { for (j = 0; j < contact.members.length; j++) {
@ -391,8 +448,7 @@
if (this.firstFocus) { if (this.firstFocus) {
onCompletePromise().then(function(element) { onCompletePromise().then(function(element) {
var textContent = angular.element(textArea).val(), var textContent = angular.element(textArea).val(),
hasSignature = (Preferences.defaults.SOGoMailSignature && hasSignature = /\n-- \n/.test(textContent),
Preferences.defaults.SOGoMailSignature.length > 0),
signatureLength = 0, signatureLength = 0,
sigLimit, sigLimit,
caretPosition; caretPosition;
@ -402,8 +458,9 @@
element.find('md-dialog-content')[0].scrollTop = 0; element.find('md-dialog-content')[0].scrollTop = 0;
} }
else { else {
// Search for signature starting from bottom
if (hasSignature) { if (hasSignature) {
sigLimit = textContent.lastIndexOf("--"); sigLimit = textContent.lastIndexOf("-- ");
if (sigLimit > -1) if (sigLimit > -1)
signatureLength = (textContent.length - sigLimit); signatureLength = (textContent.length - sigLimit);
} }
@ -447,7 +504,7 @@
if (x === null) { if (x === null) {
break; break;
} }
if (x.getText() == '--') { if (/--(%20|%A0|%C2%A0)/.test(encodeURI(x.getText()))) {
node = x.getPrevious().getPrevious(); node = x.getPrevious().getPrevious();
break; break;
} }

View File

@ -7,23 +7,23 @@
/** /**
* @ngInject * @ngInject
*/ */
AccountDialogController.$inject = ['$timeout', '$mdDialog', 'FileUploader', 'Dialog', 'sgSettings', 'Account', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled']; AccountDialogController.$inject = ['$timeout', '$window', '$mdConstant', '$mdDialog', 'FileUploader', 'Dialog', 'sgSettings', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled'];
function AccountDialogController($timeout, $mdDialog, FileUploader, Dialog, Settings, Account, defaults, account, accountId, mailCustomFromEnabled) { function AccountDialogController($timeout, $window, $mdConstant, $mdDialog, FileUploader, Dialog, Settings, defaults, account, accountId, mailCustomFromEnabled) {
var vm = this, var vm = this;
accountObject = new Account({ id: accountId, security: account.security });
vm.defaultPort = 143; this.defaultPort = 143;
vm.defaults = defaults; this.defaults = defaults;
vm.account = account; this.account = account;
vm.accountId = accountId; this.accountId = accountId;
vm.customFromIsReadonly = customFromIsReadonly; this.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./;
vm.onBeforeUploadCertificate = onBeforeUploadCertificate; this.addressesSearchText = '';
vm.removeCertificate = removeCertificate; this.emailSeparatorKeys = [
vm.importCertificate = importCertificate; $mdConstant.KEY_CODE.ENTER,
vm.cancel = cancel; $mdConstant.KEY_CODE.TAB,
vm.save = save; $mdConstant.KEY_CODE.COMMA,
vm.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./; $mdConstant.KEY_CODE.SEMICOLON
vm.ckConfig = { ];
this.ckConfig = {
'autoGrow_minHeight': 70, 'autoGrow_minHeight': 70,
'toolbar': [['Bold', 'Italic', '-', 'Link', 'toolbar': [['Bold', 'Italic', '-', 'Link',
'Font','FontSize','-','TextColor', 'Font','FontSize','-','TextColor',
@ -31,14 +31,14 @@
language: defaults.LocaleCode language: defaults.LocaleCode
}; };
if (!vm.account.encryption) if (!this.account.encryption)
vm.account.encryption = "none"; this.account.encryption = "none";
else if (vm.account.encryption == "ssl") else if (this.account.encryption == "ssl")
vm.defaultPort = 993; this.defaultPort = 993;
_loadCertificate(); _loadCertificate();
vm.uploader = new FileUploader({ this.uploader = new FileUploader({
url: [Settings.activeUser('folderURL') + 'Mail', accountId, 'importCertificate'].join('/'), url: [Settings.activeUser('folderURL') + 'Mail', accountId, 'importCertificate'].join('/'),
autoUpload: false, autoUpload: false,
queueLimit: 1, 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() { function _loadCertificate() {
if (vm.account.security && vm.account.security.hasCertificate) if (vm.account.security && vm.account.security.hasCertificate)
accountObject.$certificate().then(function(crt) { vm.account.$certificate().then(function(crt) {
vm.certificate = crt; vm.certificate = crt;
}, function() { }, function() {
delete vm.account.security.hasCertificate; delete vm.account.security.hasCertificate;
@ -73,35 +129,33 @@
return isP12File; return isP12File;
} }
function customFromIsReadonly() { this.customFromIsReadonly = function () {
if (accountId > 0) if (accountId > 0)
return false; return false;
return !mailCustomFromEnabled; return !mailCustomFromEnabled;
} };
function importCertificate() { this.importCertificate = function () {
vm.uploader.queue[0].formData = [{ password: vm.certificatePassword }]; this.uploader.queue[0].formData = [{ password: this.certificatePassword }];
vm.uploader.uploadItem(0); this.uploader.uploadItem(0);
} };
function onBeforeUploadCertificate(form) { this.onBeforeUploadCertificate = function (form) {
vm.form = form; this.form = form;
vm.uploader.clearQueue(); this.uploader.clearQueue();
} };
function removeCertificate() { this.removeCertificate = function () {
accountObject.$removeCertificate().then(function() { this.account.$removeCertificate();
delete vm.account.security.hasCertificate; };
});
}
function cancel() { this.cancel = function () {
$mdDialog.cancel(); $mdDialog.cancel();
} };
function save() { this.save = function () {
$mdDialog.hide(); $mdDialog.hide();
} };
} }
angular angular

View File

@ -98,28 +98,25 @@
}; };
this.addMailAccount = function(ev, form) { this.addMailAccount = function(ev, form) {
var account; var account, index;
this.preferences.defaults.AuxiliaryMailAccounts.push({}); account = new Account({
isNew: true,
account = _.last(this.preferences.defaults.AuxiliaryMailAccounts); name: "",
angular.extend(account, identities: [
{ {
isNew: true, fullName: "",
name: "", email: ""
identities: [ }
{ ],
fullName: "", receipts: {
email: "" receiptAction: "ignore",
} receiptNonRecipientAction: "ignore",
], receiptOutsideDomainAction: "ignore",
receipts: { receiptAnyAction: "ignore"
receiptAction: "ignore", }
receiptNonRecipientAction: "ignore", });
receiptOutsideDomainAction: "ignore", index = this.preferences.defaults.AuxiliaryMailAccounts.length;
receiptAnyAction: "ignore"
}
});
$mdDialog.show({ $mdDialog.show({
controller: 'AccountDialogController', controller: 'AccountDialogController',
@ -133,14 +130,13 @@
mailCustomFromEnabled: $window.mailCustomFromEnabled mailCustomFromEnabled: $window.mailCustomFromEnabled
} }
}).then(function() { }).then(function() {
vm.preferences.defaults.AuxiliaryMailAccounts.push(account.$omit());
form.$setDirty(); form.$setDirty();
}).catch(function() {
vm.preferences.defaults.AuxiliaryMailAccounts.pop();
}); });
}; };
this.editMailAccount = function(event, index, form) { this.editMailAccount = function(event, index, form) {
var account = this.preferences.defaults.AuxiliaryMailAccounts[index]; var account = new Account(this.preferences.defaults.AuxiliaryMailAccounts[index]);
$mdDialog.show({ $mdDialog.show({
controller: 'AccountDialogController', controller: 'AccountDialogController',
controllerAs: '$AccountDialogController', controllerAs: '$AccountDialogController',
@ -153,10 +149,8 @@
mailCustomFromEnabled: $window.mailCustomFromEnabled mailCustomFromEnabled: $window.mailCustomFromEnabled
} }
}).then(function() { }).then(function() {
vm.preferences.defaults.AuxiliaryMailAccounts[index] = account; vm.preferences.defaults.AuxiliaryMailAccounts[index] = account.$omit();
form.$setDirty(); 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) { @media (max-width: $layout-breakpoint-xs) {
// Enlarge the autocompletion menu on small devices to fit the entire screen // Enlarge the autocompletion menu on small devices to fit the entire screen
.md-autocomplete-suggestions-container { .md-autocomplete-suggestions-container {