/**
* ResolveBookmark.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.ResolveBookmark',
[
'ephox.katamari.api.Option',
'ephox.katamari.api.Options',
'tinymce.core.Env',
'tinymce.core.caret.CaretBookmark',
'tinymce.core.caret.CaretPosition',
'tinymce.core.dom.NodeType',
'tinymce.core.util.Tools'
],
function (Option, Options, Env, CaretBookmark, CaretPosition, NodeType, Tools) {
var addBogus = function (dom, node) {
// Adds a bogus BR element for empty block elements
if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
node.innerHTML = '<br data-mce-bogus="1" />';
}
return node;
};
var resolveCaretPositionBookmark = function (dom, bookmark) {
var rng, pos;
rng = dom.createRng();
pos = CaretBookmark.resolve(dom.getRoot(), bookmark.start);
rng.setStart(pos.container(), pos.offset());
pos = CaretBookmark.resolve(dom.getRoot(), bookmark.end);
rng.setEnd(pos.container(), pos.offset());
return rng;
};
var setEndPoint = function (dom, start, bookmark, rng) {
var point = bookmark[start ? 'start' : 'end'], i, node, offset, children, root = dom.getRoot();
if (point) {
offset = point[0];
// Find container node
for (node = root, i = point.length - 1; i >= 1; i--) {
children = node.childNodes;
if (point[i] > children.length - 1) {
return;
}
node = children[point[i]];
}
// Move text offset to best suitable location
if (node.nodeType === 3) {
offset = Math.min(point[0], node.nodeValue.length);
}
// Move element offset to best suitable location
if (node.nodeType === 1) {
offset = Math.min(point[0], node.childNodes.length);
}
// Set offset within container node
if (start) {
rng.setStart(node, offset);
} else {
rng.setEnd(node, offset);
}
}
return true;
};
var restoreEndPoint = function (dom, suffix, bookmark) {
var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
var container, offset;
if (marker) {
node = marker.parentNode;
if (suffix === 'start') {
if (!keep) {
idx = dom.nodeIndex(marker);
} else {
node = marker.firstChild;
idx = 1;
}
container = node;
offset = idx;
} else {
if (!keep) {
idx = dom.nodeIndex(marker);
} else {
node = marker.firstChild;
idx = 1;
}
container = node;
offset = idx;
}
if (!keep) {
prev = marker.previousSibling;
next = marker.nextSibling;
// Remove all marker text nodes
Tools.each(Tools.grep(marker.childNodes), function (node) {
if (NodeType.isText(node)) {
node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
}
});
// Remove marker but keep children if for example contents where inserted into the marker
// Also remove duplicated instances of the marker for example by a
// split operation or by WebKit auto split on paste feature
while ((marker = dom.get(bookmark.id + '_' + suffix))) {
dom.remove(marker, 1);
}
// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
// isn't worth the effort. Sorry, Opera but it's just a fact
if (prev && next && prev.nodeType === next.nodeType && NodeType.isText(prev) && !Env.opera) {
idx = prev.nodeValue.length;
prev.appendData(next.nodeValue);
dom.remove(next);
if (suffix === 'start') {
container = prev;
offset = idx;
} else {
container = prev;
offset = idx;
}
}
}
return Option.some(CaretPosition(container, offset));
} else {
return Option.none();
}
};
var alt = function (o1, o2) {
return o1.isSome() ? o1 : o2;
};
var resolvePaths = function (dom, bookmark) {
var rng = dom.createRng();
if (setEndPoint(dom, true, bookmark, rng) && setEndPoint(dom, false, bookmark, rng)) {
return Option.some(rng);
} else {
return Option.none();
}
};
var resolveId = function (dom, bookmark) {
var startPos = restoreEndPoint(dom, 'start', bookmark);
var endPos = restoreEndPoint(dom, 'end', bookmark);
return Options.liftN([
startPos,
alt(endPos, startPos)
], function (spos, epos) {
var rng = dom.createRng();
rng.setStart(addBogus(dom, spos.container()), spos.offset());
rng.setEnd(addBogus(dom, epos.container()), epos.offset());
return rng;
});
};
var resolveIndex = function (dom, bookmark) {
return Option.from(dom.select(bookmark.name)[bookmark.index]).map(function (elm) {
var rng = dom.createRng();
rng.selectNode(elm);
return rng;
});
};
var resolve = function (selection, bookmark) {
var dom = selection.dom;
if (bookmark) {
if (Tools.isArray(bookmark.start)) {
return resolvePaths(dom, bookmark);
} else if (typeof bookmark.start === 'string') {
return Option.some(resolveCaretPositionBookmark(dom, bookmark));
} else if (bookmark.id) {
return resolveId(dom, bookmark);
} else if (bookmark.name) {
return resolveIndex(dom, bookmark);
} else if (bookmark.rng) {
return Option.some(bookmark.rng);
}
}
return Option.none();
};
return {
resolve: resolve
};
}
);
|