1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-26 02:42:17 -05:00

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,10 @@
'use strict'; 'use strict';
var appController = require('../app-controller'), var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
pgp, keychain;
// //
// Controller // scope variables
// //
var SetPassphraseCtrl = function($scope) {
keychain = appController._keychain;
pgp = appController._pgp;
$scope.state.setPassphrase = { $scope.state.setPassphrase = {
toggle: function(to) { toggle: function(to) {
@ -22,10 +17,6 @@ var SetPassphraseCtrl = function($scope) {
} }
}; };
//
// scope variables
//
// //
// scope functions // scope functions
// //
@ -87,7 +78,7 @@ var SetPassphraseCtrl = function($scope) {
var keyId = pgp.getKeyParams()._id; var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) { keychain.lookupPrivateKey(keyId, function(err, savedKey) {
if (err) { if (err) {
$scope.onError(err); dialog.error(err);
return; return;
} }
@ -102,7 +93,7 @@ var SetPassphraseCtrl = function($scope) {
function onPassphraseChanged(err, newPrivateKeyArmored) { function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) { if (err) {
err.showBugReporter = false; err.showBugReporter = false;
$scope.onError(err); dialog.error(err);
return; return;
} }
@ -120,13 +111,13 @@ var SetPassphraseCtrl = function($scope) {
function onKeyPersisted(err) { function onKeyPersisted(err) {
if (err) { if (err) {
$scope.onError(err); dialog.error(err);
return; return;
} }
$scope.state.setPassphrase.toggle(false); $scope.state.setPassphrase.toggle(false);
$scope.$apply(); $scope.$apply();
$scope.onError({ dialog.info({
title: 'Success', title: 'Success',
message: 'Passphrase change complete.' 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'; 'use strict';
var appController = require('../app-controller'), var axe = require('axe-logger'),
axe = require('axe-logger'), util = require('crypto-lib').util;
util = require('crypto-lib').util,
str = require('../app-config').string,
pgp, emailDao, outbox, keychainDao, auth;
// //
// Controller // Controller
// //
var WriteCtrl = function($scope, $filter, $q) { var WriteCtrl = function($scope, $filter, $q, appConfig, auth, keychain, pgp, email, outbox, dialog) {
pgp = appController._pgp;
auth = appController._auth; var str = appConfig.string;
emailDao = appController._emailDao;
outbox = appController._outboxBo;
keychainDao = appController._keychain;
// set default value so that the popover height is correct on init // set default value so that the popover height is correct on init
$scope.keyId = 'XXXXXXXX'; $scope.keyId = 'XXXXXXXX';
@ -133,7 +127,7 @@ var WriteCtrl = function($scope, $filter, $q) {
} }
if (replyAll) { if (replyAll) {
re.to.concat(re.cc).forEach(function(recipient) { re.to.concat(re.cc).forEach(function(recipient) {
var me = emailDao._account.emailAddress; var me = auth.emailAddress;
if (recipient.address === me && replyTo !== me) { if (recipient.address === me && replyTo !== me) {
// don't reply to yourself // don't reply to yourself
return; return;
@ -225,15 +219,15 @@ var WriteCtrl = function($scope, $filter, $q) {
return; return;
} }
// keychainDao is undefined in local dev environment // keychain is undefined in local dev environment
if (keychainDao) { if (keychain) {
// check if to address is contained in known public keys // 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 // when we write an email, we always need to work with the latest keys available
keychainDao.refreshKeyForUserId({ keychain.refreshKeyForUserId({
userId: recipient.address userId: recipient.address
}, function(err, key) { }, function(err, key) {
if (err) { if (err) {
$scope.onError(err); dialog.error(err);
return; return;
} }
@ -318,13 +312,13 @@ var WriteCtrl = function($scope, $filter, $q) {
// //
$scope.sendToOutbox = function() { $scope.sendToOutbox = function() {
var email; var message;
// build email model for smtp-client // build email model for smtp-client
email = { message = {
from: [{ from: [{
name: emailDao._account.realname, name: auth.realname,
address: emailDao._account.emailAddress address: auth.emailAddress
}], }],
to: $scope.to.filter(filterEmptyAddresses), to: $scope.to.filter(filterEmptyAddresses),
cc: $scope.cc.filter(filterEmptyAddresses), cc: $scope.cc.filter(filterEmptyAddresses),
@ -337,11 +331,11 @@ var WriteCtrl = function($scope, $filter, $q) {
}; };
if ($scope.inReplyTo) { if ($scope.inReplyTo) {
email.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>'; message.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
} }
if ($scope.references && $scope.references.length) { if ($scope.references && $scope.references.length) {
email.headers.references = $scope.references.map(function(reference) { message.headers.references = $scope.references.map(function(reference) {
return '<' + reference + '>'; return '<' + reference + '>';
}).join(' '); }).join(' ');
} }
@ -350,9 +344,9 @@ var WriteCtrl = function($scope, $filter, $q) {
$scope.state.writer.close(); $scope.state.writer.close();
// persist the email to disk for later sending // persist the email to disk for later sending
outbox.put(email, function(err) { outbox.put(message, function(err) {
if (err) { if (err) {
$scope.onError(err); dialog.error(err);
return; return;
} }
@ -363,12 +357,12 @@ var WriteCtrl = function($scope, $filter, $q) {
} }
$scope.replyTo.answered = true; $scope.replyTo.answered = true;
emailDao.setFlags({ email.setFlags({
folder: currentFolder(), folder: currentFolder(),
message: $scope.replyTo message: $scope.replyTo
}, function(err) { }, function(err) {
if (err && err.code !== 42) { if (err && err.code !== 42) {
$scope.onError(err); dialog.error(err);
return; return;
} }
@ -396,9 +390,9 @@ var WriteCtrl = function($scope, $filter, $q) {
if (!$scope.addressBookCache) { if (!$scope.addressBookCache) {
// populate address book cache // populate address book cache
keychainDao.listLocalPublicKeys(function(err, keys) { keychain.listLocalPublicKeys(function(err, keys) {
if (err) { if (err) {
$scope.onError(err); dialog.error(err);
return; return;
} }

View File

@ -1,6 +1,6 @@
'use strict'; '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 !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.getAccountSettings = function() { $scope.getAccountSettings = function() {
@ -36,7 +36,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth)
$scope.oauthPossible = function() { $scope.oauthPossible = function() {
// ask user to use the platform's native OAuth api // ask user to use the platform's native OAuth api
$scope.onError({ dialog.confirm({
title: 'Google Account Login', 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?', 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', 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 // fetches the email address from the chrome identity api
auth.getOAuthToken(function(err) { auth.getOAuthToken(function(err) {
if (err) { if (err) {
return $scope.onError(err); return dialog.error(err);
} }
$scope.setCredentials(); $scope.setCredentials();
$scope.$apply(); $scope.$apply();

View File

@ -9,7 +9,7 @@ var axe = require('axe-logger'),
PgpMailer = require('pgpmailer'), PgpMailer = require('pgpmailer'),
ImapClient = require('imap-client'); 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._appConfig = appConfig;
this._auth = auth; this._auth = auth;
this._admin = admin; this._admin = admin;
@ -18,7 +18,7 @@ function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email
this._emailDao = email; this._emailDao = email;
this._pgpbuilder = pgpbuilder; this._pgpbuilder = pgpbuilder;
this._outbox = outbox; this._outbox = outbox;
this._deviceStorage = deviceStorage; this._accountStore = accountStore;
this._updateHandler = updateHandler; this._updateHandler = updateHandler;
this._accounts = []; // init accounts list 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 // Pre-Flight check: initialize and prepare user's local database
function prepareDatabase() { function prepareDatabase() {
self._deviceStorage.init(options.emailAddress, function(err) { self._accountStore.init(options.emailAddress, function(err) {
if (err) { if (err) {
return callback(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} pgpbuilder Generates and encrypts MIME and SMTP messages
* @param {Object} mailreader Parses MIME messages received from IMAP * @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._keychain = keychain;
this._pgp = pgp; this._pgp = pgp;
this._devicestorage = deviceStorage; this._devicestorage = accountStore;
this._pgpbuilder = pgpbuilder; this._pgpbuilder = pgpbuilder;
this._mailreader = mailreader; 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. * 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. * 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 */ /** @private */
this._emailDao = email; this._emailDao = email;
@ -21,7 +21,7 @@ function Outbox(email, keychain, deviceStorage) {
this._keychain = keychain; this._keychain = keychain;
/** @private */ /** @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. * 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'; 'use strict';
var ngModule = angular.module('woServices'); var ngModule = angular.module('woServices');
ngModule.service('deviceStorage', DeviceStorage); ngModule.factory('deviceStorage', function(lawnchairDAO) {
return new DeviceStorage(lawnchairDAO);
});
module.exports = DeviceStorage; 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. * High level storage api that handles all persistence of a user's data on the device.
*/ */
function DeviceStorage(lawnchairDAO) { function DeviceStorage(lawnchairDAO) {
this._localDbDao = lawnchairDAO; this._lawnchairDAO = lawnchairDAO;
} }
DeviceStorage.prototype.init = function(dbName) { 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 * Deletes items of a certain type from storage
*/ */
DeviceStorage.prototype.removeList = function(type, callback) { 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) { DeviceStorage.prototype.listItems = function(type, offset, num, callback) {
// fetch all items of a certain type from the data-store // 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 * Clear the whole device data-store
*/ */
DeviceStorage.prototype.clear = function(callback) { DeviceStorage.prototype.clear = function(callback) {
this._localDbDao.clear(callback); this._lawnchairDAO.clear(callback);
}; };
// //

View File

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

View File

@ -6,8 +6,14 @@ module.exports = Dialog;
function 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'; 'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('download', Download);
module.exports = Download;
var util = require('crypto-lib').util; 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 contentType = options.contentType || 'application/octet-stream';
var filename = options.filename || 'file'; var filename = options.filename || 'file';
var content = options.content; var content = options.content;
@ -53,6 +63,4 @@ dl.createDownload = function(options) {
} }
window.open('data:' + contentType + ';base64,' + btoa(content), "_blank"); 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('./dialog');
require('./connection-doctor'); 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'; '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) { if (window.Notification) {
notif.hasPermission = Notification.permission === "granted"; this.hasPermission = Notification.permission === "granted";
}
} }
/** /**
@ -17,25 +21,27 @@ if (window.Notification) {
* @param {Function} options.onClick (optional) callback when the notification is clicked * @param {Function} options.onClick (optional) callback when the notification is clicked
* @returns {Notification} A notification instance * @returns {Notification} A notification instance
*/ */
notif.create = function(options) { Notif.prototype.create = function(options) {
var self = this;
options.onClick = options.onClick || function() {}; options.onClick = options.onClick || function() {};
if (!window.Notification) { if (!window.Notification) {
return; return;
} }
if (!notif.hasPermission) { if (!self.hasPermission) {
// don't wait until callback returns // don't wait until callback returns
Notification.requestPermission(function(permission) { Notification.requestPermission(function(permission) {
if (permission === "granted") { if (permission === "granted") {
notif.hasPermission = true; self.hasPermission = true;
} }
}); });
} }
var notification = new Notification(options.title, { var notification = new Notification(options.title, {
body: options.message, body: options.message,
icon: cfg.iconPath icon: self._appConfig.config.iconPath
}); });
notification.onclick = function() { notification.onclick = function() {
window.focus(); window.focus();
@ -51,8 +57,6 @@ notif.create = function(options) {
return notification; return notification;
}; };
notif.close = function(notification) { Notif.prototype.close = function(notification) {
notification.close(); 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"> <div class="lightbox__body" ng-controller="DialogCtrl">
<header class="lightbox__header"> <header class="lightbox__header">
<h2>{{state.dialog.title}}</h2> <h2>{{title}}</h2>
<button class="lightbox__close" wo-touch="confirm(false)" data-action="lightbox-close"> <button class="lightbox__close" wo-touch="confirm(false)" data-action="lightbox-close">
<svg><use xlink:href="#icon-close" /><title>Close</title></svg> <svg><use xlink:href="#icon-close" /><title>Close</title></svg>
</button> </button>
</header> </header>
<div class="lightbox__content"> <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> </div>
<footer class="lightbox__controls"> <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 --> <!-- 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)" class="btn" ng-show="!state.writer || !showBugReporter">{{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); state.writer.reportBug()" class="btn" ng-show="state.writer && showBugReporter">Report bug</button>
</footer> </footer>
</div> </div>

View File

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

View File

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