From 53a01edee4193454e29931eb831f5a22c8934fc3 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Wed, 1 Jun 2011 21:10:25 +0000 Subject: [PATCH] See ChangeLog Monotone-Parent: ede90c4ec21ca642e49b4287679877bd02717ed6 Monotone-Revision: ae2c5342363a3fa87101fa6840e1c1e1f7a819c0 Monotone-Author: flachapelle@inverse.ca Monotone-Date: 2011-06-01T21:10:25 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 36 +++++ NEWS | 10 ++ SoObjects/Mailer/SOGoMailFolder.h | 5 +- SoObjects/Mailer/SOGoMailFolder.m | 30 +++- SoObjects/SOGo/NSArray+Utilities.m | 7 +- SoObjects/SOGo/SOGoDefaults.plist | 2 +- SoObjects/SOGo/SOGoUserDefaults.h | 5 +- SoObjects/SOGo/SOGoUserDefaults.m | 10 ++ UI/MailerUI/UIxMailListActions.h | 1 + UI/MailerUI/UIxMailListActions.m | 144 +++++++++++++++-- UI/MailerUI/UIxMailMainFrame.m | 21 ++- UI/PreferencesUI/UIxPreferences.m | 10 ++ UI/Templates/MailerUI/UIxMailMainFrame.wox | 19 ++- UI/Templates/PreferencesUI/UIxPreferences.wox | 5 + UI/WebServerResources/ContactsUI.css | 4 +- UI/WebServerResources/ContactsUI.js | 28 +++- UI/WebServerResources/MailerUI.css | 40 +++++ UI/WebServerResources/MailerUI.js | 151 +++++++++++++----- UI/WebServerResources/SOGoDataTable.js | 6 +- UI/WebServerResources/SOGoMailDataSource.js | 60 +++++-- UI/WebServerResources/UIxMailEditor.css | 6 +- UI/WebServerResources/UIxMailEditor.js | 1 + UI/WebServerResources/UIxPreferences.css | 2 +- UI/WebServerResources/generic.js | 10 -- UI/WebServerResources/iefixes.css | 10 +- 25 files changed, 521 insertions(+), 102 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5143efd79..bb056759e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,45 @@ 2011-06-01 Francis Lachapelle + * SoObjects/SOGo/SOGoUserDefaults.m (-setMailSortByThreads) + (-mailSortByThreads): new accessors for the "SOGoMailSortByThreads" + user defaults. + + * UI/PreferencesUI/UIxPreferences.m (-setSortByThreads) + (sortByThreads): idem. + + * SoObjects/Mailer/SOGoMailFolder.m + (-fetchUIDsMatchingQualifier:sortOrdering:threaded:): new method + to fetch a threaded-view of the folder. + + * SoObjects/SOGo/NSArray+Utilities.m (-flattenedArray): added + recurrence to flatten interleaved arrays. + * SoObjects/Mailer/SOGoMailAccount.m (-updateFilters): write multiple 'redirect' directives when forwarding to multiple email addresses. + * UI/MailerUI/UIxMailListActions.m (-threadedUIDs): new method + that returns a flatten representation of messages threads. + + * UI/MailerUI/UIxMailMainFrame.m (-columnsMetaData): added CSS + classnames for the thread column. + (-columnsDisplayOrder): add or remove the thread column depending + on the user's defaults. + + * UI/MailerUI/UIxMailListActions.m (-getUIDsAndHeadersInFolder) + (-getSortedUIDsAction): added support for threads. + + * UI/WebServerResources/ContactsUI.js (onContactContextMenu): + select row at pointer position when not already selected. + + * UI/WebServerResources/MailerUI.js: added support for the + threaded view. + (onMessageContextMenu): select row at pointer position when not + already selected. + + * UI/WebServerResources/SOGoMailDataSource.js: added support for + the threaded view. + 2011-05-31 Francis Lachapelle * UI/WebServerResources/SchedulerUI.js (initCalendarSelector): use diff --git a/NEWS b/NEWS index 0d34cc6ce..5073b20e9 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,13 @@ +1.3-2011MMDD (1.3.8) +--------------------- +New Features +- initial support for threaded-view in the webmail interface + +Enhancements + - improved list selection and contextual menu behavior in all web modules + +Bug Fixes + 1.3-20110503 (1.3.7) --------------------- New Features diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index 0d236cf1f..53a7c0183 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2009-2010 Inverse inc. + Copyright (C) 2009-2011 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of OpenGroupware.org. @@ -60,7 +60,8 @@ inContext: (id) context; - (WOResponse *) archiveAllMessagesInContext: (id) localContext; -- (NSArray *) fetchUIDsMatchingQualifier: (id)_q sortOrdering: (id) _so; +- (NSArray *) fetchUIDsMatchingQualifier: (id) _q sortOrdering: (id) _so; +- (NSArray *) fetchUIDsMatchingQualifier: (id) _q sortOrdering: (id) _so threaded: (BOOL) _threaded; - (NSArray *) fetchUIDs: (NSArray *) _uids parts: (NSArray *) _parts; - (WOResponse *) copyUIDs: (NSArray *) uids diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 75f1a95b8..98e4592e5 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2009-2010 Inverse inc. + Copyright (C) 2009-2011 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of OpenGroupware.org. @@ -315,8 +315,8 @@ static NSString *defaultUserID = @"anyone"; } } else - error = [NSException exceptionWithHTTPStatus: 500 - reason: @"Did not find Trash folder!"]; + error = [NSException exceptionWithHTTPStatus: 500 + reason: @"Did not find Trash folder!"]; } if (b) @@ -544,9 +544,27 @@ static NSString *defaultUserID = @"anyone"; - (NSArray *) fetchUIDsMatchingQualifier: (id) _q sortOrdering: (id) _so { - /* seems to return an NSArray of NSNumber's */ - return [[self imap4Connection] fetchUIDsInURL: [self imap4URL] - qualifier: _q sortOrdering: _so]; + return [self fetchUIDsMatchingQualifier: _q + sortOrdering: _so + threaded: NO]; +} + +- (NSArray *) fetchUIDsMatchingQualifier: (id) _q + sortOrdering: (id) _so + threaded: (BOOL) _threaded +{ + if (_threaded) + { + return [[self imap4Connection] fetchThreadedUIDsInURL: [self imap4URL] + qualifier: _q + sortOrdering: _so]; + } + else + { + return [[self imap4Connection] fetchUIDsInURL: [self imap4URL] + qualifier: _q + sortOrdering: _so]; + } } - (NSArray *) fetchUIDs: (NSArray *) _uids diff --git a/SoObjects/SOGo/NSArray+Utilities.m b/SoObjects/SOGo/NSArray+Utilities.m index 0111c0580..61aea0c40 100644 --- a/SoObjects/SOGo/NSArray+Utilities.m +++ b/SoObjects/SOGo/NSArray+Utilities.m @@ -1,6 +1,6 @@ /* NSArray+Utilities.m - this file is part of SOGo * - * Copyright (C) 2006-2009 Inverse inc. + * Copyright (C) 2006-2011 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -129,7 +129,10 @@ flattenedArray = [NSMutableArray array]; objects = [self objectEnumerator]; while ((currentObject = [objects nextObject])) - [flattenedArray addObjectsFromArray: currentObject]; + if ([currentObject isKindOfClass: [NSArray class]]) + [flattenedArray addObjectsFromArray: [(NSArray *)currentObject flattenedArray]]; + else + [flattenedArray addObject: currentObject]; return flattenedArray; } diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index e394a7f0a..26d65ba03 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -53,7 +53,7 @@ SOGoMailSignaturePlacement = "below"; SOGoMailPollingIntervals = ( 1, 2, 5, 10, 20, 30, 60 ); SOGoMailComposeMessageType = "text"; - SOGoMailListViewColumnsOrder = ( "Flagged", "Attachment", "Subject", + SOGoMailListViewColumnsOrder = ( "Thread", "Flagged", "Attachment", "Subject", "From", "Unread", "Date", "Priority", "Size" ); diff --git a/SoObjects/SOGo/SOGoUserDefaults.h b/SoObjects/SOGo/SOGoUserDefaults.h index 2d30cd45f..69a0cdf7c 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.h +++ b/SoObjects/SOGo/SOGoUserDefaults.h @@ -1,6 +1,6 @@ /* SOGoUserDefaults.h - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. + * Copyright (C) 2011 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -85,6 +85,9 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (void) setMailShowSubscribedFoldersOnly: (BOOL) newValue; - (BOOL) mailShowSubscribedFoldersOnly; +- (void) setMailSortByThreads: (BOOL) newValue; +- (BOOL) mailSortByThreads; + - (void) setDraftsFolderName: (NSString *) newValue; - (NSString *) draftsFolderName; diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 55e9c7230..d48ee9dd5 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -372,6 +372,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return [self boolForKey: @"SOGoMailShowSubscribedFoldersOnly"]; } +- (void) setMailSortByThreads: (BOOL) newValue +{ + [self setBool: newValue forKey: @"SOGoMailSortByThreads"]; +} + +- (BOOL) mailSortByThreads +{ + return [self boolForKey: @"SOGoMailSortByThreads"]; +} + - (void) setDraftsFolderName: (NSString *) newValue { [self setObject: newValue forKey: @"SOGoDraftsFolderName"]; diff --git a/UI/MailerUI/UIxMailListActions.h b/UI/MailerUI/UIxMailListActions.h index b00c4862d..569df1775 100644 --- a/UI/MailerUI/UIxMailListActions.h +++ b/UI/MailerUI/UIxMailListActions.h @@ -35,6 +35,7 @@ id message; SOGoDateFormatter *dateFormatter; NSTimeZone *userTimeZone; + BOOL sortByThread; int folderType; int specificMessageNumber; } diff --git a/UI/MailerUI/UIxMailListActions.m b/UI/MailerUI/UIxMailListActions.m index 1860ac02d..a8faf93c0 100644 --- a/UI/MailerUI/UIxMailListActions.m +++ b/UI/MailerUI/UIxMailListActions.m @@ -78,6 +78,7 @@ user = [[self context] activeUser]; ASSIGN (dateFormatter, [user dateFormatterInContext: context]); ASSIGN (userTimeZone, [[user userDefaults] timeZone]); + sortByThread = [[user userDefaults] mailSortByThreads]; folderType = 0; specificMessageNumber = 0; } @@ -458,13 +459,115 @@ sortedUIDs = [mailFolder fetchUIDsMatchingQualifier: fetchQualifier - sortOrdering: [self imap4SortOrdering]]; + sortOrdering: [self imap4SortOrdering] + threaded: sortByThread]; + [sortedUIDs retain]; } return sortedUIDs; } +/** + * Returns a flatten representation of the messages threads as triples of + * metadata, including the message UID, thread level and root position. + * @param _sortedUIDs the interleaved arrays representation of the messages UIDs + * @return an flatten array representation of the messages UIDs + */ +- (NSArray *) threadedUIDs: (NSArray *) _sortedUIDs +{ + NSMutableArray *threads; + NSMutableArray *currentThreads; + NSEnumerator *rootThreads; + id thread; + int count; + int i; + BOOL first; + BOOL expected; + int previousLevel; + + count = 0; + i = 0; + previousLevel = 0; + expected = YES; + threads = [NSMutableArray arrayWithObject: [NSArray arrayWithObjects: @"uid", @"level", @"first", nil]]; + rootThreads = [_sortedUIDs objectEnumerator]; + thread = [rootThreads nextObject]; + + // Make sure rootThreads starts with an NSArray + if (![thread respondsToSelector: @selector(objectEnumerator)]) + return nil; + + first = [thread count] > 1; + thread = [thread objectEnumerator]; + + currentThreads = [NSMutableArray array]; + + while (thread) + { + unsigned int ecount = 0; + id t; + + if ([thread isKindOfClass: [NSEnumerator class]]) + { + t = [thread nextObject]; + } + else + t = thread; // never happen? + while (t && ![t isKindOfClass: [NSArray class]]) + { + BOOL currentFirst; + int currentLevel; + NSArray *currentThread; + + currentFirst = (first && ecount == 0) || (i == 0 && count > 0) || (count > 0 && previousLevel < 0); + currentLevel = (first && ecount == 0)? 0 : (count > 0? count : -1); + currentThread = [NSArray arrayWithObjects: t, + [NSNumber numberWithInt: currentLevel], + [NSNumber numberWithInt: currentFirst], nil]; + [threads addObject: currentThread]; + i++; + count++; + ecount++; + expected = NO; + previousLevel = currentLevel; + t = [thread nextObject]; + } + if (t) + { + // If t is defined, it has to be an NSArray + if (expected) + { + count++; + expected = NO; + } + thread = [thread allObjects]; + if ([thread count] > 0) + [currentThreads addObject: [thread objectEnumerator]]; + thread = [t objectEnumerator]; + } + else if ([currentThreads count] > 0) + { + thread = [currentThreads objectAtIndex: 0]; + [currentThreads removeObjectAtIndex: 0]; + count -= ecount; + } + else + { + thread = [[rootThreads nextObject] objectEnumerator]; // assume all objects of rootThreads are NSArrays + count = 0; + expected = YES; + } + + // Prepare next iteration + thread = [thread allObjects]; + first = !first && (thread != nil) && [thread count] > 1; + thread = [thread objectEnumerator]; + } + + return threads; +} + - (int) indexOfMessageUID: (int) messageNbr { NSArray *messageNbrs; @@ -521,11 +624,9 @@ } */ -/* actions */ - - (NSDictionary *) getUIDsAndHeadersInFolder: (SOGoMailFolder *) mailFolder { - NSArray *uids, *headers; + NSArray *uids, *threadedUids, *headers; NSDictionary *data; NSRange r; int count; @@ -536,18 +637,31 @@ count = [uids count]; if (count > headersPrefetchMaxSize) count = headersPrefetchMaxSize; r = NSMakeRange(0, count); - headers = [self getHeadersForUIDs: [uids subarrayWithRange: r] + headers = [self getHeadersForUIDs: [[uids flattenedArray] subarrayWithRange: r] inFolder: mailFolder]; + if (sortByThread) + { + threadedUids = [self threadedUIDs: uids]; + if (threadedUids != nil) + uids = threadedUids; + else + sortByThread = NO; + } + data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids", - headers, @"headers", nil]; + headers, @"headers", + [NSNumber numberWithBool: sortByThread], @"threaded", nil]; return data; } +/* Module actions */ + - (id ) getSortedUIDsAction { - id data; + NSDictionary *data; + NSArray *uids, *threadedUids; NSString *noHeaders; SOGoMailFolder *folder; WORequest *request; @@ -563,10 +677,22 @@ [folder expungeLastMarkedFolder]; noHeaders = [request formValueForKey: @"no_headers"]; if ([noHeaders length]) - data = [self getSortedUIDsInFolder: folder]; + { + uids = [self getSortedUIDsInFolder: folder]; + if (sortByThread) + { + threadedUids = [self threadedUIDs: uids]; + if (threadedUids != nil) + uids = threadedUids; + else + sortByThread = NO; + } + data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids", + [NSNumber numberWithBool: sortByThread], @"threaded", nil]; + } else data = [self getUIDsAndHeadersInFolder: folder]; - + [response appendContentString: [data jsonRepresentation]]; return response; diff --git a/UI/MailerUI/UIxMailMainFrame.m b/UI/MailerUI/UIxMailMainFrame.m index 302404db8..ba19d4a07 100644 --- a/UI/MailerUI/UIxMailMainFrame.m +++ b/UI/MailerUI/UIxMailMainFrame.m @@ -471,13 +471,20 @@ columnsMetaData = [NSMutableDictionary dictionaryWithCapacity: 8]; tmpKeys = [NSArray arrayWithObjects: @"headerClass", @"headerId", @"value", - nil]; + nil]; tmpColumns = [NSArray arrayWithObjects: @"messageSubjectColumn tbtv_headercell sortableTableHeader resizable", - @"subjectHeader", @"Subject", nil]; + @"subjectHeader", @"Subject", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Subject"]; + + tmpColumns + = [NSArray arrayWithObjects: @"messageThreadColumn tbtv_headercell", + @"invisibleHeader", @"Thread", nil]; + [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns + forKeys: tmpKeys] + forKey: @"Thread"]; tmpColumns = [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell", @@ -566,6 +573,16 @@ finalOrder = [columnsOrder mutableCopy]; [finalOrder autorelease]; + + if (![ud mailSortByThreads]) + [finalOrder removeObject: @"Thread"]; + else + { + i = [finalOrder indexOfObject: @"Thread"]; + if (i == NSNotFound) + [finalOrder insertObject: @"Thread" atIndex: 0]; + } + if ([self showToAddress]) { i = [finalOrder indexOfObject: @"From"]; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 246546e64..7fa82b57f 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -547,6 +547,16 @@ return [userDefaults mailShowSubscribedFoldersOnly]; } +- (void) setSortByThreads: (BOOL) sortByThreads +{ + [userDefaults setMailSortByThreads: sortByThreads]; +} + +- (BOOL) sortByThreads +{ + return [userDefaults mailSortByThreads]; +} + - (NSArray *) messageCheckList { NSArray *intervalsList; diff --git a/UI/Templates/MailerUI/UIxMailMainFrame.wox b/UI/Templates/MailerUI/UIxMailMainFrame.wox index faf591e3b..34fbf1590 100644 --- a/UI/Templates/MailerUI/UIxMailMainFrame.wox +++ b/UI/Templates/MailerUI/UIxMailMainFrame.wox @@ -7,7 +7,7 @@ xmlns:label="OGo:label" className="UIxPageFrame" title="title" - const:userDefaultsKeys="SOGoMailMessageCheck,SOGoMailListViewColumnsOrder" + const:userDefaultsKeys="SOGoMailMessageCheck,SOGoMailSortByThreads,SOGoMailListViewColumnsOrder" const:userSettingsKeys="Mail" const:jsFiles="dtree.js,MailerUIdTree.js,SOGoAutoCompletion.js,SOGoResizableTable.js,SOGoMailDataSource.js,SOGoDataTable.js">