React = require 'react' _ = require "underscore-plus" EmailFixingStyles = """ <style> /* Styles for an email iframe */ @font-face { font-family: 'Proxima Nova Regular'; src: url('fonts/Proxima-Nova/regular.woff') format('woff'); font-weight: normal; font-style: normal; } @font-face { font-family: 'Proxima Nova Bold'; src: url('fonts/Proxima-Nova/bold.woff') format('woff'); font-weight: normal; font-style: normal; } /* Clean Message Display */ html, body { font-family: "Proxima Nova Regular", sans-serif; font-size: 16px; line-height: 1.35; color: #333; border: 0; margin: 0; padding: 0; -webkit-text-size-adjust: auto; word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; } ::selection { color: #f1f1f1; background: #009ec4; } strong, b, .bold { font-family: "Proxima Nova Bold", sans-serif; font-weight: normal; font-style: normal; letter-spacing: 0.3px; } body { padding: 0; margin: auto; max-width: 840px; overflow: hidden; } a { color: #1486D4; } a:hover { color: #1069a5; } a:visited { color: #1069a5; } a img { border-bottom: 0; } body.heightDetermined { overflow-y: hidden; } div,pre { max-width: 100%; } img { max-width: 100%; height: auto; border: 0; } .gmail_extra, .gmail_quote, blockquote { display:none; } .show-quoted-text .gmail_extra, .show-quoted-text .gmail_quote, .show-quoted-text blockquote { display:inherit; } </style> """ module.exports = EmailFrame = React.createClass displayName: 'EmailFrame' render: -> <iframe seamless="seamless" /> componentDidMount: -> @_writeContent() @_setFrameHeight() componentDidUpdate: -> @_writeContent() @_setFrameHeight() componentWillUnmount: -> doc = @getDOMNode().contentDocument doc?.removeEventListener?("click") doc?.removeEventListener?("keydown") @_delegateMouseEvents(doc, "removeEventListener") shouldComponentUpdate: (newProps, newState) -> # Turns out, React is not able to tell if props.children has changed, # so whenever the message list updates each email-frame is repopulated, # often with the exact same content. To avoid unnecessary calls to # _writeContent, we do a quick check for deep equality. !_.isEqual(newProps, @props) _writeContent: -> wrapperClass = if @props.showQuotedText then "show-quoted-text" else "" doc = @getDOMNode().contentDocument doc.open() doc.write(EmailFixingStyles) doc.write("<div id='inbox-html-wrapper' class='#{wrapperClass}'>#{@_emailContent()}</div>") doc.close() doc.addEventListener "click", @_onClick doc.addEventListener "keydown", @_onKeydown @_delegateMouseEvents(doc, "addEventListener") _setFrameHeight: -> _.defer => return unless @isMounted() # Sometimes the _defer will fire after React has tried to clean up # the DOM, at which point @getDOMNode will fail. # # If this happens, try to call this again to catch React next time. try domNode = @getDOMNode() catch return doc = domNode.contentDocument height = doc.getElementById("inbox-html-wrapper").scrollHeight if domNode.height != "#{height}px" domNode.height = "#{height}px" unless domNode?.contentDocument?.readyState is 'complete' @_setFrameHeight() _emailContent: -> email = @props.children # When showing quoted text, always return the pure content return email if @props.showQuotedText # Split the email into lines and remove lines that begin with > or > lines = email.split(/(\n|<br>)/) # Remove lines that are newlines - we'll add them back in when we join. # We had to break them out because we want to preserve <br> elements. lines = _.reject lines, (line) -> line == '\n' for ii in [lines.length-1..0] by -1 if (lines[ii].substr(0, 1) == '>' || lines[ii].substr(0, 4) == '\>' || lines[ii].substr(0, 10) == '<p>\> On') lines.splice(ii+1,1) if lines[ii+1] == '<br>' # Remove following newline if it's a <br> lines.splice(ii,1) # Return remaining compacted email body lines.join('\n') _onClick: (e) -> e.preventDefault() e.stopPropagation() target = e.target # This lets us detect when we click an element inside of an <a> tag while target? and (target isnt document) and (target isnt window) if target.getAttribute('href')? atom.windowEventHandler.openLink target: target target = null else target = target.parentElement _onKeydown: (event) -> @getDOMNode().dispatchEvent(new KeyboardEvent(event.type, event)) _delegateMouseEvents: (doc, method="addEventListener") -> for type in ["mousemove", "mouseup", "mousedown", "mouseover", "mouseout"] doc?[method]?(type, @_onMouseEvent) _onMouseEvent: (event) -> {top, left} = @getDOMNode().getBoundingClientRect() new_coords = {clientX: event.clientX + left, clientY: event.clientY + top} new_event = _.extend {}, event, new_coords @getDOMNode().dispatchEvent(new MouseEvent(event.type, new_event))