fix(inline): Use vw for max-width, use DOM rather than regex

This commit is contained in:
Ben Gotow 2016-03-31 14:43:44 -07:00
parent 78681f6ed1
commit 3d8133ff8d
4 changed files with 60 additions and 52 deletions

View file

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

View file

@ -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}
/>
</div>
);

View file

@ -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://<file.contentId> 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://<file.contentId> 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 = """
<img src=\"cid:#{file_inline.contentId}\"/>
<img src='cid:#{file_inline.contentId}'/>
<img src=\"cid:#{file_inline.contentId}\" width="50"/>
<img src=\"cid:#{file_inline.contentId}\" width="50" height="40"/>
<img src=\"cid:#{file_inline.contentId}\" width="1000" height="800"/>
"""
@createComponent()
body = @component.state.processedBody
expect(body).toEqual """<img src="/fake/path-inline.png"/>
<img src='/fake/path-inline.png'/>
<img src="/fake/path-inline.png" width="50"/>
<img src="/fake/path-inline.png" width="50" height="40" style="height:40px;" />
<img src="/fake/path-inline.png" width="1000" height="800" style="height:592px;" />
"""
describe "showQuotedText", ->
it "should be initialized to false", ->
@createComponent()

View file

@ -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;
}