See ChangeLog

Monotone-Parent: 2b83c62317d266d5f7a2e2046fc19f68f88a385f
Monotone-Revision: 763ab7e667a159d6b727544a067a085d622ab598

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2010-06-25T19:58:30
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle
2010-06-25 19:58:30 +00:00
parent 339a9b60c5
commit b2fc189b93
12 changed files with 501 additions and 205 deletions

View File

@@ -1,3 +1,60 @@
2010-06-25 Francis Lachapelle <flachapelle@inverse.ca>
* UI/MailerUI/UIxMailMainFrame.m (-inboxData): new method used to
avoid an AJAX call when first loading the webmail module. It
returns a dictionary with the UIDs of the inbox folder as well as
the headers of the first few messages.
* UI/MailerUI/UIxMailListActions.m (-imap4SortOrdering): fixed an
issue that would not save the sorting state when matching the
default sort key but not the sort direction.
(-getUIDsAndHeadersInFolder:): new method that returns all UIDs of
a mail folder and the headers of the first corresponding messages.
(-getHeadersForUIDs:inFolder:): was getHeadersAction. It now
returns an array of arrays instead of an array of
dictionaries. The first array contains the previous dictionary
keys so we can easily reconstruct a dictionary in JavaScript.
* UI/MailerUI/UIxMailEditor.m (-setSourceUID:, -sourceUID,
-setSourceFolder:, -sourceFolder): new methods to keep track of
the source folder replying or forwarding a message.
(-sendAction): now calls the JavaScript function refreshMessage
instead of refreshCurrentMailbox.
* SoObjects/Mailer/SOGoDraftObject.m (-setSourceFolder:,
-setSourceFolderWithMailObject:, -sourceFolder): new methods to
keep track of the source folder replying or forwarding a message.
(-fetchMailForEditing:): fixed the message UID value.
* UI/WebServerResources/SOGoMailDataSource.js (remove): new
function that was integrated to invalidate. There are now
splitted.
(init): new function to initialize the data source with
pre-fetched uids and headers.
(getData): if the source is not yet loaded, the function will now
be called by the method _loadCallback.
(_getRemoteDataCallback): headers received from the server are no
formatted as a hash but as an array with the first entry
corresponding the previous hash keys.
* UI/WebServerResources/SOGoDataTable.js (initSource): was
setSource which was redefined to received a data source as
argument.
(invalidate): new function to refresh a single row.
* UI/WebServerResources/MailerUI.js (openMailbox): added caching
of data sources (IMAP folders). The previous selection is now
restored when chaning folders.
(loadMessageCallback): the window is no longer reloaded if the
message doesn't exist; only the proper row is removed.
(refreshMessage): new function called after sending a message.
2010-06-24 Francis Lachapelle <flachapelle@inverse.ca>
* UI/MailerUI/UIxMailEditor.m (-sendAction): trigger the new
JavaScript function refreshMessage() instead of
refreshCurrentFolder() so we only refresh one row.
2010-06-23 Francis Lachapelle <flachapelle@inverse.ca>
* SoObjects/SOGo/SOGoUserDefaults.m (-language): verify that the

View File

@@ -56,6 +56,7 @@
NSString *text;
NSString *sourceURL;
NSString *sourceFlag;
NSString *sourceFolder;
}
/* contents */
@@ -75,6 +76,8 @@
/* for replies and forwards */
- (void) setSourceURL: (NSString *) newSurceURL;
- (void) setSourceFlag: (NSString *) newSourceFlag;
- (void) setSourceFolder: (NSString *) newSourceFolder;
- (NSString *) sourceFolder;
- (void) setIMAP4ID: (int) newIMAPID;
- (int) IMAP4ID;

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.
@@ -259,6 +259,35 @@ static NSString *userAgent = nil;
ASSIGN (sourceFlag, newSourceFlag);
}
- (void) setSourceFolder: (NSString *) newSourceFolder
{
ASSIGN (sourceFolder, newSourceFolder);
}
- (void) setSourceFolderWithMailObject: (SOGoMailObject *) sourceMail
{
NSMutableArray *paths;
id parent;
parent = [sourceMail container];
paths = [NSMutableArray arrayWithCapacity: 1];
while (parent && ![parent isKindOfClass: [SOGoMailAccount class]])
{
[paths insertObject: [parent nameInContainer] atIndex: 0];
parent = [parent container];
}
if (parent)
[paths insertObject: [NSString stringWithFormat: @"/%@", [parent nameInContainer]]
atIndex: 0];
[self setSourceFolder: [paths componentsJoinedByString: @"/"]];
}
- (NSString *) sourceFolder
{
return sourceFolder;
}
- (NSException *) storeInfo
{
NSMutableDictionary *infos;
@@ -273,12 +302,13 @@ static NSString *userAgent = nil;
if (inReplyTo)
[infos setObject: inReplyTo forKey: @"inReplyTo"];
if (IMAP4ID > -1)
[infos setObject: [NSNumber numberWithInt: IMAP4ID]
forKey: @"IMAP4ID"];
if (sourceURL && sourceFlag)
[infos setObject: [NSString stringWithFormat: @"%i", IMAP4ID]
forKey: @"IMAP4ID"];
if (sourceURL && sourceFlag && sourceFolder)
{
[infos setObject: sourceURL forKey: @"sourceURL"];
[infos setObject: sourceFlag forKey: @"sourceFlag"];
[infos setObject: sourceFolder forKey: @"sourceFolder"];
}
if ([infos writeToFile: [self infoPath] atomically:YES])
@@ -324,6 +354,9 @@ static NSString *userAgent = nil;
value = [infoDict objectForKey: @"sourceFlag"];
if (value)
[self setSourceFlag: value];
value = [infoDict objectForKey: @"sourceFolder"];
if (value)
[self setSourceFolder: value];
value = [infoDict objectForKey: @"inReplyTo"];
if (value)
@@ -622,7 +655,8 @@ static NSString *userAgent = nil;
[self setText: [sourceMail contentForEditing]];
[self setSourceURL: [sourceMail imap4URLString]];
IMAP4ID = [[sourceMail nameInContainer] intValue];
[self setIMAP4ID: [[sourceMail nameInContainer] intValue]];
[self setSourceFolderWithMailObject: sourceMail];
[self storeInfo];
}
@@ -650,6 +684,9 @@ static NSString *userAgent = nil;
[self setHeaders: info];
[self setSourceURL: [sourceMail imap4URLString]];
[self setSourceFlag: @"Answered"];
[self setIMAP4ID: [[sourceMail nameInContainer] intValue]];
[self setSourceFolderWithMailObject: sourceMail];
[self storeInfo];
}
@@ -670,6 +707,8 @@ static NSString *userAgent = nil;
[self setSourceURL: [sourceMail imap4URLString]];
[self setSourceFlag: @"$Forwarded"];
[self setIMAP4ID: [[sourceMail nameInContainer] intValue]];
[self setSourceFolderWithMailObject: sourceMail];
/* attach message */
ud = [[context activeUser] userDefaults];

View File

@@ -65,6 +65,8 @@
NSArray *cc;
NSArray *bcc;
NSString *subject;
NSString *sourceUID;
NSString *sourceFolder;
NSString *text;
NSMutableArray *fromEMails;
NSString *from;
@@ -116,6 +118,8 @@ static NSArray *infoKeys = nil;
[to release];
[cc release];
[bcc release];
[sourceUID release];
[sourceFolder release];
[attachmentName release];
[attachmentNames release];
[attachedFiles release];
@@ -245,6 +249,29 @@ static NSArray *infoKeys = nil;
return text;
}
- (void) setSourceUID: (int) newSourceUID
{
NSString *s;
s = [NSString stringWithFormat: @"%i", newSourceUID];
ASSIGN (sourceUID, s);
}
- (NSString *) sourceUID
{
return sourceUID;
}
- (void) setSourceFolder: (NSString *) newSourceFolder
{
ASSIGN (sourceFolder, newSourceFolder);
}
- (NSString *) sourceFolder
{
return sourceFolder;
}
- (void) setTo: (NSArray *) newTo
{
if ([newTo isKindOfClass: [NSNull class]])
@@ -490,6 +517,11 @@ static NSArray *infoKeys = nil;
return [[self attachmentNames] count] > 0 ? YES : NO;
}
- (NSString *) uid
{
return [[self clientObject] nameInContainer];
}
- (id) defaultAction
{
SOGoDraftObject *co;
@@ -498,6 +530,8 @@ static NSArray *infoKeys = nil;
[co fetchInfo];
[self loadInfo: [co headers]];
[self setText: [co text]];
[self setSourceUID: [co IMAP4ID]];
[self setSourceFolder: [co sourceFolder]];
return self;
}
@@ -537,6 +571,7 @@ static NSArray *infoKeys = nil;
- (id <WOActionResults>) sendAction
{
id <WOActionResults> result;
SOGoDraftObject *co;
// TODO: need to validate whether we have a To etc
@@ -548,7 +583,12 @@ static NSArray *infoKeys = nil;
{
result = (id <WOActionResults>) [[self clientObject] sendMail];
if (!result)
result = [self jsCloseWithRefreshMethod: @"refreshCurrentFolder()"];
{
co = [self clientObject];
result = [self jsCloseWithRefreshMethod: [NSString stringWithFormat: @"refreshMessage(\"%@\", %i)",
[co sourceFolder],
[co IMAP4ID]]];
}
}
else
result = [self failedToSaveFormResponse];

View File

@@ -45,6 +45,11 @@
- (EOQualifier *) searchQualifier;
- (NSString *) msgLabels;
- (NSArray *) getSortedUIDsInFolder: (SOGoMailFolder *) mailFolder;
- (NSArray *) getHeadersForUIDs: (NSArray *) uids
inFolder: (SOGoMailFolder *) mailFolder;
- (NSDictionary *) getUIDsAndHeadersInFolder: (SOGoMailFolder *) mailFolder;
- (id) getMailAction;
- (id <WOActionResults>) getSortedUIDsAction;
- (id <WOActionResults>) getHeadersAction;

View File

@@ -64,6 +64,9 @@
#import "UIxMailListActions.h"
// The maximum number of headers to prefetch when querying the UIDs list
#define headersPrefetchMaxSize 100
@implementation UIxMailListActions
- (id) initWithRequest: (WORequest *) newRequest
@@ -341,7 +344,7 @@
- (NSString *) imap4SortOrdering
{
NSString *sort, *ascending;
NSString *module; //*login
NSString *module;
NSMutableDictionary *moduleSettings;
BOOL asc;
SOGoUser *activeUser;
@@ -352,14 +355,23 @@
ascending = [[context request] formValueForKey: @"asc"];
asc = [ascending boolValue];
if (![sort isEqualToString: [self defaultSortKey]])
activeUser = [context activeUser];
clientObject = [self clientObject];
module = [[[clientObject container] container] nameInContainer];
us = [activeUser userSettings];
moduleSettings = [us objectForKey: module];
if ([sort isEqualToString: [self defaultSortKey]] && !asc)
{
if (moduleSettings)
{
[moduleSettings removeObjectForKey: @"SortingState"];
[us synchronize];
}
}
else
{
// Save the sorting state in the user settings
activeUser = [context activeUser];
clientObject = [self clientObject];
module = [[[clientObject container] container] nameInContainer];
us = [activeUser userSettings];
moduleSettings = [us objectForKey: module];
if (!moduleSettings)
{
moduleSettings = [NSMutableDictionary dictionary];
@@ -413,10 +425,9 @@
return qualifier;
}
- (NSArray *) sortedUIDs
- (NSArray *) getSortedUIDsInFolder: (SOGoMailFolder *) mailFolder
{
EOQualifier *qualifier, *fetchQualifier, *notDeleted;
SOGoMailFolder *folder;
if (!sortedUIDs)
{
@@ -434,10 +445,9 @@
else
fetchQualifier = notDeleted;
folder = [self clientObject];
sortedUIDs
= [folder fetchUIDsMatchingQualifier: fetchQualifier
sortOrdering: [self imap4SortOrdering]];
= [mailFolder fetchUIDsMatchingQualifier: fetchQualifier
sortOrdering: [self imap4SortOrdering]];
[sortedUIDs retain];
}
@@ -449,7 +459,7 @@
NSArray *messageNbrs;
int index;
messageNbrs = [self sortedUIDs];
messageNbrs = [self getSortedUIDsInFolder: [self clientObject]];
index
= [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
// if (index < 0)
@@ -524,75 +534,55 @@
return [self redirectToLocation:@"view"];
}
- (NSDictionary *) getUIDsAndHeadersInFolder: (SOGoMailFolder *) mailFolder
{
NSArray *uids, *headers;
NSDictionary *data;
NSRange r;
int count;
uids = [self getSortedUIDsInFolder: mailFolder]; // retrieves the form parameters "sort" and "asc"
// Also retrieve the first headers, up to 'headersPrefetchMaxSize'
count = [uids count];
if (count > headersPrefetchMaxSize) count = headersPrefetchMaxSize;
r = NSMakeRange(0, count);
headers = [self getHeadersForUIDs: [uids subarrayWithRange: r]
inFolder: mailFolder];
data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids",
headers, @"headers", nil];
return data;
}
- (id <WOActionResults>) getSortedUIDsAction
{
NSArray *uids;
NSRange r;
WORequest *request;
NSDictionary *data;
SOGoMailFolder *folder;
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];
folder = [self clientObject];
data = [self getUIDsAndHeadersInFolder: folder];
[response setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"];
[response appendContentString: [uids jsonRepresentation]];
[response appendContentString: [data jsonRepresentation]];
return response;
}
- (id <WOActionResults>) getHeadersAction
- (NSArray *) getHeadersForUIDs: (NSArray *) uids
inFolder: (SOGoMailFolder *) mailFolder
{
NSArray *uids, *to, *from;
NSArray *to, *from;
NSDictionary *msgs;
NSMutableArray *headers;
NSMutableDictionary *msg;
NSMutableArray *headers, *msg;
NSEnumerator *msgsList;
NSString *msgIconStatus, *msgDate;
SOGoMailFolder *mailFolder;
WORequest *request;
WOResponse *response;
UIxEnvelopeAddressFormatter *addressFormatter;
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
@@ -601,78 +591,109 @@
msgsList = [[msgs objectForKey: @"fetch"] objectEnumerator];
[self setMessage: [msgsList nextObject]];
msg = [NSMutableArray arrayWithObjects: @"To", @"Attachment", @"Flagged", @"Subject", @"From", @"Unread", @"Priority", @"Date", @"Size", @"rowClasses", @"labels", @"rowID", @"uid", nil];
[headers addObject: msg];
while (message)
{
msg = [NSMutableDictionary dictionaryWithCapacity: 11];
msg = [NSMutableArray arrayWithCapacity: 12];
// Columns data
// To
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"];
}
[msg addObject: [addressFormatter stringForArray: to]];
else
{
[msg setObject: [NSString stringWithFormat: @"<img src=\"%@\">",
[self urlForResourceFilename: @"dot.png"]]
forKey: @"Flagged"];
}
[msg addObject: @""];
[msg setObject: [NSString stringWithFormat: @"<span>%@</span>",
[self messageSubject]]
forKey: @"Subject"];
// Attachment
if ([self hasMessageAttachment])
[msg addObject: [NSString stringWithFormat: @"<img src=\"%@\"/>", [self urlForResourceFilename: @"title_attachment_14x14.png"]]];
else
[msg addObject: @""];
// Flagged
if ([self isMessageFlagged])
[msg addObject: [NSString stringWithFormat: @"<img src=\"%@\" class=\"messageIsFlagged\">",
[self urlForResourceFilename: @"flag.png"]]];
else
[msg addObject: [NSString stringWithFormat: @"<img src=\"%@\">",
[self urlForResourceFilename: @"dot.png"]]];
// Subject
[msg addObject: [NSString stringWithFormat: @"<span>%@</span>",
[self messageSubject]]];
// From
from = [[message objectForKey: @"envelope"] from];
if ([from count] > 0)
[msg setObject: [addressFormatter stringForArray: from] forKey: @"From"];
[msg addObject: [addressFormatter stringForArray: from]];
else
[msg setObject: @"" forKey: @"From"];
[msg addObject: @""];
// Unread
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=\"%@\"/>",
[msg addObject: [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"];
[self msgIconReadImgID]]];
[msg setObject: [self messagePriority] forKey: @"Priority"];
// Priority
[msg addObject: [self messagePriority]];
// Date
msgDate = [self messageDate];
if (msgDate == nil)
msgDate = @"";
[msg setObject: msgDate forKey: @"Date"];
[msg addObject: msgDate];
[msg setObject: [self messageSize] forKey: @"Size"];
// Size
[msg addObject: [self messageSize]];
[msg setObject: [self msgLabels] forKey: @"labels"];
// rowClasses
[msg addObject: [self messageRowStyleClass]];
[msg setObject: [self msgRowID] forKey: @"rowID"];
// labels
[msg addObject: [self msgLabels]];
[msg setObject: [message objectForKey: @"uid"] forKey: @"uid"];
// rowID
[msg addObject: [self msgRowID]];
// uid
[msg addObject: [message objectForKey: @"uid"]];
[headers addObject: msg];
[self setMessage: [msgsList nextObject]];
}
return headers;
}
- (id <WOActionResults>) getHeadersAction
{
NSArray *uids, *headers;
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 = [self getHeadersForUIDs: uids
inFolder: [self clientObject]];
response = [context response];
[response setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"];

View File

@@ -23,6 +23,8 @@
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSValue.h>
#import <EOControl/EOQualifier.h>
#import <NGCards/NGVCard.h>
#import <NGCards/NGVCardReference.h>
#import <NGCards/NGVList.h>
@@ -42,6 +44,7 @@
#import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoMailAccount.h>
#import <Mailer/SOGoMailAccounts.h>
#import <Mailer/SOGoMailFolder.h>
#import <SOGo/NSDictionary+URL.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSString+Utilities.h>
@@ -54,6 +57,7 @@
#import <SOGoUI/UIxComponent.h>
#import "UIxMailMainFrame.h"
#import "UIxMailListActions.h"
// Avoid compilation warnings
@interface SOGoUserFolder (private)
@@ -173,6 +177,33 @@
return [u hasSuffix:@"/"] ? @"view" : @"#";
}
- (NSString *) inboxData
{
SOGoMailAccounts *accounts;
SOGoMailAccount *account;
SOGoMailFolder *inbox;
NSString *firstAccount;
NSDictionary *data;
SOGoUser *activeUser;
UIxMailListActions *actions;
[self _setupContext];
actions = [[[UIxMailListActions new] initWithRequest: [context request]] autorelease];
activeUser = [context activeUser];
accounts = [self clientObject];
firstAccount = [[[accounts accountKeys] allKeys]
objectAtIndex: 0];
account = [accounts lookupName: firstAccount inContext: context acquire: NO];
inbox = [account inboxFolderInContext: context];
data = [actions getUIDsAndHeadersInFolder: inbox];
return [data jsonRepresentation];
}
- (id <WOActionResults>) composeAction
{
id contact;
@@ -333,7 +364,7 @@
[self _setupContext];
vertical = [moduleSettings objectForKey: @"DragHandleVertical"];
return ((vertical && [vertical intValue] > 0) ? (id)[vertical stringByAppendingFormat: @"px"] : nil);
return ((vertical && [vertical intValue] > 0) ? (id)[vertical stringByAppendingString: @"px"] : nil);
}
- (NSString *) horizontalDragHandleStyle
@@ -343,7 +374,7 @@
[self _setupContext];
horizontal = [moduleSettings objectForKey: @"DragHandleHorizontal"];
return ((horizontal && [horizontal intValue] > 0) ? (id)[horizontal stringByAppendingFormat: @"px"] : nil);
return ((horizontal && [horizontal intValue] > 0) ? (id)[horizontal stringByAppendingString: @"px"] : nil);
}
- (NSString *) mailboxContentStyle

View File

@@ -13,6 +13,8 @@
const:jsFiles="UIxMailToSelection.js,ckeditor/ckeditor.js,SOGoAutoCompletion.js">
<script type="text/javascript">
var mailIsReply = <var:string value="isMailReply"/>;
var sourceUID = <var:string value="sourceUID"/>;
var sourceFolder = '<var:string value="sourceFolder" const:escapeHTML="NO"/>';
</script>
<div class="popupMenu" id="contactsMenu">
<ul><!-- space --></ul>

View File

@@ -11,6 +11,7 @@
<script type="text/javascript">
var textMailAccounts = '<var:string value="mailAccounts" const:escapeHTML="NO"/>';
var textDefaultColumnsOrder = '<var:string value="defaultColumnsOrder" const:escapeHTML="NO"/>';
var inboxData = <var:string value="inboxData" const:escapeHTML="NO"/>;
</script>
<style type="text/css">
<var:if condition="horizontalDragHandleStyle">

View File

@@ -1,4 +1,4 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* -*- Mode: js2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* JavaScript for SOGoMail */
var accounts = {};
@@ -21,7 +21,8 @@ var Mailer = {
popups: new Array(),
quotas: null,
dataTable: null
dataTable: null,
dataSources: new Hash()
};
var usersRightsWindowHeight = 320;
@@ -103,6 +104,8 @@ function onMenuSharing(event) {
/* mail list DOM changes */
/* Update the messages list when flagging/unflagging a message.
* No AJAX is triggered here. */
function flagMailInWindow (win, msguid, flagged) {
var row = win.$("row_" + msguid);
@@ -120,6 +123,8 @@ function flagMailInWindow (win, msguid, flagged) {
}
}
/* Update the messages list when setting the unread/read flag of a message.
* No AJAX is triggered here. */
function markMailInWindow(win, msguid, markread) {
var row = win.$("row_" + msguid);
var unseenCount = 0;
@@ -130,8 +135,6 @@ function markMailInWindow(win, msguid, markread) {
row.removeClassName("mailer_unreadmail");
var img = win.$("readdiv_" + msguid);
if (img) {
img.removeClassName("mailerUnreadIcon");
img.addClassName("mailerReadIcon");
img.setAttribute("src", ResourcesURL + "/dot.png");
var title = img.getAttribute("title-markunread");
if (title)
@@ -148,24 +151,25 @@ function markMailInWindow(win, msguid, markread) {
row.addClassName("mailer_unreadmail");
var img = win.$("readdiv_" + msguid);
if (img) {
img.removeClassName("mailerReadIcon");
img.addClassName("mailerUnreadIcon");
img.setAttribute("src", ResourcesURL + "/icon_unread.gif");
var title = img.getAttribute("title-markread");
if (title)
img.setAttribute("title", title);
}
else {
log ("No IMG found for " + msguid);
}
unseenCount = 1;
}
}
}
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);
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);
}
}
return (unseenCount != 0);
@@ -205,11 +209,13 @@ function openMessageWindowsForSelection(action, firstOnly) {
return false;
}
/* Triggered when clicking on the read/unread dot of a message row */
function mailListMarkMessage(event) {
var msguid = this.id.split('_')[1];
var row = $(this).up('TR');
var action;
var markread;
if ($(this).hasClassName('mailerUnreadIcon')) {
if (row.hasClassName("mailer_unreadmail")) {
action = 'markMessageRead';
markread = true;
}
@@ -217,10 +223,13 @@ function mailListMarkMessage(event) {
action = 'markMessageUnread';
markread = false;
}
markMailInWindow(window, msguid, markread);
var url = ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/"
+ msguid + "/" + action;
var data = { "window": window, "msguid": msguid, "markread": markread };
var data = { "msguid": msguid };
triggerAjaxRequest(url, mailListMarkMessageCallback, data);
preventDefault(event);
@@ -228,15 +237,15 @@ function mailListMarkMessage(event) {
}
function mailListMarkMessageCallback(http) {
var data = http.callbackData;
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"]);
Mailer.dataTable.invalidate(data["msguid"], true);
}
else {
alert("Message Mark Failed (" + http.status + "): " + http.statusText);
window.location.reload();
log("Message Mark Failed (" + http.status + "): " + http.statusText);
Mailer.dataTable.invalidate(data["msguid"], false);
}
}
@@ -251,10 +260,12 @@ function mailListFlagMessageToggle (e) {
action = "markMessageUnflagged";
flagged = false;
}
flagMailInWindow(window, msguid, flagged);
var url = ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/"
+ msguid + "/" + action;
var data = { "window": window, "msguid": msguid, "flagged": flagged };
var data = { "msguid": msguid };
triggerAjaxRequest(url, mailListFlagMessageToggleCallback, data);
}
@@ -262,34 +273,14 @@ function mailListFlagMessageToggle (e) {
function mailListFlagMessageToggleCallback (http) {
if (isHttpStatus204(http.status)) {
var data = http.callbackData;
flagMailInWindow(data["window"], data["msguid"], data["flagged"]);
Mailer.dataTable.invalidate(data["msguid"], true);
}
else {
alert("Message Mark Failed (" + http.status + "): " + http.statusText);
window.location.reload();
log("Message Mark Failed (" + http.status + "): " + http.statusText);
Mailer.dataTable.invalidate(data["msguid"], true);
}
}
/* maillist row highlight */
var oldMaillistHighlight = null; // to remember deleted/selected style
function ml_highlight(sender) {
oldMaillistHighlight = sender.className;
if (oldMaillistHighlight == "tableview_highlight")
oldMaillistHighlight = null;
sender.className = "tableview_highlight";
}
function ml_lowlight(sender) {
if (oldMaillistHighlight) {
sender.className = oldMaillistHighlight;
oldMaillistHighlight = null;
}
else
sender.className = "tableview";
}
function onUnload(event) {
var url = ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/expunge";
@@ -659,22 +650,36 @@ function openMailbox(mailbox, reload, updateStatus) {
// 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);
var key = mailbox;
if (urlParams.keys().length > 0) {
var p = urlParams.keys().collect(function(key) { return key + "=" + urlParams.get(key); }).join("&");
key += "?" + p;
}
var dataSource = Mailer.dataSources.get(key);
if (!dataSource || reload) {
dataSource = new SOGoMailDataSource(Mailer.dataTable, url);
if (inboxData[key]) {
dataSource.init(inboxData[key][0], inboxData[key][1]);
inboxData = []; // invalidate this initial lookup
}
else
dataSource.load(urlParams);
Mailer.dataSources.set(key, dataSource);
}
Mailer.dataTable.setSource(dataSource);
messageList.deselectAll();
Mailer.dataTable.render();
configureDraggables();
Mailer.currentMailbox = mailbox;
/*
// TODO : restore previously selected message.
// Restore previous selection
var currentMessage = Mailer.currentMessages[mailbox];
if (currentMessage) {
Mailer.dataTable.render(currentMessage);
if (!reload)
if (!reload) {
loadMessage(currentMessage);
}
*/
}
}
if (updateStatus != false)
getStatusFolders();
@@ -685,16 +690,22 @@ function openMailbox(mailbox, reload, updateStatus) {
* Called from SOGoDataTable.render()
*/
function messageListCallback(row, data, isNew) {
var currentMessage = Mailer.currentMessages[Mailer.currentMailbox];
row.id = data['rowID'];
row.writeAttribute('labels', (data['labels']?data['labels']:""));
row.className = data['rowClasses'];
// Restore previous selection
if (data['uid'] == currentMessage)
row.addClassName('_selected');
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"];
var cells;
if (Prototype.Browser.IE)
@@ -781,8 +792,16 @@ function updateMessageListCounter(count, isDelta) {
cell.update(_("No message"));
}
/* Function is called when the event datatable:rendered is fired from SOGoDataTable. */
function onMessageListRender(event) {
// Event is fired from SOGoDataTable.
// Restore previous selection
var currentMessage = Mailer.currentMessages[Mailer.currentMailbox];
if (currentMessage) {
var rows = this.select("TR#row_" + currentMessage);
if (rows.length == 1)
rows[0].selectElement();
}
// Update message counter in folder name
updateMessageListCounter(event.memo, false);
}
@@ -926,22 +945,25 @@ function onMessageSelectionChange() {
$('messageContent').update();
}
function loadMessage(idx) {
function loadMessage(msguid) {
if (document.messageAjaxRequest) {
document.messageAjaxRequest.aborted = true;
document.messageAjaxRequest.abort();
}
var div = $('messageContent');
var cachedMessage = getCachedMessage(idx);
var row = $("row_" + idx);
var seenStateChanged = row && row.hasClassName('mailer_unreadmail');
var cachedMessage = getCachedMessage(msguid);
var row = $("row_" + msguid);
var seenStateHasChanged = row && row.hasClassName('mailer_unreadmail');
if (cachedMessage == null) {
var url = (ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/"
+ idx + "/view?noframe=1");
+ msguid + "/view?noframe=1");
div.update();
document.messageAjaxRequest = triggerAjaxRequest(url, messageCallback, idx);
markMailInWindow(window, idx, true);
document.messageAjaxRequest = triggerAjaxRequest(url,
loadMessageCallback,
{ 'msguid': msguid, 'seenStateHasChanged': seenStateHasChanged });
// Warning: We assume the user can set the read/unread flag of the message.
markMailInWindow(window, msguid, true);
}
else {
div.update(cachedMessage['text']);
@@ -949,9 +971,9 @@ function loadMessage(idx) {
document.messageAjaxRequest = null;
configureLinksInMessage();
resizeMailContent();
if (seenStateChanged) {
if (seenStateHasChanged) {
// Mark message as read on server
var img = row.select("IMG.mailerUnreadIcon").first();
var img = row.select("IMG.mailerReadIcon").first();
var fcnMarkRead = mailListMarkMessage.bind(img);
fcnMarkRead();
}
@@ -1313,7 +1335,7 @@ function onAttachmentClick (event) {
return false;
}
function messageCallback(http) {
function loadMessageCallback(http) {
var div = $('messageContent');
if (http.status == 200) {
@@ -1326,7 +1348,11 @@ function messageCallback(http) {
if (http.callbackData) {
var cachedMessage = new Array();
cachedMessage['idx'] = Mailer.currentMailbox + '/' + http.callbackData;
var msguid = http.callbackData.msguid;
// Warning: If the user can't set the read/unread flag, it won't
// be reflected in the view unless we force the refresh.
Mailer.dataTable.invalidate(msguid, true);
cachedMessage['idx'] = Mailer.currentMailbox + '/' + msguid;
cachedMessage['time'] = (new Date()).getTime();
cachedMessage['text'] = http.responseText;
if (cachedMessage['text'].length < 30000)
@@ -1335,7 +1361,7 @@ function messageCallback(http) {
}
else if (http.status == 404) {
alert (_("The message you have selected doesn't exist anymore."));
window.location.reload();
Mailer.dataTable.remove(http.callbackData.msguid);
}
else
log("messageCallback: problem during ajax request: " + http.status);
@@ -1518,6 +1544,13 @@ function refreshCurrentFolder() {
openMailbox(Mailer.currentMailbox, true);
}
/* Called after sending an email */
function refreshMessage(mailbox, messageUID) {
if (mailbox == Mailer.currentMailbox) {
Mailer.dataTable.invalidate(messageUID);
}
}
function configureMessageListEvents(headerTable, dataTable) {
if (headerTable)
// Sortable columns
@@ -1629,6 +1662,7 @@ function initMailer(event) {
if (!$(document.body).hasClassName("popup")) {
//initDnd();
Mailer.dataTable = $("mailboxList");
Mailer.dataTable.addInterface(SOGoDataTableInterface);
Mailer.dataTable.setRowRenderCallback(messageListCallback);

View File

@@ -1,4 +1,4 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* -*- Mode: js2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Data table interface to be added to a DIV (this!)
@@ -58,7 +58,13 @@ var SOGoDataTableInterface = {
this.rowRenderCallback = callbackFunction;
},
setSource: function(dataSourceClass, url, params) {
setSource: function(ds) {
this.dataSource = ds;
this._emptyTable();
this.scrollTop = 0;
},
initSource: function(dataSourceClass, url, params) {
// log ("DataTable.setSource() " + url);
if (this.dataSource) this.dataSource.destroy();
this._emptyTable();
@@ -87,7 +93,7 @@ var SOGoDataTableInterface = {
return firstRowIndex;
},
render: function(uid) {
render: function() {
var index = this.firstVisibleRowIndex();
var count = this.visibleRowCount();
@@ -95,10 +101,10 @@ var SOGoDataTableInterface = {
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);
// 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;
//if (this.dataSource.uids && this.dataSource.uids.length < end) end = this.dataSource.uids.length;
index = start;
count = end - start;
@@ -216,6 +222,7 @@ var SOGoDataTableInterface = {
}
}
// Update references to selected rows
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)");
@@ -229,12 +236,32 @@ var SOGoDataTableInterface = {
Event.fire(this, "datatable:rendered", max);
},
invalidate: function(uid, withoutRefresh) {
// Refetch the data for uid. Only refresh the data table if
// necessary.
var index = this.dataSource.invalidate(uid);
this.currentRenderID = index + "-" + 1;
this.dataSource.getData(this.currentRenderID,
index,
1,
(withoutRefresh?false:this._invalidate.bind(this)),
0);
},
_invalidate: function(renderID, start, max, data) {
if (renderID == this.currentRenderID) {
var rows = this.body.select("TR#" + data[0]['rowID']);
if (rows.length > 0)
this.rowRenderCallback(rows[0], data[0], false);
}
},
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);
var index = this.dataSource.remove(uid);
// log ("DataTable.remove(" + uid + ")");
if (this.renderedIndex < index &&
(this.renderedIndex + this.renderedCount) > index) {

View File

@@ -1,4 +1,4 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* -*- Mode: js2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
SOGoMailDataSource = Class.create({
@@ -15,7 +15,7 @@ SOGoMailDataSource = Class.create({
this.ajaxGetData = false;
// Constants
this.overflow = 60;
this.overflow = 50; // must be higher or equal to the overflow of the data table class
},
destroy: function() {
@@ -28,7 +28,13 @@ SOGoMailDataSource = Class.create({
invalidate: function(uid) {
this.cache.unset(uid);
var index = this.uids.indexOf(parseInt(uid));
log ("MailDataSource.invalidate(" + uid + ") at index " + index);
// log ("MailDataSource.invalidate(" + uid + ") at index " + index);
return index;
},
remove: function(uid) {
var index = this.invalidate(uid);
if (index >= 0) {
this.uids.splice(index, 1);
}
@@ -36,6 +42,21 @@ SOGoMailDataSource = Class.create({
return index;
},
init: function(uids, headers) {
this.uids = uids;
var keys = headers[0];
for (var i = 1; i < headers.length; i++) {
var header = [];
for (var j = 0; j < keys.length; j++)
header[keys[j]] = headers[i][j];
this.cache.set(header["uid"], header);
}
this.loaded = true;
// log ("MailDataSource.init() " + this.uids.length + " UIDs, " + this.cache.keys().length + " headers");
},
load: function(urlParams) {
var params;
this.loaded = false;
@@ -45,7 +66,7 @@ SOGoMailDataSource = Class.create({
else
params = "";
log ("MailDataSource.load() " + params);
// log ("MailDataSource.load() " + params);
triggerAjaxRequest(this.url + "/uids",
this._loadCallback.bind(this),
null,
@@ -56,9 +77,13 @@ SOGoMailDataSource = Class.create({
_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");
var data = http.responseText.evalJSON(true);
this.init(data.uids, data.headers);
this.loaded = true;
if (this.delayedGetData) {
this.delayedGetData();
this.delayedGetData = false;
}
}
}
else {
@@ -68,10 +93,9 @@ SOGoMailDataSource = Class.create({
getData: function(id, index, count, callbackFunction, delay) {
if (this.loaded == false) {
// UIDs are not yet loaded -- delay the call to the current function
// UIDs are not yet loaded -- delay the call until loading the data is completed.
// 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);
this.delayedGetData = this.getData.bind(this, id, index, count, callbackFunction, delay);
return;
}
if (this.delayed_getData) window.clearTimeout(this.delayed_getData);
@@ -88,16 +112,24 @@ SOGoMailDataSource = Class.create({
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 (count > 1) {
// 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);
else {
// Count is 1; don't fetch more data since the caller is
// SOGoDataTable.invalide() and asks for only one data row.
start = index;
end = index + count;
}
// 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])) {
@@ -115,7 +147,7 @@ SOGoMailDataSource = Class.create({
id: id },
params).delay(0.5);
}
else
else if (callbackFunction)
this._returnData(callbackFunction, id, start, end);
},
@@ -125,7 +157,7 @@ SOGoMailDataSource = Class.create({
this.ajaxGetData.abort();
// log ("MailDataSource._getData() aborted previous AJAX request");
}
// log ("MailDataSource._getData() fetching headers of " + urlParams);
// log ("MailDataSource._getData() fetching headers of " + urlParams);
this.ajaxGetData = triggerAjaxRequest(this.url + "/headers",
this._getRemoteDataCallback.bind(this),
callbackData,
@@ -139,16 +171,20 @@ SOGoMailDataSource = Class.create({
// 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]);
var keys = headers[0];
for (var i = 1; i < headers.length; i++) {
var header = [];
for (var j = 0; j < keys.length; j++)
header[keys[j]] = headers[i][j];
this.cache.set(header["uid"], header);
}
this._returnData(data["callbackFunction"], data["id"], data["start"], data["end"]);
if (data["callbackFunction"])
this._returnData(data["callbackFunction"], data["id"], data["start"], data["end"]);
}
}
else {
alert("SOGoMailDataSource._getRemoteDataCallback Error " + http.status + ": " + http.responseText);
log("SOGoMailDataSource._getRemoteDataCallback Error " + http.status + ": " + http.responseText);
}
},