mirror of
https://github.com/moparisthebest/mail
synced 2024-11-23 09:22:23 -05:00
Merge pull request #78 from whiteout-io/dev/WO-409
[WO-409] Render images in html mails
This commit is contained in:
commit
ca0f91af27
@ -338,17 +338,40 @@ define(function(require) {
|
||||
ngModule.directive('frameLoad', function($sce, $timeout) {
|
||||
return function(scope, elm, attrs) {
|
||||
scope.$watch(attrs.frameLoad, function(value) {
|
||||
var html = value;
|
||||
scope.html = undefined;
|
||||
if (value) {
|
||||
$timeout(function() {
|
||||
// wrap in html doc with scrollable html tag, since chrome apps does not scroll by default
|
||||
var prefix = '<!DOCTYPE html><html style="overflow-y: auto"><head></head><body>';
|
||||
var suffix = '</body></html>';
|
||||
// open links in new window, otherwise the sandbox with not open them
|
||||
var clickableHtml = value.replace(/<a /g, '<a target="_blank" ');
|
||||
scope.html = $sce.trustAsHtml(prefix + clickableHtml + suffix);
|
||||
});
|
||||
var iframe = elm[0];
|
||||
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are image tags in the html?
|
||||
var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html);
|
||||
scope.showImageButton = hasImages;
|
||||
|
||||
// inital loading
|
||||
$timeout(function() {
|
||||
scope.html = true;
|
||||
iframe.contentWindow.postMessage({
|
||||
html: html,
|
||||
removeImages: hasImages // avoids doing unnecessary work on the html
|
||||
}, '*');
|
||||
});
|
||||
|
||||
// no need to add a scope function to reload the html if there are no images
|
||||
if (!hasImages) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reload WITH images
|
||||
scope.displayImages = function() {
|
||||
scope.showImageButton = false;
|
||||
iframe.contentWindow.postMessage({
|
||||
html: html,
|
||||
removeImages: false
|
||||
}, '*');
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
@ -639,13 +639,13 @@ define(function(require) {
|
||||
var localMessage = localMessages[0];
|
||||
|
||||
// treat attachment and non-attachment body parts separately:
|
||||
// we need to fetch the content for non-attachment body parts (encrypted, signed, text, html)
|
||||
// we need to fetch the content for non-attachment body parts (encrypted, signed, text, html, resources referenced from the html)
|
||||
// but we spare the effort and fetch attachment content later upon explicit user request.
|
||||
var contentParts = localMessage.bodyParts.filter(function(bodyPart) {
|
||||
return bodyPart.type !== "attachment";
|
||||
return bodyPart.type !== "attachment" || (bodyPart.type === "attachment" && bodyPart.id);
|
||||
});
|
||||
var attachmentParts = localMessage.bodyParts.filter(function(bodyPart) {
|
||||
return bodyPart.type === "attachment";
|
||||
return bodyPart.type === "attachment" && !bodyPart.id;
|
||||
});
|
||||
|
||||
// do we need to fetch content from the imap server?
|
||||
@ -747,10 +747,12 @@ define(function(require) {
|
||||
message.attachments = filterBodyParts(root, 'attachment');
|
||||
message.body = body;
|
||||
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
|
||||
inlineExternalImages(message);
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
|
||||
function done(err) {
|
||||
message.loadingBody = false;
|
||||
callback(err, err ? undefined : message);
|
||||
@ -852,6 +854,7 @@ define(function(require) {
|
||||
// remove the pgp-signature from the attachments
|
||||
return attmt.mimeType === "application/pgp-signature";
|
||||
});
|
||||
inlineExternalImages(message);
|
||||
|
||||
message.decrypted = true;
|
||||
|
||||
@ -1150,7 +1153,7 @@ define(function(require) {
|
||||
|
||||
/**
|
||||
* Updates the folder information from imap (if we're online). Adds/removes folders in account.folders,
|
||||
* if we added/removed folder in IMAP. If we have an uninitialized folder that lacks folder.messages,
|
||||
* if we added/removed folder in IMAP. If we have an uninitialized folder that lacks folder.messages,
|
||||
* all the locally available messages are loaded from memory.
|
||||
*
|
||||
* @param {Function} callback Invoked when the folders are up to date
|
||||
@ -1472,5 +1475,36 @@ define(function(require) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that looks through the HTML content for <img src="cid:..."> and
|
||||
* inlines the images linked internally. Manipulates message.html as a side-effect.
|
||||
* If no attachment matching the internal reference is found, or constructing a data
|
||||
* uri fails, just remove the source.
|
||||
*
|
||||
* @param {Object} message DTO
|
||||
*/
|
||||
function inlineExternalImages(message) {
|
||||
message.html = message.html.replace(/(<img[^>]+\bsrc=['"])cid:([^'">]+)(['"])/ig, function(match, prefix, src, suffix) {
|
||||
var localSource = '',
|
||||
payload = '';
|
||||
|
||||
var internalReference = _.findWhere(message.attachments, {
|
||||
id: src
|
||||
});
|
||||
|
||||
if (internalReference) {
|
||||
for (var i = 0; i < internalReference.content.byteLength; i++) {
|
||||
payload += String.fromCharCode(internalReference.content[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
localSource = 'data:application/octet-stream;base64,' + btoa(payload); // try to replace the source
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return prefix + localSource + suffix;
|
||||
});
|
||||
}
|
||||
|
||||
return EmailDAO;
|
||||
});
|
@ -32,5 +32,9 @@
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"pages": ["sandbox.html"],
|
||||
"content_security_policy": "sandbox allow-popups allow-scripts; default-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src *"
|
||||
}
|
||||
}
|
11
src/sandbox.html
Normal file
11
src/sandbox.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="overflow-y: auto">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="sandbox.js"></script>
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
</html>
|
20
src/sandbox.js
Normal file
20
src/sandbox.js
Normal file
@ -0,0 +1,20 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// set listener for event from main window
|
||||
window.addEventListener('message', function(e) {
|
||||
var html = e.data.html;
|
||||
|
||||
// make links open in a new window
|
||||
html = html.replace(/<a /g, '<a target="_blank" ');
|
||||
|
||||
// remove sources where necessary
|
||||
if (e.data.removeImages) {
|
||||
html = html.replace(/(<img[^>]+\b)src=['"][^'">]+['"]/ig, function(match, prefix) {
|
||||
return prefix;
|
||||
});
|
||||
}
|
||||
|
||||
document.body.innerHTML = html;
|
||||
}, false);
|
||||
})();
|
@ -114,6 +114,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.showImages {
|
||||
flex-shrink: 0;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
flex-grow: 1;
|
||||
margin-top: 1.75em;
|
||||
|
@ -53,8 +53,12 @@
|
||||
</div><!--/.working-->
|
||||
</div><!--/.working-wrapper-->
|
||||
|
||||
<div class="showImages" ng-show="html && showImageButton">
|
||||
<a href='#' ng-click="displayImages(); $event.preventDefault()">Display images</a>
|
||||
</div>
|
||||
|
||||
<!-- Render html body in sandboxed iframe -->
|
||||
<iframe ng-show="html" sandbox="allow-popups" srcdoc="{{html}}"
|
||||
<iframe ng-show="html" sandbox="allow-popups allow-scripts" src="sandbox.html"
|
||||
frame-load="state.mailList.selected.html">
|
||||
</iframe>
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user