mirror of
https://github.com/fastmail/Squire.git
synced 2025-01-03 05:00:13 -05:00
Replace Node prototype extensions with normal fns.
* Firefox was sometimes not finding the extensions on elements. * This minifies to a smaller target.
This commit is contained in:
parent
10abc3faf8
commit
af1720282c
11 changed files with 6130 additions and 2951 deletions
5
Makefile
5
Makefile
|
@ -11,8 +11,11 @@ build/ie8.js: source/ie8types.js source/ie8dom.js source/ie8range.js
|
|||
mkdir -p $(@D)
|
||||
uglifyjs $^ -c -m -o $@
|
||||
|
||||
build/squire.js: source/UA.js source/TreeWalker.js source/Node.js source/Range.js source/Editor.js
|
||||
build/squire-raw.js: source/intro.js source/Constants.js source/TreeWalker.js source/Node.js source/Range.js source/Editor.js source/outro.js
|
||||
mkdir -p $(@D)
|
||||
cat $^ >$@
|
||||
|
||||
build/squire.js: build/squire-raw.js
|
||||
uglifyjs $^ -c -m -o $@
|
||||
|
||||
build/document.html: source/document.html
|
||||
|
|
3200
build/squire-raw.js
Normal file
3200
build/squire-raw.js
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
33
source/Constants.js
Normal file
33
source/Constants.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*global doc, navigator */
|
||||
|
||||
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
||||
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
||||
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
||||
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
||||
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
|
||||
var FILTER_ACCEPT = 1; // NodeFilter.FILTER_ACCEPT;
|
||||
var FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP;
|
||||
|
||||
var START_TO_START = 0; // Range.START_TO_START
|
||||
var START_TO_END = 1; // Range.START_TO_END
|
||||
var END_TO_END = 2; // Range.END_TO_END
|
||||
var END_TO_START = 3; // Range.END_TO_START
|
||||
|
||||
var win = doc.defaultView;
|
||||
var body = doc.body;
|
||||
|
||||
var ua = navigator.userAgent;
|
||||
var isGecko = /Gecko\//.test( ua );
|
||||
var isIE = /Trident\//.test( ua );
|
||||
var isIE8 = ( win.ie === 8 );
|
||||
var isIOS = /iP(?:ad|hone|od)/.test( ua );
|
||||
var isOpera = !!win.opera;
|
||||
var isWebKit = /WebKit\//.test( ua );
|
||||
|
||||
var useTextFixer = isIE || isOpera;
|
||||
var cantFocusEmptyTextNodes = isIE || isWebKit;
|
||||
var losesSelectionOnBlur = isIE;
|
||||
|
||||
var notWS = /\S/;
|
||||
|
||||
var indexOf = Array.prototype.indexOf;
|
348
source/Editor.js
348
source/Editor.js
|
@ -1,60 +1,57 @@
|
|||
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||
/*global
|
||||
DOCUMENT_POSITION_PRECEDING,
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
SHOW_ELEMENT,
|
||||
SHOW_TEXT,
|
||||
FILTER_ACCEPT,
|
||||
FILTER_SKIP,
|
||||
doc,
|
||||
win,
|
||||
body,
|
||||
isGecko,
|
||||
isIE,
|
||||
isIE8,
|
||||
isIOS,
|
||||
isOpera,
|
||||
useTextFixer,
|
||||
cantFocusEmptyTextNodes,
|
||||
losesSelectionOnBlur,
|
||||
notWS,
|
||||
indexOf,
|
||||
|
||||
/*global UA, DOMTreeWalker, Range, top, document, setTimeout, console */
|
||||
TreeWalker,
|
||||
|
||||
( function ( doc, UA, TreeWalker ) {
|
||||
hasTagAttributes,
|
||||
isLeaf,
|
||||
isInline,
|
||||
isBlock,
|
||||
isContainer,
|
||||
getPreviousBlock,
|
||||
getNextBlock,
|
||||
getNearest,
|
||||
getPath,
|
||||
getLength,
|
||||
detach,
|
||||
replaceWith,
|
||||
empty,
|
||||
fixCursor,
|
||||
split,
|
||||
mergeInlines,
|
||||
mergeWithBlock,
|
||||
mergeContainers,
|
||||
createElement,
|
||||
|
||||
"use strict";
|
||||
Range,
|
||||
top,
|
||||
console,
|
||||
setTimeout
|
||||
*/
|
||||
/*jshint strict:false */
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
var DOCUMENT_POSITION_PRECEDING = 2, // Node.DOCUMENT_POSITION_PRECEDING
|
||||
ELEMENT_NODE = 1, // Node.ELEMENT_NODE,
|
||||
TEXT_NODE = 3, // Node.TEXT_NODE,
|
||||
SHOW_ELEMENT = 1, // NodeFilter.SHOW_ELEMENT,
|
||||
SHOW_TEXT = 4, // NodeFilter.SHOW_TEXT,
|
||||
FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT,
|
||||
FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP;
|
||||
|
||||
var win = doc.defaultView;
|
||||
var body = doc.body;
|
||||
var editor;
|
||||
|
||||
var isOpera = UA.isOpera;
|
||||
var isGecko = UA.isGecko;
|
||||
var isIOS = UA.isIOS;
|
||||
var isIE = UA.isIE;
|
||||
var isIE8 = UA.isIE8;
|
||||
|
||||
var cantFocusEmptyTextNodes = UA.cantFocusEmptyTextNodes;
|
||||
var losesSelectionOnBlur = UA.losesSelectionOnBlur;
|
||||
var useTextFixer = UA.useTextFixer;
|
||||
|
||||
var notWS = /\S/;
|
||||
|
||||
// --- DOM Sugar ---
|
||||
|
||||
var createElement = function ( tag, props, children ) {
|
||||
var el = doc.createElement( tag ),
|
||||
attr, i, l;
|
||||
if ( props instanceof Array ) {
|
||||
children = props;
|
||||
props = null;
|
||||
}
|
||||
if ( props ) {
|
||||
for ( attr in props ) {
|
||||
el.setAttribute( attr, props[ attr ] );
|
||||
}
|
||||
}
|
||||
if ( children ) {
|
||||
for ( i = 0, l = children.length; i < l; i += 1 ) {
|
||||
el.appendChild( children[i] );
|
||||
}
|
||||
}
|
||||
return el;
|
||||
};
|
||||
|
||||
// --- Events ---
|
||||
// --- Events.js ---
|
||||
|
||||
// Subscribing to these events won't automatically add a listener to the
|
||||
// document node, since these events are fired in a custom manner by the
|
||||
|
@ -167,10 +164,10 @@
|
|||
// catch and log it to see if we can find what's going on.
|
||||
try {
|
||||
// FF can return the selection as being inside an <img>. WTF?
|
||||
if ( startContainer && startContainer.isLeaf() ) {
|
||||
if ( startContainer && isLeaf( startContainer ) ) {
|
||||
lastSelection.setStartBefore( startContainer );
|
||||
}
|
||||
if ( endContainer && endContainer.isLeaf() ) {
|
||||
if ( endContainer && isLeaf( endContainer ) ) {
|
||||
lastSelection.setEndBefore( endContainer );
|
||||
}
|
||||
} catch ( error ) {
|
||||
|
@ -203,19 +200,6 @@
|
|||
willEnablePlaceholderRemoval = false;
|
||||
};
|
||||
|
||||
var setPlaceholderTextNode = function ( node ) {
|
||||
if ( placeholderTextNode ) {
|
||||
mayRemovePlaceholder = true;
|
||||
removePlaceholderTextNode();
|
||||
}
|
||||
if ( !willEnablePlaceholderRemoval ) {
|
||||
setTimeout( enablePlaceholderRemoval, 0 );
|
||||
willEnablePlaceholderRemoval = true;
|
||||
}
|
||||
mayRemovePlaceholder = false;
|
||||
placeholderTextNode = node;
|
||||
};
|
||||
|
||||
var removePlaceholderTextNode = function () {
|
||||
if ( !mayRemovePlaceholder ) { return; }
|
||||
|
||||
|
@ -229,12 +213,25 @@
|
|||
node.deleteData( index, 1 );
|
||||
}
|
||||
if ( !node.data && !node.nextSibling && !node.previousSibling &&
|
||||
node.parentNode.isInline() ) {
|
||||
node.parentNode.detach();
|
||||
isInline( node.parentNode ) ) {
|
||||
detach( node.parentNode );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var setPlaceholderTextNode = function ( node ) {
|
||||
if ( placeholderTextNode ) {
|
||||
mayRemovePlaceholder = true;
|
||||
removePlaceholderTextNode();
|
||||
}
|
||||
if ( !willEnablePlaceholderRemoval ) {
|
||||
setTimeout( enablePlaceholderRemoval, 0 );
|
||||
willEnablePlaceholderRemoval = true;
|
||||
}
|
||||
mayRemovePlaceholder = false;
|
||||
placeholderTextNode = node;
|
||||
};
|
||||
|
||||
// --- Path change events ---
|
||||
|
||||
var lastAnchorNode;
|
||||
|
@ -252,7 +249,7 @@
|
|||
lastAnchorNode = anchor;
|
||||
lastFocusNode = focus;
|
||||
newPath = ( anchor && focus ) ? ( anchor === focus ) ?
|
||||
focus.getPath() : '(selection)' : '';
|
||||
getPath( focus ) : '(selection)' : '';
|
||||
if ( path !== newPath ) {
|
||||
path = newPath;
|
||||
fireEvent( 'pathChange', { path: newPath } );
|
||||
|
@ -303,8 +300,8 @@
|
|||
var node = body;
|
||||
node.innerHTML = html;
|
||||
do {
|
||||
node.fixCursor();
|
||||
} while ( node = node.getNextBlock() );
|
||||
fixCursor( node );
|
||||
} while ( node = getNextBlock( node ) );
|
||||
};
|
||||
|
||||
var insertElement = function ( el, range ) {
|
||||
|
@ -318,8 +315,6 @@
|
|||
|
||||
// --- Bookmarking ---
|
||||
|
||||
var indexOf = Array.prototype.indexOf;
|
||||
|
||||
var startSelectionId = 'squire-selection-start';
|
||||
var endSelectionId = 'squire-selection-end';
|
||||
|
||||
|
@ -372,13 +367,13 @@
|
|||
_range.endOffset -= 1;
|
||||
}
|
||||
|
||||
start.detach();
|
||||
end.detach();
|
||||
detach( start );
|
||||
detach( end );
|
||||
|
||||
// Merge any text nodes we split
|
||||
startContainer.mergeInlines( _range );
|
||||
mergeInlines( startContainer, _range );
|
||||
if ( startContainer !== endContainer ) {
|
||||
endContainer.mergeInlines( _range );
|
||||
mergeInlines( endContainer, _range );
|
||||
}
|
||||
|
||||
if ( !range ) {
|
||||
|
@ -505,7 +500,7 @@
|
|||
// have the format.
|
||||
var root = range.commonAncestorContainer,
|
||||
walker, node;
|
||||
if ( root.nearest( tag, attributes ) ) {
|
||||
if ( getNearest( root, tag, attributes ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -524,7 +519,7 @@
|
|||
|
||||
var seenNode = false;
|
||||
while ( node = walker.nextNode() ) {
|
||||
if ( !node.nearest( tag, attributes ) ) {
|
||||
if ( !getNearest( node, tag, attributes ) ) {
|
||||
return false;
|
||||
}
|
||||
seenNode = true;
|
||||
|
@ -536,8 +531,11 @@
|
|||
var addFormat = function ( tag, attributes, range ) {
|
||||
// If the range is collapsed we simply insert the node by wrapping
|
||||
// it round the range and focus it.
|
||||
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
||||
textnode, needsFormat;
|
||||
|
||||
if ( range.collapsed ) {
|
||||
var el = createElement( tag, attributes ).fixCursor();
|
||||
el = fixCursor( createElement( tag, attributes ) );
|
||||
range._insertNode( el );
|
||||
range.setStart( el.firstChild, el.firstChild.length );
|
||||
range.collapse( true );
|
||||
|
@ -551,29 +549,28 @@
|
|||
// Create an iterator to walk over all the text nodes under this
|
||||
// ancestor which are in the range and not already formatted
|
||||
// correctly.
|
||||
var walker = new TreeWalker(
|
||||
walker = new TreeWalker(
|
||||
range.commonAncestorContainer,
|
||||
SHOW_TEXT,
|
||||
function ( node ) {
|
||||
return range.containsNode( node, true ) ?
|
||||
FILTER_ACCEPT : FILTER_SKIP;
|
||||
}, false );
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Start at the beginning node of the range and iterate through
|
||||
// all the nodes in the range that need formatting.
|
||||
var startContainer,
|
||||
endContainer,
|
||||
startOffset = 0,
|
||||
endOffset = 0,
|
||||
textnode = walker.currentNode = range.startContainer,
|
||||
needsFormat;
|
||||
startOffset = 0;
|
||||
endOffset = 0;
|
||||
textnode = walker.currentNode = range.startContainer;
|
||||
|
||||
if ( textnode.nodeType !== TEXT_NODE ) {
|
||||
textnode = walker.nextNode();
|
||||
}
|
||||
|
||||
do {
|
||||
needsFormat = !textnode.nearest( tag, attributes );
|
||||
needsFormat = !getNearest( textnode, tag, attributes );
|
||||
if ( textnode === range.endContainer ) {
|
||||
if ( needsFormat && textnode.length > range.endOffset ) {
|
||||
textnode.splitText( range.endOffset );
|
||||
|
@ -589,7 +586,9 @@
|
|||
}
|
||||
}
|
||||
if ( needsFormat ) {
|
||||
createElement( tag, attributes ).wraps( textnode );
|
||||
el = createElement( tag, attributes );
|
||||
replaceWith( textnode, el );
|
||||
el.appendChild( textnode );
|
||||
endOffset = textnode.length;
|
||||
}
|
||||
endContainer = textnode;
|
||||
|
@ -622,7 +621,7 @@
|
|||
|
||||
// Find block-level ancestor of selection
|
||||
var root = range.commonAncestorContainer;
|
||||
while ( root.isInline() ) {
|
||||
while ( isInline( root ) ) {
|
||||
root = root.parentNode;
|
||||
}
|
||||
|
||||
|
@ -677,7 +676,7 @@
|
|||
formatTags = Array.prototype.filter.call(
|
||||
root.getElementsByTagName( tag ), function ( el ) {
|
||||
return range.containsNode( el, true ) &&
|
||||
el.is( tag, attributes );
|
||||
hasTagAttributes( el, tag, attributes );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -690,11 +689,14 @@
|
|||
// Now wrap unselected nodes in the tag
|
||||
toWrap.forEach( function ( item ) {
|
||||
// [ exemplar, node ] tuple
|
||||
item[0].cloneNode( false ).wraps( item[1] );
|
||||
var el = item[0].cloneNode( false ),
|
||||
node = item[1];
|
||||
replaceWith( node, el );
|
||||
el.appendChild( node );
|
||||
});
|
||||
// and remove old formatting tags.
|
||||
formatTags.forEach( function ( el ) {
|
||||
el.replaceWith( el.empty() );
|
||||
replaceWith( el, empty( el ) );
|
||||
});
|
||||
|
||||
// Merge adjacent inlines:
|
||||
|
@ -708,7 +710,7 @@
|
|||
endContainer: range.endContainer,
|
||||
endOffset: range.endOffset
|
||||
};
|
||||
root.mergeInlines( _range );
|
||||
mergeInlines( root, _range );
|
||||
range.setStart( _range.startContainer, _range.startOffset );
|
||||
range.setEnd( _range.endContainer, _range.endOffset );
|
||||
|
||||
|
@ -760,15 +762,15 @@
|
|||
|
||||
var splitBlock = function ( block, node, offset ) {
|
||||
var splitTag = tagAfterSplit[ block.nodeName ],
|
||||
nodeAfterSplit = node.split( offset, block.parentNode );
|
||||
nodeAfterSplit = split( node, offset, block.parentNode );
|
||||
|
||||
// Make sure the new node is the correct type.
|
||||
if ( nodeAfterSplit.nodeName !== splitTag ) {
|
||||
block = createElement( splitTag );
|
||||
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
||||
block.dir = nodeAfterSplit.dir;
|
||||
block.replaces( nodeAfterSplit )
|
||||
.appendChild( nodeAfterSplit.empty() );
|
||||
replaceWith( nodeAfterSplit, block );
|
||||
block.appendChild( empty( nodeAfterSplit ) );
|
||||
nodeAfterSplit = block;
|
||||
}
|
||||
return nodeAfterSplit;
|
||||
|
@ -790,7 +792,7 @@
|
|||
if ( start && end ) {
|
||||
do {
|
||||
if ( fn( start ) || start === end ) { break; }
|
||||
} while ( start = start.getNextBlock() );
|
||||
} while ( start = getNextBlock( start ) );
|
||||
}
|
||||
|
||||
if ( mutates ) {
|
||||
|
@ -834,9 +836,9 @@
|
|||
|
||||
// 6. Merge containers at edges
|
||||
if ( range.endOffset < range.endContainer.childNodes.length ) {
|
||||
range.endContainer.childNodes[ range.endOffset ].mergeContainers();
|
||||
mergeContainers( range.endContainer.childNodes[ range.endOffset ] );
|
||||
}
|
||||
range.startContainer.childNodes[ range.startOffset ].mergeContainers();
|
||||
mergeContainers( range.startContainer.childNodes[ range.startOffset ] );
|
||||
|
||||
// 7. Make it editable again
|
||||
if ( !isOpera ) {
|
||||
|
@ -861,9 +863,9 @@
|
|||
var decreaseBlockQuoteLevel = function ( frag ) {
|
||||
var blockquotes = frag.querySelectorAll( 'blockquote' );
|
||||
Array.prototype.filter.call( blockquotes, function ( el ) {
|
||||
return !el.parentNode.nearest( 'BLOCKQUOTE' );
|
||||
return !getNearest( el.parentNode, 'BLOCKQUOTE' );
|
||||
}).forEach( function ( el ) {
|
||||
el.replaceWith( el.empty() );
|
||||
replaceWith( el, empty( el ) );
|
||||
});
|
||||
return frag;
|
||||
};
|
||||
|
@ -874,7 +876,7 @@
|
|||
bq;
|
||||
while ( l-- ) {
|
||||
bq = blockquotes[l];
|
||||
bq.replaceWith( bq.empty() );
|
||||
replaceWith( bq, empty( bq ) );
|
||||
}
|
||||
return frag;
|
||||
};
|
||||
|
@ -884,37 +886,36 @@
|
|||
for ( i = 0, l = nodes.length; i < l; i += 1 ) {
|
||||
node = nodes[i];
|
||||
tag = node.nodeName;
|
||||
if ( node.isBlock() ) {
|
||||
if ( isBlock( node ) ) {
|
||||
if ( tag !== 'LI' ) {
|
||||
replacement = createElement( 'LI', {
|
||||
'class': node.dir === 'rtl' ? 'dir-rtl' : '',
|
||||
dir: node.dir
|
||||
}, [
|
||||
node.empty()
|
||||
empty( node )
|
||||
]);
|
||||
if ( node.parentNode.nodeName === type ) {
|
||||
node.replaceWith( replacement );
|
||||
replaceWith( node, replacement );
|
||||
}
|
||||
else if ( ( prev = node.previousSibling ) &&
|
||||
prev.nodeName === type ) {
|
||||
prev.appendChild( replacement );
|
||||
node.detach();
|
||||
detach( node );
|
||||
i -= 1;
|
||||
l -= 1;
|
||||
}
|
||||
else {
|
||||
node.replaceWith(
|
||||
replaceWith(
|
||||
node,
|
||||
createElement( type, [
|
||||
replacement
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if ( node.isContainer() ) {
|
||||
} else if ( isContainer( node ) ) {
|
||||
if ( tag !== type && ( /^[DOU]L$/.test( tag ) ) ) {
|
||||
node.replaceWith( createElement( type, [
|
||||
node.empty()
|
||||
]) );
|
||||
replaceWith( node, createElement( type, [ empty( node ) ] ) );
|
||||
} else {
|
||||
makeList( node.childNodes, type );
|
||||
}
|
||||
|
@ -935,10 +936,10 @@
|
|||
var decreaseListLevel = function ( frag ) {
|
||||
var lists = frag.querySelectorAll( 'UL, OL' );
|
||||
Array.prototype.filter.call( lists, function ( el ) {
|
||||
return !el.parentNode.nearest( 'UL' ) &&
|
||||
!el.parentNode.nearest( 'OL' );
|
||||
return !getNearest( el.parentNode, 'UL' ) &&
|
||||
!getNearest( el.parentNode, 'OL' );
|
||||
}).forEach( function ( el ) {
|
||||
var frag = el.empty(),
|
||||
var frag = empty( el ),
|
||||
children = frag.childNodes,
|
||||
l = children.length,
|
||||
child;
|
||||
|
@ -949,11 +950,11 @@
|
|||
'class': child.dir === 'rtl' ? 'dir-rtl' : '',
|
||||
dir: child.dir
|
||||
}, [
|
||||
child.empty()
|
||||
empty( child )
|
||||
]), child );
|
||||
}
|
||||
}
|
||||
el.replaceWith( frag );
|
||||
replaceWith( el, frag );
|
||||
});
|
||||
return frag;
|
||||
};
|
||||
|
@ -966,7 +967,7 @@
|
|||
var doc = frag.ownerDocument,
|
||||
walker = new TreeWalker( frag, SHOW_TEXT,
|
||||
function ( node ) {
|
||||
return node.nearest( 'A' ) ? FILTER_SKIP : FILTER_ACCEPT;
|
||||
return getNearest( node, 'A' ) ? FILTER_SKIP : FILTER_ACCEPT;
|
||||
}, false ),
|
||||
node, parts, i, l, text, parent, next;
|
||||
while ( node = walker.nextNode() ) {
|
||||
|
@ -1085,7 +1086,7 @@
|
|||
}
|
||||
|
||||
if ( newTreeTop ) {
|
||||
newTreeBottom.appendChild( span.empty() );
|
||||
newTreeBottom.appendChild( empty( span ) );
|
||||
parent.replaceChild( newTreeTop, span );
|
||||
}
|
||||
|
||||
|
@ -1094,13 +1095,13 @@
|
|||
STRONG: function ( node, parent ) {
|
||||
var el = createElement( 'B' );
|
||||
parent.replaceChild( el, node );
|
||||
el.appendChild( node.empty() );
|
||||
el.appendChild( empty( node ) );
|
||||
return el;
|
||||
},
|
||||
EM: function ( node, parent ) {
|
||||
var el = createElement( 'I' );
|
||||
parent.replaceChild( el, node );
|
||||
el.appendChild( node.empty() );
|
||||
el.appendChild( empty( node ) );
|
||||
return el;
|
||||
},
|
||||
FONT: function ( node, parent ) {
|
||||
|
@ -1126,7 +1127,7 @@
|
|||
newTreeTop = fontSpan || sizeSpan || createElement( 'SPAN' );
|
||||
newTreeBottom = sizeSpan || fontSpan || newTreeTop;
|
||||
parent.replaceChild( newTreeTop, node );
|
||||
newTreeBottom.appendChild( node.empty() );
|
||||
newTreeBottom.appendChild( empty( node ) );
|
||||
return newTreeBottom;
|
||||
},
|
||||
TT: function ( node, parent ) {
|
||||
|
@ -1135,7 +1136,7 @@
|
|||
style: 'font-family:menlo,consolas,"courier new",monospace'
|
||||
});
|
||||
parent.replaceChild( el, node );
|
||||
el.appendChild( node.empty() );
|
||||
el.appendChild( empty( node ) );
|
||||
return el;
|
||||
}
|
||||
};
|
||||
|
@ -1148,7 +1149,7 @@
|
|||
child = children[l];
|
||||
if ( child.nodeType === ELEMENT_NODE ) {
|
||||
removeEmptyInlines( child );
|
||||
if ( child.isInline() && !child.firstChild ) {
|
||||
if ( isInline( child ) && !child.firstChild ) {
|
||||
root.removeChild( child );
|
||||
}
|
||||
}
|
||||
|
@ -1175,10 +1176,10 @@
|
|||
if ( rewriter ) {
|
||||
child = rewriter( child, node );
|
||||
} else if ( !allowedBlock.test( nodeName ) &&
|
||||
!child.isInline() ) {
|
||||
!isInline( child ) ) {
|
||||
i -= 1;
|
||||
l += childLength - 1;
|
||||
node.replaceChild( child.empty(), child );
|
||||
node.replaceChild( empty( child ), child );
|
||||
continue;
|
||||
} else if ( !allowStyles && child.style.cssText ) {
|
||||
child.removeAttribute( 'style' );
|
||||
|
@ -1188,8 +1189,8 @@
|
|||
}
|
||||
} else if ( nodeType !== TEXT_NODE || (
|
||||
!( notWS.test( child.data ) ) &&
|
||||
!( i > 0 && children[ i - 1 ].isInline() ) &&
|
||||
!( i + 1 < l && children[ i + 1 ].isInline() )
|
||||
!( i > 0 && isInline( children[ i - 1 ] ) ) &&
|
||||
!( i + 1 < l && isInline( children[ i + 1 ] ) )
|
||||
) ) {
|
||||
node.removeChild( child );
|
||||
i -= 1;
|
||||
|
@ -1206,14 +1207,14 @@
|
|||
for ( i = 0, l = children.length; i < l; i += 1 ) {
|
||||
child = children[i];
|
||||
isBR = child.nodeName === 'BR';
|
||||
if ( !isBR && child.isInline() ) {
|
||||
if ( !isBR && isInline( child ) ) {
|
||||
if ( !wrapper ) { wrapper = createElement( tag ); }
|
||||
wrapper.appendChild( child );
|
||||
i -= 1;
|
||||
l -= 1;
|
||||
} else if ( isBR || wrapper ) {
|
||||
if ( !wrapper ) { wrapper = createElement( tag ); }
|
||||
wrapper.fixCursor();
|
||||
fixCursor( wrapper );
|
||||
if ( isBR ) {
|
||||
root.replaceChild( wrapper, child );
|
||||
} else {
|
||||
|
@ -1225,7 +1226,7 @@
|
|||
}
|
||||
}
|
||||
if ( wrapper ) {
|
||||
root.appendChild( wrapper.fixCursor() );
|
||||
root.appendChild( fixCursor( wrapper ) );
|
||||
}
|
||||
return root;
|
||||
};
|
||||
|
@ -1239,7 +1240,7 @@
|
|||
var isLineBreak = function ( br ) {
|
||||
var block = br.parentNode,
|
||||
walker;
|
||||
while ( block.isInline() ) {
|
||||
while ( isInline( block ) ) {
|
||||
block = block.parentNode;
|
||||
}
|
||||
walker = new TreeWalker(
|
||||
|
@ -1274,12 +1275,12 @@
|
|||
// Cleanup may have removed it
|
||||
block = br.parentNode;
|
||||
if ( !block ) { continue; }
|
||||
while ( block.isInline() ) {
|
||||
while ( isInline( block ) ) {
|
||||
block = block.parentNode;
|
||||
}
|
||||
// If this is not inside a block, replace it by wrapping
|
||||
// inlines in DIV.
|
||||
if ( !block.isBlock() || !tagAfterSplit[ block.nodeName ] ) {
|
||||
if ( !isBlock( block ) || !tagAfterSplit[ block.nodeName ] ) {
|
||||
wrapTopLevelInline( block, 'DIV' );
|
||||
}
|
||||
// If in a block we can split, split it instead, but only if there
|
||||
|
@ -1290,7 +1291,7 @@
|
|||
if ( brBreaksLine[l] ) {
|
||||
splitBlock( block, br.parentNode, br );
|
||||
}
|
||||
br.detach();
|
||||
detach( br );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1300,7 +1301,7 @@
|
|||
var afterCut = function () {
|
||||
try {
|
||||
// If all content removed, ensure div at start of body.
|
||||
body.fixCursor();
|
||||
fixCursor( body );
|
||||
} catch ( error ) {
|
||||
editor.didError( error );
|
||||
}
|
||||
|
@ -1372,7 +1373,7 @@
|
|||
setTimeout( function () {
|
||||
try {
|
||||
// Get the pasted content and clean
|
||||
var frag = pasteArea.detach().empty(),
|
||||
var frag = empty( detach( pasteArea ) ),
|
||||
first = frag.firstChild,
|
||||
range = createRange(
|
||||
startContainer, startOffset, endContainer, endOffset );
|
||||
|
@ -1382,7 +1383,7 @@
|
|||
// Safari and IE like putting extra divs around things.
|
||||
if ( first === frag.lastChild &&
|
||||
first.nodeName === 'DIV' ) {
|
||||
frag.replaceChild( first.empty(), first );
|
||||
frag.replaceChild( empty( first ), first );
|
||||
}
|
||||
|
||||
frag.normalize();
|
||||
|
@ -1393,8 +1394,8 @@
|
|||
|
||||
var node = frag,
|
||||
doPaste = true;
|
||||
while ( node = node.getNextBlock() ) {
|
||||
node.fixCursor();
|
||||
while ( node = getNextBlock( node ) ) {
|
||||
fixCursor( node );
|
||||
}
|
||||
|
||||
fireEvent( 'willPaste', {
|
||||
|
@ -1465,19 +1466,19 @@
|
|||
node = node.parentNode;
|
||||
}
|
||||
// If focussed in empty inline element
|
||||
if ( node.isInline() && !node.textContent ) {
|
||||
if ( isInline( node ) && !node.textContent ) {
|
||||
do {
|
||||
parent = node.parentNode;
|
||||
} while ( parent.isInline() &&
|
||||
} while ( isInline( parent ) &&
|
||||
!parent.textContent && ( node = parent ) );
|
||||
range.setStart( parent,
|
||||
indexOf.call( parent.childNodes, node ) );
|
||||
range.collapse( true );
|
||||
parent.removeChild( node );
|
||||
if ( !parent.isBlock() ) {
|
||||
parent = parent.getPreviousBlock();
|
||||
if ( !isBlock( parent ) ) {
|
||||
parent = getPreviousBlock( parent );
|
||||
}
|
||||
parent.fixCursor();
|
||||
fixCursor( parent );
|
||||
range.moveBoundariesDownTree();
|
||||
setSelection( range );
|
||||
updatePath( range );
|
||||
|
@ -1494,8 +1495,8 @@
|
|||
var firstChild = body.firstChild;
|
||||
if ( firstChild.nodeName === 'P' ) {
|
||||
saveRangeToBookmark( getSelection() );
|
||||
firstChild.replaceWith( createElement( 'DIV', [
|
||||
firstChild.empty()
|
||||
replaceWith( firstChild, createElement( 'DIV', [
|
||||
empty( firstChild )
|
||||
]) );
|
||||
setSelection( getRangeAndRemoveBookmark() );
|
||||
}
|
||||
|
@ -1554,10 +1555,10 @@
|
|||
if ( splitNode.nodeName === 'BR' ) {
|
||||
splitNode = splitNode.nextSibling;
|
||||
} else {
|
||||
splitOffset = splitNode.getLength();
|
||||
splitOffset = getLength( splitNode );
|
||||
}
|
||||
if ( !splitNode || splitNode.nodeName === 'BR' ) {
|
||||
replacement = createElement( 'DIV' ).fixCursor();
|
||||
replacement = fixCursor( createElement( 'DIV' ) );
|
||||
if ( splitNode ) {
|
||||
block.replaceChild( replacement, splitNode );
|
||||
} else {
|
||||
|
@ -1579,11 +1580,11 @@
|
|||
|
||||
if ( !block.textContent ) {
|
||||
// Break list
|
||||
if ( block.nearest( 'UL' ) || block.nearest( 'OL' ) ) {
|
||||
if ( getNearest( block, 'UL' ) || getNearest( block, 'OL' ) ) {
|
||||
return modifyBlocks( decreaseListLevel, range );
|
||||
}
|
||||
// Break blockquote
|
||||
else if ( block.nearest( 'BLOCKQUOTE' ) ) {
|
||||
else if ( getNearest( block, 'BLOCKQUOTE' ) ) {
|
||||
return modifyBlocks( removeBlockQuote, range );
|
||||
}
|
||||
}
|
||||
|
@ -1601,7 +1602,7 @@
|
|||
// Don't continue links over a block break; unlikely to be the
|
||||
// desired outcome.
|
||||
if ( nodeAfterSplit.nodeName === 'A' ) {
|
||||
nodeAfterSplit.replaceWith( nodeAfterSplit.empty() );
|
||||
replaceWith( nodeAfterSplit, empty( nodeAfterSplit ) );
|
||||
nodeAfterSplit = child;
|
||||
continue;
|
||||
}
|
||||
|
@ -1611,7 +1612,7 @@
|
|||
if ( !next || next.nodeName === 'BR' ) {
|
||||
break;
|
||||
}
|
||||
child.detach();
|
||||
detach( child );
|
||||
child = next;
|
||||
}
|
||||
|
||||
|
@ -1658,16 +1659,16 @@
|
|||
getRangeAndRemoveBookmark( range );
|
||||
event.preventDefault();
|
||||
var current = range.getStartBlock(),
|
||||
previous = current && current.getPreviousBlock();
|
||||
previous = current && getPreviousBlock( current );
|
||||
// Must not be at the very beginning of the text area.
|
||||
if ( previous ) {
|
||||
// If not editable, just delete whole block.
|
||||
if ( !previous.isContentEditable ) {
|
||||
previous.detach();
|
||||
detach( previous );
|
||||
return;
|
||||
}
|
||||
// Otherwise merge.
|
||||
previous.mergeWithBlock( current, range );
|
||||
mergeWithBlock( previous, current, range );
|
||||
// If deleted line between containers, merge newly adjacent
|
||||
// containers.
|
||||
current = previous.parentNode;
|
||||
|
@ -1675,7 +1676,7 @@
|
|||
current = current.parentNode;
|
||||
}
|
||||
if ( current && ( current = current.nextSibling ) ) {
|
||||
current.mergeContainers();
|
||||
mergeContainers( current );
|
||||
}
|
||||
setSelection( range );
|
||||
}
|
||||
|
@ -1683,11 +1684,12 @@
|
|||
// to break lists/blockquote.
|
||||
else {
|
||||
// Break list
|
||||
if ( current.nearest( 'UL' ) || current.nearest( 'OL' ) ) {
|
||||
if ( getNearest( current, 'UL' ) ||
|
||||
getNearest( current, 'OL' ) ) {
|
||||
return modifyBlocks( decreaseListLevel, range );
|
||||
}
|
||||
// Break blockquote
|
||||
else if ( current.nearest( 'BLOCKQUOTE' ) ) {
|
||||
else if ( getNearest( current, 'BLOCKQUOTE' ) ) {
|
||||
return modifyBlocks( decreaseBlockQuoteLevel, range );
|
||||
}
|
||||
setSelection( range );
|
||||
|
@ -1723,16 +1725,16 @@
|
|||
getRangeAndRemoveBookmark( range );
|
||||
event.preventDefault();
|
||||
var current = range.getStartBlock(),
|
||||
next = current && current.getNextBlock();
|
||||
next = current && getNextBlock( current );
|
||||
// Must not be at the very end of the text area.
|
||||
if ( next ) {
|
||||
// If not editable, just delete whole block.
|
||||
if ( !next.isContentEditable ) {
|
||||
next.detach();
|
||||
detach( next );
|
||||
return;
|
||||
}
|
||||
// Otherwise merge.
|
||||
current.mergeWithBlock( next, range );
|
||||
mergeWithBlock( current, next, range );
|
||||
// If deleted line between containers, merge newly adjacent
|
||||
// containers.
|
||||
next = current.parentNode;
|
||||
|
@ -1740,7 +1742,7 @@
|
|||
next = next.parentNode;
|
||||
}
|
||||
if ( next && ( next = next.nextSibling ) ) {
|
||||
next.mergeContainers();
|
||||
mergeContainers( next );
|
||||
}
|
||||
setSelection( range );
|
||||
updatePath( range, true );
|
||||
|
@ -1821,14 +1823,12 @@
|
|||
};
|
||||
};
|
||||
|
||||
win.editor = editor = {
|
||||
editor = win.editor = {
|
||||
|
||||
didError: function ( error ) {
|
||||
console.log( error );
|
||||
},
|
||||
|
||||
_setPlaceholderTextNode: setPlaceholderTextNode,
|
||||
|
||||
addEventListener: chain( addEventListener ),
|
||||
removeEventListener: chain( removeEventListener ),
|
||||
|
||||
|
@ -1867,7 +1867,7 @@
|
|||
}
|
||||
if ( useTextFixer ) {
|
||||
node = body;
|
||||
while ( node = node.getNextBlock() ) {
|
||||
while ( node = getNextBlock( node ) ) {
|
||||
if ( !node.textContent && !node.querySelector( 'BR' ) ) {
|
||||
fixer = createElement( 'BR' );
|
||||
node.appendChild( fixer );
|
||||
|
@ -1879,7 +1879,7 @@
|
|||
if ( useTextFixer ) {
|
||||
l = brs.length;
|
||||
while ( l-- ) {
|
||||
brs[l].detach();
|
||||
detach( brs[l] );
|
||||
}
|
||||
}
|
||||
if ( range ) {
|
||||
|
@ -1894,7 +1894,7 @@
|
|||
|
||||
// Parse HTML into DOM tree
|
||||
div.innerHTML = html;
|
||||
frag.appendChild( div.empty() );
|
||||
frag.appendChild( empty( div ) );
|
||||
|
||||
cleanTree( frag, true );
|
||||
cleanupBRs( frag );
|
||||
|
@ -1903,8 +1903,8 @@
|
|||
|
||||
// Fix cursor
|
||||
var node = frag;
|
||||
while ( node = node.getNextBlock() ) {
|
||||
node.fixCursor();
|
||||
while ( node = getNextBlock( node ) ) {
|
||||
fixCursor( node );
|
||||
}
|
||||
|
||||
// Remove existing body children
|
||||
|
@ -1914,7 +1914,7 @@
|
|||
|
||||
// And insert new content
|
||||
body.appendChild( frag );
|
||||
body.fixCursor();
|
||||
fixCursor( body );
|
||||
|
||||
// Reset the undo stack
|
||||
undoIndex = -1;
|
||||
|
@ -2116,5 +2116,3 @@
|
|||
win.onEditorLoad( win.editor );
|
||||
win.onEditorLoad = null;
|
||||
}
|
||||
|
||||
}( document, UA, DOMTreeWalker ) );
|
||||
|
|
586
source/Node.js
586
source/Node.js
|
@ -1,37 +1,23 @@
|
|||
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||
/*global
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
SHOW_ELEMENT,
|
||||
FILTER_ACCEPT,
|
||||
FILTER_SKIP,
|
||||
doc,
|
||||
isOpera,
|
||||
useTextFixer,
|
||||
cantFocusEmptyTextNodes,
|
||||
|
||||
/*global Node, Text, Element, HTMLDocument, window, document,
|
||||
editor, UA, DOMTreeWalker */
|
||||
TreeWalker,
|
||||
|
||||
( function ( UA, TreeWalker ) {
|
||||
Text,
|
||||
|
||||
"use strict";
|
||||
setPlaceholderTextNode
|
||||
*/
|
||||
/*jshint strict:false */
|
||||
|
||||
var implement = function ( constructors, props ) {
|
||||
var l = constructors.length,
|
||||
proto, prop;
|
||||
while ( l-- ) {
|
||||
proto = constructors[l].prototype;
|
||||
for ( prop in props ) {
|
||||
proto[ prop ] = props[ prop ];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var every = function ( nodeList, fn ) {
|
||||
var l = nodeList.length;
|
||||
while ( l-- ) {
|
||||
if ( !fn( nodeList[l] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var $False = function () { return false; };
|
||||
var $True = function () { return true; };
|
||||
|
||||
var inlineNodeNames = /^(?:A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:FN|EL)|EM|FONT|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TRONG|AMP)|U)$/;
|
||||
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:FN|EL)|EM|FONT|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TRONG|AMP)|U)$/;
|
||||
|
||||
var leafNodeNames = {
|
||||
BR: 1,
|
||||
|
@ -39,208 +25,290 @@ var leafNodeNames = {
|
|||
INPUT: 1
|
||||
};
|
||||
|
||||
var swap = function ( node, node2 ) {
|
||||
var parent = node2.parentNode;
|
||||
if ( parent ) {
|
||||
parent.replaceChild( node, node2 );
|
||||
function every ( nodeList, fn ) {
|
||||
var l = nodeList.length;
|
||||
while ( l-- ) {
|
||||
if ( !fn( nodeList[l] ) ) {
|
||||
return false;
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
var ELEMENT_NODE = 1, // Node.ELEMENT_NODE,
|
||||
TEXT_NODE = 3, // Node.TEXT_NODE,
|
||||
SHOW_ELEMENT = 1, // NodeFilter.SHOW_ELEMENT,
|
||||
FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT,
|
||||
FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP;
|
||||
|
||||
var isBlock = function ( el ) {
|
||||
return el.isBlock() ? FILTER_ACCEPT : FILTER_SKIP;
|
||||
};
|
||||
|
||||
implement( window.Node ? [ Node ] : [ Text, Element, HTMLDocument ], {
|
||||
isLeaf: $False,
|
||||
isInline: $False,
|
||||
isBlock: $False,
|
||||
isContainer: $False,
|
||||
getPath: function () {
|
||||
var parent = this.parentNode;
|
||||
return parent ? parent.getPath() : '';
|
||||
},
|
||||
detach: function () {
|
||||
var parent = this.parentNode;
|
||||
if ( parent ) {
|
||||
parent.removeChild( this );
|
||||
}
|
||||
return this;
|
||||
},
|
||||
replaceWith: function ( node ) {
|
||||
swap( node, this );
|
||||
return this;
|
||||
},
|
||||
replaces: function ( node ) {
|
||||
swap( this, node );
|
||||
return this;
|
||||
},
|
||||
nearest: function ( tag, attributes ) {
|
||||
var parent = this.parentNode;
|
||||
return parent ? parent.nearest( tag, attributes ) : null;
|
||||
},
|
||||
getPreviousBlock: function () {
|
||||
var doc = this.ownerDocument,
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
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;
|
||||
}
|
||||
function areAlike ( node, node2 ) {
|
||||
return (
|
||||
node.nodeType === node2.nodeType &&
|
||||
node.nodeName === node2.nodeName &&
|
||||
node.className === node2.className &&
|
||||
( ( !node.style && !node2.style ) ||
|
||||
node.style.cssText === node2.style.cssText )
|
||||
);
|
||||
}
|
||||
|
||||
function isLeaf ( node ) {
|
||||
return node.nodeType === ELEMENT_NODE &&
|
||||
!!leafNodeNames[ node.nodeName ];
|
||||
}
|
||||
function isInline ( node ) {
|
||||
return inlineNodeNames.test( node.nodeName );
|
||||
}
|
||||
function isBlock ( node ) {
|
||||
return node.nodeType === ELEMENT_NODE &&
|
||||
!isInline( node ) && every( node.childNodes, isInline );
|
||||
}
|
||||
function isContainer ( node ) {
|
||||
return node.nodeType === ELEMENT_NODE &&
|
||||
!isInline( node ) && !isBlock( node );
|
||||
}
|
||||
|
||||
function acceptIfBlock ( el ) {
|
||||
return isBlock( el ) ? FILTER_ACCEPT : FILTER_SKIP;
|
||||
}
|
||||
function getBlockWalker ( node ) {
|
||||
var doc = node.ownerDocument,
|
||||
walker = new TreeWalker(
|
||||
doc.body, SHOW_ELEMENT, isBlock, false );
|
||||
walker.currentNode = this;
|
||||
return walker.previousNode();
|
||||
},
|
||||
getNextBlock: function () {
|
||||
var doc = this.ownerDocument,
|
||||
walker = new TreeWalker(
|
||||
doc.body, SHOW_ELEMENT, isBlock, false );
|
||||
walker.currentNode = this;
|
||||
return walker.nextNode();
|
||||
},
|
||||
split: function ( node, stopNode ) {
|
||||
doc.body, SHOW_ELEMENT, acceptIfBlock, false );
|
||||
walker.currentNode = node;
|
||||
return walker;
|
||||
}
|
||||
|
||||
function getPreviousBlock ( node ) {
|
||||
return getBlockWalker( node ).previousNode();
|
||||
}
|
||||
function getNextBlock ( node ) {
|
||||
return getBlockWalker( node ).nextNode();
|
||||
}
|
||||
function getNearest ( node, tag, attributes ) {
|
||||
do {
|
||||
if ( hasTagAttributes( node, tag, attributes ) ) {
|
||||
return node;
|
||||
},
|
||||
mergeContainers: function () {}
|
||||
});
|
||||
|
||||
implement([ Text ], {
|
||||
isInline: $True,
|
||||
getLength: function () {
|
||||
return this.length;
|
||||
},
|
||||
isLike: function ( node ) {
|
||||
return node.nodeType === TEXT_NODE;
|
||||
},
|
||||
split: function ( offset, stopNode ) {
|
||||
var node = this;
|
||||
if ( node === stopNode ) {
|
||||
return offset;
|
||||
}
|
||||
return node.parentNode.split( node.splitText( offset ), stopNode );
|
||||
} while ( node = node.parentNode );
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
implement([ Element ], {
|
||||
isLeaf: function () {
|
||||
return !!leafNodeNames[ this.nodeName ];
|
||||
},
|
||||
isInline: function () {
|
||||
return inlineNodeNames.test( this.nodeName );
|
||||
},
|
||||
isBlock: function () {
|
||||
return !this.isInline() && every( this.childNodes, function ( child ) {
|
||||
return child.isInline();
|
||||
});
|
||||
},
|
||||
isContainer: function () {
|
||||
return !this.isInline() && !this.isBlock();
|
||||
},
|
||||
getLength: function () {
|
||||
return this.childNodes.length;
|
||||
},
|
||||
getPath: function () {
|
||||
var parent = this.parentNode,
|
||||
function getPath ( node ) {
|
||||
var parent = node.parentNode,
|
||||
path, id, className, classNames;
|
||||
if ( !parent ) {
|
||||
return '';
|
||||
}
|
||||
path = parent.getPath();
|
||||
path += ( path ? '>' : '' ) + this.nodeName;
|
||||
if ( id = this.id ) {
|
||||
if ( !parent || node.nodeType !== ELEMENT_NODE ) {
|
||||
path = parent ? getPath( parent ) : '';
|
||||
} else {
|
||||
path = getPath( parent );
|
||||
path += ( path ? '>' : '' ) + node.nodeName;
|
||||
if ( id = node.id ) {
|
||||
path += '#' + id;
|
||||
}
|
||||
if ( className = this.className.trim() ) {
|
||||
if ( className = node.className.trim() ) {
|
||||
classNames = className.split( /\s\s*/ );
|
||||
classNames.sort();
|
||||
path += '.';
|
||||
path += classNames.join( '.' );
|
||||
}
|
||||
}
|
||||
return path;
|
||||
},
|
||||
wraps: function ( node ) {
|
||||
swap( this, node ).appendChild( node );
|
||||
return this;
|
||||
},
|
||||
empty: function () {
|
||||
var frag = this.ownerDocument.createDocumentFragment(),
|
||||
l = this.childNodes.length;
|
||||
}
|
||||
|
||||
function getLength ( node ) {
|
||||
var nodeType = node.nodeType;
|
||||
return nodeType === ELEMENT_NODE ?
|
||||
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( this.firstChild );
|
||||
frag.appendChild( node.firstChild );
|
||||
}
|
||||
return frag;
|
||||
},
|
||||
is: function ( tag, attributes ) {
|
||||
if ( this.nodeName !== tag ) { return false; }
|
||||
var attr;
|
||||
for ( attr in attributes ) {
|
||||
if ( this.getAttribute( attr ) !== attributes[ attr ] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function fixCursor ( node ) {
|
||||
// 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.
|
||||
var doc = node.ownerDocument,
|
||||
fixer, child;
|
||||
|
||||
if ( node.nodeName === 'BODY' ) {
|
||||
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
||||
fixer = doc.createElement( 'DIV' );
|
||||
if ( child ) {
|
||||
node.replaceChild( fixer, child );
|
||||
}
|
||||
else {
|
||||
node.appendChild( fixer );
|
||||
}
|
||||
node = fixer;
|
||||
fixer = null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
nearest: function ( tag, attributes ) {
|
||||
var el = this;
|
||||
do {
|
||||
if ( el.is( tag, attributes ) ) {
|
||||
return el;
|
||||
|
||||
if ( isInline( node ) ) {
|
||||
if ( !node.firstChild ) {
|
||||
if ( cantFocusEmptyTextNodes ) {
|
||||
fixer = doc.createTextNode( '\u200B' );
|
||||
setPlaceholderTextNode( fixer );
|
||||
} else {
|
||||
fixer = doc.createTextNode( '' );
|
||||
}
|
||||
} while ( ( el = el.parentNode ) &&
|
||||
( el.nodeType === ELEMENT_NODE ) );
|
||||
return null;
|
||||
},
|
||||
isLike: function ( node ) {
|
||||
return (
|
||||
node.nodeType === ELEMENT_NODE &&
|
||||
node.nodeName === this.nodeName &&
|
||||
node.className === this.className &&
|
||||
node.style.cssText === this.style.cssText
|
||||
);
|
||||
},
|
||||
mergeInlines: function ( range ) {
|
||||
var children = this.childNodes,
|
||||
}
|
||||
} else {
|
||||
if ( useTextFixer ) {
|
||||
while ( node.nodeType !== TEXT_NODE && !isLeaf( node ) ) {
|
||||
child = node.firstChild;
|
||||
if ( !child ) {
|
||||
fixer = doc.createTextNode( '' );
|
||||
break;
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
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 = '';
|
||||
}
|
||||
} else if ( isLeaf( node ) ) {
|
||||
node.parentNode.insertBefore( doc.createTextNode( '' ), node );
|
||||
}
|
||||
}
|
||||
else if ( !node.querySelector( 'BR' ) ) {
|
||||
fixer = doc.createElement( 'BR' );
|
||||
while ( ( child = node.lastElementChild ) && !isInline( child ) ) {
|
||||
node = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( fixer ) {
|
||||
node.appendChild( fixer );
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function split ( node, offset, stopNode ) {
|
||||
var nodeType = node.nodeType,
|
||||
parent, clone, next;
|
||||
if ( nodeType === TEXT_NODE ) {
|
||||
if ( node === stopNode ) {
|
||||
return offset;
|
||||
}
|
||||
return split( node.parentNode, node.splitText( offset ), stopNode );
|
||||
}
|
||||
if ( nodeType === ELEMENT_NODE ) {
|
||||
if ( typeof( offset ) === 'number' ) {
|
||||
offset = offset < node.childNodes.length ?
|
||||
node.childNodes[ offset ] : null;
|
||||
}
|
||||
if ( node === stopNode ) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
// Clone node without children
|
||||
parent = node.parentNode,
|
||||
clone = node.cloneNode( false );
|
||||
|
||||
// Add right-hand siblings to the clone
|
||||
while ( offset ) {
|
||||
next = offset.nextSibling;
|
||||
clone.appendChild( offset );
|
||||
offset = next;
|
||||
}
|
||||
|
||||
// DO NOT NORMALISE. This may undo the fixCursor() call
|
||||
// of a node lower down the tree!
|
||||
|
||||
// We need something in the element in order for the cursor to appear.
|
||||
fixCursor( node );
|
||||
fixCursor( clone );
|
||||
|
||||
// Inject clone after original node
|
||||
if ( next = node.nextSibling ) {
|
||||
parent.insertBefore( clone, next );
|
||||
} else {
|
||||
parent.appendChild( clone );
|
||||
}
|
||||
|
||||
// Keep on splitting up the tree
|
||||
return split( parent, clone, stopNode );
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function mergeInlines ( node, range ) {
|
||||
if ( node.nodeType !== ELEMENT_NODE ) {
|
||||
return;
|
||||
}
|
||||
var children = node.childNodes,
|
||||
l = children.length,
|
||||
frags = [],
|
||||
child, prev, len;
|
||||
while ( l-- ) {
|
||||
child = children[l];
|
||||
prev = l && children[ l - 1 ];
|
||||
if ( l && child.isInline() && child.isLike( prev ) &&
|
||||
if ( l && isInline( child ) && areAlike( child, prev ) &&
|
||||
!leafNodeNames[ child.nodeName ] ) {
|
||||
if ( range.startContainer === child ) {
|
||||
range.startContainer = prev;
|
||||
range.startOffset += prev.getLength();
|
||||
range.startOffset += getLength( prev );
|
||||
}
|
||||
if ( range.endContainer === child ) {
|
||||
range.endContainer = prev;
|
||||
range.endOffset += prev.getLength();
|
||||
range.endOffset += getLength( prev );
|
||||
}
|
||||
if ( range.startContainer === this ) {
|
||||
if ( range.startContainer === node ) {
|
||||
if ( range.startOffset > l ) {
|
||||
range.startOffset -= 1;
|
||||
}
|
||||
else if ( range.startOffset === l ) {
|
||||
range.startContainer = prev;
|
||||
range.startOffset = prev.getLength();
|
||||
range.startOffset = getLength( prev );
|
||||
}
|
||||
}
|
||||
if ( range.endContainer === this ) {
|
||||
if ( range.endContainer === node ) {
|
||||
if ( range.endOffset > l ) {
|
||||
range.endOffset -= 1;
|
||||
}
|
||||
else if ( range.endOffset === l ) {
|
||||
range.endContainer = prev;
|
||||
range.endOffset = prev.getLength();
|
||||
range.endOffset = getLength( prev );
|
||||
}
|
||||
}
|
||||
child.detach();
|
||||
detach( child );
|
||||
if ( child.nodeType === TEXT_NODE ) {
|
||||
prev.appendData( child.data.replace( /\u200B/g, '' ) );
|
||||
}
|
||||
else {
|
||||
frags.push( child.empty() );
|
||||
frags.push( empty( child ) );
|
||||
}
|
||||
}
|
||||
else if ( child.nodeType === ELEMENT_NODE ) {
|
||||
|
@ -248,18 +316,18 @@ implement([ Element ], {
|
|||
while ( len-- ) {
|
||||
child.appendChild( frags.pop() );
|
||||
}
|
||||
child.mergeInlines( range );
|
||||
mergeInlines( child, range );
|
||||
}
|
||||
}
|
||||
},
|
||||
mergeWithBlock: function ( next, range ) {
|
||||
var block = this,
|
||||
container = next,
|
||||
}
|
||||
|
||||
function mergeWithBlock ( block, next, range ) {
|
||||
var container = next,
|
||||
last, offset, _range;
|
||||
while ( container.parentNode.childNodes.length === 1 ) {
|
||||
container = container.parentNode;
|
||||
}
|
||||
container.detach();
|
||||
detach( container );
|
||||
|
||||
offset = block.childNodes.length;
|
||||
|
||||
|
@ -277,11 +345,10 @@ implement([ Element ], {
|
|||
endOffset: offset
|
||||
};
|
||||
|
||||
block.appendChild( next.empty() );
|
||||
block.mergeInlines( _range );
|
||||
block.appendChild( empty( next ) );
|
||||
mergeInlines( block, _range );
|
||||
|
||||
range.setStart(
|
||||
_range.startContainer, _range.startOffset );
|
||||
range.setStart( _range.startContainer, _range.startOffset );
|
||||
range.collapse( true );
|
||||
|
||||
// Opera inserts a BR if you delete the last piece of text
|
||||
|
@ -292,129 +359,42 @@ implement([ Element ], {
|
|||
// Steps to reproduce bug: Type "a-b-c" (where - is return)
|
||||
// then backspace twice. The cursor goes to the top instead
|
||||
// of after "b".
|
||||
if ( window.opera && ( last = block.lastChild ) &&
|
||||
last.nodeName === 'BR' ) {
|
||||
if ( isOpera && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
|
||||
block.removeChild( last );
|
||||
}
|
||||
},
|
||||
mergeContainers: function () {
|
||||
var prev = this.previousSibling,
|
||||
first = this.firstChild;
|
||||
if ( prev && prev.isLike( this ) && prev.isContainer() ) {
|
||||
prev.appendChild( this.detach().empty() );
|
||||
}
|
||||
|
||||
function mergeContainers ( node ) {
|
||||
var prev = node.previousSibling,
|
||||
first = node.firstChild;
|
||||
if ( prev && areAlike( prev, node ) && isContainer( prev ) ) {
|
||||
detach( node );
|
||||
prev.appendChild( empty( node ) );
|
||||
if ( first ) {
|
||||
first.mergeContainers();
|
||||
mergeContainers( first );
|
||||
}
|
||||
}
|
||||
},
|
||||
split: function ( childNodeToSplitBefore, stopNode ) {
|
||||
var node = this;
|
||||
|
||||
if ( typeof( childNodeToSplitBefore ) === 'number' ) {
|
||||
childNodeToSplitBefore =
|
||||
childNodeToSplitBefore < node.childNodes.length ?
|
||||
node.childNodes[ childNodeToSplitBefore ] : null;
|
||||
}
|
||||
|
||||
if ( node === stopNode ) {
|
||||
return childNodeToSplitBefore;
|
||||
}
|
||||
|
||||
// Clone node without children
|
||||
var parent = node.parentNode,
|
||||
clone = node.cloneNode( false ),
|
||||
next;
|
||||
|
||||
// Add right-hand siblings to the clone
|
||||
while ( childNodeToSplitBefore ) {
|
||||
next = childNodeToSplitBefore.nextSibling;
|
||||
clone.appendChild( childNodeToSplitBefore );
|
||||
childNodeToSplitBefore = next;
|
||||
}
|
||||
|
||||
// DO NOT NORMALISE. This may undo the fixCursor() call
|
||||
// of a node lower down the tree!
|
||||
|
||||
// We need something in the element in order for the cursor to appear.
|
||||
node.fixCursor();
|
||||
clone.fixCursor();
|
||||
|
||||
// Inject clone after original node
|
||||
if ( next = node.nextSibling ) {
|
||||
parent.insertBefore( clone, next );
|
||||
} else {
|
||||
parent.appendChild( clone );
|
||||
}
|
||||
|
||||
// Keep on splitting up the tree
|
||||
return parent.split( clone, stopNode );
|
||||
},
|
||||
fixCursor: function () {
|
||||
// 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.
|
||||
var el = this,
|
||||
doc = el.ownerDocument,
|
||||
fixer, child;
|
||||
|
||||
if ( el.nodeName === 'BODY' ) {
|
||||
if ( !( child = el.firstChild ) || child.nodeName === 'BR' ) {
|
||||
fixer = doc.createElement( 'DIV' );
|
||||
if ( child ) {
|
||||
el.replaceChild( fixer, child );
|
||||
}
|
||||
else {
|
||||
el.appendChild( fixer );
|
||||
}
|
||||
el = fixer;
|
||||
fixer = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ( el.isInline() ) {
|
||||
if ( !el.firstChild ) {
|
||||
if ( UA.cantFocusEmptyTextNodes ) {
|
||||
fixer = doc.createTextNode( '\u200B' );
|
||||
editor._setPlaceholderTextNode( fixer );
|
||||
} else {
|
||||
fixer = doc.createTextNode( '' );
|
||||
function createElement ( tag, props, children ) {
|
||||
var el = doc.createElement( tag ),
|
||||
attr, i, l;
|
||||
if ( props instanceof Array ) {
|
||||
children = props;
|
||||
props = null;
|
||||
}
|
||||
if ( props ) {
|
||||
for ( attr in props ) {
|
||||
el.setAttribute( attr, props[ attr ] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( UA.useTextFixer ) {
|
||||
while ( el.nodeType !== TEXT_NODE && !el.isLeaf() ) {
|
||||
child = el.firstChild;
|
||||
if ( !child ) {
|
||||
fixer = doc.createTextNode( '' );
|
||||
break;
|
||||
}
|
||||
el = child;
|
||||
}
|
||||
if ( el.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( el.data ) ) {
|
||||
el.data = '';
|
||||
}
|
||||
} else if ( el.isLeaf() ) {
|
||||
el.parentNode.insertBefore( doc.createTextNode( '' ), el );
|
||||
if ( children ) {
|
||||
for ( i = 0, l = children.length; i < l; i += 1 ) {
|
||||
el.appendChild( children[i] );
|
||||
}
|
||||
}
|
||||
else if ( !el.querySelector( 'BR' ) ) {
|
||||
fixer = doc.createElement( 'BR' );
|
||||
while ( ( child = el.lastElementChild ) && !child.isInline() ) {
|
||||
el = child;
|
||||
return el;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( fixer ) {
|
||||
el.appendChild( fixer );
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
// Fix IE8/9's buggy implementation of Text#splitText.
|
||||
// If the split is at the end of the node, it doesn't insert the newly split
|
||||
|
@ -423,8 +403,8 @@ implement([ Element ], {
|
|||
// the document and replaced by another, rather than just having its data
|
||||
// shortened.
|
||||
if ( function () {
|
||||
var div = document.createElement( 'div' ),
|
||||
text = document.createTextNode( '12' );
|
||||
var div = doc.createElement( 'div' ),
|
||||
text = doc.createTextNode( '12' );
|
||||
div.appendChild( text );
|
||||
text.splitText( 2 );
|
||||
return div.childNodes.length !== 2;
|
||||
|
@ -446,5 +426,3 @@ if ( function () {
|
|||
return afterSplit;
|
||||
};
|
||||
}
|
||||
|
||||
}( UA, DOMTreeWalker ) );
|
||||
|
|
171
source/Range.js
171
source/Range.js
|
@ -1,21 +1,30 @@
|
|||
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||
/*global
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
SHOW_TEXT,
|
||||
FILTER_ACCEPT,
|
||||
START_TO_START,
|
||||
START_TO_END,
|
||||
END_TO_END,
|
||||
END_TO_START,
|
||||
indexOf,
|
||||
|
||||
/*global Range, DOMTreeWalker */
|
||||
TreeWalker,
|
||||
|
||||
( function ( TreeWalker ) {
|
||||
isLeaf,
|
||||
isInline,
|
||||
isBlock,
|
||||
getPreviousBlock,
|
||||
getNextBlock,
|
||||
getLength,
|
||||
fixCursor,
|
||||
split,
|
||||
mergeWithBlock,
|
||||
mergeContainers,
|
||||
|
||||
"use strict";
|
||||
|
||||
var indexOf = Array.prototype.indexOf;
|
||||
|
||||
var ELEMENT_NODE = 1, // Node.ELEMENT_NODE
|
||||
TEXT_NODE = 3, // Node.TEXT_NODE
|
||||
SHOW_TEXT = 4, // NodeFilter.SHOW_TEXT,
|
||||
FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT,
|
||||
START_TO_START = 0, // Range.START_TO_START
|
||||
START_TO_END = 1, // Range.START_TO_END
|
||||
END_TO_END = 2, // Range.END_TO_END
|
||||
END_TO_START = 3; // Range.END_TO_START
|
||||
Range
|
||||
*/
|
||||
/*jshint strict:false */
|
||||
|
||||
var getNodeBefore = function ( node, offset ) {
|
||||
var children = node.childNodes;
|
||||
|
@ -42,9 +51,9 @@ var getNodeAfter = function ( node, offset ) {
|
|||
return node;
|
||||
};
|
||||
|
||||
var RangePrototypeExtensions = {
|
||||
var RangePrototype = Range.prototype;
|
||||
|
||||
forEachTextNode: function ( fn ) {
|
||||
RangePrototype.forEachTextNode = function ( fn ) {
|
||||
var range = this.cloneRange();
|
||||
range.moveBoundariesDownTree();
|
||||
|
||||
|
@ -60,9 +69,9 @@ var RangePrototypeExtensions = {
|
|||
while ( !fn( textnode, range ) &&
|
||||
textnode !== endContainer &&
|
||||
( textnode = walker.nextNode() ) ) {}
|
||||
},
|
||||
};
|
||||
|
||||
getTextContent: function () {
|
||||
RangePrototype.getTextContent = function () {
|
||||
var textContent = '';
|
||||
this.forEachTextNode( function ( textnode, range ) {
|
||||
var value = textnode.data;
|
||||
|
@ -77,11 +86,11 @@ var RangePrototypeExtensions = {
|
|||
}
|
||||
});
|
||||
return textContent;
|
||||
},
|
||||
};
|
||||
|
||||
// ---
|
||||
|
||||
_insertNode: function ( node ) {
|
||||
RangePrototype._insertNode = function ( node ) {
|
||||
// Insert at start.
|
||||
var startContainer = this.startContainer,
|
||||
startOffset = this.startOffset,
|
||||
|
@ -133,9 +142,9 @@ var RangePrototypeExtensions = {
|
|||
this.setEnd( endContainer, endOffset );
|
||||
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
_extractContents: function ( common ) {
|
||||
RangePrototype._extractContents = function ( common ) {
|
||||
var startContainer = this.startContainer,
|
||||
startOffset = this.startOffset,
|
||||
endContainer = this.endContainer,
|
||||
|
@ -149,8 +158,8 @@ var RangePrototypeExtensions = {
|
|||
common = common.parentNode;
|
||||
}
|
||||
|
||||
var endNode = endContainer.split( endOffset, common ),
|
||||
startNode = startContainer.split( startOffset, common ),
|
||||
var endNode = split( endContainer, endOffset, common ),
|
||||
startNode = split( startContainer, startOffset, common ),
|
||||
frag = common.ownerDocument.createDocumentFragment(),
|
||||
next;
|
||||
|
||||
|
@ -166,12 +175,12 @@ var RangePrototypeExtensions = {
|
|||
common.childNodes.length );
|
||||
this.collapse( true );
|
||||
|
||||
common.fixCursor();
|
||||
fixCursor( common );
|
||||
|
||||
return frag;
|
||||
},
|
||||
};
|
||||
|
||||
_deleteContents: function () {
|
||||
RangePrototype._deleteContents = function () {
|
||||
// Move boundaries up as much as possible to reduce need to split.
|
||||
this.moveBoundariesUpTree();
|
||||
|
||||
|
@ -182,19 +191,19 @@ var RangePrototypeExtensions = {
|
|||
var startBlock = this.getStartBlock(),
|
||||
endBlock = this.getEndBlock();
|
||||
if ( startBlock && endBlock && startBlock !== endBlock ) {
|
||||
startBlock.mergeWithBlock( endBlock, this );
|
||||
mergeWithBlock( startBlock, endBlock, this );
|
||||
}
|
||||
|
||||
// Ensure block has necessary children
|
||||
if ( startBlock ) {
|
||||
startBlock.fixCursor();
|
||||
fixCursor( startBlock );
|
||||
}
|
||||
|
||||
// Ensure body has a block-level element in it.
|
||||
var body = this.endContainer.ownerDocument.body,
|
||||
child = body.firstChild;
|
||||
if ( !child || child.nodeName === 'BR' ) {
|
||||
body.fixCursor();
|
||||
fixCursor( body );
|
||||
this.selectNodeContents( body.firstChild );
|
||||
}
|
||||
|
||||
|
@ -207,18 +216,18 @@ var RangePrototypeExtensions = {
|
|||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
// ---
|
||||
|
||||
insertTreeFragment: function ( frag ) {
|
||||
RangePrototype.insertTreeFragment = function ( frag ) {
|
||||
// Check if it's all inline content
|
||||
var isInline = true,
|
||||
var allInline = true,
|
||||
children = frag.childNodes,
|
||||
l = children.length;
|
||||
while ( l-- ) {
|
||||
if ( !children[l].isInline() ) {
|
||||
isInline = false;
|
||||
if ( !isInline( children[l] ) ) {
|
||||
allInline = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -232,14 +241,14 @@ var RangePrototypeExtensions = {
|
|||
this.moveBoundariesDownTree();
|
||||
|
||||
// If inline, just insert at the current position.
|
||||
if ( isInline ) {
|
||||
if ( allInline ) {
|
||||
this._insertNode( frag );
|
||||
this.collapse( false );
|
||||
}
|
||||
// Otherwise, split up to body, insert inline before and after split
|
||||
// and insert block in between split, then merge containers.
|
||||
else {
|
||||
var nodeAfterSplit = this.startContainer.split( this.startOffset,
|
||||
var nodeAfterSplit = split( this.startContainer, this.startOffset,
|
||||
this.startContainer.ownerDocument.body ),
|
||||
nodeBeforeSplit = nodeAfterSplit.previousSibling,
|
||||
startContainer = nodeBeforeSplit,
|
||||
|
@ -260,18 +269,18 @@ var RangePrototypeExtensions = {
|
|||
child.nodeName !== 'BR' ) {
|
||||
endContainer = child;
|
||||
}
|
||||
while ( ( child = frag.firstChild ) && child.isInline() ) {
|
||||
while ( ( child = frag.firstChild ) && isInline( child ) ) {
|
||||
startContainer.appendChild( child );
|
||||
}
|
||||
while ( ( child = frag.lastChild ) && child.isInline() ) {
|
||||
while ( ( child = frag.lastChild ) && isInline( child ) ) {
|
||||
endContainer.insertBefore( child, endContainer.firstChild );
|
||||
endOffset += 1;
|
||||
}
|
||||
|
||||
// Fix cursor then insert block(s)
|
||||
node = frag;
|
||||
while ( node = node.getNextBlock() ) {
|
||||
node.fixCursor();
|
||||
while ( node = getNextBlock( node ) ) {
|
||||
fixCursor( node );
|
||||
}
|
||||
parent.insertBefore( frag, nodeAfterSplit );
|
||||
|
||||
|
@ -281,11 +290,11 @@ var RangePrototypeExtensions = {
|
|||
if ( !nodeAfterSplit.textContent ) {
|
||||
parent.removeChild( nodeAfterSplit );
|
||||
} else {
|
||||
nodeAfterSplit.mergeContainers();
|
||||
mergeContainers( nodeAfterSplit );
|
||||
}
|
||||
if ( !nodeAfterSplit.parentNode ) {
|
||||
endContainer = node;
|
||||
endOffset = endContainer.getLength();
|
||||
endOffset = getLength( endContainer );
|
||||
}
|
||||
|
||||
if ( !nodeBeforeSplit.textContent) {
|
||||
|
@ -293,18 +302,18 @@ var RangePrototypeExtensions = {
|
|||
startOffset = 0;
|
||||
parent.removeChild( nodeBeforeSplit );
|
||||
} else {
|
||||
nodeBeforeSplit.mergeContainers();
|
||||
mergeContainers( nodeBeforeSplit );
|
||||
}
|
||||
|
||||
this.setStart( startContainer, startOffset );
|
||||
this.setEnd( endContainer, endOffset );
|
||||
this.moveBoundariesDownTree();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// ---
|
||||
|
||||
containsNode: function ( node, partial ) {
|
||||
RangePrototype.containsNode = function ( node, partial ) {
|
||||
var range = this,
|
||||
nodeRange = node.ownerDocument.createRange();
|
||||
|
||||
|
@ -328,9 +337,9 @@ var RangePrototypeExtensions = {
|
|||
END_TO_END, nodeRange ) > -1 );
|
||||
return ( nodeStartAfterStart && nodeEndBeforeEnd );
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
moveBoundariesDownTree: function () {
|
||||
RangePrototype.moveBoundariesDownTree = function () {
|
||||
var startContainer = this.startContainer,
|
||||
startOffset = this.startOffset,
|
||||
endContainer = this.endContainer,
|
||||
|
@ -339,7 +348,7 @@ var RangePrototypeExtensions = {
|
|||
|
||||
while ( startContainer.nodeType !== TEXT_NODE ) {
|
||||
child = startContainer.childNodes[ startOffset ];
|
||||
if ( !child || child.isLeaf() ) {
|
||||
if ( !child || isLeaf( child ) ) {
|
||||
break;
|
||||
}
|
||||
startContainer = child;
|
||||
|
@ -348,16 +357,16 @@ var RangePrototypeExtensions = {
|
|||
if ( endOffset ) {
|
||||
while ( endContainer.nodeType !== TEXT_NODE ) {
|
||||
child = endContainer.childNodes[ endOffset - 1 ];
|
||||
if ( !child || child.isLeaf() ) {
|
||||
if ( !child || isLeaf( child ) ) {
|
||||
break;
|
||||
}
|
||||
endContainer = child;
|
||||
endOffset = endContainer.getLength();
|
||||
endOffset = getLength( endContainer );
|
||||
}
|
||||
} else {
|
||||
while ( endContainer.nodeType !== TEXT_NODE ) {
|
||||
child = endContainer.firstChild;
|
||||
if ( !child || child.isLeaf() ) {
|
||||
if ( !child || isLeaf( child ) ) {
|
||||
break;
|
||||
}
|
||||
endContainer = child;
|
||||
|
@ -376,9 +385,9 @@ var RangePrototypeExtensions = {
|
|||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
moveBoundariesUpTree: function ( common ) {
|
||||
RangePrototype.moveBoundariesUpTree = function ( common ) {
|
||||
var startContainer = this.startContainer,
|
||||
startOffset = this.startOffset,
|
||||
endContainer = this.endContainer,
|
||||
|
@ -396,7 +405,7 @@ var RangePrototypeExtensions = {
|
|||
}
|
||||
|
||||
while ( endContainer !== common &&
|
||||
endOffset === endContainer.getLength() ) {
|
||||
endOffset === getLength( endContainer ) ) {
|
||||
parent = endContainer.parentNode;
|
||||
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
|
||||
endContainer = parent;
|
||||
|
@ -406,37 +415,37 @@ var RangePrototypeExtensions = {
|
|||
this.setEnd( endContainer, endOffset );
|
||||
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
// Returns the first block at least partially contained by the range,
|
||||
// or null if no block is contained by the range.
|
||||
getStartBlock: function () {
|
||||
RangePrototype.getStartBlock = function () {
|
||||
var container = this.startContainer,
|
||||
block;
|
||||
|
||||
// If inline, get the containing block.
|
||||
if ( container.isInline() ) {
|
||||
block = container.getPreviousBlock();
|
||||
} else if ( container.isBlock() ) {
|
||||
if ( isInline( container ) ) {
|
||||
block = getPreviousBlock( container );
|
||||
} else if ( isBlock( container ) ) {
|
||||
block = container;
|
||||
} else {
|
||||
block = getNodeBefore( container, this.startOffset );
|
||||
block = block.getNextBlock();
|
||||
block = getNextBlock( block );
|
||||
}
|
||||
// Check the block actually intersects the range
|
||||
return block && this.containsNode( block, true ) ? block : null;
|
||||
},
|
||||
};
|
||||
|
||||
// Returns the last block at least partially contained by the range,
|
||||
// or null if no block is contained by the range.
|
||||
getEndBlock: function () {
|
||||
RangePrototype.getEndBlock = function () {
|
||||
var container = this.endContainer,
|
||||
block, child;
|
||||
|
||||
// If inline, get the containing block.
|
||||
if ( container.isInline() ) {
|
||||
block = container.getPreviousBlock();
|
||||
} else if ( container.isBlock() ) {
|
||||
if ( isInline( container ) ) {
|
||||
block = getPreviousBlock( container );
|
||||
} else if ( isBlock( container ) ) {
|
||||
block = container;
|
||||
} else {
|
||||
block = getNodeAfter( container, this.endOffset );
|
||||
|
@ -446,19 +455,19 @@ var RangePrototypeExtensions = {
|
|||
block = child;
|
||||
}
|
||||
}
|
||||
block = block.getPreviousBlock();
|
||||
block = getPreviousBlock( block );
|
||||
|
||||
}
|
||||
// Check the block actually intersects the range
|
||||
return block && this.containsNode( block, true ) ? block : null;
|
||||
},
|
||||
};
|
||||
|
||||
startsAtBlockBoundary: function () {
|
||||
RangePrototype.startsAtBlockBoundary = function () {
|
||||
var startContainer = this.startContainer,
|
||||
startOffset = this.startOffset,
|
||||
parent, child;
|
||||
|
||||
while ( startContainer.isInline() ) {
|
||||
while ( isInline( startContainer ) ) {
|
||||
if ( startOffset ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -473,15 +482,15 @@ var RangePrototypeExtensions = {
|
|||
startOffset -= 1;
|
||||
}
|
||||
return !startOffset;
|
||||
},
|
||||
};
|
||||
|
||||
endsAtBlockBoundary: function () {
|
||||
RangePrototype.endsAtBlockBoundary = function () {
|
||||
var endContainer = this.endContainer,
|
||||
endOffset = this.endOffset,
|
||||
length = endContainer.getLength(),
|
||||
length = getLength( endContainer ),
|
||||
parent, child;
|
||||
|
||||
while ( endContainer.isInline() ) {
|
||||
while ( isInline( endContainer ) ) {
|
||||
if ( endOffset !== length ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -497,9 +506,9 @@ var RangePrototypeExtensions = {
|
|||
endOffset += 1;
|
||||
}
|
||||
return endOffset === length;
|
||||
},
|
||||
};
|
||||
|
||||
expandToBlockBoundaries: function () {
|
||||
RangePrototype.expandToBlockBoundaries = function () {
|
||||
var start = this.getStartBlock(),
|
||||
end = this.getEndBlock(),
|
||||
parent;
|
||||
|
@ -512,12 +521,4 @@ var RangePrototypeExtensions = {
|
|||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
var prop;
|
||||
for ( prop in RangePrototypeExtensions ) {
|
||||
Range.prototype[ prop ] = RangePrototypeExtensions[ prop ];
|
||||
}
|
||||
|
||||
}( DOMTreeWalker ) );
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||
|
||||
/*global document, window */
|
||||
/*global FILTER_ACCEPT */
|
||||
/*jshint strict:false */
|
||||
|
||||
/*
|
||||
Native TreeWalker is buggy in IE and Opera:
|
||||
|
@ -13,10 +12,6 @@
|
|||
(subset) of the spec in all browsers.
|
||||
*/
|
||||
|
||||
var DOMTreeWalker = (function () {
|
||||
|
||||
"use strict";
|
||||
|
||||
var typeToBitArray = {
|
||||
// ELEMENT_NODE
|
||||
1: 1,
|
||||
|
@ -32,13 +27,11 @@ var DOMTreeWalker = (function () {
|
|||
11: 1024
|
||||
};
|
||||
|
||||
var FILTER_ACCEPT = 1;
|
||||
|
||||
var TreeWalker = function ( root, nodeType, filter ) {
|
||||
function TreeWalker ( root, nodeType, filter ) {
|
||||
this.root = this.currentNode = root;
|
||||
this.nodeType = nodeType;
|
||||
this.filter = filter;
|
||||
};
|
||||
}
|
||||
|
||||
TreeWalker.prototype.nextNode = function () {
|
||||
var current = this.currentNode,
|
||||
|
@ -96,7 +89,3 @@ var DOMTreeWalker = (function () {
|
|||
current = node;
|
||||
}
|
||||
};
|
||||
|
||||
return TreeWalker;
|
||||
|
||||
})();
|
||||
|
|
29
source/UA.js
29
source/UA.js
|
@ -1,29 +0,0 @@
|
|||
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||
|
||||
/*global navigator, window */
|
||||
|
||||
var UA = (function ( win ) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var ua = navigator.userAgent;
|
||||
var isOpera = !!win.opera;
|
||||
var isIE = /Trident\//.test( ua );
|
||||
var isWebKit = /WebKit\//.test( ua );
|
||||
|
||||
return {
|
||||
// Browser sniffing. Unfortunately necessary.
|
||||
isOpera: isOpera,
|
||||
isIE8: ( win.ie === 8 ),
|
||||
isIE: isIE,
|
||||
isGecko: /Gecko\//.test( ua ),
|
||||
isWebKit: isWebKit,
|
||||
isIOS: /iP(?:ad|hone|od)/.test( ua ),
|
||||
|
||||
// Browser quirks
|
||||
useTextFixer: isIE || isOpera,
|
||||
cantFocusEmptyTextNodes: isIE || isWebKit,
|
||||
losesSelectionOnBlur: isIE
|
||||
};
|
||||
|
||||
})( window );
|
5
source/intro.js
Normal file
5
source/intro.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* Copyright © 2011-2013 by Neil Jenkins. MIT Licensed. */
|
||||
|
||||
( function ( doc ) {
|
||||
|
||||
"use strict";
|
1
source/outro.js
Normal file
1
source/outro.js
Normal file
|
@ -0,0 +1 @@
|
|||
}( document ) );
|
Loading…
Reference in a new issue