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
maint-2.0.2
Francis Lachapelle 2011-06-01 21:10:25 +00:00
parent 624172fe92
commit 53a01edee4
25 changed files with 521 additions and 102 deletions

View File

@ -1,9 +1,45 @@
2011-06-01 Francis Lachapelle <flachapelle@inverse.ca>
* 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 <flachapelle@inverse.ca>
* UI/WebServerResources/SchedulerUI.js (initCalendarSelector): use

10
NEWS
View File

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

View File

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

View File

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

View File

@ -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 <wsourdeau@inverse.ca>
*
@ -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;
}

View File

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

View File

@ -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 <wsourdeau@inverse.ca>
*
@ -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;

View File

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

View File

@ -35,6 +35,7 @@
id message;
SOGoDateFormatter *dateFormatter;
NSTimeZone *userTimeZone;
BOOL sortByThread;
int folderType;
int specificMessageNumber;
}

View File

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

View File

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

View File

@ -547,6 +547,16 @@
return [userDefaults mailShowSubscribedFoldersOnly];
}
- (void) setSortByThreads: (BOOL) sortByThreads
{
[userDefaults setMailSortByThreads: sortByThreads];
}
- (BOOL) sortByThreads
{
return [userDefaults mailSortByThreads];
}
- (NSArray *) messageCheckList
{
NSArray *intervalsList;

View File

@ -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">
<script type="text/javascript">
@ -222,6 +222,9 @@
<tr class="tableview"
><var:foreach list="columnsDisplayOrder" item="currentColumn">
<th var:class="currentColumn.headerClass" var:id="currentColumn.headerId">
<var:if condition="currentColumn.value" const:value="Thread">
<entity name="nbsp"/>
</var:if>
<var:if condition="currentColumn.value" const:value="Flagged">
<entity name="nbsp"/>
</var:if>
@ -232,10 +235,12 @@
<var:if condition="currentColumn.value" const:value="Unread">
<img rsrc:src="title_read_14x14.png" label:title="$currentColumn.value" />
</var:if>
<var:if condition="currentColumn.value" const:value="Flagged" const:negate="YES">
<var:if condition="currentColumn.value" const:value="Attachment" const:negate="YES">
<var:if condition="currentColumn.value" const:value="Unread" const:negate="YES">
<var:string var:value="columnTitle" />
<var:if condition="currentColumn.value" const:value="Thread" const:negate="YES">
<var:if condition="currentColumn.value" const:value="Flagged" const:negate="YES">
<var:if condition="currentColumn.value" const:value="Attachment" const:negate="YES">
<var:if condition="currentColumn.value" const:value="Unread" const:negate="YES">
<var:string var:value="columnTitle" />
</var:if>
</var:if>
</var:if>
</var:if>
@ -255,6 +260,10 @@
<tbody>
<tr const:style="display: none;"
><var:foreach list="columnsDisplayOrder" item="currentColumn"
><var:if condition="currentColumn.value" const:value="Thread"
><td class="messageThreadColumn"
><!-- thread --></td
></var:if
><var:if condition="currentColumn.value" const:value="Flagged"
><td class="messageFlagColumn"
><!-- flagged --></td

View File

@ -176,6 +176,11 @@
const:id="subscribedFoldersOnly"
var:checked="showSubscribedFoldersOnly" />
<var:string label:value="Show subscribed mailboxes only"/></label><br/>
<label><input type="checkbox"
const:name="sortByThreads"
const:id="sortByThreads"
var:checked="sortByThreads" />
<var:string label:value="Sort messages by threads"/></label><br/>
<label><var:string label:value="Check for new mail:"/>
<var:popup list="messageCheckList" item="item"
const:id="messageCheck"

View File

@ -40,7 +40,9 @@ DIV#contactsListContent
height: 15.5em;
border-left: 1px solid #9B9B9B;
overflow: auto;
overflow-x: hidden; }
overflow-x: hidden;
-moz-user-select: none;
-khtml-user-select: none; }
.aptview_text
{

View File

@ -215,11 +215,20 @@ function contactsListCallback(http) {
}
function onContactContextMenu(event) {
var target = Event.element(event);
var contact = target.up('TR');
var contactsList = $("contactsList");
var contacts = contactsList.getSelectedRows();
if (contacts.indexOf(contact) < 0) {
onRowClick(event, target);
contacts = contactsList.getSelectedRows();
}
var menu = $("contactMenu");
menu.observe("hideMenu", onContactContextMenuHide);
if (contactsList)
popupMenu(event, "contactMenu", contactsList.getSelectedRows());
popupMenu(event, "contactMenu", contacts);
}
function onContactContextMenuHide(event) {
@ -1285,7 +1294,7 @@ function initContacts(event) {
// Initialize event delegation on contacts table
table.multiselect = true;
var tbody = $(table.tBodies[0]);
tbody.on("mousedown", onContactSelectionChange);
tbody.on("click", onContactSelectionChange);
tbody.on("dblclick", onContactRowDblClick);
tbody.on("selectstart", listRowMouseDownHandler);
tbody.on("contextmenu", onContactContextMenu);
@ -1470,12 +1479,19 @@ function startDragging (itm, e) {
var handle = $("dragDropVisual");
var contacts = $('contactsList').getSelectedRowsId();
var count = contacts.length;
var row = target.up('TR');
handle.show();
handle.update (count);
if (e.shiftKey || currentFolderIsRemote ()) {
handle.addClassName ("copy");
if (count == 0 || contacts.indexOf(row.id) < 0) {
onRowClick(e, target);
contacts = $("contactsList").getSelectedRowsId();
count = contacts.length;
}
handle.update (count);
if (e.shiftKey || currentFolderIsRemote()) {
handle.addClassName("copy");
}
handle.show();
}
function whileDragging (itm, e) {

View File

@ -256,6 +256,7 @@ TR#messageCountHeader TH
TABLE.messageList TD
{ border-right: 1px solid transparent; }
TABLE.messageList .messageThreadColumn,
TABLE.messageList .messageFlagColumn,
TABLE.messageList .messageAttachmentColumn,
TABLE.messageList .messageUnreadColumn
@ -270,6 +271,45 @@ TABLE.messageList .messageSubjectColumn
TABLE.messageList .messageSubjectColumn SPAN
{ padding-left: 20px; }
TABLE.messageList TR.openedThread TD,
TABLE.messageList TR.closedThread TD,
TABLE.messageList TR.thread .messageThreadColumn
{ background-color: #DDD; }
TABLE.messageList TR.thread TD
{ background-color: #EEE; }
TABLE.messageList TR.thread1 .messageSubjectColumn
{ background-position: 20px 0px !important;
padding-left: 20px; }
TABLE.messageList TR.thread2 .messageSubjectColumn
{ background-position: 40px 0px !important;
padding-left: 40px; }
TABLE.messageList TR.thread3 .messageSubjectColumn
{ background-position: 60px 0px !important;
padding-left: 60px; }
TABLE.messageList TR.thread4 .messageSubjectColumn
{ background-position: 80px 0px !important;
padding-left: 80px; }
TABLE.messageList TR.thread5 .messageSubjectColumn
{ background-position: 100px 0px !important;
padding-left: 100px; }
TABLE.messageList TR.thread6 .messageSubjectColumn
{ background-position: 120px 0px !important;
padding-left: 120px; }
TABLE.messageList TR.thread7 .messageSubjectColumn
{ background-position: 140px 0px !important;
padding-left: 140px; }
TABLE.messageList TR.thread8 .messageSubjectColumn
{ background-position: 160px 0px !important;
padding-left: 160px; }
TABLE.messageList TR.thread9 .messageSubjectColumn
{ background-position: 180px 0px !important;
padding-left: 180px; }
TABLE.messageList TR.thread10 .messageSubjectColumn
{ background-position: 200px 0px !important;
padding-left: 200px; }
TABLE.messageList .messageAddressColumn
{ max-width: 18%;
width: 18%; }

View File

@ -17,7 +17,10 @@ var Mailer = {
quotas: null,
dataTable: null,
dataSources: new Hash()
dataSources: new Hash(),
columnsOrder: null,
sortByThread: false
};
var usersRightsWindowHeight = 320;
@ -169,8 +172,10 @@ function markMailInWindow(win, msguid, markread) {
return (unseenCount != 0);
}
/**
* This is called by UIxMailView with window.opener.
*/
function markMailReadInWindow(win, msguid) {
/* this is called by UIxMailView with window.opener */
return markMailInWindow(win, msguid, true);
}
@ -203,6 +208,31 @@ function openMessageWindowsForSelection(action, firstOnly) {
return false;
}
/*
function mailListToggleMessageThread(row, cell) {
var show = row.hasClassName('closedThread');
$(cell).down('img').remove();
if (show) {
row.removeClassName('closedThread');
row.addClassName('openedThread');
var img = createElement("img", null, null, { src: ResourcesURL + '/arrow-down.png' });
cell.insertBefore(img, cell.firstChild);
}
else {
row.removeClassName('openedThread');
row.addClassName('closedThread');
var img = createElement("img", null, null, { src: ResourcesURL + '/arrow-right.png' });
cell.insertBefore(img, cell.firstChild);
}
while ((row = row.next()) && row.hasClassName('thread')) {
if (show)
row.show();
else
row.hide();
}
}
*/
/* Triggered when clicking on the read/unread dot of a message row or
* through the contextual menu. */
function mailListToggleMessagesRead(row) {
@ -399,6 +429,7 @@ function deleteSelectedMessages(sender) {
var uids = new Array(); // message IDs
var paths = new Array(); // row IDs
var unseenCount = 0;
var refreshFolder = false;
if (rowIds && rowIds.length > 0) {
messageList.deselectAll();
@ -432,15 +463,26 @@ function deleteSelectedMessages(sender) {
var row = $("row_" + uid);
var nextRow = false;
if (row) {
//row.addClassName("deleted"); // when we'll offer "mark as deleted"
nextRow = row.next("tr");
if (!nextRow.id.startsWith('row_'))
nextRow = row.previous("tr");
// row.addClassName("deleted"); // when we'll offer "mark as deleted"
else if (row.hasClassName('openedThread') || row.hasClassName('closedThread')) {
// Thread root deleted -- must refresh folder
refreshFolder = true;
// New row will be the new thread root -- mark it as first mail of the thread
var nextUid = nextRow.id.substr(4);
var nextIndex = Mailer.dataTable.dataSource.indexOf(nextUid);
Mailer.dataTable.dataSource.uids[nextIndex][2] = 1; // mark it as "first"
Mailer.dataTable.invalidate(nextUid, true);
}
if (nextRow.id.startsWith('row_')) {
Mailer.currentMessages[Mailer.currentMailbox] = nextRow.id.substr(4);
nextRow.selectElement();
if (loadMessage(Mailer.currentMessages[Mailer.currentMailbox]))
if (loadMessage(Mailer.currentMessages[Mailer.currentMailbox]) && !refreshFolder)
// Seen state has changed
Mailer.dataTable.invalidate(Mailer.currentMessages[Mailer.currentMailbox], true);
refreshFolder = true;
}
}
else {
@ -452,7 +494,6 @@ function deleteSelectedMessages(sender) {
lastClickedRow = nextRow.rowIndex;
lastClickedRowId = nextRow.id;
}
Mailer.dataTable.refresh();
deleteCachedMailboxByType("trash");
}
else {
@ -468,7 +509,7 @@ function deleteSelectedMessages(sender) {
}
var url = ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/batchDelete";
var parameters = "uid=" + uids.join(",");
var data = { "id": uids, "mailbox": Mailer.currentMailbox, "path": paths, "refreshUnseenCount": (unseenCount > 0) };
var data = { "id": uids, "mailbox": Mailer.currentMailbox, "path": paths, "refreshUnseenCount": (unseenCount > 0), "refreshFolder": refreshFolder };
triggerAjaxRequest(url, deleteSelectedMessagesCallback, data, parameters,
{ "Content-type": "application/x-www-form-urlencoded" });
}
@ -485,6 +526,8 @@ function deleteSelectedMessagesCallback(http) {
// TODO : the unseen count should be returned when calling the batchDelete remote action,
// in order to avoid this extra AJAX call.
getUnseenCountForFolder(data["mailbox"]);
if (data["refreshFolder"])
Mailer.dataTable.refresh();
}
else if (!http.callbackData["withoutTrash"]) {
showConfirmDialog(_("Warning"),
@ -514,16 +557,11 @@ function onMenuDeleteMessage(event) {
preventDefault(event);
}
function deleteMessage(url, id, mailbox, messageId) {
var data = { "id": new Array(id), "mailbox": mailbox, "path": new Array(messageId) };
var parameters = "uid=" + id;
deleteMessageRequestCount++;
triggerAjaxRequest(url, deleteSelectedMessagesCallback, data, parameters,
{ "Content-type": "application/x-www-form-urlencoded" });
}
/**
* The following two functions are called from UIxMailPopupView
* with window.opener.
*/
function deleteMessageWithDelay(url, id, mailbox, messageId) {
/* this is called by UIxMailPopupView with window.opener */
var row = $("row_" + id);
if (row) row.hide();
setTimeout("deleteMessage('" +
@ -534,6 +572,14 @@ function deleteMessageWithDelay(url, id, mailbox, messageId) {
50);
}
function deleteMessage(url, id, mailbox, messageId) {
var data = { "id": new Array(id), "mailbox": mailbox, "path": new Array(messageId) };
var parameters = "uid=" + id;
deleteMessageRequestCount++;
triggerAjaxRequest(url, deleteSelectedMessagesCallback, data, parameters,
{ "Content-type": "application/x-www-form-urlencoded" });
}
function onPrintCurrentMessage(event) {
var messageList = $("messageListBody").down("TBODY");
var rows = messageList.getSelectedNodesId();
@ -588,9 +634,9 @@ function toggleAddressColumn(search, replace) {
if (header) {
header.id = replace + "Header";
header.update(_(replace.capitalize()));
var i = UserDefaults["SOGoMailListViewColumnsOrder"].indexOf(search.capitalize());
var i = Mailer.columnsOrder.indexOf(search.capitalize());
if (i >= 0)
UserDefaults["SOGoMailListViewColumnsOrder"][i] = replace.capitalize();
Mailer.columnsOrder[i] = replace.capitalize();
}
if (sorting["attribute"] == search)
sorting["attribute"] = replace;
@ -767,7 +813,7 @@ function openMailbox(mailbox, reload) {
if (inboxData) {
// Use UIDs and headers from the WOX template; this only
// happens once and only with the inbox
dataSource.init(inboxData['uids'], inboxData['headers']);
dataSource.init(inboxData['uids'], inboxData['threaded'], inboxData['headers']);
inboxData = null; // invalidate this initial lookup
}
else
@ -813,12 +859,20 @@ function messageListCallback(row, data, isNew) {
row.id = data['rowID'];
row.writeAttribute('labels', (data['labels']?data['labels']:""));
row.className = data['rowClasses'];
row.show(); // make sure the row is visible
// Restore previous selection
if (data['uid'] == currentMessage)
row.addClassName('_selected');
var columnsOrder = UserDefaults["SOGoMailListViewColumnsOrder"];
if (data['Thread'])
row.addClassName('openedThread');
else if (data['ThreadLevel'] > 0) {
if (data['ThreadLevel'] > 10) data['ThreadLevel'] = 10;
row.addClassName('thread');
row.addClassName('thread' + data['ThreadLevel']);
}
var cells;
if (Prototype.Browser.IE)
cells = row.childNodes;
@ -827,7 +881,7 @@ function messageListCallback(row, data, isNew) {
for (var j = 0; j < cells.length; j++) {
var cell = $(cells[j]);
var cellType = columnsOrder[j];
var cellType = Mailer.columnsOrder[j];
if (data[cellType]) cell.innerHTML = data[cellType];
else cell.innerHTML = '&nbsp;';
@ -957,21 +1011,26 @@ function onMessageListRender(event) {
}
function onMessageContextMenu(event) {
var row = getTarget(event);
var target = Event.element(event);
var menu = $('messageListMenu');
var topNode = $('messageListBody');
var selectedNodes = topNode.getSelectedRows();
if (row.tagName != 'TR')
row = row.parentNode;
if (row.tagName != 'TR')
row = row.parentNode;
var selectedNodes = topNode.getSelectedRowsId();
var row = target.up('TR');
if (selectedNodes.indexOf(row.id) < 0) {
if (target.tagName != 'TD')
target = target.up('TD');
onRowClick(event, target);
selectedNodes = topNode.getSelectedRowsId();
}
menu.observe("hideMenu", onMessageContextMenuHide);
if (selectedNodes.length > 1)
popupMenu(event, "messagesListMenu", selectedNodes);
else
else if (selectedNodes.length == 1)
popupMenu(event, "messageListMenu", row);
return false;
}
function onMessageContextMenuHide(event) {
@ -1110,7 +1169,10 @@ function onMessageSelectionChange(event) {
if (t.tagName == 'IMG') {
t = t.parentNode;
if (t.tagName == 'TD') {
if (t.className == 'messageUnreadColumn') {
if (t.className == 'messageThreadColumn') {
//mailListToggleMessageThread(t.parentNode, t); Disable thread collapsing
}
else if (t.className == 'messageUnreadColumn') {
mailListToggleMessagesRead(t.parentNode);
return true;
}
@ -1781,15 +1843,17 @@ function refreshMessage(mailbox, messageUID) {
}
}
function configureMessageListEvents(headerTable, dataTable) {
function configureMessageListEvents() {
var headerTable = $("messageListHeader");
var dataTable = $("messageListBody");
if (headerTable)
// Sortable columns
configureSortableTableHeaders(headerTable);
if (dataTable) {
dataTable.multiselect = true;
// Each body row can load a message
dataTable.observe("mouseup", onMessageSelectionChange);
dataTable.observe("click", onMessageSelectionChange);
dataTable.observe("dblclick", onMessageDoubleClick);
dataTable.observe("selectstart", listRowMouseDownHandler);
dataTable.observe("contextmenu", onMessageContextMenu);
@ -1844,6 +1908,11 @@ function openInbox(node) {
function initMailer(event) {
if (!$(document.body).hasClassName("popup")) {
Mailer.columnsOrder = UserDefaults["SOGoMailListViewColumnsOrder"];
Mailer.sortByThread = UserDefaults["SOGoMailSortByThreads"] != null && parseInt(UserDefaults["SOGoMailSortByThreads"]) > 0;
if (!Mailer.sortByThread && Mailer.columnsOrder[0] == "Thread")
Mailer.columnsOrder.shift(); // drop the thread column
// Restore sorting from user settings
if (UserSettings && UserSettings["Mail"] && UserSettings["Mail"]["SortingState"]) {
sorting["attribute"] = UserSettings["Mail"]["SortingState"][0];
@ -1866,7 +1935,7 @@ function initMailer(event) {
messageListHeader.restore($H(UserSettings["Mail"]["ColumnsState"]));
}
configureMessageListEvents($("messageListHeader"), $("messageListBody"));
configureMessageListEvents();
initMailboxTree();
initMessageCheckTimer();
@ -2589,15 +2658,16 @@ function onLabelMenuPrepareVisibility() {
function onMarkMenuPrepareVisibility() {
var messageList = $("messageListBody");
if (messageList) {
var nodes = messageList.down("TBODY").getSelectedNodes();
var nodes = messageList.down("TBODY").getSelectedNodesId();
var isRead = false;
var isFlagged = false;
if (nodes.length > 0) {
var row = nodes[0];
var firstTd = row.childElements().first();
var img = firstTd.childElements().first();
var row = null;
for (var i = 0; row == null && i < nodes.length; i++)
row = $(nodes[i]);
var img = row.down('img');
isFlagged = img.hasClassName ("messageIsFlagged");
isRead = !row.hasClassName("mailer_unreadmail");
}
@ -2685,7 +2755,7 @@ function getMenus() {
"-", "moveMailboxMenu",
"copyMailboxMenu", "label-menu",
"mark-menu", "-",
saveAs, null,
saveAs, null, null,
onMenuDeleteMessage ],
imageMenu: [ saveImage ],
attachmentMenu: [ saveAttachment ],
@ -2857,16 +2927,15 @@ function startDragging (itm, e) {
if (target.up('TBODY') == undefined)
return;
if (target.tagName != 'TD')
target = target.up('TD');
var row = target.up('TR');
var handle = $("dragDropVisual");
var selectedIds = $("messageListBody").getSelectedRowsId();
var count = selectedIds.length;
var rowId = row.id;
if (count == 0 || selectedIds.indexOf(rowId) < 0) {
if (target.tagName != 'TD')
target = target.up('TD');
onRowClick(e, target);
selectedIds = $("messageListBody").getSelectedRowsId();
count = selectedIds.length;

View File

@ -57,9 +57,9 @@ var SOGoDataTableInterface = {
s = startIndex;
e = endIndex;
}
while (s <= e) {
uid = "row_" + div.dataSource.uids[s];
uid = "row_" + div.dataSource.uidAtIndex(s);
if (this.selectedIds.indexOf(uid) < 0)
this.selectedIds.push(uid);
s++;
@ -71,7 +71,7 @@ var SOGoDataTableInterface = {
var div = this.up('div');
this.selectedIds = new Array();
for (var i = 0; i < div.dataSource.uids.length; i++)
this.selectedIds.push("row_" + div.dataSource.uids[i]);
this.selectedIds.push("row_" + div.dataSource.uidAtIndex(i));
this.refreshSelectionByIds();
},

View File

@ -8,6 +8,7 @@ SOGoMailDataSource = Class.create({
this.url = url;
this.uids = new Array();
this.threaded = false;
this.cache = new Hash();
this.loaded = false;
@ -27,7 +28,7 @@ SOGoMailDataSource = Class.create({
invalidate: function(uid) {
this.cache.unset(uid);
var index = this.uids.indexOf(parseInt(uid));
var index = this.indexOf(uid);
// log ("MailDataSource.invalidate(" + uid + ") at index " + index);
return index;
@ -41,10 +42,16 @@ SOGoMailDataSource = Class.create({
return index;
},
init: function(uids, headers) {
init: function(uids, threaded, headers) {
this.uids = uids;
if (typeof threaded != "undefined") {
this.threaded = threaded;
if (threaded)
this.uids.shift(); // drop key fields
}
// log ("MailDataSource.init() " + this.uids.length + " uids loaded");
if (headers) {
var keys = headers[0];
for (var i = 1; i < headers.length; i++) {
@ -53,6 +60,7 @@ SOGoMailDataSource = Class.create({
header[keys[j]] = headers[i][j];
this.cache.set(header["uid"], header);
}
// log ("MailDataSource.init() " + this.cache.keys().length + " headers loaded");
}
this.loaded = true;
@ -81,7 +89,7 @@ SOGoMailDataSource = Class.create({
if (http.responseText.length > 0) {
var data = http.responseText.evalJSON(true);
if (data.uids)
this.init(data.uids, data.headers);
this.init(data.uids, data.threaded, data.headers);
else
this.init(data);
if (this.delayedGetData) {
@ -136,8 +144,9 @@ SOGoMailDataSource = Class.create({
// log ("MailDataSource._getData() from " + index + " to " + (index + count) + " boosted from " + start + " to " + end);
for (i = 0, j = start; j < end; j++) {
if (!this.cache.get(this.uids[j])) {
missingUids[i] = this.uids[j];
var uid = this.threaded? this.uids[j][0] : this.uids[j];
if (!this.cache.get(uid)) {
missingUids[i] = uid;
i++;
}
}
@ -196,12 +205,45 @@ SOGoMailDataSource = Class.create({
var i, j;
var data = new Array();
for (i = start, j = 0; i < end; i++, j++) {
data[j] = this.cache.get(this.uids[i]);
if (this.threaded) {
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">';
else
delete data[j]['Thread'];
if (parseInt(this.uids[i][1]) > -1)
data[j]['ThreadLevel'] = this.uids[i][1];
else
delete data[j]['ThreadLevel'];
}
else {
data[j] = this.cache.get(this.uids[i]);
}
}
callbackFunction(id, start, this.uids.length, data);
},
indexOf: function(uid) {
return this.uids.indexOf(parseInt(uid));
var index = -1;
if (this.threaded) {
for (var i = 0; i < this.uids.length; i++)
if (this.uids[i][0] == uid) {
index = i;
break;
}
}
else
index = this.uids.indexOf(parseInt(uid));
return index;
},
uidAtIndex: function(index) {
if (this.threaded)
return this.uids[index][0];
else
return this.uids[index];
}
});

View File

@ -93,7 +93,7 @@ div#headerArea div.addressList
div#attachmentsArea
{ display: none;
float: right;
width: 120px;
width: 200px;
padding: 2px 5px 0;
margin: auto;
border-left: 1px solid #888; }
@ -144,7 +144,9 @@ UL#attachments
list-style-type: none;
list-style-image: none;
overflow: auto;
overflow-x: hidden; }
overflow-x: hidden;
-moz-user-select: none;
-khtml-user-select: none; }
UL#attachments LI
{ white-space: nowrap;

View File

@ -361,6 +361,7 @@ function initMailEditor() {
var list = $("attachments");
if (!list) return;
list.multiselect = true;
list.on("click", onRowClick);
list.attachMenu("attachmentsMenu");
var elements = $(list).childNodesWithTag("li");

View File

@ -89,7 +89,7 @@ DIV.listWrapper
DIV#filtersListWrapper
{ bottom: 30px;
right: 2em;
top: 164px;
top: 174px;
left: 2em; }
TABLE#filtersList

View File

@ -605,14 +605,6 @@ function onRowClick(event, target) {
// Single line selection
$(node.parentNode).deselectAll();
$(node).selectElement();
if (initialSelection != $(node.parentNode).getSelectedNodesId()) {
// Selection has changed; fire mousedown event
var parentNode = node.parentNode;
if (parentNode.tagName == 'TBODY')
parentNode = parentNode.parentNode;
parentNode.fire("mousedown");
}
}
if (rowIndex != null) {
lastClickedRow = rowIndex;
@ -735,8 +727,6 @@ function hideMenu(menuNode) {
menuNode.parentMenu.submenu = null;
menuNode.parentMenu = null;
}
$(menuNode).fire("mousedown");
}
function onMenuEntryClick(event) {

View File

@ -50,7 +50,15 @@ TABLE.messageList TD
{ white-space: pre; }
TABLE.messageList TR._selected TD
{ border-right: 1px solid #9ABCD8; }
{ border-right: 1px solid #9ABCD8 !important; }
TABLE.messageList TR.openedThread TD,
TABLE.messageList TR.closedThread TD,
TABLE.messageList TR.thread .messageThreadColumn
{ border-right: 1px solid #DDD; }
TABLE.messageList TR.thread TD
{ border-right: 1px solid #EEE; }
/* ContactsUI */