/home/ivoiecob/email.hirewise-va.com/modules/MailWebclient/js/CCrea.js
'use strict'

const _ = require('underscore'),
  $ = require('jquery')

const App = require('%PathToCoreWebclientModule%/js/App.js'),
  Browser = require('%PathToCoreWebclientModule%/js/Browser.js'),
  ContenteditableUtils = require('%PathToCoreWebclientModule%/js/utils/Contenteditable.js'),
  Types = require('%PathToCoreWebclientModule%/js/utils/Types.js')

const CreaUtils = require('modules/%ModuleName%/js/utils/Crea.js')

/**
 * @constructor
 *
 * @param {Object} oOptions
 */
function CCrea(oOptions) {
  this.oOptions = _.extend(
    {
      creaId: 'creaId',
      fontNameArray: ['Tahoma'],
      defaultFontName: 'Tahoma',
      defaultFontSize: 3,
      alwaysTryUseImageWhilePasting: true,
      dropableArea: null,
      isRtl: false,
      onChange: function () {},
      onCursorMove: function () {},
      onFocus: function () {},
      onBlur: function () {},
      onUrlIn: function () {},
      onUrlOut: function () {},
      onImageSelect: function () {},
      onImageBlur: function () {},
      onItemOver: null,
      onItemOut: null,
      openInsertLinkDialog: function () {},
      onUrlClicked: false,
    },
    typeof oOptions === 'undefined' ? {} : oOptions
  )
}

/**
 * @type {Object}
 */
CCrea.prototype.oOptions = {}

/**
 * @type {Object}
 */
CCrea.prototype.$container = null

/**
 * @type {Object}
 */
CCrea.prototype.$editableArea = null

CCrea.prototype.aEditableAreaHtml = []

CCrea.prototype.iUndoRedoPosition = 0

CCrea.prototype.bEditable = false

CCrea.prototype.bFocused = false

/**
 * @type {Array}
 */
CCrea.prototype.aSizes = [
  { inNumber: 1, inPixels: 10 },
  { inNumber: 2, inPixels: 13 },
  { inNumber: 3, inPixels: 16 },
  { inNumber: 4, inPixels: 18 },
  { inNumber: 5, inPixels: 24 },
  { inNumber: 6, inPixels: 32 },
  { inNumber: 7, inPixels: 48 },
]

CCrea.prototype.bInUrl = false

CCrea.prototype.oCurrLink = null

CCrea.prototype.oCurrImage = null

CCrea.prototype.bInImage = false

CCrea.prototype.sBasicFontName = ''
CCrea.prototype.sBasicFontSize = ''
CCrea.prototype.sBasicDirection = ''

/**
 * Creates editable area.
 *
 * @param {boolean} bEditable
 */
CCrea.prototype.start = function (bEditable) {
  function isValidURL(sUrl) {
    var oRegExp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/

    return oRegExp.test(sUrl)
  }

  function isCorrectEmail(sValue) {
    return !!sValue.match(/^[A-Z0-9\"!#\$%\^\{\}`~&'\+\-=_\.]+@[A-Z0-9\.\-]+$/i)
  }

  this.aRanges = null // if this.aRanges is not null first focus doesn't work properly, then insert image doesn't work
  this.$container = $('#' + this.oOptions.creaId)
  this.$editableArea = $('<div></div>')
    .addClass('crea-content-editable')
    .prop('contentEditable', 'true')
    .appendTo(this.$container)

  var self = this

  this.$editableArea.on('focus', function () {
    self.bFocused = true
  })
  this.$editableArea.on('blur', function () {
    self.bFocused = false
    //self.editableSave(); //Undo/Redo fix
  })

  this.$editableArea.on('click', 'img', function (ev) {
    var oImage = $(this)
    self.bInImage = true
    self.oCurrImage = oImage
    self.oOptions.onImageSelect(oImage, ev)
    ev.stopPropagation()
  })
  this.$editableArea.on('click', function (ev) {
    self.bInImage = false
    self.oCurrImage = null
    self.oOptions.onImageBlur()
  })

  if (self.oOptions.onItemOver !== null) {
    this.$editableArea.on('mouseover', function (ev) {
      self.oOptions.onItemOver(ev)
    })
  }
  if (self.oOptions.onItemOver !== null) {
    this.$editableArea.on('mouseout', function (ev) {
      self.oOptions.onItemOut(ev)
    })
  }

  this.$editableArea.on('cut paste', function () {
    self.editableSave()
    _.defer(function () {
      self.editableSave()
    })
  })
  this.$editableArea.on('paste', function (oEvent) {
    oEvent = oEvent.originalEvent || oEvent

    if (oEvent.clipboardData) {
      var sText = oEvent.clipboardData.getData('text/plain'),
        sHtml = oEvent.clipboardData.getData('text/html')
      if (self.oOptions.alwaysTryUseImageWhilePasting && self.pasteImage(oEvent)) {
        oEvent.preventDefault()
      } else {
        if (isValidURL(sText)) {
          oEvent.preventDefault()
          self.execCom('insertHTML', '<a href="' + sText + '">' + sText.replaceAll('&', '&amp') + '</a>')
        } else if (isCorrectEmail(sText)) {
          oEvent.preventDefault()
          self.execCom('insertHTML', '<a href="mailto:' + sText + '">' + sText.replaceAll('&', '&amp') + '</a>')
        } else if (sHtml !== '') {
          const preparedHtml = CreaUtils.preparePastedHtml(sHtml)
          if (preparedHtml) {
            oEvent.preventDefault()
            self.execCom('insertHTML', preparedHtml)
          }
        }
      }
    }
  })
  this.$editableArea.on('keydown', function (oEvent) {
    var iKey = oEvent.keyCode || oEvent.which || oEvent.charCode || 0,
      bCtrlKey = oEvent.ctrlKey || oEvent.metaKey,
      bAltKey = oEvent.altKey,
      bShiftKey = oEvent.shiftKey,
      sLink = ''

    if ((bShiftKey && bCtrlKey && iKey === Enums.Key.z) || (bCtrlKey && iKey === Enums.Key.y)) {
      oEvent.preventDefault()

      self.editableRedo()
    } else if (bCtrlKey && !bAltKey && iKey === Enums.Key.z) {
      oEvent.preventDefault()

      self.editableUndo()
    } else if (
      bCtrlKey &&
      (iKey === Enums.Key.k || iKey === Enums.Key.b || iKey === Enums.Key.i || iKey === Enums.Key.u)
    ) {
      oEvent.preventDefault()
      switch (iKey) {
        case Enums.Key.k:
          sLink = self.getSelectedText()
          if (isValidURL(sLink)) {
            self.insertLink(sLink)
          } else if (isCorrectEmail(sLink)) {
            self.insertLink('mailto:' + sLink)
          } else {
            self.oOptions.openInsertLinkDialog()
          }
          break
        case Enums.Key.b:
          self.bold()
          break
        case Enums.Key.i:
          self.italic()
          break
        case Enums.Key.u:
          self.underline()
          break
      }
    } else if (!bAltKey && !bShiftKey && !bCtrlKey) {
      if (iKey === Enums.Key.Del || iKey === Enums.Key.Backspace) {
        self.editableSave()
      }
    }
  })
  this.$editableArea.on('keyup', function (oEvent) {
    var iKey = oEvent.keyCode || oEvent.which || oEvent.charCode || 0,
      bCtrlKey = oEvent.ctrlKey || oEvent.metaKey,
      bAltKey = oEvent.altKey,
      bShiftKey = oEvent.shiftKey
    if (!bAltKey && !bShiftKey && !bCtrlKey) {
      if (
        iKey === Enums.Key.Space ||
        iKey === Enums.Key.Enter ||
        iKey === Enums.Key.Del ||
        iKey === Enums.Key.Backspace
      ) {
        self.editableSave()
      } else {
        self.oOptions.onChange()
      }
    }
  })

  this.initContentEditable()
  this.setEditable(bEditable)

  App.broadcastEvent('%ModuleName%::StartCrea::after', {
    EditableArea: this.$editableArea,
    InsertHtmlHandler: this.insertHtml.bind(this),
  })
}

CCrea.prototype.clearUndoRedo = function () {
  this.aEditableAreaHtml = []
  this.iUndoRedoPosition = 0
}

CCrea.prototype.isUndoAvailable = function () {
  return this.iUndoRedoPosition > 0
}

CCrea.prototype.clearRedo = function () {
  this.aEditableAreaHtml = this.aEditableAreaHtml.slice(0, this.iUndoRedoPosition + 1)
}

CCrea.prototype.editableSave = function () {
  var sEditableHtml = this.$editableArea.html(),
    oLastSaved = _.last(this.aEditableAreaHtml),
    sLastSaved = oLastSaved ? oLastSaved[0] : ''
  if (sEditableHtml !== sLastSaved) {
    this.clearRedo()
    this.aEditableAreaHtml.push([sEditableHtml, this.getCaretPos(this.$editableArea[0])])
    this.iUndoRedoPosition = this.aEditableAreaHtml.length - 1
    this.oOptions.onChange()
  }
}

CCrea.prototype.editableUndo = function () {
  var sEditableHtml = this.$editableArea.html(),
    oCurrSaved = this.aEditableAreaHtml[this.iUndoRedoPosition],
    sCurrSaved = oCurrSaved ? oCurrSaved[0] : ''
  if (sEditableHtml !== sCurrSaved) {
    this.editableSave()
  }

  if (this.iUndoRedoPosition > 0) {
    this.iUndoRedoPosition--
    this.$editableArea.html(this.aEditableAreaHtml[this.iUndoRedoPosition])
    this.setCaretPos(this.$editableArea[0], this.aEditableAreaHtml[this.iUndoRedoPosition][1])
  }
}

CCrea.prototype.editableRedo = function () {
  if (this.iUndoRedoPosition < this.aEditableAreaHtml.length - 1) {
    this.iUndoRedoPosition++
    this.$editableArea.html(this.aEditableAreaHtml[this.iUndoRedoPosition])
    this.setCaretPos(
      this.$editableArea[0],
      this.aEditableAreaHtml[this.iUndoRedoPosition] ? this.aEditableAreaHtml[this.iUndoRedoPosition][1] : {}
    )
  }
}

CCrea.prototype.getCaretPos = function (oContainerEl) {
  var oSel = null,
    oRange = {},
    oPreSelectionRange = {},
    iStart = 0,
    oCaretPos = {}
  if (window.getSelection && document.createRange) {
    oSel = window.getSelection()
    if (oSel.rangeCount > 0) {
      oRange = oSel.getRangeAt(0)
      oPreSelectionRange = oRange.cloneRange()
      oPreSelectionRange.selectNodeContents(oContainerEl)
      oPreSelectionRange.setEnd(oRange.startContainer, oRange.startOffset)
      iStart = oPreSelectionRange.toString().length
      oCaretPos = {
        start: iStart,
        end: iStart + oRange.toString().length,
      }
    }
  } else if (document.selection && document.body.createTextRange) {
    oRange = document.selection.createRange()
    oPreSelectionRange = document.body.createTextRange()
    oPreSelectionRange.moveToElementText(oContainerEl)
    if (typeof oPreSelectionRange.setEndPoint === 'function') {
      oPreSelectionRange.setEndPoint('EndToStart', oRange)
    }
    iStart = oPreSelectionRange.text.length
    oCaretPos = {
      start: iStart,
      end: iStart + oRange.text.length,
    }
  }

  return oCaretPos
}

CCrea.prototype.setCaretPos = function (oContainerEl, oSavedSel) {
  if (window.getSelection && document.createRange) {
    var oNodeStack = [oContainerEl],
      oNode = {},
      oSel = {},
      bFoundStart = false,
      bStop = false,
      iCharIndex = 0,
      iNextCharIndex = 0,
      iChildNodes = 0,
      oRange = document.createRange()
    oRange.setStart(oContainerEl, 0)
    oRange.collapse(true)

    oNode = oNodeStack.pop()

    while (!bStop && oNode) {
      if (oNode.nodeType === 3) {
        iNextCharIndex = iCharIndex + oNode.length
        if (!bFoundStart && oSavedSel.start >= iCharIndex && oSavedSel.start <= iNextCharIndex) {
          oRange.setStart(oNode, oSavedSel.start - iCharIndex)
          bFoundStart = true
        }
        if (bFoundStart && oSavedSel.end >= iCharIndex && oSavedSel.end <= iNextCharIndex) {
          oRange.setEnd(oNode, oSavedSel.end - iCharIndex)
          bStop = true
        }
        iCharIndex = iNextCharIndex
      } else {
        iChildNodes = oNode.childNodes.length
        while (iChildNodes--) {
          oNodeStack.push(oNode.childNodes[iChildNodes])
        }
      }
      oNode = oNodeStack.pop()
    }

    oSel = window.getSelection()
    oSel.removeAllRanges()
    oSel.addRange(oRange)
  } else if (document.selection && document.body.createTextRange) {
    var oTextRange = document.body.createTextRange()

    oTextRange.moveToElementText(oContainerEl)
    oTextRange.collapse(true)
    oTextRange.moveEnd('character', oSavedSel.end)
    oTextRange.moveStart('character', oSavedSel.start)
    oTextRange.select()
  }
}

/**
 * Sets tab index.
 *
 * @param {string} sTabIndex
 */
CCrea.prototype.setTabIndex = function (sTabIndex) {
  if (sTabIndex) {
    this.$editableArea.attr('tabindex', sTabIndex)
  }
}

/**
 * Initializes properties.
 */
CCrea.prototype.initContentEditable = function () {
  this.$editableArea.bind({
    mousemove: _.bind(this.storeSelectionPosition, this),
    mouseup: _.bind(this.onCursorMove, this),
    keydown: _.bind(this.onButtonPressed, this),
    keyup: _.bind(this.onCursorMove, this),
    click: _.bind(this.onClickWith, this),
    focus: this.oOptions.onFocus,
    blur: this.oOptions.onBlur,
  })

  if (window.File && window.FileReader && window.FileList) {
    if (this.oOptions.enableDrop) {
      this.$editableArea.bind({
        dragover: _.bind(this.onDragOver, this),
        dragleave: _.bind(this.onDragLeave, this),
        drop: _.bind(this.onFileSelect, this),
      })
    }
  }

  var self = this,
    lazyScroll = _.debounce(function () {
      self.oCurrLink = null
      self.bInUrl = false
      self.oOptions.onUrlOut()
    }, 300)
  $('html, body').on('scroll', lazyScroll)
}

/**
 * Starts cursor move handlers.
 * @param {Object} ev
 */
CCrea.prototype.onCursorMove = function (ev) {
  var iKey = -1
  if (window.event) {
    iKey = window.event.keyCode
  } else if (ev) {
    iKey = ev.which
  }

  if (iKey === 13) {
    // Enter
    this.breakQuotes(ev)
  }

  if (iKey === 17) {
    // Cntr
    this.$editableArea.find('a').css('cursor', 'inherit')
  }

  if (iKey === 8) {
    // BackSpace
    this.uniteWithNextQuote(ev)
  }

  if (iKey === 46 && Browser.chrome) {
    // Delete
    this.uniteWithPrevQuote(ev)
  }

  this.storeSelectionPosition()
  this.oOptions.onCursorMove()
}

/**
 * Starts when clicked.
 * @param {Object} oEvent
 */
CCrea.prototype.onClickWith = function (oEvent) {
  if (oEvent.ctrlKey) {
    if (oEvent.target.nodeName === 'A') {
      window.open(oEvent.target.href, '_blank')
    }
  }
  this.checkAnchorNode()
}

/**
 * Starts when key pressed.
 * @param {Object} oEvent
 */
CCrea.prototype.onButtonPressed = function (oEvent) {
  var iKey = -1
  if (window.event) {
    iKey = window.event.keyCode
  } else if (oEvent) {
    iKey = oEvent.which
  }

  if (iKey === 17) {
    // Cntr
    this.$editableArea.find('a').css('cursor', 'pointer')
  }
}

/**
 * Starts cursor move handlers.
 * @param {Object} oEvent
 */
CCrea.prototype.onFileSelect = function (oEvent) {
  oEvent = (oEvent && oEvent.originalEvent ? oEvent.originalEvent : oEvent) || window.event

  if (oEvent) {
    oEvent.stopPropagation()
    oEvent.preventDefault()

    var oReader = null,
      oFile = null,
      aFiles = oEvent.files || (oEvent.dataTransfer ? oEvent.dataTransfer.files : null),
      self = this
    if (aFiles && 1 === aFiles.length && this.checkIsImage(aFiles[0])) {
      oFile = aFiles[0]

      oReader = new window.FileReader()
      oReader.onload = (function () {
        return function (oEvent) {
          self.insertImage(oEvent.target.result)
        }
      })()

      oReader.readAsDataURL(oFile)
    }
  }
}

CCrea.prototype.onDragLeave = function () {
  this.$editableArea.removeClass('editorDragOver')
}

/**
 * @param {Object} oEvent
 */
CCrea.prototype.onDragOver = function (oEvent) {
  oEvent.stopPropagation()
  oEvent.preventDefault()

  this.$editableArea.addClass('editorDragOver')
}

/**
 * @param {Object} oEvent
 * @returns {Boolean}
 */
CCrea.prototype.pasteImage = function (oEvent) {
  var oClipboardItems = oEvent.clipboardData && oEvent.clipboardData.items,
    self = this,
    bImagePasted = false
  if (window.File && window.FileReader && window.FileList && oClipboardItems) {
    _.each(oClipboardItems, function (oItem) {
      if (self.checkIsImage(oItem) && oItem['getAsFile']) {
        var oReader = null,
          oFile = oItem['getAsFile']()
        if (oFile) {
          oReader = new window.FileReader()
          oReader.onload = (function () {
            return function (oEvent) {
              self.insertImage(oEvent.target.result)
            }
          })()

          oReader.readAsDataURL(oFile)
          bImagePasted = true
        }
      }
    })
  }

  return bImagePasted
}

/**
 * @param {Object} oItem
 * @return {boolean}
 */
CCrea.prototype.checkIsImage = function (oItem) {
  return oItem && oItem.type && 0 === oItem.type.indexOf('image/')
}

/**
 * Sets plain text to rich editor.
 *
 * @param {string} sText
 */
CCrea.prototype.setPlainText = function (sText) {
  if (typeof sText !== 'string') {
    sText = ''
  }

  if (this.$editableArea) {
    this.editableSave()
    this.$editableArea.empty().text(sText).css('white-space', 'pre')
    this.editableSave()
  }
}

/**
 * Sets text to rich editor.
 *
 * @param {string} sText
 */
CCrea.prototype.setText = function (sText) {
  if (typeof sText !== 'string') {
    sText = ''
  }

  if (this.$editableArea) {
    if (sText.length === 0) {
      sText = '<br />'
    }

    const preparedHtml = this.prepareHtmlWithoutWrappers(sText)
    this.$editableArea.empty().append(preparedHtml).css('white-space', 'normal')
    this.clearUndoRedo()
    this.editableSave()
  }
}

CCrea.prototype.prepareHtmlWithoutWrappers = function (html) {
  let outerNode = $(html)
  let isOuterElemChanged = false
  while (
    outerNode.length === 1 &&
    (outerNode.data('x-div-type') === 'html' || outerNode.data('x-div-type') === 'body')
  ) {
    outerNode = outerNode.children()
    isOuterElemChanged = true
  }
  if (outerNode.length === 1 && outerNode.data('crea') === 'font-wrapper') {
    this.setBasicStyles(outerNode.css('font-family'), outerNode.css('font-size'), outerNode.css('direction'))
    return outerNode.html()
  }
  this.setBasicStyles(
    this.oOptions.defaultFontName,
    this.convertFontSizeToPixels(this.oOptions.defaultFontSize),
    this.oOptions.isRtl ? 'rtl' : 'ltr'
  )
  if (!isOuterElemChanged) {
    return html
  } else {
    let res = ''
    outerNode.each((index, elem) => {
      res += elem.outerHTML
    })
    return res
  }
}

/**
 * @param {string} sFontName
 * @param {string} sFontSize
 * @param {string} sDirection
 */
CCrea.prototype.setBasicStyles = function (sFontName, sFontSize, sDirection) {
  this.sBasicFontName = sFontName
  this.sBasicFontSize = sFontSize
  this.sBasicDirection = sDirection

  this.$editableArea.css({
    'font-family': this.getFontNameWithFamily(this.sBasicFontName),
    'font-size': this.sBasicFontSize,
    direction: this.sBasicDirection,
  })
}

/**
 * Gets plain text from rich editor.
 *
 * @return {string}
 */
CCrea.prototype.getPlainText = function () {
  var sVal = ''

  if (this.$editableArea) {
    sVal = this.$editableArea
      .html()
      .replace(/([^>]{1})<div>/gi, '$1\n')
      .replace(/<style[^>]*>[^<]*<\/style>/gi, '\n')
      .replace(/<br *\/{0,1}>/gi, '\n')
      .replace(/<\/p>/gi, '\n')
      .replace(/<\/div>/gi, '\n')
      .replace(/<a [^>]*href="([^"]*?)"[^>]*>(.*?)<\/a>/gi, '$2 ($1)')
      .replace(/<[^>]*>/g, '')
      .replace(/&nbsp;/g, ' ')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&amp;/g, '&')
      .replace(/&quot;/g, '"')
  }

  return sVal
}

/**
 * Gets text from rich editor.
 *
 * @param {boolean=} bRemoveSignatureAnchor = false
 * @return {string}
 */
CCrea.prototype.getText = function (bRemoveSignatureAnchor) {
  var $Anchor = null,
    sVal = ''
  if (this.$editableArea && this.$editableArea.length > 0) {
    if (bRemoveSignatureAnchor) {
      $Anchor = this.$editableArea.find('div[data-anchor="signature"]')
      $Anchor.removeAttr('data-anchor')
    }

    sVal = this.$editableArea.html()
    sVal =
      '<div data-crea="font-wrapper" style="font-family: ' +
      this.getFontNameWithFamily(this.sBasicFontName) +
      '; font-size: ' +
      this.sBasicFontSize +
      '; direction: ' +
      this.sBasicDirection +
      '">' +
      sVal +
      '</div>'
  }

  return sVal
}

/**
 * @param {string} sNewSignatureContent
 * @param {string} sOldSignatureContent
 */
CCrea.prototype.changeSignatureContent = function (sNewSignatureContent, sOldSignatureContent) {
  var $Anchor = this.$editableArea.find('div[data-anchor="signature"]'),
    $NewSignature = $(sNewSignatureContent).closest('div[data-crea="font-wrapper"]'),
    $OldSignature = $(sOldSignatureContent).closest('div[data-crea="font-wrapper"]'),
    sClearOldSignature,
    sClearNewSignature,
    sAnchorHtml,
    $SignatureContainer,
    $SignatureBlockquoteParent,
    sFoundOldSignature,
    $AnchorBlockquoteParent

  /*** there is a signature container in the message ***/
  if ($Anchor.length > 0) {
    sAnchorHtml = $Anchor.html()
    /*** previous signature is empty -> append to the container a new signature ***/
    if (sOldSignatureContent === '') {
      $Anchor.html(sAnchorHtml + sNewSignatureContent)
    } else if (sAnchorHtml.indexOf(sOldSignatureContent) !== -1) {
      /*** previous signature was found in the container -> replace it with a new ***/
      $Anchor.html(sAnchorHtml.replace(sOldSignatureContent, sNewSignatureContent))
    } else if (sAnchorHtml.indexOf(sNewSignatureContent) !== -1) {
      /*** new signature is found in the container -> do nothing ***/
    } else {
      sClearOldSignature =
        $NewSignature.length === 0 || $OldSignature.length === 0 ? sOldSignatureContent : $OldSignature.html()
      sClearNewSignature =
        $NewSignature.length === 0 || $OldSignature.length === 0 ? sNewSignatureContent : $NewSignature.html()
      /*** found a previous signature without wrapper -> replace it with a new ***/
      if (sAnchorHtml.indexOf(sClearOldSignature) !== -1) {
        $Anchor.html(sAnchorHtml.replace(sClearOldSignature, sNewSignatureContent))
      } else if (sAnchorHtml.indexOf(sClearNewSignature) !== -1) {
        /*** found a new signature without wrapper -> do nothing ***/
      } else {
        /*** append the new signature to the end of the container ***/
        $Anchor.html(sAnchorHtml + sNewSignatureContent)
      }
    }
  } else {
    /*** there is NO signature container in the message ***/
    sFoundOldSignature = sOldSignatureContent
    try {
      $SignatureContainer = this.$editableArea.find('*:contains("' + sFoundOldSignature + '")')
    } catch (oErr) {
      $SignatureContainer = $('')
    }
    if ($SignatureContainer.length === 0 && $OldSignature.length > 0) {
      sFoundOldSignature = $OldSignature.html()
      try {
        $SignatureContainer = this.$editableArea.find('*:contains("' + sFoundOldSignature + '")')
      } catch (oErr) {
        $SignatureContainer = $('')
      }
    }

    if ($SignatureContainer.length > 0) {
      $SignatureContainer = $($SignatureContainer[0])
      $SignatureBlockquoteParent = $SignatureContainer.closest('blockquote')
    }

    if ($SignatureBlockquoteParent && $SignatureBlockquoteParent.length === 0) {
      $SignatureContainer.html($SignatureContainer.html().replace(sFoundOldSignature, sNewSignatureContent))
    } else {
      $Anchor = this.$editableArea.find('div[data-anchor="reply-title"]')
      $AnchorBlockquoteParent = $Anchor.length > 0 ? $($Anchor[0]).closest('blockquote') : $Anchor
      if ($Anchor.length === 0 || $AnchorBlockquoteParent.length > 0) {
        $Anchor = this.$editableArea.find('blockquote')
      }

      if ($Anchor.length > 0) {
        $($Anchor[0]).before($('<br /><div data-anchor="signature">' + sNewSignatureContent + '</div><br />'))
      } else {
        this.$editableArea.append($('<br /><div data-anchor="signature">' + sNewSignatureContent + '</div><br />'))
      }
    }
  }

  this.editableSave()
}

/**
 * @return {boolean}
 */
CCrea.prototype.isFocused = function () {
  return this.bFocused
}

/**
 * Sets focus.
 * @param {boolean} keepCurrent
 * @param {string} restoreText
 */
CCrea.prototype.setFocus = function (keepCurrent = false, restoreText) {
  const contents = this.$editableArea.contents()
  this.$editableArea.focus()
  if (keepCurrent && _.isArray(this.aRanges) && this.aRanges.length > 0) {
    this.restoreSelectionPosition(restoreText)
  } else if (contents.length > 0) {
    const TEXT_NODE_TYPE = 3
    let textNode = null
    if (contents[0].nodeType === TEXT_NODE_TYPE) {
      textNode = $(contents[0])
    } else {
      textNode = $(document.createTextNode(''))
      $(contents[0]).before(textNode)
    }

    const nodeText = textNode.text()
    this.setCursorPosition(textNode[0], nodeText.length)
  }
}

CCrea.prototype.setBlur = function () {
  this.$editableArea.blur()
}

/**
 * @param {boolean} bEditable
 */
CCrea.prototype.setEditable = function (bEditable) {
  if (bEditable) {
    this.enableContentEditable()
  } else {
    this.disableContentEditable()
  }
}

CCrea.prototype.disableContentEditable = function () {
  this.bEditable = false
  this.$editableArea.prop('contentEditable', 'false')
}

CCrea.prototype.enableContentEditable = function () {
  this.$editableArea.prop('contentEditable', 'true')
  setTimeout(
    _.bind(function () {
      this.bEditable = true
    }, this),
    0
  )
}

CCrea.prototype.fixFirefoxCursorBug = function () {
  if (Browser.firefox) {
    this.disableContentEditable()

    setTimeout(
      _.bind(function () {
        this.enableContentEditable()
      }, this),
      0
    )
  }
}

CCrea.prototype.setRtlDirection = function () {
  this.setBasicStyles(this.sBasicFontName, this.sBasicFontSize, 'rtl')
}

CCrea.prototype.setLtrDirection = function () {
  this.setBasicStyles(this.sBasicFontName, this.sBasicFontSize, 'ltr')
}

CCrea.prototype.pasteHtmlAtCaret = function (html) {
  var sel, range
  if (window.getSelection) {
    // IE9 and non-IE
    sel = window.getSelection()
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0)
      range.deleteContents()

      // Range.createContextualFragment() would be useful here but is
      // only relatively recently standardized and is not supported in
      // some browsers (IE9, for one)
      var el = document.createElement('div')
      el.innerHTML = html
      var frag = document.createDocumentFragment(),
        node,
        lastNode
      while ((node = el.firstChild)) {
        lastNode = frag.appendChild(node)
      }
      range.insertNode(frag)

      // Preserve the selection
      if (lastNode) {
        range = range.cloneRange()
        range.setStartAfter(lastNode)
        range.collapse(true)
        sel.removeAllRanges()
        sel.addRange(range)
      }
    }
  } else if (document.selection && document.selection.type !== 'Control') {
    // IE < 9
    range = document.selection.createRange()
    if (range && range.pasteHTML) {
      range.pasteHTML(html)
    }
  }
}

/**
 * Executes command.
 *
 * @param {string} sCmd
 * @param {string=} sParam
 * @param {boolean=} bDontAddToHistory
 * @return {boolean}
 */
CCrea.prototype.execCom = function (sCmd, sParam, bDontAddToHistory) {
  var bRes = false,
    oRange

  if (this.bEditable) {
    this.editableSave()

    if (Browser.opera) {
      this.restoreSelectionPosition()
    }

    if ('insertHTML' === sCmd && Browser.ie) {
      this.pasteHtmlAtCaret(sParam)
    } else {
      if (typeof sParam === 'undefined') {
        bRes = window.document.execCommand(sCmd)
      } else {
        bRes = window.document.execCommand(sCmd, false, sParam)
      }
    }

    if (Browser.chrome) {
      // Chrome need to resave the selection after the operation.
      this.storeSelectionPosition()
      if (sCmd === 'insertHTML' && this.aRanges.length > 0) {
        // Chrome selects line after inserted text. Disable do it.
        oRange = this.aRanges[0]
        oRange.setEnd(oRange.startContainer, oRange.startOffset)
        this.restoreSelectionPosition()
      }
    }

    if (!bDontAddToHistory) {
      this.editableSave()
    }
  }
  return bRes
}

/**
 * Inserts html.
 *
 * @param {string} sHtml
 * @param {boolean} bDontAddToHistory
 */
CCrea.prototype.insertHtml = function (sHtml, bDontAddToHistory) {
  this.execCom('insertHTML', sHtml, bDontAddToHistory)
}

/**
 * @param {string} sId
 * @param {string} sSrc
 */
CCrea.prototype.changeImageSource = function (sId, sSrc) {
  this.$editableArea.find('img[id="' + sId + '"]').attr('src', sSrc)
  this.editableSave()
}

/**
 * Inserts link.
 *
 * @param {string} sLink
 */
CCrea.prototype.insertEmailLink = function (sLink) {
  this.restoreSelectionPosition()
  if (this.getSelectedText() === '') {
    this.execCom('insertHTML', '<a href="mailto:' + sLink + '">' + sLink + '</a>')
  } else {
    this.insertLink('mailto:' + sLink)
  }
}

/**
 * Inserts link.
 *
 * @param {string} link
 */
CCrea.prototype.insertLink = function (link) {
  const normalisedLink = this.normaliseURL(link)
  if (!this.isFocused()) {
    this.setFocus(true, normalisedLink)
  } else {
    this.restoreSelectionPosition(normalisedLink)
  }

  this.execCom('createlink', normalisedLink)
}

/**
 * Removes link.
 */
CCrea.prototype.removeLink = function () {
  var sCmd = Browser.ie8AndBelow ? 'Unlink' : 'unlink'
  this.execCom(sCmd)
}

/**
 * Inserts image.
 *
 * @param {string} sImage
 * @return {boolean}
 */
CCrea.prototype.insertImage = function (sImage) {
  var sCmd = Browser.ie8AndBelow ? 'InsertImage' : 'insertimage'
  if (!this.isFocused()) {
    this.setFocus(true)
  } else {
    this.restoreSelectionPosition()
  }

  return this.execCom(sCmd, sImage)
}

/**
 * Inserts ordered list.
 */
CCrea.prototype.numbering = function () {
  this.execCom('InsertOrderedList')
}

/**
 * Inserts unordered list.
 */
CCrea.prototype.bullets = function () {
  this.execCom('InsertUnorderedList')
}

/**
 * Inserts horizontal line.
 */
CCrea.prototype.insertHorizontalLine = function () {
  if (!this.isFocused()) {
    this.setFocus(true)
  } else {
    this.restoreSelectionPosition()
  }
  this.execCom('InsertHorizontalRule')
}

/**
 * @param {string} sFontName
 */
CCrea.prototype.getFontNameWithFamily = function (sFontName) {
  var sFamily = ''

  switch (sFontName) {
    case 'Arial':
    case 'Arial Black':
    case 'Tahoma':
    case 'Verdana':
      sFamily = ', sans-serif'
      break
    case 'Courier New':
      sFamily = ', monospace'
      break
    case 'Times New Roman':
      sFamily = ', serif'
      break
  }

  return sFontName + sFamily
}

/**
 * Sets font name.
 *
 * @param {string} sFontName
 */
CCrea.prototype.fontName = function (sFontName) {
  var bFirstTime = !this.aRanges

  this.setFocus(true)
  this.execCom('FontName', this.getFontNameWithFamily(sFontName))

  if (bFirstTime) {
    this.setBasicStyles(sFontName, this.sBasicFontSize, this.sBasicDirection)
  }
}

/**
 * Sets font size.
 *
 * @param {string} sFontSize
 */
CCrea.prototype.fontSize = function (sFontSize) {
  var bFirstTime = !this.aRanges

  this.setFocus(true)
  this.execCom('FontSize', sFontSize)

  if (bFirstTime) {
    this.setBasicStyles(this.sBasicFontName, this.convertFontSizeToPixels(sFontSize), this.sBasicDirection)
  }
}

/**
 * Sets bold style.
 */
CCrea.prototype.bold = function () {
  this.execCom('Bold')
  this.$editableArea.focus()
}

/**
 * Sets italic style.
 */
CCrea.prototype.italic = function () {
  this.execCom('Italic')
  this.$editableArea.focus()
}

/**
 * Sets underline style.
 */
CCrea.prototype.underline = function () {
  this.execCom('Underline')
  this.$editableArea.focus()
}

/**
 * Sets strikethrough style.
 */
CCrea.prototype.strikeThrough = function () {
  this.execCom('StrikeThrough')
  this.$editableArea.focus()
}

CCrea.prototype.undo = function () {
  this.editableUndo()
}

CCrea.prototype.redo = function () {
  this.editableRedo()
}

/**
 * Sets left justify.
 */
CCrea.prototype.alignLeft = function () {
  this.execCom('JustifyLeft')
}

/**
 * Sets center justify.
 */
CCrea.prototype.center = function () {
  this.execCom('JustifyCenter')
}

/**
 * Sets right justify.
 */
CCrea.prototype.alignRight = function () {
  this.execCom('JustifyRight')
}

/**
 * Sets full justify.
 */
CCrea.prototype.justify = function () {
  this.execCom('JustifyFull')
}

/**
 * Sets text color.
 *
 * @param {string} sFontColor
 */
CCrea.prototype.textColor = function (sFontColor) {
  this.execCom('ForeColor', sFontColor)
  this.$editableArea.focus()
}

/**
 * Sets background color.
 *
 * @param {string} sBackColor
 */
CCrea.prototype.backgroundColor = function (sBackColor) {
  var sCmd = Browser.ie ? 'BackColor' : 'hilitecolor'
  this.execCom(sCmd, sBackColor)
  this.$editableArea.focus()
}

/**
 * Wraps selection with the background tag.
 *
 * @param {string} sBackColor
 */
CCrea.prototype.blockquote = function (sBackColor) {
  var sCmd = 'formatBlock'
  this.execCom(sCmd, '<blockquote>')
  this.$editableArea.focus()
}

/**
 * Removes format.
 */
CCrea.prototype.removeFormat = function () {
  this.execCom('removeformat')
  this.$editableArea.focus()
}

/**
 * Gets font name from selected text.
 *
 * @return {string}
 */
CCrea.prototype.getFontName = function () {
  if (this.bEditable) {
    var sFontName = window.document.queryCommandValue('FontName'),
      sValidFontName = this.sBasicFontName,
      sFoundFontName = ''
    if (typeof sFontName === 'string') {
      sFontName = sFontName.replace(/'/g, '')
      $.each(this.oOptions.fontNameArray, function (iIndex, sFont) {
        if (sFontName.indexOf(sFont) > -1 || sFontName.indexOf(sFont.toLowerCase()) > -1) {
          sFoundFontName = sFont
        }
      })

      if (sFoundFontName !== '') {
        sValidFontName = sFoundFontName
      }
    }
  }

  return sValidFontName
}

/**
 * Gets is font-weight bold.
 *
 * @return {boolean}
 */
CCrea.prototype.getIsBold = function () {
  if (this.bEditable) {
    var bIsBold = window.document.queryCommandState('bold')
  }

  return bIsBold
}

/**
 * Gets is font-style italic.
 *
 * @return {boolean}
 */
CCrea.prototype.getIsItalic = function () {
  if (this.bEditable) {
    var bIsItalic = window.document.queryCommandState('italic')
  }

  return bIsItalic
}

/**
 * Gets is text-decoration underline.
 *
 * @return {boolean}
 */
CCrea.prototype.getIsUnderline = function () {
  if (this.bEditable) {
    var bIsUnderline = window.document.queryCommandState('underline')
  }

  return bIsUnderline
}

/**
 * Gets is ordered list active.
 *
 * @return {boolean}
 */
CCrea.prototype.getIsEnumeration = function () {
  if (this.bEditable) {
    var bIsEnumeration = window.document.queryCommandState('insertOrderedList')
  }

  return bIsEnumeration
}

/**
 * Gets is unordered list active.
 *
 * @return {boolean}
 */
CCrea.prototype.getIsBullets = function () {
  if (this.bEditable) {
    var bIsBullets = window.document.queryCommandState('insertUnorderedList')
  }

  return bIsBullets
}

/**
 * Gets is text-decoration strike-through.
 *
 * @return {boolean}
 */
CCrea.prototype.getIsStrikeThrough = function () {
  if (this.bEditable) {
    var bIsStrikeThrough = window.document.queryCommandState('StrikeThrough')
  }

  return bIsStrikeThrough
}

/**
 * @param {number} iFontSizeInNumber
 *
 * @return {string}
 */
CCrea.prototype.convertFontSizeToPixels = function (iFontSizeInNumber) {
  var iFontSizeInPixels = 0

  $.each(this.aSizes, function (iIndex, oSize) {
    if (iFontSizeInPixels === 0 && iFontSizeInNumber <= oSize.inNumber) {
      iFontSizeInPixels = oSize.inPixels
    }
  })

  return iFontSizeInPixels + 'px'
}

/**
 * @param {string} sFontSizeInPixels
 *
 * @return {number}
 */
CCrea.prototype.convertFontSizeToNumber = function (sFontSizeInPixels) {
  var iFontSizeInPixels = Types.pInt(sFontSizeInPixels),
    iFontSizeInNumber = 0
  if (iFontSizeInPixels > 0) {
    $.each(this.aSizes, function (iIndex, oSize) {
      if (iFontSizeInNumber === 0 && iFontSizeInPixels <= oSize.inPixels) {
        iFontSizeInNumber = oSize.inNumber
      }
    })
  }

  return iFontSizeInNumber
}

/**
 * Gets font size from selected text.
 *
 * @return {number}
 */
CCrea.prototype.getFontSizeInNumber = function () {
  var sFontSizeInNumber = '',
    iFontSizeInNumber = 0
  if (this.bEditable) {
    sFontSizeInNumber = window.document.queryCommandValue('FontSize')
    iFontSizeInNumber = Types.pInt(sFontSizeInNumber)
  }

  if (isNaN(iFontSizeInNumber) || iFontSizeInNumber <= 0) {
    iFontSizeInNumber = this.convertFontSizeToNumber(this.sBasicFontSize)
  }

  return iFontSizeInNumber
}

/**
 * @param {string} sHref
 */
CCrea.prototype.changeLink = function (sHref) {
  var sNormHref = this.normaliseURL(sHref),
    oCurrLink = $(this.oCurrLink)
  if (this.oCurrLink) {
    if (oCurrLink.attr('href') === oCurrLink.text()) {
      oCurrLink.text(sNormHref)
    }
    if (this.oCurrLink.tagName === 'A') {
      oCurrLink.attr('href', sNormHref)
    } else {
      oCurrLink.parent().attr('href', sNormHref)
    }

    this.oCurrLink = null
    this.bInUrl = false
  }
}

CCrea.prototype.removeCurrentLink = function () {
  if (this.oCurrLink && document.createRange && window.getSelection) {
    var oRange = document.createRange(),
      oSel = window.getSelection()
    oRange.selectNodeContents(this.oCurrLink)
    oSel.removeAllRanges()
    oSel.addRange(oRange)

    this.removeLink()
    this.oCurrLink = null
    this.bInUrl = false
    this.oOptions.onUrlOut()
  }
}

CCrea.prototype.removeCurrentImage = function () {
  if (this.oCurrImage) {
    this.oCurrImage.remove()
    this.oCurrImage = null
    this.bInImage = false
    this.oOptions.onImageBlur()
  }
  this.setFocus(true)
}

CCrea.prototype.changeCurrentImage = function (aParams) {
  if (this.oCurrImage && aParams !== undefined) {
    var image = this.oCurrImage
    $.each(aParams, function (key, value) {
      image.css(key, value)
    })
  }
  this.setFocus(true)
}

CCrea.prototype.showImageTooltip = function (aParams) {
  if (this.oCurrImage && aParams !== undefined) {
    var image = this.oCurrImage
    $.each(aParams, function (key, value) {
      image.css(key, value)
    })
  }
}

/**
 * @param {string} sText
 * @return {string}
 */
CCrea.prototype.normaliseURL = function (sText) {
  return sText.search(/^https?:\/\/|^mailto:|^tel:/g) !== -1 ? sText : 'http://' + sText
}

/**
 * @return {string}
 */
CCrea.prototype.getSelectedText = function () {
  var sText = '',
    oSel = null
  if (window.getSelection) {
    oSel = window.getSelection()
    if (oSel.rangeCount > 0) {
      sText = oSel.getRangeAt(0).toString()
    }
  }

  return sText
}

/**
 * Stores selection position.
 */
CCrea.prototype.storeSelectionPosition = function () {
  const aNewRanges = ContenteditableUtils.getSelectionRanges()
  // check is selection is inside editable area
  if (_.isArray(aNewRanges) && aNewRanges.length > 0 && this.$editableArea[0].contains(aNewRanges[0].commonAncestorContainer)) {
    this.aRanges = [aNewRanges[0]]
  }
}

/**
 * @return {Array}
 */
CCrea.prototype.editableIsActive = function () {
  return !!(
    $(document.activeElement).hasClass('crea-content-editable') ||
    $(document.activeElement).children().first().hasClass('crea-content-editable')
  )
}

CCrea.prototype.checkAnchorNode = function () {
  if (window.getSelection && this.editableIsActive()) {
    var oSel = window.getSelection(),
      oCurrLink = null
    if (oSel.anchorNode && (oSel.anchorNode.parentElement || oSel.anchorNode.parentNode)) {
      oCurrLink = oSel.anchorNode.parentElement || oSel.anchorNode.parentNode

      if (oCurrLink.parentNode.tagName === 'A') {
        oCurrLink = oCurrLink.parentNode
      } else if (oCurrLink.parentElement.tagName === 'A') {
        oCurrLink = oCurrLink.parentNode
      }

      if (oCurrLink.tagName === 'A') {
        if (!this.bInUrl || oCurrLink !== this.oCurrLink) {
          this.oCurrLink = oCurrLink
          this.bInUrl = true
          this.oOptions.onUrlIn($(oCurrLink))
        } else if (this.bInUrl && oCurrLink === this.oCurrLink) {
          this.oCurrLink = null
          this.bInUrl = false
          this.oOptions.onUrlOut()
        }
      } else if (this.bInUrl) {
        this.oCurrLink = null
        this.bInUrl = false
        this.oOptions.onUrlOut()
      }
    }
  }
}

/**
 * Restores selection position.
 *
 * @param {string} restoreText
 */
CCrea.prototype.restoreSelectionPosition = function (restoreText = '') {
  const rangeText = ContenteditableUtils.setSelectionRanges(this.aRanges)
  if (window.getSelection && _.isArray(this.aRanges)) {
    if (Browser.firefox && rangeText === '' && restoreText !== '') {
      if (window.getSelection && window.getSelection().getRangeAt) {
        const selection = window.getSelection()
        if (selection.getRangeAt && selection.rangeCount > 0) {
          const range = selection.getRangeAt(0)
          const node = range.createContextualFragment(restoreText)
          range.insertNode(node)
        }
      } else if (document.selection && document.selection.createRange) {
        document.selection.createRange().pasteHTML(restoreText)
      }
    }
  }
}

CCrea.prototype.uniteWithNextQuote = function () {
  var oSel = window.getSelection ? window.getSelection() : null,
    eFocused = oSel ? oSel.focusNode : null,
    eBlock = eFocused ? this.getLastBlockQuote(eFocused) : null,
    oNext = eBlock ? $(eBlock).next() : null,
    eNext = oNext && oNext.length > 0 && oNext[0].tagName === 'BLOCKQUOTE' ? oNext[0] : null,
    aChildren = [],
    iIndex = 0,
    iLen = 0,
    eChild = null
  if (eBlock && eNext) {
    $('<br />').appendTo(eBlock)

    aChildren = $(eNext).contents()
    iLen = aChildren.length

    for (iIndex = 0; iIndex < iLen; iIndex++) {
      eChild = aChildren[iIndex]
      $(eChild).appendTo(eBlock)
    }

    $(eNext).remove()
  }
}

CCrea.prototype.uniteWithPrevQuote = function () {
  var oSel = window.getSelection ? window.getSelection() : null,
    eFocused = oSel ? oSel.focusNode : null,
    eBlock = eFocused ? this.getLastBlockQuote(eFocused) : null
  this.getPrevAndUnite(eBlock)
  this.getPrevAndUnite(eBlock)
}

/**
 * @param {Object} eBlock
 */
CCrea.prototype.getPrevAndUnite = function (eBlock) {
  var oPrev = eBlock ? $(eBlock).prev() : null,
    ePrev = oPrev && oPrev.length > 0 && oPrev[0].tagName === 'BLOCKQUOTE' ? oPrev[0] : null,
    aChildren = [],
    iIndex = 0,
    iLen = 0,
    eChild = null
  if (eBlock && ePrev) {
    $('<br />').prependTo(eBlock)

    aChildren = $(ePrev).contents()
    iLen = aChildren.length

    for (iIndex = iLen - 1; iIndex > 0; iIndex--) {
      eChild = aChildren[iIndex]
      $(eChild).prependTo(eBlock)
    }

    $(ePrev).remove()
  }
}

/**
 * @param {Object} eFocused
 * @return {Object}
 */
CCrea.prototype.getLastBlockQuote = function (eFocused) {
  var eCurrent = eFocused,
    eBlock = null
  while (eCurrent && eCurrent.parentNode) {
    if (eCurrent.tagName === 'BLOCKQUOTE') {
      eBlock = eCurrent
    }
    eCurrent = eCurrent.parentNode
  }

  return eBlock
}

/**
 * @param {Object} ev
 */
CCrea.prototype.breakQuotes = function (ev) {
  var oSel = window.getSelection ? window.getSelection() : null,
    eFocused = oSel ? oSel.focusNode : null,
    eBlock = eFocused ? this.getLastBlockQuote(eFocused) : null
  if (eFocused && eBlock) {
    this.breakBlocks(eFocused, eBlock, oSel.focusOffset)
  }
}

/**
 * @param {Object} eStart
 * @param {number} iStartOffset
 */
CCrea.prototype.setCursorPosition = function (eStart, iStartOffset) {
  if (document.createRange && window.getSelection) {
    var oRange = document.createRange(),
      oSel = window.getSelection()
    oSel.removeAllRanges()

    oRange.setStart(eStart, iStartOffset)
    oRange.setEnd(eStart, iStartOffset)
    oRange.collapse(true)

    oSel.addRange(oRange)

    this.aRanges = [oRange]
  }
}

/**
 * @param {Object} eNode
 * @return {Object}
 */
CCrea.prototype.cloneNode = function (eNode) {
  var $clonedNode = null,
    sTagName = ''
  try {
    $clonedNode = $(eNode).clone()
  } catch (er) {
    sTagName = eNode.tagName
    $clonedNode = $('<' + sTagName + '></' + sTagName + '>')
  }

  return $clonedNode
}

/**
 * @param {Object} eFocused
 * @param {Object} eBlock
 * @param {number} iFocusOffset
 */
CCrea.prototype.breakBlocks = function (eFocused, eBlock, iFocusOffset) {
  var eCurrent = eFocused,
    eCurChild = null,
    aChildren = [],
    iIndex = 0,
    iLen = 0,
    eChild = null,
    bBeforeCurrent = true,
    $firstParent = null,
    $secondParent = null,
    $first = null,
    $second = null,
    bLast = false,
    bContinue = true,
    $span = null
  while (bContinue && eCurrent.parentNode) {
    $first = $firstParent
    $second = $secondParent

    $firstParent = this.cloneNode(eCurrent).empty()
    $secondParent = this.cloneNode(eCurrent).empty()

    aChildren = $(eCurrent).contents()
    iLen = aChildren.length
    bBeforeCurrent = true

    if (eCurChild === null) {
      eCurChild = aChildren[iFocusOffset]
    }
    if (iLen === 0) {
      $firstParent = null
    }

    for (iIndex = 0; iIndex < iLen; iIndex++) {
      eChild = aChildren[iIndex]
      if (eChild === eCurChild) {
        if ($first === null) {
          if (!(iIndex === iFocusOffset && eChild.tagName === 'BR')) {
            $(eChild).appendTo($secondParent)
          }
        } else {
          if ($first.html().length > 0) {
            $first.appendTo($firstParent)
          }

          $second.appendTo($secondParent)
        }
        bBeforeCurrent = false
      } else if (bBeforeCurrent) {
        $(eChild).appendTo($firstParent)
      } else {
        $(eChild).appendTo($secondParent)
      }
    }

    bLast = eBlock === eCurrent
    if (bLast) {
      bContinue = false
    }

    eCurChild = eCurrent
    eCurrent = eCurrent.parentNode
  }

  if ($firstParent !== null && $secondParent !== null) {
    $firstParent.insertBefore($(eBlock))
    $span = $('<span>&nbsp;</span>').insertBefore($(eBlock))
    $('<br>').insertBefore($(eBlock))
    $secondParent.insertBefore($(eBlock))

    $(eBlock).remove()
    this.setCursorPosition($span[0], 0)
  }
}

module.exports = CCrea