-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
+
diff --git a/UI/WebServerResources/angular-material b/UI/WebServerResources/angular-material
index bf5aa3511..374614854 160000
--- a/UI/WebServerResources/angular-material
+++ b/UI/WebServerResources/angular-material
@@ -1 +1 @@
-Subproject commit bf5aa3511ef8f849d54818ce1e2ac37f179f6f23
+Subproject commit 3746148548a304f664c09fcad16ab48a42e7810f
diff --git a/UI/WebServerResources/js/Common/sgCkeditor.component.js b/UI/WebServerResources/js/Common/sgCkeditor.component.js
index 0a574d5be..24f22a186 100644
--- a/UI/WebServerResources/js/Common/sgCkeditor.component.js
+++ b/UI/WebServerResources/js/Common/sgCkeditor.component.js
@@ -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 = '';
diff --git a/UI/WebServerResources/js/Mailer/Account.service.js b/UI/WebServerResources/js/Mailer/Account.service.js
index de214c200..503c8d7fd 100644
--- a/UI/WebServerResources/js/Mailer/Account.service.js
+++ b/UI/WebServerResources/js/Mailer/Account.service.js
@@ -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('' + identity.signature + '
');
+ 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('' + identity.signature + '
');
+ 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;
+ };
+
})();
diff --git a/UI/WebServerResources/js/Mailer/MessageEditorController.js b/UI/WebServerResources/js/Mailer/MessageEditorController.js
index 7d4cf4e0f..6b35e65d6 100644
--- a/UI/WebServerResources/js/Mailer/MessageEditorController.js
+++ b/UI/WebServerResources/js/Mailer/MessageEditorController.js
@@ -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 = '
';
+ space = ' ';
+ } 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}(> |= 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;
}
diff --git a/UI/WebServerResources/js/Preferences/AccountDialogController.js b/UI/WebServerResources/js/Preferences/AccountDialogController.js
index 7cc764257..df5a99967 100644
--- a/UI/WebServerResources/js/Preferences/AccountDialogController.js
+++ b/UI/WebServerResources/js/Preferences/AccountDialogController.js
@@ -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
diff --git a/UI/WebServerResources/js/Preferences/PreferencesController.js b/UI/WebServerResources/js/Preferences/PreferencesController.js
index 4990e84d2..eebf1e8cc 100644
--- a/UI/WebServerResources/js/Preferences/PreferencesController.js
+++ b/UI/WebServerResources/js/Preferences/PreferencesController.js
@@ -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
});
};
diff --git a/UI/WebServerResources/scss/components/autocomplete/autocomplete.scss b/UI/WebServerResources/scss/components/autocomplete/autocomplete.scss
index e627b1b4b..361b6abdc 100644
--- a/UI/WebServerResources/scss/components/autocomplete/autocomplete.scss
+++ b/UI/WebServerResources/scss/components/autocomplete/autocomplete.scss
@@ -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 {