Refactor app controller to use new services

This commit is contained in:
Tankred Hase 2014-11-20 15:14:39 +01:00
parent 4c04ba4e74
commit da5a9e2c17
29 changed files with 322 additions and 327 deletions

View File

@ -35,8 +35,8 @@ var axe = require('axe-logger'),
WriteCtrl = require('./controller/write'),
NavigationCtrl = require('./controller/navigation'),
ActionBarCtrl = require('./controller/action-bar'),
errorUtil = require('./util/error'),
backButtonUtil = require('./util/backbutton-handler');
backButtonUtil = require('./util/backbutton-handler'),
StatusDisplayCtrl = require('./controller/app/status-display');
// include angular modules
require('./app-config');
@ -121,9 +121,6 @@ app.run(function($rootScope) {
// global state... inherited to all child scopes
$rootScope.state = {};
// attach global error handler
errorUtil.attachHandler($rootScope);
// attach the back button handler to the root scope
backButtonUtil.attachHandler($rootScope);
@ -142,6 +139,7 @@ app.controller('ContactsCtrl', ContactsCtrl);
app.controller('AboutCtrl', AboutCtrl);
app.controller('DialogCtrl', DialogCtrl);
app.controller('ActionBarCtrl', ActionBarCtrl);
app.controller('StatusDisplayCtrl', StatusDisplayCtrl);
//
// Manual angular bootstraping

View File

@ -1,12 +1,6 @@
'use strict';
var cfg = require('../app-config').config;
//
// Controller
//
var AboutCtrl = function($scope) {
var AboutCtrl = function($scope, appConfig) {
$scope.state.about = {
toggle: function(to) {
@ -18,7 +12,7 @@ var AboutCtrl = function($scope) {
// scope variables
//
$scope.version = cfg.appVersion + ' (beta)';
$scope.version = appConfig.config.appVersion + ' (beta)';
$scope.date = new Date();
//

View File

@ -1,18 +1,7 @@
'use strict';
var appController = require('../app-controller'),
dl = require('../util/download'),
config = require('../app-config').config,
pgp, keychain, userId;
//
// Controller
//
var AccountCtrl = function($scope) {
userId = appController._emailDao._account.emailAddress;
keychain = appController._keychain;
pgp = appController._pgp;
var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dialog) {
var userId = auth.emailAddress;
$scope.state.account = {
toggle: function(to) {
@ -31,7 +20,7 @@ var AccountCtrl = function($scope) {
var fpr = keyParams.fingerprint;
$scope.fingerprint = fpr.slice(0, 4) + ' ' + fpr.slice(4, 8) + ' ' + fpr.slice(8, 12) + ' ' + fpr.slice(12, 16) + ' ' + fpr.slice(16, 20) + ' ' + fpr.slice(20, 24) + ' ' + fpr.slice(24, 28) + ' ' + fpr.slice(28, 32) + ' ' + fpr.slice(32, 36) + ' ' + fpr.slice(36);
$scope.keysize = keyParams.bitSize;
$scope.publicKeyUrl = config.cloudUrl + '/' + userId;
$scope.publicKeyUrl = appConfig.config.cloudUrl + '/' + userId;
//
// scope functions
@ -40,14 +29,14 @@ var AccountCtrl = function($scope) {
$scope.exportKeyFile = function() {
keychain.getUserKeyPair(userId, function(err, keys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
var keyId = keys.publicKey._id;
var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length);
dl.createDownload({
download.createDownload({
content: keys.publicKey.publicKey + '\r\n' + keys.privateKey.encryptedKey,
filename: file + '.asc',
contentType: 'text/plain'

View File

@ -1,15 +1,6 @@
'use strict';
var appController = require('../app-controller'),
emailDao;
//
// Controller
//
var ActionBarCtrl = function($scope) {
emailDao = appController._emailDao;
var ActionBarCtrl = function($scope, email, dialog, statusDisplay) {
/**
* Move a single message from the currently selected folder to another folder
@ -24,9 +15,9 @@ var ActionBarCtrl = function($scope) {
// close read state
$scope.state.read.open = false;
$scope.state.mailList.updateStatus('Moving message...');
statusDisplay.update('Moving message...');
emailDao.moveMessage({
email.moveMessage({
folder: currentFolder(),
destination: destination,
message: message
@ -35,14 +26,14 @@ var ActionBarCtrl = function($scope) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
$scope.state.mailList.updateStatus('Unable to move message in offline mode!');
statusDisplay.update('Unable to move message in offline mode!');
return;
}
$scope.state.mailList.updateStatus('Error during move!');
$scope.onError(err);
statusDisplay.update('Error during move!');
dialog.error(err);
return;
}
$scope.state.mailList.updateStatus('Message moved.');
statusDisplay.update('Message moved.');
$scope.$apply();
});
};
@ -70,9 +61,9 @@ var ActionBarCtrl = function($scope) {
// close read state
$scope.state.read.open = false;
$scope.state.mailList.updateStatus('Deleting message...');
statusDisplay.update('Deleting message...');
emailDao.deleteMessage({
email.deleteMessage({
folder: currentFolder(),
message: message
}, function(err) {
@ -80,14 +71,14 @@ var ActionBarCtrl = function($scope) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
$scope.state.mailList.updateStatus('Unable to delete message in offline mode!');
statusDisplay.update('Unable to delete message in offline mode!');
return;
}
$scope.state.mailList.updateStatus('Error during delete!');
$scope.onError(err);
statusDisplay.update('Error during delete!');
dialog.error(err);
return;
}
$scope.state.mailList.updateStatus('Message deleted.');
statusDisplay.update('Message deleted.');
$scope.$apply();
});
};
@ -110,31 +101,31 @@ var ActionBarCtrl = function($scope) {
return;
}
$scope.state.mailList.updateStatus('Updating unread flag...');
statusDisplay.update('Updating unread flag...');
// close read state
$scope.state.read.open = false;
var originalState = message.unread;
message.unread = unread;
emailDao.setFlags({
email.setFlags({
folder: currentFolder(),
message: message
}, function(err) {
if (err && err.code === 42) {
// offline, restore
message.unread = originalState;
$scope.state.mailList.updateStatus('Unable to mark message in offline mode!');
statusDisplay.update('Unable to mark message in offline mode!');
return;
}
if (err) {
$scope.state.mailList.updateStatus('Error on sync!');
$scope.onError(err);
statusDisplay.update('Error on sync!');
dialog.error(err);
return;
}
$scope.state.mailList.updateStatus('Online');
statusDisplay.update('Online');
$scope.$apply();
});
};

View File

@ -1,15 +1,10 @@
'use strict';
var appController = require('../app-controller'),
keychain, pgp;
//
// Controller
//
var ContactsCtrl = function($scope) {
keychain = appController._keychain,
pgp = appController._pgp;
var ContactsCtrl = function($scope, keychain, pgp, dialog) {
$scope.state.contacts = {
toggle: function(to) {
@ -26,7 +21,7 @@ var ContactsCtrl = function($scope) {
$scope.listKeys = function() {
keychain.listLocalPublicKeys(function(err, keys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -54,7 +49,7 @@ var ContactsCtrl = function($scope) {
// verifiy public key string
if (publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
$scope.onError({
dialog.error({
showBugReporter: false,
message: 'Invalid public key!'
});
@ -64,7 +59,7 @@ var ContactsCtrl = function($scope) {
try {
keyParams = pgp.getKeyParams(publicKeyArmored);
} catch (e) {
$scope.onError(new Error('Error reading public key params!'));
dialog.error(new Error('Error reading public key params!'));
return;
}
@ -78,7 +73,7 @@ var ContactsCtrl = function($scope) {
keychain.saveLocalPublicKey(pubkey, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -90,7 +85,7 @@ var ContactsCtrl = function($scope) {
$scope.removeKey = function(key) {
keychain.removeLocalPublicKey(key._id, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}

View File

@ -1,13 +1,66 @@
'use strict';
var DialogCtrl = function($scope) {
$scope.confirm = function(ok) {
$scope.state.dialog.open = false;
var axe = require('axe-logger');
if ($scope.state.dialog.callback) {
$scope.state.dialog.callback(ok);
var DialogCtrl = function($scope, $q, dialog) {
var callback;
//
// Set dialog disply functions
//
dialog.displayInfo = function(options) {
return $q(function(resolve) {
setOptions(options);
resolve();
});
};
dialog.displayError = function(options) {
return $q(function(resolve) {
if (options) {
axe.error((options.errMsg || options.message) + (options.stack ? ('\n' + options.stack) : ''));
setOptions(options);
$scope.title = options.title || 'Error';
$scope.showBugReporter = (typeof options.showBugReporter !== 'undefined' ? options.showBugReporter : !options.title); // if title is set, presume it's not an error by default
}
resolve();
});
};
dialog.displayConfirm = function(options) {
return $q(function(resolve) {
setOptions(options);
resolve();
});
};
function setOptions(options) {
$scope.open = true;
$scope.title = options.title;
$scope.message = options.errMsg || options.message;
$scope.faqLink = options.faqLink;
$scope.positiveBtnStr = options.positiveBtnStr || 'Ok';
$scope.negativeBtnStr = options.negativeBtnStr || 'Cancel';
$scope.showNegativeBtn = options.showNegativeBtn || false;
callback = options.callback;
}
//
// Scope functions
//
$scope.confirm = function(ok) {
$scope.open = false;
if (callback) {
callback(ok);
}
$scope.state.dialog.callback = undefined;
callback = undefined;
};
};

View File

@ -1,8 +1,6 @@
'use strict';
var appController = require('../app-controller'),
notification = require('../util/notification'),
emailDao, outboxBo, keychainDao, searchTimeout, firstSelect;
var searchTimeout, firstSelect;
//
// Constants
@ -13,15 +11,11 @@ var INIT_DISPLAY_LEN = 20,
FOLDER_TYPE_INBOX = 'Inbox',
NOTIFICATION_INBOX_TIMEOUT = 5000;
var MailListCtrl = function($scope, $routeParams) {
var MailListCtrl = function($scope, $routeParams, statusDisplay, notification, email, keychain, dialog) {
//
// Init
//
emailDao = appController._emailDao;
outboxBo = appController._outboxBo;
keychainDao = appController._keychain;
/**
* Gathers unread notifications to be cancelled later
*/
@ -31,39 +25,39 @@ var MailListCtrl = function($scope, $routeParams) {
// scope functions
//
$scope.getBody = function(email) {
emailDao.getBody({
$scope.getBody = function(message) {
email.getBody({
folder: currentFolder(),
message: email
message: message
}, function(err) {
if (err && err.code !== 42) {
$scope.onError(err);
dialog.error(err);
return;
}
// display fetched body
$scope.$digest();
// automatically decrypt if it's the selected email
if (email === currentMessage()) {
emailDao.decryptBody({
message: email
}, $scope.onError);
// automatically decrypt if it's the selected message
if (message === currentMessage()) {
email.decryptBody({
message: message
}, dialog.error);
}
});
};
/**
* Called when clicking on an email list item
* Called when clicking on an message list item
*/
$scope.select = function(email) {
$scope.select = function(message) {
// unselect an item
if (!email) {
if (!message) {
$scope.state.mailList.selected = undefined;
return;
}
$scope.state.mailList.selected = email;
$scope.state.mailList.selected = message;
if (!firstSelect) {
// only toggle to read view on 2nd select in mobile mode
@ -71,22 +65,22 @@ var MailListCtrl = function($scope, $routeParams) {
}
firstSelect = false;
keychainDao.refreshKeyForUserId({
userId: email.from[0].address
keychain.refreshKeyForUserId({
userId: message.from[0].address
}, onKeyRefreshed);
function onKeyRefreshed(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
}
emailDao.decryptBody({
message: email
}, $scope.onError);
email.decryptBody({
message: message
}, dialog.error);
// if the email is unread, please sync the new state.
// if the message is unread, please sync the new state.
// otherweise forget about it.
if (!email.unread) {
if (!message.unread) {
return;
}
@ -97,21 +91,16 @@ var MailListCtrl = function($scope, $routeParams) {
}
}
$scope.state.actionBar.markMessage(email, false);
$scope.state.actionBar.markMessage(message, false);
}
};
// share local scope functions with root state
$scope.state.mailList = {
updateStatus: updateStatus
};
//
// watch tasks
//
/**
* List emails from folder when user changes folder
* List messages from folder when user changes folder
*/
$scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
if (!currentFolder()) {
@ -123,7 +112,7 @@ var MailListCtrl = function($scope, $routeParams) {
// in development, display dummy mail objects
if ($routeParams.dev) {
updateStatus('Last update: ', new Date());
statusDisplay.update('Last update: ', new Date());
currentFolder().messages = createDummyMails();
return;
}
@ -183,20 +172,20 @@ var MailListCtrl = function($scope, $routeParams) {
if (!searchText) {
// set display buffer to first messages
$scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN);
setSearching(false);
updateStatus('Online');
statusDisplay.setSearching(false);
statusDisplay.update('Online');
return;
}
// display searching spinner
setSearching(true);
updateStatus('Searching ...');
statusDisplay.setSearching(true);
statusDisplay.update('Searching ...');
searchTimeout = setTimeout(function() {
$scope.$apply(function() {
// filter relevant messages
$scope.displayMessages = $scope.search(currentFolder().messages, searchText);
setSearching(false);
updateStatus('Matches in this folder');
statusDisplay.setSearching(false);
statusDisplay.update('Matches in this folder');
});
}, 500);
};
@ -276,10 +265,10 @@ var MailListCtrl = function($scope, $routeParams) {
*/
$scope.watchOnline = $scope.$watch('account.online', function(isOnline) {
if (isOnline) {
updateStatus('Online');
statusDisplay.update('Online');
openCurrentFolder();
} else {
updateStatus('Offline mode');
statusDisplay.update('Offline mode');
}
}, true);
@ -292,7 +281,7 @@ var MailListCtrl = function($scope, $routeParams) {
return;
}
emailDao.openFolder({
email.openFolder({
folder: currentFolder()
}, function(error) {
// dont wait until scroll to load visible mail bodies
@ -302,19 +291,10 @@ var MailListCtrl = function($scope, $routeParams) {
if (error && error.code === 42) {
return;
}
$scope.onError(error);
dialog.error(error);
});
}
function updateStatus(lbl, time) {
$scope.state.mailList.lastUpdateLbl = lbl;
$scope.state.mailList.lastUpdate = (time) ? time : '';
}
function setSearching(state) {
$scope.state.mailList.searching = state;
}
function currentFolder() {
return $scope.state.nav.currentFolder;
}
@ -327,7 +307,7 @@ var MailListCtrl = function($scope, $routeParams) {
// Notification API
//
(emailDao || {}).onIncomingMessage = function(msgs) {
(email || {}).onIncomingMessage = function(msgs) {
var note, title, message, unreadMsgs;
unreadMsgs = msgs.filter(function(msg) {
@ -402,7 +382,7 @@ ngModule.directive('listScroll', function() {
}
for (var i = 0, len = listItems.length; i < len; i++) {
// the n-th list item (the dom representation of an email) corresponds to
// the n-th list item (the dom representation of an message) corresponds to
// the n-th message model in the filteredMessages array
listItem = listItems.item(i).getBoundingClientRect();

View File

@ -1,11 +1,6 @@
'use strict';
var appController = require('../app-controller'),
notification = require('../util/notification'),
backBtnHandler = require('../util/backbutton-handler'),
appCfg = require('../app-config'),
config = appCfg.config,
str = appCfg.string;
var backBtnHandler = require('../util/backbutton-handler');
//
// Constants
@ -18,9 +13,12 @@ var NOTIFICATION_SENT_TIMEOUT = 2000;
// Controller
//
var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox) {
var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox, notification, appConfig, dialog) {
!$routeParams.dev && !account.isLoggedIn() && $location.path('/'); // init app
var str = appConfig.string,
config = appConfig.config;
//
// scope functions
//
@ -39,7 +37,7 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o
$scope.onOutboxUpdate = function(err, count) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -52,19 +50,18 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o
email.refreshFolder({
folder: ob
}, $scope.onError);
}, dialog.error);
};
$scope.logout = function() {
$scope.onError({
dialog.confirm({
title: str.logoutTitle,
message: str.logoutMessage,
callback: function(confirm) {
if (confirm) {
appController.logout();
account.logout();
}
},
sync: true
}
});
};
@ -82,9 +79,9 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o
$scope.openFolder($scope.account.folders[0]);
}
// connect imap/smtp clients on first startup
appController.onConnect(function(err) {
account.onConnect(function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}

View File

@ -1,12 +1,8 @@
'use strict';
var appController = require('../app-controller'),
util = require('crypto-lib').util,
keychain, pgp;
var util = require('crypto-lib').util;
var PrivateKeyUploadCtrl = function($scope) {
keychain = appController._keychain;
pgp = keychain._pgp;
var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) {
$scope.state.privateKeyUpload = {
toggle: function(to) {
@ -24,7 +20,7 @@ var PrivateKeyUploadCtrl = function($scope) {
// close lightbox
$scope.state.lightbox = undefined;
// show message
$scope.onError({
dialog.info({
title: 'Info',
message: 'Your PGP key has already been synced.'
});
@ -64,7 +60,7 @@ var PrivateKeyUploadCtrl = function($scope) {
keyId: keyParams._id
}, function(err, privateKeySynced) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -93,8 +89,7 @@ var PrivateKeyUploadCtrl = function($scope) {
if (inputCode.toUpperCase() !== $scope.code) {
var err = new Error('The code does not match. Please go back and check the generated code.');
err.sync = true;
$scope.onError(err);
dialog.error(err);
return false;
}
@ -106,7 +101,7 @@ var PrivateKeyUploadCtrl = function($scope) {
};
$scope.encryptAndUploadKey = function(callback) {
var userId = appController._emailDao._account.emailAddress;
var userId = auth.emailAddress;
var code = $scope.code;
// register device to keychain service
@ -114,7 +109,7 @@ var PrivateKeyUploadCtrl = function($scope) {
userId: userId
}, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -147,7 +142,7 @@ var PrivateKeyUploadCtrl = function($scope) {
// set device name to local storage
$scope.setDeviceName(function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -158,14 +153,14 @@ var PrivateKeyUploadCtrl = function($scope) {
// init key sync
$scope.encryptAndUploadKey(function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
// close sync dialog
$scope.state.privateKeyUpload.toggle(false);
// show success message
$scope.onError({
dialog.info({
title: 'Success',
message: 'Whiteout Keychain setup successful!'
});

View File

@ -1,21 +1,12 @@
'use strict';
var appController = require('../app-controller'),
download = require('../util/download'),
str = require('../app-config').string,
emailDao, invitationDao, outbox, pgp, keychain;
//
// Controller
//
var ReadCtrl = function($scope) {
var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) {
emailDao = appController._emailDao;
invitationDao = appController._invitationDao;
outbox = appController._outboxBo;
pgp = appController._pgp;
keychain = appController._keychain;
var str = appConfig.string;
// set default value so that the popover height is correct on init
$scope.keyId = 'No key found.';
@ -31,7 +22,7 @@ var ReadCtrl = function($scope) {
$scope.keyId = 'Searching...';
keychain.getReceiverPublicKey(address, function(err, pubkey) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -71,7 +62,7 @@ var ReadCtrl = function($scope) {
keychain.getReceiverPublicKey(user.address, function(err, pubkey) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -97,12 +88,12 @@ var ReadCtrl = function($scope) {
}
var folder = $scope.state.nav.currentFolder;
var email = $scope.state.mailList.selected;
emailDao.getAttachment({
var message = $scope.state.mailList.selected;
email.getAttachment({
folder: folder,
uid: email.uid,
uid: message.uid,
attachment: attachment
}, $scope.onError);
}, dialog.error);
};
$scope.invite = function(user) {
@ -113,15 +104,15 @@ var ReadCtrl = function($scope) {
$scope.keyId = 'Sending invitation...';
var sender = emailDao._account.emailAddress,
var sender = auth.emailAddress,
recipient = user.address;
invitationDao.invite({
invitation.invite({
recipient: recipient,
sender: sender
}, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -139,7 +130,7 @@ var ReadCtrl = function($scope) {
};
// send invitation mail
outbox.put(invitationMail, $scope.onError);
outbox.put(invitationMail, dialog.error);
});
};
};

View File

@ -1,15 +1,10 @@
'use strict';
var appController = require('../app-controller'),
pgp, keychain;
var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
//
// Controller
//
var SetPassphraseCtrl = function($scope) {
keychain = appController._keychain;
pgp = appController._pgp;
//
// scope variables
//
$scope.state.setPassphrase = {
toggle: function(to) {
@ -22,10 +17,6 @@ var SetPassphraseCtrl = function($scope) {
}
};
//
// scope variables
//
//
// scope functions
//
@ -87,7 +78,7 @@ var SetPassphraseCtrl = function($scope) {
var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -102,7 +93,7 @@ var SetPassphraseCtrl = function($scope) {
function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) {
err.showBugReporter = false;
$scope.onError(err);
dialog.error(err);
return;
}
@ -120,13 +111,13 @@ var SetPassphraseCtrl = function($scope) {
function onKeyPersisted(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
$scope.state.setPassphrase.toggle(false);
$scope.$apply();
$scope.onError({
dialog.info({
title: 'Success',
message: 'Passphrase change complete.'
});

View File

@ -0,0 +1,20 @@
'use strict';
var StatusDisplayCtrl = function($scope, statusDisplay) {
// set the show functions
statusDisplay.showStatus = updateStatus;
statusDisplay.showSearching = setSearching;
function updateStatus(lbl, time) {
$scope.lastUpdateLbl = lbl;
$scope.lastUpdate = (time) ? time : '';
}
function setSearching(state) {
$scope.searching = state;
}
};
module.exports = StatusDisplayCtrl;

View File

@ -1,21 +1,15 @@
'use strict';
var appController = require('../app-controller'),
axe = require('axe-logger'),
util = require('crypto-lib').util,
str = require('../app-config').string,
pgp, emailDao, outbox, keychainDao, auth;
var axe = require('axe-logger'),
util = require('crypto-lib').util;
//
// Controller
//
var WriteCtrl = function($scope, $filter, $q) {
pgp = appController._pgp;
auth = appController._auth;
emailDao = appController._emailDao;
outbox = appController._outboxBo;
keychainDao = appController._keychain;
var WriteCtrl = function($scope, $filter, $q, appConfig, auth, keychain, pgp, email, outbox, dialog) {
var str = appConfig.string;
// set default value so that the popover height is correct on init
$scope.keyId = 'XXXXXXXX';
@ -133,7 +127,7 @@ var WriteCtrl = function($scope, $filter, $q) {
}
if (replyAll) {
re.to.concat(re.cc).forEach(function(recipient) {
var me = emailDao._account.emailAddress;
var me = auth.emailAddress;
if (recipient.address === me && replyTo !== me) {
// don't reply to yourself
return;
@ -225,15 +219,15 @@ var WriteCtrl = function($scope, $filter, $q) {
return;
}
// keychainDao is undefined in local dev environment
if (keychainDao) {
// keychain is undefined in local dev environment
if (keychain) {
// 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({
keychain.refreshKeyForUserId({
userId: recipient.address
}, function(err, key) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -318,13 +312,13 @@ var WriteCtrl = function($scope, $filter, $q) {
//
$scope.sendToOutbox = function() {
var email;
var message;
// build email model for smtp-client
email = {
message = {
from: [{
name: emailDao._account.realname,
address: emailDao._account.emailAddress
name: auth.realname,
address: auth.emailAddress
}],
to: $scope.to.filter(filterEmptyAddresses),
cc: $scope.cc.filter(filterEmptyAddresses),
@ -337,11 +331,11 @@ var WriteCtrl = function($scope, $filter, $q) {
};
if ($scope.inReplyTo) {
email.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
message.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
}
if ($scope.references && $scope.references.length) {
email.headers.references = $scope.references.map(function(reference) {
message.headers.references = $scope.references.map(function(reference) {
return '<' + reference + '>';
}).join(' ');
}
@ -350,9 +344,9 @@ var WriteCtrl = function($scope, $filter, $q) {
$scope.state.writer.close();
// persist the email to disk for later sending
outbox.put(email, function(err) {
outbox.put(message, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -363,12 +357,12 @@ var WriteCtrl = function($scope, $filter, $q) {
}
$scope.replyTo.answered = true;
emailDao.setFlags({
email.setFlags({
folder: currentFolder(),
message: $scope.replyTo
}, function(err) {
if (err && err.code !== 42) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -396,9 +390,9 @@ var WriteCtrl = function($scope, $filter, $q) {
if (!$scope.addressBookCache) {
// populate address book cache
keychainDao.listLocalPublicKeys(function(err, keys) {
keychain.listLocalPublicKeys(function(err, keys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}

View File

@ -1,6 +1,6 @@
'use strict';
var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth) {
var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth, dialog) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.getAccountSettings = function() {
@ -36,7 +36,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth)
$scope.oauthPossible = function() {
// ask user to use the platform's native OAuth api
$scope.onError({
dialog.confirm({
title: 'Google Account Login',
message: 'You are signing into a Google account. Would you like to sign in with Google or just continue with a password login?',
positiveBtnStr: 'Google sign in',
@ -59,7 +59,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth)
// fetches the email address from the chrome identity api
auth.getOAuthToken(function(err) {
if (err) {
return $scope.onError(err);
return dialog.error(err);
}
$scope.setCredentials();
$scope.$apply();

View File

@ -9,7 +9,7 @@ var axe = require('axe-logger'),
PgpMailer = require('pgpmailer'),
ImapClient = require('imap-client');
function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email, outbox, deviceStorage, updateHandler) {
function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email, outbox, accountStore, updateHandler) {
this._appConfig = appConfig;
this._auth = auth;
this._admin = admin;
@ -18,7 +18,7 @@ function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email
this._emailDao = email;
this._pgpbuilder = pgpbuilder;
this._outbox = outbox;
this._deviceStorage = deviceStorage;
this._accountStore = accountStore;
this._updateHandler = updateHandler;
this._accounts = []; // init accounts list
}
@ -61,7 +61,7 @@ Account.prototype.init = function(options, callback) {
// Pre-Flight check: initialize and prepare user's local database
function prepareDatabase() {
self._deviceStorage.init(options.emailAddress, function(err) {
self._accountStore.init(options.emailAddress, function(err) {
if (err) {
return callback(err);
}

View File

@ -50,10 +50,10 @@ var MSG_PART_TYPE_HTML = 'html';
* @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages
* @param {Object} mailreader Parses MIME messages received from IMAP
*/
function Email(keychain, pgp, deviceStorage, pgpbuilder, mailreader, dialog) {
function Email(keychain, pgp, accountStore, pgpbuilder, mailreader, dialog) {
this._keychain = keychain;
this._pgp = pgp;
this._devicestorage = deviceStorage;
this._devicestorage = accountStore;
this._pgpbuilder = pgpbuilder;
this._mailreader = mailreader;

View File

@ -13,7 +13,7 @@ var util = require('crypto-lib').util,
* The local outbox takes care of the emails before they are being sent.
* It also checks periodically if there are any mails in the local device storage to be sent.
*/
function Outbox(email, keychain, deviceStorage) {
function Outbox(email, keychain, accountStore) {
/** @private */
this._emailDao = email;
@ -21,7 +21,7 @@ function Outbox(email, keychain, deviceStorage) {
this._keychain = keychain;
/** @private */
this._devicestorage = deviceStorage;
this._devicestorage = accountStore;
/**
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.

View File

@ -1,15 +0,0 @@
'use strict';
var ngModule = angular.module('woServices');
ngModule.service('appConfigStore', AppConfigStore);
module.exports = AppConfigStore;
/**
* A service for storing app configuration and user credential data locally
*/
function AppConfigStore(lawnchairDAO) {
this._localDbDao = lawnchairDAO;
this._localDbDao.init('app-config');
}
// TODO: inherit DeviceStorage service api

View File

@ -1,18 +1,29 @@
'use strict';
var ngModule = angular.module('woServices');
ngModule.service('deviceStorage', DeviceStorage);
ngModule.factory('deviceStorage', function(lawnchairDAO) {
return new DeviceStorage(lawnchairDAO);
});
module.exports = DeviceStorage;
// expose an instance with the static dbName 'app-config' to store configuration data
ngModule.factory('appConfigStore', function(deviceStorage) {
deviceStorage.init('app-config');
return deviceStorage;
});
// expose a singleton instance of DeviceStorage called 'accountStore' to persist user data
ngModule.service('accountStore', DeviceStorage);
/**
* High level storage api that handles all persistence of a user's data on the device.
*/
function DeviceStorage(lawnchairDAO) {
this._localDbDao = lawnchairDAO;
this._lawnchairDAO = lawnchairDAO;
}
DeviceStorage.prototype.init = function(dbName) {
this._localDbDao.init(dbName);
this._lawnchairDAO.init(dbName);
};
/**
@ -46,14 +57,14 @@ DeviceStorage.prototype.storeList = function(list, type, callback) {
});
});
this._localDbDao.batch(items, callback);
this._lawnchairDAO.batch(items, callback);
};
/**
* Deletes items of a certain type from storage
*/
DeviceStorage.prototype.removeList = function(type, callback) {
this._localDbDao.removeList(type, callback);
this._lawnchairDAO.removeList(type, callback);
};
/**
@ -64,14 +75,14 @@ DeviceStorage.prototype.removeList = function(type, callback) {
*/
DeviceStorage.prototype.listItems = function(type, offset, num, callback) {
// fetch all items of a certain type from the data-store
this._localDbDao.list(type, offset, num, callback);
this._lawnchairDAO.list(type, offset, num, callback);
};
/**
* Clear the whole device data-store
*/
DeviceStorage.prototype.clear = function(callback) {
this._localDbDao.clear(callback);
this._lawnchairDAO.clear(callback);
};
//

View File

@ -12,6 +12,5 @@ require('./publickey');
require('./admin');
require('./lawnchair');
require('./devicestorage');
require('./app-config-store');
require('./auth');
require('./keychain');

View File

@ -6,8 +6,14 @@ module.exports = Dialog;
function Dialog() {}
Dialog.prototype.error = function() {};
Dialog.prototype.info = function(options) {
this.displayInfo(options);
};
Dialog.prototype.info = function() {};
Dialog.prototype.error = function(options) {
this.displayError(options);
};
Dialog.prototype.confirm = function() {};
Dialog.prototype.confirm = function(options) {
this.displayConfirm(options);
};

View File

@ -1,10 +1,20 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('download', Download);
module.exports = Download;
var util = require('crypto-lib').util;
var dl = {};
/**
* A download helper to abstract platform specific behavior
*/
function Download() {}
dl.createDownload = function(options) {
/**
* Create download link and click on it.
*/
Download.prototype.createDownload = function(options) {
var contentType = options.contentType || 'application/octet-stream';
var filename = options.filename || 'file';
var content = options.content;
@ -53,6 +63,4 @@ dl.createDownload = function(options) {
}
window.open('data:' + contentType + ';base64,' + btoa(content), "_blank");
}
};
module.exports = dl;
};

View File

@ -1,33 +0,0 @@
'use strict';
var axe = require('axe-logger');
var er = {};
er.attachHandler = function(scope) {
scope.onError = function(options) {
if (!options) {
scope.$apply();
return;
}
axe.error((options.errMsg || options.message) + (options.stack ? ('\n' + options.stack) : ''));
scope.state.dialog = {
open: true,
title: options.title || 'Error',
message: options.errMsg || options.message,
faqLink: options.faqLink,
positiveBtnStr: options.positiveBtnStr || 'Ok',
negativeBtnStr: options.negativeBtnStr || 'Cancel',
showNegativeBtn: options.showNegativeBtn || false,
showBugReporter: (typeof options.showBugReporter !== 'undefined' ? options.showBugReporter : !options.title), // if title is set, presume it's not an error by default
callback: options.callback
};
// don't call apply for synchronous calls
if (!options.sync) {
scope.$apply();
}
};
};
module.exports = er;

View File

@ -4,4 +4,7 @@ angular.module('woUtil', []);
require('./dialog');
require('./connection-doctor');
require('./update/update-handler');
require('./update/update-handler');
require('./status-display');
require('./download');
require('./notification');

View File

@ -1,11 +1,15 @@
'use strict';
var cfg = require('../app-config').config;
var ngModule = angular.module('woUtil');
ngModule.service('notification', Notif);
module.exports = Notif;
var notif = {};
function Notif(appConfig) {
this._appConfig = appConfig;
if (window.Notification) {
notif.hasPermission = Notification.permission === "granted";
if (window.Notification) {
this.hasPermission = Notification.permission === "granted";
}
}
/**
@ -17,25 +21,27 @@ if (window.Notification) {
* @param {Function} options.onClick (optional) callback when the notification is clicked
* @returns {Notification} A notification instance
*/
notif.create = function(options) {
Notif.prototype.create = function(options) {
var self = this;
options.onClick = options.onClick || function() {};
if (!window.Notification) {
return;
}
if (!notif.hasPermission) {
if (!self.hasPermission) {
// don't wait until callback returns
Notification.requestPermission(function(permission) {
if (permission === "granted") {
notif.hasPermission = true;
self.hasPermission = true;
}
});
}
var notification = new Notification(options.title, {
body: options.message,
icon: cfg.iconPath
icon: self._appConfig.config.iconPath
});
notification.onclick = function() {
window.focus();
@ -51,8 +57,6 @@ notif.create = function(options) {
return notification;
};
notif.close = function(notification) {
Notif.prototype.close = function(notification) {
notification.close();
};
module.exports = notif;
};

View File

@ -0,0 +1,24 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('statusDisplay', StatusDisplay);
module.exports = StatusDisplay;
function StatusDisplay() {}
/**
* Update the status disply in the lower left of the screen
* @param {String} msg The status message that is to be displayed to the user
* @param {Date} time The time of the last update
*/
StatusDisplay.prototype.update = function(msg, time) {
this.showStatus(msg, time);
};
/**
* Update the searching status to show a spinner while searching
* @param {Boolean} state If the spinner should be displayed or not
*/
StatusDisplay.prototype.setSearching = function(state) {
this.showSearching(state);
};

View File

@ -1,19 +1,19 @@
<div class="lightbox__body" ng-controller="DialogCtrl">
<header class="lightbox__header">
<h2>{{state.dialog.title}}</h2>
<h2>{{title}}</h2>
<button class="lightbox__close" wo-touch="confirm(false)" data-action="lightbox-close">
<svg><use xlink:href="#icon-close" /><title>Close</title></svg>
</button>
</header>
<div class="lightbox__content">
<p class="typo-paragraph">{{state.dialog.message}} <a ng-show="state.dialog.faqLink" href="{{state.dialog.faqLink}}" target="_blank">Learn more</a></p>
<p class="typo-paragraph">{{message}} <a ng-show="faqLink" href="{{faqLink}}" target="_blank">Learn more</a></p>
</div>
<footer class="lightbox__controls">
<button wo-touch="confirm(false)" class="btn btn--secondary" ng-show="state.dialog.showNegativeBtn">{{state.dialog.negativeBtnStr}}</button>
<button wo-touch="confirm(false)" class="btn btn--secondary" ng-show="showNegativeBtn">{{negativeBtnStr}}</button>
<!-- only show bug report button if we can actually report a bug, i.e. the writer is attached to the scope -->
<button wo-touch="confirm(true)" class="btn" ng-show="!state.writer || !state.dialog.showBugReporter">{{state.dialog.positiveBtnStr}}</button>
<button wo-touch="confirm(true); state.writer.reportBug()" class="btn" ng-show="state.writer && state.dialog.showBugReporter">Report bug</button>
<button wo-touch="confirm(true)" class="btn" ng-show="!state.writer || !showBugReporter">{{positiveBtnStr}}</button>
<button wo-touch="confirm(true); state.writer.reportBug()" class="btn" ng-show="state.writer && showBugReporter">Report bug</button>
</footer>
</div>

View File

@ -17,7 +17,7 @@
<svg><use xlink:href="#icon-search" /><title>Search</title></svg>
<input class="input-text" type="text" ng-model="searchText"
ng-change="displaySearchResults(searchText)"
placeholder="Search" wo-focus-me="state.mailList.searching">
placeholder="Search">
</div>
</div>
@ -71,13 +71,13 @@
</ul>
</div>
<footer>
<span class="spinner" ng-show="account.loggingIn || account.busy || state.mailList.searching"></span>
<footer ng-controller="StatusDisplayCtrl">
<span class="spinner" ng-show="account.loggingIn || account.busy || searching"></span>
<span class="text" ng-switch="account.online">
<span ng-switch-when="false">
<svg><use xlink:href="#icon-offline" /></svg>
</span>
{{state.mailList.lastUpdateLbl}} {{state.mailList.lastUpdate | date:'shortTime'}}
{{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}}
</span>
</footer>
</div>

View File

@ -69,13 +69,13 @@
</li>
</ul><!--/nav__secondary-->
<footer>
<span class="spinner" ng-show="account.loggingIn || account.busy || state.mailList.searching"></span>
<footer ng-controller="StatusDisplayCtrl">
<span class="spinner" ng-show="account.loggingIn || account.busy || searching"></span>
<span class="text" ng-switch="account.online">
<span ng-switch-when="false">
<svg><use xlink:href="#icon-offline" /></svg>
</span>
{{state.mailList.lastUpdateLbl}} {{state.mailList.lastUpdate | date:'shortTime'}}
{{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}}
</span>
</footer>
</nav>