2016-03-15 03:30:54 +08:00
|
|
|
import React from 'react';
|
2016-03-29 16:41:24 +08:00
|
|
|
import ReactDOM from 'react-dom';
|
2016-03-15 03:30:54 +08:00
|
|
|
import _ from "underscore";
|
|
|
|
import {EventedIFrame} from 'nylas-component-kit';
|
2016-05-03 03:32:51 +08:00
|
|
|
import {Utils, QuotedHTMLTransformer, MessageStore} from 'nylas-exports';
|
2016-03-15 03:30:54 +08:00
|
|
|
import {autolink} from './autolinker';
|
2016-04-01 05:43:44 +08:00
|
|
|
import {autoscaleImages} from './autoscale-images';
|
2017-02-18 03:50:39 +08:00
|
|
|
import {addInlineImageListeners} from './inline-image-listeners';
|
2016-03-15 03:30:54 +08:00
|
|
|
import EmailFrameStylesStore from './email-frame-styles-store';
|
|
|
|
|
|
|
|
export default class EmailFrame extends React.Component {
|
|
|
|
static displayName = 'EmailFrame';
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
content: React.PropTypes.string.isRequired,
|
2016-05-03 06:59:14 +08:00
|
|
|
message: React.PropTypes.object,
|
2016-03-15 03:30:54 +08:00
|
|
|
showQuotedText: React.PropTypes.bool,
|
|
|
|
};
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
this._mounted = true;
|
|
|
|
this._writeContent();
|
|
|
|
this._unlisten = EmailFrameStylesStore.listen(this._writeContent);
|
|
|
|
}
|
|
|
|
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
|
|
return (!Utils.isEqualReact(nextProps, this.props) ||
|
|
|
|
!Utils.isEqualReact(nextState, this.state));
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate() {
|
|
|
|
this._writeContent();
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this._mounted = false;
|
|
|
|
if (this._unlisten) {
|
|
|
|
this._unlisten();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_emailContent = () => {
|
|
|
|
// When showing quoted text, always return the pure content
|
|
|
|
if (this.props.showQuotedText) {
|
|
|
|
return this.props.content;
|
|
|
|
}
|
|
|
|
return QuotedHTMLTransformer.removeQuotedHTML(this.props.content, {
|
|
|
|
keepIfWholeBodyIsQuote: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_writeContent = () => {
|
2016-04-01 03:13:46 +08:00
|
|
|
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
|
|
|
const doc = iframeNode.contentDocument;
|
2016-03-15 03:30:54 +08:00
|
|
|
if (!doc) { return; }
|
|
|
|
doc.open();
|
|
|
|
|
|
|
|
// NOTE: The iframe must have a modern DOCTYPE. The lack of this line
|
|
|
|
// will cause some bizzare non-standards compliant rendering with the
|
|
|
|
// message bodies. This is particularly felt with <table> elements use
|
|
|
|
// the `border-collapse: collapse` css property while setting a
|
|
|
|
// `padding`.
|
|
|
|
doc.write("<!DOCTYPE html>");
|
|
|
|
const styles = EmailFrameStylesStore.styles();
|
|
|
|
if (styles) {
|
|
|
|
doc.write(`<style>${styles}</style>`);
|
|
|
|
}
|
|
|
|
doc.write(`<div id='inbox-html-wrapper'>${this._emailContent()}</div>`);
|
|
|
|
doc.close();
|
|
|
|
|
2016-03-24 10:02:51 +08:00
|
|
|
autolink(doc, {async: true});
|
2016-04-01 05:43:44 +08:00
|
|
|
autoscaleImages(doc);
|
2017-02-18 03:50:39 +08:00
|
|
|
addInlineImageListeners(doc);
|
2016-03-15 03:30:54 +08:00
|
|
|
|
2016-05-03 03:32:51 +08:00
|
|
|
for (const extension of MessageStore.extensions()) {
|
|
|
|
if (!extension.renderedMessageBodyIntoDocument) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
try {
|
2016-05-03 06:59:14 +08:00
|
|
|
extension.renderedMessageBodyIntoDocument({
|
|
|
|
document: doc,
|
|
|
|
message: this.props.message,
|
2016-05-28 06:18:12 +08:00
|
|
|
iframe: iframeNode,
|
2016-05-03 06:59:14 +08:00
|
|
|
});
|
2016-05-03 03:32:51 +08:00
|
|
|
} catch (e) {
|
|
|
|
NylasEnv.reportError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-15 03:30:54 +08:00
|
|
|
// Notify the EventedIFrame that we've replaced it's document (with `open`)
|
|
|
|
// so it can attach event listeners again.
|
2016-04-01 05:43:44 +08:00
|
|
|
this.refs.iframe.didReplaceDocument();
|
2016-04-01 03:13:46 +08:00
|
|
|
this._onMustRecalculateFrameHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
_onMustRecalculateFrameHeight = () => {
|
2016-04-01 05:43:44 +08:00
|
|
|
this.refs.iframe.setHeightQuietly(0);
|
2016-04-01 03:13:46 +08:00
|
|
|
this._lastComputedHeight = 0;
|
2016-03-15 03:30:54 +08:00
|
|
|
this._setFrameHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
_getFrameHeight = (doc) => {
|
2016-05-07 07:44:30 +08:00
|
|
|
let height = 0;
|
|
|
|
|
2016-11-03 06:56:27 +08:00
|
|
|
// If documentElement has a scroll height, prioritize that as height
|
|
|
|
// If not, fall back to body scroll height by setting it to auto
|
2016-11-03 06:33:39 +08:00
|
|
|
if (doc && doc.documentElement && doc.documentElement.scrollHeight > 0) {
|
2016-11-03 06:28:18 +08:00
|
|
|
height = doc.documentElement.scrollHeight;
|
2016-11-03 06:56:27 +08:00
|
|
|
} else if (doc && doc.body) {
|
|
|
|
const style = window.getComputedStyle(doc.body);
|
|
|
|
if (style.height === '0px') {
|
|
|
|
doc.body.style.height = "auto";
|
|
|
|
}
|
2016-11-03 06:28:18 +08:00
|
|
|
height = doc.body.scrollHeight;
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
2016-05-07 07:44:30 +08:00
|
|
|
|
|
|
|
// scrollHeight does not include space required by scrollbar
|
|
|
|
return height + 25;
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_setFrameHeight = () => {
|
|
|
|
if (!this._mounted) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-04-01 03:13:46 +08:00
|
|
|
// Q: What's up with this holder?
|
|
|
|
// A: If you resize the window, or do something to trigger setFrameHeight
|
|
|
|
// on an already-loaded message view, all the heights go to zero for a brief
|
|
|
|
// second while the heights are recomputed. This causes the ScrollRegion to
|
|
|
|
// reset it's scrollTop to ~0 (the new combined heiht of all children).
|
|
|
|
// To prevent this, the holderNode holds the last computed height until
|
|
|
|
// the new height is computed.
|
|
|
|
const holderNode = ReactDOM.findDOMNode(this.refs.iframeHeightHolder);
|
|
|
|
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
|
|
|
const height = this._getFrameHeight(iframeNode.contentDocument);
|
2016-03-15 03:30:54 +08:00
|
|
|
|
|
|
|
// Why 5px? Some emails have elements with a height of 100%, and then put
|
|
|
|
// tracking pixels beneath that. In these scenarios, the scrollHeight of the
|
|
|
|
// message is always <100% + 1px>, which leads us to resize them constantly.
|
|
|
|
// This is a hack, but I'm not sure of a better solution.
|
|
|
|
if (Math.abs(height - this._lastComputedHeight) > 5) {
|
2016-04-01 03:13:46 +08:00
|
|
|
this.refs.iframe.setHeightQuietly(height);
|
2016-04-06 05:27:54 +08:00
|
|
|
holderNode.style.height = `${height}px`;
|
2016-03-15 03:30:54 +08:00
|
|
|
this._lastComputedHeight = height;
|
|
|
|
}
|
|
|
|
|
2016-04-01 03:13:46 +08:00
|
|
|
if (iframeNode.contentDocument.readyState !== 'complete') {
|
2016-05-06 13:30:34 +08:00
|
|
|
_.defer(() => this._setFrameHeight());
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
2016-04-06 05:27:54 +08:00
|
|
|
<div
|
|
|
|
className="iframe-container"
|
|
|
|
ref="iframeHeightHolder"
|
2016-05-07 07:24:40 +08:00
|
|
|
style={{height: this._lastComputedHeight}}
|
|
|
|
>
|
2016-04-01 03:13:46 +08:00
|
|
|
<EventedIFrame
|
|
|
|
ref="iframe"
|
|
|
|
seamless="seamless"
|
|
|
|
searchable
|
2016-04-01 05:43:44 +08:00
|
|
|
onResize={this._onMustRecalculateFrameHeight}
|
2016-04-01 03:13:46 +08:00
|
|
|
/>
|
|
|
|
</div>
|
2016-03-15 03:30:54 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|