mirror of
https://github.com/moparisthebest/mail
synced 2024-11-26 19:02:20 -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) {
|
ngModule.directive('frameLoad', function($sce, $timeout) {
|
||||||
return function(scope, elm, attrs) {
|
return function(scope, elm, attrs) {
|
||||||
scope.$watch(attrs.frameLoad, function(value) {
|
scope.$watch(attrs.frameLoad, function(value) {
|
||||||
|
var html = value;
|
||||||
scope.html = undefined;
|
scope.html = undefined;
|
||||||
if (value) {
|
var iframe = elm[0];
|
||||||
$timeout(function() {
|
|
||||||
// wrap in html doc with scrollable html tag, since chrome apps does not scroll by default
|
if (!html) {
|
||||||
var prefix = '<!DOCTYPE html><html style="overflow-y: auto"><head></head><body>';
|
return;
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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];
|
var localMessage = localMessages[0];
|
||||||
|
|
||||||
// treat attachment and non-attachment body parts separately:
|
// 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.
|
// but we spare the effort and fetch attachment content later upon explicit user request.
|
||||||
var contentParts = localMessage.bodyParts.filter(function(bodyPart) {
|
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) {
|
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?
|
// do we need to fetch content from the imap server?
|
||||||
@ -747,10 +747,12 @@ define(function(require) {
|
|||||||
message.attachments = filterBodyParts(root, 'attachment');
|
message.attachments = filterBodyParts(root, 'attachment');
|
||||||
message.body = body;
|
message.body = body;
|
||||||
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
|
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
|
||||||
|
inlineExternalImages(message);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function done(err) {
|
function done(err) {
|
||||||
message.loadingBody = false;
|
message.loadingBody = false;
|
||||||
callback(err, err ? undefined : message);
|
callback(err, err ? undefined : message);
|
||||||
@ -852,6 +854,7 @@ define(function(require) {
|
|||||||
// remove the pgp-signature from the attachments
|
// remove the pgp-signature from the attachments
|
||||||
return attmt.mimeType === "application/pgp-signature";
|
return attmt.mimeType === "application/pgp-signature";
|
||||||
});
|
});
|
||||||
|
inlineExternalImages(message);
|
||||||
|
|
||||||
message.decrypted = true;
|
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,
|
* 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.
|
* all the locally available messages are loaded from memory.
|
||||||
*
|
*
|
||||||
* @param {Function} callback Invoked when the folders are up to date
|
* @param {Function} callback Invoked when the folders are up to date
|
||||||
@ -1472,5 +1475,36 @@ define(function(require) {
|
|||||||
return result;
|
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;
|
return EmailDAO;
|
||||||
});
|
});
|
@ -32,5 +32,9 @@
|
|||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.js"]
|
"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 {
|
iframe {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin-top: 1.75em;
|
margin-top: 1.75em;
|
||||||
|
@ -53,8 +53,12 @@
|
|||||||
</div><!--/.working-->
|
</div><!--/.working-->
|
||||||
</div><!--/.working-wrapper-->
|
</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 -->
|
<!-- 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">
|
frame-load="state.mailList.selected.html">
|
||||||
</iframe>
|
</iframe>
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user