'use strict';

var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var editorCommon = require('@uidu/editor-common');
var editorJsonTransformer = require('@uidu/editor-json-transformer');
var baseCommand = require('prosemirror-commands');
var prosemirrorModel = require('prosemirror-model');
var prosemirrorState = require('prosemirror-state');
var prosemirrorTransform = require('prosemirror-transform');
var prosemirrorUtils = require('prosemirror-utils');
var _inherits = require('@babel/runtime/helpers/inherits');
var _createSuper = require('@babel/runtime/helpers/createSuper');
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _createClass = require('@babel/runtime/helpers/createClass');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var prosemirrorView = require('prosemirror-view');
var selection = require('@uidu/editor-common/selection');
var adfUtils = require('@uidu/adf-utils');
var mentions = require('@uidu/mentions');
var utils = require('@uidu/editor-common/utils');
var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator');
var _regeneratorRuntime = require('@babel/runtime/regenerator');
var _typeof = require('@babel/runtime/helpers/typeof');
var _assertThisInitialized = require('@babel/runtime/helpers/assertThisInitialized');

function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }

var _regeneratorRuntime__default = /*#__PURE__*/_interopDefault(_regeneratorRuntime);

var LEFT = 37;
var RIGHT = 39;
var UP = 38;
var DOWN = 40;
var KEY_0 = 48;
var KEY_1 = 49;
var KEY_2 = 50;
var KEY_3 = 51;
var KEY_4 = 52;
var KEY_5 = 53;
var KEY_6 = 54;
var HEADING_KEYS = [KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6];

var FakeTextCursorBookmark = /*#__PURE__*/function () {
  function FakeTextCursorBookmark(pos) {
    _classCallCheck(this, FakeTextCursorBookmark);
    _defineProperty(this, "pos", undefined);
    _defineProperty(this, "visible", false);
    this.pos = pos;
  }
  _createClass(FakeTextCursorBookmark, [{
    key: "map",
    value: function map(mapping) {
      return new FakeTextCursorBookmark(mapping.map(this.pos));
    }
  }, {
    key: "resolve",
    value: function resolve(doc) {
      var $pos = doc.resolve(this.pos);
      return prosemirrorState.Selection.near($pos);
    }
  }]);
  return FakeTextCursorBookmark;
}();
var FakeTextCursorSelection = /*#__PURE__*/function (_Selection) {
  _inherits(FakeTextCursorSelection, _Selection);
  var _super = _createSuper(FakeTextCursorSelection);
  function FakeTextCursorSelection($pos) {
    _classCallCheck(this, FakeTextCursorSelection);
    return _super.call(this, $pos, $pos);
  }
  _createClass(FakeTextCursorSelection, [{
    key: "map",
    value: function map(doc, mapping) {
      var $pos = doc.resolve(mapping.map(this.$head.pos));
      return new FakeTextCursorSelection($pos);
    }
  }, {
    key: "eq",
    value: function eq(other) {
      return other instanceof FakeTextCursorSelection && other.head === this.head;
    }
  }, {
    key: "toJSON",
    value: function toJSON() {
      return {
        type: 'Cursor',
        pos: this.head
      };
    }
  }, {
    key: "getBookmark",
    value: function getBookmark() {
      return new FakeTextCursorBookmark(this.anchor);
    }
  }], [{
    key: "content",
    value: function content() {
      return prosemirrorModel.Slice.empty;
    }
  }, {
    key: "fromJSON",
    value: function fromJSON(doc, json) {
      return new FakeTextCursorSelection(doc.resolve(json.pos));
    }
  }]);
  return FakeTextCursorSelection;
}(prosemirrorState.Selection);
prosemirrorState.Selection.jsonID('fake-text-cursor', FakeTextCursorSelection);
var drawFakeTextCursor = function drawFakeTextCursor(state) {
  if (!(state.selection instanceof FakeTextCursorSelection)) {
    return null;
  }
  var node = document.createElement('div');
  node.className = 'ProseMirror-fake-text-cursor';
  return prosemirrorView.DecorationSet.create(state.doc, [prosemirrorView.Decoration.widget(state.selection.head, node, {
    key: 'Cursor'
  })]);
};

/**
 * Sanitises a document where some content should not be in the document (e.g. mention names).
 *
 * It is expected that these names we be resolved separately (e.g. when rendering
 * a node view).
 */
function sanitizeNodeForPrivacy(json, providerFactory) {
  var mentionNames = new Map();
  var hasCacheableMentions = false;
  var sanitizedJSON = adfUtils.traverse(json, {
    mention: function mention(node) {
      if (node.attrs && node.attrs.text) {
        hasCacheableMentions = true;
        // Remove @ prefix
        var text = node.attrs.text;
        var name = text.startsWith('@') ? text.slice(1) : text;
        mentionNames.set(node.attrs.id, name);
      }
      return _objectSpread(_objectSpread({}, node), {}, {
        attrs: _objectSpread(_objectSpread({}, node.attrs), {}, {
          text: ''
        })
      });
    }
  });
  if (hasCacheableMentions && providerFactory) {
    var handler = function handler(_name, providerPromise) {
      if (providerPromise) {
        providerPromise.then(function (provider) {
          if (mentions.isResolvingMentionProvider(provider)) {
            mentionNames.forEach(function (name, id) {
              provider.cacheMentionName(id, name);
            });
            mentionNames.clear();
            providerFactory.unsubscribe('mentionProvider', handler);
          }
        });
      }
    };
    providerFactory.subscribe('mentionProvider', handler);
  }
  return sanitizedJSON;
}

/**
 * Checks if node is an empty paragraph.
 */
function isEmptyParagraph(node) {
  return !!node && node.type.name === 'paragraph' && !node.childCount;
}

/**
 * Returns false if node contains only empty inline nodes and hardBreaks.
 */
function hasVisibleContent(node) {
  var isInlineNodeHasVisibleContent = function isInlineNodeHasVisibleContent(inlineNode) {
    return inlineNode.isText ? !!inlineNode.textContent.trim() : inlineNode.type.name !== 'hardBreak';
  };
  if (node.isInline) {
    return isInlineNodeHasVisibleContent(node);
  } else if (node.isBlock && (node.isLeaf || node.isAtom)) {
    return true;
  } else if (!node.childCount) {
    return false;
  }
  for (var _index = 0; _index < node.childCount; _index++) {
    var child = node.child(_index);
    if (hasVisibleContent(child)) {
      return true;
    }
  }
  return false;
}

/**
 * Checks if a node has any content. Ignores node that only contain empty block nodes.
 */
function isNodeEmpty(node) {
  if (node && node.textContent) {
    return false;
  }
  if (!node || !node.childCount || node.childCount === 1 && isEmptyParagraph(node.firstChild)) {
    return true;
  }
  var block = [];
  var nonBlock = [];
  node.forEach(function (child) {
    child.isInline ? nonBlock.push(child) : block.push(child);
  });
  return !nonBlock.length && !block.filter(function (childNode) {
    return !!childNode.childCount && !(childNode.childCount === 1 && isEmptyParagraph(childNode.firstChild)) || childNode.isAtom;
  }).length;
}

/**
 * Checks if a node looks like an empty document
 */
function isEmptyDocument(node) {
  var nodeChild = node.content.firstChild;
  if (node.childCount !== 1 || !nodeChild) {
    return false;
  }
  return isEmptyParagraph(nodeChild);
}

// Checks to see if the parent node is the document, ie not contained within another entity
function hasDocAsParent($anchor) {
  return $anchor.depth === 1;
}
function isInEmptyLine(state) {
  var selection = state.selection;
  var _ref = selection,
    $cursor = _ref.$cursor,
    $anchor = _ref.$anchor;
  if (!$cursor) {
    return false;
  }
  var node = $cursor.node();
  if (!node) {
    return false;
  }
  return isEmptyParagraph(node) && hasDocAsParent($anchor);
}
function bracketTyped(state) {
  var selection = state.selection;
  var _ref2 = selection,
    $cursor = _ref2.$cursor,
    $anchor = _ref2.$anchor;
  if (!$cursor) {
    return false;
  }
  var node = $cursor.nodeBefore;
  if (!node) {
    return false;
  }
  if (node.type.name === 'text' && node.text === '{') {
    var paragraphNode = $anchor.node();
    return paragraphNode.marks.length === 0 && hasDocAsParent($anchor);
  }
  return false;
}
var maySanitizePrivateContent = function maySanitizePrivateContent(entity, providerFactory, sanitizePrivateContent) {
  if (sanitizePrivateContent && providerFactory) {
    return sanitizeNodeForPrivacy(entity, providerFactory);
  }
  return entity;
};
function processRawValue(schema, value, providerFactory, sanitizePrivateContent, contentTransformer, dispatchAnalyticsEvent) {
  if (!value) {
    return;
  }
  var node;
  if (typeof value === 'string') {
    try {
      if (contentTransformer) {
        var doc = contentTransformer.parse(value);
        node = doc.toJSON();
      } else {
        node = JSON.parse(value);
      }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.error("Error processing value: ".concat(value, " isn't a valid JSON"));
      return;
    }
  } else {
    node = value;
  }
  if (Array.isArray(node)) {
    // eslint-disable-next-line no-console
    console.error("Error processing value: ".concat(node, " is an array, but it must be an object."));
    return;
  }
  try {
    // ProseMirror always require a child under doc
    if (node.type === 'doc') {
      if (Array.isArray(node.content) && node.content.length === 0) {
        node.content.push({
          type: 'paragraph',
          content: []
        });
      }
      // Just making sure doc is always valid
      if (!node.version) {
        node.version = 1;
      }
    }
    if (contentTransformer) {
      return prosemirrorModel.Node.fromJSON(schema, node);
    }
    var entity = editorCommon.validateADFEntity(schema, node, dispatchAnalyticsEvent);
    var newEntity = maySanitizePrivateContent(entity, providerFactory, sanitizePrivateContent);
    var parsedDoc = prosemirrorModel.Node.fromJSON(schema, newEntity);

    // throws an error if the document is invalid
    parsedDoc.check();
    if (dispatchAnalyticsEvent) {
      editorCommon.findAndTrackUnsupportedContentNodes(parsedDoc, schema, dispatchAnalyticsEvent);
    }
    return parsedDoc;
  } catch (e) {
    // eslint-disable-next-line no-console
    console.error("Error processing document:\n".concat(e.message, "\n\n"), JSON.stringify(node));
    return;
  }
}
var getStepRange = function getStepRange(transaction) {
  var from = -1;
  var to = -1;
  transaction.steps.forEach(function (step) {
    step.getMap().forEach(function (_oldStart, _oldEnd, newStart, newEnd) {
      from = newStart < from || from === -1 ? newStart : from;
      to = newEnd < to || to === -1 ? newEnd : to;
    });
  });
  if (from !== -1) {
    return {
      from: from,
      to: to
    };
  }
  return null;
};

/**
 * Find the farthest node given a condition
 * @param predicate Function to check the node
 */
var findFarthestParentNode = function findFarthestParentNode(predicate) {
  return function ($pos) {
    var candidate = null;
    for (var i = $pos.depth; i > 0; i--) {
      var _node = $pos.node(i);
      if (predicate(_node)) {
        candidate = {
          pos: i > 0 ? $pos.before(i) : 0,
          start: $pos.start(i),
          depth: i,
          node: _node
        };
      }
    }
    return candidate;
  };
};
var isSelectionEndOfParagraph = function isSelectionEndOfParagraph(state) {
  return state.selection.$to.parent.type === state.schema.nodes.paragraph && state.selection.$to.pos === state.doc.resolve(state.selection.$to.pos).end();
};
function nodesBetweenChanged(tr, f, startPos) {
  var stepRange = getStepRange(tr);
  if (!stepRange) {
    return;
  }
  tr.doc.nodesBetween(stepRange.from, stepRange.to, f, startPos);
}
function getNodesCount(node) {
  var count = {};
  node.nodesBetween(0, node.nodeSize - 2, function (node) {
    count[node.type.name] = (count[node.type.name] || 0) + 1;
  });
  return count;
}

function atTheEndOfDoc(state) {
  var selection = state.selection,
    doc = state.doc;
  return doc.nodeSize - selection.$to.pos - 2 === selection.$to.depth;
}
function atTheBeginningOfDoc(state) {
  var selection = state.selection;
  return selection.$from.pos === selection.$from.depth;
}
function atTheEndOfBlock(state) {
  var selection$1 = state.selection;
  var $to = selection$1.$to;
  if (selection$1 instanceof selection.GapCursorSelection) {
    return false;
  }
  if (selection$1 instanceof prosemirrorState.NodeSelection && selection$1.node.isBlock) {
    return true;
  }
  return endPositionOfParent($to) === $to.pos + 1;
}
function atTheBeginningOfBlock(state) {
  var selection$1 = state.selection;
  var $from = selection$1.$from;
  if (selection$1 instanceof selection.GapCursorSelection) {
    return false;
  }
  if (selection$1 instanceof prosemirrorState.NodeSelection && selection$1.node.isBlock) {
    return true;
  }
  return startPositionOfParent($from) === $from.pos;
}
function startPositionOfParent(resolvedPos) {
  return resolvedPos.start(resolvedPos.depth);
}
function endPositionOfParent(resolvedPos) {
  return resolvedPos.end(resolvedPos.depth) + 1;
}

var stateKey = new prosemirrorState.PluginKey('mediaPlugin');

function getEditorValueWithMedia(_x) {
  return _getEditorValueWithMedia.apply(this, arguments);
}

/**
 * Iterates over the commands one after the other,
 * passes the tr through and dispatches the cumulated transaction
 */
function _getEditorValueWithMedia() {
  _getEditorValueWithMedia = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee(editorView) {
    var state, mediaPluginState;
    return _regeneratorRuntime__default["default"].wrap(function _callee$(_context) {
      while (1) switch (_context.prev = _context.next) {
        case 0:
          if (editorView) {
            _context.next = 2;
            break;
          }
          return _context.abrupt("return", undefined);
        case 2:
          state = editorView.state;
          mediaPluginState = state && stateKey.getState(state);
          if (mediaPluginState && mediaPluginState.waitForMediaUpload) ;
          return _context.abrupt("return", editorView.state.doc);
        case 6:
        case "end":
          return _context.stop();
      }
    }, _callee);
  }));
  return _getEditorValueWithMedia.apply(this, arguments);
}
function cascadeCommands(cmds) {
  return function (state, dispatch) {
    var baseTr = state.tr;
    var shouldDispatch = false;
    var onDispatchAction = function onDispatchAction(tr) {
      baseTr.setSelection(tr.selection.map(baseTr.doc, baseTr.mapping));
      tr.steps.forEach(function (st) {
        baseTr.step(st);
      });
      shouldDispatch = true;
    };
    cmds.forEach(function (cmd) {
      return cmd(state, onDispatchAction);
    });
    if (dispatch && shouldDispatch) {
      dispatch(baseTr);
      return true;
    }
    return false;
  };
}

var isMarkAllowedInRange = function isMarkAllowedInRange(doc, ranges, type) {
  var _loop = function _loop() {
      var _ranges$i = ranges[i],
        $from = _ranges$i.$from,
        $to = _ranges$i.$to;
      var can = $from.depth === 0 ? doc.type.allowsMarkType(type) : false;
      doc.nodesBetween($from.pos, $to.pos, function (node) {
        if (can) {
          return false;
        }
        can = node.inlineContent && node.type.allowsMarkType(type);
        return undefined;
      });
      if (can) {
        return {
          v: can
        };
      }
    },
    _ret;
  for (var i = 0; i < ranges.length; i++) {
    _ret = _loop();
    if (_ret) return _ret.v;
  }
  return false;
};
var isMarkExcluded = function isMarkExcluded(type, marks) {
  if (marks) {
    return marks.some(function (mark) {
      return mark.type !== type && mark.type.excludes(type);
    });
  }
  return false;
};
var not = function not(fn) {
  return function (arg) {
    return !fn(arg);
  };
};
var removeBlockMarks = function removeBlockMarks(state, marks) {
  var selection = state.selection,
    schema = state.schema;
  var tr = state.tr;

  // Marks might not exist in Schema
  var marksToRemove = marks.filter(Boolean);
  if (marksToRemove.length === 0) {
    return undefined;
  }

  /** Saves an extra dispatch */
  var blockMarksExists = false;
  var hasMark = function hasMark(mark) {
    return marksToRemove.indexOf(mark.type) > -1;
  };
  /**
   * When you need to toggle the selection
   * when another type which does not allow alignment is applied
   */
  state.doc.nodesBetween(selection.from, selection.to, function (node, pos) {
    if (node.type === schema.nodes.paragraph && node.marks.some(hasMark)) {
      blockMarksExists = true;
      var resolvedPos = state.doc.resolve(pos);
      var withoutBlockMarks = node.marks.filter(not(hasMark));
      tr = tr.setNodeMarkup(resolvedPos.pos, undefined, node.attrs, withoutBlockMarks);
    }
  });
  return blockMarksExists ? tr : undefined;
};

/**
 * Removes marks from nodes in the current selection that are not supported
 */
var sanitiseSelectionMarksForWrapping = function sanitiseSelectionMarksForWrapping(state, newParentType) {
  var tr;
  var _state$tr$selection = state.tr.selection,
    from = _state$tr$selection.from,
    to = _state$tr$selection.to;
  state.doc.nodesBetween(from, to, function (node, pos, parent) {
    // If iterate over a node thats out of our defined range
    // We skip here but continue to iterate over its children.
    if (node.isText || pos < from || pos > to) {
      return true;
    }
    node.marks.forEach(function (mark) {
      if (!parent.type.allowsMarkType(mark.type) || newParentType && !newParentType.allowsMarkType(mark.type)) {
        var filteredMarks = node.marks.filter(function (m) {
          return m.type !== mark.type;
        });
        var position = pos > 0 ? pos - 1 : 0;
        tr = (tr || state.tr).setNodeMarkup(position, undefined, node.attrs, filteredMarks);
      }
    });
    return undefined;
  }, from);
  return tr;
};

/**
 * Finds all top level nodes affected by the transaction
 * Uses from/to positions in transaction's steps to work out which nodes will
 * be changed by the transaction
 */
var findChangedNodesFromTransaction = function findChangedNodesFromTransaction(tr) {
  var nodes = [];
  var steps = tr.steps || [];
  steps.forEach(function (step) {
    var to = step.to,
      from = step.from,
      slice = step.slice;
    var size = slice && slice.content ? slice.content.size : 0;
    var _loop = function _loop() {
      if (i <= tr.doc.content.size) {
        var topLevelNode = tr.doc.resolve(i).node(1);
        if (topLevelNode && !nodes.find(function (n) {
          return n === topLevelNode;
        })) {
          nodes.push(topLevelNode);
        }
      }
    };
    for (var i = from; i <= to + size; i++) {
      _loop();
    }
  });
  return nodes;
};
var validNode = function validNode(node) {
  try {
    node.check(); // this will throw an error if the node is invalid
  } catch (error) {
    return false;
  }
  return true;
};

/** Validates prosemirror nodes, and returns true only if all nodes are valid */
var validateNodes = function validateNodes(nodes) {
  return nodes.every(validNode);
};
var isNodeTypeParagraph = function isNodeTypeParagraph(node) {
  return Boolean(node && node.type && node.type.name === 'paragraph');
};

/**
 * For more context on what this is about:
 * @see https://discuss.prosemirror.net/t/preventing-image-placeholder-replacement-from-being-undone/1394
 */
var SetAttrsStep = /*#__PURE__*/function (_Step) {
  _inherits(SetAttrsStep, _Step);
  var _super = _createSuper(SetAttrsStep);
  function SetAttrsStep(pos, attrs) {
    var _this;
    _classCallCheck(this, SetAttrsStep);
    _this = _super.call(this);
    _defineProperty(_assertThisInitialized(_this), "pos", void 0);
    _defineProperty(_assertThisInitialized(_this), "attrs", void 0);
    _this.pos = pos;
    _this.attrs = attrs;
    return _this;
  }
  _createClass(SetAttrsStep, [{
    key: "apply",
    value: function apply(doc) {
      var target = doc.nodeAt(this.pos);
      if (!target) {
        return prosemirrorTransform.StepResult.fail('No node at given position');
      }
      var attrs = _objectSpread(_objectSpread({}, target.attrs || {}), this.attrs || {});
      var newNode = target.type.create(attrs, prosemirrorModel.Fragment.empty, target.marks);
      var slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(newNode), 0, target.isLeaf ? 0 : 1);
      return prosemirrorTransform.StepResult.fromReplace(doc, this.pos, this.pos + 1, slice);
    }
  }, {
    key: "invert",
    value: function invert(doc) {
      var target = doc.nodeAt(this.pos);
      return new SetAttrsStep(this.pos, target ? target.attrs : {});
    }
  }, {
    key: "map",
    value: function map(mapping) {
      var result = mapping.mapResult(this.pos, 1);
      return result.deleted ? null : new SetAttrsStep(result.pos, this.attrs);
    }
  }, {
    key: "toJSON",
    value: function toJSON() {
      return {
        stepType: 'setAttrs',
        pos: this.pos,
        attrs: this.attrs
      };
    }
  }], [{
    key: "fromJSON",
    value: function fromJSON(_schema, json) {
      if (typeof json.pos !== 'number' || json.attrs !== null && _typeof(json.attrs) !== 'object') {
        throw new RangeError('Invalid input for SetAttrsStep.fromJSON');
      }
      return new SetAttrsStep(json.pos, json.attrs);
    }
  }]);
  return SetAttrsStep;
}(prosemirrorTransform.Step);
prosemirrorTransform.Step.jsonID('setAttrs', SetAttrsStep);

var ZeroWidthSpace = "\u200B";
function validateNode(_node) {
  return false;
}
function isMarkTypeCompatibleWithMark(markType, mark) {
  return !mark.type.excludes(markType) && !markType.excludes(mark.type);
}
function isMarkTypeAllowedInNode(markType, state) {
  return baseCommand.toggleMark(markType)(state);
}
function canMoveUp(state) {
  var selection = state.selection,
    doc = state.doc;

  /**
   * If there's a media element on the selection,
   * add text blocks with arrow navigation.
   * Also, the selection could be media | mediaGroup.
   */
  if (selection instanceof prosemirrorState.NodeSelection) {
    if (selection.node.type.name === 'media') {
      /** Weird way of checking if the previous element is a paragraph */
      var mediaAncestorNode = doc.nodeAt(selection.anchor - 3);
      return !!(mediaAncestorNode && mediaAncestorNode.type.name === 'paragraph');
    } else if (selection.node.type.name === 'mediaGroup') {
      var mediaGroupAncestorNode = selection.$anchor.nodeBefore;
      return !!(mediaGroupAncestorNode && mediaGroupAncestorNode.type.name === 'paragraph');
    }
  }
  if (selection instanceof prosemirrorState.TextSelection) {
    if (!selection.empty) {
      return true;
    }
  }
  return !atTheBeginningOfDoc(state);
}
function canMoveDown(state) {
  var selection = state.selection,
    doc = state.doc;

  /**
   * If there's a media element on the selection,
   * add text blocks with arrow navigation.
   * Also, the selection could be media | mediaGroup.
   */
  if (selection instanceof prosemirrorState.NodeSelection) {
    if (selection.node.type.name === 'media') {
      var nodeAfter = doc.nodeAt(selection.$head.after());
      return !!(nodeAfter && nodeAfter.type.name === 'paragraph');
    } else if (selection.node.type.name === 'mediaGroup') {
      return !(selection.$head.parentOffset === selection.$anchor.parent.content.size);
    }
  }
  if (selection instanceof prosemirrorState.TextSelection) {
    if (!selection.empty) {
      return true;
    }
  }
  return !atTheEndOfDoc(state);
}
function isSelectionInsideLastNodeInDocument(selection) {
  var docNode = selection.$anchor.node(0);
  var rootNode = selection.$anchor.node(1);
  return docNode.lastChild === rootNode;
}
function getCursor(selection) {
  return selection.$cursor || undefined;
}

/**
 * Check if a mark is allowed at the current selection / cursor based on a given state.
 * This method looks at both the currently active marks on the transaction, as well as
 * the node and marks at the current selection to determine if the given mark type is
 * allowed.
 */
function isMarkTypeAllowedInCurrentSelection(markType, state) {
  if (state.selection instanceof FakeTextCursorSelection) {
    return true;
  }
  if (!isMarkTypeAllowedInNode(markType, state)) {
    return false;
  }
  var _ref = state.selection,
    empty = _ref.empty,
    $cursor = _ref.$cursor,
    ranges = _ref.ranges;
  if (empty && !$cursor) {
    return false;
  }
  var isCompatibleMarkType = function isCompatibleMarkType(mark) {
    return isMarkTypeCompatibleWithMark(markType, mark);
  };

  // Handle any new marks in the current transaction
  if (state.tr.storedMarks && !state.tr.storedMarks.every(isCompatibleMarkType)) {
    return false;
  }
  if ($cursor) {
    return $cursor.marks().every(isCompatibleMarkType);
  }

  // Check every node in a selection - ensuring that it is compatible with the current mark type
  return ranges.every(function (_ref2) {
    var $from = _ref2.$from,
      $to = _ref2.$to;
    var allowedInActiveMarks = $from.depth === 0 ? state.doc.marks.every(isCompatibleMarkType) : true;
    state.doc.nodesBetween($from.pos, $to.pos, function (node) {
      allowedInActiveMarks = allowedInActiveMarks && node.marks.every(isCompatibleMarkType);
    });
    return allowedInActiveMarks;
  });
}
function createSliceWithContent(content, state) {
  return new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(state.schema.text(content)), 0, 0);
}

/**
 * Determines if content inside a selection can be joined with the next block.
 * We need this check since the built-in method for "joinDown" will join a orderedList with bulletList.
 */
function canJoinDown(selection, doc, nodeType) {
  return checkNodeDown(selection, doc, function (node) {
    return node.type === nodeType;
  });
}
function checkNodeDown(selection, doc, filter) {
  var ancestorDepth = findAncestorPosition(doc, selection.$to).depth;

  // Top level node
  if (ancestorDepth === 0) {
    return false;
  }
  var res = doc.resolve(selection.$to.after(ancestorDepth));
  return res.nodeAfter ? filter(res.nodeAfter) : false;
}
var setNodeSelection = function setNodeSelection(view, pos) {
  var state = view.state,
    dispatch = view.dispatch;
  if (!isFinite(pos)) {
    return;
  }
  var tr = state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, pos));
  dispatch(tr);
};
function setTextSelection(view, anchor, head) {
  var state = view.state;
  var tr = state.tr.setSelection(prosemirrorState.TextSelection.create(state.doc, anchor, head));
  view.dispatch(tr);
}
function setGapCursorSelection(view, pos, side) {
  var state = view.state;
  view.dispatch(state.tr.setSelection(new selection.GapCursorSelection(state.doc.resolve(pos), side)));
}

/**
 * Determines if content inside a selection can be joined with the previous block.
 * We need this check since the built-in method for "joinUp" will join a orderedList with bulletList.
 */
function canJoinUp(selection, doc, nodeType) {
  var res = doc.resolve(selection.$from.before(findAncestorPosition(doc, selection.$from).depth));
  return res.nodeBefore && res.nodeBefore.type === nodeType;
}

/**
 * Finds all "selection-groups" within a range. A selection group is based on ancestors.
 *
 * Example:
 * Given the following document and selection ({<} = start of selection and {>} = end)
 *  doc
 *    blockquote
 *      ul
 *        li
 *        li{<}
 *        li
 *     p
 *     p{>}
 *
 * The output will be two selection-groups. One within the ul and one with the two paragraphs.
 */
function getGroupsInRange(doc, $from, $to) {
  var isNodeValid = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : validateNode;
  var groups = Array();
  var commonAncestor = hasCommonAncestor(doc, $from, $to);
  var fromAncestor = findAncestorPosition(doc, $from);
  if (commonAncestor || fromAncestor.depth === 1 && isNodeValid($from.node(1))) {
    groups.push({
      $from: $from,
      $to: $to
    });
  } else {
    var current = $from;
    while (current.pos < $to.pos) {
      var ancestorPos = findAncestorPosition(doc, current);
      while (ancestorPos.depth > 1) {
        ancestorPos = findAncestorPosition(doc, ancestorPos);
      }
      var endPos = doc.resolve(Math.min(
      // should not be smaller then start position in case of an empty paragraph for example.
      Math.max(ancestorPos.start(ancestorPos.depth), ancestorPos.end(ancestorPos.depth) - 3), $to.pos));
      groups.push({
        $from: current,
        $to: endPos
      });
      current = doc.resolve(Math.min(endPos.after(1) + 1, doc.nodeSize - 2));
    }
  }
  return groups;
}

/**
 * Traverse the document until an "ancestor" is found. Any nestable block can be an ancestor.
 */
function findAncestorPosition(doc, pos) {
  var nestableBlocks = ['blockquote', 'bulletList', 'orderedList'];
  if (pos.depth === 1) {
    return pos;
  }
  var node = pos.node(pos.depth);
  var newPos = pos;
  while (pos.depth >= 1) {
    pos = doc.resolve(pos.before(pos.depth));
    node = pos.node(pos.depth);
    if (node && nestableBlocks.indexOf(node.type.name) !== -1) {
      newPos = pos;
    }
  }
  return newPos;
}

/**
 * Determine if two positions have a common ancestor.
 */
function hasCommonAncestor(doc, $from, $to) {
  var current;
  var target;
  if ($from.depth > $to.depth) {
    current = findAncestorPosition(doc, $from);
    target = findAncestorPosition(doc, $to);
  } else {
    current = findAncestorPosition(doc, $to);
    target = findAncestorPosition(doc, $from);
  }
  while (current.depth > target.depth && current.depth > 1) {
    current = findAncestorPosition(doc, current);
  }
  return current.node(current.depth) === target.node(target.depth);
}

/**
 * Takes a selection $from and $to and lift all text nodes from their parents to document-level
 */
function liftSelection(tr, doc, $from, $to) {
  var startPos = $from.start($from.depth);
  var endPos = $to.end($to.depth);
  var target = Math.max(0, findAncestorPosition(doc, $from).depth - 1);
  tr.doc.nodesBetween(startPos, endPos, function (node, pos) {
    if (node.isText ||
    // Text node
    node.isTextblock && !node.textContent // Empty paragraph
    ) {
      var res = tr.doc.resolve(tr.mapping.map(pos));
      var sel = new prosemirrorState.NodeSelection(res);
      var range = sel.$from.blockRange(sel.$to);
      if (prosemirrorTransform.liftTarget(range) !== undefined) {
        tr.lift(range, target);
      }
    }
  });
  startPos = tr.mapping.map(startPos);
  endPos = tr.mapping.map(endPos);
  endPos = tr.doc.resolve(endPos).end(tr.doc.resolve(endPos).depth); // We want to select the entire node

  tr.setSelection(new prosemirrorState.TextSelection(tr.doc.resolve(startPos), tr.doc.resolve(endPos)));
  return {
    tr: tr,
    $from: tr.doc.resolve(startPos),
    $to: tr.doc.resolve(endPos)
  };
}

/**
 * Lift nodes in block to one level above.
 */
function liftSiblingNodes(view) {
  var tr = view.state.tr;
  var _view$state$selection = view.state.selection,
    $from = _view$state$selection.$from,
    $to = _view$state$selection.$to;
  var blockStart = tr.doc.resolve($from.start($from.depth - 1));
  var blockEnd = tr.doc.resolve($to.end($to.depth - 1));
  var range = blockStart.blockRange(blockEnd);
  view.dispatch(tr.lift(range, blockStart.depth - 1));
}

/**
 * Lift sibling nodes to document-level and select them.
 */
function liftAndSelectSiblingNodes(view) {
  var tr = view.state.tr;
  var _view$state$selection2 = view.state.selection,
    $from = _view$state$selection2.$from,
    $to = _view$state$selection2.$to;
  var blockStart = tr.doc.resolve($from.start($from.depth - 1));
  var blockEnd = tr.doc.resolve($to.end($to.depth - 1));
  // TODO: [ts30] handle void and null properly
  var range = blockStart.blockRange(blockEnd);
  tr.setSelection(new prosemirrorState.TextSelection(blockStart, blockEnd));
  tr.lift(range, blockStart.depth - 1);
  return tr;
}
function wrapIn(nodeType, tr, $from, $to) {
  var range = $from.blockRange($to);
  var wrapping = range && prosemirrorTransform.findWrapping(range, nodeType);
  if (wrapping) {
    tr = tr.wrap(range, wrapping).scrollIntoView();
  }
  return tr;
}
var transformer = new editorJsonTransformer.JSONTransformer();
function toJSON(node) {
  return transformer.encode(node);
}

/**
 * Repeating string for multiple times
 */
function stringRepeat(text, length) {
  var result = '';
  for (var x = 0; x < length; x++) {
    result += text;
  }
  return result;
}

/**
 * A replacement for `Array.from` until it becomes widely implemented.
 */
function arrayFrom(obj) {
  return Array.prototype.slice.call(obj);
}

/*
 * From Modernizr
 * Returns the kind of transitionevent available for the element
 */
function whichTransitionEvent() {
  var el = document.createElement('fakeelement');
  var transitions = {
    transition: 'transitionend',
    MozTransition: 'transitionend',
    OTransition: 'oTransitionEnd',
    WebkitTransition: 'webkitTransitionEnd'
  };
  for (var t in transitions) {
    if (el.style[t] !== undefined) {
      // Use a generic as the return type because TypeScript doesnt know
      // about cross browser features, so we cast here to align to the
      // standard Event spec and propagate the type properly to the callbacks
      // of `addEventListener` and `removeEventListener`.
      return transitions[t];
    }
  }
  return undefined;
}
function moveLeft(view) {
  var event = new CustomEvent('keydown', {
    bubbles: true,
    cancelable: true
  });
  event.keyCode = LEFT;
  view.dispatchEvent(event);
}

/**
 * Function will create a list of wrapper blocks present in a selection.
 */
function getSelectedWrapperNodes(state) {
  var nodes = [];
  if (state.selection) {
    var _state$selection = state.selection,
      $from = _state$selection.$from,
      $to = _state$selection.$to;
    var _state$schema$nodes = state.schema.nodes,
      blockquote = _state$schema$nodes.blockquote,
      panel = _state$schema$nodes.panel,
      orderedList = _state$schema$nodes.orderedList,
      bulletList = _state$schema$nodes.bulletList,
      listItem = _state$schema$nodes.listItem,
      codeBlock = _state$schema$nodes.codeBlock,
      decisionItem = _state$schema$nodes.decisionItem,
      decisionList = _state$schema$nodes.decisionList,
      taskItem = _state$schema$nodes.taskItem,
      taskList = _state$schema$nodes.taskList;
    state.doc.nodesBetween($from.pos, $to.pos, function (node) {
      if (node.isBlock && [blockquote, panel, orderedList, bulletList, listItem, codeBlock, decisionItem, decisionList, taskItem, taskList].indexOf(node.type) >= 0) {
        nodes.push(node.type);
      }
    });
  }
  return nodes;
}

/**
 * Function will check if changing block types: Paragraph, Heading is enabled.
 */
function areBlockTypesDisabled(state) {
  var nodesTypes = getSelectedWrapperNodes(state);
  var panel = state.schema.nodes.panel;
  return nodesTypes.filter(function (type) {
    return type !== panel;
  }).length > 0;
}
var isTemporary = function isTemporary(id) {
  return id.indexOf('temporary:') === 0;
};

// @see: https://github.com/ProseMirror/prosemirror/issues/710
// @see: https://bugs.chromium.org/p/chromium/issues/detail?id=740085
// Chrome >= 58 (desktop only)
var isChromeWithSelectionBug = editorCommon.browser.chrome && !editorCommon.browser.android && editorCommon.browser.chrome_version >= 58;
var isEmptyNode = function isEmptyNode(schema) {
  var _schema$nodes = schema.nodes,
    doc = _schema$nodes.doc,
    paragraph = _schema$nodes.paragraph,
    codeBlock = _schema$nodes.codeBlock,
    blockquote = _schema$nodes.blockquote,
    panel = _schema$nodes.panel,
    heading = _schema$nodes.heading,
    listItem = _schema$nodes.listItem,
    bulletList = _schema$nodes.bulletList,
    orderedList = _schema$nodes.orderedList,
    taskList = _schema$nodes.taskList,
    taskItem = _schema$nodes.taskItem,
    decisionList = _schema$nodes.decisionList,
    decisionItem = _schema$nodes.decisionItem,
    media = _schema$nodes.media,
    mediaGroup = _schema$nodes.mediaGroup,
    mediaSingle = _schema$nodes.mediaSingle;
  var innerIsEmptyNode = function innerIsEmptyNode(node) {
    switch (node.type) {
      case media:
      case mediaGroup:
      case mediaSingle:
        return false;
      case paragraph:
      case codeBlock:
      case heading:
      case taskItem:
      case decisionItem:
        return node.content.size === 0;
      case blockquote:
      case panel:
      case listItem:
        return node.content.size === 2 && innerIsEmptyNode(node.content.firstChild);
      case bulletList:
      case orderedList:
        return node.content.size === 4 && innerIsEmptyNode(node.content.firstChild);
      case taskList:
      case decisionList:
        return node.content.size === 2 && innerIsEmptyNode(node.content.firstChild);
      case doc:
        var isEmpty = true;
        node.content.forEach(function (child) {
          isEmpty = isEmpty && innerIsEmptyNode(child);
        });
        return isEmpty;
      default:
        return isNodeEmpty(node);
    }
  };
  return innerIsEmptyNode;
};
var insideTable = function insideTable(state) {
  var _state$schema$nodes2 = state.schema.nodes,
    table = _state$schema$nodes2.table,
    tableCell = _state$schema$nodes2.tableCell;
  return prosemirrorUtils.hasParentNodeOfType([table, tableCell])(state.selection);
};
var insideTableCell = function insideTableCell(state) {
  var _state$schema$nodes3 = state.schema.nodes,
    tableCell = _state$schema$nodes3.tableCell,
    tableHeader = _state$schema$nodes3.tableHeader;
  return prosemirrorUtils.hasParentNodeOfType([tableCell, tableHeader])(state.selection);
};
var isElementInTableCell = function isElementInTableCell(element) {
  return utils.closest(element, 'td') || utils.closest(element, 'th');
};
var isLastItemMediaGroup = function isLastItemMediaGroup(node) {
  var content = node.content;
  return !!content.lastChild && content.lastChild.type.name === 'mediaGroup';
};
var isInListItem = function isInListItem(state) {
  return prosemirrorUtils.hasParentNodeOfType(state.schema.nodes.listItem)(state.selection);
};
var hasOpenEnd = function hasOpenEnd(slice) {
  return slice.openStart > 0 || slice.openEnd > 0;
};
function filterChildrenBetween(doc, from, to, predicate) {
  var results = [];
  doc.nodesBetween(from, to, function (node, pos, parent) {
    if (predicate(node, pos, parent)) {
      results.push({
        node: node,
        pos: pos
      });
    }
  });
  return results;
}
function dedupe() {
  var list = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  var iteratee = arguments.length > 1 ? arguments[1] : undefined;
  var transformed = iteratee ? list.map(iteratee) : list;
  return transformed.map(function (item, index, list) {
    return list.indexOf(item) === index ? item : null;
  }).reduce(function (acc, item, index) {
    return !!item ? acc.concat(list[index]) : acc;
  }, []);
}
var isTextSelection = function isTextSelection(selection) {
  return selection instanceof prosemirrorState.TextSelection;
};

/** Helper type for single arg function */

/**
 * Compose 1 to n functions.
 * @param func first function
 * @param funcs additional functions
 */
function compose(func) {
  for (var _len = arguments.length, funcs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    funcs[_key - 1] = arguments[_key];
  }
  var allFuncs = [func].concat(funcs);
  return function composed(raw) {
    return allFuncs.reduceRight(function (memo, func) {
      return func(memo);
    }, raw);
  };
}

// one function

// two function

// three function

// If needed add more than 3 function
// Generic

// rest
function pipe() {
  for (var _len2 = arguments.length, fns = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
    fns[_key2] = arguments[_key2];
  }
  if (fns.length === 0) {
    return function (a) {
      return a;
    };
  }
  if (fns.length === 1) {
    return fns[0];
  }
  return fns.reduce(function (prevFn, nextFn) {
    return function () {
      return nextFn(prevFn.apply(void 0, arguments));
    };
  });
}
var normaliseNestedLayout = function normaliseNestedLayout(state, node) {
  if (state.selection.$from.depth > 1) {
    if (node.attrs.layout && node.attrs.layout !== 'default') {
      return node.type.createChecked(_objectSpread(_objectSpread({}, node.attrs), {}, {
        layout: 'default'
      }), node.content, node.marks);
    }

    // If its a breakout layout, we can remove the mark
    // Since default isn't a valid breakout mode.
    var breakoutMark = state.schema.marks.breakout;
    if (breakoutMark && breakoutMark.isInSet(node.marks)) {
      var newMarks = breakoutMark.removeFromSet(node.marks);
      return node.type.createChecked(node.attrs, node.content, newMarks);
    }
  }
  return node;
};
function shallowEqual() {
  var obj1 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  var obj2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  var keys1 = Object.keys(obj1);
  var keys2 = Object.keys(obj2);
  return keys1.length === keys2.length && keys1.reduce(function (acc, key) {
    return acc && obj1[key] === obj2[key];
  }, true);
}
function sum(arr, f) {
  return arr.reduce(function (val, x) {
    return val + f(x);
  }, 0);
}
function nonNullable(value) {
  return value !== null && value !== undefined;
}

exports.DOWN = DOWN;
exports.FakeTextCursorSelection = FakeTextCursorSelection;
exports.HEADING_KEYS = HEADING_KEYS;
exports.KEY_0 = KEY_0;
exports.KEY_1 = KEY_1;
exports.KEY_2 = KEY_2;
exports.KEY_3 = KEY_3;
exports.KEY_4 = KEY_4;
exports.KEY_5 = KEY_5;
exports.KEY_6 = KEY_6;
exports.LEFT = LEFT;
exports.RIGHT = RIGHT;
exports.SetAttrsStep = SetAttrsStep;
exports.UP = UP;
exports.ZeroWidthSpace = ZeroWidthSpace;
exports.areBlockTypesDisabled = areBlockTypesDisabled;
exports.arrayFrom = arrayFrom;
exports.atTheBeginningOfBlock = atTheBeginningOfBlock;
exports.atTheBeginningOfDoc = atTheBeginningOfDoc;
exports.atTheEndOfBlock = atTheEndOfBlock;
exports.atTheEndOfDoc = atTheEndOfDoc;
exports.bracketTyped = bracketTyped;
exports.canJoinDown = canJoinDown;
exports.canJoinUp = canJoinUp;
exports.canMoveDown = canMoveDown;
exports.canMoveUp = canMoveUp;
exports.cascadeCommands = cascadeCommands;
exports.checkNodeDown = checkNodeDown;
exports.compose = compose;
exports.createSliceWithContent = createSliceWithContent;
exports.dedupe = dedupe;
exports.drawFakeTextCursor = drawFakeTextCursor;
exports.endPositionOfParent = endPositionOfParent;
exports.filterChildrenBetween = filterChildrenBetween;
exports.findAncestorPosition = findAncestorPosition;
exports.findChangedNodesFromTransaction = findChangedNodesFromTransaction;
exports.findFarthestParentNode = findFarthestParentNode;
exports.getCursor = getCursor;
exports.getEditorValueWithMedia = getEditorValueWithMedia;
exports.getGroupsInRange = getGroupsInRange;
exports.getNodesCount = getNodesCount;
exports.getStepRange = getStepRange;
exports.hasCommonAncestor = hasCommonAncestor;
exports.hasOpenEnd = hasOpenEnd;
exports.hasVisibleContent = hasVisibleContent;
exports.insideTable = insideTable;
exports.insideTableCell = insideTableCell;
exports.isChromeWithSelectionBug = isChromeWithSelectionBug;
exports.isElementInTableCell = isElementInTableCell;
exports.isEmptyDocument = isEmptyDocument;
exports.isEmptyNode = isEmptyNode;
exports.isEmptyParagraph = isEmptyParagraph;
exports.isInEmptyLine = isInEmptyLine;
exports.isInListItem = isInListItem;
exports.isLastItemMediaGroup = isLastItemMediaGroup;
exports.isMarkAllowedInRange = isMarkAllowedInRange;
exports.isMarkExcluded = isMarkExcluded;
exports.isMarkTypeAllowedInCurrentSelection = isMarkTypeAllowedInCurrentSelection;
exports.isNodeEmpty = isNodeEmpty;
exports.isNodeTypeParagraph = isNodeTypeParagraph;
exports.isSelectionEndOfParagraph = isSelectionEndOfParagraph;
exports.isSelectionInsideLastNodeInDocument = isSelectionInsideLastNodeInDocument;
exports.isTemporary = isTemporary;
exports.isTextSelection = isTextSelection;
exports.liftAndSelectSiblingNodes = liftAndSelectSiblingNodes;
exports.liftSelection = liftSelection;
exports.liftSiblingNodes = liftSiblingNodes;
exports.moveLeft = moveLeft;
exports.nodesBetweenChanged = nodesBetweenChanged;
exports.nonNullable = nonNullable;
exports.normaliseNestedLayout = normaliseNestedLayout;
exports.pipe = pipe;
exports.processRawValue = processRawValue;
exports.removeBlockMarks = removeBlockMarks;
exports.sanitiseSelectionMarksForWrapping = sanitiseSelectionMarksForWrapping;
exports.setGapCursorSelection = setGapCursorSelection;
exports.setNodeSelection = setNodeSelection;
exports.setTextSelection = setTextSelection;
exports.shallowEqual = shallowEqual;
exports.startPositionOfParent = startPositionOfParent;
exports.stateKey = stateKey;
exports.stringRepeat = stringRepeat;
exports.sum = sum;
exports.toJSON = toJSON;
exports.validNode = validNode;
exports.validateNodes = validateNodes;
exports.whichTransitionEvent = whichTransitionEvent;
exports.wrapIn = wrapIn;
