| /**
 * GetBookmark.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
 */
define(
  'tinymce.core.dom.GetBookmark',
  [
    'ephox.katamari.api.Fun',
    'tinymce.core.caret.CaretBookmark',
    'tinymce.core.caret.CaretContainer',
    'tinymce.core.caret.CaretPosition',
    'tinymce.core.dom.NodeType',
    'tinymce.core.selection.RangeNodes',
    'tinymce.core.text.Zwsp',
    'tinymce.core.util.Tools'
  ],
  function (Fun, CaretBookmark, CaretContainer, CaretPosition, NodeType, RangeNodes, Zwsp, Tools) {
    var isContentEditableFalse = NodeType.isContentEditableFalse;
    var getNormalizedTextOffset = function (trim, container, offset) {
      var node, trimmedOffset;
      trimmedOffset = trim(container.data.slice(0, offset)).length;
      for (node = container.previousSibling; node && NodeType.isText(node); node = node.previousSibling) {
        trimmedOffset += trim(node.data).length;
      }
      return trimmedOffset;
    };
    var getPoint = function (dom, trim, normalized, rng, start) {
      var container = rng[start ? 'startContainer' : 'endContainer'];
      var offset = rng[start ? 'startOffset' : 'endOffset'], point = [], childNodes, after = 0;
      var root = dom.getRoot();
      if (NodeType.isText(container)) {
        point.push(normalized ? getNormalizedTextOffset(trim, container, offset) : offset);
      } else {
        childNodes = container.childNodes;
        if (offset >= childNodes.length && childNodes.length) {
          after = 1;
          offset = Math.max(0, childNodes.length - 1);
        }
        point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
      }
      for (; container && container !== root; container = container.parentNode) {
        point.push(dom.nodeIndex(container, normalized));
      }
      return point;
    };
    var getLocation = function (trim, selection, normalized, rng) {
      var dom = selection.dom, bookmark = {};
      bookmark.start = getPoint(dom, trim, normalized, rng, true);
      if (!selection.isCollapsed()) {
        bookmark.end = getPoint(dom, trim, normalized, rng, false);
      }
      return bookmark;
    };
    var trimEmptyTextNode = function (node) {
      if (NodeType.isText(node) && node.data.length === 0) {
        node.parentNode.removeChild(node);
      }
    };
    var findIndex = function (dom, name, element) {
      var count = 0;
      Tools.each(dom.select(name), function (node) {
        if (node.getAttribute('data-mce-bogus') === 'all') {
          return;
        }
        if (node === element) {
          return false;
        }
        count++;
      });
      return count;
    };
    var moveEndPoint = function (rng, start) {
      var container, offset, childNodes, prefix = start ? 'start' : 'end';
      container = rng[prefix + 'Container'];
      offset = rng[prefix + 'Offset'];
      if (NodeType.isElement(container) && container.nodeName === 'TR') {
        childNodes = container.childNodes;
        container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
        if (container) {
          offset = start ? 0 : container.childNodes.length;
          rng['set' + (start ? 'Start' : 'End')](container, offset);
        }
      }
    };
    var normalizeTableCellSelection = function (rng) {
      moveEndPoint(rng, true);
      moveEndPoint(rng, false);
      return rng;
    };
    var findSibling = function (node, offset) {
      var sibling;
      if (NodeType.isElement(node)) {
        node = RangeNodes.getNode(node, offset);
        if (isContentEditableFalse(node)) {
          return node;
        }
      }
      if (CaretContainer.isCaretContainer(node)) {
        if (NodeType.isText(node) && CaretContainer.isCaretContainerBlock(node)) {
          node = node.parentNode;
        }
        sibling = node.previousSibling;
        if (isContentEditableFalse(sibling)) {
          return sibling;
        }
        sibling = node.nextSibling;
        if (isContentEditableFalse(sibling)) {
          return sibling;
        }
      }
    };
    var findAdjacentContentEditableFalseElm = function (rng) {
      return findSibling(rng.startContainer, rng.startOffset) || findSibling(rng.endContainer, rng.endOffset);
    };
    var getOffsetBookmark = function (trim, normalized, selection) {
      var element = selection.getNode();
      var name = element ? element.nodeName : null;
      var rng = selection.getRng();
      if (isContentEditableFalse(element) || name === 'IMG') {
        return { name: name, index: findIndex(selection.dom, name, element) };
      }
      element = findAdjacentContentEditableFalseElm(rng);
      if (element) {
        name = element.tagName;
        return { name: name, index: findIndex(selection.dom, name, element) };
      }
      return getLocation(trim, selection, normalized, rng);
    };
    var getCaretBookmark = function (selection) {
      var rng = selection.getRng();
      return {
        start: CaretBookmark.create(selection.dom.getRoot(), CaretPosition.fromRangeStart(rng)),
        end: CaretBookmark.create(selection.dom.getRoot(), CaretPosition.fromRangeEnd(rng))
      };
    };
    var getRangeBookmark = function (selection) {
      return { rng: selection.getRng() };
    };
    var getPersistentBookmark = function (selection) {
      var dom = selection.dom;
      var rng = selection.getRng();
      var id = dom.uniqueId();
      var collapsed = selection.isCollapsed();
      var styles = 'overflow:hidden;line-height:0px';
      var element = selection.getNode();
      var name = element.nodeName;
      var chr = '';
      if (name === 'IMG') {
        return { name: name, index: findIndex(dom, name, element) };
      }
      // W3C method
      var rng2 = normalizeTableCellSelection(rng.cloneRange());
      // Insert end marker
      if (!collapsed) {
        rng2.collapse(false);
        var endBookmarkNode = dom.create('span', { 'data-mce-type': 'bookmark', id: id + '_end', style: styles }, chr);
        rng2.insertNode(endBookmarkNode);
        trimEmptyTextNode(endBookmarkNode.nextSibling);
      }
      rng = normalizeTableCellSelection(rng);
      rng.collapse(true);
      var startBookmarkNode = dom.create('span', { 'data-mce-type': 'bookmark', id: id + '_start', style: styles }, chr);
      rng.insertNode(startBookmarkNode);
      trimEmptyTextNode(startBookmarkNode.previousSibling);
      selection.moveToBookmark({ id: id, keep: 1 });
      return { id: id };
    };
    var getBookmark = function (selection, type, normalized) {
      if (type === 2) {
        return getOffsetBookmark(Zwsp.trim, normalized, selection);
      } else if (type === 3) {
        return getCaretBookmark(selection);
      } else if (type) {
        return getRangeBookmark(selection);
      } else {
        return getPersistentBookmark(selection);
      }
    };
    return {
      getBookmark: getBookmark,
      getUndoBookmark: Fun.curry(getOffsetBookmark, Fun.identity, true)
    };
  }
);
 |