Live loading in the Webmail module! See ChangeLog.

Monotone-Parent: c81c7151deb5466ad48ca5eb97d70f3b1172934c
Monotone-Revision: 4c3c63649f1424cf932228d812cb2a38ae67b434

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2010-05-27T14:41:59
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle
2010-05-27 14:41:59 +00:00
parent 113a01009f
commit 3c0bf460bd
20 changed files with 2061 additions and 1443 deletions
+43
View File
@@ -1,3 +1,46 @@
2010-05-27 Francis Lachapelle <flachapelle@inverse.ca>
* UI/MailerUI/UIxMailListActions.m: renamed from
UIxMailListview.m. Associated .wox was removed.
(-getSortedUIDsAction): new method that returns a JSON
array with all the messages UIDs of the mailbox, sorted as requested.
(-getHeadersAction): new method that returns a JSON dictionary of
the headers of requested messages UIDs.
* UI/WebServerResources/SOGoDataTable.js: new interface to add
"live loading" to a table.
* UI/WebServerResources/SOGoMailDataSource.js: new class that
dynamically fetches the message headers depending on the requests
from a SOGoDataTable.
* UI/WebServerResources/SOGoResizableTable.js: new interface to
add resizable headers to a table.
* UI/WebServerResources/MailerUI.js (initMailer): associates a DIV
with the new SOGoDataTable interface.
(openMailbox): uses the new SOGoMailDataSource class to fetch the
messages list.
(messageListCallback): new function called to populate a row from
the data received from the SOGoDataTable interface and
SOGoMailDataSource class.
(updateMessageListCounter): new function to update the message
counter that appears in the headers table.
(toggleAddressColumn): new
function to toggle the address column header content
(From/To). This column changes depending on the mailbox type
selected (draft, sent or other).
* UI/WebServerResources/HTMLElement.js (refreshSelectionByIds):
new method to restore the selection based on the elements IDs instead
of the elements themselves.
* UI/MailerUI/UIxMailMainFrame.m (-showToAddress,
-columnsMetaData, -columnsMetaData, -columnsDisplayOrder,
-columnsOrder, setCurrentColumn, -currentColumn, -columnTitle):
moved those methods from UIxMailListView, since the messages table
is now populated with a JSON representation of the data.
2010-05-25 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Appointments/SOGoCalendarComponent.m
+1 -1
View File
@@ -17,7 +17,7 @@ MailerUI_OBJC_FILES += \
\
UIxMailMainFrame.m \
\
UIxMailListView.m \
UIxMailListActions.m \
UIxMailView.m \
UIxMailSourceView.m \
UIxMailPopupView.m \
@@ -19,8 +19,8 @@
02111-1307, USA.
*/
#ifndef UIXMAILLISTVIEW_H
#define UIXMAILLISTVIEW_H
#ifndef UIXMAILLISTACTIONS_H
#define UIXMAILLISTACTIONS_H
#import <SOGoUI/UIxComponent.h>
@@ -28,24 +28,23 @@
@class EOQualifier;
@class SOGoDateFormatter;
@interface UIxMailListView : UIxComponent
@interface UIxMailListActions : UIxComponent
{
NSArray *sortedUIDs; /* we always need to retrieve all anyway! */
NSArray *messages;
NSArray *columnsOrder;
unsigned firstMessageNumber;
id message;
EOQualifier *qualifier;
SOGoDateFormatter *dateFormatter;
NSTimeZone *userTimeZone;
int folderType;
NSDictionary *currentColumn;
int specificMessageNumber;
}
- (NSString *) defaultSortKey;
- (NSString *) imap4SortKey;
- (NSString *) imap4SortOrdering;
- (EOQualifier *) searchQualifier;
- (NSString *) msgLabels;
@end
#endif /* UIXMAILLISTVIEW_H */
#endif /* UIXMAILLISTACTIONS_H */
+665
View File
@@ -0,0 +1,665 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOGo
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
/*
UIxMailListActions
This component represent a list of mails and is attached to an SOGoMailFolder
object.
*/
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGImap4/NGImap4Envelope.h>
#import <EOControl/EOQualifier.h>
#import <Mailer/NSString+Mail.h>
#import <Mailer/SOGoDraftsFolder.h>
#import <Mailer/SOGoMailFolder.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoSentFolder.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/SOGoDateFormatter.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import "WOContext+UIxMailer.h"
#import "UIxMailListActions.h"
@implementation UIxMailListActions
- (id) init
{
SOGoUser *user;
if ((self = [super init]))
{
user = [context activeUser];
ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
ASSIGN (userTimeZone, [[user userDefaults] timeZone]);
folderType = 0;
specificMessageNumber = 0;
}
return self;
}
- (void) dealloc
{
[sortedUIDs release];
[messages release];
[message release];
[dateFormatter release];
[userTimeZone release];
[super dealloc];
}
/* accessors */
- (void) setMessage: (id) _msg
{
ASSIGN (message, _msg);
}
- (id) message
{
return message;
}
- (NSString *) messageDate
{
NSCalendarDate *messageDate;
messageDate = [[message valueForKey: @"envelope"] date];
[messageDate setTimeZone: userTimeZone];
return [dateFormatter formattedDateAndTime: messageDate];
}
- (NSString *) messageSize
{
NSString *rc;
int size;
size = [[message valueForKey: @"size"] intValue];
if (size > 1024*1024)
rc = [NSString stringWithFormat: @"%.1f MB", (float) size/1024/1024];
else if (size > 1024*100)
rc = [NSString stringWithFormat: @"%d KB", size/1024];
else
rc = [NSString stringWithFormat: @"%.1f KB", (float) size/1024];
return rc;
}
//
// Priorities are defined like this:
//
// X-Priority: 1 (Highest)
// X-Priority: 2 (High)
// X-Priority: 3 (Normal)
// X-Priority: 4 (Low)
// X-Priority: 5 (Lowest)
//
// Sometimes, the MUAs don't send over the string in () so we ignore it.
//
- (NSString *) messagePriority
{
NSString *result;
NSData *data;
data = [message objectForKey: @"header"];
result = @"";
if (data)
{
NSString *s;
s = [[NSString alloc] initWithData: data
encoding: NSASCIIStringEncoding];
if (s)
{
NSRange r;
[s autorelease];
r = [s rangeOfString: @":"];
if (r.length)
{
s = [[s substringFromIndex: r.location+1]
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([s hasPrefix: @"1"]) result = [self labelForKey: @"highest"];
else if ([s hasPrefix: @"2"]) result = [self labelForKey: @"high"];
else if ([s hasPrefix: @"4"]) result = [self labelForKey: @"low"];
else if ([s hasPrefix: @"5"]) result = [self labelForKey: @"lowest"];
}
}
}
return result;
}
- (NSString *) messageSubject
{
id baseSubject;
NSString *subject;
baseSubject = [[message valueForKey: @"envelope"] subject];
subject = [baseSubject decodedHeader];
if (![subject length])
subject = [self labelForKey: @"Untitled"];
return subject;
}
- (BOOL) showToAddress
{
SOGoMailFolder *co;
if (!folderType)
{
co = [self clientObject];
if ([co isKindOfClass: [SOGoSentFolder class]]
|| [co isKindOfClass: [SOGoDraftsFolder class]])
folderType = 1;
else
folderType = -1;
}
return (folderType == 1);
}
/* title */
- (NSString *) objectTitle
{
return [[self clientObject] nameInContainer];
}
- (NSString *) panelTitle
{
NSString *s;
s = [self labelForKey:@"View Mail Folder"];
s = [s stringByAppendingString:@": "];
s = [s stringByAppendingString:[self objectTitle]];
return s;
}
/* derived accessors */
- (BOOL) isMessageDeleted
{
NSArray *flags;
flags = [[self message] valueForKey:@"flags"];
return [flags containsObject:@"deleted"];
}
- (BOOL) isMessageRead
{
NSArray *flags;
flags = [[self message] valueForKey:@"flags"];
return [flags containsObject:@"seen"];
}
- (BOOL) isMessageFlagged
{
NSArray *flags;
flags = [[self message] valueForKey:@"flags"];
return [flags containsObject:@"flagged"];
}
- (NSString *) messageUidString
{
return [[[self message] valueForKey:@"uid"] stringValue];
}
- (NSString *) messageRowStyleClass
{
NSArray *flags;
NSString *cellClass = @"";
flags = [[self message] valueForKey:@"flags"];
if ([self isMessageDeleted])
cellClass = [cellClass stringByAppendingString: @"mailer_listcell_deleted "];
if (![self isMessageRead])
cellClass = [cellClass stringByAppendingString: @"mailer_unreadmail "];
if ([flags containsObject: @"answered"])
{
if ([flags containsObject: @"$forwarded"])
cellClass = [cellClass stringByAppendingString: @"mailer_forwardedrepliedmailsubject"];
else
cellClass = [cellClass stringByAppendingString: @"mailer_repliedmailsubject"];
}
else if ([flags containsObject: @"$forwarded"])
cellClass = [cellClass stringByAppendingString: @"mailer_forwardedmailsubject"];
else
cellClass = [cellClass stringByAppendingString: @"mailer_readmailsubject"];
return cellClass;
}
- (BOOL) hasMessageAttachment
{
NSArray *parts;
NSEnumerator *dispositions;
NSDictionary *currentDisp;
BOOL hasAttachment;
hasAttachment = NO;
parts = [[message objectForKey: @"body"] objectForKey: @"parts"];
if ([parts count] > 1)
{
dispositions = [[parts objectsForKey: @"disposition"
notFoundMarker: nil] objectEnumerator];
while (!hasAttachment
&& (currentDisp = [dispositions nextObject]))
hasAttachment = ([[currentDisp objectForKey: @"type"] length]);
}
return hasAttachment;
}
/* fetching messages */
- (NSArray *) fetchKeys
{
/* Note: see SOGoMailManager.m for allowed IMAP4 keys */
static NSArray *keys = nil;
if (!keys)
keys = [[NSArray alloc] initWithObjects: @"UID",
@"FLAGS", @"ENVELOPE", @"RFC822.SIZE",
@"BODYSTRUCTURE", @"BODY.PEEK[HEADER.FIELDS (X-PRIORITY)]", nil];
return keys;
}
- (NSString *) defaultSortKey
{
return @"ARRIVAL";
}
- (NSString *) imap4SortKey
{
NSString *sort;
sort = [[context request] formValueForKey: @"sort"];
if (![sort length])
sort = [self defaultSortKey];
return [sort uppercaseString];
}
- (NSString *) imap4SortOrdering
{
NSString *sort, *ascending;
sort = [self imap4SortKey];
ascending = [[context request] formValueForKey: @"asc"];
if (![ascending boolValue])
sort = [@"REVERSE " stringByAppendingString: sort];
return sort;
}
- (EOQualifier *) searchQualifier
{
NSString *criteria, *value;
EOQualifier *qualifier;
WORequest *request;
request = [context request];
criteria = [request formValueForKey: @"search"];
value = [request formValueForKey: @"value"];
qualifier = nil;
if ([value length])
{
if ([criteria isEqualToString: @"subject"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"(subject doesContain: %@)", value];
else if ([criteria isEqualToString: @"sender"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"(from doesContain: %@)", value];
else if ([criteria isEqualToString: @"subject_or_sender"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"((subject doesContain: %@)"
@" OR (from doesContain: %@))",
value, value];
else if ([criteria isEqualToString: @"to_or_cc"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"((to doesContain: %@)"
@" OR (cc doesContain: %@))",
value, value];
else if ([criteria isEqualToString: @"entire_message"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"(body doesContain: %@)", value];
}
return qualifier;
}
- (NSArray *) sortedUIDs
{
EOQualifier *qualifier, *fetchQualifier, *notDeleted;
SOGoMailFolder *folder;
if (!sortedUIDs)
{
notDeleted = [EOQualifier qualifierWithQualifierFormat:
@"(not (flags = %@))",
@"deleted"];
qualifier = [self searchQualifier];
if (qualifier)
{
fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
notDeleted, qualifier,
nil];
[fetchQualifier autorelease];
}
else
fetchQualifier = notDeleted;
folder = [self clientObject];
sortedUIDs
= [folder fetchUIDsMatchingQualifier: fetchQualifier
sortOrdering: [self imap4SortOrdering]];
[sortedUIDs retain];
}
return sortedUIDs;
}
- (int) indexOfMessageUID: (int) messageNbr
{
NSArray *messageNbrs;
int index;
messageNbrs = [self sortedUIDs];
index
= [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
// if (index < 0)
// index = 0;
return index;
}
/* JavaScript */
- (NSString *) msgRowID
{
return [@"row_" stringByAppendingString:[self messageUidString]];
}
- (NSString *) msgIconReadImgID
{
return [@"readdiv_" stringByAppendingString:[self messageUidString]];
}
- (NSString *) msgIconUnreadImgID
{
return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
}
/* error redirects */
/*
- (id) redirectToViewWithError: (id) _error
{
// TODO: DUP in UIxMailAccountView
// TODO: improve, localize
// TODO: there is a bug in the treeview which preserves the current URL for
// the active object (displaying the error again)
id url;
if (![_error isNotNull])
return [self redirectToLocation:@"view"];
if ([_error isKindOfClass:[NSException class]])
_error = [_error reason];
else if ([_error isKindOfClass:[NSString class]])
_error = [_error stringValue];
url = [_error stringByEscapingURL];
url = [@"view?error=" stringByAppendingString:url];
return [self redirectToLocation:url];
}
*/
/* actions */
- (id) getMailAction
{
// TODO: we might want to flush the caches?
id client;
if ((client = [self clientObject]) == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"did not find mail folder"];
}
if (![client respondsToSelector:@selector(flushMailCaches) ])
{
return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason:
@"invalid client object (does not support flush)"];
}
[client flushMailCaches];
return [self redirectToLocation:@"view"];
}
- (id <WOActionResults>) getSortedUIDsAction
{
NSArray *uids;
NSRange r;
WORequest *request;
WOResponse *response;
int firstUID, firstIndex, count;
request = [context request];
uids = [self sortedUIDs]; // retrieves the form parameters "sort" and "asc"
if ([request formValueForKey: @"start"] != nil)
{
firstUID = [[request formValueForKey: @"start"] intValue];
firstIndex = [self indexOfMessageUID: firstUID];
if (firstIndex == NSNotFound)
return [NSException exceptionWithHTTPStatus: 404
reason: @"Message not found"];
}
else
firstIndex = -1;
if ([request formValueForKey: @"count"] != nil)
{
count = [[request formValueForKey: @"count"] intValue];
}
else
count = 0;
if (firstIndex > -1)
{
if (count <= 0 || (count + firstIndex) > [uids count])
count = [uids count] - firstIndex;
r = NSMakeRange(firstIndex, count);
uids = [uids subarrayWithRange: r];
}
response = [context response];
[response setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"];
[response appendContentString: [uids jsonRepresentation]];
return response;
}
- (id <WOActionResults>) getHeadersAction
{
NSFormatter *addressFormatter;
NSArray *uids, *to;
NSDictionary *msgs;
NSMutableArray *headers;
NSMutableDictionary *msg;
NSEnumerator *msgsList;
NSString *msgIconStatus;
SOGoMailFolder *mailFolder;
WORequest *request;
WOResponse *response;
request = [context request];
if ([request formValueForKey: @"uids"] == nil)
{
return [NSException exceptionWithHTTPStatus: 404
reason: @"No UID specified"];
}
uids = [[request formValueForKey: @"uids"] componentsSeparatedByString: @","]; // Should we support ranges? ie "x-y"
headers = [NSMutableArray arrayWithCapacity: [uids count]];
mailFolder = [self clientObject];
addressFormatter = [context mailEnvelopeAddressFormatter];
// Fetch headers
msgs = (NSDictionary *)[mailFolder fetchUIDs: uids
parts: [self fetchKeys]];
msgsList = [[msgs objectForKey: @"fetch"] objectEnumerator];
[self setMessage: [msgsList nextObject]];
while (message)
{
msg = [NSMutableDictionary dictionaryWithCapacity: 11];
// Columns data
to = [[message objectForKey: @"envelope"] to];
if ([to count] > 0)
[msg setObject: [addressFormatter stringForArray: to]
forKey: @"To"];
if ([self hasMessageAttachment])
[msg setObject: [NSString stringWithFormat: @"<img src=\"%@\"/>", [self urlForResourceFilename: @"title_attachment_14x14.png"]]
forKey: @"Attachment"];
if ([self isMessageFlagged])
{
[msg setObject: [NSString stringWithFormat: @"<img src=\"%@\" class=\"messageIsFlagged\">",
[self urlForResourceFilename: @"flag.png"]]
forKey: @"Flagged"];
}
else
{
[msg setObject: [NSString stringWithFormat: @"<img src=\"%@\">",
[self urlForResourceFilename: @"dot.png"]]
forKey: @"Flagged"];
}
[msg setObject: [NSString stringWithFormat: @"<span>%@</span>",
[self messageSubject]]
forKey: @"Subject"];
[msg setObject: [addressFormatter stringForArray: [[message objectForKey: @"envelope"] from]] forKey: @"From"];
if ([self isMessageRead])
msgIconStatus = @"dot.png";
else
msgIconStatus = @"icon_unread.gif";
[msg setObject: [self messageRowStyleClass] forKey: @"rowClasses"];
[msg setObject: [NSString stringWithFormat: @"<img src=\"%@\" class=\"mailerReadIcon\" title=\"%@\" title-markread=\"%@\" title-markunread=\"%@\" id=\"%@\"/>",
[self urlForResourceFilename: msgIconStatus],
[self labelForKey: @"Mark Unread"],
[self labelForKey: @"Mark Read"],
[self labelForKey: @"Mark Unread"],
[self msgIconReadImgID]]
forKey: @"Unread"];
[msg setObject: [self messagePriority] forKey: @"Priority"];
[msg setObject: [self messageDate] forKey: @"Date"];
[msg setObject: [self messageSize] forKey: @"Size"];
[msg setObject: [self msgLabels] forKey: @"labels"];
[msg setObject: [self msgRowID] forKey: @"rowID"];
[msg setObject: [message objectForKey: @"uid"] forKey: @"uid"];
[headers addObject: msg];
[self setMessage: [msgsList nextObject]];
}
response = [context response];
[response setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"];
[response appendContentString: [headers jsonRepresentation]];
return response;
}
- (NSString *) msgLabels
{
NSMutableArray *labels;
NSEnumerator *flags;
NSString *currentFlag;
labels = [NSMutableArray array];
flags = [[message objectForKey: @"flags"] objectEnumerator];
while ((currentFlag = [flags nextObject]))
if ([currentFlag hasPrefix: @"$label"])
[labels addObject: [currentFlag substringFromIndex: 1]];
return [labels componentsJoinedByString: @" "];
}
@end
/* UIxMailListActions */
-874
View File
@@ -1,874 +0,0 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2009 Inverse inc.
This file is part of SOGo
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
/*
UIxMailListView
This component represent a list of mails and is attached to an SOGoMailFolder
object.
*/
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <EOControl/EOQualifier.h>
#import <Mailer/NSString+Mail.h>
#import <Mailer/SOGoDraftsFolder.h>
#import <Mailer/SOGoMailFolder.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoSentFolder.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/SOGoDateFormatter.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import "UIxMailListView.h"
#define messagesPerPage 50
@implementation UIxMailListView
- (id) init
{
SOGoUser *user;
if ((self = [super init]))
{
qualifier = nil;
user = [context activeUser];
ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
ASSIGN (userTimeZone, [[user userDefaults] timeZone]);
columnsOrder = nil;
folderType = 0;
currentColumn = nil;
}
return self;
}
- (void) dealloc
{
[qualifier release];
[sortedUIDs release];
[messages release];
[message release];
[dateFormatter release];
[userTimeZone release];
[currentColumn release];
[columnsOrder release];
[super dealloc];
}
/* accessors */
- (void) setMessage: (id) _msg
{
ASSIGN (message, _msg);
}
- (id) message
{
return message;
}
- (NSString *) messageDate
{
NSCalendarDate *messageDate;
messageDate = [[message valueForKey: @"envelope"] date];
[messageDate setTimeZone: userTimeZone];
return [dateFormatter formattedDateAndTime: messageDate];
}
- (NSString *) messageSize
{
NSString *rc;
int size;
size = [[message valueForKey: @"size"] intValue];
if (size > 1024*1024)
rc = [NSString stringWithFormat: @"%.1f MB", (float) size/1024/1024];
else if (size > 1024*100)
rc = [NSString stringWithFormat: @"%d KB", size/1024];
else
rc = [NSString stringWithFormat: @"%.1f KB", (float) size/1024];
return rc;
}
//
// Priorities are defined like this:
//
// X-Priority: 1 (Highest)
// X-Priority: 2 (High)
// X-Priority: 3 (Normal)
// X-Priority: 4 (Low)
// X-Priority: 5 (Lowest)
//
// Sometimes, the MUAs don't send over the string in () so we ignore it.
//
- (NSString *) messagePriority
{
NSString *result;
NSData *data;
data = [message objectForKey: @"header"];
result = @"";
if (data)
{
NSString *s;
s = [[NSString alloc] initWithData: data
encoding: NSASCIIStringEncoding];
if (s)
{
NSRange r;
[s autorelease];
r = [s rangeOfString: @":"];
if (r.length)
{
s = [[s substringFromIndex: r.location+1]
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([s hasPrefix: @"1"]) result = [self labelForKey: @"highest"];
else if ([s hasPrefix: @"2"]) result = [self labelForKey: @"high"];
else if ([s hasPrefix: @"4"]) result = [self labelForKey: @"low"];
else if ([s hasPrefix: @"5"]) result = [self labelForKey: @"lowest"];
}
}
}
return result;
}
- (NSString *) messageSubject
{
id baseSubject;
NSString *subject;
baseSubject = [[message valueForKey: @"envelope"] subject];
subject = [baseSubject decodedHeader];
if (![subject length])
subject = [self labelForKey: @"Untitled"];
return subject;
}
- (BOOL) showToAddress
{
SOGoMailFolder *co;
if (!folderType)
{
co = [self clientObject];
if ([co isKindOfClass: [SOGoSentFolder class]]
|| [co isKindOfClass: [SOGoDraftsFolder class]])
folderType = 1;
else
folderType = -1;
}
return (folderType == 1);
}
/* title */
- (NSString *) objectTitle
{
return [[self clientObject] nameInContainer];
}
- (NSString *) panelTitle
{
NSString *s;
s = [self labelForKey:@"View Mail Folder"];
s = [s stringByAppendingString:@": "];
s = [s stringByAppendingString:[self objectTitle]];
return s;
}
/* derived accessors */
- (BOOL) isMessageDeleted
{
NSArray *flags;
flags = [[self message] valueForKey:@"flags"];
return [flags containsObject:@"deleted"];
}
- (BOOL) isMessageRead
{
NSArray *flags;
flags = [[self message] valueForKey:@"flags"];
return [flags containsObject:@"seen"];
}
- (BOOL) isMessageFlagged
{
NSArray *flags;
flags = [[self message] valueForKey:@"flags"];
return [flags containsObject:@"flagged"];
}
- (NSString *) messageUidString
{
return [[[self message] valueForKey:@"uid"] stringValue];
}
- (NSString *) messageRowStyleClass
{
NSString *rowClass;
rowClass = [self isMessageDeleted]? @"mailer_listcell_deleted" : @"mailer_listcell_regular";
if (![self isMessageRead])
rowClass = [rowClass stringByAppendingString: @" mailer_unreadmail"];
return rowClass;
}
- (NSString *) messageSubjectCellStyleClass
{
NSArray *flags;
NSString *cellClass = @"messageSubjectColumn ";
flags = [[self message] valueForKey:@"flags"];
if ([flags containsObject: @"answered"])
{
if ([flags containsObject: @"$forwarded"])
cellClass = [cellClass stringByAppendingString: @"mailer_forwardedrepliedmailsubject"];
else
cellClass = [cellClass stringByAppendingString: @"mailer_repliedmailsubject"];
}
else if ([flags containsObject: @"$forwarded"])
cellClass = [cellClass stringByAppendingString: @"mailer_forwardedmailsubject"];
else
cellClass = [cellClass stringByAppendingString: @"mailer_readmailsubject"];
return cellClass;
}
- (BOOL) hasMessageAttachment
{
NSArray *parts;
NSEnumerator *dispositions;
NSDictionary *currentDisp;
BOOL hasAttachment;
hasAttachment = NO;
parts = [[message objectForKey: @"body"] objectForKey: @"parts"];
if ([parts count] > 1)
{
dispositions = [[parts objectsForKey: @"disposition"
notFoundMarker: nil] objectEnumerator];
while (!hasAttachment
&& (currentDisp = [dispositions nextObject]))
hasAttachment = ([[currentDisp objectForKey: @"type"] length]);
}
return hasAttachment;
}
/* fetching messages */
- (NSArray *) fetchKeys
{
/* Note: see SOGoMailManager.m for allowed IMAP4 keys */
static NSArray *keys = nil;
if (!keys)
keys = [[NSArray alloc] initWithObjects: @"UID",
@"FLAGS", @"ENVELOPE", @"RFC822.SIZE",
@"BODYSTRUCTURE", @"BODY.PEEK[HEADER.FIELDS (X-PRIORITY)]", nil];
return keys;
}
- (NSString *) defaultSortKey
{
return @"ARRIVAL";
}
- (NSString *) imap4SortKey
{
NSString *sort;
sort = [[context request] formValueForKey: @"sort"];
if (![sort length])
sort = [self defaultSortKey];
return [sort uppercaseString];
}
- (NSString *) imap4SortOrdering
{
NSString *sort, *ascending;
sort = [self imap4SortKey];
ascending = [[context request] formValueForKey: @"asc"];
if (![ascending boolValue])
sort = [@"REVERSE " stringByAppendingString: sort];
return sort;
}
- (NSRange) fetchRange
{
if (firstMessageNumber == 0)
return NSMakeRange(0, messagesPerPage);
return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
}
- (NSArray *) sortedUIDs
{
EOQualifier *fetchQualifier, *notDeleted;
if (!sortedUIDs)
{
notDeleted = [EOQualifier qualifierWithQualifierFormat:
@"(not (flags = %@))",
@"deleted"];
if (qualifier)
{
fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
notDeleted, qualifier,
nil];
[fetchQualifier autorelease];
}
else
fetchQualifier = notDeleted;
sortedUIDs
= [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier
sortOrdering: [self imap4SortOrdering]];
[sortedUIDs retain];
}
return sortedUIDs;
}
- (unsigned int) totalMessageCount
{
return [sortedUIDs count];
}
- (BOOL) showsAllMessages
{
return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
}
- (NSRange) fetchBlock
{
NSRange r;
unsigned len;
NSArray *uids;
r = [self fetchRange];
uids = [self sortedUIDs];
/* only need to restrict if we have a lot */
if ((len = [uids count]) <= r.length) {
r.location = 0;
r.length = len;
return r;
}
if (len < r.location) {
// TODO: CHECK CONDITION (< vs <=)
/* out of range, recover at first block */
r.location = 0;
return r;
}
if (r.location + r.length > len)
r.length = len - r.location;
return r;
}
- (unsigned int) firstMessageNumber
{
return [self fetchBlock].location + 1;
}
- (unsigned int) lastMessageNumber
{
NSRange r;
r = [self fetchBlock];
return r.location + r.length;
}
- (BOOL) hasPrevious
{
return [self fetchBlock].location == 0 ? NO : YES;
}
- (BOOL) hasNext
{
NSRange r = [self fetchBlock];
return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
}
- (unsigned int) nextFirstMessageNumber
{
return [self firstMessageNumber] + [self fetchRange].length;
}
- (unsigned int) lastFirstMessageNumber
{
unsigned int max, modulo;
if (!sortedUIDs)
[self sortedUIDs];
max = [sortedUIDs count];
modulo = (max % messagesPerPage);
if (modulo == 0)
modulo = messagesPerPage;
return (max + 1 - modulo);
}
- (unsigned int) prevFirstMessageNumber
{
NSRange r;
unsigned idx;
idx = [self firstMessageNumber];
r = [self fetchRange];
if (idx > r.length)
return (idx - r.length);
return 1;
}
- (NSArray *) messages
{
NSMutableArray *unsortedMsgs;
NSMutableDictionary *map;
NSDictionary *msgs;
NSArray *uids;
unsigned len, i, count;
NSRange r;
if (!messages)
{
r = [self fetchBlock];
uids = [self sortedUIDs];
len = [uids count];
// only need to restrict if we have a lot
if (len > r.length)
{
uids = [uids subarrayWithRange: r];
len = [uids count];
}
// Don't assume the IMAP server return the messages in the
// same order as the specified list of UIDs (specially true for
// dovecot).
msgs = (NSDictionary *) [[self clientObject] fetchUIDs: uids
parts: [self fetchKeys]];
unsortedMsgs = [msgs objectForKey: @"fetch"];
count = [unsortedMsgs count];
messages = [NSMutableArray arrayWithCapacity: count];
// We build our uid->message map from our FETCH response
map = [[NSMutableDictionary alloc] initWithCapacity: count];
for (i = 0; i < count; i++)
[map setObject: [unsortedMsgs objectAtIndex: i]
forKey: [[unsortedMsgs objectAtIndex: i] objectForKey: @"uid"]];
for (i = 0; i < len; i++)
{
[(NSMutableArray *)messages addObject: [map objectForKey: [uids objectAtIndex: i]]];
}
RELEASE(map);
RETAIN(messages);
}
return messages;
}
/* URL processing */
- (NSString *) messageViewTarget
{
return [NSString stringWithFormat: @"SOGo_msg_%@",
[self messageUidString]];
}
- (NSString *) messageViewURL
{
// TODO: noframe only when view-target is empty
// TODO: markread only if the message is unread
NSString *s;
s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
return s;
}
- (NSString *) markReadURL
{
return [@"markMessageRead?uid=" stringByAppendingString:
[self messageUidString]];
}
- (NSString *) markUnreadURL
{
return [@"markMessageUnread?uid=" stringByAppendingString:
[self messageUidString]];
}
/* JavaScript */
- (NSString *)msgRowID
{
return [@"row_" stringByAppendingString:[self messageUidString]];
}
- (NSString *)msgDivID
{
return [@"div_" stringByAppendingString:[self messageUidString]];
}
- (NSString *)msgIconReadImgID
{
return [@"readdiv_" stringByAppendingString:[self messageUidString]];
}
- (NSString *)msgIconUnreadImgID
{
return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
}
/* error redirects */
- (id) redirectToViewWithError: (id) _error
{
// TODO: DUP in UIxMailAccountView
// TODO: improve, localize
// TODO: there is a bug in the treeview which preserves the current URL for
// the active object (displaying the error again)
id url;
if (![_error isNotNull])
return [self redirectToLocation:@"view"];
if ([_error isKindOfClass:[NSException class]])
_error = [_error reason];
else if ([_error isKindOfClass:[NSString class]])
_error = [_error stringValue];
url = [_error stringByEscapingURL];
url = [@"view?error=" stringByAppendingString:url];
return [self redirectToLocation:url];
}
/* actions */
- (int) firstMessageOfPageFor: (int) messageNbr
{
NSArray *messageNbrs;
int nbrInArray;
int firstMessage;
messageNbrs = [self sortedUIDs];
nbrInArray
= [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
if (nbrInArray > -1)
firstMessage = ((int) (nbrInArray / messagesPerPage)
* messagesPerPage) + 1;
else
firstMessage = 1;
return firstMessage;
}
- (void) _setQualifierForCriteria: (NSString *) criteria
andValue: (NSString *) value
{
[qualifier release];
if ([criteria isEqualToString: @"subject"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"(subject doesContain: %@)", value];
else if ([criteria isEqualToString: @"sender"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"(from doesContain: %@)", value];
else if ([criteria isEqualToString: @"subject_or_sender"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"((subject doesContain: %@)"
@" OR (from doesContain: %@))",
value, value];
else if ([criteria isEqualToString: @"to_or_cc"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"((to doesContain: %@)"
@" OR (cc doesContain: %@))",
value, value];
else if ([criteria isEqualToString: @"entire_message"])
qualifier = [EOQualifier qualifierWithQualifierFormat:
@"(body doesContain: %@)", value];
else
qualifier = nil;
[qualifier retain];
}
- (id) defaultAction
{
WORequest *request;
NSString *specificMessage, *searchCriteria, *searchValue;
SOGoMailFolder *co;
request = [context request];
co = [self clientObject];
[co flushMailCaches];
[co expungeLastMarkedFolder];
specificMessage = [request formValueForKey: @"pageforuid"];
searchCriteria = [request formValueForKey: @"search"];
searchValue = [request formValueForKey: @"value"];
if ([searchValue length])
[self _setQualifierForCriteria: searchCriteria
andValue: searchValue];
firstMessageNumber
= ((specificMessage)
? [self firstMessageOfPageFor: [specificMessage intValue]]
: [[request formValueForKey:@"idx"] intValue]);
return self;
}
- (id) getMailAction
{
// TODO: we might want to flush the caches?
id client;
if ((client = [self clientObject]) == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"did not find mail folder"];
}
if (![client respondsToSelector:@selector(flushMailCaches) ])
{
return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason:
@"invalid client object (does not support flush)"];
}
[client flushMailCaches];
return [self redirectToLocation:@"view"];
}
- (NSString *) msgLabels
{
NSMutableArray *labels;
NSEnumerator *flags;
NSString *currentFlag;
labels = [NSMutableArray array];
flags = [[message objectForKey: @"flags"] objectEnumerator];
while ((currentFlag = [flags nextObject]))
if ([currentFlag hasPrefix: @"$label"])
[labels addObject: [currentFlag substringFromIndex: 1]];
return [labels componentsJoinedByString: @" "];
}
- (NSDictionary *) columnsMetaData
{
NSMutableDictionary *columnsMetaData;
NSArray *tmpColumns, *tmpKeys;
columnsMetaData = [NSMutableDictionary dictionaryWithCapacity:8];
tmpKeys = [NSArray arrayWithObjects: @"headerClass", @"headerId", @"value",
nil];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell sortableTableHeader",
@"subjectHeader", @"Subject", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Subject"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell messageFlagColumn",
@"invisibleHeader", @"Flagged", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Flagged"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell messageFlagColumn",
@"attachmentHeader", @"Attachment", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects:
tmpColumns
forKeys: tmpKeys]
forKey: @"Attachment"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell", @"messageFlagHeader",
@"Unread", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Unread"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell sortableTableHeader",
@"toHeader", @"To", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"To"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell sortableTableHeader",
@"fromHeader", @"From", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"From"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell sortableTableHeader",
@"dateHeader", @"Date", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Date"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell", @"priorityHeader",
@"Priority", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Priority"];
tmpColumns
= [NSArray arrayWithObjects: @"tbtv_headercell sortableTableHeader", @"sizeHeader",
@"Size", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Size"];
return columnsMetaData;
}
- (NSArray *) columnsDisplayOrder
{
NSMutableArray *finalOrder, *invalid;
NSArray *available;
NSDictionary *metaData;
SOGoUserDefaults *ud;
unsigned int i;
if (!columnsOrder)
{
ud = [[context activeUser] userDefaults];
columnsOrder = [ud mailListViewColumnsOrder];
metaData = [self columnsMetaData];
invalid = [columnsOrder mutableCopy];
[invalid autorelease];
available = [metaData allKeys];
[invalid removeObjectsInArray: available];
if ([invalid count] > 0)
{
[self errorWithFormat: @"those column names specified in"
@" SOGoMailListViewColumnsOrder are invalid: '%@'",
[invalid componentsJoinedByString: @"', '"]];
[self errorWithFormat: @" falling back on hardcoded column order"];
columnsOrder = available;
}
finalOrder = [columnsOrder mutableCopy];
[finalOrder autorelease];
if ([self showToAddress])
{
i = [finalOrder indexOfObject: @"From"];
if (i != NSNotFound)
[finalOrder replaceObjectAtIndex: i withObject: @"To"];
}
else
{
i = [finalOrder indexOfObject: @"To"];
if (i != NSNotFound)
[finalOrder replaceObjectAtIndex: i withObject: @"From"];
}
columnsOrder = [[self columnsMetaData] objectsForKeys: finalOrder
notFoundMarker: @""];
[columnsOrder retain];
}
return columnsOrder;
}
- (NSString *) columnsDisplayCount
{
return [NSString stringWithFormat: @"%d", [[self columnsDisplayOrder] count]];
}
- (void) setCurrentColumn: (NSDictionary *) newCurrentColumn
{
ASSIGN (currentColumn, newCurrentColumn);
}
- (NSDictionary *) currentColumn
{
return currentColumn;
}
- (NSString *) columnTitle
{
return [self labelForKey: [currentColumn objectForKey: @"value"]];
}
@end
/* UIxMailListView */
+6
View File
@@ -29,6 +29,10 @@
{
SOGoUserSettings *us;
NSMutableDictionary *moduleSettings;
NSArray *columnsOrder;
int folderType;
NSDictionary *currentColumn;
}
- (WOResponse *) getFoldersStateAction;
@@ -42,6 +46,8 @@
- (NSString *) formattedMailtoString: (NGVCard *) card;
- (NSArray *) columnsDisplayOrder;
@end
#endif /* UIXMAILMAINFRAME_H */
+186 -6
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2007-2009 Inverse inc.
Copyright (C) 2007-2010 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOGo.
@@ -30,6 +30,7 @@
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/SoComponent.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <Contacts/SOGoContactObject.h>
@@ -114,11 +115,8 @@
- (NSString *) defaultColumnsOrder
{
SOGoDomainDefaults *dd;
dd = [[context activeUser] domainDefaults];
return [[dd mailListViewColumnsOrder] jsonRepresentation];
return [[[self columnsDisplayOrder] objectsForKey: @"value"
notFoundMarker: @""] jsonRepresentation];
}
- (NSString *) pageFormURL
@@ -402,4 +400,186 @@
return [super defaultAction];
}
/**
*
* methods from UIxMailListView
*/
- (id) init
{
if ((self = [super init]))
{
folderType = 0;
}
return self;
}
- (BOOL) showToAddress
{
SOGoMailFolder *co;
if (!folderType)
{
co = [self clientObject];
if ([co isKindOfClass: [SOGoSentFolder class]]
|| [co isKindOfClass: [SOGoDraftsFolder class]])
folderType = 1;
else
folderType = -1;
}
return (folderType == 1);
}
- (NSDictionary *) columnsMetaData
{
NSMutableDictionary *columnsMetaData;
NSArray *tmpColumns, *tmpKeys;
columnsMetaData = [NSMutableDictionary dictionaryWithCapacity: 8];
tmpKeys = [NSArray arrayWithObjects: @"headerClass", @"headerId", @"value",
nil];
tmpColumns
= [NSArray arrayWithObjects: @"messageSubjectColumn tbtv_headercell sortableTableHeader resizable",
@"subjectHeader", @"Subject", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Subject"];
tmpColumns
= [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell",
@"invisibleHeader", @"Flagged", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Flagged"];
tmpColumns
= [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell",
@"attachmentHeader", @"Attachment", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Attachment"];
tmpColumns
= [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell", @"messageFlagHeader",
@"Unread", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys]
forKey: @"Unread"];
tmpColumns
= [NSArray arrayWithObjects: @"messageAddressHeader tbtv_headercell sortableTableHeader resizable",
@"toHeader", @"To", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys]
forKey: @"To"];
tmpColumns
= [NSArray arrayWithObjects: @"messageAddressColumn tbtv_headercell sortableTableHeader resizable",
@"fromHeader", @"From", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"From"];
tmpColumns
= [NSArray arrayWithObjects: @"messageDateColumn tbtv_headercell sortableTableHeader resizable",
@"dateHeader", @"Date", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Date"];
tmpColumns
= [NSArray arrayWithObjects: @"messagePriorityColumn tbtv_headercell resizable", @"priorityHeader",
@"Priority", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Priority"];
tmpColumns
= [NSArray arrayWithObjects: @"messageSizeColumn tbtv_headercell sortableTableHeader", @"sizeHeader",
@"Size", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Size"];
return columnsMetaData;
}
- (NSArray *) columnsDisplayOrder
{
NSMutableArray *finalOrder, *invalid;
NSArray *available;
NSDictionary *metaData;
SOGoUserDefaults *ud;
unsigned int i;
if (!columnsOrder)
{
ud = [[context activeUser] userDefaults];
columnsOrder = [ud mailListViewColumnsOrder];
metaData = [self columnsMetaData];
invalid = [columnsOrder mutableCopy];
[invalid autorelease];
available = [metaData allKeys];
[invalid removeObjectsInArray: available];
if ([invalid count] > 0)
{
[self errorWithFormat: @"those column names specified in"
@" SOGoMailListViewColumnsOrder are invalid: '%@'",
[invalid componentsJoinedByString: @"', '"]];
[self errorWithFormat: @" falling back on hardcoded column order"];
columnsOrder = available;
}
finalOrder = [columnsOrder mutableCopy];
[finalOrder autorelease];
if ([self showToAddress])
{
i = [finalOrder indexOfObject: @"From"];
if (i != NSNotFound)
{
[finalOrder removeObject: @"To"];
[finalOrder replaceObjectAtIndex: i withObject: @"To"];
}
}
else
{
i = [finalOrder indexOfObject: @"To"];
if (i != NSNotFound)
{
[finalOrder removeObject: @"From"];
[finalOrder replaceObjectAtIndex: i withObject: @"From"];
}
}
columnsOrder = [[self columnsMetaData] objectsForKeys: finalOrder
notFoundMarker: @""];
[columnsOrder retain];
}
return columnsOrder;
}
- (NSString *) columnsDisplayCount
{
return [NSString stringWithFormat: @"%d", [[self columnsDisplayOrder] count]];
}
- (void) setCurrentColumn: (NSDictionary *) newCurrentColumn
{
ASSIGN (currentColumn, newCurrentColumn);
}
- (NSDictionary *) currentColumn
{
return currentColumn;
}
- (NSString *) columnTitle
{
return [self labelForKey: [currentColumn objectForKey: @"value"]];
}
@end /* UIxMailMainFrame */
+13 -15
View File
@@ -77,9 +77,20 @@
};
};
methods = {
view = {
getMail = {
protectedBy = "View";
pageName = "UIxMailListView";
pageName = "UIxMailListActions";
actionName = "getMail";
};
uids = {
protectedBy = "<public>";
pageName = "UIxMailListActions";
actionName = "getSortedUIDs";
};
headers = {
protectedBy = "<public>";
pageName = "UIxMailListActions";
actionName = "getHeaders";
};
subscribe = {
protectedBy = "<public>";
@@ -96,19 +107,6 @@
actionClass = "UIxMailFolderActions";
actionName = "quotas";
};
index = {
protectedBy = "View";
pageName = "UIxMailListView";
};
GET = { /* hack to make it work as the default method */
protectedBy = "View";
pageName = "UIxMailListView";
};
getMail = {
protectedBy = "View";
pageName = "UIxMailListView";
actionName = "getMail";
};
expunge = {
protectedBy = "View";
actionClass = "UIxMailFolderActions";
-152
View File
@@ -1,152 +0,0 @@
<?xml version='1.0' standalone='yes'?>
<!DOCTYPE table>
<table id="messageList" cellspacing="0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label">
<thead>
<tr class="tableview"
><var:foreach list="columnsDisplayOrder" item="currentColumn">
<td var:class="currentColumn.headerClass" var:id="currentColumn.headerId">
<var:if condition="currentColumn.value" const:value="Flagged">
<img rsrc:src="empty.gif" width="100%" height="100%" const:title="" />
</var:if>
<var:if condition="currentColumn.value" const:value="Attachment">
<img rsrc:src="title_attachment_14x14.png" label:title="$currentColumn.value" width="14"
height="14"/>
</var:if>
<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>
</var:if>
</var:if>
</td>
</var:foreach>
</tr>
<tr id="messageCountHeader" class="tableview"
><td var:colspan="columnsDisplayCount" class="tbtv_navcell"
><var:if condition="hasPrevious">
<a href="#"
idx="1"><var:string label:value="first"/></a> |
<a href="#"
var:idx="prevFirstMessageNumber"
><var:string label:value="previous"/></a> |
</var:if>
<var:if condition="lastMessageNumber" const:negate="YES">
0 <var:string label:value="message"/>
</var:if>
<var:if condition="lastMessageNumber" const:negate="0">
<var:string value="firstMessageNumber" />
<var:string label:value="msgnumber_to" />
<var:string value="lastMessageNumber" />
<var:string label:value="msgnumber_of" />
<var:string value="sortedUIDs.count" />
<var:if condition="hasNext"
>| <a href="#"
var:idx="nextFirstMessageNumber"
><var:string label:value="next" /></a>
| <a href="#"
var:idx="lastFirstMessageNumber"
><var:string label:value="last" /></a>
</var:if>
</var:if
></td
></tr
>
</thead>
<tbody>
<var:foreach list="messages" item="message">
<tr var:class="messageRowStyleClass" var:id="msgRowID" var:labels="msgLabels"
><var:foreach list="columnsDisplayOrder" item="currentColumn"
><var:if condition="currentColumn.value" const:value="Flagged"
><td class="messageFlagColumn messageFlag"
><var:if condition="isMessageFlagged"
><img rsrc:src="flag.png" const:class="messageIsFlagged"
/></var:if
><var:if condition="isMessageFlagged" const:negate="YES"
><img rsrc:src="dot.png"
/></var:if
></td
></var:if>
<var:if condition="currentColumn.value" const:value="Attachment"
><td class="messageFlagColumn"
><var:if condition="hasMessageAttachment"
><img rsrc:src="title_attachment_14x14.png"
/></var:if
></td
></var:if
><var:if condition="currentColumn.value" const:value="Subject"
><td var:class="messageSubjectCellStyleClass" var:id="msgDivID"
><var:string value="messageSubject"
/></td
></var:if
><var:if condition="currentColumn.value" const:value="From"
><td class="messageAddressColumn"
><var:if condition="showToAddress" const:negate="YES"
><var:string value="message.envelope.from" formatter="context.mailEnvelopeAddressFormatter"
/></var:if
><var:if condition="showToAddress"
><var:string value="message.envelope.to" formatter="context.mailEnvelopeAddressFormatter"
/></var:if
></td
></var:if
><var:if condition="currentColumn.value" const:value="To"
><td class="messageAddressColumn"
><var:if condition="showToAddress" const:negate="YES"
><var:string value="message.envelope.from" formatter="context.mailEnvelopeAddressFormatter"
/></var:if
><var:if condition="showToAddress"
><var:string value="message.envelope.to" formatter="context.mailEnvelopeAddressFormatter"
/></var:if
></td
></var:if
><var:if condition="currentColumn.value" const:value="Unread"
><td class="messageFlagColumn"
><var:if condition="isMessageRead"
><img rsrc:src="dot.png"
class="mailerReadIcon"
label:title="Mark Unread"
label:title-markread="Mark Read"
label:title-markunread="Mark Unread"
var:id="msgIconReadImgID"
/></var:if
><var:if condition="isMessageRead" const:negate="YES"
><img rsrc:src="icon_unread.gif"
class="mailerUnreadIcon"
label:title="Mark Read"
label:title-markread="Mark Read"
label:title-markunread="Mark Unread"
var:id="msgIconUnreadImgID"
/></var:if
></td
></var:if
><var:if condition="currentColumn.value" const:value="Date"
><td class="messageDateColumn"
><var:string value="messageDate"
/><entity name="nbsp"
/></td
></var:if
><var:if condition="currentColumn.value" const:value="Priority"
><td class="messagePriorityColumn"
><var:string value="messagePriority"
/><entity name="nbsp"
/></td
></var:if
><var:if condition="currentColumn.value" const:value="Size"
><td class="messagePriorityColumn"
><var:string value="messageSize"
/><entity name="nbsp"
/></td
></var:if>
</var:foreach>
</tr>
</var:foreach>
</tbody>
</table>
+84 -2
View File
@@ -7,7 +7,7 @@
xmlns:label="OGo:label"
className="UIxPageFrame"
title="title"
const:jsFiles="dtree.js,MailerUIdTree.js,SOGoAutoCompletion.js">
const:jsFiles="dtree.js,MailerUIdTree.js,SOGoAutoCompletion.js,SOGoResizableTable.js,SOGoMailDataSource.js,SOGoDataTable.js">
<script type="text/javascript">
var textMailAccounts = '<var:string value="mailAccounts" const:escapeHTML="NO"/>';
var textDefaultColumnsOrder = '<var:string value="defaultColumnsOrder" const:escapeHTML="NO"/>';
@@ -215,7 +215,89 @@
<div id="rightPanel">
<var:component className="UIxMailFilterPanel" qualifier="qualifier" />
<div id="mailboxContent"><!-- space --></div>
<div id="mailboxContent">
<table id="messageListHeader" class="messageList" cellspacing="0">
<thead>
<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="Flagged">
<img rsrc:src="empty.gif" width="100%" height="100%" const:title="" />
</var:if>
<var:if condition="currentColumn.value" const:value="Attachment">
<img rsrc:src="title_attachment_14x14.png" label:title="$currentColumn.value" width="14"
height="14"/>
</var:if>
<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>
</var:if>
</var:if>
</th>
</var:foreach>
</tr>
<tr id="messageCountHeader" class="tableview"
><th var:colspan="columnsDisplayCount" class="tbtv_navcell"
><!-- empty --></th
></tr
>
</thead>
</table>
<div id="mailboxList">
<table id="messageListBody" class="messageList" cellspacing="0">
<tbody>
<tr const:style="display: none;"
><var:foreach list="columnsDisplayOrder" item="currentColumn"
><var:if condition="currentColumn.value" const:value="Flagged"
><td class="messageFlagColumn messageFlag"
><!-- flagged --></td
></var:if>
<var:if condition="currentColumn.value" const:value="Attachment"
><td class="messageFlagColumn"
><!-- attachment --></td
></var:if
><var:if condition="currentColumn.value" const:value="Subject"
><td class="messageSubjectColumn"
><!-- subject --></td
></var:if
><var:if condition="currentColumn.value" const:value="From"
><td class="messageAddressColumn"
><!-- from --></td
></var:if
><var:if condition="currentColumn.value" const:value="To"
><td class="messageAddressColumn"
><!-- to --></td
></var:if
><var:if condition="currentColumn.value" const:value="Unread"
><td class="messageFlagColumn"
><!-- unread --></td
></var:if
><var:if condition="currentColumn.value" const:value="Date"
><td class="messageDateColumn"
><!-- date --></td
></var:if
><var:if condition="currentColumn.value" const:value="Priority"
><td class="messagePriorityColumn"
><!-- priority --></td
></var:if
><var:if condition="currentColumn.value" const:value="Size"
><td class="messageSizeColumn"
><!-- size --></td
></var:if>
</var:foreach>
</tr>
</tbody>
</table>
</div>
</div>
<div class="dragHandle" id="rightDragHandle"><!-- space --></div>
<div id="messageContent"><!-- space --></div>
</div>
+28 -5
View File
@@ -8,7 +8,7 @@ Element.addMethods({
if (element.bind)
element.bind();
},
childNodesWithTag: function(element, tagName) {
element = $(element);
@@ -165,15 +165,18 @@ Element.addMethods({
element.addClassName('_selected');
var parent = element.up();
if (!parent.selectedElements)
if (!parent.selectedElements) {
// Selected nodes are kept in a array at the
// container level.
parent.selectedElements = new Array();
parent.selectedIds = new Array();
}
for (var i = 0; i < parent.selectedElements.length; i++)
if (parent.selectedElements[i] == element) return;
parent.selectedElements.push(element); // use index instead ?
parent.selectedIds.push(element.id);
},
selectRange: function(element, startIndex, endIndex) {
element = $(element);
var s;
@@ -202,10 +205,11 @@ Element.addMethods({
deselect: function(element) {
element = $(element);
element.removeClassName('_selected');
var parent = element.up();
if (parent && parent.selectedElements)
if (parent && parent.selectedElements) {
parent.selectedElements = parent.selectedElements.without(element);
parent.selectedIds = parent.selectedIds.without(element.id);
}
},
deselectAll: function(element) {
@@ -215,6 +219,25 @@ Element.addMethods({
element.selectedElements[i].removeClassName('_selected');
element.selectedElements = null;
}
if (element.selectedIds) {
for (var i = 0; i < element.selectedIds.length; i++) {
var e = element.down('#' + element.selectedIds[i]);
if (e && e.hasClassName('_selected'))
e.removeClassName('_selected');
}
element.selectedIds = null;
}
},
refreshSelectionByIds: function(element) {
element = $(element);
if (element.selectedIds) {
for (var i = 0; i < element.selectedIds.length; i++) {
var e = element.down('#'+element.selectedIds[i]);
if (e && !e.hasClassName('_selected'))
e.addClassName('_selected');
}
}
},
setCaretTo: function(element, pos) {
+119 -131
View File
@@ -18,7 +18,6 @@
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
/* TODO: is the section below used in the mailer? */
DIV#leftPanel
{
@@ -53,7 +52,10 @@ DIV#mailboxContent
right: 0px;
height: 15.5em;
border-left: 1px solid #9B9B9B;
overflow: auto;
overflow: hidden; }
DIV#mailboxList
{ overflow: auto;
overflow-x: hidden; }
DIV#messageContent
@@ -225,81 +227,160 @@ DIV.dTreeNode SPAN.unseen
/* mail tableview */
TD.mailer_readmailsubject
{
padding-left: 20px !important;
}
/* messages table with fixed headers */
TR.mailer_unreadmail TD
{
font-weight: bold !important;
}
TABLE.messageList
{ width: 100%;
-moz-user-select: none;
-khtml-user-select: none; }
TABLE.messageList TH,
TABLE.messageList TD
{ height: 20px;
min-height: 20px;
text-overflow: ellipsis;
overflow: hidden;
padding: 2px 3px;
min-width: 22px;
margin: 0;
white-space: nowrap; }
TR#messageCountHeader TH
{ border-top: 0px; }
TABLE.messageList TD
{ border-right: 1px solid #fff; }
TABLE.messageList .messageFlagColumn
{ width: 22px;
max-width: 22px;
text-align: center; }
TABLE.messageList .messageSubjectColumn
{ max-width: 30%;
width: 30%; }
TABLE.messageList .messageSubjectColumn SPAN
{ padding-left: 20px; }
TABLE.messageList .messageAddressColumn
{ max-width: 18%;
width: 18%; }
TABLE.messageList .messageDateColumn
{ max-width: 22%;
width: 22%; }
TABLE.messageList .messagePriorityColumn
{ width: 60px;
max-width: 60px; }
TABLE.messageList .messageSizeColumn
{ min-width: 40px; }
TR#rowTop TD
{ height: 0;
min-height: 0;
padding-top: 0;
padding-bottom: 0; }
TABLE.messageList TR._selected TD
{ background-color: #9ABCD8;
color: #fff; }
TABLE.messageList TR._deleted TD
{ text-decoration: line-through; }
TABLE.messageList TR[labels~="label5"] TD
{ color: #9c309c; }
TABLE.messageList TR[labels~="label5"]._selected TD
{ color: #fff;
background-color: #9c309c; }
TABLE.messageList TR[labels~="label4"] TD
{ color: #3130ff; }
TABLE.messageList TR[labels~="label4"]._selected TD
{ color: #fff;
background-color: #3130ff; }
TABLE.messageList TR[labels~="label3"] TD
{ color: #009a00; }
TABLE.messageList TR[labels~="label3"]._selected TD
{ color: #fff;
background-color: #009a00; }
TABLE.messageList TR[labels~="label2"] TD
{ color: #ff9a00; }
TABLE.messageList TR[labels~="label2"]._selected TD
{ color: #fff;
background-color: #ff9a00; }
TABLE.messageList TR[labels~="label1"] TD
{ color: #f00; }
TABLE.messageList TR[labels~="label1"]._selected TD
{ color: #fff;
background-color: #f00; }
TABLE.messageList TR.mailer_unreadmail TD
{ font-weight: bold !important; }
TR.mailer_unreadmail TD.messageSubjectColumn
{
background-image: url(icon-new.png);
{ background-image: url(icon-new.png);
background-repeat: no-repeat !important;
background-position: 0px 0px !important;
padding-left: 20px !important;
font-weight: bold !important;
}
background-position: 0px 3px !important;
font-weight: bold !important; }
TD.mailer_repliedmailsubject
{
background-image: url(icon-replied.png) !important;
TR.mailer_repliedmailsubject TD.messageSubjectColumn
{ background-image: url(icon-replied.png) !important;
background-repeat: no-repeat !important;
background-position: 0px 0px !important;
padding-left: 20px !important;
}
background-position: 0px 0px !important; }
TD.mailer_forwardedmailsubject
{
background-image: url(icon-forwarded.png) !important;
{ background-image: url(icon-forwarded.png) !important;
background-repeat: no-repeat !important;
background-position: 0px 0px !important;
padding-left: 20px !important;
}
background-position: 0px 0px !important; }
TD.mailer_forwardedrepliedmailsubject
{
background-image: url(icon-forwarded-replied.png) !important;
background-repeat: no-repeat !important;
background-position: 0px 0px !important;
padding-left: 20px !important;
}
background-position: 0px 0px !important; }
TD.mailer_deletedmailsubject
{
background-image: url(icon-deleted.png) !important;
background-repeat: no-repeat !important;
background-position: 0px 0px !important;
padding-left: 20px !important;
}
background-position: 0px 0px !important; }
TD.mailer_readmailsubject a
TD.mailer_readmailsubject A
{
color: black;
text-decoration: none;
}
TD.mailer_unreadmailsubject a
TD.mailer_unreadmailsubject A
{
color: black;
text-decoration: none;
}
tr.mailer_listcell_deleted td
TR.mailer_listcell_deleted TD
{
text-decoration: line-through;
}
tr.mailer_listcell_regular td a
TR.mailer_listcell_regular TD A
{
color: black;
text-decoration: none;
}
/* fields (key/value UI), eg used in mail viewer */
/* mail viewer */
INPUT#editDraftButton
{
position: absolute;
@@ -547,99 +628,6 @@ TABLE#addr_table
width: 100%;
}
TABLE#messageList
{ width: 100%;
-moz-user-select: none;
-khtml-user-select: none; }
TABLE#messageList TD,
TABLE#messageList TH
{ height: 20px;
overflow: hidden;
white-space: nowrap; }
TABLE#messageList TH
{ white-space: pre; }
TABLE#messageList TBODY TD
{ white-space: pre; }
TD#messageFlagHeader,
TABLE#messageList TD.messageFlagColumn
{ width: 22px;
text-align: center; }
TABLE#messageList TD.messageFlagColumn IMG
{ width: 14px;
height: 14px; }
TD#subjectHeader,
TABLE#messageList TD.mailer_readmailsubject
{ /*width: 40%;*/
min-width: 35%; }
TD#fromHeader,
TABLE#messageList TD.messageAddressColumn
{ /*width: 35%;*/
min-width: 30%;
overflow: hidden; }
TD#dateHeader
{ /*width: 25%;*/
overflow: hidden; }
TD#priorityHeader,
TD#sizeHeader
{ /*width: 25%;*/
width: 7%;
overflow: hidden; }
TABLE#messageList TR._selected TD
{
background-color: #9ABCD8;
color: #fff;
}
TABLE#messageList TR._deleted TD
{
text-decoration: line-through;
}
TABLE#messageList TR[labels~="label5"] TD
{ color: #9c309c; }
TABLE#messageList TR[labels~="label5"]._selected TD
{ color: #fff;
background-color: #9c309c; }
TABLE#messageList TR[labels~="label4"] TD
{ color: #3130ff; }
TABLE#messageList TR[labels~="label4"]._selected TD
{ color: #fff;
background-color: #3130ff; }
TABLE#messageList TR[labels~="label3"] TD
{ color: #009a00; }
TABLE#messageList TR[labels~="label3"]._selected TD
{ color: #fff;
background-color: #009a00; }
TABLE#messageList TR[labels~="label2"] TD
{ color: #ff9a00; }
TABLE#messageList TR[labels~="label2"]._selected TD
{ color: #fff;
background-color: #ff9a00; }
TABLE#messageList TR[labels~="label1"] TD
{ color: #f00; }
TABLE#messageList TR[labels~="label1"]._selected TD
{ color: #fff;
background-color: #f00; }
/* quota indicator */
DIV.quota
{ border-bottom: 1px solid #ccc;
+230 -240
View File
@@ -10,14 +10,6 @@ if (typeof textMailAccounts != 'undefined') {
else
mailAccounts = new Array();
}
var defaultColumnsOrder;
if (typeof textDefaultColumnsOrder != 'undefined') {
if (textDefaultColumnsOrder.length > 0)
defaultColumnsOrder = textDefaultColumnsOrder.evalJSON(true);
else
defaultColumnsOrder = new Array();
}
var Mailer = {
currentMailbox: null,
@@ -27,7 +19,9 @@ var Mailer = {
cachedMessages: new Array(),
foldersStateTimer: false,
popups: new Array(),
quotas: null
quotas: null,
dataTable: null
};
var usersRightsWindowHeight = 320;
@@ -113,8 +107,8 @@ function flagMailInWindow (win, msguid, flagged) {
var row = win.$("row_" + msguid);
if (row) {
var col = row.select("TD.messageFlag").first();
var img = col.select("img").first();
var col = row.down("TD.messageFlag");
var img = col.down("img");
if (flagged) {
img.setAttribute("src", ResourcesURL + "/flag.png");
img.addClassName("messageIsFlagged");
@@ -128,36 +122,34 @@ function flagMailInWindow (win, msguid, flagged) {
function markMailInWindow(win, msguid, markread) {
var row = win.$("row_" + msguid);
var subjectCell = win.$("div_" + msguid);
var unseenCount = 0;
if (row && subjectCell) {
if (row) {
if (markread) {
if (row.hasClassName("mailer_unreadmail")) {
row.removeClassName("mailer_unreadmail");
subjectCell.addClassName("mailer_readmailsubject");
var img = win.$("unreaddiv_" + msguid);
var img = win.$("readdiv_" + msguid);
if (img) {
img.removeClassName("mailerUnreadIcon");
img.addClassName("mailerReadIcon");
img.setAttribute("id", "readdiv_" + msguid);
img.setAttribute("src", ResourcesURL + "/dot.png");
var title = img.getAttribute("title-markunread");
if (title)
img.setAttribute("title", title);
}
else {
log ("No IMG found for " + msguid);
}
unseenCount = -1;
}
}
else {
if (!row.hasClassName("mailer_unreadmail")) {
row.addClassName("mailer_unreadmail");
subjectCell.removeClassName('mailer_readmailsubject');
var img = win.$("readdiv_" + msguid);
if (img) {
img.removeClassName("mailerReadIcon");
img.addClassName("mailerUnreadIcon");
img.setAttribute("id", "unreaddiv_" + msguid);
img.setAttribute("src", ResourcesURL + "/icon_unread.gif");
var title = img.getAttribute("title-markread");
if (title)
@@ -169,7 +161,7 @@ function markMailInWindow(win, msguid, markread) {
}
if (unseenCount != 0) {
/* Update unseen count only if it's the inbox */
// Update unseen count only if it's the inbox
for (var i = 0; i < mailboxTree.aNodes.length; i++)
if (mailboxTree.aNodes[i].datatype == "inbox") break;
if (i != mailboxTree.aNodes.length && Mailer.currentMailbox == mailboxTree.aNodes[i].dataname)
@@ -194,7 +186,7 @@ function openMessageWindowsForSelection(action, firstOnly) {
window.location.href = parts.join("/");
}
else {
var messageList = $("messageList");
var messageList = $("messageListBody");
var rows = messageList.getSelectedRowsId();
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
@@ -236,7 +228,9 @@ function mailListMarkMessage(event) {
}
function mailListMarkMessageCallback(http) {
if (isHttpStatus204(http.status)) {
if (isHttpStatus204(http.status)
|| http.status == 304) { // In some cases, Safari returns a 304 even
// though SOGo returns a 204!
var data = http.callbackData;
markMailInWindow(data["window"], data["msguid"], data["markread"]);
}
@@ -264,6 +258,7 @@ function mailListFlagMessageToggle (e) {
triggerAjaxRequest(url, mailListFlagMessageToggleCallback, data);
}
function mailListFlagMessageToggleCallback (http) {
if (isHttpStatus204(http.status)) {
var data = http.callbackData;
@@ -326,7 +321,7 @@ function onDocumentKeydown(event) {
nextRow = row.next("tr");
else
nextRow = row.previous("tr");
if (nextRow) {
if (nextRow && nextRow.id != 'rowTop' && nextRow.id != 'rowBottom') {
Mailer.currentMessages[Mailer.currentMailbox] = nextRow.getAttribute("id").substr(4);
row.up().deselectAll();
@@ -355,10 +350,11 @@ function onDocumentKeydown(event) {
/* bulk delete of messages */
function deleteSelectedMessages(sender) {
var messageList = $("messageList").down("TBODY");
var messageList = $("messageListBody").down("TBODY");
var rows = messageList.getSelectedNodes();
var uids = new Array(); // message IDs
var paths = new Array(); // row IDs
var unseenCount = 0;
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
@@ -368,6 +364,17 @@ function deleteSelectedMessages(sender) {
rows[i].hide();
uids.push(uid);
paths.push(path);
if (rows[i].hasClassName("mailer_unreadmail"))
unseenCount--;
}
messageList.deselectAll();
updateMessageListCounter(0 - rows.length, true);
if (unseenCount < 0) {
// Update unseen count only if it's the inbox
for (var i = 0; i < mailboxTree.aNodes.length; i++)
if (mailboxTree.aNodes[i].datatype == "inbox") break;
if (i != mailboxTree.aNodes.length && Mailer.currentMailbox == mailboxTree.aNodes[i].dataname)
updateStatusFolders(unseenCount, true);
}
var url = ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/batchDelete";
var parameters = "uid=" + uids.join(",");
@@ -391,31 +398,35 @@ function deleteSelectedMessagesCallback(http) {
var div = $('messageContent');
if (Mailer.currentMessages[Mailer.currentMailbox] == data["id"][i]) {
div.update();
Mailer.currentMessages[Mailer.currentMailbox] = null;
Mailer.currentMessages[Mailer.currentMailbox] = null;
}
var row = $("row_" + data["id"][i]);
if (deleteMessageRequestCount == 0) {
var nextRow = row.next("tr");
if (!nextRow)
nextRow = row.previous("tr");
// row.addClassName("deleted"); // when we'll offer "mark as deleted"
if (nextRow) {
Mailer.currentMessages[Mailer.currentMailbox] = nextRow.getAttribute("id").substr(4);
nextRow.selectElement();
loadMessage(Mailer.currentMessages[Mailer.currentMailbox]);
var row = $("row_" + data["id"][i]);
if (row) {
var nextRow = row.next("tr");
if (!nextRow.id.startsWith('row_'))
nextRow = row.previous("tr");
// row.addClassName("deleted"); // when we'll offer "mark as deleted"
if (nextRow.id.startsWith('row_')) {
Mailer.currentMessages[Mailer.currentMailbox] = nextRow.getAttribute("id").substr(4);
nextRow.selectElement();
loadMessage(Mailer.currentMessages[Mailer.currentMailbox]);
}
}
else {
div.update();
}
refreshCurrentFolder();
Mailer.dataTable.remove(data["id"][i]);
Mailer.dataTable.render();
}
else {
Mailer.dataTable.remove(data["id"][i]);
}
row.parentNode.removeChild(row);
}
}
}
else {
log ("deleteSelectedMessagesCallback: problem during ajax request " + http.status);
window.alert(getLabel("Operation failed"));
refreshCurrentFolder();
}
}
@@ -485,15 +496,33 @@ function onMailboxTreeItemClick(event) {
if (head.rows[1])
head.rows[1].firstChild.update();
}
else
else {
var datatype = this.parentNode.getAttribute("datatype");
if (datatype == 'draft' || datatype == 'sent')
toggleAddressColumn("from", "to");
else
toggleAddressColumn("to", "from");
openMailbox(mailbox);
}
Event.stop(event);
}
function toggleAddressColumn(search, replace) {
var header = $(search + "Header");
if (header) {
header.id = replace + "Header";
header.update(getLabel(replace.capitalize()));
var i = UserDefaults["SOGoMailListViewColumnsOrder"].indexOf(search.capitalize());
if (i >= 0)
UserDefaults["SOGoMailListViewColumnsOrder"][i] = replace.capitalize();
}
}
function onMailboxMenuMove(event) {
var targetMailbox;
var messageList = $("messageList").down("TBODY");
var messageList = $("messageListBody").down("TBODY");
var rows = messageList.getSelectedNodes();
var uids = new Array(); // message IDs
var paths = new Array(); // row IDs
@@ -534,7 +563,7 @@ function onMailboxMenuMove(event) {
}
function onMailboxMenuCopy(event) {
var messageList = $("messageList").down("TBODY");
var messageList = $("messageListBody").down("TBODY");
var rows = messageList.getSelectedNodes();
var uids = new Array(); // message IDs
var paths = new Array(); // row IDs
@@ -590,138 +619,34 @@ function composeNewMessage() {
}
}
function openMailbox(mailbox, reload, idx, updateStatus) {
function openMailbox(mailbox, reload, updateStatus) {
if (mailbox != Mailer.currentMailbox || reload) {
Mailer.currentMailbox = mailbox;
var url = ApplicationBaseURL + encodeURI(mailbox) + "/view?noframe=1";
if (!reload || idx) {
var url = ApplicationBaseURL + encodeURI(mailbox);
var urlParams = new Hash();
if (!reload) {
var messageContent = $("messageContent");
messageContent.update();
$("messageCountHeader").down().update();
lastClickedRow = -1; // from generic.js
}
var currentMessage;
if (!idx) {
currentMessage = Mailer.currentMessages[mailbox];
if (currentMessage) {
url += '&pageforuid=' + currentMessage;
if (!reload)
loadMessage(currentMessage);
}
}
var searchValue = search["value"];
if (searchValue && searchValue.length > 0)
url += ("&search=" + search["criteria"]
+ "&value=" + escape(searchValue.utf8encode()));
if (searchValue && searchValue.length > 0) {
urlParams.set("search", search["criteria"]);
urlParams.set("value", escape(searchValue.utf8encode()));
}
var sortAttribute = sorting["attribute"];
if (sortAttribute && sortAttribute.length > 0)
url += ("&sort=" + sorting["attribute"]
+ "&asc=" + sorting["ascending"]);
if (idx)
url += "&idx=" + idx;
if (document.messageListAjaxRequest) {
document.messageListAjaxRequest.aborted = true;
document.messageListAjaxRequest.abort();
}
var mailboxContent = $("mailboxContent");
if (mailboxContent.getStyle('visibility') == "hidden") {
mailboxContent.setStyle({ visibility: "visible" });
var rightDragHandle = $("rightDragHandle");
rightDragHandle.setStyle({ visibility: "visible" });
messageContent.setStyle({ top: (rightDragHandle.offsetTop
+ rightDragHandle.offsetHeight
+ 'px') });
}
document.messageListAjaxRequest
= triggerAjaxRequest(url, messageListCallback,
currentMessage);
if (updateStatus != false)
getStatusFolders();
}
}
function openMailboxAtIndex(event) {
openMailbox(Mailer.currentMailbox, true, this.getAttribute("idx"));
Event.stop(event);
}
function messageListCallback(http) {
var div = $('mailboxContent');
var table = $('messageList');
var columnsOrder = UserDefaults["SOGoMailListViewColumnsOrder"];
if ( typeof(columnsOrder) == "undefined" ) {
columnsOrder = defaultColumnsOrder;
}
var addrIndex = 3;
for(var i=0; i<columnsOrder.length; i++) {
if (columnsOrder[i] == "From" || columnsOrder[i] == "To") {
addrIndex = i;
}
}
if (http.status == 200) {
document.messageListAjaxRequest = null;
if (table) {
// Update table
var thead = table.tHead;
var addressHeaderCell = thead.rows[0].cells[addrIndex];
var tbody = table.tBodies[0];
var tmp = document.createElement('div');
$(tmp).update(http.responseText);
var newRows = tmp.firstChild.tHead.rows;
thead.rows[1].parentNode.replaceChild(newRows[1], thead.rows[1]);
addressHeaderCell.replaceChild(newRows[0].cells[addrIndex].lastChild,
addressHeaderCell.lastChild);
addressHeaderCell.setAttribute("id", newRows[0].cells[addrIndex].getAttribute("id"));
table.replaceChild(tmp.firstChild.tBodies[0], tbody);
configureMessageListEvents(table);
}
else {
// Add table
div.update(http.responseText);
table = $("messageList");
configureMessageListEvents(table);
TableKit.Resizable.init(table, {'trueResize' : true, 'keepWidth' : true});
configureDraggables();
}
configureMessageListBodyEvents(table);
var selected = http.callbackData;
if (selected) {
var row = $("row_" + selected);
if (row) {
row.selectElement();
lastClickedRow = row.rowIndex - $(row).up('table').down('thead').getElementsByTagName('tr').length;
var rowPosition = row.rowIndex * row.getHeight();
if (rowPosition < div.scrollTop
|| rowPosition > div.scrollTop + div.getHeight ())
div.scrollTop = rowPosition; // scroll to selected message
}
else
$("messageContent").update();
}
else
div.scrollTop = 0;
if (sorting["attribute"] && sorting["attribute"].length > 0) {
if (sortAttribute && sortAttribute.length > 0) {
urlParams.set("sort", sorting["attribute"]);
urlParams.set("asc", sorting["ascending"]);
var sortHeader = $(sorting["attribute"] + "Header");
if (sortHeader) {
var sortImages = $(table.tHead).select(".sortImage");
var sortImages = sortHeader.up('THEAD').select(".sortImage");
$(sortImages).each(function(item) {
item.remove();
});
var sortImage = createElement("img", "messageSortImage", "sortImage");
sortHeader.insertBefore(sortImage, sortHeader.firstChild);
if (sorting["ascending"])
@@ -730,13 +655,73 @@ function messageListCallback(http) {
sortImage.src = ResourcesURL + "/arrow-up.png";
}
}
// TODO : refresh mailbox without removing all rows.
var messageList = $("messageListBody").down('TBODY');
var dataSource = new SOGoMailDataSource(Mailer.dataTable, url);
Mailer.dataTable.setSource('SOGoMailDataSource', url, urlParams);
messageList.deselectAll();
Mailer.dataTable.render();
configureDraggables();
Mailer.currentMailbox = mailbox;
/*
// TODO : restore previously selected message.
var currentMessage = Mailer.currentMessages[mailbox];
if (currentMessage) {
Mailer.dataTable.render(currentMessage);
if (!reload)
loadMessage(currentMessage);
}
*/
if (updateStatus != false)
getStatusFolders();
}
else {
var data = http.responseText;
var msg = data.replace(/^(.*\n)*.*<p>((.*\n)*.*)<\/p>(.*\n)*.*$/, "$2");
log("messageListCallback: problem during ajax request (readyState = " + http.readyState + ", status = " + http.status + ", response = " + msg + ")");
}
/*
* Called from SOGoDataTable.render()
*/
function messageListCallback(row, data, isNew) {
if (isNew) {
row.observe("mousedown", onRowClick);
row.observe("selectstart", listRowMouseDownHandler);
row.observe("contextmenu", onMessageContextMenu);
}
row.className = data['rowClasses'];
row.id = data['rowID'];
row.writeAttribute('labels', (data['labels']?data['labels']:""));
var columnsOrder = UserDefaults["SOGoMailListViewColumnsOrder"];
// if (typeof columnsOrder == "undefined") {
// columnsOrder = defaultColumnsOrder;
// }
var cells;
if (Prototype.Browser.IE)
cells = row.childNodes;
else
cells = row.cells;
for (var j = 0; j < cells.length; j++) {
var cell = $(cells[j]);
var cellType = columnsOrder[j];
if (data[cellType]) cell.update(data[cellType]);
cell.observe("mousedown", listRowMouseDownHandler);
if (cellType == "Subject" || cellType == "From" || cellType == "To" || cellType == "Date")
cell.observe("dblclick", onMessageDoubleClick.bindAsEventListener(cell));
else if (cellType == "Unread") {
var img = cell.down('img');
if (img)
img.observe("click", mailListMarkMessage.bindAsEventListener(img));
}
else if (cellType == 'Flagged' && isNew)
cell.observe("click", mailListFlagMessageToggle.bindAsEventListener(cell));
}
initFlagIcons ();
}
function getStatusFolders() {
@@ -784,9 +769,28 @@ function updateStatusFolders(count, isDelta) {
}
}
function updateMessageListCounter(count, isDelta) {
var cell = $("messageCountHeader").down();
if (isDelta) {
var value = parseInt(cell.innerHTML);
count += value;
}
if (count > 0)
cell.update(count + " " + getLabel("messages"));
else
cell.update(getLabel("No message"));
}
function onMessageListRender(event) {
// Event is fired from SOGoDataTable.
updateMessageListCounter(event.memo, false);
}
function onMessageContextMenu(event) {
var menu = $('messageListMenu');
var topNode = $('messageList');
var topNode = $('messageListBody');
var selectedNodes = topNode.getSelectedRows();
menu.observe("hideMenu", onMessageContextMenuHide);
@@ -1409,7 +1413,7 @@ function onMenuForwardMessage(event) {
}
function onMenuViewMessageSource(event) {
var messageList = $("messageList");
var messageList = $("messageListBody");
var rows = messageList.getSelectedRowsId();
if (rows.length > 0) {
@@ -1482,9 +1486,9 @@ function expandUpperTree(node) {
}
function onHeaderClick(event) {
if (TableKit.Resizable._onHandle)
if (SOGoResizableTable._onHandle)
return;
var headerId = this.getAttribute("id");
var newSortAttribute;
if (headerId == "subjectHeader")
@@ -1515,63 +1519,16 @@ function refreshCurrentFolder() {
openMailbox(Mailer.currentMailbox, true);
}
/* a model for a futur refactoring of the sortable table headers mechanism */
function configureMessageListEvents(table) {
if (table) {
table.multiselect = true;
// Each body row can load a message
table.observe("mousedown", onMessageSelectionChange);
function configureMessageListEvents(headerTable, dataTable) {
if (headerTable)
// Sortable columns
configureSortableTableHeaders(table);
}
}
function configureMessageListBodyEvents(table) {
if (table) {
// Page navigation
var cell = table.tHead.rows[1].cells[0];
if ($(cell).hasClassName("tbtv_navcell")) {
var anchors = $(cell).childNodesWithTag("a");
for (var i = 0; i < anchors.length; i++)
$(anchors[i]).observe("click", openMailboxAtIndex);
}
rows = table.tBodies[0].rows;
for (var i = 0; i < rows.length; i++) {
var row = $(rows[i]);
row.observe("mousedown", onRowClick);
row.observe("selectstart", listRowMouseDownHandler);
row.observe("contextmenu", onMessageContextMenu);
//row.dndTypes = function() { return new Array("mailRow"); };
//row.dndGhost = messageListGhost;
//row.dndDataForType = messageListData;
//document.DNDManager.registerSource(row);
// Correspondances index <> nom de la colonne
// 0 => Invisible
// 1 => Attachment
// 2 => Subject
// 3 => From
// 4 => Unread
// 5 => Date
// 6 => Priority
var columnsOrder = UserDefaults["SOGoMailListViewColumnsOrder"];
if ( typeof(columnsOrder) == "undefined" ) {
columnsOrder = defaultColumnsOrder;
}
for (var j = 0; j < row.cells.length; j++) {
var cell = $(row.cells[j]);
var cellType = columnsOrder[j];
cell.observe("mousedown", listRowMouseDownHandler);
if (cellType == "Subject" || cellType == "From" || cellType == "To" || cellType == "Date")
cell.observe("dblclick", onMessageDoubleClick.bindAsEventListener(cell));
else if (cellType == "Unread") {
var img = $(cell.childNodesWithTag("img")[0]);
if (img)
img.observe("click", mailListMarkMessage.bindAsEventListener(img));
}
}
}
configureSortableTableHeaders(headerTable);
if (dataTable) {
dataTable.multiselect = true;
// Each body row can load a message
dataTable.observe("mousedown",
onMessageSelectionChange.bindAsEventListener(dataTable));
}
}
@@ -1589,9 +1546,15 @@ function configureDragHandles() {
handle.addInterface(SOGoDragHandlesInterface);
handle.upperBlock=$("mailboxContent");
handle.lowerBlock=$("messageContent");
handle.upperBlock.observe("handle:resize", onMessageListResize);
}
}
function onMessageListResize(event) {
var h = $("mailboxContent").getHeight() - $("messageListHeader").getHeight();
$("mailboxList").setStyle({'height': h + 'px'});
}
function onWindowResize(event) {
var handle = $("verticalDragHandle");
if (handle)
@@ -1641,15 +1604,36 @@ function openInbox(node) {
mailboxTree.o(1);
}
function initFlagIcons () {
var icons = $$("TABLE#messageList TBODY TR.mailer_listcell_regular TD.messageFlag");
for (var i = 0; i < icons.length; i++)
icons[i].onclick = mailListFlagMessageToggle;
}
function initMailer(event) {
// Default sort options
sorting["attribute"] = "date";
sorting["ascending"] = false;
// Define columns order
if (typeof UserDefaults["SOGoMailListViewColumnsOrder"] == "undefined") {
var defaultColumnsOrder;
if (typeof textDefaultColumnsOrder != 'undefined') {
if (textDefaultColumnsOrder.length > 0)
defaultColumnsOrder = textDefaultColumnsOrder.evalJSON(true);
else
defaultColumnsOrder = new Array();
}
UserDefaults["SOGoMailListViewColumnsOrder"] = defaultColumnsOrder;
}
if (!$(document.body).hasClassName("popup")) {
//initDnd();
Mailer.dataTable = $("mailboxList");
Mailer.dataTable.addInterface(SOGoDataTableInterface);
Mailer.dataTable.setRowRenderCallback(messageListCallback);
Mailer.dataTable.observe("datatable:rendered", onMessageListRender);
var messageListHeader = $("messageListHeader");
messageListHeader.addInterface(SOGoResizableTableInterface);
configureMessageListEvents($("messageListHeader"), $("messageListBody"));
initMailboxTree();
initMessageCheckTimer();
@@ -1668,12 +1652,9 @@ function initMailer(event) {
Event.observe(window, "beforeunload", onUnload);
}
onMessageListResize();
onWindowResize.defer();
Event.observe(window, "resize", onWindowResize);
// Default sort options
sorting["attribute"] = "date";
sorting["ascending"] = false;
}
function initMessageCheckTimer() {
@@ -2188,8 +2169,17 @@ function folderRefreshCallback(http) {
&& isHttpStatus204(http.status)) {
var oldMailbox = http.callbackData.mailbox;
if (http.callbackData.refresh
&& oldMailbox == Mailer.currentMailbox)
refreshCurrentFolder();
&& oldMailbox == Mailer.currentMailbox) {
if (http.callbackData.id) {
var s = http.callbackData.id + "";
var uids = s.split(",");
for (var i = 0; i < uids.length; i++)
Mailer.dataTable.remove(uids[i]);
Mailer.dataTable.render();
}
else
refreshCurrentFolder();
}
}
else {
if (http.callbackData.id) {
@@ -2237,7 +2227,7 @@ function messageFlagCallback(http) {
}
function onLabelMenuPrepareVisibility() {
var messageList = $("messageList");
var messageList = $("messageListBody");
var flags = {};
if (messageList) {
@@ -2266,7 +2256,7 @@ function onLabelMenuPrepareVisibility() {
}
function saveAs(event) {
var messageList = $("messageList").down("TBODY");
var messageList = $("messageListBody").down("TBODY");
var rows = messageList.getSelectedNodes();
var uids = new Array(); // message IDs
var paths = new Array(); // row IDs
@@ -2434,7 +2424,7 @@ function configureDraggables () {
mainElement.hide();
new Draggable ("dragDropVisual",
{ handle: "messageList",
{ handle: "messageListBody",
onStart: startDragging,
onEnd: stopDragging,
onDrag: whileDragging,
@@ -2455,11 +2445,11 @@ function configureDroppables () {
function startDragging (itm, e) {
var target = Event.element(e);
if (target.up().up().tagName != "TBODY")
if (target.up('TBODY') == undefined)
return false;
var handle = $("dragDropVisual");
var count = $('messageList').getSelectedRowsId().length;
var count = $("messageListBody").getSelectedRowsId().length;
handle.update (count);
if (e.shiftKey)
+260
View File
@@ -0,0 +1,260 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Data table interface to be added to a DIV (this!)
*
* Available events:
* datatable:rendered -- fired once the view rendering is completed
*
*/
var SOGoDataTableInterface = {
// Object variables initialized with "bind"
columnsCount: null,
rowModel: null,
rowHeight: 0,
body: null,
// Object variables
dataSource: null,
rowTop: null,
rowBottom: null,
renderedIndex: -1,
renderedCount: 0,
rowRenderCallback: null,
// Constants
overflow: 30, // must be lower than the overflow of the data source class
renderDelay: 0.2, // delay (in seconds) before which the table is rendered upon scrolling
bind: function() {
this.observe("scroll" , this.render.bind(this));
this.body = this.down("tbody");
this.rowModel = this.body.down("tr");
// Since we use the fixed table layout, the first row must have the
// proper CSS classes that will define the columns width.
this.rowTop = new Element('tr', {'id': 'rowTop'});//.update(new Element('td'));
this.body.insertBefore(this.rowTop, this.rowModel); // IE requires the element to be inside the DOM before appending new children
var cells = this.rowModel.select('TD');
for (var i = 0; i < cells.length; i++) {
var cell = cells[i];
var td = new Element('td', {'class': cell.className});
this.rowTop.appendChild(td);
}
this.rowBottom = new Element('tr', {'id': 'rowBottom'}).update(new Element('td'));
this.body.insertBefore(this.rowBottom, this.rowModel);
this.columnsCount = this.rowModel.select("td").length;
this.rowHeight = this.rowModel.getHeight();
},
setRowRenderCallback: function(callbackFunction) {
// Each time a row is created or updated with new data, this callback
// function will be called.
this.rowRenderCallback = callbackFunction;
},
setSource: function(dataSourceClass, url, params) {
// log ("DataTable.setSource() " + url);
if (this.dataSource) this.dataSource.destroy();
this._emptyTable();
this.dataSource = new window[dataSourceClass](this, url);
this.load(params);
},
load: function(urlParams) {
if (!this.dataSource) return;
// log ("DataTable.load() with parameters [" + urlParams.keys().join(' ') + "]");
if (Object.isHash(urlParams) && urlParams.keys().length > 0) this.dataSource.load(urlParams);
else this.dataSource.load(new Hash());
},
visibleRowCount: function() {
var divHeight = this.getHeight();
var visibleRowCount = Math.ceil(divHeight/this.rowHeight);
return visibleRowCount;
},
firstVisibleRowIndex: function() {
var firstRowIndex = Math.floor(this.scrollTop/this.rowHeight);
return firstRowIndex;
},
render: function(uid) {
var index = this.firstVisibleRowIndex();
var count = this.visibleRowCount();
// Overflow the query to the maximum defined in the class variable overflow
var start = index - (this.overflow/2);
if (start < 0) start = 0;
var end = index + count + this.overflow - (index - start);
// log ("DataTable.getData() from " + index + " to " + (index + count) + " boosted from " + start + " to " + end);
// Don't overflow above the maximum number of entries from the data source
if (this.dataSource.uids && this.dataSource.uids.length < end) end = this.dataSource.uids.length;
index = start;
count = end - start;
this.currentRenderID = index + "-" + count;
// Query the data source only if at least one row is not loaded
if (this.renderedIndex < 0 ||
this.renderedIndex > index ||
this.renderedCount < count ||
(index + count) > (this.renderedIndex + this.renderedCount)) {
this.dataSource.getData(this.currentRenderID,
index,
count,
this._render.bind(this),
this.renderDelay);
}
},
_render: function(renderID, start, max, data) {
if (this.currentRenderID != renderID) {
// log ("DataTable._render() ignore render for " + renderID + " (current is " + this.currentRenderID + ")");
return;
}
// log("DataTable._render() for " + data.length + " uids (from " + start + ")");
var h, i, j;
var rows = this.body.select("tr");
var scroll;
scroll = this.scrollTop;
lastClickedRow = -1; // defined in generic.js
h = start * this.rowHeight;
if (Prototype.Browser.IE)
this.rowTop.setStyle({ 'height': h + 'px', 'line-height': h + 'px' });
this.rowTop.firstChild.setStyle({ 'height': h + 'px', 'line-height': h + 'px' });
h = (max - start - data.length) * this.rowHeight;
if (Prototype.Browser.IE)
this.rowBottom.setStyle({ 'height': h + 'px', 'line-height': h + 'px' });
this.rowBottom.firstChild.setStyle({ 'height': h + 'px', 'line-height': h + 'px' });
if (this.renderedIndex < 0) {
this.renderedIndex = 0;
this.renderedCount = 0;
}
if (start > (this.renderedIndex + this.renderedCount) ||
start + data.length < this.renderedIndex) {
// No reusable row in the viewport;
// refresh the complete view port
for (i = 0, j = start;
i < this.renderedCount && i < data.length;
i++, j++) {
// Render all existing rows with new data
var row = rows[i+1]; // must skip the first row (this.rowTop)
row.removeClassName('_selected');
this.rowRenderCallback(row, data[i], false);
}
for (i = this.renderedCount;
i < data.length;
i++, j++) {
// Add new rows, if necessary
var row = this.rowModel.cloneNode(true);
this.rowRenderCallback(row, data[i], true);
row.show();
this.body.insertBefore(row, this.rowBottom);
}
for (i = this.renderedCount;
i > data.length;
i--) {
// Delete extra rows, if necessary
this.body.removeChild(rows[i]);
}
}
else if (start >= this.renderedIndex) {
// Scrolling down
// Delete top rows
for (i = start; i > this.renderedIndex; i--) {
this.body.removeChild(rows[i - this.renderedIndex]);
}
// Add bottom rows
for (j = this.renderedIndex + this.renderedCount - start, i = this.renderedIndex + this.renderedCount;
j < data.length;
j++, i++) {
var row = this.rowModel.cloneNode(true);
this.rowRenderCallback(row, data[j], true);
row.show();
this.body.insertBefore(row, this.rowBottom);
}
}
else {
// Scrolling up
// Delete bottom rows
for (i = this.renderedIndex + this.renderedCount, j = this.renderedCount;
i > (start + data.length);
i--, j--) {
this.body.removeChild(rows[j]);
}
// Add top rows
for (i = 0, j = start;
j < this.renderedIndex;
i++, j++) {
var row = this.rowModel.cloneNode(true);
this.rowRenderCallback(row, data[i], true);
row.show();
this.body.insertBefore(row, rows[1]);
}
}
this.body.refreshSelectionByIds();
// log ("DataTable._render() top gap/bottom gap/total rows = " + this.rowTop.getStyle('height') + "/" + this.rowBottom.getStyle('height') + "/" + this.body.select("tr").length + " (height = " + this.down("table").getHeight() + "px)");
// Save current rendered view index and count
this.renderedIndex = start;
this.renderedCount = data.length;
// Restore scroll position (necessary in certain cases)
this.scrollTop = scroll;
Event.fire(this, "datatable:rendered", max);
},
remove: function(uid) {
var rows = this.body.select("TR#row_" + uid);
if (rows.length == 1) {
var row = rows.first();
row.parentNode.removeChild(row);
var index = this.dataSource.invalidate(uid);
// log ("DataTable.remove(" + uid + ")");
if (this.renderedIndex < index &&
(this.renderedIndex + this.renderedCount) > index) {
this.renderedCount--;
}
}
},
_emptyTable: function() {
var rows = this.body.select("tr");
var currentCount = rows.length;
for (var i = currentCount - 1; i >= 0; i--) {
if (rows[i] != this.rowModel &&
rows[i] != this.rowTop &&
rows[i] != this.rowBottom)
this.body.removeChild(rows[i]);
}
this.renderedIndex = -1;
this.renderedCount = 0;
this.rowTop.firstChild.setStyle({ 'height': '0px', 'line-height': '0px' });
this.rowBottom.firstChild.setStyle({ 'height': '0px', 'line-height': '0px' });
}
};
+3 -2
View File
@@ -120,10 +120,11 @@ var SOGoDragHandlesInterface = {
deltaY = Math.floor(pointerY - this.origY - (this.offsetHeight / 2));
this.lowerBlock.setStyle({ top: (this.origLower + deltaY - this.delta) + 'px' });
this.upperBlock.setStyle({ height: (this.origUpper + deltaY - this.delta) + 'px' });
//this.lowerBlock.fire("handle:resize");
this.upperBlock.fire("handle:resize");
this.saveDragHandleState(this.dhType, parseInt(this.lowerBlock.getStyle("top")));
}
if (Prototype.Browser.IE)
if (Prototype.Browser.IE)
Event.stopObserving(document.body, "mouseup", this.stopHandleDraggingBound);
else
Event.stopObserving(window, "mouseup", this.stopHandleDraggingBound);
+167
View File
@@ -0,0 +1,167 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
SOGoMailDataSource = Class.create({
initialize: function(dataTable, url) {
// Instance variables
this.dataTable = dataTable;
this.url = url;
this.uids = new Array();
this.cache = new Hash();
this.loaded = false;
this.delayedGetData = false;
this.ajaxGetData = false;
// Constants
this.overflow = 60;
},
destroy: function() {
this.uids.clear();
var keys = this.cache.keys();
for (var i = 0; i < keys.length; i++)
this.cache.unset(keys[i]);
},
invalidate: function(uid) {
this.cache.unset(uid);
var index = this.uids.indexOf(parseInt(uid));
log ("MailDataSource.invalidate(" + uid + ") at index " + index);
if (index >= 0) {
this.uids.splice(index, 1);
}
return index;
},
load: function(urlParams) {
var params;
this.loaded = false;
if (urlParams.keys().length > 0) {
params = urlParams.keys().collect(function(key) { return key + "=" + urlParams.get(key); }).join("&");
}
else
params = "";
// log ("MailDataSource.load() " + params);
triggerAjaxRequest(this.url + "/uids",
this._loadCallback.bind(this),
null,
params,
{ "Content-type": "application/x-www-form-urlencoded" });
},
_loadCallback: function(http) {
if (http.status == 200) {
if (http.responseText.length > 0) {
this.uids = $A(http.responseText.evalJSON(true));
log ("MailDataSource._loadCallback() " + this.uids.length + " uids");
this.loaded = true;
}
}
else {
alert("SOGoMailDataSource._loadCallback Error " + http.status + ": " + http.responseText);
}
},
getData: function(id, index, count, callbackFunction, delay) {
if (this.loaded == false) {
// UIDs are not yet loaded -- delay the call to the current function
// log ("MailDataSource.getData() delaying data fetching while waiting for UIDs");
if (this.delayedGetData) window.clearTimeout(this.delayedGetData);
this.delayedGetData = this.getData.bind(this, id, index, count, callbackFunction, delay).delay(0.3);
return;
}
if (this.delayed_getData) window.clearTimeout(this.delayed_getData);
this.delayed_getData = this._getData.bind(this,
id,
index,
count,
callbackFunction
).delay(delay);
},
_getData: function(id, index, count, callbackFunction) {
var start, end;
var i, j;
var missingUids = new Array();
// Compute last index depending on number of UIDs
start = index - (this.overflow/2);
if (start < 0) start = 0;
end = index + count + this.overflow - (index - start);
if (end > this.uids.length) {
start -= end - this.uids.length;
end = this.uids.length;
if (start < 0) start = 0;
}
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];
i++;
}
}
if (this.delayed_getRemoteData) window.clearTimeout(this.delayed_getRemoteData);
if (missingUids.length > 0) {
var params = "uids=" + missingUids.join(",");
this.delayed_getRemoteData = this._getRemoteData.bind(this,
{ callbackFunction: callbackFunction,
start: start, end: end,
id: id },
params).delay(0.5);
}
else
this._returnData(callbackFunction, id, start, end);
},
_getRemoteData: function(callbackData, urlParams) {
if (this.ajaxGetData) {
this.ajaxGetData.aborted = true;
this.ajaxGetData.abort();
// log ("MailDataSource._getData() aborted previous AJAX request");
}
// log ("MailDataSource._getData() fetching headers of " + urlParams);
this.ajaxGetData = triggerAjaxRequest(this.url + "/headers",
this._getRemoteDataCallback.bind(this),
callbackData,
urlParams,
{ "Content-type": "application/x-www-form-urlencoded" });
},
_getRemoteDataCallback: function(http) {
if (http.status == 200) {
if (http.responseText.length > 0) {
// We receives an array of hashes
var headers = $A(http.responseText.evalJSON(true));
var data = http.callbackData;
for (var i = 0; i < headers.length; i++) {
this.cache.set(headers[i]["uid"], headers[i]);
}
this._returnData(data["callbackFunction"], data["id"], data["start"], data["end"]);
}
}
else {
alert("SOGoMailDataSource._getRemoteDataCallback Error " + http.status + ": " + http.responseText);
}
},
_returnData: function(callbackFunction, id, start, end) {
var i, j;
var data = new Array();
for (i = start, j = 0; i < end; i++, j++) {
data[j] = this.cache.get(this.uids[i]);
}
callbackFunction(id, start, this.uids.length, data);
},
indexOf: function(uid) {
this.uids.indexOf(uid + "");
}
});
+237
View File
@@ -0,0 +1,237 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Resizable table interface to be added to a TABLE (this!)
*
* Columns with the class resizable will be .. resizable.
*
*/
var SOGoResizableTableInterface = {
delayedResize: null,
bind: function() {
var i;
var cells = $(this).down('tr').childElements();
for (i = 0; i < cells.length; i++) {
var cell = cells[i];
if (Prototype.Browser.IE)
cell.observe("selectstart", Event.stop);
if (cell.hasClassName('resizable')) {
Event.observe(cell, 'mouseover', SOGoResizableTable.initDetect);
Event.observe(cell, 'mouseout', SOGoResizableTable.killDetect);
}
SOGoResizableTable._resize(this, $(cell), i, null, cell.getWidth());
}
Event.observe(window, "resize", this.restore.bind(this));
},
restore: function(e) {
// Only resize the columns after a certain delay, otherwise it slow
// down the interface.
if (this.delayedResize) window.clearTimeout(this.delayedResize);
this.delayedResize = this._restore.bind(this).delay(0.2);
},
_restore: function() {
if (Prototype.Browser.IE)
while (SOGoResizableTable._stylesheet.styleSheet.rules.length)
SOGoResizableTable._stylesheet.styleSheet.removeRule();
else
while (SOGoResizableTable._stylesheet.firstChild)
SOGoResizableTable._stylesheet.removeChild(SOGoResizableTable._stylesheet.firstChild);
// TODO : widths ratios should be computed and columns restored accordingly.
var cells = $(this).down('tr').childElements();
for (i = 0; i < cells.length; i++) {
var cell = cells[i];
SOGoResizableTable._resize(this, $(cell), i, null, cell.getWidth());
}
}
};
SOGoResizableTable = {
_onHandle: false,
_cell: null,
_tbl: null,
_handle: null,
_stylesheet: null,
resize: function(table, index, w) {
var cell;
if (typeof index === 'number') {
if (!table || (table.tagName && table.tagName !== "TABLE")) { return; }
table = $(table);
index = Math.min(table.rows[0].cells.length, index);
index = Math.max(1, index);
index -= 1;
cell = $(table.rows[0].cells[index]);
}
else {
cell = $(index);
table = table ? $(table) : cell.up('table');
index = SOGoResizableTable.getCellIndex(cell);
}
var cells = table.down('tr').childElements();
var nextResizableCell = null;
for (var i = index + 1; i < cells.length; i++) {
var c = cells[i];
if (c.hasClassName('resizable')) {
nextResizableCell = c;
break;
}
}
var delta = SOGoResizableTable._resize(table, cell, index, nextResizableCell, w, false);
if (delta != 0 && nextResizableCell != null) {
var w = nextResizableCell.getWidth() - delta;
SOGoResizableTable._resize(table, nextResizableCell, i, null, w, true);
}
},
_resize: function(table, cell, index, nextResizableCell, w, isAdjustment) {
var pad = 0;
if (!Prototype.Browser.WebKit) {
pad = parseInt(cell.getStyle('paddingLeft'),10) + parseInt(cell.getStyle('paddingRight'),10);
pad += parseInt(cell.getStyle('borderLeftWidth'),10) + parseInt(cell.getStyle('borderRightWidth'),10);
}
var cells = table.down('tr').childElements();
if ((index + 1) == cells.length) {
return 0;
}
if (!isAdjustment && cell.getWidth() < w) {
if (nextResizableCell == null && (index + 2) == cells.length)
// The next cell is the last cell; respect its minimum width
// event if it's not resizable.
nextResizableCell = cells[index + 1];
if (nextResizableCell != null) {
// Respect the minimum width of the next resizable cell.
var max = cells[index].getWidth()
+ nextResizableCell.getWidth()
- parseInt(nextResizableCell.getStyle('minWidth'))
- pad;
w = Math.min(max, w);
}
}
// Respect the minimum width of the cell.
w = Math.max(w - pad, parseInt(cell.getStyle('minWidth')));
var delta = w - cell.getWidth() + pad;
var cssSelector = ' TABLE.' + $w(table.className).first() + ' .' + $w(cell.className).first();
if (SOGoResizableTable._stylesheet == null) {
SOGoResizableTable._stylesheet = document.createElement("style");
SOGoResizableTable._stylesheet.type = "text/css";
document.getElementsByTagName("head")[0].appendChild(SOGoResizableTable._stylesheet);
}
if (SOGoResizableTable._stylesheet.styleSheet && SOGoResizableTable._stylesheet.styleSheet.addRule) {
// IE
SOGoResizableTable._stylesheet.styleSheet.addRule(cssSelector,
' { width: ' + w + 'px; max-width: ' + w + 'px; }');
}
else {
// Mozilla + Safari
SOGoResizableTable._stylesheet.appendChild(document.createTextNode(cssSelector +
' { width: ' + w + 'px; max-width: ' + w + 'px; }'));
}
return delta;
},
initDetect: function(e) {
var cell = Event.element(e);
if (cell.tagName != "TH") { return; }
Event.observe(cell, 'mousemove', SOGoResizableTable.detectHandle);
Event.observe(cell, 'mousedown', SOGoResizableTable.startResize);
},
detectHandle: function(e) {
var cell = Event.element(e);
if (SOGoResizableTable.pointerPos(cell, Event.pointerX(e), Event.pointerY(e))) {
cell.addClassName('resize-handle-active');
SOGoResizableTable._onHandle = true;
}
else {
cell.removeClassName('resize-handle-active');
SOGoResizableTable._onHandle = false;
}
},
killDetect: function(e) {
SOGoResizableTable._onHandle = false;
var cell = Event.element(e);
Event.stopObserving(cell, 'mousemove', SOGoResizableTable.detectHandle);
Event.stopObserving(cell, 'mousedown', SOGoResizableTable.startResize);
cell.removeClassName('resize-handle-active');
},
startResize: function(e) {
if (!SOGoResizableTable._onHandle) { return; }
var cell = Event.element(e);
Event.stopObserving(cell, 'mousemove', SOGoResizableTable.detectHandle);
Event.stopObserving(cell, 'mousedown', SOGoResizableTable.startResize);
Event.stopObserving(cell, 'mouseout', SOGoResizableTable.killDetect);
SOGoResizableTable._cell = cell;
var table = cell.up('table');
SOGoResizableTable._tbl = table;
SOGoResizableTable._handle = $(document.createElement('div')).addClassName('resize-handle').setStyle({
'top' : table.cumulativeOffset()[1] + 'px',
'left' : Event.pointerX(e) + 'px',
'height' : table.getHeight() + 'px',
'max-height' : table.getHeight() + 'px'
});
document.body.appendChild(SOGoResizableTable._handle);
Event.observe(document, 'mousemove', SOGoResizableTable.drag);
Event.observe(document, 'mouseup', SOGoResizableTable.endResize);
Event.stop(e);
},
endResize: function(e) {
var cell = SOGoResizableTable._cell;
if (!cell) { return; }
SOGoResizableTable.resize(null, cell, (Event.pointerX(e) - cell.cumulativeOffset()[0]));
Event.stopObserving(document, 'mousemove', SOGoResizableTable.drag);
Event.stopObserving(document, 'mouseup', SOGoResizableTable.endResize);
$$('div.resize-handle').each(function(elm){
document.body.removeChild(elm);
});
Event.observe(cell, 'mouseout', SOGoResizableTable.killDetect);
SOGoResizableTable._tbl = SOGoResizableTable._handle = SOGoResizableTable._cell = null;
Event.stop(e);
},
drag: function(e) {
e = $(e);
if (SOGoResizableTable._handle === null) {
try {
SOGoResizableTable.resize(SOGoResizableTable._tbl, SOGoResizableTable._cell, (Event.pointerX(e) - SOGoResizableTable._cell.cumulativeOffset()[0]));
}
catch(e) {}
}
else {
SOGoResizableTable._handle.setStyle({'left' : Event.pointerX(e) + 'px'});
}
return false;
},
pointerPos: function(element, x, y) {
var offset = $(element).cumulativeOffset();
return (y >= offset[1] &&
y < offset[1] + element.offsetHeight &&
x >= offset[0] + element.offsetWidth - 5 &&
x < offset[0] + element.offsetWidth);
},
getCellIndex : function(cell) {
return $A(cell.parentNode.cells).indexOf(cell);
}
};
+5 -5
View File
@@ -409,7 +409,7 @@ td img.tbtv_sortcell
height: 12px; }
TD.subjectCell,
td.tbtv_subject_headercell
TD.tbtv_subject_headercell
{ overflow: hidden; }
/* drag handles */
@@ -530,9 +530,10 @@ DIV.dTreeNode SPAN._dragOver
INPUT.checkBox
{ vertical-align: middle; }
/* tablekit resizable columns */
/* resizable columns */
TABLE TD.resize-handle-active
TABLE TD.resize-handle-active,
TABLE TH.resize-handle-active
{ cursor: e-resize; }
DIV.resize-handle
@@ -541,8 +542,7 @@ DIV.resize-handle
border-right: 1px solid #fff;
position: absolute;
top: 0px;
left: 0px;
max-height: 2em; } /* will be set in JavaScript when setting drag handles */
left: 0px; }
@media print
{
+4 -2
View File
@@ -466,7 +466,10 @@ function onRowClick(event) {
node = node.parentNode; // select TR
}
if (node.tagName == 'TR') {
rowIndex = node.rowIndex - $(node).up('table').down('thead').getElementsByTagName('tr').length;
var head = $(node).up('table').down('thead');
rowIndex = node.rowIndex;
if (head)
rowIndex -= head.getElementsByTagName('tr').length;
}
else if (node.tagName == 'LI') {
// Find index of clicked row
@@ -573,7 +576,6 @@ function popupMenu(event, menuId, target) {
$(document.body).observe("click", onBodyClickMenuHandler);
}
}
function getParentMenu(node) {
+3
View File
@@ -54,6 +54,9 @@ IMG.dragMessage
TD.mailer_fieldname
{ width: 8em; }
TABLE.messageList TD
{ white-space: pre; }
/* ContactsUI */
/*DIV#contactFoldersList SPAN.toolbarButton