| /**
 * DragDropOverrides.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */
/**
 * This module contains logic overriding the drag/drop logic of the editor.
 *
 * @private
 * @class tinymce.DragDropOverrides
 */
define(
  'tinymce.core.DragDropOverrides',
  [
    'global!document',
    'tinymce.core.dom.DOMUtils',
    'tinymce.core.dom.MousePosition',
    'tinymce.core.dom.NodeType',
    'tinymce.core.util.Arr',
    'tinymce.core.util.Delay',
    'tinymce.core.util.Fun'
  ],
  function (document, DOMUtils, MousePosition, NodeType, Arr, Delay, Fun) {
    var isContentEditableFalse = NodeType.isContentEditableFalse,
      isContentEditableTrue = NodeType.isContentEditableTrue;
    var isDraggable = function (rootElm, elm) {
      return isContentEditableFalse(elm) && elm !== rootElm;
    };
    var isValidDropTarget = function (editor, targetElement, dragElement) {
      if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) {
        return false;
      }
      if (isContentEditableFalse(targetElement)) {
        return false;
      }
      return true;
    };
    var cloneElement = function (elm) {
      var cloneElm = elm.cloneNode(true);
      cloneElm.removeAttribute('data-mce-selected');
      return cloneElm;
    };
    var createGhost = function (editor, elm, width, height) {
      var clonedElm = elm.cloneNode(true);
      editor.dom.setStyles(clonedElm, { width: width, height: height });
      editor.dom.setAttrib(clonedElm, 'data-mce-selected', null);
      var ghostElm = editor.dom.create('div', {
        'class': 'mce-drag-container',
        'data-mce-bogus': 'all',
        unselectable: 'on',
        contenteditable: 'false'
      });
      editor.dom.setStyles(ghostElm, {
        position: 'absolute',
        opacity: 0.5,
        overflow: 'hidden',
        border: 0,
        padding: 0,
        margin: 0,
        width: width,
        height: height
      });
      editor.dom.setStyles(clonedElm, {
        margin: 0,
        boxSizing: 'border-box'
      });
      ghostElm.appendChild(clonedElm);
      return ghostElm;
    };
    var appendGhostToBody = function (ghostElm, bodyElm) {
      if (ghostElm.parentNode !== bodyElm) {
        bodyElm.appendChild(ghostElm);
      }
    };
    var moveGhost = function (ghostElm, position, width, height, maxX, maxY) {
      var overflowX = 0, overflowY = 0;
      ghostElm.style.left = position.pageX + 'px';
      ghostElm.style.top = position.pageY + 'px';
      if (position.pageX + width > maxX) {
        overflowX = (position.pageX + width) - maxX;
      }
      if (position.pageY + height > maxY) {
        overflowY = (position.pageY + height) - maxY;
      }
      ghostElm.style.width = (width - overflowX) + 'px';
      ghostElm.style.height = (height - overflowY) + 'px';
    };
    var removeElement = function (elm) {
      if (elm && elm.parentNode) {
        elm.parentNode.removeChild(elm);
      }
    };
    var isLeftMouseButtonPressed = function (e) {
      return e.button === 0;
    };
    var hasDraggableElement = function (state) {
      return state.element;
    };
    var applyRelPos = function (state, position) {
      return {
        pageX: position.pageX - state.relX,
        pageY: position.pageY + 5
      };
    };
    var start = function (state, editor) {
      return function (e) {
        if (isLeftMouseButtonPressed(e)) {
          var ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue));
          if (isDraggable(editor.getBody(), ceElm)) {
            var elmPos = editor.dom.getPos(ceElm);
            var bodyElm = editor.getBody();
            var docElm = editor.getDoc().documentElement;
            state.element = ceElm;
            state.screenX = e.screenX;
            state.screenY = e.screenY;
            state.maxX = (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2;
            state.maxY = (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2;
            state.relX = e.pageX - elmPos.x;
            state.relY = e.pageY - elmPos.y;
            state.width = ceElm.offsetWidth;
            state.height = ceElm.offsetHeight;
            state.ghost = createGhost(editor, ceElm, state.width, state.height);
          }
        }
      };
    };
    var move = function (state, editor) {
      // Reduces laggy drag behavior on Gecko
      var throttledPlaceCaretAt = Delay.throttle(function (clientX, clientY) {
        editor._selectionOverrides.hideFakeCaret();
        editor.selection.placeCaretAt(clientX, clientY);
      }, 0);
      return function (e) {
        var movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY));
        if (hasDraggableElement(state) && !state.dragging && movement > 10) {
          var args = editor.fire('dragstart', { target: state.element });
          if (args.isDefaultPrevented()) {
            return;
          }
          state.dragging = true;
          editor.focus();
        }
        if (state.dragging) {
          var targetPos = applyRelPos(state, MousePosition.calc(editor, e));
          appendGhostToBody(state.ghost, editor.getBody());
          moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY);
          throttledPlaceCaretAt(e.clientX, e.clientY);
        }
      };
    };
    // Returns the raw element instead of the fake cE=false element
    var getRawTarget = function (selection) {
      var rng = selection.getSel().getRangeAt(0);
      var startContainer = rng.startContainer;
      return startContainer.nodeType === 3 ? startContainer.parentNode : startContainer;
    };
    var drop = function (state, editor) {
      return function (e) {
        if (state.dragging) {
          if (isValidDropTarget(editor, getRawTarget(editor.selection), state.element)) {
            var targetClone = cloneElement(state.element);
            var args = editor.fire('drop', {
              targetClone: targetClone,
              clientX: e.clientX,
              clientY: e.clientY
            });
            if (!args.isDefaultPrevented()) {
              targetClone = args.targetClone;
              editor.undoManager.transact(function () {
                removeElement(state.element);
                editor.insertContent(editor.dom.getOuterHTML(targetClone));
                editor._selectionOverrides.hideFakeCaret();
              });
            }
          }
        }
        removeDragState(state);
      };
    };
    var stop = function (state, editor) {
      return function () {
        removeDragState(state);
        if (state.dragging) {
          editor.fire('dragend');
        }
      };
    };
    var removeDragState = function (state) {
      state.dragging = false;
      state.element = null;
      removeElement(state.ghost);
    };
    var bindFakeDragEvents = function (editor) {
      var state = {}, pageDom, dragStartHandler, dragHandler, dropHandler, dragEndHandler, rootDocument;
      pageDom = DOMUtils.DOM;
      rootDocument = document;
      dragStartHandler = start(state, editor);
      dragHandler = move(state, editor);
      dropHandler = drop(state, editor);
      dragEndHandler = stop(state, editor);
      editor.on('mousedown', dragStartHandler);
      editor.on('mousemove', dragHandler);
      editor.on('mouseup', dropHandler);
      pageDom.bind(rootDocument, 'mousemove', dragHandler);
      pageDom.bind(rootDocument, 'mouseup', dragEndHandler);
      editor.on('remove', function () {
        pageDom.unbind(rootDocument, 'mousemove', dragHandler);
        pageDom.unbind(rootDocument, 'mouseup', dragEndHandler);
      });
    };
    var blockIeDrop = function (editor) {
      editor.on('drop', function (e) {
        // FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead
        var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null;
        if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) {
          e.preventDefault();
        }
      });
    };
    var init = function (editor) {
      bindFakeDragEvents(editor);
      blockIeDrop(editor);
    };
    return {
      init: init
    };
  }
);
 |