Add links to download one or all attachments

Also removed the contextual menu over file attachments and changed the
label color when moving over the file attachments.
pull/17/head
Francis Lachapelle 2013-12-20 15:37:01 -05:00
parent dc21c723f6
commit 5f369f201d
11 changed files with 172 additions and 74 deletions

1
NEWS
View File

@ -6,6 +6,7 @@ New features
using SOGoCalendarDefaultReminder using SOGoCalendarDefaultReminder
- select multiple files to attach to a message or drag'n'drop files onto the - select multiple files to attach to a message or drag'n'drop files onto the
mail editor; will also now display progress of uploads mail editor; will also now display progress of uploads
- new popup menu to download all attachments of a mail
Enhancements Enhancements
- we now automatically convert <img src=data...> into file attachments - we now automatically convert <img src=data...> into file attachments

View File

@ -97,11 +97,11 @@
"Reply-To" = "Reply-To"; "Reply-To" = "Reply-To";
"Add address" = "Add address"; "Add address" = "Add address";
"Attachments:" = "Attachments:";
"Open" = "Open"; "Open" = "Open";
"Select All" = "Select All"; "Select All" = "Select All";
"Attach Web Page..." = "Attach Web Page..."; "Attach Web Page..." = "Attach Web Page...";
"Attach File(s)..." = "Attach File(s)..."; "file" = "file";
"files" = "files";
"to" = "To"; "to" = "To";
"cc" = "Cc"; "cc" = "Cc";

View File

@ -163,6 +163,11 @@ static NSArray *infoKeys = nil;
return item; return item;
} }
- (NSString *) uid
{
return [[self clientObject] nameInContainer];
}
- (NSArray *) priorityClasses - (NSArray *) priorityClasses
{ {
static NSArray *priorities = nil; static NSArray *priorities = nil;
@ -382,16 +387,16 @@ static NSArray *infoKeys = nil;
ASSIGN (attachment, newAttachment); ASSIGN (attachment, newAttachment);
} }
- (NSFormatter *) sizeFormatter
{
return [UIxMailSizeFormatter sharedMailSizeFormatter];
}
- (NSDictionary *) attachment - (NSDictionary *) attachment
{ {
return attachment; return attachment;
} }
- (NSFormatter *) sizeFormatter
{
return [UIxMailSizeFormatter sharedMailSizeFormatter];
}
/* from addresses */ /* from addresses */
- (NSArray *) fromEMails - (NSArray *) fromEMails
@ -672,11 +677,6 @@ static NSArray *infoKeys = nil;
return [[self attachmentAttrs] count] > 0 ? YES : NO; return [[self attachmentAttrs] count] > 0 ? YES : NO;
} }
- (NSString *) uid
{
return [[self clientObject] nameInContainer];
}
- (id) defaultAction - (id) defaultAction
{ {
SOGoDraftObject *co; SOGoDraftObject *co;

View File

@ -47,11 +47,12 @@
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h> #import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserManager.h> #import <SOGo/SOGoUserManager.h>
#import <SOGoUI/UIxComponent.h>
#import <Mailer/SOGoMailObject.h> #import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoMailAccount.h> #import <Mailer/SOGoMailAccount.h>
#import <Mailer/SOGoMailFolder.h> #import <Mailer/SOGoMailFolder.h>
#import <SOGoUI/UIxComponent.h>
#import <MailPartViewers/UIxMailRenderingContext.h> // cyclic #import <MailPartViewers/UIxMailRenderingContext.h> // cyclic
#import <MailPartViewers/UIxMailSizeFormatter.h>
#import "WOContext+UIxMailer.h" #import "WOContext+UIxMailer.h"
@ -60,6 +61,8 @@
id currentAddress; id currentAddress;
NSString *shouldAskReceipt; NSString *shouldAskReceipt;
NSString *matchingIdentityEMail; NSString *matchingIdentityEMail;
NSDictionary *attachment;
NSArray *attachmentAttrs;
} }
@end @end
@ -80,6 +83,8 @@ static NSString *mailETag = nil;
- (void) dealloc - (void) dealloc
{ {
[matchingIdentityEMail release]; [matchingIdentityEMail release];
[attachment release];
[attachmentAttrs release];
[super dealloc]; [super dealloc];
} }
@ -111,6 +116,16 @@ static NSString *mailETag = nil;
[self messageSubject]]; [self messageSubject]];
} }
- (void) setAttachment: (NSDictionary *) newAttachment
{
ASSIGN (attachment, newAttachment);
}
- (NSDictionary *) attachment
{
return attachment;
}
/* links (DUP to UIxMailPartViewer!) */ /* links (DUP to UIxMailPartViewer!) */
- (NSString *) linkToEnvelopeAddress: (NGImap4EnvelopeAddress *) _address - (NSString *) linkToEnvelopeAddress: (NGImap4EnvelopeAddress *) _address
@ -146,6 +161,36 @@ static NSString *mailETag = nil;
return [[[self clientObject] replyToEnvelopeAddresses] count] > 0 ? YES : NO; return [[[self clientObject] replyToEnvelopeAddresses] count] > 0 ? YES : NO;
} }
/* attachment helper */
- (NSArray *) attachmentAttrs
{
if (!attachmentAttrs)
{
ASSIGN (attachmentAttrs, [[self clientObject] fetchFileAttachmentKeys]);
}
return attachmentAttrs;
}
- (BOOL) hasAttachments
{
return [[self attachmentAttrs] count] > 0 ? YES : NO;
}
- (NSFormatter *) sizeFormatter
{
return [UIxMailSizeFormatter sharedMailSizeFormatter];
}
- (NSString *) attachmentsText
{
if ([[self attachmentAttrs] count] > 1)
return [self labelForKey: @"files"];
else
return [self labelForKey: @"file"];
}
/* viewers */ /* viewers */
- (id) contentViewerComponent - (id) contentViewerComponent

View File

@ -211,12 +211,6 @@
</ul> </ul>
</div> </div>
<div class="menu" id="attachmentMenu">
<ul>
<li id="save_attachment"><var:string label:value="Save Attachment"/></li>
</ul>
</div>
<div id="leftPanel"> <div id="leftPanel">
<div class="titlediv"><var:string label:value="Folders" /></div> <div class="titlediv"><var:string label:value="Folders" /></div>
<div id="folderTreeContent"><!-- space --></div> <div id="folderTreeContent"><!-- space --></div>

View File

@ -9,13 +9,11 @@
const:jsFiles="SOGoAutoCompletion.js" const:jsFiles="SOGoAutoCompletion.js"
const:userDefaultsKeys="SOGoMailDisplayRemoteInlineImages" const:userDefaultsKeys="SOGoMailDisplayRemoteInlineImages"
const:popup="YES"> const:popup="YES">
<script type="text/javascript"> <script type="text/javascript">
var messageName = '<var:string value="clientObject.relativeImap4Name"/>'; var messageName = '<var:string value="clientObject.relativeImap4Name"/>';
var mailboxName = '/<var:string value="clientObject.container.container.nameInContainer"/>/<var:string value="clientObject.container.nameInContainer"/>'; var mailboxName = '/<var:string value="clientObject.container.container.nameInContainer"/>/<var:string value="clientObject.container.nameInContainer"/>';
</script> </script>
<span id="messageContent"> <var:component className="UIxMailView" />
<var:component className="UIxMailView" />
</span>
<div class="menu" id="addressMenu"> <div class="menu" id="addressMenu">
<ul> <ul>
<li id="add_to_addressbook"><var:string label:value="Add to Address Book..."/></li> <li id="add_to_addressbook"><var:string label:value="Add to Address Book..."/></li>
@ -28,9 +26,4 @@
<li id="save_image"><var:string label:value="Save Image"/></li> <li id="save_image"><var:string label:value="Save Image"/></li>
</ul> </ul>
</div> </div>
<div class="menu" id="attachmentMenu"> </var:component>
<ul>
<li id="save_attachment"><var:string label:value="Save Attachment"/></li>
</ul>
</div>
</var:component>

View File

@ -5,14 +5,25 @@
xmlns:uix="OGo:uix" xmlns:uix="OGo:uix"
xmlns:rsrc="OGo:url" xmlns:rsrc="OGo:url"
xmlns:label="OGo:label"> xmlns:label="OGo:label">
<var:if condition="clientObject.hasAttachment">
<div class="menu" const:id="attachmentsMenu">
<ul>
<li><var:string label:value="Save all"/></li>
<li class="separator"><!-- separator --></li
><var:foreach list="attachmentAttrs" item="attachment">
<li var:data-url="attachment.urlAsAttachment"><img rsrc:src="attachment.gif"/><var:string value="attachment.filename"
/> <span class="muted">(<var:string value="attachment.size" formatter="sizeFormatter"/>)</span></li>
</var:foreach
></ul>
</div>
</var:if>
<span id="messageContent">
<input type="hidden" const:id="shouldAskReceipt" var:value="shouldAskReceipt"/> <input type="hidden" const:id="shouldAskReceipt" var:value="shouldAskReceipt"/>
<var:if var:condition="mailIsDraft" <var:if var:condition="mailIsDraft"
><input const:name="editDraftButton" const:id="editDraftButton" ><a href="#" const:id="editDraftButton" class="button"><span><var:string label:value="Edit Draft..."/></span></a
type="button" class="button" label:value="Edit Draft..." ></var:if>
/></var:if> <a href="#" const:name="loadImagesButton" const:id="loadImagesButton"
<input const:name="loadImagesButton" const:id="loadImagesButton" class="button"><span><var:string label:value="Load Images"/></span></a>
type="button" class="button" label:value="Load Images"
/>
<table class="mailer_fieldtable"> <table class="mailer_fieldtable">
<tr class="mailer_fieldrow"> <tr class="mailer_fieldrow">
<td class="mailer_fieldname" ><var:string label:value="Subject"/>:</td> <td class="mailer_fieldname" ><var:string label:value="Subject"/>:</td>
@ -93,11 +104,19 @@
</td> </td>
</tr> </tr>
</var:if> </var:if>
<var:if condition="clientObject.hasAttachment">
<tr class="mailer_fieldrow">
<td class="mailer_fieldname"><img rsrc:src="title_attachment_14x14.png"/></td>
<td class="mailer_fieldvalue">
<a href="#" const:id="attachmentsHref"><var:string value="attachmentAttrs.count"/> <var:string value="attachmentsText"/></a>
</td>
</tr>
</var:if>
</table> </table>
<div class="mailer_mailcontent"> <div class="mailer_mailcontent">
<var:component value="contentViewerComponent" <var:component value="contentViewerComponent"
bodyInfo="clientObject.bodyStructure" /> bodyInfo="clientObject.bodyStructure" />
</div> </div>
</span>
</container> </container>

View File

@ -374,21 +374,21 @@ TR.mailer_listcell_regular TD A
} }
/* mail viewer */ /* mail viewer */
INPUT#editDraftButton #editDraftButton
{ {
position: absolute; position: absolute;
top: 2.5em; top: 2.5em;
right: 1em; right: 1em;
} }
INPUT#loadImagesButton #loadImagesButton
{ {
position: absolute; position: absolute;
top: 2.5em; top: 2.5em;
right: 1em; right: 1em;
} }
.popup INPUT#loadImagesButton .popup #loadImagesButton
{ {
top: 9.0em; top: 9.0em;
right: 1em; right: 1em;
@ -434,7 +434,8 @@ DIV.mailer_mailcontent TABLE
} }
/* collapsable header */ /* collapsable header */
TD.mailer_fieldname IMG TD.mailer_fieldname IMG.collapse,
TD.mailer_fieldname IMG.expand
{ cursor: pointer; { cursor: pointer;
padding-right: 5px; } padding-right: 5px; }
TD.mailer_fieldvalue SPAN.collapse TD.mailer_fieldvalue SPAN.collapse
@ -577,7 +578,7 @@ DIV.linked_attachment_meta
{ {
color: #444444; color: #444444;
border-width: 0; border-width: 0;
padding: 2px; padding: 2px 4px;
} }
TABLE.linked_attachment_meta TABLE.linked_attachment_meta
@ -585,6 +586,25 @@ TABLE.linked_attachment_meta
color: #444444; color: #444444;
} }
.linked_attachment_body a:hover
{
text-decoration: none;
}
.linked_attachment_body a:hover .linked_attachment_meta
{
background-color: #9ABCD8;
color: #fff;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.linked_attachment_body a:hover .muted
{
color: #fff !important;
}
DIV.linked_attachment_body HR DIV.linked_attachment_body HR
{ {
border: 0px; border: 0px;

View File

@ -1357,13 +1357,27 @@ function configureLinksInMessage() {
anchor.observe("contextmenu", onEmailAddressClick); anchor.observe("contextmenu", onEmailAddressClick);
anchor.writeAttribute("moz-do-not-send", false); anchor.writeAttribute("moz-do-not-send", false);
} }
else else if (!anchor.id)
anchor.observe("click", onMessageAnchorClick); anchor.observe("click", onMessageAnchorClick);
} }
var attachments = messageDiv.select ("DIV.linked_attachment_body"); var attachmentsMenu = $("attachmentsMenu");
for (var i = 0; i < attachments.length; i++) if (attachmentsMenu) {
$(attachments[i]).observe("contextmenu", onAttachmentClick); var options = attachmentsMenu.select("li");
var callbacks = [];
for (var i = 0; i < options.length; i++) {
if (options[i].className == 'separator')
callbacks.push(null);
else
callbacks.push(saveAttachment);
}
initMenu(attachmentsMenu, callbacks);
$("attachmentsHref").on("click", function (event) {
popupMenu(event, 'attachmentsMenu', this);
preventDefault(event);
return false;
});
}
var images = messageDiv.select("IMG.mailer_imagecontent"); var images = messageDiv.select("IMG.mailer_imagecontent");
for (var i = 0; i < images.length; i++) for (var i = 0; i < images.length; i++)
@ -1371,12 +1385,11 @@ function configureLinksInMessage() {
var editDraftButton = $("editDraftButton"); var editDraftButton = $("editDraftButton");
if (editDraftButton) if (editDraftButton)
editDraftButton.observe("click", editDraftButton.on("click", onMessageEditDraft);
onMessageEditDraft.bindAsEventListener(editDraftButton));
var loadImagesButton = $("loadImagesButton"); var loadImagesButton = $("loadImagesButton");
if (loadImagesButton) if (loadImagesButton)
$(loadImagesButton).observe("click", onMessageLoadImages); loadImagesButton.on("click", onMessageLoadImages);
configureiCalLinksInMessage(); configureiCalLinksInMessage();
} }
@ -1562,12 +1575,13 @@ function onMessageContentMenu(event) {
} }
function onMessageEditDraft(event) { function onMessageEditDraft(event) {
Event.stop(event);
return openMessageWindowsForSelection("edit", true); return openMessageWindowsForSelection("edit", true);
} }
function onMessageLoadImages(event) { function onMessageLoadImages(event) {
loadRemoteImages();
Event.stop(event); Event.stop(event);
loadRemoteImages();
} }
function loadRemoteImages() { function loadRemoteImages() {
@ -1613,12 +1627,6 @@ function onImageClick(event) {
return false; return false;
} }
function onAttachmentClick (event) {
popupMenu (event, 'attachmentMenu', this);
preventDefault (event);
return false;
}
function handleReturnReceipt() { function handleReturnReceipt() {
var input = $("shouldAskReceipt"); var input = $("shouldAskReceipt");
if (input) { if (input) {
@ -1773,13 +1781,29 @@ function saveImage(event) {
window.location.href = urlAsAttachment; window.location.href = urlAsAttachment;
} }
function saveAttachment(event) { /* Download a file using a temporary iframe that we delete once the download is started */
var div = document.menuTarget; function download(url) {
var link = div.select ("a").first (); var form = createElement('form', null, 'hidden', { action: url, method: 'GET'});
var url = link.getAttribute("href"); $(document.body).appendChild(form);
var urlAsAttachment = url.replace(/(\/[^\/]*)$/,"/asAttachment$1"); var div = AIM.submit(form);
form.submit();
setTimeout(function () {
form.remove();
div.remove();
}, 2000);
}
window.location.href = urlAsAttachment; function saveAttachment(event) {
var url = $(this).readAttribute('data-url');
if (url) {
download(url);
}
else {
$(this).up('ul').select('li[data-url]').each(function (item) {
url = $(item).readAttribute('data-url');
download(url);
});
}
} }
/* contacts */ /* contacts */
@ -2820,7 +2844,6 @@ function getMenus() {
saveAs, null, null, saveAs, null, null,
onMenuDeleteMessage ], onMenuDeleteMessage ],
imageMenu: [ saveImage ], imageMenu: [ saveImage ],
attachmentMenu: [ saveAttachment ],
messageContentMenu: [ onMenuReplyToSender, messageContentMenu: [ onMenuReplyToSender,
onMenuReplyToAll, onMenuReplyToAll,
onMenuForwardMessage, onMenuForwardMessage,

View File

@ -251,7 +251,8 @@ DIV.contactSelector DIV.contactList
padding-bottom: .15em; padding-bottom: .15em;
margin: 0px; margin: 0px;
width: auto; width: auto;
white-space: nowrap; } white-space: nowrap;
cursor: pointer; }
.menu LI.disabled, .menu LI.disabled,
.popuMenu LI.disabled, .popuMenu LI.disabled,
@ -283,10 +284,11 @@ UL.choiceMenu LI._chosen:hover
{ list-style-image: url("menu-check-hover.gif"); } { list-style-image: url("menu-check-hover.gif"); }
.menu LI:hover, .menu LI:hover,
.menu LI:hover .muted,
.menu LI.selected, .menu LI.selected,
.menu LI.submenu-selected .menu LI.submenu-selected
{ background-color: #9ABCD8; { background-color: #9ABCD8;
color: #fff; } color: #fff !important; }
.menu LI.disabled:hover .menu LI.disabled:hover
{ background-color: inherit; } { background-color: inherit; }

View File

@ -1925,7 +1925,7 @@ AIM = {
d.innerHTML = '<iframe class="hidden" src="about:blank" id="' d.innerHTML = '<iframe class="hidden" src="about:blank" id="'
+ n + '" name="' + n + '" onload="AIM.loaded(\'' + n + '\')"></iframe>'; + n + '" name="' + n + '" onload="AIM.loaded(\'' + n + '\')"></iframe>';
document.body.appendChild(d); document.body.appendChild(d);
var i = $(n); // TODO: useful? var i = $(n);
if (c && typeof(c.onComplete) == 'function') if (c && typeof(c.onComplete) == 'function')
i.onComplete = c.onComplete; i.onComplete = c.onComplete;
return n; return n;
@ -1936,27 +1936,28 @@ AIM = {
}, },
submit: function(f, c) { submit: function(f, c) {
AIM.form(f, AIM.frame(c)); var id = AIM.frame(c);
AIM.form(f, id);
if (c && typeof(c.onStart) == 'function') if (c && typeof(c.onStart) == 'function')
return c.onStart(); return c.onStart();
else else
return true; return $(id);
}, },
loaded: function(id) { loaded: function(id) {
var i = $(id); var i = $(id);
var d;
if (i.contentDocument) { if (i.contentDocument) {
var d = i.contentDocument; d = i.contentDocument;
} }
else if (i.contentWindow) { else if (i.contentWindow) {
var d = i.contentWindow.document; d = i.contentWindow.document;
} }
else { else {
var d = window.frames[id].document; d = window.frames[id].document;
} }
if (d.location.href == "about:blank") if (d.location.href == "about:blank")
return; return;
if (typeof(i.onComplete) == 'function') { if (typeof(i.onComplete) == 'function') {
i.onComplete(Element.allTextContent(d.body)); i.onComplete(Element.allTextContent(d.body));
} }