2016-08-31 10:23:01 +08:00
|
|
|
Utils = require './utils'
|
|
|
|
SimpleMDE = require 'simplemde'
|
2016-09-02 08:25:39 +08:00
|
|
|
{React, ReactDOM, QuotedHTMLTransformer} = require 'nylas-exports'
|
2016-08-31 10:23:01 +08:00
|
|
|
|
|
|
|
# Keep a file-scope variable containing the contents of the markdown stylesheet.
|
|
|
|
# This will be embedded in the markdown preview iFrame, as well as the email body.
|
|
|
|
# The stylesheet is loaded when a preview component is first mounted.
|
|
|
|
markdownStylesheet = null
|
|
|
|
|
2016-09-02 08:25:39 +08:00
|
|
|
splitContents = (contents) ->
|
|
|
|
quoteStart = contents.search(/(<div class="gmail_quote|<signature)/i)
|
|
|
|
if quoteStart > 0
|
|
|
|
return [contents.substr(0, quoteStart), contents.substr(quoteStart)]
|
|
|
|
return [contents, ""]
|
|
|
|
|
2016-08-31 10:23:01 +08:00
|
|
|
class MarkdownEditor extends React.Component
|
|
|
|
@displayName: 'MarkdownEditor'
|
|
|
|
|
2016-09-02 07:44:53 +08:00
|
|
|
@containerRequired: false
|
|
|
|
|
|
|
|
@contextTypes:
|
|
|
|
parentTabGroup: React.PropTypes.object,
|
|
|
|
|
2016-08-31 10:23:01 +08:00
|
|
|
@propTypes:
|
|
|
|
body: React.PropTypes.string.isRequired,
|
|
|
|
onBodyChanged: React.PropTypes.func.isRequired,
|
|
|
|
|
|
|
|
componentDidMount: =>
|
|
|
|
@mde = new SimpleMDE(
|
2016-09-02 10:43:10 +08:00
|
|
|
inputStyle: 'contenteditable'
|
|
|
|
element: ReactDOM.findDOMNode(@refs.container),
|
2016-08-31 10:23:01 +08:00
|
|
|
hideIcons: ['fullscreen', 'side-by-side']
|
|
|
|
showIcons: ['code', 'table']
|
2016-09-02 10:43:10 +08:00
|
|
|
spellChecker: false,
|
2016-08-31 10:23:01 +08:00
|
|
|
)
|
2016-09-02 08:25:39 +08:00
|
|
|
@mde.codemirror.on("change", @_onBodyChanged)
|
|
|
|
@mde.codemirror.on("keydown", @_onKeyDown)
|
|
|
|
@setCurrentBodyInDOM()
|
|
|
|
|
|
|
|
componentDidUpdate: (prevProps) =>
|
|
|
|
wasEmpty = prevProps.body.length is 0
|
|
|
|
|
|
|
|
if @props.body isnt prevProps.body and @props.body isnt @currentBodyInDOM()
|
|
|
|
@setCurrentBodyInDOM()
|
2016-08-31 10:23:01 +08:00
|
|
|
|
2016-09-02 08:25:39 +08:00
|
|
|
if wasEmpty
|
|
|
|
@mde.codemirror.execCommand('goDocEnd')
|
2016-08-31 10:23:01 +08:00
|
|
|
|
|
|
|
focus: =>
|
|
|
|
@mde.codemirror.focus()
|
|
|
|
|
|
|
|
focusAbsoluteEnd: =>
|
|
|
|
@focus()
|
2016-09-02 07:52:55 +08:00
|
|
|
@mde.codemirror.execCommand('goDocEnd')
|
2016-08-31 10:23:01 +08:00
|
|
|
|
2016-09-02 08:25:39 +08:00
|
|
|
setCurrentBodyInDOM: =>
|
|
|
|
[editable, uneditable] = splitContents(@props.body)
|
|
|
|
|
|
|
|
uneditableEl = ReactDOM.findDOMNode(@refs.uneditable)
|
|
|
|
uneditableEl.innerHTML = uneditable
|
|
|
|
uneditableNoticeEl = ReactDOM.findDOMNode(@refs.uneditableNotice)
|
|
|
|
if Utils.getTextFromHtml(uneditable).length > 0
|
|
|
|
uneditableNoticeEl.style.display = 'block'
|
|
|
|
else
|
|
|
|
uneditableNoticeEl.style.display = 'none'
|
|
|
|
|
|
|
|
@mde.value(Utils.getTextFromHtml(editable))
|
|
|
|
|
|
|
|
currentBodyInDOM: =>
|
|
|
|
uneditableEl = ReactDOM.findDOMNode(@refs.uneditable)
|
|
|
|
return @mde.value() + uneditableEl.innerHTML
|
|
|
|
|
2016-08-31 10:23:01 +08:00
|
|
|
getCurrentSelection: ->
|
|
|
|
|
|
|
|
getPreviousSelection: ->
|
|
|
|
|
|
|
|
setSelection: ->
|
2016-09-02 10:43:10 +08:00
|
|
|
container = ReactDOM.findDOMNode(@refs.container)
|
2016-09-02 09:27:39 +08:00
|
|
|
sel = document.getSelection()
|
2016-09-02 10:43:10 +08:00
|
|
|
sel.setBaseAndExtent(container, 0, container, 0)
|
2016-08-31 10:23:01 +08:00
|
|
|
|
|
|
|
_onDOMMutated: ->
|
|
|
|
|
|
|
|
_onBodyChanged: =>
|
|
|
|
setImmediate =>
|
2016-09-02 08:25:39 +08:00
|
|
|
value = @currentBodyInDOM()
|
|
|
|
@props.onBodyChanged({target: {value}})
|
2016-08-31 10:23:01 +08:00
|
|
|
|
2016-09-02 07:44:53 +08:00
|
|
|
_onKeyDown: (codemirror, e)=>
|
|
|
|
if e.key is 'Tab' and e.shiftKey is true
|
|
|
|
position = codemirror.cursorCoords(true, 'local')
|
|
|
|
isAtBeginning = position.top <= 5 and position.left <= 5
|
|
|
|
if isAtBeginning
|
|
|
|
# TODO i'm /really/ sorry
|
|
|
|
# Subject is at position 2 within the tab group, the focused text area
|
|
|
|
# in this component is at position 17, so that's why we shift back 15
|
|
|
|
# positions.
|
|
|
|
# This will break if the dom elements between here and the subject ever
|
|
|
|
# change
|
|
|
|
@context.parentTabGroup.shiftFocus(-15)
|
|
|
|
e.preventDefault()
|
|
|
|
e.codemirrorIgnore = true
|
|
|
|
|
2016-08-31 10:23:01 +08:00
|
|
|
render: ->
|
|
|
|
# TODO sorry
|
|
|
|
# Add style tag to disable incompatible plugins
|
2016-09-02 07:44:53 +08:00
|
|
|
<div tabIndex="1" className="markdown-editor" onFocus={@focus}>
|
2016-08-31 10:23:01 +08:00
|
|
|
<style>
|
|
|
|
{".btn-mail-merge { display:none; }"}
|
|
|
|
{".btn-emoji { display:none; }"}
|
|
|
|
{".btn-templates { display:none; }"}
|
|
|
|
{".btn-scheduler { display:none; }"}
|
|
|
|
{".btn-translate { display:none; }"}
|
|
|
|
</style>
|
2016-09-02 10:43:10 +08:00
|
|
|
<div
|
|
|
|
ref="container"
|
2016-08-31 10:23:01 +08:00
|
|
|
className="editing-region"
|
|
|
|
/>
|
2016-09-02 08:25:39 +08:00
|
|
|
<div ref="uneditableNotice" style={{display: 'none'}} className="uneditable-notice">
|
|
|
|
The markdown editor does not support editing signatures or quoted text. Content below will be included in your message.
|
|
|
|
</div>
|
|
|
|
<div ref="uneditable"></div>
|
2016-08-31 10:23:01 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
module.exports = MarkdownEditor
|