2015-01-08 00:21:53 -05:00
|
|
|
|
/*jshint strict:false, undef:false, unused:false, latedef:false */
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2011-11-02 20:10:16 -05:00
|
|
|
|
var getNodeBefore = function ( node, offset ) {
|
|
|
|
|
var children = node.childNodes;
|
|
|
|
|
while ( offset && node.nodeType === ELEMENT_NODE ) {
|
|
|
|
|
node = children[ offset - 1 ];
|
|
|
|
|
children = node.childNodes;
|
|
|
|
|
offset = children.length;
|
|
|
|
|
}
|
|
|
|
|
return node;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getNodeAfter = function ( node, offset ) {
|
|
|
|
|
if ( node.nodeType === ELEMENT_NODE ) {
|
|
|
|
|
var children = node.childNodes;
|
|
|
|
|
if ( offset < children.length ) {
|
|
|
|
|
node = children[ offset ];
|
|
|
|
|
} else {
|
|
|
|
|
while ( node && !node.nextSibling ) {
|
|
|
|
|
node = node.parentNode;
|
|
|
|
|
}
|
|
|
|
|
if ( node ) { node = node.nextSibling; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return node;
|
|
|
|
|
};
|
|
|
|
|
|
2013-06-20 06:03:01 -05:00
|
|
|
|
// ---
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var insertNodeInRange = function ( range, node ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// Insert at start.
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var startContainer = range.startContainer,
|
|
|
|
|
startOffset = range.startOffset,
|
|
|
|
|
endContainer = range.endContainer,
|
|
|
|
|
endOffset = range.endOffset,
|
2013-04-07 22:27:06 -05:00
|
|
|
|
parent, children, childCount, afterSplit;
|
|
|
|
|
|
|
|
|
|
// If part way through a text node, split it.
|
|
|
|
|
if ( startContainer.nodeType === TEXT_NODE ) {
|
|
|
|
|
parent = startContainer.parentNode;
|
|
|
|
|
children = parent.childNodes;
|
|
|
|
|
if ( startOffset === startContainer.length ) {
|
|
|
|
|
startOffset = indexOf.call( children, startContainer ) + 1;
|
2013-06-20 06:03:01 -05:00
|
|
|
|
if ( range.collapsed ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
endContainer = parent;
|
|
|
|
|
endOffset = startOffset;
|
2011-11-10 02:27:54 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
} else {
|
|
|
|
|
if ( startOffset ) {
|
|
|
|
|
afterSplit = startContainer.splitText( startOffset );
|
|
|
|
|
if ( endContainer === startContainer ) {
|
|
|
|
|
endOffset -= startOffset;
|
|
|
|
|
endContainer = afterSplit;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
else if ( endContainer === parent ) {
|
|
|
|
|
endOffset += 1;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
startContainer = afterSplit;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
startOffset = indexOf.call( children, startContainer );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
startContainer = parent;
|
|
|
|
|
} else {
|
|
|
|
|
children = startContainer.childNodes;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
childCount = children.length;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2015-09-28 08:48:23 -05:00
|
|
|
|
if ( startOffset === childCount ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
startContainer.appendChild( node );
|
|
|
|
|
} else {
|
|
|
|
|
startContainer.insertBefore( node, children[ startOffset ] );
|
|
|
|
|
}
|
2015-02-06 02:09:37 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( startContainer === endContainer ) {
|
|
|
|
|
endOffset += children.length - childCount;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-06-20 06:03:01 -05:00
|
|
|
|
range.setStart( startContainer, startOffset );
|
|
|
|
|
range.setEnd( endContainer, endOffset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var extractContentsOfRange = function ( range, common, root ) {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var startContainer = range.startContainer,
|
|
|
|
|
startOffset = range.startOffset,
|
|
|
|
|
endContainer = range.endContainer,
|
|
|
|
|
endOffset = range.endOffset;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( !common ) {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
common = range.commonAncestorContainer;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( common.nodeType === TEXT_NODE ) {
|
|
|
|
|
common = common.parentNode;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var endNode = split( endContainer, endOffset, common, root ),
|
|
|
|
|
startNode = split( startContainer, startOffset, common, root ),
|
2013-04-07 22:27:06 -05:00
|
|
|
|
frag = common.ownerDocument.createDocumentFragment(),
|
2015-03-04 00:15:21 -05:00
|
|
|
|
next, before, after;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// End node will be null if at end of child nodes list.
|
|
|
|
|
while ( startNode !== endNode ) {
|
|
|
|
|
next = startNode.nextSibling;
|
|
|
|
|
frag.appendChild( startNode );
|
|
|
|
|
startNode = next;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2015-03-04 00:15:21 -05:00
|
|
|
|
startContainer = common;
|
|
|
|
|
startOffset = endNode ?
|
2013-04-07 22:27:06 -05:00
|
|
|
|
indexOf.call( common.childNodes, endNode ) :
|
2015-03-04 00:15:21 -05:00
|
|
|
|
common.childNodes.length;
|
|
|
|
|
|
|
|
|
|
// Merge text nodes if adjacent. IE10 in particular will not focus
|
|
|
|
|
// between two text nodes
|
|
|
|
|
after = common.childNodes[ startOffset ];
|
|
|
|
|
before = after && after.previousSibling;
|
|
|
|
|
if ( before &&
|
|
|
|
|
before.nodeType === TEXT_NODE &&
|
|
|
|
|
after.nodeType === TEXT_NODE ) {
|
|
|
|
|
startContainer = before;
|
|
|
|
|
startOffset = before.length;
|
|
|
|
|
before.appendData( after.data );
|
|
|
|
|
detach( after );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
range.setStart( startContainer, startOffset );
|
2013-06-20 06:03:01 -05:00
|
|
|
|
range.collapse( true );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixCursor( common, root );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
return frag;
|
|
|
|
|
};
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var deleteContentsOfRange = function ( range, root ) {
|
2017-01-08 22:07:20 -05:00
|
|
|
|
var startBlock = getStartBlockOfRange( range, root );
|
|
|
|
|
var endBlock = getEndBlockOfRange( range, root );
|
|
|
|
|
var needsMerge = ( startBlock !== endBlock );
|
|
|
|
|
var frag, child;
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2017-01-08 22:07:20 -05:00
|
|
|
|
// Move boundaries up as much as possible without exiting block,
|
|
|
|
|
// to reduce need to split.
|
2017-01-12 23:15:59 -05:00
|
|
|
|
moveRangeBoundariesDownTree( range );
|
|
|
|
|
moveRangeBoundariesUpTree( range, startBlock, endBlock, root );
|
2015-07-25 20:03:51 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// Remove selected range
|
2017-01-08 22:07:20 -05:00
|
|
|
|
frag = extractContentsOfRange( range, null, root );
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2017-01-08 22:07:20 -05:00
|
|
|
|
// Move boundaries back down tree as far as possible.
|
2014-12-26 02:48:13 -05:00
|
|
|
|
moveRangeBoundariesDownTree( range );
|
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// If we split into two different blocks, merge the blocks.
|
2015-07-25 20:03:51 -05:00
|
|
|
|
if ( needsMerge ) {
|
2017-01-08 22:07:20 -05:00
|
|
|
|
// endBlock will have been split, so need to refetch
|
2016-03-22 01:57:00 -05:00
|
|
|
|
endBlock = getEndBlockOfRange( range, root );
|
2015-07-25 20:03:51 -05:00
|
|
|
|
if ( startBlock && endBlock && startBlock !== endBlock ) {
|
2017-07-15 05:33:32 -05:00
|
|
|
|
mergeWithBlock( startBlock, endBlock, range, root );
|
2015-07-25 20:03:51 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// Ensure block has necessary children
|
|
|
|
|
if ( startBlock ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixCursor( startBlock, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
// Ensure root has a block-level element in it.
|
2017-01-08 22:07:20 -05:00
|
|
|
|
child = root.firstChild;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( !child || child.nodeName === 'BR' ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixCursor( root, root );
|
|
|
|
|
range.selectNodeContents( root.firstChild );
|
2015-07-25 20:03:51 -05:00
|
|
|
|
} else {
|
2017-01-08 22:07:20 -05:00
|
|
|
|
range.collapse( true );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2016-03-10 23:22:49 -05:00
|
|
|
|
return frag;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ---
|
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// Contents of range will be deleted.
|
|
|
|
|
// After method, range will be around inserted content
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var insertTreeFragmentIntoRange = function ( range, frag, root ) {
|
2017-07-15 05:33:32 -05:00
|
|
|
|
var node, block, blockContentsAfterSplit, stopPoint, container, offset;
|
|
|
|
|
var nodeAfterSplit, nodeBeforeSplit, tempRange;
|
|
|
|
|
|
|
|
|
|
// Fixup content: ensure no top-level inline, and add cursor fix elements.
|
|
|
|
|
fixContainer( frag, root );
|
|
|
|
|
node = frag;
|
|
|
|
|
while ( ( node = getNextBlock( node, root ) ) ) {
|
|
|
|
|
fixCursor( node, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// Delete any selected content.
|
2013-06-20 06:03:01 -05:00
|
|
|
|
if ( !range.collapsed ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
deleteContentsOfRange( range, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// Move range down into text nodes.
|
2013-06-20 06:03:01 -05:00
|
|
|
|
moveRangeBoundariesDownTree( range );
|
2017-07-15 05:33:32 -05:00
|
|
|
|
range.collapse( false ); // collapse to end
|
|
|
|
|
|
|
|
|
|
// Where will we split up to? First blockquote parent, otherwise root.
|
|
|
|
|
stopPoint = getNearest( range.endContainer, root, 'BLOCKQUOTE' ) || root;
|
|
|
|
|
|
|
|
|
|
// Merge the contents of the first block in the frag with the focused block.
|
|
|
|
|
// If there are contents in the block after the focus point, collect this
|
|
|
|
|
// up to insert in the last block later
|
|
|
|
|
block = getStartBlockOfRange( range );
|
|
|
|
|
if ( block ) {
|
|
|
|
|
moveRangeBoundariesUpTree( range, block, block, root );
|
|
|
|
|
range.collapse( true ); // collapse to start
|
|
|
|
|
container = range.endContainer;
|
|
|
|
|
offset = range.endOffset;
|
|
|
|
|
// Remove trailing <br> – we don't want this considered content to be
|
|
|
|
|
// inserted again later
|
|
|
|
|
cleanupBRs( block, root, false );
|
|
|
|
|
if ( isInline( container ) ) {
|
|
|
|
|
// Split up to block parent.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
nodeAfterSplit = split(
|
2017-07-15 05:33:32 -05:00
|
|
|
|
container, offset, getPreviousBlock( container, root ), root );
|
|
|
|
|
container = nodeAfterSplit.parentNode;
|
|
|
|
|
offset = indexOf.call( container.childNodes, nodeAfterSplit );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2017-07-15 05:33:32 -05:00
|
|
|
|
if ( /*isBlock( container ) && */offset !== getLength( container ) ) {
|
|
|
|
|
// Collect any inline contents of the block after the range point
|
|
|
|
|
blockContentsAfterSplit =
|
|
|
|
|
root.ownerDocument.createDocumentFragment();
|
|
|
|
|
while ( ( node = container.childNodes[ offset ] ) ) {
|
|
|
|
|
blockContentsAfterSplit.appendChild( node );
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// And merge the first block in.
|
|
|
|
|
mergeWithBlock( container, getNextBlock( frag, frag ), range, root );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// And where we will insert
|
|
|
|
|
offset = indexOf.call( container.parentNode.childNodes, container ) + 1;
|
|
|
|
|
container = container.parentNode;
|
|
|
|
|
range.setEnd( container, offset );
|
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// Is there still any content in the fragment?
|
|
|
|
|
if ( getLength( frag ) ) {
|
|
|
|
|
moveRangeBoundariesUpTree( range, stopPoint, stopPoint, root );
|
|
|
|
|
// Now split after block up to blockquote (if a parent) or root
|
|
|
|
|
nodeAfterSplit = split(
|
|
|
|
|
range.endContainer, range.endOffset, stopPoint, root );
|
|
|
|
|
nodeBeforeSplit = nodeAfterSplit ?
|
|
|
|
|
nodeAfterSplit.previousSibling :
|
|
|
|
|
stopPoint.lastChild;
|
|
|
|
|
stopPoint.insertBefore( frag, nodeAfterSplit );
|
|
|
|
|
if ( nodeAfterSplit ) {
|
|
|
|
|
range.setEndBefore( nodeAfterSplit );
|
|
|
|
|
} else {
|
|
|
|
|
range.setEnd( stopPoint, getLength( stopPoint ) );
|
2015-06-12 05:48:06 -05:00
|
|
|
|
}
|
2017-07-15 05:33:32 -05:00
|
|
|
|
block = getEndBlockOfRange( range, root );
|
|
|
|
|
|
|
|
|
|
// Get a reference that won't be invalidated if we merge containers.
|
|
|
|
|
moveRangeBoundariesDownTree( range );
|
|
|
|
|
container = range.endContainer;
|
|
|
|
|
offset = range.endOffset;
|
|
|
|
|
|
2015-06-12 05:48:06 -05:00
|
|
|
|
// Merge inserted containers with edges of split
|
2015-06-17 03:36:29 -05:00
|
|
|
|
if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
mergeContainers( nodeAfterSplit, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2017-07-15 05:33:32 -05:00
|
|
|
|
nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;
|
|
|
|
|
if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
|
|
|
|
|
mergeContainers( nodeAfterSplit, root );
|
|
|
|
|
}
|
|
|
|
|
range.setEnd( container, offset );
|
|
|
|
|
}
|
2013-01-29 19:30:11 -05:00
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
// Insert inline content saved from before.
|
|
|
|
|
if ( blockContentsAfterSplit ) {
|
|
|
|
|
tempRange = range.cloneRange();
|
|
|
|
|
mergeWithBlock( block, blockContentsAfterSplit, tempRange, root );
|
|
|
|
|
range.setEnd( tempRange.endContainer, tempRange.endOffset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2017-07-15 05:33:32 -05:00
|
|
|
|
moveRangeBoundariesDownTree( range );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// ---
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var isNodeContainedInRange = function ( range, node, partial ) {
|
|
|
|
|
var nodeRange = node.ownerDocument.createRange();
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
nodeRange.selectNode( node );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( partial ) {
|
|
|
|
|
// Node must not finish before range starts or start after range
|
|
|
|
|
// finishes.
|
|
|
|
|
var nodeEndBeforeStart = ( range.compareBoundaryPoints(
|
|
|
|
|
END_TO_START, nodeRange ) > -1 ),
|
|
|
|
|
nodeStartAfterEnd = ( range.compareBoundaryPoints(
|
|
|
|
|
START_TO_END, nodeRange ) < 1 );
|
|
|
|
|
return ( !nodeEndBeforeStart && !nodeStartAfterEnd );
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Node must start after range starts and finish before range
|
|
|
|
|
// finishes
|
|
|
|
|
var nodeStartAfterStart = ( range.compareBoundaryPoints(
|
|
|
|
|
START_TO_START, nodeRange ) < 1 ),
|
|
|
|
|
nodeEndBeforeEnd = ( range.compareBoundaryPoints(
|
|
|
|
|
END_TO_END, nodeRange ) > -1 );
|
|
|
|
|
return ( nodeStartAfterStart && nodeEndBeforeEnd );
|
|
|
|
|
}
|
|
|
|
|
};
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var moveRangeBoundariesDownTree = function ( range ) {
|
|
|
|
|
var startContainer = range.startContainer,
|
|
|
|
|
startOffset = range.startOffset,
|
|
|
|
|
endContainer = range.endContainer,
|
|
|
|
|
endOffset = range.endOffset,
|
2016-09-25 15:43:47 -05:00
|
|
|
|
maySkipBR = true,
|
2013-04-07 22:27:06 -05:00
|
|
|
|
child;
|
|
|
|
|
|
|
|
|
|
while ( startContainer.nodeType !== TEXT_NODE ) {
|
|
|
|
|
child = startContainer.childNodes[ startOffset ];
|
|
|
|
|
if ( !child || isLeaf( child ) ) {
|
|
|
|
|
break;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
startContainer = child;
|
|
|
|
|
startOffset = 0;
|
|
|
|
|
}
|
|
|
|
|
if ( endOffset ) {
|
|
|
|
|
while ( endContainer.nodeType !== TEXT_NODE ) {
|
|
|
|
|
child = endContainer.childNodes[ endOffset - 1 ];
|
|
|
|
|
if ( !child || isLeaf( child ) ) {
|
2016-09-25 15:43:47 -05:00
|
|
|
|
if ( maySkipBR && child && child.nodeName === 'BR' ) {
|
|
|
|
|
endOffset -= 1;
|
|
|
|
|
maySkipBR = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
break;
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
endContainer = child;
|
|
|
|
|
endOffset = getLength( endContainer );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
} else {
|
|
|
|
|
while ( endContainer.nodeType !== TEXT_NODE ) {
|
|
|
|
|
child = endContainer.firstChild;
|
|
|
|
|
if ( !child || isLeaf( child ) ) {
|
|
|
|
|
break;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
endContainer = child;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// If collapsed, this algorithm finds the nearest text node positions
|
|
|
|
|
// *outside* the range rather than inside, but also it flips which is
|
|
|
|
|
// assigned to which.
|
2013-06-20 06:03:01 -05:00
|
|
|
|
if ( range.collapsed ) {
|
|
|
|
|
range.setStart( endContainer, endOffset );
|
|
|
|
|
range.setEnd( startContainer, startOffset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
} else {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
range.setStart( startContainer, startOffset );
|
|
|
|
|
range.setEnd( endContainer, endOffset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
};
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2017-01-12 23:15:59 -05:00
|
|
|
|
var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
|
2017-01-08 22:07:20 -05:00
|
|
|
|
var startContainer = range.startContainer;
|
|
|
|
|
var startOffset = range.startOffset;
|
|
|
|
|
var endContainer = range.endContainer;
|
|
|
|
|
var endOffset = range.endOffset;
|
|
|
|
|
var maySkipBR = true;
|
|
|
|
|
var parent;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2017-01-08 22:07:20 -05:00
|
|
|
|
if ( !startMax ) {
|
|
|
|
|
startMax = range.commonAncestorContainer;
|
|
|
|
|
}
|
|
|
|
|
if ( !endMax ) {
|
|
|
|
|
endMax = startMax;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2017-01-12 23:15:59 -05:00
|
|
|
|
while ( !startOffset &&
|
|
|
|
|
startContainer !== startMax &&
|
|
|
|
|
startContainer !== root ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
parent = startContainer.parentNode;
|
|
|
|
|
startOffset = indexOf.call( parent.childNodes, startContainer );
|
|
|
|
|
startContainer = parent;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2016-09-25 15:43:47 -05:00
|
|
|
|
while ( true ) {
|
|
|
|
|
if ( maySkipBR &&
|
|
|
|
|
endContainer.nodeType !== TEXT_NODE &&
|
|
|
|
|
endContainer.childNodes[ endOffset ] &&
|
|
|
|
|
endContainer.childNodes[ endOffset ].nodeName === 'BR' ) {
|
|
|
|
|
endOffset += 1;
|
|
|
|
|
maySkipBR = false;
|
|
|
|
|
}
|
2017-01-08 22:07:20 -05:00
|
|
|
|
if ( endContainer === endMax ||
|
2017-01-12 23:15:59 -05:00
|
|
|
|
endContainer === root ||
|
2016-09-25 15:43:47 -05:00
|
|
|
|
endOffset !== getLength( endContainer ) ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
parent = endContainer.parentNode;
|
|
|
|
|
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
|
|
|
|
|
endContainer = parent;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-06-20 06:03:01 -05:00
|
|
|
|
range.setStart( startContainer, startOffset );
|
|
|
|
|
range.setEnd( endContainer, endOffset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
2011-11-02 02:46:18 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// Returns the first block at least partially contained by the range,
|
|
|
|
|
// or null if no block is contained by the range.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var getStartBlockOfRange = function ( range, root ) {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var container = range.startContainer,
|
2013-04-07 22:27:06 -05:00
|
|
|
|
block;
|
|
|
|
|
|
|
|
|
|
// If inline, get the containing block.
|
|
|
|
|
if ( isInline( container ) ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
block = getPreviousBlock( container, root );
|
2016-09-22 03:44:03 -05:00
|
|
|
|
} else if ( container !== root && isBlock( container ) ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
block = container;
|
|
|
|
|
} else {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
block = getNodeBefore( container, range.startOffset );
|
2016-03-22 01:57:00 -05:00
|
|
|
|
block = getNextBlock( block, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
// Check the block actually intersects the range
|
2013-06-20 06:03:01 -05:00
|
|
|
|
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// Returns the last block at least partially contained by the range,
|
|
|
|
|
// or null if no block is contained by the range.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var getEndBlockOfRange = function ( range, root ) {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var container = range.endContainer,
|
2013-04-07 22:27:06 -05:00
|
|
|
|
block, child;
|
|
|
|
|
|
|
|
|
|
// If inline, get the containing block.
|
|
|
|
|
if ( isInline( container ) ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
block = getPreviousBlock( container, root );
|
2016-09-22 03:44:03 -05:00
|
|
|
|
} else if ( container !== root && isBlock( container ) ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
block = container;
|
|
|
|
|
} else {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
block = getNodeAfter( container, range.endOffset );
|
2016-05-25 20:39:40 -05:00
|
|
|
|
if ( !block || !isOrContains( root, block ) ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
block = root;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
while ( child = block.lastChild ) {
|
|
|
|
|
block = child;
|
2011-11-02 20:10:16 -05:00
|
|
|
|
}
|
2011-11-02 00:31:46 -05:00
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
block = getPreviousBlock( block, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
// Check the block actually intersects the range
|
2013-06-20 06:03:01 -05:00
|
|
|
|
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2014-09-02 22:27:38 -05:00
|
|
|
|
var contentWalker = new TreeWalker( null,
|
|
|
|
|
SHOW_TEXT|SHOW_ELEMENT,
|
|
|
|
|
function ( node ) {
|
|
|
|
|
return node.nodeType === TEXT_NODE ?
|
|
|
|
|
notWS.test( node.data ) :
|
|
|
|
|
node.nodeName === 'IMG';
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var rangeDoesStartAtBlockBoundary = function ( range, root ) {
|
2016-05-25 20:39:40 -05:00
|
|
|
|
var startContainer = range.startContainer;
|
|
|
|
|
var startOffset = range.startOffset;
|
|
|
|
|
var nodeAfterCursor;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
2014-09-02 22:27:38 -05:00
|
|
|
|
// If in the middle or end of a text node, we're not at the boundary.
|
2015-06-18 22:00:55 -05:00
|
|
|
|
contentWalker.root = null;
|
2014-09-02 22:27:38 -05:00
|
|
|
|
if ( startContainer.nodeType === TEXT_NODE ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( startOffset ) {
|
|
|
|
|
return false;
|
2011-11-02 00:31:46 -05:00
|
|
|
|
}
|
2016-05-25 20:39:40 -05:00
|
|
|
|
nodeAfterCursor = startContainer;
|
2014-09-02 22:27:38 -05:00
|
|
|
|
} else {
|
2016-05-25 20:39:40 -05:00
|
|
|
|
nodeAfterCursor = getNodeAfter( startContainer, startOffset );
|
|
|
|
|
if ( nodeAfterCursor && !isOrContains( root, nodeAfterCursor ) ) {
|
|
|
|
|
nodeAfterCursor = null;
|
|
|
|
|
}
|
|
|
|
|
// The cursor was right at the end of the document
|
|
|
|
|
if ( !nodeAfterCursor ) {
|
|
|
|
|
nodeAfterCursor = getNodeBefore( startContainer, startOffset );
|
|
|
|
|
if ( nodeAfterCursor.nodeType === TEXT_NODE &&
|
|
|
|
|
nodeAfterCursor.length ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-05-16 09:08:29 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2014-09-02 22:27:38 -05:00
|
|
|
|
|
|
|
|
|
// Otherwise, look for any previous content in the same block.
|
2016-05-25 20:39:40 -05:00
|
|
|
|
contentWalker.currentNode = nodeAfterCursor;
|
2016-03-22 01:57:00 -05:00
|
|
|
|
contentWalker.root = getStartBlockOfRange( range, root );
|
2014-09-02 22:27:38 -05:00
|
|
|
|
|
|
|
|
|
return !contentWalker.previousNode();
|
2013-04-07 22:27:06 -05:00
|
|
|
|
};
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var rangeDoesEndAtBlockBoundary = function ( range, root ) {
|
2013-06-20 06:03:01 -05:00
|
|
|
|
var endContainer = range.endContainer,
|
|
|
|
|
endOffset = range.endOffset,
|
2014-09-02 22:27:38 -05:00
|
|
|
|
length;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
2014-09-02 22:27:38 -05:00
|
|
|
|
// If in a text node with content, and not at the end, we're not
|
|
|
|
|
// at the boundary
|
2015-06-18 22:00:55 -05:00
|
|
|
|
contentWalker.root = null;
|
2014-09-02 22:27:38 -05:00
|
|
|
|
if ( endContainer.nodeType === TEXT_NODE ) {
|
|
|
|
|
length = endContainer.data.length;
|
|
|
|
|
if ( length && endOffset < length ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-09-02 22:27:38 -05:00
|
|
|
|
contentWalker.currentNode = endContainer;
|
|
|
|
|
} else {
|
|
|
|
|
contentWalker.currentNode = getNodeBefore( endContainer, endOffset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2014-09-02 22:27:38 -05:00
|
|
|
|
|
|
|
|
|
// Otherwise, look for any further content in the same block.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
contentWalker.root = getEndBlockOfRange( range, root );
|
2014-09-02 22:27:38 -05:00
|
|
|
|
|
|
|
|
|
return !contentWalker.nextNode();
|
2012-11-12 01:48:24 -05:00
|
|
|
|
};
|
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
var expandRangeToBlockBoundaries = function ( range, root ) {
|
|
|
|
|
var start = getStartBlockOfRange( range, root ),
|
|
|
|
|
end = getEndBlockOfRange( range, root ),
|
2013-04-07 22:27:06 -05:00
|
|
|
|
parent;
|
|
|
|
|
|
|
|
|
|
if ( start && end ) {
|
|
|
|
|
parent = start.parentNode;
|
2013-06-20 06:03:01 -05:00
|
|
|
|
range.setStart( parent, indexOf.call( parent.childNodes, start ) );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
parent = end.parentNode;
|
2013-06-20 06:03:01 -05:00
|
|
|
|
range.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
};
|