mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-23 08:46:07 +08:00
d996273b7b
There are cases where the documentElement has a scrollHeight of 0, even if the body has a larger scrollHeight. Before, we were always using the documentElement if it was present. Now, we use the maximum scrollHeight.
172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
import React from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import _ from "underscore";
|
|
import {EventedIFrame} from 'nylas-component-kit';
|
|
import {Utils, QuotedHTMLTransformer, MessageStore} from 'nylas-exports';
|
|
import {autolink} from './autolinker';
|
|
import {autoscaleImages} from './autoscale-images';
|
|
import {addInlineDownloadPrompts} from './inline-download-prompts';
|
|
import EmailFrameStylesStore from './email-frame-styles-store';
|
|
|
|
export default class EmailFrame extends React.Component {
|
|
static displayName = 'EmailFrame';
|
|
|
|
static propTypes = {
|
|
content: React.PropTypes.string.isRequired,
|
|
message: React.PropTypes.object,
|
|
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 = () => {
|
|
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
|
const doc = iframeNode.contentDocument;
|
|
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();
|
|
|
|
autolink(doc, {async: true});
|
|
autoscaleImages(doc);
|
|
addInlineDownloadPrompts(doc);
|
|
|
|
for (const extension of MessageStore.extensions()) {
|
|
if (!extension.renderedMessageBodyIntoDocument) {
|
|
continue;
|
|
}
|
|
try {
|
|
extension.renderedMessageBodyIntoDocument({
|
|
document: doc,
|
|
message: this.props.message,
|
|
iframe: iframeNode,
|
|
});
|
|
} catch (e) {
|
|
NylasEnv.reportError(e);
|
|
}
|
|
}
|
|
|
|
// Notify the EventedIFrame that we've replaced it's document (with `open`)
|
|
// so it can attach event listeners again.
|
|
this.refs.iframe.didReplaceDocument();
|
|
this._onMustRecalculateFrameHeight();
|
|
}
|
|
|
|
_onMustRecalculateFrameHeight = () => {
|
|
this.refs.iframe.setHeightQuietly(0);
|
|
this._lastComputedHeight = 0;
|
|
this._setFrameHeight();
|
|
}
|
|
|
|
_getFrameHeight = (doc) => {
|
|
let height = 0;
|
|
|
|
if (doc && doc.body) {
|
|
// Why reset the height? body.scrollHeight will always be 0 if the height
|
|
// of the body is dependent on the iframe height e.g. if height ===
|
|
// 100% in inline styles or an email stylesheet
|
|
const style = window.getComputedStyle(doc.body)
|
|
if (style.height === '0px') {
|
|
doc.body.style.height = "auto"
|
|
}
|
|
height = doc.body.scrollHeight;
|
|
}
|
|
|
|
if (doc && doc.documentElement) {
|
|
height = Math.max(height, doc.documentElement.scrollHeight);
|
|
}
|
|
|
|
// scrollHeight does not include space required by scrollbar
|
|
return height + 25;
|
|
}
|
|
|
|
_setFrameHeight = () => {
|
|
if (!this._mounted) {
|
|
return;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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) {
|
|
this.refs.iframe.setHeightQuietly(height);
|
|
holderNode.style.height = `${height}px`;
|
|
this._lastComputedHeight = height;
|
|
}
|
|
|
|
if (iframeNode.contentDocument.readyState !== 'complete') {
|
|
_.defer(() => this._setFrameHeight());
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div
|
|
className="iframe-container"
|
|
ref="iframeHeightHolder"
|
|
style={{height: this._lastComputedHeight}}
|
|
>
|
|
<EventedIFrame
|
|
ref="iframe"
|
|
seamless="seamless"
|
|
searchable
|
|
onResize={this._onMustRecalculateFrameHeight}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
}
|