diff --git a/internal_packages/message-list/lib/autoscale-images.es6 b/internal_packages/message-list/lib/autoscale-images.es6 new file mode 100644 index 000000000..d0d8549c5 --- /dev/null +++ b/internal_packages/message-list/lib/autoscale-images.es6 @@ -0,0 +1,51 @@ + +function _getDimension(node, dim) { + const raw = node.style[dim] || node[dim]; + if (!raw) { + return [null, '']; + } + const valueRegexp = /(\d*)(.*)/; + const match = valueRegexp.exec(raw); + if (!match) { + return [null, '']; + } + + const value = match[1]; + const units = match[2] || 'px'; + return [value / 1, units]; +} + +function _runOnImageNode(node) { + const [width, widthUnits] = _getDimension(node, 'width'); + const [height, heightUnits] = _getDimension(node, 'height'); + + if (node.style.maxWidth || node.style.maxHeight) { + return; + } + // VW is like %, but always basd on the iframe width, regardless of whether + // a container is position: relative. + // https://web-design-weekly.com/2014/11/18/viewport-units-vw-vh-vmin-vmax/ + if (width && height && (widthUnits === heightUnits)) { + node.style.maxWidth = '100vw'; + node.style.maxHeight = `${100 * height / width}vw`; + } else if (width && !height) { + node.style.maxWidth = '100vw'; + } +} + +export function autoscaleImages(doc) { + // Traverse the new DOM tree and make things that look like links clickable, + // and ensure anything with an href has a title attribute. + const imgTagWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, { + acceptNode: (node) => { + if (node.nodeName === 'IMG') { + return NodeFilter.FILTER_ACCEPT; + } + return NodeFilter.FILTER_SKIP; + }, + }); + + while (imgTagWalker.nextNode()) { + _runOnImageNode(imgTagWalker.currentNode); + } +} diff --git a/internal_packages/message-list/lib/email-frame.jsx b/internal_packages/message-list/lib/email-frame.jsx index d430a34cb..cc20e4880 100644 --- a/internal_packages/message-list/lib/email-frame.jsx +++ b/internal_packages/message-list/lib/email-frame.jsx @@ -4,6 +4,7 @@ import _ from "underscore"; import {EventedIFrame} from 'nylas-component-kit'; import {Utils, QuotedHTMLTransformer} from 'nylas-exports'; import {autolink} from './autolinker'; +import {autoscaleImages} from './autoscale-images'; import EmailFrameStylesStore from './email-frame-styles-store'; export default class EmailFrame extends React.Component { @@ -66,16 +67,16 @@ export default class EmailFrame extends React.Component { doc.close(); autolink(doc, {async: true}); + autoscaleImages(doc); // Notify the EventedIFrame that we've replaced it's document (with `open`) // so it can attach event listeners again. - this.refs.iframe.documentWasReplaced(); + this.refs.iframe.didReplaceDocument(); this._onMustRecalculateFrameHeight(); } _onMustRecalculateFrameHeight = () => { - const iframeNode = ReactDOM.findDOMNode(this.refs.iframe); - iframeNode.height = `0px`; + this.refs.iframe.setHeightQuietly(0); this._lastComputedHeight = 0; this._setFrameHeight(); } @@ -128,7 +129,7 @@ export default class EmailFrame extends React.Component { ref="iframe" seamless="seamless" searchable - onResize={this._onResize} + onResize={this._onMustRecalculateFrameHeight} /> ); diff --git a/internal_packages/message-list/spec/message-item-body-spec.cjsx b/internal_packages/message-list/spec/message-item-body-spec.cjsx index ab5bba6a4..60179495b 100644 --- a/internal_packages/message-list/spec/message-item-body-spec.cjsx +++ b/internal_packages/message-list/spec/message-item-body-spec.cjsx @@ -1,5 +1,6 @@ proxyquire = require 'proxyquire' React = require "react" +ReactDOM = require "react-dom" ReactTestUtils = require('react-addons-test-utils') {Contact, @@ -143,34 +144,17 @@ describe "MessageItem", -> @createComponent() it "should never leave src=cid:// in the message body", -> - body = @component.state.processedBody + body = ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub).props.content expect(body.indexOf('cid')).toEqual(-1) it "should replace cid:// with the FileDownloadStore's path for the file", -> - body = @component.state.processedBody + body = ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub).props.content expect(body.indexOf('alt="A" src="/fake/path-inline.png"')).toEqual(@message.body.indexOf('alt="A"')) it "should not replace cid:// with the FileDownloadStore's path if the download is in progress", -> - body = @component.state.processedBody + body = ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub).props.content expect(body.indexOf('/fake/path-downloading.png')).toEqual(-1) - it "should give images a fixed height when height and width are set as html attributes", -> - @message.body = """ - - - - - - """ - @createComponent() - body = @component.state.processedBody - expect(body).toEqual """ - - - - -""" - describe "showQuotedText", -> it "should be initialized to false", -> @createComponent() diff --git a/src/flux/stores/message-body-processor.es6 b/src/flux/stores/message-body-processor.es6 index c17d203be..fbb30b016 100644 --- a/src/flux/stores/message-body-processor.es6 +++ b/src/flux/stores/message-body-processor.es6 @@ -4,8 +4,6 @@ import MessageUtils from '../models/message-utils'; import MessageStore from './message-store'; import DatabaseStore from './database-store'; -const MessageBodyWidth = 740; - class MessageBodyProcessor { constructor() { this._subscriptions = []; @@ -122,32 +120,6 @@ class MessageBodyProcessor { } } - // Find inline images and give them a calculated CSS height based on - // html width and height, when available. This means nothing changes size - // as the image is loaded, and we can estimate final height correctly. - // Note that MessageBodyWidth must be updated if the UI is changed! - let result = MessageUtils.cidRegex.exec(body); - - while (result !== null) { - const imgstart = body.lastIndexOf('<', result.index); - const imgend = body.indexOf('/>', result.index); - - if ((imgstart !== -1) && (imgend > imgstart)) { - const imgtag = body.substr(imgstart, imgend - imgstart); - const widthMatch = imgtag.match(/width[ ]?=[ ]?['"]?(\d*)['"]?/); - const width = widthMatch ? widthMatch[1] : null; - const heightMatch = imgtag.match(/height[ ]?=[ ]?['"]?(\d*)['"]?/); - const height = heightMatch ? heightMatch[1] : null; - if (width && height) { - const scale = Math.min(1, MessageBodyWidth / width); - const style = ` style="height:${height * scale}px;" ` - body = body.substr(0, imgend) + style + body.substr(imgend); - } - } - - result = MessageUtils.cidRegex.exec(body); - } - return body; }