Improve webmail editor
- Fixed handling of saving drafts - Fixed handling of message type (HTML/plain) - Added primitive handling of attachmentspull/91/head
parent
32196b56db
commit
533d7110c7
|
@ -1,6 +1,6 @@
|
|||
/* UIxMailAccountActions.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2013 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -126,7 +126,7 @@
|
|||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
accountName, @"accountId",
|
||||
mailboxName, @"mailboxPath",
|
||||
messageName, @"uid", nil];
|
||||
messageName, @"draftId", nil];
|
||||
|
||||
return [self responseWithStatus: 201
|
||||
andString: [data jsonRepresentation]];
|
||||
|
|
|
@ -113,7 +113,7 @@ static NSArray *infoKeys = nil;
|
|||
@"subject", @"to", @"cc", @"bcc",
|
||||
@"from", @"inReplyTo",
|
||||
@"replyTo",
|
||||
@"priority", @"receipt",
|
||||
@"priority", @"receipt", @"isHTML",
|
||||
@"text", nil];
|
||||
}
|
||||
|
||||
|
@ -447,7 +447,7 @@ static NSArray *infoKeys = nil;
|
|||
[self setValuesForKeysWithDictionary:_info];
|
||||
}
|
||||
|
||||
- (NSDictionary *) storeInfo
|
||||
- (NSDictionary *) infoFromRequest
|
||||
{
|
||||
WORequest *request;
|
||||
NSDictionary *params, *filteredParams;
|
||||
|
@ -460,6 +460,7 @@ static NSArray *infoKeys = nil;
|
|||
[self setTo: [filteredParams objectForKey: @"to"]];
|
||||
[self setCc: [filteredParams objectForKey: @"cc"]];
|
||||
[self setBcc: [filteredParams objectForKey: @"bcc"]];
|
||||
[self setIsHTML: [[filteredParams objectForKey: @"isHTML"] boolValue]];
|
||||
[self setText: [filteredParams objectForKey: @"text"]];
|
||||
|
||||
return filteredParams;
|
||||
|
@ -600,46 +601,53 @@ static NSArray *infoKeys = nil;
|
|||
WORequest *request;
|
||||
NSEnumerator *allAttachments;
|
||||
NSDictionary *attrs, *filenames;
|
||||
NGMimeType *mimeType;
|
||||
id httpBody;
|
||||
SOGoDraftObject *co;
|
||||
|
||||
error = nil;
|
||||
request = [context request];
|
||||
|
||||
mimeType = [[request httpRequest] contentType];
|
||||
if ([[mimeType type] isEqualToString: @"multipart"])
|
||||
{
|
||||
httpBody = [[request httpRequest] body];
|
||||
filenames = [self _scanAttachmentFilenamesInRequest: httpBody];
|
||||
httpBody = [[request httpRequest] body];
|
||||
filenames = [self _scanAttachmentFilenamesInRequest: httpBody];
|
||||
|
||||
co = [self clientObject];
|
||||
allAttachments = [filenames objectEnumerator];
|
||||
while ((attrs = [allAttachments nextObject]) && !error)
|
||||
{
|
||||
error = [co saveAttachment: (NSData *) [attrs objectForKey: @"body"]
|
||||
withMetadata: attrs];
|
||||
// Keep the name of the last attachment saved
|
||||
ASSIGN(currentAttachment, [attrs objectForKey: @"filename"]);
|
||||
}
|
||||
co = [self clientObject];
|
||||
allAttachments = [filenames objectEnumerator];
|
||||
while ((attrs = [allAttachments nextObject]) && !error)
|
||||
{
|
||||
error = [co saveAttachment: (NSData *) [attrs objectForKey: @"body"]
|
||||
withMetadata: attrs];
|
||||
// Keep the name of the last attachment saved
|
||||
ASSIGN(currentAttachment, [attrs objectForKey: @"filename"]);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
- (NSException *) _saveFormInfo
|
||||
/**
|
||||
* Save received data to the filesystem, either the attached files or the message itself.
|
||||
*/
|
||||
- (NSException *) _saveRequestInfo
|
||||
{
|
||||
NSDictionary *info;
|
||||
NSException *error;
|
||||
NGMimeType *mimeType;
|
||||
WORequest *request;
|
||||
SOGoDraftObject *co;
|
||||
|
||||
error = nil;
|
||||
request = [context request];
|
||||
mimeType = [[request httpRequest] contentType];
|
||||
|
||||
co = [self clientObject];
|
||||
[co fetchInfo];
|
||||
|
||||
error = [self _saveAttachments];
|
||||
if (!error)
|
||||
if ([[mimeType type] isEqualToString: @"multipart"])
|
||||
{
|
||||
info = [self storeInfo];
|
||||
error = [self _saveAttachments];
|
||||
}
|
||||
else if ([[mimeType subType] isEqualToString: @"json"])
|
||||
{
|
||||
info = [self infoFromRequest];
|
||||
[co setHeaders: info];
|
||||
[co setIsHTML: isHTML];
|
||||
[co setText: (isHTML ? [NSString stringWithFormat: @"<html>%@</html>", text] : text)];;
|
||||
|
@ -720,8 +728,9 @@ static NSArray *infoKeys = nil;
|
|||
[self setSourceFolder: [co sourceFolder]];
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[self from], @"from",
|
||||
[self from], @"from",
|
||||
[self localeCode], @"locale",
|
||||
[NSNumber numberWithBool: [self isHTML]], @"isHTML",
|
||||
text, @"text",
|
||||
nil];
|
||||
if ((value = [self replyTo]))
|
||||
|
@ -748,19 +757,33 @@ static NSArray *infoKeys = nil;
|
|||
{
|
||||
id result;
|
||||
NSArray *attrs;
|
||||
NSDictionary *data;
|
||||
SOGoDraftObject *co;
|
||||
|
||||
co = [self clientObject];
|
||||
[self setIsHTML: [self isHTML]];
|
||||
result = [self _saveFormInfo];
|
||||
|
||||
result = [self _saveRequestInfo];
|
||||
if (!result)
|
||||
{
|
||||
result = [[self clientObject] save];
|
||||
// Save message to IMAP server
|
||||
result = [co save];
|
||||
}
|
||||
if (!result)
|
||||
{
|
||||
// Save new UID to plist
|
||||
[self setSourceUID: [co IMAP4ID]];
|
||||
[co storeInfo];
|
||||
|
||||
// Prepare response
|
||||
attachmentAttrs = nil;
|
||||
attrs = [self attachmentAttrs];
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[self sourceUID], @"uid",
|
||||
attrs, @"lastAttachmentAttrs",
|
||||
nil];
|
||||
result = [self responseWithStatus: 200
|
||||
andString: [attrs jsonRepresentation]];
|
||||
andString: [data jsonRepresentation]];
|
||||
}
|
||||
else
|
||||
result = [self failedToSaveFormResponse: [result reason]];
|
||||
|
@ -834,8 +857,8 @@ static NSArray *infoKeys = nil;
|
|||
|
||||
co = [self clientObject];
|
||||
|
||||
/* first, save form data */
|
||||
error = [self _saveFormInfo];
|
||||
// First, save form data to filesystem
|
||||
error = [self _saveRequestInfo];
|
||||
if (!error)
|
||||
{
|
||||
error = [self validateForSend];
|
||||
|
|
|
@ -295,20 +295,6 @@ static NSString *mailETag = nil;
|
|||
if ((addresses = [addressFormatter dictionariesForArray: [co replyToEnvelopeAddresses]]))
|
||||
[data setObject: addresses forKey: @"reply-to"];
|
||||
|
||||
if ([self mailIsDraft])
|
||||
{
|
||||
SOGoMailAccount *account;
|
||||
SOGoDraftsFolder *folder;
|
||||
SOGoDraftObject *newMail;
|
||||
|
||||
account = [co mailAccountFolder];
|
||||
folder = [account draftsFolderInContext: context];
|
||||
newMail = [folder newDraft];
|
||||
[newMail fetchMailForEditing: co];
|
||||
[newMail storeInfo];
|
||||
[data setObject: [newMail nameInContainer] forKey: @"draftId"];
|
||||
}
|
||||
|
||||
response = [self responseWithStatus: 200
|
||||
andString: [data jsonRepresentation]];
|
||||
|
||||
|
|
|
@ -19,6 +19,18 @@
|
|||
<auto-complete data-source="userFilter($query)"><!-- to --></auto-complete></tags-input></label>
|
||||
<label><var:string label:value="Subject"/>
|
||||
<input type="text" name="subject" ng-model="message.editable.subject"/></label>
|
||||
<label><var:string label:value="Attachments"/>
|
||||
<input type="file"
|
||||
data-nv-file-select="nv-file-select"
|
||||
data-uploader="uploader"/></label>
|
||||
<ul>
|
||||
<li ng-repeat="item in message.editable.attachmentAttrs">
|
||||
<span ng-bind="item.filename"><!-- filename --></span>
|
||||
</li>
|
||||
<li ng-repeat="item in uploader.queue">
|
||||
<span ng-bind="item.file.name"><!-- filename --></span> (<span ng-bind="item.file.progress"><!-- progress --></span>)
|
||||
</li>
|
||||
</ul>
|
||||
<textarea name="content" var:class="editorClass" ng-model="message.editable.text"/>
|
||||
<div class="buttonsToolbar">
|
||||
<span>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"angular-foundation": "~0.3",
|
||||
"angular-recursion": "~1.0",
|
||||
"angular-vs-repeat": ">=1.0",
|
||||
"angular-file-upload": "~1.0",
|
||||
"ng-tags-input": "~2.0",
|
||||
"foundation": ">5.3",
|
||||
"ionic": "1.0.0-beta.11",
|
||||
|
|
|
@ -128,12 +128,39 @@
|
|||
}
|
||||
return mailbox;
|
||||
};
|
||||
mailbox = _find(this.mailboxes);
|
||||
mailbox = _find(this.$mailboxes);
|
||||
|
||||
console.debug(mailbox);
|
||||
console.debug(this.specialMailboxes);
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $getMailboxByPath
|
||||
* @memberof Account.prototype
|
||||
* @desc Recursively find a mailbox using its path
|
||||
* @returns a promise of the HTTP operation
|
||||
*/
|
||||
Account.prototype.$getMailboxByPath = function(path) {
|
||||
var mailbox = null,
|
||||
// Recursive find function
|
||||
_find = function(mailboxes) {
|
||||
var mailbox = _.find(mailboxes, function(o) {
|
||||
return o.path == path;
|
||||
});
|
||||
if (!mailbox) {
|
||||
angular.forEach(mailboxes, function(o) {
|
||||
if (!mailbox && o.children && o.children.length > 0) {
|
||||
mailbox = _find(o.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
return mailbox;
|
||||
};
|
||||
mailbox = _find(this.$mailboxes);
|
||||
|
||||
return mailbox;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $newMessage
|
||||
* @memberof Account.prototype
|
||||
|
@ -146,10 +173,11 @@
|
|||
message;
|
||||
|
||||
// Query account for draft folder and draft UID
|
||||
Account.$$resource.fetch(this.id, 'compose').then(function(data) {
|
||||
message = new Account.$Message(data.accountId, data.mailboxPath, data);
|
||||
Account.$$resource.fetch(this.id.toString(), 'compose').then(function(data) {
|
||||
Account.$log.debug('New message: ' + JSON.stringify(data, undefined, 2));
|
||||
message = new Account.$Message(data.accountId, _this.$getMailboxByPath(data.mailboxPath), data);
|
||||
// Fetch draft initial data
|
||||
Account.$$resource.fetch(message.id, 'edit').then(function(data) {
|
||||
Account.$$resource.fetch(message.$absolutePath({asDraft: true}), 'edit').then(function(data) {
|
||||
Account.$log.debug('New message: ' + JSON.stringify(data, undefined, 2));
|
||||
message.editable = data;
|
||||
deferred.resolve(message);
|
||||
|
|
|
@ -277,7 +277,7 @@
|
|||
// Build map of UID <=> index
|
||||
_this.uidsMap[data.uid] = i;
|
||||
|
||||
msgs.push(new Mailbox.$Message(_this.$account.id, _this.path, data));
|
||||
msgs.push(new Mailbox.$Message(_this.$account.id, _this, data));
|
||||
|
||||
return msgs;
|
||||
}, _this.$messages);
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
* @param {string} mailboxPath - an array of the mailbox path components
|
||||
* @param {object} futureAddressBookData - either an object literal or a promise
|
||||
*/
|
||||
function Message(accountId, mailboxPath, futureMessageData) {
|
||||
function Message(accountId, mailbox, futureMessageData) {
|
||||
this.accountId = accountId;
|
||||
this.mailboxPath = mailboxPath;
|
||||
this.$mailbox = mailbox;
|
||||
// Data is immediately available
|
||||
if (typeof futureMessageData.then !== 'function') {
|
||||
//console.debug(JSON.stringify(futureMessageData, undefined, 2));
|
||||
|
@ -56,7 +56,7 @@
|
|||
Message.prototype.$absolutePath = function(options) {
|
||||
var path;
|
||||
|
||||
path = _.map(this.mailboxPath.split('/'), function(component) {
|
||||
path = _.map(this.$mailbox.path.split('/'), function(component) {
|
||||
return 'folder' + component.asCSSIdentifier();
|
||||
});
|
||||
path.splice(0, 0, this.accountId); // insert account ID
|
||||
|
@ -70,6 +70,25 @@
|
|||
return path.join('/');
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $setUID
|
||||
* @memberof Message.prototype
|
||||
* @desc Change the UID of the message. This happens when saving a draft.
|
||||
* @param {number} uid - the new message UID
|
||||
*/
|
||||
Message.prototype.$setUID = function(uid) {
|
||||
var oldUID = this.uid || -1;
|
||||
|
||||
if (oldUID != uid) {
|
||||
this.uid = uid;
|
||||
this.id = this.$absolutePath();
|
||||
if (oldUID > -1) {
|
||||
this.$mailbox.uidsMap[uid] = this.$mailbox.uidsMap[oldUID];
|
||||
this.$mailbox.uidsMap[oldUID] = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $formatFullAddresses
|
||||
* @memberof Message.prototype
|
||||
|
@ -117,20 +136,22 @@
|
|||
/**
|
||||
* @function $editableContent
|
||||
* @memberof Message.prototype
|
||||
* @desc Fetch the editable message body along with other metadat such as the recipients.
|
||||
* @desc First, fetch the draft ID that corresponds to the temporary draft object on the SOGo server.
|
||||
* Secondly, fetch the editable message body along with other metadata such as the recipients.
|
||||
* @returns the HTML representation of the body
|
||||
*/
|
||||
Message.prototype.$editableContent = function() {
|
||||
var _this = this,
|
||||
deferred = Message.$q.defer();
|
||||
|
||||
Message.$$resource.fetch(this.$absolutePath({asDraft: true}), 'edit').then(function(data) {
|
||||
Message.$log.debug('editable = ' + JSON.stringify(data, undefined, 2));
|
||||
_this.editable = data;
|
||||
deferred.resolve(data.text);
|
||||
}, function(data) {
|
||||
deferred.reject();
|
||||
});
|
||||
Message.$$resource.fetch(this.id, 'edit').then(function(data) {
|
||||
angular.extend(_this, data);
|
||||
Message.$$resource.fetch(_this.$absolutePath({asDraft: true}), 'edit').then(function(data) {
|
||||
Message.$log.debug('editable = ' + JSON.stringify(data, undefined, 2));
|
||||
_this.editable = data;
|
||||
deferred.resolve(data.text);
|
||||
}, deferred.reject);
|
||||
}, deferred.reject);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
@ -156,7 +177,8 @@
|
|||
* @returns a promise of the HTTP operation
|
||||
*/
|
||||
Message.prototype.$save = function() {
|
||||
var data = this.editable;
|
||||
var _this = this,
|
||||
data = this.editable;
|
||||
|
||||
// Flatten recipient addresses
|
||||
_.each(['to', 'cc', 'bcc', 'reply-to'], function(type) {
|
||||
|
@ -166,7 +188,11 @@
|
|||
});
|
||||
Message.$log.debug('save = ' + JSON.stringify(data, undefined, 2));
|
||||
|
||||
return Message.$$resource.save(this.$absolutePath({asDraft: true}), data);
|
||||
return Message.$$resource.save(this.$absolutePath({asDraft: true}), data).then(function(response) {
|
||||
Message.$log.debug('save = ' + JSON.stringify(response, undefined, 2));
|
||||
_this.$setUID(response.uid);
|
||||
_this.$update(); // fetch a new viewable version of the message
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue