diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index 59ba1ef1e..0ae2430cb 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -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; diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index bdee5fead..eb1ee8b72 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -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++) { diff --git a/UI/MailerUI/UIxMailListActions.m b/UI/MailerUI/UIxMailListActions.m index d5e03f8cf..275649f8d 100644 --- a/UI/MailerUI/UIxMailListActions.m +++ b/UI/MailerUI/UIxMailListActions.m @@ -40,6 +40,7 @@ #import #import #import +#import #import #import @@ -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 ) 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 { diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index 9f5c158fe..906ed30b7 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -19,6 +19,11 @@ actionClass = "UIxMailListActions"; actionName = "getUIDs"; }; + changes = { + protectedBy = "View"; + actionClass = "UIxMailListActions"; + actionName = "getChanges"; + }; headers = { protectedBy = "View"; actionClass = "UIxMailListActions"; diff --git a/UI/Templates/MailerUI/UIxMailFolderTemplate.wox b/UI/Templates/MailerUI/UIxMailFolderTemplate.wox index b23c9f316..d0537dccb 100644 --- a/UI/Templates/MailerUI/UIxMailFolderTemplate.wox +++ b/UI/Templates/MailerUI/UIxMailFolderTemplate.wox @@ -156,7 +156,7 @@ arrow_back - +
@@ -286,8 +286,8 @@ + sg-drag-start="mailbox.selectedFolder.hasSelectedMessage() || mailbox.selectedFolder.selectedCount()" + sg-drag-count="mailbox.selectedFolder.selectedCount()"> = 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 diff --git a/UI/WebServerResources/js/Mailer/MailboxController.js b/UI/WebServerResources/js/Mailer/MailboxController.js index 705f644f8..6f6721dcd 100644 --- a/UI/WebServerResources/js/Mailer/MailboxController.js +++ b/UI/WebServerResources/js/Mailer/MailboxController.js @@ -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) { diff --git a/UI/WebServerResources/js/Mailer/MailboxesController.js b/UI/WebServerResources/js/Mailer/MailboxesController.js index 08d3164d1..d312513cb 100644 --- a/UI/WebServerResources/js/Mailer/MailboxesController.js +++ b/UI/WebServerResources/js/Mailer/MailboxesController.js @@ -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); diff --git a/UI/WebServerResources/js/Mailer/Mailer.app.js b/UI/WebServerResources/js/Mailer/Mailer.app.js index a2d514093..5c28c1a9e 100644 --- a/UI/WebServerResources/js/Mailer/Mailer.app.js +++ b/UI/WebServerResources/js/Mailer/Mailer.app.js @@ -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; } /** diff --git a/UI/WebServerResources/js/Mailer/Message.service.js b/UI/WebServerResources/js/Mailer/Message.service.js index c201f58d3..543930493 100644 --- a/UI/WebServerResources/js/Mailer/Message.service.js +++ b/UI/WebServerResources/js/Mailer/Message.service.js @@ -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') { diff --git a/UI/WebServerResources/js/Mailer/MessageController.js b/UI/WebServerResources/js/Mailer/MessageController.js index 430144a8d..d597bfab6 100644 --- a/UI/WebServerResources/js/Mailer/MessageController.js +++ b/UI/WebServerResources/js/Mailer/MessageController.js @@ -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; }); }; diff --git a/UI/WebServerResources/js/Mailer/VirtualMailbox.service.js b/UI/WebServerResources/js/Mailer/VirtualMailbox.service.js index 3a441f5db..17d61939a 100644 --- a/UI/WebServerResources/js/Mailer/VirtualMailbox.service.js +++ b/UI/WebServerResources/js/Mailer/VirtualMailbox.service.js @@ -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')); }; /** diff --git a/UI/WebServerResources/js/Mailer/sgMessageListItem.directive.js b/UI/WebServerResources/js/Mailer/sgMessageListItem.directive.js index 88000d3f1..acd97c115 100644 --- a/UI/WebServerResources/js/Mailer/sgMessageListItem.directive.js +++ b/UI/WebServerResources/js/Mailer/sgMessageListItem.directive.js @@ -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'); diff --git a/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js b/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js index 9b9071b44..971c3c808 100644 --- a/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js +++ b/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js @@ -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('
'); - 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('
'); + 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)(); diff --git a/UI/WebServerResources/js/Preferences/Preferences.service.js b/UI/WebServerResources/js/Preferences/Preferences.service.js index 217acb5c9..ccca2e820 100644 --- a/UI/WebServerResources/js/Preferences/Preferences.service.js +++ b/UI/WebServerResources/js/Preferences/Preferences.service.js @@ -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; diff --git a/UI/WebServerResources/scss/views/MailerUI.scss b/UI/WebServerResources/scss/views/MailerUI.scss index a60bb835d..56a4a908f 100644 --- a/UI/WebServerResources/scss/views/MailerUI.scss +++ b/UI/WebServerResources/scss/views/MailerUI.scss @@ -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%; } }