diff --git a/package.json b/package.json
index eda180c..35c72ff 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,17 @@
"type": "git",
"url": "https://github.com/whiteout-io/mail-html5.git"
},
- "keywords": ["email", "mail", "client", "app", "openpgp", "pgp", "gpg", "imap", "smtp"],
+ "keywords": [
+ "email",
+ "mail",
+ "client",
+ "app",
+ "openpgp",
+ "pgp",
+ "gpg",
+ "imap",
+ "smtp"
+ ],
"engines": {
"node": ">=0.10"
},
diff --git a/src/js/app.js b/src/js/app.js
index 40b6465..0051cda 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -27,7 +27,8 @@ requirejs([
'fastclick',
'angularRoute',
'angularAnimate',
- 'ngInfiniteScroll'
+ 'ngInfiniteScroll',
+ 'ngTagsInput'
], function(
angular,
DialogCtrl,
@@ -70,7 +71,8 @@ requirejs([
'login-new-device',
'privatekey-upload',
'popover',
- 'infinite-scroll'
+ 'infinite-scroll',
+ 'ngTagsInput'
]);
// set router paths
diff --git a/src/js/controller/write.js b/src/js/controller/write.js
index d2f1e33..b9b4e9e 100644
--- a/src/js/controller/write.js
+++ b/src/js/controller/write.js
@@ -14,14 +14,13 @@ define(function(require) {
// Controller
//
- var WriteCtrl = function($scope, $filter) {
+ var WriteCtrl = function($scope, $filter, $q) {
pgp = appController._pgp;
auth = appController._auth;
emailDao = appController._emailDao;
outbox = appController._outboxBo;
keychainDao = appController._keychain;
-
// set default value so that the popover height is correct on init
$scope.keyId = 'XXXXXXXX';
@@ -55,21 +54,16 @@ define(function(require) {
function resetFields() {
$scope.writerTitle = 'New email';
- $scope.to = [{
- address: ''
- }];
+ $scope.to = [];
$scope.showCC = false;
- $scope.cc = [{
- address: ''
- }];
+ $scope.cc = [];
$scope.showBCC = false;
- $scope.bcc = [{
- address: ''
- }];
+ $scope.bcc = [];
$scope.subject = '';
$scope.body = '';
$scope.ciphertextPreview = '';
$scope.attachments = [];
+ $scope.addressBookCache = undefined;
}
function reportBug() {
@@ -218,17 +212,13 @@ define(function(require) {
//
/**
- * This event is fired when editing the email address headers. It checks is space is pressed and if so, creates a new address field.
- */
- $scope.onAddressUpdate = function(field, index) {
- var recipient = field[index];
- $scope.verify(recipient);
- };
-
- /**
- * Verify and email address and fetch its public key
+ * Verify email address and fetch its public key
*/
$scope.verify = function(recipient) {
+ if (!recipient) {
+ return;
+ }
+
// set display to insecure while fetching keys
recipient.key = undefined;
recipient.secure = false;
@@ -241,42 +231,32 @@ define(function(require) {
return;
}
- // check if to address is contained in known public keys
- // when we write an email, we always need to work with the latest keys available
- keychainDao.refreshKeyForUserId(recipient.address, function(err, key) {
- if (err) {
- $scope.onError(err);
- return;
- }
-
- if (key) {
- // compare again since model could have changed during the roundtrip
- var matchingUserId = _.findWhere(key.userIds, {
- emailAddress: recipient.address
- });
- // compare either primary userId or (if available) multiple IDs
- if (key.userId === recipient.address || matchingUserId) {
- recipient.key = key;
- recipient.secure = true;
+ // keychainDao is undefined in local dev environment
+ if (keychainDao) {
+ // check if to address is contained in known public keys
+ // when we write an email, we always need to work with the latest keys available
+ keychainDao.refreshKeyForUserId(recipient.address, function(err, key) {
+ if (err) {
+ $scope.onError(err);
+ return;
}
- }
- $scope.checkSendStatus();
- $scope.$digest();
- });
- };
+ if (key) {
+ // compare again since model could have changed during the roundtrip
+ var matchingUserId = _.findWhere(key.userIds, {
+ emailAddress: recipient.address
+ });
+ // compare either primary userId or (if available) multiple IDs
+ if (key.userId === recipient.address || matchingUserId) {
+ recipient.key = key;
+ recipient.secure = true;
+ }
+ }
- $scope.getKeyId = function(recipient) {
- $scope.keyId = 'Key not found for that user.';
-
- if (!recipient.key) {
- return;
+ $scope.checkSendStatus();
+ $scope.$digest();
+ });
}
-
- var fpr = pgp.getFingerprint(recipient.key.publicKey);
- var formatted = fpr.slice(32);
-
- $scope.keyId = formatted;
};
/**
@@ -418,6 +398,52 @@ define(function(require) {
};
+ //
+ // Tag input & Autocomplete
+ //
+
+ $scope.tagStyle = function(recipient) {
+ var classes = ['label'];
+ if (recipient.secure === false) {
+ classes.push('label-primary');
+ }
+ return classes;
+ };
+
+ $scope.lookupAddressBook = function(query) {
+ var deferred = $q.defer();
+
+ if (!$scope.addressBookCache) {
+ // populate address book cache
+ keychainDao.listLocalPublicKeys(function(err, keys) {
+ if (err) {
+ $scope.onError(err);
+ return;
+ }
+
+ $scope.addressBookCache = keys.map(function(key) {
+ return {
+ address: key.userId
+ };
+ });
+ filter();
+ });
+
+ } else {
+ filter();
+ }
+
+ // query address book cache
+ function filter() {
+ var addresses = $scope.addressBookCache.filter(function(i) {
+ return i.address.indexOf(query) !== -1;
+ });
+ deferred.resolve(addresses);
+ }
+
+ return deferred.promise;
+ };
+
//
// Helpers
//
@@ -489,138 +515,28 @@ define(function(require) {
};
});
- ngModule.directive('autoSize', function($parse) {
+ ngModule.directive('focusInput', function($timeout, $parse) {
return {
- require: 'ngModel',
- link: function(scope, elm, attrs) {
- // resize text input depending on value length
- var model = $parse(attrs.autoSize);
- scope.$watch(model, function(value) {
- var width;
-
- if (!value || value.length < 12) {
- width = (14 * 8) + 'px';
- } else {
- width = ((value.length + 2) * 8) + 'px';
- }
-
- elm.css('width', width);
- });
- }
- };
- });
-
- function addInput(field, scope) {
- scope.$apply(function() {
- field.push({
- address: ''
- });
- });
- }
-
- function removeInput(field, index, scope) {
- scope.$apply(function() {
- field.splice(index, 1);
- });
- }
-
- function checkForEmptyInput(field) {
- var emptyFieldExists = false;
- field.forEach(function(recipient) {
- if (!recipient.address) {
- emptyFieldExists = true;
- }
- });
- return emptyFieldExists;
- }
-
- function cleanupEmptyInputs(field, scope) {
- scope.$apply(function() {
- for (var i = field.length - 2; i >= 0; i--) {
- if (!field[i].address) {
- field.splice(i, 1);
- }
- }
- });
- }
-
- function focusInput(fieldName, index) {
- var fieldId = fieldName + (index);
- var fieldEl = document.getElementById(fieldId);
- if (fieldEl) {
- fieldEl.focus();
- }
- }
-
- ngModule.directive('field', function() {
- return {
- scope: true,
+ //scope: true, // optionally create a child scope
link: function(scope, element, attrs) {
- element.on('click', function(e) {
- if (e.target.nodeName === 'INPUT') {
- return;
+ var model = $parse(attrs.focusInput);
+ scope.$watch(model, function(value) {
+ if (value === true) {
+ $timeout(function() {
+ element.find('input').first().focus();
+ }, 100);
}
-
- var fieldName = attrs.field;
- var field = scope[fieldName];
-
- if (!checkForEmptyInput(field)) {
- // create new field input if no empy one exists
- addInput(field, scope);
- }
-
- // focus on last input when clicking on field
- focusInput(fieldName, field.length - 1);
});
}
};
});
- ngModule.directive('addressInput', function() {
+ ngModule.directive('focusInputOnClick', function() {
return {
- scope: true,
- link: function(scope, elm, attrs) {
- // get prefix for id
- var fieldName = attrs.addressInput;
- var field = scope[fieldName];
- var index = parseInt(attrs.id.replace(fieldName, ''), 10);
-
- elm.on('blur', function() {
- if (!checkForEmptyInput(field)) {
- // create new field input
- addInput(field, scope);
- }
-
- cleanupEmptyInputs(field, scope);
- });
-
- elm.on('keydown', function(e) {
- var code = e.keyCode;
- var address = elm[0].value;
-
- if (code === 32 || code === 188 || code === 186) {
- // catch space, comma, semicolon
- e.preventDefault();
-
- // add next field only if current input is not empty
- if (address) {
- // create new field input
- addInput(field, scope);
-
- // find next input and focus
- focusInput(fieldName, index + 1);
- }
-
- } else if ((code === 8 || code === 46) && !address && field.length > 1) {
- // backspace, delete on empty input
- // remove input
- e.preventDefault();
-
- removeInput(field, index, scope);
-
- // focus on previous id
- focusInput(fieldName, index - 1);
- }
+ //scope: true, // optionally create a child scope
+ link: function(scope, element) {
+ element.on('click', function() {
+ element.find('input').first().focus();
});
}
};
diff --git a/src/lib/ngtagsinput/ng-tags-input.min.js b/src/lib/ngtagsinput/ng-tags-input.min.js
new file mode 100755
index 0000000..340314a
--- /dev/null
+++ b/src/lib/ngtagsinput/ng-tags-input.min.js
@@ -0,0 +1,5 @@
+/*
+ FROM FORKED REPO OF ngTagsInput: https://github.com/nanlabs/ngTagsInput
+ It adds tagStyle attribute for custom styling of tags.
+*/
+/*! ngTagsInput v2.1.0 License: MIT */!function(){"use strict";function a(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(b,c){return angular.forEach(a[b],function(a){a.call(null,c)}),this}}}function b(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a}function c(a,b,c){for(var d=null,f=0;f
+
-
-
-
+
+
-
-
-
+
+
-
-
-
+