mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-04-02 10:33:29 +08:00
fix(inline): Use vw
for max-width, use DOM rather than regex
This commit is contained in:
parent
78681f6ed1
commit
3d8133ff8d
4 changed files with 60 additions and 52 deletions
internal_packages/message-list
src/flux/stores
51
internal_packages/message-list/lib/autoscale-images.es6
Normal file
51
internal_packages/message-list/lib/autoscale-images.es6
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import _ from "underscore";
|
||||||
import {EventedIFrame} from 'nylas-component-kit';
|
import {EventedIFrame} from 'nylas-component-kit';
|
||||||
import {Utils, QuotedHTMLTransformer} from 'nylas-exports';
|
import {Utils, QuotedHTMLTransformer} from 'nylas-exports';
|
||||||
import {autolink} from './autolinker';
|
import {autolink} from './autolinker';
|
||||||
|
import {autoscaleImages} from './autoscale-images';
|
||||||
import EmailFrameStylesStore from './email-frame-styles-store';
|
import EmailFrameStylesStore from './email-frame-styles-store';
|
||||||
|
|
||||||
export default class EmailFrame extends React.Component {
|
export default class EmailFrame extends React.Component {
|
||||||
|
@ -66,16 +67,16 @@ export default class EmailFrame extends React.Component {
|
||||||
doc.close();
|
doc.close();
|
||||||
|
|
||||||
autolink(doc, {async: true});
|
autolink(doc, {async: true});
|
||||||
|
autoscaleImages(doc);
|
||||||
|
|
||||||
// Notify the EventedIFrame that we've replaced it's document (with `open`)
|
// Notify the EventedIFrame that we've replaced it's document (with `open`)
|
||||||
// so it can attach event listeners again.
|
// so it can attach event listeners again.
|
||||||
this.refs.iframe.documentWasReplaced();
|
this.refs.iframe.didReplaceDocument();
|
||||||
this._onMustRecalculateFrameHeight();
|
this._onMustRecalculateFrameHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMustRecalculateFrameHeight = () => {
|
_onMustRecalculateFrameHeight = () => {
|
||||||
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
this.refs.iframe.setHeightQuietly(0);
|
||||||
iframeNode.height = `0px`;
|
|
||||||
this._lastComputedHeight = 0;
|
this._lastComputedHeight = 0;
|
||||||
this._setFrameHeight();
|
this._setFrameHeight();
|
||||||
}
|
}
|
||||||
|
@ -128,7 +129,7 @@ export default class EmailFrame extends React.Component {
|
||||||
ref="iframe"
|
ref="iframe"
|
||||||
seamless="seamless"
|
seamless="seamless"
|
||||||
searchable
|
searchable
|
||||||
onResize={this._onResize}
|
onResize={this._onMustRecalculateFrameHeight}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
proxyquire = require 'proxyquire'
|
proxyquire = require 'proxyquire'
|
||||||
React = require "react"
|
React = require "react"
|
||||||
|
ReactDOM = require "react-dom"
|
||||||
ReactTestUtils = require('react-addons-test-utils')
|
ReactTestUtils = require('react-addons-test-utils')
|
||||||
|
|
||||||
{Contact,
|
{Contact,
|
||||||
|
@ -143,34 +144,17 @@ describe "MessageItem", ->
|
||||||
@createComponent()
|
@createComponent()
|
||||||
|
|
||||||
it "should never leave src=cid:// in the message body", ->
|
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)
|
expect(body.indexOf('cid')).toEqual(-1)
|
||||||
|
|
||||||
it "should replace cid://<file.contentId> with the FileDownloadStore's path for the file", ->
|
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"'))
|
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", ->
|
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)
|
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", ->
|
describe "showQuotedText", ->
|
||||||
it "should be initialized to false", ->
|
it "should be initialized to false", ->
|
||||||
@createComponent()
|
@createComponent()
|
||||||
|
|
|
@ -4,8 +4,6 @@ import MessageUtils from '../models/message-utils';
|
||||||
import MessageStore from './message-store';
|
import MessageStore from './message-store';
|
||||||
import DatabaseStore from './database-store';
|
import DatabaseStore from './database-store';
|
||||||
|
|
||||||
const MessageBodyWidth = 740;
|
|
||||||
|
|
||||||
class MessageBodyProcessor {
|
class MessageBodyProcessor {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._subscriptions = [];
|
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;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue