0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2025-01-05 06:10:07 -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:
Neil Jenkins 2013-04-08 13:27:06 +10:00
parent 10abc3faf8
commit af1720282c
11 changed files with 6130 additions and 2951 deletions

View file

@ -11,8 +11,11 @@ build/ie8.js: source/ie8types.js source/ie8dom.js source/ie8range.js
mkdir -p $(@D) mkdir -p $(@D)
uglifyjs $^ -c -m -o $@ 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) mkdir -p $(@D)
cat $^ >$@
build/squire.js: build/squire-raw.js
uglifyjs $^ -c -m -o $@ uglifyjs $^ -c -m -o $@
build/document.html: source/document.html build/document.html: source/document.html

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
View 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;

File diff suppressed because it is too large Load diff

View file

@ -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, TreeWalker,
editor, UA, DOMTreeWalker */
( function ( UA, TreeWalker ) { Text,
"use strict"; setPlaceholderTextNode
*/
/*jshint strict:false */
var implement = function ( constructors, props ) { 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 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 leafNodeNames = { var leafNodeNames = {
BR: 1, BR: 1,
@ -39,208 +25,290 @@ var leafNodeNames = {
INPUT: 1 INPUT: 1
}; };
var swap = function ( node, node2 ) { function every ( nodeList, fn ) {
var parent = node2.parentNode; var l = nodeList.length;
if ( parent ) { while ( l-- ) {
parent.replaceChild( node, node2 ); 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; return true;
}, }
replaceWith: function ( node ) {
swap( node, this ); // ---
return this;
}, function hasTagAttributes ( node, tag, attributes ) {
replaces: function ( node ) { if ( node.nodeName !== tag ) {
swap( this, node ); return false;
return this; }
}, for ( var attr in attributes ) {
nearest: function ( tag, attributes ) { if ( node.getAttribute( attr ) !== attributes[ attr ] ) {
var parent = this.parentNode; return false;
return parent ? parent.nearest( tag, attributes ) : null; }
}, }
getPreviousBlock: function () { return true;
var doc = this.ownerDocument, }
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( walker = new TreeWalker(
doc.body, SHOW_ELEMENT, isBlock, false ); doc.body, SHOW_ELEMENT, acceptIfBlock, false );
walker.currentNode = this; walker.currentNode = node;
return walker.previousNode(); return walker;
}, }
getNextBlock: function () {
var doc = this.ownerDocument, function getPreviousBlock ( node ) {
walker = new TreeWalker( return getBlockWalker( node ).previousNode();
doc.body, SHOW_ELEMENT, isBlock, false ); }
walker.currentNode = this; function getNextBlock ( node ) {
return walker.nextNode(); return getBlockWalker( node ).nextNode();
}, }
split: function ( node, stopNode ) { function getNearest ( node, tag, attributes ) {
do {
if ( hasTagAttributes( node, tag, attributes ) ) {
return node; 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 ], { function getPath ( node ) {
isLeaf: function () { var parent = node.parentNode,
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,
path, id, className, classNames; path, id, className, classNames;
if ( !parent ) { if ( !parent || node.nodeType !== ELEMENT_NODE ) {
return ''; path = parent ? getPath( parent ) : '';
} } else {
path = parent.getPath(); path = getPath( parent );
path += ( path ? '>' : '' ) + this.nodeName; path += ( path ? '>' : '' ) + node.nodeName;
if ( id = this.id ) { if ( id = node.id ) {
path += '#' + id; path += '#' + id;
} }
if ( className = this.className.trim() ) { if ( className = node.className.trim() ) {
classNames = className.split( /\s\s*/ ); classNames = className.split( /\s\s*/ );
classNames.sort(); classNames.sort();
path += '.'; path += '.';
path += classNames.join( '.' ); path += classNames.join( '.' );
} }
}
return path; return path;
}, }
wraps: function ( node ) {
swap( this, node ).appendChild( node ); function getLength ( node ) {
return this; var nodeType = node.nodeType;
}, return nodeType === ELEMENT_NODE ?
empty: function () { node.childNodes.length : node.length || 0;
var frag = this.ownerDocument.createDocumentFragment(), }
l = this.childNodes.length;
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-- ) { while ( l-- ) {
frag.appendChild( this.firstChild ); frag.appendChild( node.firstChild );
} }
return frag; return frag;
}, }
is: function ( tag, attributes ) {
if ( this.nodeName !== tag ) { return false; } function fixCursor ( node ) {
var attr; // In Webkit and Gecko, block level elements are collapsed and
for ( attr in attributes ) { // unfocussable if they have no content. To remedy this, a <BR> must be
if ( this.getAttribute( attr ) !== attributes[ attr ] ) { // inserted. In Opera and IE, we just need a textnode in order for the
return false; // 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;
}, if ( isInline( node ) ) {
nearest: function ( tag, attributes ) { if ( !node.firstChild ) {
var el = this; if ( cantFocusEmptyTextNodes ) {
do { fixer = doc.createTextNode( '\u200B' );
if ( el.is( tag, attributes ) ) { setPlaceholderTextNode( fixer );
return el; } else {
fixer = doc.createTextNode( '' );
} }
} while ( ( el = el.parentNode ) && }
( el.nodeType === ELEMENT_NODE ) ); } else {
return null; if ( useTextFixer ) {
}, while ( node.nodeType !== TEXT_NODE && !isLeaf( node ) ) {
isLike: function ( node ) { child = node.firstChild;
return ( if ( !child ) {
node.nodeType === ELEMENT_NODE && fixer = doc.createTextNode( '' );
node.nodeName === this.nodeName && break;
node.className === this.className && }
node.style.cssText === this.style.cssText node = child;
); }
}, if ( node.nodeType === TEXT_NODE ) {
mergeInlines: function ( range ) { // Opera will collapse the block element if it contains
var children = this.childNodes, // 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, l = children.length,
frags = [], frags = [],
child, prev, len; child, prev, len;
while ( l-- ) { while ( l-- ) {
child = children[l]; child = children[l];
prev = l && children[ l - 1 ]; prev = l && children[ l - 1 ];
if ( l && child.isInline() && child.isLike( prev ) && if ( l && isInline( child ) && areAlike( child, prev ) &&
!leafNodeNames[ child.nodeName ] ) { !leafNodeNames[ child.nodeName ] ) {
if ( range.startContainer === child ) { if ( range.startContainer === child ) {
range.startContainer = prev; range.startContainer = prev;
range.startOffset += prev.getLength(); range.startOffset += getLength( prev );
} }
if ( range.endContainer === child ) { if ( range.endContainer === child ) {
range.endContainer = prev; range.endContainer = prev;
range.endOffset += prev.getLength(); range.endOffset += getLength( prev );
} }
if ( range.startContainer === this ) { if ( range.startContainer === node ) {
if ( range.startOffset > l ) { if ( range.startOffset > l ) {
range.startOffset -= 1; range.startOffset -= 1;
} }
else if ( range.startOffset === l ) { else if ( range.startOffset === l ) {
range.startContainer = prev; range.startContainer = prev;
range.startOffset = prev.getLength(); range.startOffset = getLength( prev );
} }
} }
if ( range.endContainer === this ) { if ( range.endContainer === node ) {
if ( range.endOffset > l ) { if ( range.endOffset > l ) {
range.endOffset -= 1; range.endOffset -= 1;
} }
else if ( range.endOffset === l ) { else if ( range.endOffset === l ) {
range.endContainer = prev; range.endContainer = prev;
range.endOffset = prev.getLength(); range.endOffset = getLength( prev );
} }
} }
child.detach(); detach( child );
if ( child.nodeType === TEXT_NODE ) { if ( child.nodeType === TEXT_NODE ) {
prev.appendData( child.data.replace( /\u200B/g, '' ) ); prev.appendData( child.data.replace( /\u200B/g, '' ) );
} }
else { else {
frags.push( child.empty() ); frags.push( empty( child ) );
} }
} }
else if ( child.nodeType === ELEMENT_NODE ) { else if ( child.nodeType === ELEMENT_NODE ) {
@ -248,18 +316,18 @@ implement([ Element ], {
while ( len-- ) { while ( len-- ) {
child.appendChild( frags.pop() ); child.appendChild( frags.pop() );
} }
child.mergeInlines( range ); mergeInlines( child, range );
} }
} }
}, }
mergeWithBlock: function ( next, range ) {
var block = this, function mergeWithBlock ( block, next, range ) {
container = next, var container = next,
last, offset, _range; last, offset, _range;
while ( container.parentNode.childNodes.length === 1 ) { while ( container.parentNode.childNodes.length === 1 ) {
container = container.parentNode; container = container.parentNode;
} }
container.detach(); detach( container );
offset = block.childNodes.length; offset = block.childNodes.length;
@ -277,11 +345,10 @@ implement([ Element ], {
endOffset: offset endOffset: offset
}; };
block.appendChild( next.empty() ); block.appendChild( empty( next ) );
block.mergeInlines( _range ); mergeInlines( block, _range );
range.setStart( range.setStart( _range.startContainer, _range.startOffset );
_range.startContainer, _range.startOffset );
range.collapse( true ); range.collapse( true );
// Opera inserts a BR if you delete the last piece of text // 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) // Steps to reproduce bug: Type "a-b-c" (where - is return)
// then backspace twice. The cursor goes to the top instead // then backspace twice. The cursor goes to the top instead
// of after "b". // of after "b".
if ( window.opera && ( last = block.lastChild ) && if ( isOpera && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
last.nodeName === 'BR' ) {
block.removeChild( last ); block.removeChild( last );
} }
}, }
mergeContainers: function () {
var prev = this.previousSibling, function mergeContainers ( node ) {
first = this.firstChild; var prev = node.previousSibling,
if ( prev && prev.isLike( this ) && prev.isContainer() ) { first = node.firstChild;
prev.appendChild( this.detach().empty() ); if ( prev && areAlike( prev, node ) && isContainer( prev ) ) {
detach( node );
prev.appendChild( empty( node ) );
if ( first ) { if ( first ) {
first.mergeContainers(); mergeContainers( first );
} }
} }
}, }
split: function ( childNodeToSplitBefore, stopNode ) {
var node = this;
if ( typeof( childNodeToSplitBefore ) === 'number' ) { function createElement ( tag, props, children ) {
childNodeToSplitBefore = var el = doc.createElement( tag ),
childNodeToSplitBefore < node.childNodes.length ? attr, i, l;
node.childNodes[ childNodeToSplitBefore ] : null; if ( props instanceof Array ) {
children = props;
props = null;
} }
if ( props ) {
if ( node === stopNode ) { for ( attr in props ) {
return childNodeToSplitBefore; el.setAttribute( attr, props[ attr ] );
}
// 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 ( children ) {
if ( el.isInline() ) { for ( i = 0, l = children.length; i < l; i += 1 ) {
if ( !el.firstChild ) { el.appendChild( children[i] );
if ( UA.cantFocusEmptyTextNodes ) {
fixer = doc.createTextNode( '\u200B' );
editor._setPlaceholderTextNode( fixer );
} else {
fixer = doc.createTextNode( '' );
} }
} }
} else { return el;
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 );
}
}
else if ( !el.querySelector( 'BR' ) ) {
fixer = doc.createElement( 'BR' );
while ( ( child = el.lastElementChild ) && !child.isInline() ) {
el = child;
}
}
}
if ( fixer ) {
el.appendChild( fixer );
}
return this;
}
});
// Fix IE8/9's buggy implementation of Text#splitText. // 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 // 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 // the document and replaced by another, rather than just having its data
// shortened. // shortened.
if ( function () { if ( function () {
var div = document.createElement( 'div' ), var div = doc.createElement( 'div' ),
text = document.createTextNode( '12' ); text = doc.createTextNode( '12' );
div.appendChild( text ); div.appendChild( text );
text.splitText( 2 ); text.splitText( 2 );
return div.childNodes.length !== 2; return div.childNodes.length !== 2;
@ -446,5 +426,3 @@ if ( function () {
return afterSplit; return afterSplit;
}; };
} }
}( UA, DOMTreeWalker ) );

View file

@ -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"; Range
*/
var indexOf = Array.prototype.indexOf; /*jshint strict:false */
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
var getNodeBefore = function ( node, offset ) { var getNodeBefore = function ( node, offset ) {
var children = node.childNodes; var children = node.childNodes;
@ -42,9 +51,9 @@ var getNodeAfter = function ( node, offset ) {
return node; return node;
}; };
var RangePrototypeExtensions = { var RangePrototype = Range.prototype;
forEachTextNode: function ( fn ) { RangePrototype.forEachTextNode = function ( fn ) {
var range = this.cloneRange(); var range = this.cloneRange();
range.moveBoundariesDownTree(); range.moveBoundariesDownTree();
@ -60,9 +69,9 @@ var RangePrototypeExtensions = {
while ( !fn( textnode, range ) && while ( !fn( textnode, range ) &&
textnode !== endContainer && textnode !== endContainer &&
( textnode = walker.nextNode() ) ) {} ( textnode = walker.nextNode() ) ) {}
}, };
getTextContent: function () { RangePrototype.getTextContent = function () {
var textContent = ''; var textContent = '';
this.forEachTextNode( function ( textnode, range ) { this.forEachTextNode( function ( textnode, range ) {
var value = textnode.data; var value = textnode.data;
@ -77,11 +86,11 @@ var RangePrototypeExtensions = {
} }
}); });
return textContent; return textContent;
}, };
// --- // ---
_insertNode: function ( node ) { RangePrototype._insertNode = function ( node ) {
// Insert at start. // Insert at start.
var startContainer = this.startContainer, var startContainer = this.startContainer,
startOffset = this.startOffset, startOffset = this.startOffset,
@ -133,9 +142,9 @@ var RangePrototypeExtensions = {
this.setEnd( endContainer, endOffset ); this.setEnd( endContainer, endOffset );
return this; return this;
}, };
_extractContents: function ( common ) { RangePrototype._extractContents = function ( common ) {
var startContainer = this.startContainer, var startContainer = this.startContainer,
startOffset = this.startOffset, startOffset = this.startOffset,
endContainer = this.endContainer, endContainer = this.endContainer,
@ -149,8 +158,8 @@ var RangePrototypeExtensions = {
common = common.parentNode; common = common.parentNode;
} }
var endNode = endContainer.split( endOffset, common ), var endNode = split( endContainer, endOffset, common ),
startNode = startContainer.split( startOffset, common ), startNode = split( startContainer, startOffset, common ),
frag = common.ownerDocument.createDocumentFragment(), frag = common.ownerDocument.createDocumentFragment(),
next; next;
@ -166,12 +175,12 @@ var RangePrototypeExtensions = {
common.childNodes.length ); common.childNodes.length );
this.collapse( true ); this.collapse( true );
common.fixCursor(); fixCursor( common );
return frag; return frag;
}, };
_deleteContents: function () { RangePrototype._deleteContents = function () {
// Move boundaries up as much as possible to reduce need to split. // Move boundaries up as much as possible to reduce need to split.
this.moveBoundariesUpTree(); this.moveBoundariesUpTree();
@ -182,19 +191,19 @@ var RangePrototypeExtensions = {
var startBlock = this.getStartBlock(), var startBlock = this.getStartBlock(),
endBlock = this.getEndBlock(); endBlock = this.getEndBlock();
if ( startBlock && endBlock && startBlock !== endBlock ) { if ( startBlock && endBlock && startBlock !== endBlock ) {
startBlock.mergeWithBlock( endBlock, this ); mergeWithBlock( startBlock, endBlock, this );
} }
// Ensure block has necessary children // Ensure block has necessary children
if ( startBlock ) { if ( startBlock ) {
startBlock.fixCursor(); fixCursor( startBlock );
} }
// Ensure body has a block-level element in it. // Ensure body has a block-level element in it.
var body = this.endContainer.ownerDocument.body, var body = this.endContainer.ownerDocument.body,
child = body.firstChild; child = body.firstChild;
if ( !child || child.nodeName === 'BR' ) { if ( !child || child.nodeName === 'BR' ) {
body.fixCursor(); fixCursor( body );
this.selectNodeContents( body.firstChild ); this.selectNodeContents( body.firstChild );
} }
@ -207,18 +216,18 @@ var RangePrototypeExtensions = {
} }
return this; return this;
}, };
// --- // ---
insertTreeFragment: function ( frag ) { RangePrototype.insertTreeFragment = function ( frag ) {
// Check if it's all inline content // Check if it's all inline content
var isInline = true, var allInline = true,
children = frag.childNodes, children = frag.childNodes,
l = children.length; l = children.length;
while ( l-- ) { while ( l-- ) {
if ( !children[l].isInline() ) { if ( !isInline( children[l] ) ) {
isInline = false; allInline = false;
break; break;
} }
} }
@ -232,14 +241,14 @@ var RangePrototypeExtensions = {
this.moveBoundariesDownTree(); this.moveBoundariesDownTree();
// If inline, just insert at the current position. // If inline, just insert at the current position.
if ( isInline ) { if ( allInline ) {
this._insertNode( frag ); this._insertNode( frag );
this.collapse( false ); this.collapse( false );
} }
// Otherwise, split up to body, insert inline before and after split // Otherwise, split up to body, insert inline before and after split
// and insert block in between split, then merge containers. // and insert block in between split, then merge containers.
else { else {
var nodeAfterSplit = this.startContainer.split( this.startOffset, var nodeAfterSplit = split( this.startContainer, this.startOffset,
this.startContainer.ownerDocument.body ), this.startContainer.ownerDocument.body ),
nodeBeforeSplit = nodeAfterSplit.previousSibling, nodeBeforeSplit = nodeAfterSplit.previousSibling,
startContainer = nodeBeforeSplit, startContainer = nodeBeforeSplit,
@ -260,18 +269,18 @@ var RangePrototypeExtensions = {
child.nodeName !== 'BR' ) { child.nodeName !== 'BR' ) {
endContainer = child; endContainer = child;
} }
while ( ( child = frag.firstChild ) && child.isInline() ) { while ( ( child = frag.firstChild ) && isInline( child ) ) {
startContainer.appendChild( child ); startContainer.appendChild( child );
} }
while ( ( child = frag.lastChild ) && child.isInline() ) { while ( ( child = frag.lastChild ) && isInline( child ) ) {
endContainer.insertBefore( child, endContainer.firstChild ); endContainer.insertBefore( child, endContainer.firstChild );
endOffset += 1; endOffset += 1;
} }
// Fix cursor then insert block(s) // Fix cursor then insert block(s)
node = frag; node = frag;
while ( node = node.getNextBlock() ) { while ( node = getNextBlock( node ) ) {
node.fixCursor(); fixCursor( node );
} }
parent.insertBefore( frag, nodeAfterSplit ); parent.insertBefore( frag, nodeAfterSplit );
@ -281,11 +290,11 @@ var RangePrototypeExtensions = {
if ( !nodeAfterSplit.textContent ) { if ( !nodeAfterSplit.textContent ) {
parent.removeChild( nodeAfterSplit ); parent.removeChild( nodeAfterSplit );
} else { } else {
nodeAfterSplit.mergeContainers(); mergeContainers( nodeAfterSplit );
} }
if ( !nodeAfterSplit.parentNode ) { if ( !nodeAfterSplit.parentNode ) {
endContainer = node; endContainer = node;
endOffset = endContainer.getLength(); endOffset = getLength( endContainer );
} }
if ( !nodeBeforeSplit.textContent) { if ( !nodeBeforeSplit.textContent) {
@ -293,18 +302,18 @@ var RangePrototypeExtensions = {
startOffset = 0; startOffset = 0;
parent.removeChild( nodeBeforeSplit ); parent.removeChild( nodeBeforeSplit );
} else { } else {
nodeBeforeSplit.mergeContainers(); mergeContainers( nodeBeforeSplit );
} }
this.setStart( startContainer, startOffset ); this.setStart( startContainer, startOffset );
this.setEnd( endContainer, endOffset ); this.setEnd( endContainer, endOffset );
this.moveBoundariesDownTree(); this.moveBoundariesDownTree();
} }
}, };
// --- // ---
containsNode: function ( node, partial ) { RangePrototype.containsNode = function ( node, partial ) {
var range = this, var range = this,
nodeRange = node.ownerDocument.createRange(); nodeRange = node.ownerDocument.createRange();
@ -328,9 +337,9 @@ var RangePrototypeExtensions = {
END_TO_END, nodeRange ) > -1 ); END_TO_END, nodeRange ) > -1 );
return ( nodeStartAfterStart && nodeEndBeforeEnd ); return ( nodeStartAfterStart && nodeEndBeforeEnd );
} }
}, };
moveBoundariesDownTree: function () { RangePrototype.moveBoundariesDownTree = function () {
var startContainer = this.startContainer, var startContainer = this.startContainer,
startOffset = this.startOffset, startOffset = this.startOffset,
endContainer = this.endContainer, endContainer = this.endContainer,
@ -339,7 +348,7 @@ var RangePrototypeExtensions = {
while ( startContainer.nodeType !== TEXT_NODE ) { while ( startContainer.nodeType !== TEXT_NODE ) {
child = startContainer.childNodes[ startOffset ]; child = startContainer.childNodes[ startOffset ];
if ( !child || child.isLeaf() ) { if ( !child || isLeaf( child ) ) {
break; break;
} }
startContainer = child; startContainer = child;
@ -348,16 +357,16 @@ var RangePrototypeExtensions = {
if ( endOffset ) { if ( endOffset ) {
while ( endContainer.nodeType !== TEXT_NODE ) { while ( endContainer.nodeType !== TEXT_NODE ) {
child = endContainer.childNodes[ endOffset - 1 ]; child = endContainer.childNodes[ endOffset - 1 ];
if ( !child || child.isLeaf() ) { if ( !child || isLeaf( child ) ) {
break; break;
} }
endContainer = child; endContainer = child;
endOffset = endContainer.getLength(); endOffset = getLength( endContainer );
} }
} else { } else {
while ( endContainer.nodeType !== TEXT_NODE ) { while ( endContainer.nodeType !== TEXT_NODE ) {
child = endContainer.firstChild; child = endContainer.firstChild;
if ( !child || child.isLeaf() ) { if ( !child || isLeaf( child ) ) {
break; break;
} }
endContainer = child; endContainer = child;
@ -376,9 +385,9 @@ var RangePrototypeExtensions = {
} }
return this; return this;
}, };
moveBoundariesUpTree: function ( common ) { RangePrototype.moveBoundariesUpTree = function ( common ) {
var startContainer = this.startContainer, var startContainer = this.startContainer,
startOffset = this.startOffset, startOffset = this.startOffset,
endContainer = this.endContainer, endContainer = this.endContainer,
@ -396,7 +405,7 @@ var RangePrototypeExtensions = {
} }
while ( endContainer !== common && while ( endContainer !== common &&
endOffset === endContainer.getLength() ) { endOffset === getLength( endContainer ) ) {
parent = endContainer.parentNode; parent = endContainer.parentNode;
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1; endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
endContainer = parent; endContainer = parent;
@ -406,37 +415,37 @@ var RangePrototypeExtensions = {
this.setEnd( endContainer, endOffset ); this.setEnd( endContainer, endOffset );
return this; return this;
}, };
// Returns the first block at least partially contained by the range, // Returns the first block at least partially contained by the range,
// or null if no block is contained by the range. // or null if no block is contained by the range.
getStartBlock: function () { RangePrototype.getStartBlock = function () {
var container = this.startContainer, var container = this.startContainer,
block; block;
// If inline, get the containing block. // If inline, get the containing block.
if ( container.isInline() ) { if ( isInline( container ) ) {
block = container.getPreviousBlock(); block = getPreviousBlock( container );
} else if ( container.isBlock() ) { } else if ( isBlock( container ) ) {
block = container; block = container;
} else { } else {
block = getNodeBefore( container, this.startOffset ); block = getNodeBefore( container, this.startOffset );
block = block.getNextBlock(); block = getNextBlock( block );
} }
// Check the block actually intersects the range // Check the block actually intersects the range
return block && this.containsNode( block, true ) ? block : null; return block && this.containsNode( block, true ) ? block : null;
}, };
// Returns the last block at least partially contained by the range, // Returns the last block at least partially contained by the range,
// or null if no block is contained by the range. // or null if no block is contained by the range.
getEndBlock: function () { RangePrototype.getEndBlock = function () {
var container = this.endContainer, var container = this.endContainer,
block, child; block, child;
// If inline, get the containing block. // If inline, get the containing block.
if ( container.isInline() ) { if ( isInline( container ) ) {
block = container.getPreviousBlock(); block = getPreviousBlock( container );
} else if ( container.isBlock() ) { } else if ( isBlock( container ) ) {
block = container; block = container;
} else { } else {
block = getNodeAfter( container, this.endOffset ); block = getNodeAfter( container, this.endOffset );
@ -446,19 +455,19 @@ var RangePrototypeExtensions = {
block = child; block = child;
} }
} }
block = block.getPreviousBlock(); block = getPreviousBlock( block );
} }
// Check the block actually intersects the range // Check the block actually intersects the range
return block && this.containsNode( block, true ) ? block : null; return block && this.containsNode( block, true ) ? block : null;
}, };
startsAtBlockBoundary: function () { RangePrototype.startsAtBlockBoundary = function () {
var startContainer = this.startContainer, var startContainer = this.startContainer,
startOffset = this.startOffset, startOffset = this.startOffset,
parent, child; parent, child;
while ( startContainer.isInline() ) { while ( isInline( startContainer ) ) {
if ( startOffset ) { if ( startOffset ) {
return false; return false;
} }
@ -473,15 +482,15 @@ var RangePrototypeExtensions = {
startOffset -= 1; startOffset -= 1;
} }
return !startOffset; return !startOffset;
}, };
endsAtBlockBoundary: function () { RangePrototype.endsAtBlockBoundary = function () {
var endContainer = this.endContainer, var endContainer = this.endContainer,
endOffset = this.endOffset, endOffset = this.endOffset,
length = endContainer.getLength(), length = getLength( endContainer ),
parent, child; parent, child;
while ( endContainer.isInline() ) { while ( isInline( endContainer ) ) {
if ( endOffset !== length ) { if ( endOffset !== length ) {
return false; return false;
} }
@ -497,9 +506,9 @@ var RangePrototypeExtensions = {
endOffset += 1; endOffset += 1;
} }
return endOffset === length; return endOffset === length;
}, };
expandToBlockBoundaries: function () { RangePrototype.expandToBlockBoundaries = function () {
var start = this.getStartBlock(), var start = this.getStartBlock(),
end = this.getEndBlock(), end = this.getEndBlock(),
parent; parent;
@ -512,12 +521,4 @@ var RangePrototypeExtensions = {
} }
return this; return this;
}
}; };
var prop;
for ( prop in RangePrototypeExtensions ) {
Range.prototype[ prop ] = RangePrototypeExtensions[ prop ];
}
}( DOMTreeWalker ) );

View file

@ -1,6 +1,5 @@
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */ /*global FILTER_ACCEPT */
/*jshint strict:false */
/*global document, window */
/* /*
Native TreeWalker is buggy in IE and Opera: Native TreeWalker is buggy in IE and Opera:
@ -13,11 +12,7 @@
(subset) of the spec in all browsers. (subset) of the spec in all browsers.
*/ */
var DOMTreeWalker = (function () { var typeToBitArray = {
"use strict";
var typeToBitArray = {
// ELEMENT_NODE // ELEMENT_NODE
1: 1, 1: 1,
// ATTRIBUTE_NODE // ATTRIBUTE_NODE
@ -30,17 +25,15 @@ var DOMTreeWalker = (function () {
9: 256, 9: 256,
// DOCUMENT_FRAGMENT_NODE // DOCUMENT_FRAGMENT_NODE
11: 1024 11: 1024
}; };
var FILTER_ACCEPT = 1; function TreeWalker ( root, nodeType, filter ) {
var TreeWalker = function ( root, nodeType, filter ) {
this.root = this.currentNode = root; this.root = this.currentNode = root;
this.nodeType = nodeType; this.nodeType = nodeType;
this.filter = filter; this.filter = filter;
}; }
TreeWalker.prototype.nextNode = function () { TreeWalker.prototype.nextNode = function () {
var current = this.currentNode, var current = this.currentNode,
root = this.root, root = this.root,
nodeType = this.nodeType, nodeType = this.nodeType,
@ -65,9 +58,9 @@ var DOMTreeWalker = (function () {
} }
current = node; current = node;
} }
}; };
TreeWalker.prototype.previousNode = function () { TreeWalker.prototype.previousNode = function () {
var current = this.currentNode, var current = this.currentNode,
root = this.root, root = this.root,
nodeType = this.nodeType, nodeType = this.nodeType,
@ -95,8 +88,4 @@ var DOMTreeWalker = (function () {
} }
current = node; current = node;
} }
}; };
return TreeWalker;
})();

View file

@ -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
View file

@ -0,0 +1,5 @@
/* Copyright © 2011-2013 by Neil Jenkins. MIT Licensed. */
( function ( doc ) {
"use strict";

1
source/outro.js Normal file
View file

@ -0,0 +1 @@
}( document ) );