Mailspring/internal_packages/message-list/lib/email-frame.jsx
Ben Gotow aa7ef91b0b fix(files): When download mode is “manual” prompt about inline attachments
Summary:
When you have your "Download attachments for new mail" setting set
to "manually", inline images always appear broken with no explanation.

This patch listens for the image load to fail and displays a button which
queues the fetchFile task on click. This seemed like the best approach because
it doesn't slow down the loading of the message with more fstats / lookups.
(Seeing if the file has already been downloaded is an async operation)

Test Plan: No specs atm

Reviewers: evan, juan

Reviewed By: juan

Differential Revision: https://phab.nylas.com/D3313
2016-10-03 11:22:44 -07:00

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 = 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>
);
}
}