2014-10-02 16:05:44 -04:00
|
|
|
'use strict';
|
2013-09-04 12:39:26 -04:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
var searchTimeout, firstSelect;
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-17 05:17:40 -04:00
|
|
|
//
|
|
|
|
// Constants
|
|
|
|
//
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
var INIT_DISPLAY_LEN = 20,
|
|
|
|
SCROLL_DISPLAY_LEN = 10,
|
2014-10-17 05:17:40 -04:00
|
|
|
FOLDER_TYPE_INBOX = 'Inbox',
|
|
|
|
NOTIFICATION_INBOX_TIMEOUT = 5000;
|
2013-09-04 12:39:26 -04:00
|
|
|
|
2014-12-01 04:04:20 -05:00
|
|
|
var MailListCtrl = function($scope, $timeout, $routeParams, $filter, statusDisplay, notification, email, keychain, dialog, search, dummy) {
|
2014-11-20 16:53:30 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// Init
|
|
|
|
//
|
2013-09-28 10:08:12 -04:00
|
|
|
|
2014-11-20 16:53:30 -05:00
|
|
|
$scope.state.mailList = {};
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* Gathers unread notifications to be cancelled later
|
|
|
|
*/
|
|
|
|
$scope.pendingNotifications = [];
|
2014-02-21 10:22:33 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// scope functions
|
|
|
|
//
|
2014-02-17 08:31:14 -05:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
$scope.getBody = function(message) {
|
|
|
|
email.getBody({
|
2014-10-02 16:05:44 -04:00
|
|
|
folder: currentFolder(),
|
2014-11-20 09:14:39 -05:00
|
|
|
message: message
|
2014-10-02 16:05:44 -04:00
|
|
|
}, function(err) {
|
|
|
|
if (err && err.code !== 42) {
|
2014-11-20 09:14:39 -05:00
|
|
|
dialog.error(err);
|
2013-10-04 12:01:42 -04:00
|
|
|
return;
|
|
|
|
}
|
2013-11-18 11:44:59 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// display fetched body
|
|
|
|
$scope.$digest();
|
2014-07-21 10:05:29 -04:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
// automatically decrypt if it's the selected message
|
|
|
|
if (message === currentMessage()) {
|
|
|
|
email.decryptBody({
|
|
|
|
message: message
|
|
|
|
}, dialog.error);
|
2014-07-21 10:05:29 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
};
|
2014-02-21 04:47:49 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
2014-11-20 09:14:39 -05:00
|
|
|
* Called when clicking on an message list item
|
2014-10-02 16:05:44 -04:00
|
|
|
*/
|
2014-11-20 09:14:39 -05:00
|
|
|
$scope.select = function(message) {
|
2014-10-02 16:05:44 -04:00
|
|
|
// unselect an item
|
2014-11-20 09:14:39 -05:00
|
|
|
if (!message) {
|
2014-10-02 16:05:44 -04:00
|
|
|
$scope.state.mailList.selected = undefined;
|
|
|
|
return;
|
|
|
|
}
|
2014-02-17 08:31:14 -05:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
$scope.state.mailList.selected = message;
|
2013-12-05 12:28:18 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!firstSelect) {
|
|
|
|
// only toggle to read view on 2nd select in mobile mode
|
|
|
|
$scope.state.read.toggle(true);
|
|
|
|
}
|
|
|
|
firstSelect = false;
|
2014-05-23 04:52:34 -04:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
keychain.refreshKeyForUserId({
|
|
|
|
userId: message.from[0].address
|
2014-11-06 08:28:14 -05:00
|
|
|
}, onKeyRefreshed);
|
2014-05-23 04:52:34 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
function onKeyRefreshed(err) {
|
|
|
|
if (err) {
|
2014-11-20 09:14:39 -05:00
|
|
|
dialog.error(err);
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
email.decryptBody({
|
|
|
|
message: message
|
|
|
|
}, dialog.error);
|
2014-10-02 16:05:44 -04:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
// if the message is unread, please sync the new state.
|
2014-10-02 16:05:44 -04:00
|
|
|
// otherweise forget about it.
|
2014-11-20 09:14:39 -05:00
|
|
|
if (!message.unread) {
|
2014-10-02 16:05:44 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// let's close pending notifications for unread messages in the inbox
|
|
|
|
if (currentFolder().type === FOLDER_TYPE_INBOX) {
|
|
|
|
while ($scope.pendingNotifications.length) {
|
|
|
|
notification.close($scope.pendingNotifications.shift());
|
2014-09-16 13:01:49 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
2014-09-16 13:01:49 -04:00
|
|
|
|
2014-11-27 09:15:44 -05:00
|
|
|
$scope.state.actionBar.markMessage(message, false, true);
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
};
|
2013-11-11 09:53:34 -05:00
|
|
|
|
2014-12-02 12:36:15 -05:00
|
|
|
$scope.flag = function(message, flagged) {
|
|
|
|
$scope.state.actionBar.flagMessage(message, flagged);
|
|
|
|
};
|
|
|
|
|
2014-12-01 04:04:20 -05:00
|
|
|
/**
|
|
|
|
* Date formatting
|
|
|
|
*/
|
|
|
|
$scope.formatDate = function(date) {
|
|
|
|
var now = new Date();
|
|
|
|
|
|
|
|
// return time if mail is from today
|
2014-12-01 11:33:09 -05:00
|
|
|
if (now.getDay() === date.getDay() && now.getMonth() === date.getMonth() && now.getFullYear() === date.getFullYear()) {
|
2014-12-01 04:04:20 -05:00
|
|
|
return $filter('date')(date, 'shortTime');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $filter('date')(date, 'mediumDate');
|
|
|
|
};
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// watch tasks
|
|
|
|
//
|
2013-12-09 13:21:52 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
2014-11-20 09:14:39 -05:00
|
|
|
* List messages from folder when user changes folder
|
2014-10-02 16:05:44 -04:00
|
|
|
*/
|
|
|
|
$scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
|
|
|
|
if (!currentFolder()) {
|
|
|
|
return;
|
|
|
|
}
|
2013-12-09 13:21:52 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// reset searchFilter
|
|
|
|
$scope.searchText = undefined;
|
2013-10-10 13:15:16 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// in development, display dummy mail objects
|
|
|
|
if ($routeParams.dev) {
|
2014-11-20 09:14:39 -05:00
|
|
|
statusDisplay.update('Last update: ', new Date());
|
2014-11-26 06:59:44 -05:00
|
|
|
currentFolder().messages = dummy.listMails();
|
2014-10-02 16:05:44 -04:00
|
|
|
return;
|
|
|
|
}
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// display and select first
|
|
|
|
openCurrentFolder();
|
|
|
|
});
|
2013-11-21 11:37:07 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
$scope.watchMessages = $scope.$watchCollection('state.nav.currentFolder.messages', function(messages) {
|
|
|
|
if (!messages) {
|
|
|
|
return;
|
|
|
|
}
|
2013-09-04 12:39:26 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// sort message by uid
|
|
|
|
currentFolder().messages.sort(byUidDescending);
|
|
|
|
// set display buffer to first messages
|
|
|
|
$scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN);
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// Shows the next message based on the uid of the currently selected element
|
|
|
|
if (currentFolder().messages.indexOf(currentMessage()) === -1) {
|
|
|
|
firstSelect = true; // reset first selection
|
|
|
|
$scope.select($scope.displayMessages[0]);
|
|
|
|
}
|
|
|
|
});
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* display more items (for infinite scrolling)
|
|
|
|
*/
|
|
|
|
$scope.displayMore = function() {
|
|
|
|
if (!currentFolder() || !$scope.displayMessages) {
|
|
|
|
// folders not yet initialized
|
|
|
|
return;
|
|
|
|
}
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
var len = currentFolder().messages.length,
|
|
|
|
dLen = $scope.displayMessages.length;
|
2014-07-01 13:49:19 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
if (dLen === len || $scope.searchText) {
|
|
|
|
// all messages are already displayed or we're in search mode
|
|
|
|
return;
|
|
|
|
}
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// copy next interval of messages to the end of the display messages array
|
|
|
|
var next = currentFolder().messages.slice(dLen, dLen + SCROLL_DISPLAY_LEN);
|
|
|
|
Array.prototype.push.apply($scope.displayMessages, next);
|
|
|
|
};
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-12-01 11:33:09 -05:00
|
|
|
/**
|
|
|
|
* Handle search event in other parts of the app by filtering messages in the mail-list
|
|
|
|
*/
|
|
|
|
$scope.$on('search', function(e, query) {
|
|
|
|
$scope.displaySearchResults(query);
|
|
|
|
});
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* This method is called when the user changes the searchText
|
|
|
|
*/
|
|
|
|
$scope.displaySearchResults = function(searchText) {
|
|
|
|
if (searchTimeout) {
|
|
|
|
// remove timeout to wait for user typing query
|
|
|
|
clearTimeout(searchTimeout);
|
|
|
|
}
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!searchText) {
|
|
|
|
// set display buffer to first messages
|
|
|
|
$scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN);
|
2014-11-20 09:14:39 -05:00
|
|
|
statusDisplay.setSearching(false);
|
|
|
|
statusDisplay.update('Online');
|
2014-10-02 16:05:44 -04:00
|
|
|
return;
|
|
|
|
}
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// display searching spinner
|
2014-11-20 09:14:39 -05:00
|
|
|
statusDisplay.setSearching(true);
|
|
|
|
statusDisplay.update('Searching ...');
|
2014-10-02 16:05:44 -04:00
|
|
|
searchTimeout = setTimeout(function() {
|
|
|
|
$scope.$apply(function() {
|
|
|
|
// filter relevant messages
|
2014-11-26 06:59:44 -05:00
|
|
|
$scope.displayMessages = search.filter(currentFolder().messages, searchText);
|
2014-11-20 09:14:39 -05:00
|
|
|
statusDisplay.setSearching(false);
|
|
|
|
statusDisplay.update('Matches in this folder');
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}, 500);
|
|
|
|
};
|
2014-07-16 06:47:25 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* Sync current folder when client comes back online
|
|
|
|
*/
|
|
|
|
$scope.watchOnline = $scope.$watch('account.online', function(isOnline) {
|
2014-11-26 15:12:40 -05:00
|
|
|
// wait one cycle for the status display controllers to init
|
|
|
|
$timeout(function() {
|
|
|
|
if (isOnline) {
|
|
|
|
statusDisplay.update('Online');
|
|
|
|
openCurrentFolder();
|
|
|
|
} else {
|
|
|
|
statusDisplay.update('Offline mode');
|
|
|
|
}
|
|
|
|
});
|
2014-10-02 16:05:44 -04:00
|
|
|
}, true);
|
2013-09-30 15:22:46 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// Helper Functions
|
|
|
|
//
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
function openCurrentFolder() {
|
2014-10-22 11:21:28 -04:00
|
|
|
if (!currentFolder()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
email.openFolder({
|
2014-10-02 16:05:44 -04:00
|
|
|
folder: currentFolder()
|
|
|
|
}, function(error) {
|
|
|
|
// dont wait until scroll to load visible mail bodies
|
|
|
|
$scope.loadVisibleBodies();
|
2014-05-08 10:25:20 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// don't display error for offline case
|
|
|
|
if (error && error.code === 42) {
|
|
|
|
return;
|
|
|
|
}
|
2014-11-20 09:14:39 -05:00
|
|
|
dialog.error(error);
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}
|
2014-05-08 10:25:20 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
function currentFolder() {
|
2014-11-26 11:57:14 -05:00
|
|
|
return $scope.state.nav && $scope.state.nav.currentFolder;
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
2014-05-08 10:25:20 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
function currentMessage() {
|
|
|
|
return $scope.state.mailList.selected;
|
|
|
|
}
|
2014-05-08 10:25:20 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// Notification API
|
|
|
|
//
|
2014-05-08 10:25:20 -04:00
|
|
|
|
2014-11-20 09:14:39 -05:00
|
|
|
(email || {}).onIncomingMessage = function(msgs) {
|
2014-10-02 16:05:44 -04:00
|
|
|
var note, title, message, unreadMsgs;
|
2014-05-08 10:25:20 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
unreadMsgs = msgs.filter(function(msg) {
|
|
|
|
return msg.unread;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (unreadMsgs.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
2014-09-16 13:01:49 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
if (unreadMsgs.length === 1) {
|
|
|
|
title = unreadMsgs[0].from[0].name || unreadMsgs[0].from[0].address;
|
|
|
|
message = unreadMsgs[0].subject;
|
|
|
|
} else {
|
|
|
|
title = unreadMsgs.length + ' new messages';
|
|
|
|
message = _.pluck(unreadMsgs, 'subject').join('\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
note = notification.create({
|
|
|
|
title: title,
|
|
|
|
message: message,
|
|
|
|
onClick: function() {
|
|
|
|
// force toggle into read mode when notification is clicked
|
|
|
|
firstSelect = false;
|
|
|
|
|
|
|
|
// remove from pending notificatiosn
|
|
|
|
var index = $scope.pendingNotifications.indexOf(note);
|
|
|
|
if (index !== -1) {
|
|
|
|
$scope.pendingNotifications.splice(index, 1);
|
2014-09-16 13:01:49 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
|
|
|
|
// mark message as read
|
|
|
|
$scope.select(_.findWhere(currentFolder().messages, {
|
|
|
|
uid: unreadMsgs[0].uid
|
|
|
|
}));
|
2014-10-17 05:17:40 -04:00
|
|
|
},
|
|
|
|
timeout: NOTIFICATION_INBOX_TIMEOUT
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
$scope.pendingNotifications.push(note);
|
2013-09-26 07:26:57 -04:00
|
|
|
};
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
2013-09-05 05:53:14 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// Directives
|
|
|
|
//
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
var ngModule = angular.module('mail-list', []);
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
ngModule.directive('listScroll', function() {
|
|
|
|
return {
|
|
|
|
link: function(scope, elm, attrs) {
|
|
|
|
var model = attrs.listScroll,
|
|
|
|
listEl = elm[0],
|
|
|
|
scrollTimeout;
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/*
|
|
|
|
* iterates over the mails in the mail list and loads their bodies if they are visible in the viewport
|
|
|
|
*/
|
|
|
|
scope.loadVisibleBodies = function() {
|
|
|
|
var listBorder = listEl.getBoundingClientRect(),
|
|
|
|
top = listBorder.top,
|
|
|
|
bottom = listBorder.bottom,
|
|
|
|
listItems = listEl.children[0].children,
|
|
|
|
inViewport = false,
|
|
|
|
listItem, message,
|
|
|
|
isPartiallyVisibleTop, isPartiallyVisibleBottom, isVisible,
|
|
|
|
displayMessages = scope[model];
|
|
|
|
|
|
|
|
if (!top && !bottom) {
|
|
|
|
// list not visible
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0, len = listItems.length; i < len; i++) {
|
2014-11-20 09:14:39 -05:00
|
|
|
// the n-th list item (the dom representation of an message) corresponds to
|
2014-10-02 16:05:44 -04:00
|
|
|
// the n-th message model in the filteredMessages array
|
|
|
|
listItem = listItems.item(i).getBoundingClientRect();
|
|
|
|
|
|
|
|
if (!displayMessages || displayMessages.length <= i) {
|
|
|
|
// stop if i get larger than the size of filtered messages
|
|
|
|
break;
|
2014-05-23 08:23:50 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
message = displayMessages[i];
|
|
|
|
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
isPartiallyVisibleTop = listItem.top < top && listItem.bottom > top; // a portion of the list item is visible on the top
|
|
|
|
isPartiallyVisibleBottom = listItem.top < bottom && listItem.bottom > bottom; // a portion of the list item is visible on the bottom
|
|
|
|
isVisible = (listItem.top || listItem.bottom) && listItem.top >= top && listItem.bottom <= bottom; // the list item is visible as a whole
|
|
|
|
|
|
|
|
if (isPartiallyVisibleTop || isVisible || isPartiallyVisibleBottom) {
|
|
|
|
// we are now iterating over visible elements
|
|
|
|
inViewport = true;
|
|
|
|
// load mail body of visible
|
|
|
|
scope.getBody(message);
|
|
|
|
} else if (inViewport) {
|
|
|
|
// we are leaving the viewport, so stop iterating over the items
|
|
|
|
break;
|
2014-06-16 08:52:23 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
};
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// load body when scrolling
|
|
|
|
listEl.onscroll = function() {
|
|
|
|
if (scrollTimeout) {
|
|
|
|
// remove timeout so that only scroll end
|
|
|
|
clearTimeout(scrollTimeout);
|
|
|
|
}
|
|
|
|
scrollTimeout = setTimeout(function() {
|
2014-05-23 08:23:50 -04:00
|
|
|
scope.loadVisibleBodies();
|
2014-10-02 16:05:44 -04:00
|
|
|
}, 300);
|
|
|
|
};
|
2014-05-23 08:23:50 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// load the visible message bodies, when the list is re-initialized and when scrolling stopped
|
|
|
|
scope.$watchCollection(model, function() {
|
|
|
|
scope.loadVisibleBodies();
|
|
|
|
});
|
2014-07-16 06:47:25 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
function byUidDescending(a, b) {
|
|
|
|
if (a.uid < b.uid) {
|
|
|
|
return 1;
|
|
|
|
} else if (b.uid < a.uid) {
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
2014-07-16 06:47:25 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
|
2014-10-08 06:34:34 -04:00
|
|
|
module.exports = MailListCtrl;
|