2014-10-02 04:36:39 -05:00
|
|
|
|
/*jshint strict:false, undef:false, unused:false */
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2016-08-01 15:43:07 -05:00
|
|
|
|
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
|
|
|
|
var leafNodeNames = {
|
|
|
|
|
BR: 1,
|
2016-05-01 01:33:47 -05:00
|
|
|
|
HR: 1,
|
2016-05-01 01:36:43 -05:00
|
|
|
|
IFRAME: 1,
|
2013-04-07 22:27:06 -05:00
|
|
|
|
IMG: 1,
|
|
|
|
|
INPUT: 1
|
2011-10-28 22:15:21 -05:00
|
|
|
|
};
|
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
function every ( nodeList, fn ) {
|
2011-10-28 22:15:21 -05:00
|
|
|
|
var l = nodeList.length;
|
|
|
|
|
while ( l-- ) {
|
|
|
|
|
if ( !fn( nodeList[l] ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// ---
|
2011-11-01 19:55:08 -05:00
|
|
|
|
|
2016-11-14 19:07:15 -05:00
|
|
|
|
var UNKNOWN = 0;
|
|
|
|
|
var INLINE = 1;
|
|
|
|
|
var BLOCK = 2;
|
|
|
|
|
var CONTAINER = 3;
|
|
|
|
|
|
|
|
|
|
var nodeCategoryCache = canWeakMap ? new WeakMap() : null;
|
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
function isLeaf ( node ) {
|
2016-11-14 19:07:15 -05:00
|
|
|
|
return node.nodeType === ELEMENT_NODE && !!leafNodeNames[ node.nodeName ];
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2016-11-14 19:07:15 -05:00
|
|
|
|
function getNodeCategory ( node ) {
|
2016-11-20 18:59:11 -05:00
|
|
|
|
switch ( node.nodeType ) {
|
|
|
|
|
case TEXT_NODE:
|
|
|
|
|
return INLINE;
|
|
|
|
|
case ELEMENT_NODE:
|
|
|
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
|
|
|
if ( canWeakMap && nodeCategoryCache.has( node ) ) {
|
|
|
|
|
return nodeCategoryCache.get( node );
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return UNKNOWN;
|
2016-11-14 19:07:15 -05:00
|
|
|
|
}
|
2016-11-20 18:59:11 -05:00
|
|
|
|
|
2016-11-14 19:07:15 -05:00
|
|
|
|
var nodeCategory;
|
2016-11-20 18:59:11 -05:00
|
|
|
|
if ( !every( node.childNodes, isInline ) ) {
|
2016-09-26 05:37:38 -05:00
|
|
|
|
// Malformed HTML can have block tags inside inline tags. Need to treat
|
|
|
|
|
// these as containers rather than inline. See #239.
|
2016-11-14 19:07:15 -05:00
|
|
|
|
nodeCategory = CONTAINER;
|
|
|
|
|
} else if ( inlineNodeNames.test( node.nodeName ) ) {
|
|
|
|
|
nodeCategory = INLINE;
|
|
|
|
|
} else {
|
|
|
|
|
nodeCategory = BLOCK;
|
|
|
|
|
}
|
|
|
|
|
if ( canWeakMap ) {
|
|
|
|
|
nodeCategoryCache.set( node, nodeCategory );
|
|
|
|
|
}
|
|
|
|
|
return nodeCategory;
|
|
|
|
|
}
|
|
|
|
|
function isInline ( node ) {
|
|
|
|
|
return getNodeCategory( node ) === INLINE;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
function isBlock ( node ) {
|
2016-11-14 19:07:15 -05:00
|
|
|
|
return getNodeCategory( node ) === BLOCK;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
function isContainer ( node ) {
|
2016-11-14 19:07:15 -05:00
|
|
|
|
return getNodeCategory( node ) === CONTAINER;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function getBlockWalker ( node, root ) {
|
2016-04-08 02:42:47 -05:00
|
|
|
|
var walker = new TreeWalker( root, SHOW_ELEMENT, isBlock );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
walker.currentNode = node;
|
|
|
|
|
return walker;
|
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function getPreviousBlock ( node, root ) {
|
|
|
|
|
node = getBlockWalker( node, root ).previousNode();
|
|
|
|
|
return node !== root ? node : null;
|
|
|
|
|
}
|
|
|
|
|
function getNextBlock ( node, root ) {
|
|
|
|
|
node = getBlockWalker( node, root ).nextNode();
|
|
|
|
|
return node !== root ? node : null;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
function isEmptyBlock ( block ) {
|
|
|
|
|
return !block.textContent && !block.querySelector( 'IMG' );
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function areAlike ( node, node2 ) {
|
|
|
|
|
return !isLeaf( node ) && (
|
|
|
|
|
node.nodeType === node2.nodeType &&
|
|
|
|
|
node.nodeName === node2.nodeName &&
|
2016-05-25 19:32:45 -05:00
|
|
|
|
node.nodeName !== 'A' &&
|
2016-03-22 01:57:00 -05:00
|
|
|
|
node.className === node2.className &&
|
|
|
|
|
( ( !node.style && !node2.style ) ||
|
|
|
|
|
node.style.cssText === node2.style.cssText )
|
|
|
|
|
);
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function hasTagAttributes ( node, tag, attributes ) {
|
|
|
|
|
if ( node.nodeName !== tag ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for ( var attr in attributes ) {
|
|
|
|
|
if ( node.getAttribute( attr ) !== attributes[ attr ] ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function getNearest ( node, root, tag, attributes ) {
|
|
|
|
|
while ( node && node !== root ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( hasTagAttributes( node, tag, attributes ) ) {
|
|
|
|
|
return node;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
node = node.parentNode;
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
return null;
|
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function isOrContains ( parent, node ) {
|
|
|
|
|
while ( node ) {
|
|
|
|
|
if ( node === parent ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
node = node.parentNode;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
2018-07-11 22:35:20 -05:00
|
|
|
|
function getPath ( node, root, config ) {
|
2016-03-25 19:32:59 -05:00
|
|
|
|
var path = '';
|
2018-07-11 22:35:20 -05:00
|
|
|
|
var id, className, classNames, dir, styleNames;
|
2016-03-25 19:32:59 -05:00
|
|
|
|
if ( node && node !== root ) {
|
2018-07-11 22:35:20 -05:00
|
|
|
|
path = getPath( node.parentNode, root, config );
|
2016-03-22 01:57:00 -05:00
|
|
|
|
if ( node.nodeType === ELEMENT_NODE ) {
|
|
|
|
|
path += ( path ? '>' : '' ) + node.nodeName;
|
|
|
|
|
if ( id = node.id ) {
|
|
|
|
|
path += '#' + id;
|
|
|
|
|
}
|
|
|
|
|
if ( className = node.className.trim() ) {
|
|
|
|
|
classNames = className.split( /\s\s*/ );
|
|
|
|
|
classNames.sort();
|
|
|
|
|
path += '.';
|
|
|
|
|
path += classNames.join( '.' );
|
|
|
|
|
}
|
|
|
|
|
if ( dir = node.dir ) {
|
|
|
|
|
path += '[dir=' + dir + ']';
|
|
|
|
|
}
|
2016-07-07 01:09:22 -05:00
|
|
|
|
if ( classNames ) {
|
2018-07-11 22:35:20 -05:00
|
|
|
|
styleNames = config.classNames;
|
|
|
|
|
if ( indexOf.call( classNames, styleNames.highlight ) > -1 ) {
|
2016-07-07 01:09:22 -05:00
|
|
|
|
path += '[backgroundColor=' +
|
|
|
|
|
node.style.backgroundColor.replace( / /g,'' ) + ']';
|
2016-07-06 12:27:22 -05:00
|
|
|
|
}
|
2018-07-11 22:35:20 -05:00
|
|
|
|
if ( indexOf.call( classNames, styleNames.colour ) > -1 ) {
|
2016-07-07 01:09:22 -05:00
|
|
|
|
path += '[color=' +
|
|
|
|
|
node.style.color.replace( / /g,'' ) + ']';
|
2016-07-06 12:27:22 -05:00
|
|
|
|
}
|
2018-07-11 22:35:20 -05:00
|
|
|
|
if ( indexOf.call( classNames, styleNames.fontFamily ) > -1 ) {
|
2016-07-07 01:09:22 -05:00
|
|
|
|
path += '[fontFamily=' +
|
|
|
|
|
node.style.fontFamily.replace( / /g,'' ) + ']';
|
2016-07-06 12:27:22 -05:00
|
|
|
|
}
|
2018-07-11 22:35:20 -05:00
|
|
|
|
if ( indexOf.call( classNames, styleNames.fontSize ) > -1 ) {
|
2016-07-06 12:27:22 -05:00
|
|
|
|
path += '[fontSize=' + node.style.fontSize + ']';
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-05-09 05:14:51 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLength ( node ) {
|
|
|
|
|
var nodeType = node.nodeType;
|
2017-07-15 05:33:32 -05:00
|
|
|
|
return nodeType === ELEMENT_NODE || nodeType === DOCUMENT_FRAGMENT_NODE ?
|
2013-04-07 22:27:06 -05:00
|
|
|
|
node.childNodes.length : node.length || 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function detach ( node ) {
|
|
|
|
|
var parent = node.parentNode;
|
|
|
|
|
if ( parent ) {
|
|
|
|
|
parent.removeChild( node );
|
|
|
|
|
}
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
function replaceWith ( node, node2 ) {
|
|
|
|
|
var parent = node.parentNode;
|
|
|
|
|
if ( parent ) {
|
|
|
|
|
parent.replaceChild( node2, node );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function empty ( node ) {
|
|
|
|
|
var frag = node.ownerDocument.createDocumentFragment(),
|
|
|
|
|
childNodes = node.childNodes,
|
|
|
|
|
l = childNodes ? childNodes.length : 0;
|
|
|
|
|
while ( l-- ) {
|
|
|
|
|
frag.appendChild( node.firstChild );
|
|
|
|
|
}
|
|
|
|
|
return frag;
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-02 04:36:39 -05:00
|
|
|
|
function createElement ( doc, tag, props, children ) {
|
|
|
|
|
var el = doc.createElement( tag ),
|
|
|
|
|
attr, value, i, l;
|
|
|
|
|
if ( props instanceof Array ) {
|
|
|
|
|
children = props;
|
|
|
|
|
props = null;
|
|
|
|
|
}
|
|
|
|
|
if ( props ) {
|
|
|
|
|
for ( attr in props ) {
|
|
|
|
|
value = props[ attr ];
|
|
|
|
|
if ( value !== undefined ) {
|
|
|
|
|
el.setAttribute( attr, props[ attr ] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( children ) {
|
|
|
|
|
for ( i = 0, l = children.length; i < l; i += 1 ) {
|
|
|
|
|
el.appendChild( children[i] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return el;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function fixCursor ( node, root ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// In Webkit and Gecko, block level elements are collapsed and
|
|
|
|
|
// unfocussable if they have no content. To remedy this, a <BR> must be
|
|
|
|
|
// inserted. In Opera and IE, we just need a textnode in order for the
|
|
|
|
|
// cursor to appear.
|
2016-12-07 01:54:13 -05:00
|
|
|
|
var self = root.__squire__;
|
|
|
|
|
var doc = node.ownerDocument;
|
|
|
|
|
var originalNode = node;
|
|
|
|
|
var fixer, child;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
if ( node === root ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
2016-12-07 01:54:13 -05:00
|
|
|
|
fixer = self.createDefaultBlock();
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( child ) {
|
|
|
|
|
node.replaceChild( fixer, child );
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
node.appendChild( fixer );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
node = fixer;
|
|
|
|
|
fixer = null;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-06 22:08:45 -05:00
|
|
|
|
if ( node.nodeType === TEXT_NODE ) {
|
|
|
|
|
return originalNode;
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( isInline( node ) ) {
|
2014-08-27 20:10:31 -05:00
|
|
|
|
child = node.firstChild;
|
|
|
|
|
while ( cantFocusEmptyTextNodes && child &&
|
|
|
|
|
child.nodeType === TEXT_NODE && !child.data ) {
|
|
|
|
|
node.removeChild( child );
|
|
|
|
|
child = node.firstChild;
|
|
|
|
|
}
|
|
|
|
|
if ( !child ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( cantFocusEmptyTextNodes ) {
|
2014-12-25 03:09:46 -05:00
|
|
|
|
fixer = doc.createTextNode( ZWS );
|
2016-12-07 01:54:13 -05:00
|
|
|
|
self._didAddZWS();
|
2013-04-07 22:27:06 -05:00
|
|
|
|
} else {
|
|
|
|
|
fixer = doc.createTextNode( '' );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if ( useTextFixer ) {
|
|
|
|
|
while ( node.nodeType !== TEXT_NODE && !isLeaf( node ) ) {
|
|
|
|
|
child = node.firstChild;
|
|
|
|
|
if ( !child ) {
|
|
|
|
|
fixer = doc.createTextNode( '' );
|
|
|
|
|
break;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
node = child;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( node.nodeType === TEXT_NODE ) {
|
|
|
|
|
// Opera will collapse the block element if it contains
|
|
|
|
|
// just spaces (but not if it contains no data at all).
|
|
|
|
|
if ( /^ +$/.test( node.data ) ) {
|
|
|
|
|
node.data = '';
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
} else if ( isLeaf( node ) ) {
|
|
|
|
|
node.parentNode.insertBefore( doc.createTextNode( '' ), node );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
else if ( !node.querySelector( 'BR' ) ) {
|
2015-03-29 02:35:03 -05:00
|
|
|
|
fixer = createElement( doc, 'BR' );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
while ( ( child = node.lastElementChild ) && !isInline( child ) ) {
|
|
|
|
|
node = child;
|
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
if ( fixer ) {
|
2016-03-24 06:04:42 -05:00
|
|
|
|
try {
|
|
|
|
|
node.appendChild( fixer );
|
|
|
|
|
} catch ( error ) {
|
2016-12-07 01:54:13 -05:00
|
|
|
|
self.didError({
|
2016-03-24 06:04:42 -05:00
|
|
|
|
name: 'Squire: fixCursor – ' + error,
|
|
|
|
|
message: 'Parent: ' + node.nodeName + '/' + node.innerHTML +
|
|
|
|
|
' appendChild: ' + fixer.nodeName
|
|
|
|
|
});
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
return originalNode;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2014-10-02 04:36:39 -05:00
|
|
|
|
// Recursively examine container nodes and wrap any inline children.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function fixContainer ( container, root ) {
|
2016-12-07 01:54:13 -05:00
|
|
|
|
var children = container.childNodes;
|
|
|
|
|
var doc = container.ownerDocument;
|
|
|
|
|
var wrapper = null;
|
|
|
|
|
var i, l, child, isBR;
|
|
|
|
|
var config = root.__squire__._config;
|
2015-05-09 05:14:51 -05:00
|
|
|
|
|
2014-10-02 04:36:39 -05:00
|
|
|
|
for ( i = 0, l = children.length; i < l; i += 1 ) {
|
|
|
|
|
child = children[i];
|
|
|
|
|
isBR = child.nodeName === 'BR';
|
|
|
|
|
if ( !isBR && isInline( child ) ) {
|
2015-05-09 05:14:51 -05:00
|
|
|
|
if ( !wrapper ) {
|
|
|
|
|
wrapper = createElement( doc,
|
|
|
|
|
config.blockTag, config.blockAttributes );
|
|
|
|
|
}
|
2014-10-02 04:36:39 -05:00
|
|
|
|
wrapper.appendChild( child );
|
|
|
|
|
i -= 1;
|
|
|
|
|
l -= 1;
|
|
|
|
|
} else if ( isBR || wrapper ) {
|
2015-05-09 05:14:51 -05:00
|
|
|
|
if ( !wrapper ) {
|
|
|
|
|
wrapper = createElement( doc,
|
|
|
|
|
config.blockTag, config.blockAttributes );
|
|
|
|
|
}
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixCursor( wrapper, root );
|
2014-10-02 04:36:39 -05:00
|
|
|
|
if ( isBR ) {
|
|
|
|
|
container.replaceChild( wrapper, child );
|
|
|
|
|
} else {
|
|
|
|
|
container.insertBefore( wrapper, child );
|
|
|
|
|
i += 1;
|
|
|
|
|
l += 1;
|
|
|
|
|
}
|
|
|
|
|
wrapper = null;
|
|
|
|
|
}
|
|
|
|
|
if ( isContainer( child ) ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixContainer( child, root );
|
2014-10-02 04:36:39 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( wrapper ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
container.appendChild( fixCursor( wrapper, root ) );
|
2014-10-02 04:36:39 -05:00
|
|
|
|
}
|
|
|
|
|
return container;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function split ( node, offset, stopNode, root ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
var nodeType = node.nodeType,
|
|
|
|
|
parent, clone, next;
|
2013-04-09 22:43:31 -05:00
|
|
|
|
if ( nodeType === TEXT_NODE && node !== stopNode ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
return split(
|
|
|
|
|
node.parentNode, node.splitText( offset ), stopNode, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
if ( nodeType === ELEMENT_NODE ) {
|
|
|
|
|
if ( typeof( offset ) === 'number' ) {
|
|
|
|
|
offset = offset < node.childNodes.length ?
|
|
|
|
|
node.childNodes[ offset ] : null;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2011-11-04 00:53:12 -05:00
|
|
|
|
if ( node === stopNode ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
return offset;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2011-10-28 22:15:21 -05:00
|
|
|
|
// Clone node without children
|
2013-06-20 08:15:18 -05:00
|
|
|
|
parent = node.parentNode;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
clone = node.cloneNode( false );
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2011-10-28 22:15:21 -05:00
|
|
|
|
// Add right-hand siblings to the clone
|
2013-04-07 22:27:06 -05:00
|
|
|
|
while ( offset ) {
|
|
|
|
|
next = offset.nextSibling;
|
|
|
|
|
clone.appendChild( offset );
|
|
|
|
|
offset = next;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2015-03-29 02:38:40 -05:00
|
|
|
|
// Maintain li numbering if inside a quote.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
if ( node.nodeName === 'OL' &&
|
|
|
|
|
getNearest( node, root, 'BLOCKQUOTE' ) ) {
|
2015-02-06 02:19:06 -05:00
|
|
|
|
clone.start = ( +node.start || 1 ) + node.childNodes.length - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-28 22:15:21 -05:00
|
|
|
|
// DO NOT NORMALISE. This may undo the fixCursor() call
|
|
|
|
|
// of a node lower down the tree!
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2011-10-28 22:15:21 -05:00
|
|
|
|
// We need something in the element in order for the cursor to appear.
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixCursor( node, root );
|
|
|
|
|
fixCursor( clone, root );
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2011-10-28 22:15:21 -05:00
|
|
|
|
// Inject clone after original node
|
|
|
|
|
if ( next = node.nextSibling ) {
|
|
|
|
|
parent.insertBefore( clone, next );
|
|
|
|
|
} else {
|
|
|
|
|
parent.appendChild( clone );
|
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2011-10-28 22:15:21 -05:00
|
|
|
|
// Keep on splitting up the tree
|
2016-03-22 01:57:00 -05:00
|
|
|
|
return split( parent, clone, stopNode, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2013-04-09 22:43:31 -05:00
|
|
|
|
return offset;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2016-07-13 21:15:06 -05:00
|
|
|
|
function _mergeInlines ( node, fakeRange ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
var children = node.childNodes,
|
|
|
|
|
l = children.length,
|
|
|
|
|
frags = [],
|
|
|
|
|
child, prev, len;
|
|
|
|
|
while ( l-- ) {
|
|
|
|
|
child = children[l];
|
|
|
|
|
prev = l && children[ l - 1 ];
|
|
|
|
|
if ( l && isInline( child ) && areAlike( child, prev ) &&
|
|
|
|
|
!leafNodeNames[ child.nodeName ] ) {
|
2016-07-13 21:15:06 -05:00
|
|
|
|
if ( fakeRange.startContainer === child ) {
|
|
|
|
|
fakeRange.startContainer = prev;
|
|
|
|
|
fakeRange.startOffset += getLength( prev );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2016-07-13 21:15:06 -05:00
|
|
|
|
if ( fakeRange.endContainer === child ) {
|
|
|
|
|
fakeRange.endContainer = prev;
|
|
|
|
|
fakeRange.endOffset += getLength( prev );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2016-07-13 21:15:06 -05:00
|
|
|
|
if ( fakeRange.startContainer === node ) {
|
|
|
|
|
if ( fakeRange.startOffset > l ) {
|
|
|
|
|
fakeRange.startOffset -= 1;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2016-07-13 21:15:06 -05:00
|
|
|
|
else if ( fakeRange.startOffset === l ) {
|
|
|
|
|
fakeRange.startContainer = prev;
|
|
|
|
|
fakeRange.startOffset = getLength( prev );
|
2011-11-01 19:55:08 -05:00
|
|
|
|
}
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2016-07-13 21:15:06 -05:00
|
|
|
|
if ( fakeRange.endContainer === node ) {
|
|
|
|
|
if ( fakeRange.endOffset > l ) {
|
|
|
|
|
fakeRange.endOffset -= 1;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2016-07-13 21:15:06 -05:00
|
|
|
|
else if ( fakeRange.endOffset === l ) {
|
|
|
|
|
fakeRange.endContainer = prev;
|
|
|
|
|
fakeRange.endOffset = getLength( prev );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
detach( child );
|
|
|
|
|
if ( child.nodeType === TEXT_NODE ) {
|
2014-04-16 03:06:10 -05:00
|
|
|
|
prev.appendData( child.data );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
frags.push( empty( child ) );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
else if ( child.nodeType === ELEMENT_NODE ) {
|
|
|
|
|
len = frags.length;
|
|
|
|
|
while ( len-- ) {
|
|
|
|
|
child.appendChild( frags.pop() );
|
|
|
|
|
}
|
2016-07-13 21:15:06 -05:00
|
|
|
|
_mergeInlines( child, fakeRange );
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-13 21:15:06 -05:00
|
|
|
|
function mergeInlines ( node, range ) {
|
|
|
|
|
if ( node.nodeType === TEXT_NODE ) {
|
|
|
|
|
node = node.parentNode;
|
|
|
|
|
}
|
|
|
|
|
if ( node.nodeType === ELEMENT_NODE ) {
|
|
|
|
|
var fakeRange = {
|
|
|
|
|
startContainer: range.startContainer,
|
|
|
|
|
startOffset: range.startOffset,
|
|
|
|
|
endContainer: range.endContainer,
|
|
|
|
|
endOffset: range.endOffset
|
|
|
|
|
};
|
|
|
|
|
_mergeInlines( node, fakeRange );
|
|
|
|
|
range.setStart( fakeRange.startContainer, fakeRange.startOffset );
|
|
|
|
|
range.setEnd( fakeRange.endContainer, fakeRange.endOffset );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-15 05:33:32 -05:00
|
|
|
|
function mergeWithBlock ( block, next, range, root ) {
|
|
|
|
|
var container = next;
|
|
|
|
|
var parent, last, offset;
|
|
|
|
|
while ( ( parent = container.parentNode ) &&
|
|
|
|
|
parent !== root &&
|
|
|
|
|
parent.nodeType === ELEMENT_NODE &&
|
|
|
|
|
parent.childNodes.length === 1 ) {
|
|
|
|
|
container = parent;
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
detach( container );
|
|
|
|
|
|
|
|
|
|
offset = block.childNodes.length;
|
2012-01-24 19:47:26 -05:00
|
|
|
|
|
2013-04-07 22:27:06 -05:00
|
|
|
|
// Remove extra <BR> fixer if present.
|
|
|
|
|
last = block.lastChild;
|
|
|
|
|
if ( last && last.nodeName === 'BR' ) {
|
|
|
|
|
block.removeChild( last );
|
|
|
|
|
offset -= 1;
|
2011-10-28 22:15:21 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
|
|
|
|
block.appendChild( empty( next ) );
|
|
|
|
|
|
2016-07-13 21:15:06 -05:00
|
|
|
|
range.setStart( block, offset );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
range.collapse( true );
|
2016-07-13 21:15:06 -05:00
|
|
|
|
mergeInlines( block, range );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
|
|
|
|
|
// Opera inserts a BR if you delete the last piece of text
|
|
|
|
|
// in a block-level element. Unfortunately, it then gets
|
|
|
|
|
// confused when setting the selection subsequently and
|
|
|
|
|
// refuses to accept the range that finishes just before the
|
|
|
|
|
// BR. Removing the BR fixes the bug.
|
|
|
|
|
// Steps to reproduce bug: Type "a-b-c" (where - is return)
|
|
|
|
|
// then backspace twice. The cursor goes to the top instead
|
|
|
|
|
// of after "b".
|
2014-10-02 04:22:51 -05:00
|
|
|
|
if ( isPresto && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
block.removeChild( last );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-22 01:57:00 -05:00
|
|
|
|
function mergeContainers ( node, root ) {
|
2013-04-07 22:27:06 -05:00
|
|
|
|
var prev = node.previousSibling,
|
2014-04-06 22:05:44 -05:00
|
|
|
|
first = node.firstChild,
|
2014-06-01 19:17:00 -05:00
|
|
|
|
doc = node.ownerDocument,
|
2014-05-23 00:26:47 -05:00
|
|
|
|
isListItem = ( node.nodeName === 'LI' ),
|
2014-05-26 20:31:06 -05:00
|
|
|
|
needsFix, block;
|
2014-04-06 22:05:44 -05:00
|
|
|
|
|
|
|
|
|
// Do not merge LIs, unless it only contains a UL
|
|
|
|
|
if ( isListItem && ( !first || !/^[OU]L$/.test( first.nodeName ) ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-23 00:26:47 -05:00
|
|
|
|
if ( prev && areAlike( prev, node ) ) {
|
|
|
|
|
if ( !isContainer( prev ) ) {
|
|
|
|
|
if ( isListItem ) {
|
2015-03-29 02:35:03 -05:00
|
|
|
|
block = createElement( doc, 'DIV' );
|
2014-05-23 00:26:47 -05:00
|
|
|
|
block.appendChild( empty( prev ) );
|
|
|
|
|
prev.appendChild( block );
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
detach( node );
|
2014-05-26 20:31:06 -05:00
|
|
|
|
needsFix = !isContainer( node );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
prev.appendChild( empty( node ) );
|
2014-05-26 20:31:06 -05:00
|
|
|
|
if ( needsFix ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixContainer( prev, root );
|
2014-05-26 20:31:06 -05:00
|
|
|
|
}
|
2013-04-07 22:27:06 -05:00
|
|
|
|
if ( first ) {
|
2016-03-22 01:57:00 -05:00
|
|
|
|
mergeContainers( first, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
2014-04-06 22:05:44 -05:00
|
|
|
|
} else if ( isListItem ) {
|
2015-03-29 02:35:03 -05:00
|
|
|
|
prev = createElement( doc, 'DIV' );
|
2014-04-06 22:05:44 -05:00
|
|
|
|
node.insertBefore( prev, first );
|
2016-03-22 01:57:00 -05:00
|
|
|
|
fixCursor( prev, root );
|
2013-04-07 22:27:06 -05:00
|
|
|
|
}
|
|
|
|
|
}
|