Merge pull request #54 from alexcloutier/feature/FoldingThreads

Feature/folding threads
pull/52/head
extrafu 2014-09-09 14:06:30 -04:00
commit 126a05d391
7 changed files with 260 additions and 18 deletions

2
NEWS
View File

@ -2,7 +2,7 @@
------------------
New features
-
- new user settings for threads collapsing
Enchancements
- major refactoring of the GCS component saving code (dropped OGoContentStore)

View File

@ -34,6 +34,7 @@
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoUserSettings.h>
#import <SoObjects/SOGo/SOGoUserDefaults.h>
#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
{

View File

@ -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

View File

@ -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";

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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'] = '&nbsp;'; //'<img class="messageThread" src="' + ResourcesURL + '/arrow-down.png">';
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'] = '<img class="messageThread" src="' + ResourcesURL + '/arrow-right.png">';
}
else
data[j]['Thread'] = '<img class="messageThread" src="' + ResourcesURL + '/arrow-down.png">';
}
else if (data[j]['Thread'])
delete data[j]['Thread'];
if (parseInt(this.uids[i][1]) > -1)