v1.0
只读模式 group, 分享 评论更多问题 博客标签总是存在一个
This commit is contained in:
278
public/tinymce/classes/dom/RangeUtils.js
Normal file → Executable file
278
public/tinymce/classes/dom/RangeUtils.js
Normal file → Executable file
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Range.js
|
||||
* RangeUtils.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
@ -9,16 +9,30 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* RangeUtils
|
||||
* This class contains a few utility methods for ranges.
|
||||
*
|
||||
* @class tinymce.dom.RangeUtils
|
||||
* @private
|
||||
*/
|
||||
define("tinymce/dom/RangeUtils", [
|
||||
"tinymce/util/Tools"
|
||||
], function(Tools) {
|
||||
"tinymce/util/Tools",
|
||||
"tinymce/dom/TreeWalker"
|
||||
], function(Tools, TreeWalker) {
|
||||
var each = Tools.each;
|
||||
|
||||
function getEndChild(container, index) {
|
||||
var childNodes = container.childNodes;
|
||||
|
||||
index--;
|
||||
|
||||
if (index > childNodes.length - 1) {
|
||||
index = childNodes.length - 1;
|
||||
} else if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
return childNodes[index] || container;
|
||||
}
|
||||
|
||||
function RangeUtils(dom) {
|
||||
/**
|
||||
* Walks the specified range like object and executes the callback for each sibling collection it finds.
|
||||
@ -104,7 +118,7 @@ define("tinymce/dom/RangeUtils", [
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
} while(node);
|
||||
} while (node);
|
||||
}
|
||||
|
||||
function walkBoundary(start_node, end_node, next) {
|
||||
@ -131,7 +145,7 @@ define("tinymce/dom/RangeUtils", [
|
||||
|
||||
// If index based end position then resolve it
|
||||
if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
|
||||
endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
|
||||
endContainer = getEndChild(endContainer, endOffset);
|
||||
}
|
||||
|
||||
// Same container
|
||||
@ -239,6 +253,209 @@ define("tinymce/dom/RangeUtils", [
|
||||
endOffset: endOffset
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes the specified range by finding the closest best suitable caret location.
|
||||
*
|
||||
* @private
|
||||
* @param {Range} rng Range to normalize.
|
||||
* @return {Boolean} True/false if the specified range was normalized or not.
|
||||
*/
|
||||
this.normalize = function(rng) {
|
||||
var normalized, collapsed;
|
||||
|
||||
function normalizeEndPoint(start) {
|
||||
var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
|
||||
var directionLeft, isAfterNode;
|
||||
|
||||
function hasBrBeforeAfter(node, left) {
|
||||
var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
|
||||
|
||||
while ((node = walker[left ? 'prev' : 'next']())) {
|
||||
if (node.nodeName === "BR") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPrevNode(node, name) {
|
||||
return node.previousSibling && node.previousSibling.nodeName == name;
|
||||
}
|
||||
|
||||
// Walks the dom left/right to find a suitable text node to move the endpoint into
|
||||
// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
|
||||
function findTextNodeRelative(left, startNode) {
|
||||
var walker, lastInlineElement, parentBlockContainer;
|
||||
|
||||
startNode = startNode || container;
|
||||
parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
|
||||
|
||||
// Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
|
||||
// This: <p><br>|</p> becomes <p>|<br></p>
|
||||
if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
|
||||
container = startNode.parentNode;
|
||||
offset = dom.nodeIndex(startNode);
|
||||
normalized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk left until we hit a text node we can move to or a block/br/img
|
||||
walker = new TreeWalker(startNode, parentBlockContainer);
|
||||
while ((node = walker[left ? 'prev' : 'next']())) {
|
||||
// Break if we hit a non content editable node
|
||||
if (dom.getContentEditableParent(node) === "false") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Found text node that has a length
|
||||
if (node.nodeType === 3 && node.nodeValue.length > 0) {
|
||||
container = node;
|
||||
offset = left ? node.nodeValue.length : 0;
|
||||
normalized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Break if we find a block or a BR/IMG/INPUT etc
|
||||
if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastInlineElement = node;
|
||||
}
|
||||
|
||||
// Only fetch the last inline element when in caret mode for now
|
||||
if (collapsed && lastInlineElement) {
|
||||
container = lastInlineElement;
|
||||
normalized = true;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
container = rng[(start ? 'start' : 'end') + 'Container'];
|
||||
offset = rng[(start ? 'start' : 'end') + 'Offset'];
|
||||
isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
|
||||
nonEmptyElementsMap = dom.schema.getNonEmptyElements();
|
||||
directionLeft = start;
|
||||
|
||||
if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
|
||||
directionLeft = false;
|
||||
}
|
||||
|
||||
// If the container is a document move it to the body element
|
||||
if (container.nodeType === 9) {
|
||||
container = dom.getRoot();
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
// If the container is body try move it into the closest text node or position
|
||||
if (container === body) {
|
||||
// If start is before/after a image, table etc
|
||||
if (directionLeft) {
|
||||
node = container.childNodes[offset > 0 ? offset - 1 : 0];
|
||||
if (node) {
|
||||
if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the index
|
||||
if (container.hasChildNodes()) {
|
||||
offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
|
||||
container = container.childNodes[offset];
|
||||
offset = 0;
|
||||
|
||||
// Don't walk into elements that doesn't have any child nodes like a IMG
|
||||
if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
|
||||
// Walk the DOM to find a text node to place the caret at or a BR
|
||||
node = container;
|
||||
walker = new TreeWalker(container, body);
|
||||
|
||||
do {
|
||||
// Found a text node use that position
|
||||
if (node.nodeType === 3 && node.nodeValue.length > 0) {
|
||||
offset = directionLeft ? 0 : node.nodeValue.length;
|
||||
container = node;
|
||||
normalized = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Found a BR/IMG element that we can place the caret before
|
||||
if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
||||
offset = dom.nodeIndex(node);
|
||||
container = node.parentNode;
|
||||
|
||||
// Put caret after image when moving the end point
|
||||
if (node.nodeName == "IMG" && !directionLeft) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
normalized = true;
|
||||
break;
|
||||
}
|
||||
} while ((node = (directionLeft ? walker.next() : walker.prev())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lean the caret to the left if possible
|
||||
if (collapsed) {
|
||||
// So this: <b>x</b><i>|x</i>
|
||||
// Becomes: <b>x|</b><i>x</i>
|
||||
// Seems that only gecko has issues with this
|
||||
if (container.nodeType === 3 && offset === 0) {
|
||||
findTextNodeRelative(true);
|
||||
}
|
||||
|
||||
// Lean left into empty inline elements when the caret is before a BR
|
||||
// So this: <i><b></b><i>|<br></i>
|
||||
// Becomes: <i><b>|</b><i><br></i>
|
||||
// Seems that only gecko has issues with this.
|
||||
// Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
|
||||
if (container.nodeType === 1) {
|
||||
node = container.childNodes[offset];
|
||||
|
||||
// Offset is after the containers last child
|
||||
// then use the previous child for normalization
|
||||
if (!node) {
|
||||
node = container.childNodes[offset - 1];
|
||||
}
|
||||
|
||||
if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
|
||||
!hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
|
||||
findTextNodeRelative(true, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lean the start of the selection right if possible
|
||||
// So this: x[<b>x]</b>
|
||||
// Becomes: x<b>[x]</b>
|
||||
if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
|
||||
findTextNodeRelative(false);
|
||||
}
|
||||
|
||||
// Set endpoint if it was normalized
|
||||
if (normalized) {
|
||||
rng['set' + (start ? 'Start' : 'End')](container, offset);
|
||||
}
|
||||
}
|
||||
|
||||
collapsed = rng.collapsed;
|
||||
|
||||
normalizeEndPoint(true);
|
||||
|
||||
if (!collapsed) {
|
||||
normalizeEndPoint();
|
||||
}
|
||||
|
||||
// If it was collapsed then make sure it still is
|
||||
if (normalized && collapsed) {
|
||||
rng.collapse(true);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,5 +489,52 @@ define("tinymce/dom/RangeUtils", [
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the caret range for the given x/y location.
|
||||
*
|
||||
* @static
|
||||
* @method getCaretRangeFromPoint
|
||||
* @param {Number} x X coordinate for range
|
||||
* @param {Number} y Y coordinate for range
|
||||
* @param {Document} doc Document that x/y are relative to
|
||||
* @returns {Range} caret range
|
||||
*/
|
||||
RangeUtils.getCaretRangeFromPoint = function(x, y, doc) {
|
||||
var rng, point;
|
||||
|
||||
if (doc.caretPositionFromPoint) {
|
||||
point = doc.caretPositionFromPoint(x, y);
|
||||
rng = doc.createRange();
|
||||
rng.setStart(point.offsetNode, point.offset);
|
||||
rng.collapse(true);
|
||||
} else if (doc.caretRangeFromPoint) {
|
||||
rng = doc.caretRangeFromPoint(x, y);
|
||||
} else if (doc.body.createTextRange) {
|
||||
rng = doc.body.createTextRange();
|
||||
|
||||
try {
|
||||
rng.moveToPoint(x, y);
|
||||
rng.collapse(true);
|
||||
} catch (ex) {
|
||||
// Append to top or bottom depending on drop location
|
||||
rng.collapse(y < doc.body.clientHeight);
|
||||
}
|
||||
}
|
||||
|
||||
return rng;
|
||||
};
|
||||
|
||||
RangeUtils.getNode = function(container, offset) {
|
||||
if (container.nodeType == 1 && container.hasChildNodes()) {
|
||||
if (offset >= container.childNodes.length) {
|
||||
offset = container.childNodes.length - 1;
|
||||
}
|
||||
|
||||
container = container.childNodes[offset];
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
return RangeUtils;
|
||||
});
|
||||
|
Reference in New Issue
Block a user