diff --git a/NEWS b/NEWS index 3007c204e..9bebe203d 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,7 @@ ------------------ New features - - +- new user settings for threads collapsing Enchancements - major refactoring of the GCS component saving code (dropped OGoContentStore) diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m index 71bb05f8f..691a75f26 100644 --- a/UI/MailerUI/UIxMailActions.m +++ b/UI/MailerUI/UIxMailActions.m @@ -34,6 +34,7 @@ #import #import #import +#import #import #import "../Common/WODirectAction+SOGo.h" @@ -145,6 +146,80 @@ return response; } +- (void) collapseAction: (BOOL) isCollapsing +{ + SOGoMailObject *co; + NSMutableDictionary *moduleSettings, *threadsCollapsed; + NSMutableArray *mailboxThreadsCollapsed; + NSString *msguid, *currentMailbox, *currentAccount, *keyForMsgUIDs; + SOGoUserSettings *us; + + co = [self clientObject]; + us = [[context activeUser] userSettings]; + if (!(moduleSettings = [us objectForKey: @"Mail"])) + [us setObject:[NSMutableDictionary dictionnary] forKey: @"Mail"]; + msguid = [co nameInContainer]; + currentMailbox = [[co container] nameInContainer]; + currentAccount = [[[co container] container] nameInContainer]; + keyForMsgUIDs = [NSString stringWithFormat:@"/%@/%@", currentAccount, currentMailbox]; + + if (isCollapsing) + { + // Check if the module threadsCollapsed is created in the userSettings + if ((threadsCollapsed = [moduleSettings objectForKey:@"threadsCollapsed"])) + { + // Check if the currentMailbox already have other threads saved and add the new collapsed thread + if ((mailboxThreadsCollapsed = [threadsCollapsed objectForKey:keyForMsgUIDs])) + { + if (![mailboxThreadsCollapsed containsObject:msguid]) + [mailboxThreadsCollapsed addObject:msguid]; + } + else + { + mailboxThreadsCollapsed = [NSMutableArray arrayWithObject:msguid]; + [threadsCollapsed setObject:mailboxThreadsCollapsed forKey:keyForMsgUIDs]; + } + } + else + { + // Created the module threadsCollapsed and add the new collapsed thread + mailboxThreadsCollapsed = [NSMutableArray arrayWithObject:msguid]; + threadsCollapsed = [NSMutableDictionary dictionaryWithObject:mailboxThreadsCollapsed forKey:keyForMsgUIDs]; + [moduleSettings setObject:threadsCollapsed forKey: @"threadsCollapsed"]; + } + } + else + { + // Check if the module threadsCollapsed is created in the userSettings + if ((threadsCollapsed = [moduleSettings objectForKey:@"threadsCollapsed"])) + { + // Check if the currentMailbox already have other threads saved and remove the uncollapsed thread + if ((mailboxThreadsCollapsed = [threadsCollapsed objectForKey:keyForMsgUIDs])) + { + [mailboxThreadsCollapsed removeObject:msguid]; + if ([mailboxThreadsCollapsed count] == 0) + [threadsCollapsed removeObjectForKey:keyForMsgUIDs]; + } + } + // TODO : Manage errors + } + [us synchronize]; +} + +- (id) markMessageCollapseAction +{ + [self collapseAction: YES]; + + return [self responseWith204]; +} + +- (id) markMessageUncollapseAction +{ + [self collapseAction: NO]; + + return [self responseWith204]; +} + /* SOGoDraftObject */ - (WOResponse *) editAction { diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index 91312d583..06f559cab 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -89,22 +89,45 @@ - (WOResponse *) renameFolderAction { SOGoMailFolder *co; + SOGoUserSettings *us; WOResponse *response; NSException *error; - NSString *folderName; + NSString *newFolderName, *currentMailbox, *currentAccount, *keyForMsgUIDs, *newKeyForMsgUIDs; + NSMutableDictionary *moduleSettings, *threadsCollapsed; + NSArray *values; co = [self clientObject]; + //Prepare the variables need to verify if the current folder have any collapsed threads saved in userSettings + us = [[context activeUser] userSettings]; + moduleSettings = [us objectForKey: @"Mail"]; + threadsCollapsed = [moduleSettings objectForKey:@"threadsCollapsed"]; + currentMailbox = [co nameInContainer]; + currentAccount = [[co container] nameInContainer]; + keyForMsgUIDs = [NSString stringWithFormat:@"/%@/%@", currentAccount, currentMailbox]; - folderName = [[context request] formValueForKey: @"name"]; - error = [co renameTo: folderName]; + newFolderName = [[context request] formValueForKey: @"name"]; + newKeyForMsgUIDs = [NSString stringWithFormat:@"/%@/folder%@", currentAccount, newFolderName]; + error = [co renameTo: newFolderName]; if (error) { response = [self responseWithStatus: 500]; [response appendContentString: @"Unable to rename folder."]; } else - response = [self responseWith204]; - + { + // Verify if the current folder have any collapsed threads save under it old name and adjust the folderName + if (threadsCollapsed) + { + if ([threadsCollapsed objectForKey:keyForMsgUIDs]) + { + values = [NSArray arrayWithArray:[threadsCollapsed objectForKey:keyForMsgUIDs]]; + [threadsCollapsed setObject:values forKey:newKeyForMsgUIDs]; + [threadsCollapsed removeObjectForKey:keyForMsgUIDs]; + [us synchronize]; + } + } + response = [self responseWith204]; + } return response; } @@ -149,10 +172,13 @@ - (WOResponse *) deleteAction { SOGoMailFolder *co, *inbox; + SOGoUserSettings *us; WOResponse *response; NGImap4Connection *connection; NSException *error; NSURL *srcURL, *destURL; + NSMutableDictionary *moduleSettings, *threadsCollapsed; + NSString *currentMailbox, *currentAccount, *keyForMsgUIDs; co = [self clientObject]; if ([co ensureTrashFolder]) @@ -174,7 +200,23 @@ // We unsubscribe to the old one, and subscribe back to the new one [[connection client] subscribe: [destURL path]]; [[connection client] unsubscribe: [srcURL path]]; - + + // Verify if the current folder have any collapsed threads save under it name and erase it + us = [[context activeUser] userSettings]; + moduleSettings = [us objectForKey: @"Mail"]; + threadsCollapsed = [moduleSettings objectForKey:@"threadsCollapsed"]; + currentMailbox = [co nameInContainer]; + currentAccount = [[co container] nameInContainer]; + keyForMsgUIDs = [NSString stringWithFormat:@"/%@/%@", currentAccount, currentMailbox]; + + if (threadsCollapsed) + { + if ([threadsCollapsed objectForKey:keyForMsgUIDs]) + { + [threadsCollapsed removeObjectForKey:keyForMsgUIDs]; + [us synchronize]; + } + } response = [self responseWith204]; } } @@ -191,11 +233,16 @@ { SOGoMailFolder *co; SOGoMailAccount *account; + SOGoUserSettings *us; WOResponse *response; NSArray *uids; NSString *value; NSDictionary *data; BOOL withTrash; + NSMutableDictionary *moduleSettings, *threadsCollapsed; + NSString *currentMailbox, *currentAccount, *keyForMsgUIDs; + NSMutableArray *mailboxThreadsCollapsed; + int i; co = [self clientObject]; value = [[context request] formValueForKey: @"uid"]; @@ -217,7 +264,26 @@ andString: [data jsonRepresentation]]; } else - response = [self responseWith204]; + { + // Verify if the message beeing delete is saved as the root of a collapsed thread + us = [[context activeUser] userSettings]; + moduleSettings = [us objectForKey: @"Mail"]; + threadsCollapsed = [moduleSettings objectForKey:@"threadsCollapsed"]; + currentMailbox = [co nameInContainer]; + currentAccount = [[co container] nameInContainer]; + keyForMsgUIDs = [NSString stringWithFormat:@"/%@/%@", currentAccount, currentMailbox]; + + if (threadsCollapsed) + { + if ((mailboxThreadsCollapsed = [threadsCollapsed objectForKey:keyForMsgUIDs])) + { + for (i = 0; i < [uids count]; i++) + [mailboxThreadsCollapsed removeObject:[uids objectAtIndex:i]]; + [us synchronize]; + } + } + response = [self responseWith204]; + } } } else @@ -317,9 +383,14 @@ - (WOResponse *) moveMessagesAction { SOGoMailFolder *co; + SOGoUserSettings *us; WOResponse *response; NSArray *uids; NSString *value, *destinationFolder; + NSMutableDictionary *moduleSettings, *threadsCollapsed; + NSString *currentMailbox, *currentAccount, *keyForMsgUIDs; + NSMutableArray *mailboxThreadsCollapsed; + int i; co = [self clientObject]; value = [[context request] formValueForKey: @"uid"]; @@ -331,6 +402,23 @@ uids = [value componentsSeparatedByString: @","]; response = [co moveUIDs: uids toFolder: destinationFolder inContext: context]; if (!response) + // Verify if the message beeing delete is saved as the root of a collapsed thread + us = [[context activeUser] userSettings]; + moduleSettings = [us objectForKey: @"Mail"]; + threadsCollapsed = [moduleSettings objectForKey:@"threadsCollapsed"]; + currentMailbox = [co nameInContainer]; + currentAccount = [[co container] nameInContainer]; + keyForMsgUIDs = [NSString stringWithFormat:@"/%@/%@", currentAccount, currentMailbox]; + + if (threadsCollapsed) + { + if ((mailboxThreadsCollapsed = [threadsCollapsed objectForKey:keyForMsgUIDs])) + { + for (i = 0; i < [uids count]; i++) + [mailboxThreadsCollapsed removeObject:[uids objectAtIndex:i]]; + [us synchronize]; + } + } response = [self responseWith204]; } else diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index d35b9c5ae..4ef063eca 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -262,6 +262,16 @@ actionClass = "UIxMailActions"; actionName = "forward"; }; + markMessageUncollapse = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "markMessageUncollapse"; + }; + markMessageCollapse = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "markMessageCollapse"; + }; markMessageUnflagged = { protectedBy = "View"; actionClass = "UIxMailActions"; diff --git a/UI/WebServerResources/MailerUI.js b/UI/WebServerResources/MailerUI.js index 4e7567d0d..aed69c52e 100644 --- a/UI/WebServerResources/MailerUI.js +++ b/UI/WebServerResources/MailerUI.js @@ -33,6 +33,10 @@ var deleteMessageRequestCount = 0; var messageCheckTimer; +// Variables for feature threadsCollapsing +var displayThreadElement = false; +var cachedThreadsCollapsed = (UserSettings.Mail ? UserSettings.Mail.threadsCollapsed: []); + /* We need to override this method since it is adapted to GCS-based folder references, which we do not use here */ function URLForFolderID(folderID, application) { @@ -219,13 +223,16 @@ function openMessageWindowsForSelection(action, firstOnly) { return false; } -/* + function mailListToggleMessageThread(row, cell) { var show = row.hasClassName('closedThread'); + var msguid = row.id.split("_")[1]; + var action = "markMessageCollapse"; $(cell).down('img').remove(); if (show) { row.removeClassName('closedThread'); row.addClassName('openedThread'); + action = "markMessageUncollapse"; var img = createElement("img", null, null, { src: ResourcesURL + '/arrow-down.png' }); cell.insertBefore(img, cell.firstChild); } @@ -241,8 +248,34 @@ function mailListToggleMessageThread(row, cell) { else row.hide(); } + + // Update the dictionnary of the collapsed threads + var mailbox = Mailer.currentMailbox; + var url = ApplicationBaseURL + encodeURI(mailbox) + "/" + msguid + "/" + action; + var callbackData = { "currentMailbox": Mailer.currentMailbox, "msguid": msguid, "action": action}; + + triggerAjaxRequest(url, mailListToggleMessageCollapseCallback, callbackData); } -*/ + +function mailListToggleMessageCollapseCallback(http) { + var data = http.callbackData; + if (isHttpStatus204(http.status)) { + if (data.action == "markMessageCollapse") { + if (cachedThreadsCollapsed[data.currentMailbox]) + cachedThreadsCollapsed[data.currentMailbox].push(data.msguid); + else + cachedThreadsCollapsed[data.currentMailbox] = [data.msguid]; + } + else { + var index = cachedThreadsCollapsed[data.currentMailbox].indexOf(data.msguid); + cachedThreadsCollapsed[data.currentMailbox].splice(index, 1); + } + } + else { + log("Message Collapse Failed (" + http.status + "): " + http.statusText); + } +} + /* Triggered when clicking on the read/unread dot of a message row or * through the contextual menu. */ @@ -929,6 +962,7 @@ function openMailbox(mailbox, reload) { /* * Called from SOGoDataTable.render() */ + function messageListCallback(row, data, isNew) { var currentMessages = []; if (!Object.isArray(document.menuTarget)) { @@ -954,13 +988,31 @@ function messageListCallback(row, data, isNew) { if (currentMessages.indexOf(String(data['uid'])) != -1) row.addClassName('_selected'); - if (data['Thread']) - row.addClassName('openedThread'); + if (data['Thread']) { + if (cachedThreadsCollapsed) { + var mailbox = Mailer.currentMailbox; + var collapsedList = cachedThreadsCollapsed[mailbox]; + if (collapsedList != undefined && collapsedList.indexOf(row.id.split("_")[1]) != -1) { + row.addClassName('closedThread'); + displayThreadElement = true; + } + else { + row.addClassName('openedThread'); + } + } + } + else if (data['ThreadLevel'] > 0) { if (data['ThreadLevel'] > 10) data['ThreadLevel'] = 10; row.addClassName('thread'); row.addClassName('thread' + data['ThreadLevel']); + + if (displayThreadElement) + row.hide(); } + + else + displayThreadElement = false; var cells = row.childElements(); for (var j = 0; j < cells.length; j++) { @@ -1236,7 +1288,7 @@ function onMessageSelectionChange(event) { t = t.parentNode; if (t.tagName == 'TD') { if (t.className == 'messageThreadColumn') { - //mailListToggleMessageThread(t.parentNode, t); Disable thread collapsing + mailListToggleMessageThread(t.parentNode, t); } else if (t.className == 'messageUnreadColumn') { mailListToggleMessagesRead(t.parentNode); @@ -2620,10 +2672,20 @@ function onMenuToggleMessageFlag(event) { mailListToggleMessagesFlagged(); } +function refreshUserSettingsCallback(http) { + var allUserSettings = http.response.evalJSON(); + if (UserSettings.Mail) { + UserSettings.Mail = allUserSettings.Mail; + cachedThreadsCollapsed = UserSettings.Mail.threadsCollapsed; + } + refreshMailbox(); +} + function folderOperationCallback(http) { - if (http.readyState == 4 - && isHttpStatus204(http.status)) + if (http.readyState == 4 && isHttpStatus204(http.status)) { initMailboxTree(); + triggerAjaxRequest(UserFolderURL + "/preferences/jsonSettings", refreshUserSettingsCallback); + } else showAlertDialog(http.callbackData); } diff --git a/UI/WebServerResources/SOGoDataTable.js b/UI/WebServerResources/SOGoDataTable.js index ee04b19f7..8cb0a94f0 100644 --- a/UI/WebServerResources/SOGoDataTable.js +++ b/UI/WebServerResources/SOGoDataTable.js @@ -248,7 +248,6 @@ var SOGoDataTableInterface = { j++, i++) { var row = this.rowModel.cloneNode(true); this.rowRenderCallback(row, data[j], true); - row.show(); this.body.insertBefore(row, this.rowBottom); } } diff --git a/UI/WebServerResources/SOGoMailDataSource.js b/UI/WebServerResources/SOGoMailDataSource.js index 7826ffe32..b6758e559 100644 --- a/UI/WebServerResources/SOGoMailDataSource.js +++ b/UI/WebServerResources/SOGoMailDataSource.js @@ -196,8 +196,16 @@ SOGoMailDataSource = Class.create({ data[j] = this.cache.get(this.uids[i][0]); // Add thread-related data - if (parseInt(this.uids[i][2]) > 0) - data[j]['Thread'] = ' '; //''; + if (parseInt(this.uids[i][2]) > 0) { + var mailbox = Mailer.currentMailbox; + if ((UserSettings.Mail.threadsCollapsed != undefined) && + (UserSettings.Mail.threadsCollapsed[Mailer.currentMailbox] != undefined) && + (UserSettings.Mail.threadsCollapsed[Mailer.currentMailbox].indexOf((this.uids[i][0]).toString())) != -1) { + data[j]['Thread'] = ''; + } + else + data[j]['Thread'] = ''; + } else if (data[j]['Thread']) delete data[j]['Thread']; if (parseInt(this.uids[i][1]) > -1)