perf(mail(js)): various optimizations

We now use IMAP QRESYNC to synchronize mailbox.
pull/299/head
Francis Lachapelle 2021-06-15 17:13:32 -04:00
parent b4ccafd546
commit a9c6f09273
16 changed files with 362 additions and 172 deletions

View File

@ -105,7 +105,14 @@
matchingSyncToken: (NSString *) theSyncToken
fromDate: (NSCalendarDate *) theStartDate
initialLoad: (BOOL) initialLoadInProgress;
/* flags */
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) theProperties
matchingSyncToken: (NSString *) theSyncToken
fromDate: (NSCalendarDate *) theStartDate
initialLoad: (BOOL) initialLoadInProgress
sortOrdering: (id) theSortOrdering
threaded: (BOOL) isThreaded;
/* flags */
- (NSException *) addFlagsToAllMessages: (id) _f;

View File

@ -2259,6 +2259,21 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
matchingSyncToken: (NSString *) theSyncToken
fromDate: (NSCalendarDate *) theStartDate
initialLoad: (BOOL) initialLoadInProgress
{
return [self syncTokenFieldsWithProperties: theProperties
matchingSyncToken: theSyncToken
fromDate: theStartDate
initialLoad: initialLoadInProgress
sortOrdering: nil
threaded: NO];
}
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) theProperties
matchingSyncToken: (NSString *) theSyncToken
fromDate: (NSCalendarDate *) theStartDate
initialLoad: (BOOL) initialLoadInProgress
sortOrdering: (id) theSortOrdering
threaded: (BOOL) isThreaded
{
EOQualifier *searchQualifier;
NSMutableArray *allTokens;
@ -2316,17 +2331,19 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
// we fetch modified or added uids
uids = [self fetchUIDsMatchingQualifier: searchQualifier
sortOrdering: nil];
sortOrdering: theSortOrdering];
fetchResults = [(NSDictionary *)[self fetchUIDs: uids
parts: [NSArray arrayWithObjects: @"modseq", @"flags", nil]]
objectForKey: @"fetch"];
/* NOTE: we sort items manually because Cyrus does not properly sort
entries with a MODSEQ of 0 */
fetchResults
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
context: NULL];
if (theSortOrdering == nil)
{
/* NOTE: we sort items manually because Cyrus does not properly sort
entries with a MODSEQ of 0 */
fetchResults = [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
context: NULL];
}
for (i = 0; i < [fetchResults count]; i++)
{

View File

@ -40,6 +40,7 @@
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGImap4/NGImap4Connection.h>
#import <NGImap4/NGImap4Envelope.h>
#import <EOControl/EOQualifier.h>
@ -489,7 +490,7 @@
}
else
fetchQualifier = notDeleted;
sortedUIDs = [mailFolder fetchUIDsMatchingQualifier: fetchQualifier
sortOrdering: [self imap4SortOrdering]
threaded: sortByThread];
@ -648,7 +649,7 @@
int count;
data = [NSMutableDictionary dictionary];
// TODO: we might want to flush the caches?
//[folder flushMailCaches];
[folder expungeLastMarkedFolder];
@ -661,6 +662,16 @@
return nil;
}
// We first make sure QRESYNC is enabled
if (![[folder imap4Connection] enableExtensions: [NSArray arrayWithObject: @"QRESYNC"]])
{
NSString *tag = [folder davCollectionTag];
if (![tag isEqualToString: @"-1"])
{
[data setObject: tag forKey: @"syncToken"];
}
}
// Get rid of the extra parenthesis
// uids = [[[[uids stringValue] stringByReplacingOccurrencesOfString:@"(" withString:@""] stringByReplacingOccurrencesOfString:@")" withString:@""] componentsSeparatedByString:@","];
@ -786,6 +797,72 @@
return response;
}
- (id <WOActionResults>) getChangesAction
{
NSArray *changedMessages, *headers;
NSDictionary *requestContent, *data, *changedMessage;
NSMutableArray *changedUids, *deletedUids;
NSString *syncToken, *newSyncToken, *uid;
SOGoMailFolder *folder;
WORequest *request;
WOResponse *response;
int i, max;
request = [context request];
requestContent = [[request contentAsString] objectFromJSONString];
response = nil;
folder = [self clientObject];
syncToken = [requestContent objectForKey: @"syncToken"];
newSyncToken = [folder davCollectionTag];
if ([syncToken length] && ![syncToken isEqual: newSyncToken])
{
// Fetch list of changed uids
changedMessages = [folder syncTokenFieldsWithProperties: nil
matchingSyncToken: syncToken
fromDate: nil
initialLoad: NO
sortOrdering: [self imap4SortOrdering]
threaded: sortByThread];
if ((max = [changedMessages count]))
{
// Split new or modified uids from deleted uids
changedUids = [NSMutableArray array];
deletedUids = [NSMutableArray array];
for (i = 0; i < max; i++)
{
changedMessage = [changedMessages objectAtIndex: i];
uid = [[changedMessage allKeys] lastObject];
if ([[changedMessage objectForKey: uid] isEqual: [NSNull null]])
[deletedUids addObject: uid];
else
[changedUids addObject: uid];
}
// Fetch headers for new or modified messages
headers = [self getHeadersForUIDs: changedUids
inFolder: folder];
data = [NSDictionary dictionaryWithObjectsAndKeys:
changedUids, @"changed",
deletedUids, @"deleted",
headers, @"headers",
newSyncToken, @"syncToken",
nil];
response = [self responseWithStatus: 200 andJSONRepresentation: data];
}
}
if (!response)
{
data = [NSDictionary dictionaryWithObjectsAndKeys:
newSyncToken, @"syncToken",
nil];
response = [self responseWithStatus: 200 andJSONRepresentation: data];
}
return response;
}
- (NSArray *) getHeadersForUIDs: (NSArray *) uids
inFolder: (SOGoMailFolder *) mailFolder
{

View File

@ -19,6 +19,11 @@
actionClass = "UIxMailListActions";
actionName = "getUIDs";
};
changes = {
protectedBy = "View";
actionClass = "UIxMailListActions";
actionName = "getChanges";
};
headers = {
protectedBy = "View";
actionClass = "UIxMailListActions";

View File

@ -156,7 +156,7 @@
<md-button class="sg-icon-button" ng-click="mailbox.unselectMessages()">
<md-icon>arrow_back</md-icon>
</md-button>
<label class="md-truncate"><span ng-bind="mailbox.service.selectedFolder.$selectedCount()"><!-- count --></span> <var:string label:value="selected"/></label>
<label class="md-truncate"><span ng-bind="mailbox.service.selectedFolder.selectedCount()"><!-- count --></span> <var:string label:value="selected"/></label>
<div class="md-flex"><!-- spacer --></div>
<md-button class="sg-icon-button" ng-click="mailbox.selectAll()">
<md-tooltip md-direction="bottom"><var:string label:value="Select All"/></md-tooltip>
@ -286,8 +286,8 @@
<md-list class="sg-section-list"
ng-class="{ 'sg-list-selectable': mailbox.mode.multiple }"
sg-draggable="mailbox.selectedFolder"
sg-drag-start="mailbox.selectedFolder.hasSelectedMessage() || mailbox.selectedFolder.$selectedCount()"
sg-drag-count="mailbox.selectedFolder.$selectedCount()">
sg-drag-start="mailbox.selectedFolder.hasSelectedMessage() || mailbox.selectedFolder.selectedCount()"
sg-drag-count="mailbox.selectedFolder.selectedCount()">
<md-list-item
aria-label="{{currentMessage.subject}}"
class="sg-message-list-item"

View File

@ -42,6 +42,7 @@
$Preferences: Preferences,
$query: { sort: 'arrival', asc: 0 }, // The default sort must match [UIxMailListActions defaultSortKey]
selectedFolder: null,
$selectedMessages: [],
$refreshTimeout: null,
$virtualMode: false,
$virtualPath: false,
@ -265,9 +266,8 @@
if (index >= 0 && index < visibleMessages.length) {
message = visibleMessages[index];
this.$lastVisibleIndex = Math.max(0, index - 3); // Magic number is NUM_EXTRA from virtual-repeater.js
if (this.$loadMessage(message.uid))
return message;
this.$loadMessage(message.uid);
return message; // skeleton is displayed while headers are being fetched
}
return null;
};
@ -283,23 +283,25 @@
};
/**
* @function $selectedMessages
* @function selectedMessages
* @memberof Mailbox.prototype
* @desc Return the messages selected by the user.
* @returns Message instances
*/
Mailbox.prototype.$selectedMessages = function() {
return _.filter(this.$messages, function(message) { return message.selected; });
Mailbox.prototype.selectedMessages = function(options) {
if (options && options.updateCache)
this.$selectedMessages = _.filter(this.$messages, function(message) { return message.selected; });
return this.$selectedMessages;
};
/**
* @function $selectedCount
* @function selectedCount
* @memberof Mailbox.prototype
* @desc Return the number of messages selected by the user.
* @returns the number of selected messages
*/
Mailbox.prototype.$selectedCount = function() {
return this.$selectedMessages().length;
Mailbox.prototype.selectedCount = function() {
return this.$selectedMessages.length;
};
/**
@ -308,9 +310,10 @@
* @desc Unselect all messages.
*/
Mailbox.prototype.$unselectMessages = function() {
_.forEach(this.$selectedMessages(), function(message) {
_.forEach(this.$selectedMessages, function(message) {
message.selected = false;
});
this.$selectedMessages = [];
};
/**
@ -321,7 +324,7 @@
* @returns true if the specified message is displayed
*/
Mailbox.prototype.isSelectedMessage = function(messageId) {
return this.selectedMessage == messageId;
return this.$selectedMessage == messageId;
};
/**
@ -330,10 +333,9 @@
* @desc Return the currently visible message.
* @returns a Message instance or undefined if no message is displayed
*/
Mailbox.prototype.$selectedMessage = function() {
Mailbox.prototype.selectedMessage = function() {
var _this = this;
return _.find(this.$messages, function(message) { return message.uid == _this.selectedMessage; });
return _.find(this.$messages, function(message) { return message.uid == _this.$selectedMessage; });
};
/**
@ -343,7 +345,7 @@
* @returns a number or undefined if no message is selected
*/
Mailbox.prototype.$selectedMessageIndex = function() {
return this.uidsMap[this.selectedMessage];
return this.uidsMap[this.$selectedMessage];
};
/**
@ -353,7 +355,7 @@
* @returns true if the a message is selected
*/
Mailbox.prototype.hasSelectedMessage = function() {
return angular.isDefined(this.selectedMessage);
return angular.isDefined(this.$selectedMessage);
};
/**
@ -371,7 +373,7 @@
* @returns a promise of the HTTP operation
*/
Mailbox.prototype.$filter = function(sortingAttributes, filters) {
var _this = this, options = {};
var _this = this, action = 'view', options = {};
if (!angular.isDefined(this.unseenCount))
this.unseenCount = 0;
@ -403,6 +405,10 @@
}
});
}
else if (!sortingAttributes && this.$syncToken) {
action = 'changes';
options.syncToken = this.$syncToken;
}
// Restart the refresh timer, if needed
if (!Mailbox.$virtualMode) {
@ -413,14 +419,14 @@
}
}
var futureMailboxData = Mailbox.$$resource.post(this.id, 'view', options);
var futureMailboxData = Mailbox.$$resource.post(this.id, action, options);
return this.$unwrap(futureMailboxData);
};
/**
* @function $loadMessage
* @memberof Mailbox.prototype
* @desc Check if the message is loaded and in any case, fetch more messages headers from the server.
* @desc Check if the message headers are loaded and in any case, fetch more messages headers from the server.
* @returns true if the message metadata are already fetched
*/
Mailbox.prototype.$loadMessage = function(messageId) {
@ -765,8 +771,8 @@
if (selectedIndex > -1) {
uids.splice(selectedIndex, 1);
delete _this.uidsMap[message.uid];
if (message.uid == _this.selectedMessage)
delete _this.selectedMessage;
if (message.uid == _this.$selectedMessage)
delete _this.$selectedMessage;
_this.$messages.splice(index, 1);
if (index < firstIndex)
firstIndex = index;
@ -813,7 +819,10 @@
});
}
return _deleteMessages(0, Math.min(batchSize, uids.length));
return _deleteMessages(0, Math.min(batchSize, uids.length)).then(function(firstIndex) {
_this.$selectedMessages = []; // reset selection
return firstIndex;
});
};
/**
@ -860,6 +869,7 @@
uids = _.map(messages, 'uid');
return Mailbox.$$resource.post(this.id, 'moveMessages', {uids: uids, folder: folder})
.then(function() {
_this.$selectedMessages = []; // reset selection
return _this.$_deleteMessages(uids, messages);
});
};
@ -959,12 +969,48 @@
this.$futureMailboxData = futureMailboxData;
this.$futureMailboxData.then(function(data) {
var selectedMessages = _.map(_this.$selectedMessages(), 'uid');
var selectedMessages = _.map(_this.$selectedMessages, 'uid');
Mailbox.$timeout(function() {
var uids, headers, headersFields;
var uids, headers, headersFields, msgObject;
if (!data.uids || _this.$topIndex > data.uids.length - 1)
_this.$topIndex = 0;
if (data.syncToken)
_this.$syncToken = data.syncToken;
if (data.deleted) {
var deletedMessages = [];
_.forEachRight(data.deleted, function(uid, i) {
var j = _this.uidsMap[uid.toString()];
if (j >= 0 && _this.$messages[j])
deletedMessages.push(_this.$messages[j]);
else
// Unkown message
data.deleted.splice(i, 1);
});
if (deletedMessages.length)
_this.$_deleteMessages(data.deleted, deletedMessages);
}
if (data.changed) {
var i = 0, j;
_.forEach(data.changed, function(uid) {
if (angular.isUndefined(_this.uidsMap[uid.toString()])) {
// New messsage; update map of UID <=> index
_this.uidsMap[uid] = i;
msgObject = new Mailbox.$Message(_this.$account.id, _this, {uid: uid}, true);
_this.$messages.splice(i, 0, msgObject);
i++;
}
});
if (i > 0) {
// New messages received, update uidsMap
for (j = i; j < _this.$messages.length; j++) {
msgObject = _this.$messages[j];
_this.uidsMap[msgObject.uid] += i;
}
}
}
if (data.uids) {
// Initialization phase, we received complete list of UIDs

View File

@ -166,9 +166,9 @@
this.cancelSearch = function() {
vm.mode.search = false;
vm.selectedFolder.$filter().then(function() {
if (vm.selectedFolder.selectedMessage) {
if (vm.selectedFolder.$selectedMessage) {
$timeout(function() {
vm.selectedFolder.$topIndex = vm.selectedFolder.uidsMap[vm.selectedFolder.selectedMessage];
vm.selectedFolder.$topIndex = vm.selectedFolder.uidsMap[vm.selectedFolder.$selectedMessage];
});
}
});
@ -320,14 +320,14 @@
selectedIndex, nextSelectedIndex, i;
if (!message)
message = folder.$selectedMessage();
message = folder.selectedMessage();
if (!message)
return true;
message.selected = !message.selected;
vm.mode.multiple += message.selected? 1 : -1;
// Select closest range of messages when shift key is pressed
if ($event.shiftKey && folder.$selectedCount() > 1) {
if ($event.shiftKey && folder.selectedCount() > 0) {
selectedIndex = folder.uidsMap[message.uid];
// Search for next selected message above
nextSelectedIndex = selectedIndex - 2;
@ -349,6 +349,8 @@
}
}
folder.selectedMessages({ updateCache: true });
vm.mode.multiple = vm.selectedFolder.selectedCount();
$event.preventDefault();
$event.stopPropagation();
};
@ -368,7 +370,7 @@
// This function must not be called in virtual mode.
function _unselectMessage(message, index) {
var nextMessage, previousMessage, nextIndex = index;
vm.mode.multiple = vm.selectedFolder.$selectedCount();
vm.mode.multiple = vm.selectedFolder.selectedCount();
if (message) {
// Select either the next or previous message
if (index > 0) {
@ -404,7 +406,7 @@
}
this.confirmDeleteSelectedMessages = function($event) {
var selectedMessages = vm.selectedFolder.$selectedMessages();
var selectedMessages = vm.selectedFolder.selectedMessages();
if (vm.messageDialog === null && _.size(selectedMessages) > 0)
vm.messageDialog = Dialog.confirm(l('Confirmation'),
@ -456,9 +458,10 @@
this.markOrUnMarkMessagesAsJunk = function() {
var moveSelectedMessage = vm.selectedFolder.hasSelectedMessage();
var selectedMessages = vm.selectedFolder.$selectedMessages();
var selectedMessages = vm.selectedFolder.selectedMessages();
if (_.size(selectedMessages) === 0 && moveSelectedMessage)
selectedMessages = [vm.selectedFolder.$selectedMessage()];
// No selection, user has pressed keyboard shortcut
selectedMessages = [vm.selectedFolder.selectedMessage()];
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$markOrUnMarkMessagesAsJunk(selectedMessages).then(function() {
var dstFolder = '/' + vm.account.id + '/folderINBOX';
@ -481,12 +484,12 @@
};
this.copySelectedMessages = function(dstFolder) {
var selectedMessages = vm.selectedFolder.$selectedMessages();
var selectedMessages = vm.selectedFolder.selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$copyMessages(selectedMessages, '/' + dstFolder).then(function() {
$mdToast.show(
$mdToast.simple()
.textContent(l('%{0} message(s) copied', vm.selectedFolder.$selectedCount()))
.textContent(l('%{0} message(s) copied', vm.selectedFolder.selectedCount()))
.position('top right')
.hideDelay(2000));
});
@ -494,8 +497,8 @@
this.moveSelectedMessages = function(dstFolder) {
var moveSelectedMessage = vm.selectedFolder.hasSelectedMessage();
var selectedMessages = vm.selectedFolder.$selectedMessages();
var count = vm.selectedFolder.$selectedCount();
var selectedMessages = vm.selectedFolder.selectedMessages();
var count = vm.selectedFolder.selectedCount();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$moveMessages(selectedMessages, '/' + dstFolder).then(function(index) {
$mdToast.show(
@ -520,8 +523,11 @@
var count = 0;
_.forEach(_currentMailboxes(), function(folder) {
var i = 0, length = folder.$messages.length;
for (; i < length; i++)
folder.$selectedMessages = [];
for (; i < length; i++) {
folder.$messages[i].selected = true;
folder.$selectedMessages.push(folder.$messages[i]);
}
count += length;
});
vm.mode.multiple = count;
@ -529,6 +535,7 @@
this.unselectMessages = function() {
_.forEach(_currentMailboxes(), function(folder) {
folder.$selectedMessages = [];
_.forEach(folder.$messages, function(message) {
message.selected = false;
});
@ -537,7 +544,7 @@
};
this.markSelectedMessagesAsFlagged = function() {
var selectedMessages = vm.selectedFolder.$selectedMessages();
var selectedMessages = vm.selectedFolder.selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$flagMessages(selectedMessages, '\\Flagged', 'add').then(function(messages) {
_.forEach(messages, function(message) {
@ -547,7 +554,7 @@
};
this.markSelectedMessagesAsUnread = function() {
var selectedMessages = vm.selectedFolder.$selectedMessages();
var selectedMessages = vm.selectedFolder.selectedMessages();
if (_.size(selectedMessages) > 0) {
vm.selectedFolder.$flagMessages(selectedMessages, 'seen', 'remove').then(function(messages) {
_.forEach(messages, function(message) {
@ -560,7 +567,7 @@
};
this.markSelectedMessagesAsRead = function() {
var selectedMessages = vm.selectedFolder.$selectedMessages();
var selectedMessages = vm.selectedFolder.selectedMessages();
if (_.size(selectedMessages) > 0) {
vm.selectedFolder.$flagMessages(selectedMessages, 'seen', 'add').then(function(messages) {
_.forEach(messages, function(message) {

View File

@ -321,11 +321,11 @@
var dstId, messages, uids, clearMessageView, promise, success;
dstId = '/' + dstFolder.id;
messages = srcFolder.$selectedMessages();
messages = srcFolder.selectedMessages();
if (messages.length === 0)
messages = [srcFolder.$selectedMessage()];
messages = [srcFolder.selectedMessage()];
uids = _.map(messages, 'uid');
clearMessageView = (srcFolder.selectedMessage && uids.indexOf(srcFolder.selectedMessage) >= 0);
clearMessageView = (srcFolder.$selectedMessage && uids.indexOf(srcFolder.$selectedMessage) >= 0);
if (mode == 'copy') {
promise = srcFolder.$copyMessages(messages, dstId);

View File

@ -317,7 +317,7 @@
return messageObject.uid == parseInt($stateParams.messageId);
});
if (message) {
if (message && message.$reload) {
return message.$reload({useCache: true});
}
else {
@ -331,7 +331,7 @@
*/
onEnterMessage.$inject = ['$stateParams', 'stateMailbox'];
function onEnterMessage($stateParams, stateMailbox) {
stateMailbox.selectedMessage = parseInt($stateParams.messageId);
stateMailbox.$selectedMessage = parseInt($stateParams.messageId);
}
/**
@ -339,7 +339,7 @@
*/
onExitMessage.$inject = ['stateMailbox'];
function onExitMessage(stateMailbox) {
delete stateMailbox.selectedMessage;
delete stateMailbox.$selectedMessage;
}
/**

View File

@ -26,8 +26,10 @@
this.init(futureMessageData);
}
this.uid = parseInt(futureMessageData.uid);
this.selected = !!futureMessageData.selected;
this.level = parseInt(futureMessageData.level);
this.first = parseInt(futureMessageData.first) === 1;
this.flags = [];
if (this.first) {
this.threadCount = parseInt(futureMessageData.count);
this.collapsed = (futureMessageData.collapsed === true);
@ -61,6 +63,8 @@
// Initialize tags form user's defaults
if (Preferences.defaults.SOGoMailLabelsColors) {
Message.$tags = Preferences.defaults.SOGoMailLabelsColors;
} else {
Message.$tags = {};
}
if (Preferences.defaults.SOGoMailDisplayRemoteInlineImages &&
Preferences.defaults.SOGoMailDisplayRemoteInlineImages == 'always') {

View File

@ -148,7 +148,7 @@
keys.push(sgHotkeys.createHotkey({
key: hotkey,
callback: _unlessInDialog(function($event) {
if (vm.mailbox.$selectedCount() === 0)
if (vm.mailbox.selectedCount() === 0)
vm.deleteMessage();
$event.preventDefault();
}),
@ -371,7 +371,7 @@
else {
state.go('mail.account.mailbox').then(function() {
message = null;
delete mailbox.selectedMessage;
delete mailbox.$selectedMessage;
});
}
}
@ -446,7 +446,7 @@
var destination = Mailbox.$virtualMode ? 'mail.account.virtualMailbox' : 'mail.account.mailbox';
$state.go(destination).then(function() {
vm.message = null;
delete stateMailbox.selectedMessage;
delete stateMailbox.$selectedMessage;
});
};

View File

@ -122,7 +122,7 @@
*/
VirtualMailbox.prototype.resetSelectedMessage = function() {
_.forEach(this.$mailboxes, function(mailbox) {
delete mailbox.selectedMessage;
delete mailbox.$selectedMessage;
});
};
@ -134,7 +134,7 @@
*/
VirtualMailbox.prototype.hasSelectedMessage = function() {
return angular.isDefined(_.find(this.$mailboxes, function(mailbox) {
return angular.isDefined(mailbox.selectedMessage);
return angular.isDefined(mailbox.$selectedMessage);
}));
};
@ -148,7 +148,7 @@
*/
VirtualMailbox.prototype.isSelectedMessage = function(messageId, mailboxPath) {
return angular.isDefined(_.find(this.$mailboxes, function(mailbox) {
return mailbox.path == mailboxPath && mailbox.selectedMessage == messageId;
return mailbox.path == mailboxPath && mailbox.$selectedMessage == messageId;
}));
};
@ -216,7 +216,7 @@
VirtualMailbox.prototype.$selectedMessageIndex = function() {
var offset = 0;
var selectedMailbox = _.find(this.$mailboxes, function(mailbox) {
if (angular.isDefined(mailbox.selectedMessage)) {
if (angular.isDefined(mailbox.$selectedMessage)) {
return true;
}
else {
@ -224,7 +224,7 @@
return false;
}
});
return offset + selectedMailbox.uidsMap[selectedMailbox.selectedMessage];
return offset + selectedMailbox.uidsMap[selectedMailbox.$selectedMessage];
};
/**
@ -233,23 +233,23 @@
* @desc Return an associative array of the selected messages for each mailbox. Keys are the mailboxes ids.
* @returns an associative array
*/
VirtualMailbox.prototype.$selectedMessages = function() {
VirtualMailbox.prototype.selectedMessages = function() {
var messagesMap = {};
return _.filter(_.transform(this.$mailboxes, function(messagesMap, mailbox) {
messagesMap[mailbox.id] = mailbox.$selectedMessages();
messagesMap[mailbox.id] = mailbox.$selectedMessages;
}, {}), function(o) {
return _.size(o) > 0;
});
};
/**
* @function $selectedCount
* @function selectedCount
* @memberof VirtualMailbox.prototype
* @desc Return the number of messages selected by the user.
* @returns the number of selected messages
*/
VirtualMailbox.prototype.$selectedCount = function() {
return _.sum(_.invokeMap(this.$mailboxes, '$selectedCount'));
VirtualMailbox.prototype.selectedCount = function() {
return _.sum(_.invokeMap(this.$mailboxes, 'selectedCount'));
};
/**

View File

@ -28,7 +28,7 @@
this.$onInit = function () {
var watchedAttrs = ['uid', 'isread', 'isflagged', 'flags', 'subject'];
var watchedAttrs = ['uid', 'isread', 'isflagged', 'flags', 'subject', 'loading'];
// this.service = Message;
this.MailboxService = Mailbox;
@ -52,6 +52,11 @@
this.onUpdate = function () {
if (this.message.loading) {
$element.addClass('sg-skeleton');
return;
}
$element.removeClass('sg-skeleton');
// Is the message unread?
if (this.message.isread)
$element.removeClass('unread');

View File

@ -121,71 +121,73 @@
var i;
$ctrl.message = $ctrl.parentController.message;
// Flags
var flagList = $element[0].querySelector('.sg-category-dot-container'),
$flagList = angular.element(flagList),
flagElements = $mdUtil.nodesToArray(flagList.querySelectorAll('.sg-category-dot'));
_.forEach(flagElements, function(flagElement) {
flagList.removeChild(flagElement);
});
for (i = 0; i < $ctrl.message.flags.length && i < 5; i++) {
var tag = $ctrl.message.flags[i];
if ($ctrl.service.$tags[tag]) {
var flagElement = angular.element('<div class="sg-category-dot"></div>');
flagElement.css('background-color', $ctrl.service.$tags[tag][1]);
$flagList.append(flagElement);
if (!$ctrl.message.loading) {
// Flags
var flagList = $element[0].querySelector('.sg-category-dot-container'),
$flagList = angular.element(flagList),
flagElements = $mdUtil.nodesToArray(flagList.querySelectorAll('.sg-category-dot'));
_.forEach(flagElements, function(flagElement) {
flagList.removeChild(flagElement);
});
for (i = 0; i < $ctrl.message.flags.length && i < 5; i++) {
var tag = $ctrl.message.flags[i];
if ($ctrl.service.$tags[tag]) {
var flagElement = angular.element('<div class="sg-category-dot"></div>');
flagElement.css('background-color', $ctrl.service.$tags[tag][1]);
$flagList.append(flagElement);
}
}
}
// Mailbox name when in virtual mode
if ($ctrl.mailboxNameElement)
$ctrl.mailboxNameElement.innerHTML = $ctrl.message.$mailbox.$displayName;
// Mailbox name when in virtual mode
if ($ctrl.mailboxNameElement)
$ctrl.mailboxNameElement.innerHTML = $ctrl.message.$mailbox.$displayName;
// Sender or recipient when in
if ($ctrl.MailboxService.selectedFolder.isSentFolder)
$ctrl.senderElement.innerHTML = $ctrl.message.$shortAddress('to').encodeEntities();
else
$ctrl.senderElement.innerHTML = $ctrl.message.$shortAddress('from').encodeEntities();
// Priority icon
if ($ctrl.message.priority && $ctrl.message.priority.level < 3) {
$ctrl.priorityIconElement.classList.remove('ng-hide');
if ($ctrl.message.priority.level < 2)
$ctrl.priorityIconElement.classList.add('md-warn');
// Sender or recipient when in
if ($ctrl.MailboxService.selectedFolder.isSentFolder)
$ctrl.senderElement.innerHTML = $ctrl.message.$shortAddress('to').encodeEntities();
else
$ctrl.priorityIconElement.classList.remove('md-warn');
$ctrl.senderElement.innerHTML = $ctrl.message.$shortAddress('from').encodeEntities();
// Priority icon
if ($ctrl.message.priority && $ctrl.message.priority.level < 3) {
$ctrl.priorityIconElement.classList.remove('ng-hide');
if ($ctrl.message.priority.level < 2)
$ctrl.priorityIconElement.classList.add('md-warn');
else
$ctrl.priorityIconElement.classList.remove('md-warn');
}
else
$ctrl.priorityIconElement.classList.add('ng-hide');
// Mail thread
if ($ctrl.message.first) {
$ctrl.threadButton.classList.remove('ng-hide');
$ctrl.threadCountElement.innerHTML = $ctrl.message.threadCount;
if ($ctrl.message.collapsed)
$ctrl.threadIconElement.classList.remove('md-rotate-180-ccw');
}
else {
$ctrl.threadButton.classList.add('ng-hide');
}
// Subject
$ctrl.subjectElement.innerHTML = $ctrl.message.subject.encodeEntities();
// Message size
$ctrl.sizeElement.innerHTML = $ctrl.message.size;
// Received Date
$ctrl.dateElement.innerHTML = $ctrl.message.relativedate;
setVisibility($ctrl.flagIconElement,
$ctrl.message.isflagged);
setVisibility($ctrl.answerIconElement,
$ctrl.message.isanswered);
setVisibility($ctrl.forwardIconElement,
$ctrl.message.isforwarded);
setVisibility($ctrl.attachmentIconElement,
$ctrl.message.hasattachment);
}
else
$ctrl.priorityIconElement.classList.add('ng-hide');
// Mail thread
if ($ctrl.message.first) {
$ctrl.threadButton.classList.remove('ng-hide');
$ctrl.threadCountElement.innerHTML = $ctrl.message.threadCount;
if ($ctrl.message.collapsed)
$ctrl.threadIconElement.classList.remove('md-rotate-180-ccw');
}
else {
$ctrl.threadButton.classList.add('ng-hide');
}
// Subject
$ctrl.subjectElement.innerHTML = $ctrl.message.subject.encodeEntities();
// Message size
$ctrl.sizeElement.innerHTML = $ctrl.message.size;
// Received Date
$ctrl.dateElement.innerHTML = $ctrl.message.relativedate;
setVisibility($ctrl.flagIconElement,
$ctrl.message.isflagged);
setVisibility($ctrl.answerIconElement,
$ctrl.message.isanswered);
setVisibility($ctrl.forwardIconElement,
$ctrl.message.isforwarded);
setVisibility($ctrl.attachmentIconElement,
$ctrl.message.hasattachment);
// Call original method on parent controller
angular.bind($ctrl.parentController, parentControllerOnUpdate)();

View File

@ -1,6 +1,7 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
(function() {
/* jshint loopfunc: true */
'use strict';
/**
@ -386,38 +387,50 @@
if (this.nextInboxPoll)
Preferences.$timeout.cancel(this.nextInboxPoll);
Preferences.$$resource.post('Mail', '0/folderINBOX/view', params).then(function(data) {
var uids = data.uids;
var uidHeaderIndex = data.headers[0].indexOf('uid');
var fromHeaderIndex = data.headers[0].indexOf('From');
var subjectHeaderIndex = data.headers[0].indexOf('Subject');
if (data.threaded) {
data.uids.splice(0, 1);
uids = _.map(data.uids, 0);
if (this.inboxSyncToken)
params.syncToken = this.inboxSyncToken;
Preferences.$$resource.post('Mail', '0/folderINBOX/changes', params).then(function(data) {
if (data.syncToken) {
_this.inboxSyncToken = data.syncToken;
Preferences.$log.debug("New syncToken is " + _this.inboxSyncToken);
}
if (_this.lastUid) {
_.find(uids, function (uid, index) {
var headers, id, href, toast;
if (uid > _this.lastUid) {
if (angular.isDefined(data.headers) && data.headers.length > 0) {
var uidHeaderIndex = data.headers[0].indexOf('uid');
var isReadHeaderIndex = data.headers[0].indexOf('isRead');
var fromHeaderIndex = data.headers[0].indexOf('From');
var subjectHeaderIndex = data.headers[0].indexOf('Subject');
var i;
var showToast = function() {
var _this = this;
return Preferences.$toast.show(this)
.then(function(response) {
if (response === 'ok') {
_this.viewInboxMessage(_this.locals.uid);
}
});
};
for (i = 1; i < data.headers.length; i++) {
var headers = data.headers[i],
uid = headers[uidHeaderIndex],
id, href, toast;
if (!headers[isReadHeaderIndex]) {
// New unseen message
Preferences.$log.debug('Show notification for message ' + uid);
headers = _.find(data.headers, function(h) {
return h[uidHeaderIndex] == uid;
});
if (_this.defaults.SOGoDesktopNotifications) {
id = 'mail-inbox-' + uid;
href = Preferences.$state.href('mail.account.mailbox.message', { accountId: 0, mailboxId: 'INBOX', messageId: uid });
_this.createNotification(id, headers[subjectHeaderIndex], {
body: headers[fromHeaderIndex][0].name || headers[fromHeaderIndex][0].email,
icon: '/SOGo.woa/WebServerResources/img/email-256px.png',
onClick: function () {
_this.viewInboxMessage(uid);
}
onClick: angular.bind(_this, _this.viewInboxMessage, uid)
});
}
else {
toast = {
locals: {
uid: uid,
title: headers[subjectHeaderIndex],
body: headers[fromHeaderIndex][0].name || headers[fromHeaderIndex][0].email
},
@ -440,28 +453,13 @@
].join(''),
position: 'top right',
hideDelay: 5000,
controller: toastController
controller: toastController,
viewInboxMessage: _this.viewInboxMessage
};
_this.currentToast = _this.currentToast.then(function () {
return Preferences.$toast.show(toast)
.then(function(response) {
if (response === 'ok') {
_this.viewInboxMessage(uid);
}
});
});
_this.currentToast = _this.currentToast.then(angular.bind(toast, showToast));
}
return false; // Continue to next unseen message
}
else {
return true; // No more new messages
}
});
if (uids[0] > _this.lastUid) {
_this.lastUid = uids[0];
}
} else {
_this.lastUid = uids[0];
}
}).finally(function () {
var refreshViewCheck = _this.defaults.SOGoRefreshViewCheck;

View File

@ -51,11 +51,33 @@
.unread {
.#{$md}-subhead,
.#{$md}-body {
.#{$md}-body,
.sg-tile-date {
font-weight: $sg-font-medium;
}
.sg-tile-date {
color: sg-color($sogoBlue, 600);
}
.sg-skeleton {
background-color: inherit;
sg-avatar-image, .sg-category-dot-container, md-icon, .sg-tile-btn {
display: none;
}
.md-icon-button, .sg-md-subhead, .sg-md-body {
color: $colorGrey300;
background-color: $colorGrey300;
font-size: 0;
}
.sg-md-subhead, .sg-md-body {
margin-bottom: 3px;
border-radius: 3px;
}
.sg-md-subhead {
height: 1.2rem;
width: 45%;
}
.sg-md-body {
height: 1rem;
width: 70%;
}
}