diff --git a/NEWS b/NEWS index 1019b7973..ab1d00fca 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ Enhancements - [web] add Indonesian (id) translation - [web] updated Angular Material to version 1.1.19 - [web] replaced bower packages by npm packages + - [web] restored mail threads (#3478, #4616, #4735) Bug fixes - [web] fixed wrong translation of custom calendar categories diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m index 688b22073..2d5329677 100644 --- a/UI/MailerUI/UIxMailActions.m +++ b/UI/MailerUI/UIxMailActions.m @@ -18,6 +18,7 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import @@ -221,16 +222,30 @@ - (void) collapseAction: (BOOL) isCollapsing { - WORequest *request; - NSMutableDictionary *moduleSettings, *threadsCollapsed, *content; + NSArray *currentComponents; NSMutableArray *mailboxThreadsCollapsed; - NSString *msguid, *keyForMsgUIDs; + NSMutableDictionary *moduleSettings, *threadsCollapsed; + NSString *accountName, *msguid, *keyForMsgUIDs; + SOGoMailAccount *account; + SOGoMailFolder *mailbox; + SOGoMailObject *co; SOGoUserSettings *us; + int count; - request = [context request]; - content = [[request contentAsString] objectFromJSONString]; - keyForMsgUIDs = [content objectForKey:@"currentMailbox"]; - msguid = [content objectForKey:@"msguid"]; + co = [self clientObject]; + account = [co mailAccountFolder]; + accountName = [account nameInContainer]; + mailbox = [co container]; + msguid = [co nameInContainer]; + + // Build lookup key for current mailbox path + currentComponents = [[mailbox imap4URL] pathComponents]; + count = [currentComponents count]; + currentComponents = [[currentComponents subarrayWithRange: NSMakeRange(1,count-1)] + resultsOfSelector: @selector (asCSSIdentifier)]; + currentComponents = [currentComponents stringsWithFormat: @"folder%@"]; + keyForMsgUIDs = [NSString stringWithFormat:@"/%@/%@", accountName, + [currentComponents componentsJoinedByString: @"/"]]; us = [[context activeUser] userSettings]; if (!(moduleSettings = [us objectForKey: @"Mail"])) diff --git a/UI/Templates/MailerUI/UIxMailFolderTemplate.wox b/UI/Templates/MailerUI/UIxMailFolderTemplate.wox index 20e117604..e958d9ac9 100644 --- a/UI/Templates/MailerUI/UIxMailFolderTemplate.wox +++ b/UI/Templates/MailerUI/UIxMailFolderTemplate.wox @@ -300,7 +300,7 @@ -
+
= 0 && index < this.$messages.length) { - message = this.$messages[index]; + 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)) @@ -928,7 +948,7 @@ // First entry of 'uids' are keys when threaded view is enabled if (_this.threaded) { - uids = _this.uids[0]; + uids = _this.uids[0]; // uid, level, first _this.uids.splice(0, 1); } @@ -937,6 +957,19 @@ var data, msgObject; if (_this.threaded) { data = _.zipObject(uids, msg); + if (data.first === 1) { + var count = 1; + while (_this.uids[i + count] && + _this.uids[i + count][1] >= 0 && + _this.uids[i + count][2] !== 1) { + count++; + } + data.count = count; + data.collapsed = false; + if (_this.$collapsedThreads.indexOf(data.uid.toString()) >= 0) { + data.collapsed = true; + } + } } else { data = {uid: msg.toString()}; } diff --git a/UI/WebServerResources/js/Mailer/Message.service.js b/UI/WebServerResources/js/Mailer/Message.service.js index 94d6bfe90..880e14d6d 100644 --- a/UI/WebServerResources/js/Mailer/Message.service.js +++ b/UI/WebServerResources/js/Mailer/Message.service.js @@ -26,6 +26,15 @@ this.init(futureMessageData); } this.uid = parseInt(futureMessageData.uid); + this.level = parseInt(futureMessageData.level); + this.first = parseInt(futureMessageData.first) === 1; + if (this.first) { + this.threadCount = parseInt(futureMessageData.count); + this.collapsed = (futureMessageData.collapsed === true); + } + else if (!isNaN(this.level) && this.level >= 0) { + this.threadMember = true; + } } else { // The promise will be unwrapped first @@ -539,7 +548,7 @@ }; /** - * @function $markAsFlaggedOrUnflagged + * @function toggleFlag * @memberof Message.prototype * @desc Add or remove a the \\Flagged flag on the current message. * @returns a promise of the HTTP operation @@ -558,6 +567,24 @@ }); }; + /** + * @function toggleThread + * @memberof Message.prototype + * @desc Collapse or expand mail thread + * @returns a promise of the HTTP operation + */ + Message.prototype.toggleThread = function() { + var _this = this, + action = 'markMessageCollapse'; + + if (this.collapsed) + action = 'markMessageUncollapse'; + + this.collapsed = !this.collapsed; + + return Message.$$resource.post(this.$absolutePath(), action); + }; + /** * @function $isLoading * @memberof Message.prototype diff --git a/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js b/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js index 0c1d8aebb..1d365afd2 100644 --- a/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js +++ b/UI/WebServerResources/js/Mailer/sgMessageListItemMain.directive.js @@ -26,6 +26,9 @@ '
', '
', '
', + ' ', + ' expand_more', // expanded by default (icon is rotated) + ' ', '
', '
', '
', @@ -59,7 +62,7 @@ var $ctrl = this; this.$postLink = function () { - var contentDivElement, iconsDivElement; + var contentDivElement, threadButton, iconsDivElement; var parentControllerOnUpdate, setVisibility; this.parentController = $scope.parentController; @@ -74,6 +77,12 @@ iconsDivElement = angular.element(div); }); + threadButton = contentDivElement.find('button')[0]; + this.threadButton = threadButton; + threadButton = angular.element(threadButton); + this.threadIconElement = threadButton.find('md-icon')[0]; + this.threadCountElement = threadButton.find('span')[0]; + this.priorityIconElement = contentDivElement.find('md-icon')[0]; if (Mailbox.$virtualMode) { @@ -147,9 +156,19 @@ 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; @@ -173,6 +192,14 @@ this.MailboxService = Mailbox; }; + this.toggleThread = function() { + if (this.message.collapsed) + this.threadIconElement.classList.add('md-rotate-180-ccw'); + else + this.threadIconElement.classList.remove('md-rotate-180-ccw'); + this.message.toggleThread(); + }; + } diff --git a/UI/WebServerResources/js/Preferences/Preferences.service.js b/UI/WebServerResources/js/Preferences/Preferences.service.js index 2f5fd9158..d174f4db7 100644 --- a/UI/WebServerResources/js/Preferences/Preferences.service.js +++ b/UI/WebServerResources/js/Preferences/Preferences.service.js @@ -11,7 +11,7 @@ var _this = this, defaultsElement, settingsElement, data; this.defaults = {}; - this.settings = {}; + this.settings = {Mail: {}}; defaultsElement = Preferences.$document[0].getElementById('UserDefaults'); if (defaultsElement) { diff --git a/UI/WebServerResources/scss/components/list/list.scss b/UI/WebServerResources/scss/components/list/list.scss index b64ab2d49..438a1611b 100644 --- a/UI/WebServerResources/scss/components/list/list.scss +++ b/UI/WebServerResources/scss/components/list/list.scss @@ -172,16 +172,25 @@ div.md-tile-left { &-tile-content { flex: 1; overflow: hidden; // required in Firefox - .sg-tile-date, .sg-tile-size { + .sg-tile-date, .sg-tile-size, .sg-tile-thread { flex-shrink: 0; font-size: sg-size(body); font-weight: $sg-font-light; line-height: $sg-line-height-2; - margin-left: 3px; + margin-left: 3px !important; } .sg-tile-size { font-size: sg-size(caption); } + .sg-tile-thread { + min-height: auto; + min-width: auto; + padding: 0 3px !important; + font-weight: $sg-font-medium; + md-icon { + font-size: sg-size(body); + } + } .#{$md}-subhead { @extend .#{$md}-body-1; white-space: pre;