_ = require 'underscore-plus'
React = require 'react'
classNames = require 'classnames'
sanitizeHtml = require 'sanitize-html'
{Utils, DraftStore} = require 'inbox-exports'
FloatingToolbar = require './floating-toolbar'

linkUUID = 0
genLinkId = -> linkUUID += 1; return linkUUID

class ContenteditableComponent extends React.Component
  @displayName = "Contenteditable"
  @propTypes =
    html: React.PropTypes.string
    style: React.PropTypes.object
    tabIndex: React.PropTypes.string
    onChange: React.PropTypes.func.isRequired
    mode: React.PropTypes.object
    onChangeMode: React.PropTypes.func
    initialSelectionSnapshot: React.PropTypes.object

  constructor: (@props) ->
    @state =
      toolbarTop: 0
      toolbarMode: "buttons"
      toolbarLeft: 0
      toolbarPos: "above"
      editAreaWidth: 9999 # This will get set on first selection
      toolbarVisible: false

  componentDidMount: =>
    @_editableNode().addEventListener('contextmenu', @_onShowContextualMenu)
    @_setupSelectionListeners()
    @_setupLinkHoverListeners()
    @_setupGlobalMouseListener()

    @_refreshToolbarState = _.debounce(@__refreshToolbarState, 100)

    @_disposable = atom.commands.add '.contenteditable-container *', {
      'core:focus-next': (event) =>
        editableNode = @_editableNode()
        range = @_getRangeInScope()
        for extension in DraftStore.extensions()
          extension.onFocusNext(editableNode, range, event) if extension.onFocusNext
      'core:focus-previous': (event) =>
        editableNode = @_editableNode()
        range = @_getRangeInScope()
        for extension in DraftStore.extensions()
          extension.onFocusPrevious(editableNode, range, event) if extension.onFocusPrevious
    }

  componentWillUnmount: =>
    @_editableNode().removeEventListener('contextmenu', @_onShowContextualMenu)
    @_teardownSelectionListeners()
    @_teardownLinkHoverListeners()
    @_teardownGlobalMouseListener()
    @_disposable.dispose()

  componentWillReceiveProps: (nextProps) =>
    if nextProps.initialSelectionSnapshot?
      @_setSelectionSnapshot(nextProps.initialSelectionSnapshot)
      @_refreshToolbarState()

  componentWillUpdate: =>
    @_teardownLinkHoverListeners()

  componentDidUpdate: =>
    @_setupLinkHoverListeners()
    @_restoreSelection()

  render: =>
    <div className="contenteditable-container">
      <FloatingToolbar ref="floatingToolbar"
                       top={@state.toolbarTop}
                       left={@state.toolbarLeft}
                       pos={@state.toolbarPos}
                       visible={@state.toolbarVisible}
                       tabIndex={@props.tabIndex}
                       onSaveUrl={@_onSaveUrl}
                       initialMode={@state.toolbarMode}
                       onMouseEnter={@_onTooltipMouseEnter}
                       onMouseLeave={@_onTooltipMouseLeave}
                       linkToModify={@state.linkToModify}
                       contentPadding={@CONTENT_PADDING}
                       editAreaWidth={@state.editAreaWidth} />
      <div id="contenteditable"
           ref="contenteditable"
           contentEditable
           tabIndex={@props.tabIndex}
           style={@props.style ? {}}
           onBlur={@_onBlur}
           onPaste={@_onPaste}
           onInput={@_onInput}
           dangerouslySetInnerHTML={@_dangerouslySetInnerHTML()}></div>
      <a className={@_quotedTextClasses()} onClick={@_onToggleQuotedText}></a>
    </div>

  focus: =>
    @_editableNode().focus()

  _onInput: (event) =>
    @_dragging = false
    editableNode = @_editableNode()
    editableNode.normalize()

    for extension in DraftStore.extensions()
      extension.onInput(editableNode, event) if extension.onInput

    @_setNewSelectionState()

    html = @_unapplyHTMLDisplayFilters(editableNode.innerHTML)
    @props.onChange(target: {value: html})

  _onBlur: (event) =>
    # The delay here is necessary to see if the blur was caused by us
    # navigating to the toolbar and focusing on the set-url input.
    _.delay =>
      @_hideToolbar()
    , 50

  _editableNode: =>
    React.findDOMNode(@refs.contenteditable)

  _getAllLinks: =>
    Array.prototype.slice.call(@_editableNode().querySelectorAll("*[href]"))

  _dangerouslySetInnerHTML: =>
    __html: @_applyHTMLDisplayFilters(@props.html)

  _applyHTMLDisplayFilters: (html) =>
    html = @_removeQuotedTextFromHTML(html) unless @props.mode?.showQuotedText
    return html

  _unapplyHTMLDisplayFilters: (html) =>
    html = @_addQuotedTextToHTML(html) unless @props.mode?.showQuotedText
    return html




  ######### SELECTION MANAGEMENT ##########
  #
  # Saving and restoring a selection is difficult with React.
  #
  # React only handles Input and Textarea elements:
  # https://github.com/facebook/react/blob/master/src/browser/ui/ReactInputSelection.js
  # This is because they expose a very convenient `selectionStart` and
  # `selectionEnd` integer.
  #
  # Contenteditable regions are trickier. They require the more
  # sophisticated `Range` and `Selection` APIs.
  #
  # Range docs:
  # http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
  #
  # Selection API docs:
  # http://www.w3.org/TR/selection-api/#dfn-range
  #
  # A Contenteditable region can have arbitrary html inside of it. This
  # means that a selection start point can be some node (the `anchorNode`)
  # and its end point can be a completely different node (the `focusNode`)
  #
  # When React re-renders, all of the DOM nodes may change. They may
  # look exactly the same, but have different object references.
  #
  # This means that your old references to `anchorNode` and `focusNode`
  # may be bad and no longer in scope or painted.
  #
  # In order to restore the selection properly we need to re-find the
  # equivalent `anchorNode` and `focusNode`. Luckily we can use the
  # `isEqualNode` method to get a shallow comparison of the nodes.
  #
  # Unfortunately it's possible for `isEqualNode` to match more than one
  # node since two nodes may look very similar.
  #
  # To fix this we need to keep track of the original indices to determine
  # which node is most likely the matching one.

  # http://www.w3.org/TR/selection-api/#selectstart-event
  _setupSelectionListeners: =>
    @_onSelectionChange = => @_setNewSelectionState()
    document.addEventListener "selectionchange", @_onSelectionChange

  _teardownSelectionListeners: =>
    document.removeEventListener("selectionchange", @_onSelectionChange)

  getCurrentSelection: => _.clone(@_selection ? {})
  getPreviousSelection: => _.clone(@_previousSelection ? {})

  _getRangeInScope: =>
    selection = document.getSelection()
    return null if not @_selectionInScope(selection)
    try
      range = selection.getRangeAt(0)
    catch
      return
    range

  # Every time the cursor changes we need to preserve its location and
  # state.
  #
  # We can't use React's `state` variable because cursor position is not
  # naturally supported in the virtual DOM.
  #
  # We also need to make sure that node references are cloned so they
  # don't change out from underneath us.
  #
  # We also need to keep references to the previous selection state in
  # order for undo/redo to work properly.
  #
  # We need to be sure to deeply `cloneNode`. This is because sometimes
  # our anchorNodes are divs with nested <br> tags. If we don't do a deep
  # clone then when `isEqualNode` is run it will erroneously return false
  # and our selection restoration will fail
  _setNewSelectionState: =>
    selection = document.getSelection()
    return if @_checkSameSelection(selection)

    range = @_getRangeInScope()
    return unless range

    @_previousSelection = @_selection

    if selection.isCollapsed
      selectionRect = null
    else
      selectionRect = range.getBoundingClientRect()

    @_selection =
      startNode: range.startContainer?.cloneNode(true)
      startOffset: range.startOffset
      startNodeIndex: @_getNodeIndex(range.startContainer)
      endNode: range.endContainer?.cloneNode(true)
      endOffset: range.endOffset
      endNodeIndex: @_getNodeIndex(range.endContainer)
      isCollapsed: selection.isCollapsed
      selectionRect: selectionRect
      atEndOfContent: @_atEndOfContent(range, selection)

    @_refreshToolbarState()
    return @_selection

  _atEndOfContent: (range, selection) =>
    if selection.isCollapsed
      lastChild = @_editableNode().lastElementChild
      return false unless lastChild
      inLastChild = lastChild.contains(range.endContainer)
      isLastChild = lastChild is range.endContainer
      if range.endContainer?.length
        atEndIndex = range.endOffset is range.endContainer.length
      else
        atEndIndex = range.endOffset is 0

      return (inLastChild or isLastChild) and atEndIndex
    else return false

  _setSelectionSnapshot: (selection) =>
    @_previousSelection = @_selection
    @_selection = selection


  # We use global listeners to determine whether or not dragging is
  # happening. This is because dragging may stop outside the scope of
  # this element. Note that the `dragstart` and `dragend` events don't
  # detect text selection. They are for drag & drop.
  _setupGlobalMouseListener: =>
    @__onMouseDown = _.bind(@_onMouseDown, @)
    @__onMouseMove = _.bind(@_onMouseMove, @)
    @__onMouseUp = _.bind(@_onMouseUp, @)
    window.addEventListener("mousedown", @__onMouseDown)
    window.addEventListener("mouseup", @__onMouseUp)

  _teardownGlobalMouseListener: =>
    window.removeEventListener("mousedown", @__onMouseDown)
    window.removeEventListener("mouseup", @__onMouseUp)

  _onShowContextualMenu: (event) =>
    @_hideToolbar()
    event.preventDefault()

    selection = document.getSelection()
    range = selection.getRangeAt(0)
    text = range.toString()

    remote = require('remote')
    clipboard = require('clipboard')
    Menu = remote.require('menu')
    MenuItem = remote.require('menu-item')
    spellchecker = require('spellchecker')

    apply = (newtext) =>
      range.deleteContents()
      node = document.createTextNode(newtext)
      range.insertNode(node)
      range.selectNode(node)
      selection.removeAllRanges()
      selection.addRange(range)

    cut = =>
      clipboard.writeText(text)
      apply('')

    copy = =>
      clipboard.writeText(text)

    paste = =>
      apply(clipboard.readText())

    menu = new Menu()

    if spellchecker.isMisspelled(text)
      corrections = spellchecker.getCorrectionsForMisspelling(text)
      if corrections.length > 0
        corrections.forEach (correction) ->
          menu.append(new MenuItem({ label: correction, click:( -> apply(correction))}))
        menu.append(new MenuItem({ type: 'separator' }))
        menu.append(new MenuItem({ label: 'Learn Spelling', click:( -> spellchecker.add(text))}))
        menu.append(new MenuItem({ type: 'separator' }))

    menu.append(new MenuItem({ label: 'Cut', click:cut}))
    menu.append(new MenuItem({ label: 'Copy', click:copy}))
    menu.append(new MenuItem({ label: 'Paste', click:paste}))
    menu.popup(remote.getCurrentWindow())
      
  _onMouseDown: (event) =>
    @_mouseDownEvent = event
    @_mouseHasMoved = false
    window.addEventListener("mousemove", @__onMouseMove)

    # We can't use the native double click event because that only fires
    # on the second up-stroke
    if Date.now() - (@_lastMouseDown ? 0) < 250
      @_onDoubleDown(event)
      @_lastMouseDown = 0 # to prevent triple down
    else
      @_lastMouseDown = Date.now()

  _onDoubleDown: (event) =>
    editable = @_editableNode()
    return unless editable?
    if editable is event.target or editable.contains(event.target)
      @_doubleDown = true

  _onMouseMove: (event) =>
    if not @_mouseHasMoved
      @_onDragStart(@_mouseDownEvent)
      @_mouseHasMoved = true

  _onMouseUp: (event) =>
    window.removeEventListener("mousemove", @__onMouseMove)

    if @_doubleDown
      @_doubleDown = false
      @_refreshToolbarState()

    if @_mouseHasMoved
      @_mouseHasMoved = false
      @_onDragEnd(event)

    editableNode = @_editableNode()
    selection = document.getSelection()
    return event unless @_selectionInScope(selection)

    range = @_getRangeInScope()
    if range
      try
        for extension in DraftStore.extensions()
          extension.onMouseUp(editableNode, range, event) if extension.onMouseUp
      catch e
        console.log('DraftStore extension raised an error: '+e.toString())

    event

  _onDragStart: (event) =>
    editable = @_editableNode()
    return unless editable?
    if editable is event.target or editable.contains(event.target)
      @_dragging = true

  _onDragEnd: (event) =>
    if @_dragging
      @_dragging = false
      @_refreshToolbarState()
    return event

  # We manually restore the selection on every render and when we need to
  # move the selection around manually.
  #
  # force - when set to true it will not care whether or not the selection
  #         is already in the box. Normally we only restore when the
  #         contenteditable is in focus
  # collapse - Can either be "end" or "start". When we reset the
  #            selection, we'll collapse the range into a single caret
  #            position
  _restoreSelection: ({force, collapse}={}) =>
    return if @_dragging
    return if not @_selection?
    return if document.activeElement isnt @_editableNode() and not force
    return if not @_selection.startNode? or not @_selection.endNode?
    range = document.createRange()

    startNode = @_findSimilarNodes(@_selection.startNode)[@_selection.startNodeIndex]
    endNode = @_findSimilarNodes(@_selection.endNode)[@_selection.endNodeIndex]
    return if not startNode? or not endNode?


    # We want to not care about the selection direction.
    # Selecting from index 1 to index 5 is the same as selecting from
    # index 5 to index 1. However, this only works if the nodes we are
    # grabbing the index from are the same. If they are different, then we
    # can no longer make this gaurantee and have to grab their listed
    # offsets.
    if startNode is endNode
      startIndex = Math.min(@_selection.startOffset ? 0, @_selection.endOffset ? 0)
      startIndex = Math.min(startIndex, startNode.length)

      endIndex = Math.max(@_selection.startOffset ? 0, @_selection.endOffset ? 0)
      endIndex = Math.min(endIndex, endNode.length)
    else
      startIndex = @_selection.startOffset
      endIndex = @_selection.endOffset

    if collapse is "end"
      startNode = endNode
      startIndex = endIndex
    else if collapse is "start"
      endNode = startNode
      endIndex = endIndex


    try
      range.setStart(startNode, startIndex)
      range.setEnd(endNode, endIndex)
    catch
      return

    selection = document.getSelection()
    @_teardownSelectionListeners()
    selection.removeAllRanges()
    selection.addRange(range)
    @_setupSelectionListeners()

  # We need to break each node apart and cache since the `selection`
  # object will mutate underneath us.
  _checkSameSelection: (newSelection) =>
    return true if not newSelection?
    return false if not @_selection
    return false if not newSelection.anchorNode? or not newSelection.focusNode?

    anchorIndex = @_getNodeIndex(newSelection.anchorNode)
    focusIndex = @_getNodeIndex(newSelection.focusNode)

    anchorEqual = newSelection.anchorNode.isEqualNode @_selection.startNode
    anchorIndexEqual = anchorIndex is @_selection.startNodeIndex
    focusEqual = newSelection.focusNode.isEqualNode @_selection.endNode
    focusIndexEqual = focusIndex is @_selection.endNodeIndex
    if not anchorEqual and not focusEqual
      # This means the newSelection is the same, but just from the opposite
      # direction. We don't care in this case, so check the reciprocal as
      # well.
      anchorEqual = newSelection.anchorNode.isEqualNode @_selection.endNode
      anchorIndexEqual = anchorIndex is @_selection.endNodeIndex
      focusEqual = newSelection.focusNode.isEqualNode @_selection.startNode
      focusIndexEqual = focusIndex is @_selection.startndNodeIndex

    anchorOffsetEqual = newSelection.anchorOffset == @_selection.startOffset
    focusOffsetEqual = newSelection.focusOffset == @_selection.endOffset
    if not anchorOffsetEqual and not focusOffsetEqual
      # This means the newSelection is the same, but just from the opposite
      # direction. We don't care in this case, so check the reciprocal as
      # well.
      anchorOffsetEqual = newSelection.anchorOffset == @_selection.focusOffset
      focusOffsetEqual = newSelection.focusOffset == @_selection.anchorOffset

    if (anchorEqual and
        anchorIndexEqual and
        anchorOffsetEqual and
        focusEqual and
        focusIndexEqual and
        focusOffsetEqual)
      return true
    else
      return false

  _getNodeIndex: (nodeToFind) =>
    @_findSimilarNodes(nodeToFind).indexOf nodeToFind

  _findSimilarNodes: (nodeToFind) =>
    nodeList = []
    treeWalker = document.createTreeWalker @_editableNode()
    while treeWalker.nextNode()
      if treeWalker.currentNode.isEqualNode nodeToFind
        nodeList.push(treeWalker.currentNode)

    return nodeList

  _isEqualNode: =>

  _linksInside: (selection) =>
    return _.filter @_getAllLinks(), (link) ->
      selection.containsNode(link, true)




  ####### TOOLBAR ON SELECTION #########

  # We want the toolbar's state to be declaratively defined from other
  # states.
  #
  # There are a variety of conditions that the toolbar should display:
  # 1. When you're hovering over a link
  # 2. When you've arrow-keyed the cursor into a link
  # 3. When you have selected a range of text.
  __refreshToolbarState: =>
    return if @_dragging or (@_doubleDown and not @state.toolbarVisible)
    if @_linkHoveringOver
      url = @_linkHoveringOver.getAttribute('href')
      rect = @_linkHoveringOver.getBoundingClientRect()
      [left, top, editAreaWidth, toolbarPos] = @_getToolbarPos(rect)
      @setState
        toolbarVisible: true
        toolbarMode: "edit-link"
        toolbarTop: top
        toolbarLeft: left
        toolbarPos: toolbarPos
        linkToModify: @_linkHoveringOver
        editAreaWidth: editAreaWidth
    else
      if not @_selection? or @_selection.isCollapsed
        @_hideToolbar()
      else
        if @_selection.isCollapsed
          linkRect = linksInside[0].getBoundingClientRect()
          [left, top, editAreaWidth, toolbarPos] = @_getToolbarPos(linkRect)
        else
          selectionRect = @_selection.selectionRect
          [left, top, editAreaWidth, toolbarPos] = @_getToolbarPos(selectionRect)

        @setState
          toolbarVisible: true
          toolbarMode: "buttons"
          toolbarTop: top
          toolbarLeft: left
          toolbarPos: toolbarPos
          linkToModify: null
          editAreaWidth: editAreaWidth

  # See selection API: http://www.w3.org/TR/selection-api/
  _selectionInScope: (selection) =>
    return false if not selection?
    editable = @_editableNode()
    return false if not editable?
    return (editable.contains(selection.anchorNode) and
            editable.contains(selection.focusNode))

  CONTENT_PADDING: 15

  _getToolbarPos: (referenceRect) =>

    TOP_PADDING = 10

    BORDER_RADIUS_PADDING = 15

    editArea = @_editableNode().getBoundingClientRect()

    calcLeft = (referenceRect.left - editArea.left) + referenceRect.width/2
    calcLeft = Math.min(Math.max(calcLeft, @CONTENT_PADDING+BORDER_RADIUS_PADDING), editArea.width - BORDER_RADIUS_PADDING)

    calcTop = referenceRect.top - editArea.top - 48
    toolbarPos = "above"
    if calcTop < TOP_PADDING
      calcTop = referenceRect.top - editArea.top + referenceRect.height + TOP_PADDING + 4
      toolbarPos = "below"

    return [calcLeft, calcTop, editArea.width, toolbarPos]

  _hideToolbar: =>
    if not @_focusedOnToolbar() and @state.toolbarVisible
      @setState toolbarVisible: false

  _focusedOnToolbar: =>
    React.findDOMNode(@refs.floatingToolbar)?.contains(document.activeElement)

  # This needs to be in the contenteditable area because we need to first
  # restore the selection before calling the `execCommand`
  #
  # If the url is empty, that means we want to remove the url.
  _onSaveUrl: (url, linkToModify) =>
    if linkToModify?
      linkToModify = @_findSimilarNodes(linkToModify)?[0]?.childNodes[0]
      return if not linkToModify?
      range = document.createRange()
      try
        range.setStart(linkToModify, 0)
        range.setEnd(linkToModify, linkToModify.length)
      catch
        return
      selection = document.getSelection()
      @_teardownSelectionListeners()
      selection.removeAllRanges()
      selection.addRange(range)
      if url.trim().length is 0
        document.execCommand("unlink", false)
      else
        document.execCommand("createLink", false, url)
      @_setupSelectionListeners()
    else
      @_restoreSelection(force: true)
      if document.getSelection().isCollapsed
        # TODO
      else
        if url.trim().length is 0
          document.execCommand("unlink", false)
        else
          document.execCommand("createLink", false, url)
        @_restoreSelection(force: true, collapse: "end")

  _setupLinkHoverListeners: =>
    HOVER_IN_DELAY = 250
    HOVER_OUT_DELAY = 1000
    @_links = {}
    links =  @_getAllLinks()
    return if links.length is 0
    links.forEach (link) =>
      link.hoverId = genLinkId()
      @_links[link.hoverId] = {}

      enterListener = (event) =>
        @_clearLinkTimeouts()
        @_linkHoveringOver = link
        @_links[link.hoverId].enterTimeout = setTimeout =>
          @_refreshToolbarState()
        , HOVER_IN_DELAY

      leaveListener = (event) =>
        @_clearLinkTimeouts()
        @_linkHoveringOver = null
        @_links[link.hoverId].leaveTimeout = setTimeout =>
          return if @refs.floatingToolbar.isHovering
          @_refreshToolbarState()
        , HOVER_OUT_DELAY

      link.addEventListener "mouseenter", enterListener
      link.addEventListener "mouseleave", leaveListener
      @_links[link.hoverId].link = link
      @_links[link.hoverId].enterListener = enterListener
      @_links[link.hoverId].leaveListener = leaveListener

  _clearLinkTimeouts: =>
    for hoverId, linkData of @_links
      clearTimeout(linkData.enterTimeout) if linkData.enterTimeout?
      clearTimeout(linkData.leaveTimeout) if linkData.leaveTimeout?

  _onTooltipMouseEnter: =>
    clearTimeout(@_clearTooltipTimeout) if @_clearTooltipTimeout?

  _onTooltipMouseLeave: =>
    @_clearTooltipTimeout = setTimeout =>
      @_refreshToolbarState()
    , 500

  _teardownLinkHoverListeners: =>
    for hoverId, linkData of @_links
      clearTimeout linkData.enterTimeout
      clearTimeout linkData.leaveTimeout
      linkData.link.removeEventListener "mouseenter", linkData.enterListener
      linkData.link.removeEventListener "mouseleave", linkData.leaveListener
    @_links = {}




  ####### CLEAN PASTE #########

  _onPaste: (evt) =>
    html = evt.clipboardData.getData("text/html") ? ""
    if html.length is 0
      text = evt.clipboardData.getData("text/plain") ? ""
      if text.length > 0
        evt.preventDefault()
        cleanHtml = text
      else
    else
      evt.preventDefault()
      cleanHtml = @_sanitizeHtml(html)

    document.execCommand("insertHTML", false, cleanHtml)
    return false

  # This is used primarily when pasting text in
  _sanitizeHtml: (html) =>
    cleanHTML = sanitizeHtml html.replace(/[\n\r]/g, "<br/>"),
      allowedTags: ['p', 'b', 'i', 'em', 'strong', 'a', 'br', 'img', 'ul', 'ol', 'li', 'strike']
      allowedAttributes:
        a: ['href', 'name']
        img: ['src', 'alt']
      transformTags:
        h1: "p"
        h2: "p"
        h3: "p"
        h4: "p"
        h5: "p"
        h6: "p"
        div: "p"
        pre: "p"
        blockquote: "p"
        table: "p"

    # We sanitized everything and convert all whitespace-inducing elements
    # into <p> tags. We want to de-wrap <p> tags and replace with two line
    # breaks instead.
    cleanHTML = cleanHTML.replace(/<p[\s\S]*?>/gim, "").replace(/<\/p>/gi, "<br/>")

    # We never want more then 2 line breaks in a row.
    # https://regex101.com/r/gF6bF4/4
    cleanHTML = cleanHTML.replace(/(<br\s*\/?>\s*){3,}/g, "<br/><br/>")

    return cleanHTML



  ####### QUOTED TEXT #########

  _onToggleQuotedText: =>
    @props.onChangeMode?(showQuotedText: !@props.mode?.showQuotedText)

  _quotedTextClasses: => classNames
    "quoted-text-control": true
    "no-quoted-text": @_htmlQuotedTextStart() is -1
    "show-quoted-text": @props.mode?.showQuotedText

  _htmlQuotedTextStart: =>
    @props.html.search(/(<br\/?>)?(<br\/?>)?<[^>]*gmail_quote/)

  _removeQuotedTextFromHTML: (html) =>
    quoteStart = @_htmlQuotedTextStart()
    if quoteStart is -1 then return html
    else return html.substr(0, quoteStart)

  _addQuotedTextToHTML: (innerHTML) =>
    quoteStart = @_htmlQuotedTextStart()
    if quoteStart is -1 then return innerHTML
    else return (innerHTML + @props.html.substr(quoteStart))


module.exports = ContenteditableComponent