只读模式
group, 分享
评论更多问题
博客标签总是存在一个
This commit is contained in:
lealife
2015-06-15 18:01:48 +08:00
parent 7e458bb433
commit 6987a38820
1453 changed files with 114561 additions and 91536 deletions

278
public/tinymce/classes/dom/RangeUtils.js Normal file → Executable file
View 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;
});