init
This commit is contained in:
502
public/tinymce/classes/dom/TridentSelection.js
Normal file
502
public/tinymce/classes/dom/TridentSelection.js
Normal file
@ -0,0 +1,502 @@
|
||||
/**
|
||||
* TridentSelection.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Selection class for old explorer versions. This one fakes the
|
||||
* native selection object available on modern browsers.
|
||||
*
|
||||
* @class tinymce.dom.TridentSelection
|
||||
*/
|
||||
define("tinymce/dom/TridentSelection", [], function() {
|
||||
function Selection(selection) {
|
||||
var self = this, dom = selection.dom, FALSE = false;
|
||||
|
||||
function getPosition(rng, start) {
|
||||
var checkRng, startIndex = 0, endIndex, inside,
|
||||
children, child, offset, index, position = -1, parent;
|
||||
|
||||
// Setup test range, collapse it and get the parent
|
||||
checkRng = rng.duplicate();
|
||||
checkRng.collapse(start);
|
||||
parent = checkRng.parentElement();
|
||||
|
||||
// Check if the selection is within the right document
|
||||
if (parent.ownerDocument !== selection.dom.doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IE will report non editable elements as it's parent so look for an editable one
|
||||
while (parent.contentEditable === "false") {
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
// If parent doesn't have any children then return that we are inside the element
|
||||
if (!parent.hasChildNodes()) {
|
||||
return {node: parent, inside: 1};
|
||||
}
|
||||
|
||||
// Setup node list and endIndex
|
||||
children = parent.children;
|
||||
endIndex = children.length - 1;
|
||||
|
||||
// Perform a binary search for the position
|
||||
while (startIndex <= endIndex) {
|
||||
index = Math.floor((startIndex + endIndex) / 2);
|
||||
|
||||
// Move selection to node and compare the ranges
|
||||
child = children[index];
|
||||
checkRng.moveToElementText(child);
|
||||
position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
|
||||
|
||||
// Before/after or an exact match
|
||||
if (position > 0) {
|
||||
endIndex = index - 1;
|
||||
} else if (position < 0) {
|
||||
startIndex = index + 1;
|
||||
} else {
|
||||
return {node: child};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if child position is before or we didn't find a position
|
||||
if (position < 0) {
|
||||
// No element child was found use the parent element and the offset inside that
|
||||
if (!child) {
|
||||
checkRng.moveToElementText(parent);
|
||||
checkRng.collapse(true);
|
||||
child = parent;
|
||||
inside = true;
|
||||
} else {
|
||||
checkRng.collapse(false);
|
||||
}
|
||||
|
||||
// Walk character by character in text node until we hit the selected range endpoint,
|
||||
// hit the end of document or parent isn't the right one
|
||||
// We need to walk char by char since rng.text or rng.htmlText will trim line endings
|
||||
offset = 0;
|
||||
while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
|
||||
if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
} else {
|
||||
// Child position is after the selection endpoint
|
||||
checkRng.collapse(true);
|
||||
|
||||
// Walk character by character in text node until we hit the selected range endpoint, hit
|
||||
// the end of document or parent isn't the right one
|
||||
offset = 0;
|
||||
while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
|
||||
if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
return {node: child, position: position, offset: offset, inside: inside};
|
||||
}
|
||||
|
||||
// Returns a W3C DOM compatible range object by using the IE Range API
|
||||
function getRange() {
|
||||
var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
|
||||
|
||||
// If selection is outside the current document just return an empty range
|
||||
element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
|
||||
if (element.ownerDocument != dom.doc) {
|
||||
return domRange;
|
||||
}
|
||||
|
||||
collapsed = selection.isCollapsed();
|
||||
|
||||
// Handle control selection
|
||||
if (ieRange.item) {
|
||||
domRange.setStart(element.parentNode, dom.nodeIndex(element));
|
||||
domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
|
||||
|
||||
return domRange;
|
||||
}
|
||||
|
||||
function findEndPoint(start) {
|
||||
var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
|
||||
|
||||
container = endPoint.node;
|
||||
offset = endPoint.offset;
|
||||
|
||||
if (endPoint.inside && !container.hasChildNodes()) {
|
||||
domRange[start ? 'setStart' : 'setEnd'](container, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset === undef) {
|
||||
domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
|
||||
return;
|
||||
}
|
||||
|
||||
if (endPoint.position < 0) {
|
||||
sibling = endPoint.inside ? container.firstChild : container.nextSibling;
|
||||
|
||||
if (!sibling) {
|
||||
domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!offset) {
|
||||
if (sibling.nodeType == 3) {
|
||||
domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
|
||||
} else {
|
||||
domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the text node and offset
|
||||
while (sibling) {
|
||||
nodeValue = sibling.nodeValue;
|
||||
textNodeOffset += nodeValue.length;
|
||||
|
||||
// We are at or passed the position we where looking for
|
||||
if (textNodeOffset >= offset) {
|
||||
container = sibling;
|
||||
textNodeOffset -= offset;
|
||||
textNodeOffset = nodeValue.length - textNodeOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
sibling = sibling.nextSibling;
|
||||
}
|
||||
} else {
|
||||
// Find the text node and offset
|
||||
sibling = container.previousSibling;
|
||||
|
||||
if (!sibling) {
|
||||
return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
|
||||
}
|
||||
|
||||
// If there isn't any text to loop then use the first position
|
||||
if (!offset) {
|
||||
if (container.nodeType == 3) {
|
||||
domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
|
||||
} else {
|
||||
domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (sibling) {
|
||||
textNodeOffset += sibling.nodeValue.length;
|
||||
|
||||
// We are at or passed the position we where looking for
|
||||
if (textNodeOffset >= offset) {
|
||||
container = sibling;
|
||||
textNodeOffset -= offset;
|
||||
break;
|
||||
}
|
||||
|
||||
sibling = sibling.previousSibling;
|
||||
}
|
||||
}
|
||||
|
||||
domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
|
||||
}
|
||||
|
||||
try {
|
||||
// Find start point
|
||||
findEndPoint(true);
|
||||
|
||||
// Find end point if needed
|
||||
if (!collapsed) {
|
||||
findEndPoint();
|
||||
}
|
||||
} catch (ex) {
|
||||
// IE has a nasty bug where text nodes might throw "invalid argument" when you
|
||||
// access the nodeValue or other properties of text nodes. This seems to happend when
|
||||
// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
|
||||
if (ex.number == -2147024809) {
|
||||
// Get the current selection
|
||||
bookmark = self.getBookmark(2);
|
||||
|
||||
// Get start element
|
||||
tmpRange = ieRange.duplicate();
|
||||
tmpRange.collapse(true);
|
||||
element = tmpRange.parentElement();
|
||||
|
||||
// Get end element
|
||||
if (!collapsed) {
|
||||
tmpRange = ieRange.duplicate();
|
||||
tmpRange.collapse(false);
|
||||
element2 = tmpRange.parentElement();
|
||||
element2.innerHTML = element2.innerHTML;
|
||||
}
|
||||
|
||||
// Remove the broken elements
|
||||
element.innerHTML = element.innerHTML;
|
||||
|
||||
// Restore the selection
|
||||
self.moveToBookmark(bookmark);
|
||||
|
||||
// Since the range has moved we need to re-get it
|
||||
ieRange = selection.getRng();
|
||||
|
||||
// Find start point
|
||||
findEndPoint(true);
|
||||
|
||||
// Find end point if needed
|
||||
if (!collapsed) {
|
||||
findEndPoint();
|
||||
}
|
||||
} else {
|
||||
throw ex; // Throw other errors
|
||||
}
|
||||
}
|
||||
|
||||
return domRange;
|
||||
}
|
||||
|
||||
this.getBookmark = function(type) {
|
||||
var rng = selection.getRng(), bookmark = {};
|
||||
|
||||
function getIndexes(node) {
|
||||
var parent, root, children, i, indexes = [];
|
||||
|
||||
parent = node.parentNode;
|
||||
root = dom.getRoot().parentNode;
|
||||
|
||||
while (parent != root && parent.nodeType !== 9) {
|
||||
children = parent.children;
|
||||
|
||||
i = children.length;
|
||||
while (i--) {
|
||||
if (node === children[i]) {
|
||||
indexes.push(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
node = parent;
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
function getBookmarkEndPoint(start) {
|
||||
var position;
|
||||
|
||||
position = getPosition(rng, start);
|
||||
if (position) {
|
||||
return {
|
||||
position: position.position,
|
||||
offset: position.offset,
|
||||
indexes: getIndexes(position.node),
|
||||
inside: position.inside
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Non ubstructive bookmark
|
||||
if (type === 2) {
|
||||
// Handle text selection
|
||||
if (!rng.item) {
|
||||
bookmark.start = getBookmarkEndPoint(true);
|
||||
|
||||
if (!selection.isCollapsed()) {
|
||||
bookmark.end = getBookmarkEndPoint();
|
||||
}
|
||||
} else {
|
||||
bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
|
||||
}
|
||||
}
|
||||
|
||||
return bookmark;
|
||||
};
|
||||
|
||||
this.moveToBookmark = function(bookmark) {
|
||||
var rng, body = dom.doc.body;
|
||||
|
||||
function resolveIndexes(indexes) {
|
||||
var node, i, idx, children;
|
||||
|
||||
node = dom.getRoot();
|
||||
for (i = indexes.length - 1; i >= 0; i--) {
|
||||
children = node.children;
|
||||
idx = indexes[i];
|
||||
|
||||
if (idx <= children.length - 1) {
|
||||
node = children[idx];
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function setBookmarkEndPoint(start) {
|
||||
var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
|
||||
|
||||
if (endPoint) {
|
||||
moveLeft = endPoint.position > 0;
|
||||
|
||||
moveRng = body.createTextRange();
|
||||
moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
|
||||
|
||||
offset = endPoint.offset;
|
||||
if (offset !== undef) {
|
||||
moveRng.collapse(endPoint.inside || moveLeft);
|
||||
moveRng.moveStart('character', moveLeft ? -offset : offset);
|
||||
} else {
|
||||
moveRng.collapse(start);
|
||||
}
|
||||
|
||||
rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
|
||||
|
||||
if (start) {
|
||||
rng.collapse(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bookmark.start) {
|
||||
if (bookmark.start.ctrl) {
|
||||
rng = body.createControlRange();
|
||||
rng.addElement(resolveIndexes(bookmark.start.indexes));
|
||||
rng.select();
|
||||
} else {
|
||||
rng = body.createTextRange();
|
||||
setBookmarkEndPoint(true);
|
||||
setBookmarkEndPoint();
|
||||
rng.select();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.addRange = function(rng) {
|
||||
var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
|
||||
doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
|
||||
|
||||
function setEndPoint(start) {
|
||||
var container, offset, marker, tmpRng, nodes;
|
||||
|
||||
marker = dom.create('a');
|
||||
container = start ? startContainer : endContainer;
|
||||
offset = start ? startOffset : endOffset;
|
||||
tmpRng = ieRng.duplicate();
|
||||
|
||||
if (container == doc || container == doc.documentElement) {
|
||||
container = body;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (container.nodeType == 3) {
|
||||
container.parentNode.insertBefore(marker, container);
|
||||
tmpRng.moveToElementText(marker);
|
||||
tmpRng.moveStart('character', offset);
|
||||
dom.remove(marker);
|
||||
ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
|
||||
} else {
|
||||
nodes = container.childNodes;
|
||||
|
||||
if (nodes.length) {
|
||||
if (offset >= nodes.length) {
|
||||
dom.insertAfter(marker, nodes[nodes.length - 1]);
|
||||
} else {
|
||||
container.insertBefore(marker, nodes[offset]);
|
||||
}
|
||||
|
||||
tmpRng.moveToElementText(marker);
|
||||
} else if (container.canHaveHTML) {
|
||||
// Empty node selection for example <div>|</div>
|
||||
// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
|
||||
container.innerHTML = '<span></span>';
|
||||
marker = container.firstChild;
|
||||
tmpRng.moveToElementText(marker);
|
||||
tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
|
||||
}
|
||||
|
||||
ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
|
||||
dom.remove(marker);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup some shorter versions
|
||||
startContainer = rng.startContainer;
|
||||
startOffset = rng.startOffset;
|
||||
endContainer = rng.endContainer;
|
||||
endOffset = rng.endOffset;
|
||||
ieRng = body.createTextRange();
|
||||
|
||||
// If single element selection then try making a control selection out of it
|
||||
if (startContainer == endContainer && startContainer.nodeType == 1) {
|
||||
// Trick to place the caret inside an empty block element like <p></p>
|
||||
if (startOffset == endOffset && !startContainer.hasChildNodes()) {
|
||||
if (startContainer.canHaveHTML) {
|
||||
// Check if previous sibling is an empty block if it is then we need to render it
|
||||
// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
|
||||
// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
|
||||
sibling = startContainer.previousSibling;
|
||||
if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
|
||||
sibling.innerHTML = '';
|
||||
} else {
|
||||
sibling = null;
|
||||
}
|
||||
|
||||
startContainer.innerHTML = '<span></span><span></span>';
|
||||
ieRng.moveToElementText(startContainer.lastChild);
|
||||
ieRng.select();
|
||||
dom.doc.selection.clear();
|
||||
startContainer.innerHTML = '';
|
||||
|
||||
if (sibling) {
|
||||
sibling.innerHTML = '';
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
startOffset = dom.nodeIndex(startContainer);
|
||||
startContainer = startContainer.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (startOffset == endOffset - 1) {
|
||||
try {
|
||||
ctrlElm = startContainer.childNodes[startOffset];
|
||||
ctrlRng = body.createControlRange();
|
||||
ctrlRng.addElement(ctrlElm);
|
||||
ctrlRng.select();
|
||||
|
||||
// Check if the range produced is on the correct element and is a control range
|
||||
// On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
|
||||
nativeRng = selection.getRng();
|
||||
if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set start/end point of selection
|
||||
setEndPoint(true);
|
||||
setEndPoint();
|
||||
|
||||
// Select the new range and scroll it into view
|
||||
ieRng.select();
|
||||
};
|
||||
|
||||
// Expose range method
|
||||
this.getRangeAt = getRange;
|
||||
}
|
||||
|
||||
return Selection;
|
||||
});
|
Reference in New Issue
Block a user