mirror of
https://github.com/fastmail/Squire.git
synced 2025-01-03 05:00:13 -05:00
Make Squire work without an iframe(!)
This commit is contained in:
parent
e133f26db1
commit
6a348e084b
10 changed files with 643 additions and 665 deletions
124
Demo.html
124
Demo.html
|
@ -23,10 +23,41 @@
|
||||||
p {
|
p {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
iframe {
|
#editor {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 200px;
|
||||||
border: 1px solid #888;
|
border: 1px solid #888;
|
||||||
width: 100%;
|
padding: 1em;
|
||||||
height: 500px;
|
background: transparent;
|
||||||
|
color: #2b2b2b;
|
||||||
|
font: 13px/1.35 Helvetica, arial, sans-serif;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 123.1%;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 108%;
|
||||||
|
}
|
||||||
|
h1,h2,h3,p {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
h4,h5,h6 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
margin: 0 1em;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
border-left: 2px solid blue;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
@ -69,84 +100,19 @@
|
||||||
<span id="redo">Redo</span>
|
<span id="redo">Redo</span>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<script type="text/template" id="editorStyles">
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 100%;
|
|
||||||
padding: 1em;
|
|
||||||
background: transparent;
|
|
||||||
color: #2b2b2b;
|
|
||||||
font: 13px/1.35 Helvetica, arial, sans-serif;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 138.5%;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 123.1%;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 108%;
|
|
||||||
}
|
|
||||||
h1,h2,h3,p {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
h4,h5,h6 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
ul, ol {
|
|
||||||
margin: 0 1em;
|
|
||||||
padding: 0 1em;
|
|
||||||
}
|
|
||||||
blockquote {
|
|
||||||
border-left: 2px solid blue;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="build/squire-raw.js"></script>
|
<script type="text/javascript" src="build/squire-raw.js"></script>
|
||||||
|
<div id="editor"></div>
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" charset="utf-8">
|
||||||
var editor;
|
var div = document.getElementById( 'editor' );
|
||||||
var iframe = document.createElement( 'iframe' );
|
var editor = new Squire( div, {
|
||||||
iframe.addEventListener( 'load', function () {
|
blockTag: 'p',
|
||||||
// Make sure we're in standards mode.
|
blockAttributes: {'class': 'paragraph'},
|
||||||
var doc = iframe.contentDocument;
|
tagAttributes: {
|
||||||
if ( doc.compatMode !== 'CSS1Compat' ) {
|
ul: {'class': 'UL'},
|
||||||
doc.open();
|
ol: {'class': 'OL'},
|
||||||
doc.write( '<!DOCTYPE html><title></title>' );
|
li: {'class': 'listItem'}
|
||||||
doc.close();
|
}
|
||||||
}
|
});
|
||||||
// doc.close() can cause a re-entrant load event in some browsers,
|
|
||||||
// such as IE9.
|
|
||||||
if ( editor ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Create Squire instance
|
|
||||||
editor = new Squire( doc, {
|
|
||||||
blockTag: 'p',
|
|
||||||
blockAttributes: {'class': 'paragraph'},
|
|
||||||
tagAttributes: {
|
|
||||||
ul: {'class': 'UL'},
|
|
||||||
ol: {'class': 'OL'},
|
|
||||||
li: {'class': 'listItem'}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Add styles to frame
|
|
||||||
var style = doc.createElement( 'style' );
|
|
||||||
style.type = 'text/css';
|
|
||||||
style.textContent = document.getElementById( 'editorStyles' ).textContent;
|
|
||||||
doc.querySelector( 'head' ).appendChild( style );
|
|
||||||
}, false );
|
|
||||||
|
|
||||||
document.body.appendChild( iframe );
|
|
||||||
|
|
||||||
document.addEventListener( 'click', function ( e ) {
|
document.addEventListener( 'click', function ( e ) {
|
||||||
var id = e.target.id,
|
var id = e.target.id,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
@ -272,8 +272,8 @@ var cleanTree = function cleanTree ( node ) {
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
var removeEmptyInlines = function removeEmptyInlines ( root ) {
|
var removeEmptyInlines = function removeEmptyInlines ( node ) {
|
||||||
var children = root.childNodes,
|
var children = node.childNodes,
|
||||||
l = children.length,
|
l = children.length,
|
||||||
child;
|
child;
|
||||||
while ( l-- ) {
|
while ( l-- ) {
|
||||||
|
@ -281,10 +281,10 @@ var removeEmptyInlines = function removeEmptyInlines ( root ) {
|
||||||
if ( child.nodeType === ELEMENT_NODE && !isLeaf( child ) ) {
|
if ( child.nodeType === ELEMENT_NODE && !isLeaf( child ) ) {
|
||||||
removeEmptyInlines( child );
|
removeEmptyInlines( child );
|
||||||
if ( isInline( child ) && !child.firstChild ) {
|
if ( isInline( child ) && !child.firstChild ) {
|
||||||
root.removeChild( child );
|
node.removeChild( child );
|
||||||
}
|
}
|
||||||
} else if ( child.nodeType === TEXT_NODE && !child.data ) {
|
} else if ( child.nodeType === TEXT_NODE && !child.data ) {
|
||||||
root.removeChild( child );
|
node.removeChild( child );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -314,8 +314,8 @@ var isLineBreak = function ( br ) {
|
||||||
// line breaks by wrapping the inline text in a <div>. Browsers that want <br>
|
// line breaks by wrapping the inline text in a <div>. Browsers that want <br>
|
||||||
// elements at the end of each block will then have them added back in a later
|
// elements at the end of each block will then have them added back in a later
|
||||||
// fixCursor method call.
|
// fixCursor method call.
|
||||||
var cleanupBRs = function ( root ) {
|
var cleanupBRs = function ( node, root ) {
|
||||||
var brs = root.querySelectorAll( 'BR' ),
|
var brs = node.querySelectorAll( 'BR' ),
|
||||||
brBreaksLine = [],
|
brBreaksLine = [],
|
||||||
l = brs.length,
|
l = brs.length,
|
||||||
i, br, parent;
|
i, br, parent;
|
||||||
|
@ -340,7 +340,7 @@ var cleanupBRs = function ( root ) {
|
||||||
if ( !brBreaksLine[l] ) {
|
if ( !brBreaksLine[l] ) {
|
||||||
detach( br );
|
detach( br );
|
||||||
} else if ( !isInline( parent ) ) {
|
} else if ( !isInline( parent ) ) {
|
||||||
fixContainer( parent );
|
fixContainer( parent, root );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ var onCut = function ( event ) {
|
||||||
var clipboardData = event.clipboardData;
|
var clipboardData = event.clipboardData;
|
||||||
var range = this.getSelection();
|
var range = this.getSelection();
|
||||||
var node = this.createElement( 'div' );
|
var node = this.createElement( 'div' );
|
||||||
var body = this._body;
|
var root = this._root;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Save undo checkpoint
|
// Save undo checkpoint
|
||||||
|
@ -12,8 +12,8 @@ var onCut = function ( event ) {
|
||||||
|
|
||||||
// Edge only seems to support setting plain text as of 2016-03-11.
|
// Edge only seems to support setting plain text as of 2016-03-11.
|
||||||
if ( !isEdge && clipboardData ) {
|
if ( !isEdge && clipboardData ) {
|
||||||
moveRangeBoundariesUpTree( range, body );
|
moveRangeBoundariesUpTree( range, root );
|
||||||
node.appendChild( deleteContentsOfRange( range, body ) );
|
node.appendChild( deleteContentsOfRange( range, root ) );
|
||||||
clipboardData.setData( 'text/html', node.innerHTML );
|
clipboardData.setData( 'text/html', node.innerHTML );
|
||||||
clipboardData.setData( 'text/plain',
|
clipboardData.setData( 'text/plain',
|
||||||
node.innerText || node.textContent );
|
node.innerText || node.textContent );
|
||||||
|
@ -21,7 +21,7 @@ var onCut = function ( event ) {
|
||||||
} else {
|
} else {
|
||||||
setTimeout( function () {
|
setTimeout( function () {
|
||||||
try {
|
try {
|
||||||
// If all content removed, ensure div at start of body.
|
// If all content removed, ensure div at start of root.
|
||||||
self._ensureBottomLine();
|
self._ensureBottomLine();
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
self.didError( error );
|
self.didError( error );
|
||||||
|
@ -141,21 +141,18 @@ var onPaste = function ( event ) {
|
||||||
|
|
||||||
this._awaitingPaste = true;
|
this._awaitingPaste = true;
|
||||||
|
|
||||||
var body = this._body,
|
var body = this._doc.body,
|
||||||
range = this.getSelection(),
|
range = this.getSelection(),
|
||||||
startContainer = range.startContainer,
|
startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset,
|
startOffset = range.startOffset,
|
||||||
endContainer = range.endContainer,
|
endContainer = range.endContainer,
|
||||||
endOffset = range.endOffset,
|
endOffset = range.endOffset;
|
||||||
startBlock = getStartBlockOfRange( range );
|
|
||||||
|
|
||||||
// We need to position the pasteArea in the visible portion of the screen
|
// We need to position the pasteArea in the visible portion of the screen
|
||||||
// to stop the browser auto-scrolling.
|
// to stop the browser auto-scrolling.
|
||||||
var pasteArea = this.createElement( 'DIV', {
|
var pasteArea = this.createElement( 'DIV', {
|
||||||
style: 'position: absolute; overflow: hidden; top:' +
|
contenteditable: 'true',
|
||||||
( body.scrollTop +
|
style: 'position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;'
|
||||||
( startBlock ? startBlock.getBoundingClientRect().top : 0 ) ) +
|
|
||||||
'px; right: 150%; width: 1px; height: 1px;'
|
|
||||||
});
|
});
|
||||||
body.appendChild( pasteArea );
|
body.appendChild( pasteArea );
|
||||||
range.selectNodeContents( pasteArea );
|
range.selectNodeContents( pasteArea );
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
||||||
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
||||||
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
||||||
|
var DOCUMENT_NODE = 9; // Node.DOCUMENT_NODE;
|
||||||
var DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;
|
var DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;
|
||||||
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
||||||
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
|
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
|
||||||
|
|
256
source/Editor.js
256
source/Editor.js
|
@ -28,14 +28,17 @@ function mergeObjects ( base, extras ) {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Squire ( doc, config ) {
|
function Squire ( root, config ) {
|
||||||
|
if ( root.nodeType === DOCUMENT_NODE ) {
|
||||||
|
root = root.body;
|
||||||
|
}
|
||||||
|
var doc = root.ownerDocument;
|
||||||
var win = doc.defaultView;
|
var win = doc.defaultView;
|
||||||
var body = doc.body;
|
|
||||||
var mutation;
|
var mutation;
|
||||||
|
|
||||||
this._win = win;
|
this._win = win;
|
||||||
this._doc = doc;
|
this._doc = doc;
|
||||||
this._body = body;
|
this._root = root;
|
||||||
|
|
||||||
this._events = {};
|
this._events = {};
|
||||||
|
|
||||||
|
@ -56,9 +59,6 @@ function Squire ( doc, config ) {
|
||||||
this.addEventListener( 'keyup', this._updatePathOnEvent );
|
this.addEventListener( 'keyup', this._updatePathOnEvent );
|
||||||
this.addEventListener( 'mouseup', this._updatePathOnEvent );
|
this.addEventListener( 'mouseup', this._updatePathOnEvent );
|
||||||
|
|
||||||
win.addEventListener( 'focus', this, false );
|
|
||||||
win.addEventListener( 'blur', this, false );
|
|
||||||
|
|
||||||
this._undoIndex = -1;
|
this._undoIndex = -1;
|
||||||
this._undoStack = [];
|
this._undoStack = [];
|
||||||
this._undoStackLength = 0;
|
this._undoStackLength = 0;
|
||||||
|
@ -67,7 +67,7 @@ function Squire ( doc, config ) {
|
||||||
|
|
||||||
if ( canObserveMutations ) {
|
if ( canObserveMutations ) {
|
||||||
mutation = new MutationObserver( this._docWasChanged.bind( this ) );
|
mutation = new MutationObserver( this._docWasChanged.bind( this ) );
|
||||||
mutation.observe( body, {
|
mutation.observe( root, {
|
||||||
childList: true,
|
childList: true,
|
||||||
attributes: true,
|
attributes: true,
|
||||||
characterData: true,
|
characterData: true,
|
||||||
|
@ -123,7 +123,7 @@ function Squire ( doc, config ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
body.setAttribute( 'contenteditable', 'true' );
|
root.setAttribute( 'contenteditable', 'true' );
|
||||||
|
|
||||||
// Remove Firefox's built-in controls
|
// Remove Firefox's built-in controls
|
||||||
try {
|
try {
|
||||||
|
@ -167,7 +167,8 @@ proto.createElement = function ( tag, props, children ) {
|
||||||
proto.createDefaultBlock = function ( children ) {
|
proto.createDefaultBlock = function ( children ) {
|
||||||
var config = this._config;
|
var config = this._config;
|
||||||
return fixCursor(
|
return fixCursor(
|
||||||
this.createElement( config.blockTag, config.blockAttributes, children )
|
this.createElement( config.blockTag, config.blockAttributes, children ),
|
||||||
|
this._root
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -185,8 +186,8 @@ proto.getDocument = function () {
|
||||||
// document node, since these events are fired in a custom manner by the
|
// document node, since these events are fired in a custom manner by the
|
||||||
// editor code.
|
// editor code.
|
||||||
var customEvents = {
|
var customEvents = {
|
||||||
focus: 1, blur: 1,
|
pathChange: 1, select: 1, input: 1,
|
||||||
pathChange: 1, select: 1, input: 1, undoStateChange: 1
|
undoStateChange: 1, scrollPointIntoView: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.fireEvent = function ( type, event ) {
|
proto.fireEvent = function ( type, event ) {
|
||||||
|
@ -220,15 +221,12 @@ proto.fireEvent = function ( type, event ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.destroy = function () {
|
proto.destroy = function () {
|
||||||
var win = this._win,
|
var root = this._root,
|
||||||
doc = this._doc,
|
|
||||||
events = this._events,
|
events = this._events,
|
||||||
type;
|
type;
|
||||||
win.removeEventListener( 'focus', this, false );
|
|
||||||
win.removeEventListener( 'blur', this, false );
|
|
||||||
for ( type in events ) {
|
for ( type in events ) {
|
||||||
if ( !customEvents[ type ] ) {
|
if ( !customEvents[ type ] ) {
|
||||||
doc.removeEventListener( type, this, true );
|
root.removeEventListener( type, this, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( this._mutation ) {
|
if ( this._mutation ) {
|
||||||
|
@ -258,7 +256,7 @@ proto.addEventListener = function ( type, fn ) {
|
||||||
if ( !handlers ) {
|
if ( !handlers ) {
|
||||||
handlers = this._events[ type ] = [];
|
handlers = this._events[ type ] = [];
|
||||||
if ( !customEvents[ type ] ) {
|
if ( !customEvents[ type ] ) {
|
||||||
this._doc.addEventListener( type, this, true );
|
this._root.addEventListener( type, this, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handlers.push( fn );
|
handlers.push( fn );
|
||||||
|
@ -278,7 +276,7 @@ proto.removeEventListener = function ( type, fn ) {
|
||||||
if ( !handlers.length ) {
|
if ( !handlers.length ) {
|
||||||
delete this._events[ type ];
|
delete this._events[ type ];
|
||||||
if ( !customEvents[ type ] ) {
|
if ( !customEvents[ type ] ) {
|
||||||
this._doc.removeEventListener( type, this, false );
|
this._root.removeEventListener( type, this, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,26 +313,18 @@ proto.scrollRangeIntoView = function ( range ) {
|
||||||
parent.removeChild( node );
|
parent.removeChild( node );
|
||||||
parent.normalize();
|
parent.normalize();
|
||||||
}
|
}
|
||||||
if ( !rect ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Then check and scroll
|
|
||||||
var win = this._win;
|
|
||||||
var height = win.innerHeight;
|
|
||||||
var top = rect.top;
|
|
||||||
if ( top > height ) {
|
|
||||||
win.scrollBy( 0, top - height + 20 );
|
|
||||||
}
|
|
||||||
// And fire event for integrations to use
|
// And fire event for integrations to use
|
||||||
this.fireEvent( 'scrollPointIntoView', {
|
if ( rect ) {
|
||||||
x: rect.left,
|
this.fireEvent( 'scrollPointIntoView', {
|
||||||
y: top
|
x: rect.left,
|
||||||
});
|
y: rect.top
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
proto._moveCursorTo = function ( toStart ) {
|
proto._moveCursorTo = function ( toStart ) {
|
||||||
var body = this._body,
|
var root = this._root,
|
||||||
range = this._createRange( body, toStart ? 0 : body.childNodes.length );
|
range = this._createRange( root, toStart ? 0 : root.childNodes.length );
|
||||||
moveRangeBoundariesDownTree( range );
|
moveRangeBoundariesDownTree( range );
|
||||||
this.setSelection( range );
|
this.setSelection( range );
|
||||||
return this;
|
return this;
|
||||||
|
@ -370,8 +360,9 @@ proto.setSelection = function ( range ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.getSelection = function () {
|
proto.getSelection = function () {
|
||||||
var sel = getWindowSelection( this ),
|
var sel = getWindowSelection( this );
|
||||||
selection, startContainer, endContainer;
|
var root = this._root;
|
||||||
|
var selection, startContainer, endContainer;
|
||||||
if ( sel && sel.rangeCount ) {
|
if ( sel && sel.rangeCount ) {
|
||||||
selection = sel.getRangeAt( 0 ).cloneRange();
|
selection = sel.getRangeAt( 0 ).cloneRange();
|
||||||
startContainer = selection.startContainer;
|
startContainer = selection.startContainer;
|
||||||
|
@ -383,12 +374,15 @@ proto.getSelection = function () {
|
||||||
if ( endContainer && isLeaf( endContainer ) ) {
|
if ( endContainer && isLeaf( endContainer ) ) {
|
||||||
selection.setEndBefore( endContainer );
|
selection.setEndBefore( endContainer );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if ( selection &&
|
||||||
|
isOrContains( root, selection.commonAncestorContainer ) ) {
|
||||||
this._lastSelection = selection;
|
this._lastSelection = selection;
|
||||||
} else {
|
} else {
|
||||||
selection = this._lastSelection;
|
selection = this._lastSelection;
|
||||||
}
|
}
|
||||||
if ( !selection ) {
|
if ( !selection ) {
|
||||||
selection = this._createRange( this._body.firstChild, 0 );
|
selection = this._createRange( root.firstChild, 0 );
|
||||||
}
|
}
|
||||||
return selection;
|
return selection;
|
||||||
};
|
};
|
||||||
|
@ -474,7 +468,7 @@ proto._removeZWS = function () {
|
||||||
if ( !this._hasZWS ) {
|
if ( !this._hasZWS ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removeZWS( this._body );
|
removeZWS( this._root );
|
||||||
this._hasZWS = false;
|
this._hasZWS = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -489,7 +483,7 @@ proto._updatePath = function ( range, force ) {
|
||||||
this._lastAnchorNode = anchor;
|
this._lastAnchorNode = anchor;
|
||||||
this._lastFocusNode = focus;
|
this._lastFocusNode = focus;
|
||||||
newPath = ( anchor && focus ) ? ( anchor === focus ) ?
|
newPath = ( anchor && focus ) ? ( anchor === focus ) ?
|
||||||
getPath( focus ) : '(selection)' : '';
|
getPath( focus, this._root ) : '(selection)' : '';
|
||||||
if ( this._path !== newPath ) {
|
if ( this._path !== newPath ) {
|
||||||
this._path = newPath;
|
this._path = newPath;
|
||||||
this.fireEvent( 'pathChange', { path: newPath } );
|
this.fireEvent( 'pathChange', { path: newPath } );
|
||||||
|
@ -507,26 +501,12 @@ proto._updatePathOnEvent = function () {
|
||||||
// --- Focus ---
|
// --- Focus ---
|
||||||
|
|
||||||
proto.focus = function () {
|
proto.focus = function () {
|
||||||
// FF seems to need the body to be focussed (at least on first load).
|
this._root.focus();
|
||||||
// Chrome also now needs body to be focussed in order to show the cursor
|
|
||||||
// (otherwise it is focussed, but the cursor doesn't appear).
|
|
||||||
// Opera (Presto-variant) however will lose the selection if you call this!
|
|
||||||
if ( !isPresto ) {
|
|
||||||
this._body.focus();
|
|
||||||
}
|
|
||||||
this._win.focus();
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.blur = function () {
|
proto.blur = function () {
|
||||||
// IE will remove the whole browser window from focus if you call
|
this._root.blur();
|
||||||
// win.blur() or body.blur(), so instead we call top.focus() to focus
|
|
||||||
// the top frame, thus blurring this frame. This works in everything
|
|
||||||
// except FF, so we need to call body.blur() in that as well.
|
|
||||||
if ( isGecko ) {
|
|
||||||
this._body.blur();
|
|
||||||
}
|
|
||||||
top.focus();
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -565,9 +545,9 @@ proto._saveRangeToBookmark = function ( range ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto._getRangeAndRemoveBookmark = function ( range ) {
|
proto._getRangeAndRemoveBookmark = function ( range ) {
|
||||||
var doc = this._doc,
|
var root = this._root,
|
||||||
start = doc.getElementById( startSelectionId ),
|
start = root.querySelector( '#' + startSelectionId ),
|
||||||
end = doc.getElementById( endSelectionId );
|
end = root.querySelector( '#' + endSelectionId );
|
||||||
|
|
||||||
if ( start && end ) {
|
if ( start && end ) {
|
||||||
var startContainer = start.parentNode,
|
var startContainer = start.parentNode,
|
||||||
|
@ -595,7 +575,7 @@ proto._getRangeAndRemoveBookmark = function ( range ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !range ) {
|
if ( !range ) {
|
||||||
range = doc.createRange();
|
range = this._doc.createRange();
|
||||||
}
|
}
|
||||||
range.setStart( _range.startContainer, _range.startOffset );
|
range.setStart( _range.startContainer, _range.startOffset );
|
||||||
range.setEnd( _range.endContainer, _range.endOffset );
|
range.setEnd( _range.endContainer, _range.endOffset );
|
||||||
|
@ -744,27 +724,28 @@ proto.hasFormat = function ( tag, attributes, range ) {
|
||||||
|
|
||||||
// If the common ancestor is inside the tag we require, we definitely
|
// If the common ancestor is inside the tag we require, we definitely
|
||||||
// have the format.
|
// have the format.
|
||||||
var root = range.commonAncestorContainer,
|
var root = this._root;
|
||||||
walker, node;
|
var common = range.commonAncestorContainer;
|
||||||
if ( getNearest( root, tag, attributes ) ) {
|
var walker, node;
|
||||||
|
if ( getNearest( common, root, tag, attributes ) ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If common ancestor is a text node and doesn't have the format, we
|
// If common ancestor is a text node and doesn't have the format, we
|
||||||
// definitely don't have it.
|
// definitely don't have it.
|
||||||
if ( root.nodeType === TEXT_NODE ) {
|
if ( common.nodeType === TEXT_NODE ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, check each text node at least partially contained within
|
// Otherwise, check each text node at least partially contained within
|
||||||
// the selection and make sure all of them have the format we want.
|
// the selection and make sure all of them have the format we want.
|
||||||
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
|
walker = new TreeWalker( common, SHOW_TEXT, function ( node ) {
|
||||||
return isNodeContainedInRange( range, node, true );
|
return isNodeContainedInRange( range, node, true );
|
||||||
}, false );
|
}, false );
|
||||||
|
|
||||||
var seenNode = false;
|
var seenNode = false;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
if ( !getNearest( node, tag, attributes ) ) {
|
if ( !getNearest( node, root, tag, attributes ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
seenNode = true;
|
seenNode = true;
|
||||||
|
@ -823,11 +804,12 @@ proto.getFontInfo = function ( range ) {
|
||||||
proto._addFormat = function ( tag, attributes, range ) {
|
proto._addFormat = function ( tag, attributes, range ) {
|
||||||
// If the range is collapsed we simply insert the node by wrapping
|
// If the range is collapsed we simply insert the node by wrapping
|
||||||
// it round the range and focus it.
|
// it round the range and focus it.
|
||||||
|
var root = this._root;
|
||||||
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
||||||
node, needsFormat;
|
node, needsFormat;
|
||||||
|
|
||||||
if ( range.collapsed ) {
|
if ( range.collapsed ) {
|
||||||
el = fixCursor( this.createElement( tag, attributes ) );
|
el = fixCursor( this.createElement( tag, attributes ), root );
|
||||||
insertNodeInRange( range, el );
|
insertNodeInRange( range, el );
|
||||||
range.setStart( el.firstChild, el.firstChild.length );
|
range.setStart( el.firstChild, el.firstChild.length );
|
||||||
range.collapse( true );
|
range.collapse( true );
|
||||||
|
@ -880,7 +862,7 @@ proto._addFormat = function ( tag, attributes, range ) {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
node = walker.currentNode;
|
node = walker.currentNode;
|
||||||
needsFormat = !getNearest( node, tag, attributes );
|
needsFormat = !getNearest( node, root, tag, attributes );
|
||||||
if ( needsFormat ) {
|
if ( needsFormat ) {
|
||||||
// <br> can never be a container node, so must have a text node
|
// <br> can never be a container node, so must have a text node
|
||||||
// if node == (end|start)Container
|
// if node == (end|start)Container
|
||||||
|
@ -1078,7 +1060,7 @@ var tagAfterSplit = {
|
||||||
var splitBlock = function ( self, block, node, offset ) {
|
var splitBlock = function ( self, block, node, offset ) {
|
||||||
var splitTag = tagAfterSplit[ block.nodeName ],
|
var splitTag = tagAfterSplit[ block.nodeName ],
|
||||||
splitProperties = null,
|
splitProperties = null,
|
||||||
nodeAfterSplit = split( node, offset, block.parentNode ),
|
nodeAfterSplit = split( node, offset, block.parentNode, self._root ),
|
||||||
config = self._config;
|
config = self._config;
|
||||||
|
|
||||||
if ( !splitTag ) {
|
if ( !splitTag ) {
|
||||||
|
@ -1110,12 +1092,13 @@ proto.forEachBlock = function ( fn, mutates, range ) {
|
||||||
this.saveUndoState( range );
|
this.saveUndoState( range );
|
||||||
}
|
}
|
||||||
|
|
||||||
var start = getStartBlockOfRange( range ),
|
var root = this._root;
|
||||||
end = getEndBlockOfRange( range );
|
var start = getStartBlockOfRange( range, root );
|
||||||
|
var end = getEndBlockOfRange( range, root );
|
||||||
if ( start && end ) {
|
if ( start && end ) {
|
||||||
do {
|
do {
|
||||||
if ( fn( start ) || start === end ) { break; }
|
if ( fn( start ) || start === end ) { break; }
|
||||||
} while ( start = getNextBlock( start ) );
|
} while ( start = getNextBlock( start, root ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mutates ) {
|
if ( mutates ) {
|
||||||
|
@ -1144,23 +1127,24 @@ proto.modifyBlocks = function ( modify, range ) {
|
||||||
this._recordUndoState( range );
|
this._recordUndoState( range );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var root = this._root;
|
||||||
|
var frag;
|
||||||
|
|
||||||
// 2. Expand range to block boundaries
|
// 2. Expand range to block boundaries
|
||||||
expandRangeToBlockBoundaries( range );
|
expandRangeToBlockBoundaries( range, root );
|
||||||
|
|
||||||
// 3. Remove range.
|
// 3. Remove range.
|
||||||
var body = this._body,
|
moveRangeBoundariesUpTree( range, root );
|
||||||
frag;
|
frag = extractContentsOfRange( range, root, root );
|
||||||
moveRangeBoundariesUpTree( range, body );
|
|
||||||
frag = extractContentsOfRange( range, body );
|
|
||||||
|
|
||||||
// 4. Modify tree of fragment and reinsert.
|
// 4. Modify tree of fragment and reinsert.
|
||||||
insertNodeInRange( range, modify.call( this, frag ) );
|
insertNodeInRange( range, modify.call( this, frag ) );
|
||||||
|
|
||||||
// 5. Merge containers at edges
|
// 5. Merge containers at edges
|
||||||
if ( range.endOffset < range.endContainer.childNodes.length ) {
|
if ( range.endOffset < range.endContainer.childNodes.length ) {
|
||||||
mergeContainers( range.endContainer.childNodes[ range.endOffset ] );
|
mergeContainers( range.endContainer.childNodes[ range.endOffset ], root );
|
||||||
}
|
}
|
||||||
mergeContainers( range.startContainer.childNodes[ range.startOffset ] );
|
mergeContainers( range.startContainer.childNodes[ range.startOffset ], root );
|
||||||
|
|
||||||
// 6. Restore selection
|
// 6. Restore selection
|
||||||
this._getRangeAndRemoveBookmark( range );
|
this._getRangeAndRemoveBookmark( range );
|
||||||
|
@ -1183,9 +1167,10 @@ var increaseBlockQuoteLevel = function ( frag ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var decreaseBlockQuoteLevel = function ( frag ) {
|
var decreaseBlockQuoteLevel = function ( frag ) {
|
||||||
|
var root = this._root;
|
||||||
var blockquotes = frag.querySelectorAll( 'blockquote' );
|
var blockquotes = frag.querySelectorAll( 'blockquote' );
|
||||||
Array.prototype.filter.call( blockquotes, function ( el ) {
|
Array.prototype.filter.call( blockquotes, function ( el ) {
|
||||||
return !getNearest( el.parentNode, 'BLOCKQUOTE' );
|
return !getNearest( el.parentNode, root, 'BLOCKQUOTE' );
|
||||||
}).forEach( function ( el ) {
|
}).forEach( function ( el ) {
|
||||||
replaceWith( el, empty( el ) );
|
replaceWith( el, empty( el ) );
|
||||||
});
|
});
|
||||||
|
@ -1206,7 +1191,7 @@ var removeBlockQuote = function (/* frag */) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var makeList = function ( self, frag, type ) {
|
var makeList = function ( self, frag, type ) {
|
||||||
var walker = getBlockWalker( frag ),
|
var walker = getBlockWalker( frag, self._root ),
|
||||||
node, tag, prev, newLi,
|
node, tag, prev, newLi,
|
||||||
tagAttributes = self._config.tagAttributes,
|
tagAttributes = self._config.tagAttributes,
|
||||||
listAttrs = tagAttributes[ type.toLowerCase() ],
|
listAttrs = tagAttributes[ type.toLowerCase() ],
|
||||||
|
@ -1269,7 +1254,7 @@ var removeList = function ( frag ) {
|
||||||
child = children[ll];
|
child = children[ll];
|
||||||
replaceWith( child, empty( child ) );
|
replaceWith( child, empty( child ) );
|
||||||
}
|
}
|
||||||
fixContainer( listFrag );
|
fixContainer( listFrag, this._root );
|
||||||
replaceWith( list, listFrag );
|
replaceWith( list, listFrag );
|
||||||
}
|
}
|
||||||
return frag;
|
return frag;
|
||||||
|
@ -1305,6 +1290,7 @@ var increaseListLevel = function ( frag ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var decreaseListLevel = function ( frag ) {
|
var decreaseListLevel = function ( frag ) {
|
||||||
|
var root = this._root;
|
||||||
var items = frag.querySelectorAll( 'LI' );
|
var items = frag.querySelectorAll( 'LI' );
|
||||||
Array.prototype.filter.call( items, function ( el ) {
|
Array.prototype.filter.call( items, function ( el ) {
|
||||||
return !isContainer( el.firstChild );
|
return !isContainer( el.firstChild );
|
||||||
|
@ -1315,7 +1301,7 @@ var decreaseListLevel = function ( frag ) {
|
||||||
node = first,
|
node = first,
|
||||||
next;
|
next;
|
||||||
if ( item.previousSibling ) {
|
if ( item.previousSibling ) {
|
||||||
parent = split( parent, item, newParent );
|
parent = split( parent, item, newParent, root );
|
||||||
}
|
}
|
||||||
while ( node ) {
|
while ( node ) {
|
||||||
next = node.nextSibling;
|
next = node.nextSibling;
|
||||||
|
@ -1326,7 +1312,7 @@ var decreaseListLevel = function ( frag ) {
|
||||||
node = next;
|
node = next;
|
||||||
}
|
}
|
||||||
if ( newParent.nodeName === 'LI' && first.previousSibling ) {
|
if ( newParent.nodeName === 'LI' && first.previousSibling ) {
|
||||||
split( newParent, first, newParent.parentNode );
|
split( newParent, first, newParent.parentNode, root );
|
||||||
}
|
}
|
||||||
while ( item !== frag && !item.childNodes.length ) {
|
while ( item !== frag && !item.childNodes.length ) {
|
||||||
parent = item.parentNode;
|
parent = item.parentNode;
|
||||||
|
@ -1334,16 +1320,16 @@ var decreaseListLevel = function ( frag ) {
|
||||||
item = parent;
|
item = parent;
|
||||||
}
|
}
|
||||||
}, this );
|
}, this );
|
||||||
fixContainer( frag );
|
fixContainer( frag, root );
|
||||||
return frag;
|
return frag;
|
||||||
};
|
};
|
||||||
|
|
||||||
proto._ensureBottomLine = function () {
|
proto._ensureBottomLine = function () {
|
||||||
var body = this._body,
|
var root = this._root;
|
||||||
last = body.lastElementChild;
|
var last = root.lastElementChild;
|
||||||
if ( !last ||
|
if ( !last ||
|
||||||
last.nodeName !== this._config.blockTag || !isBlock( last ) ) {
|
last.nodeName !== this._config.blockTag || !isBlock( last ) ) {
|
||||||
body.appendChild( this.createDefaultBlock() );
|
root.appendChild( this.createDefaultBlock() );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1357,27 +1343,29 @@ proto.setKeyHandler = function ( key, fn ) {
|
||||||
// --- Get/Set data ---
|
// --- Get/Set data ---
|
||||||
|
|
||||||
proto._getHTML = function () {
|
proto._getHTML = function () {
|
||||||
return this._body.innerHTML;
|
return this._root.innerHTML;
|
||||||
};
|
};
|
||||||
|
|
||||||
proto._setHTML = function ( html ) {
|
proto._setHTML = function ( html ) {
|
||||||
var node = this._body;
|
var root = this._root;
|
||||||
|
var node = root;
|
||||||
node.innerHTML = html;
|
node.innerHTML = html;
|
||||||
do {
|
do {
|
||||||
fixCursor( node );
|
fixCursor( node, root );
|
||||||
} while ( node = getNextBlock( node ) );
|
} while ( node = getNextBlock( node, root ) );
|
||||||
this._ignoreChange = true;
|
this._ignoreChange = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.getHTML = function ( withBookMark ) {
|
proto.getHTML = function ( withBookMark ) {
|
||||||
var brs = [],
|
var brs = [],
|
||||||
node, fixer, html, l, range;
|
root, node, fixer, html, l, range;
|
||||||
if ( withBookMark && ( range = this.getSelection() ) ) {
|
if ( withBookMark && ( range = this.getSelection() ) ) {
|
||||||
this._saveRangeToBookmark( range );
|
this._saveRangeToBookmark( range );
|
||||||
}
|
}
|
||||||
if ( useTextFixer ) {
|
if ( useTextFixer ) {
|
||||||
node = this._body;
|
root = this._root;
|
||||||
while ( node = getNextBlock( node ) ) {
|
node = root;
|
||||||
|
while ( node = getNextBlock( node, root ) ) {
|
||||||
if ( !node.textContent && !node.querySelector( 'BR' ) ) {
|
if ( !node.textContent && !node.querySelector( 'BR' ) ) {
|
||||||
fixer = this.createElement( 'BR' );
|
fixer = this.createElement( 'BR' );
|
||||||
node.appendChild( fixer );
|
node.appendChild( fixer );
|
||||||
|
@ -1399,37 +1387,37 @@ proto.getHTML = function ( withBookMark ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.setHTML = function ( html ) {
|
proto.setHTML = function ( html ) {
|
||||||
var frag = this._doc.createDocumentFragment(),
|
var frag = this._doc.createDocumentFragment();
|
||||||
div = this.createElement( 'DIV' ),
|
var div = this.createElement( 'DIV' );
|
||||||
child;
|
var root = this._root;
|
||||||
|
var child;
|
||||||
|
|
||||||
// Parse HTML into DOM tree
|
// Parse HTML into DOM tree
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
frag.appendChild( empty( div ) );
|
frag.appendChild( empty( div ) );
|
||||||
|
|
||||||
cleanTree( frag );
|
cleanTree( frag );
|
||||||
cleanupBRs( frag );
|
cleanupBRs( frag, root );
|
||||||
|
|
||||||
fixContainer( frag );
|
fixContainer( frag, root );
|
||||||
|
|
||||||
// Fix cursor
|
// Fix cursor
|
||||||
var node = frag;
|
var node = frag;
|
||||||
while ( node = getNextBlock( node ) ) {
|
while ( node = getNextBlock( node, root ) ) {
|
||||||
fixCursor( node );
|
fixCursor( node, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't fire an input event
|
// Don't fire an input event
|
||||||
this._ignoreChange = true;
|
this._ignoreChange = true;
|
||||||
|
|
||||||
// Remove existing body children
|
// Remove existing root children
|
||||||
var body = this._body;
|
while ( child = root.lastChild ) {
|
||||||
while ( child = body.lastChild ) {
|
root.removeChild( child );
|
||||||
body.removeChild( child );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And insert new content
|
// And insert new content
|
||||||
body.appendChild( frag );
|
root.appendChild( frag );
|
||||||
fixCursor( body );
|
fixCursor( root, root );
|
||||||
|
|
||||||
// Reset the undo stack
|
// Reset the undo stack
|
||||||
this._undoIndex = -1;
|
this._undoIndex = -1;
|
||||||
|
@ -1439,17 +1427,13 @@ proto.setHTML = function ( html ) {
|
||||||
|
|
||||||
// Record undo state
|
// Record undo state
|
||||||
var range = this._getRangeAndRemoveBookmark() ||
|
var range = this._getRangeAndRemoveBookmark() ||
|
||||||
this._createRange( body.firstChild, 0 );
|
this._createRange( root.firstChild, 0 );
|
||||||
this.saveUndoState( range );
|
this.saveUndoState( range );
|
||||||
// IE will also set focus when selecting text so don't use
|
// IE will also set focus when selecting text so don't use
|
||||||
// setSelection. Instead, just store it in lastSelection, so if
|
// setSelection. Instead, just store it in lastSelection, so if
|
||||||
// anything calls getSelection before first focus, we have a range
|
// anything calls getSelection before first focus, we have a range
|
||||||
// to return.
|
// to return.
|
||||||
if ( losesSelectionOnBlur ) {
|
this._lastSelection = range;
|
||||||
this._lastSelection = range;
|
|
||||||
} else {
|
|
||||||
this.setSelection( range );
|
|
||||||
}
|
|
||||||
this._updatePath( range, true );
|
this._updatePath( range, true );
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -1463,25 +1447,25 @@ proto.insertElement = function ( el, range ) {
|
||||||
range.setStartAfter( el );
|
range.setStartAfter( el );
|
||||||
} else {
|
} else {
|
||||||
// Get containing block node.
|
// Get containing block node.
|
||||||
var body = this._body,
|
var root = this._root;
|
||||||
splitNode = getStartBlockOfRange( range ) || body,
|
var splitNode = getStartBlockOfRange( range, root ) || root;
|
||||||
parent, nodeAfterSplit;
|
var parent, nodeAfterSplit;
|
||||||
// While at end of container node, move up DOM tree.
|
// While at end of container node, move up DOM tree.
|
||||||
while ( splitNode !== body && !splitNode.nextSibling ) {
|
while ( splitNode !== root && !splitNode.nextSibling ) {
|
||||||
splitNode = splitNode.parentNode;
|
splitNode = splitNode.parentNode;
|
||||||
}
|
}
|
||||||
// If in the middle of a container node, split up to body.
|
// If in the middle of a container node, split up to root.
|
||||||
if ( splitNode !== body ) {
|
if ( splitNode !== root ) {
|
||||||
parent = splitNode.parentNode;
|
parent = splitNode.parentNode;
|
||||||
nodeAfterSplit = split( parent, splitNode.nextSibling, body );
|
nodeAfterSplit = split( parent, splitNode.nextSibling, root, root );
|
||||||
}
|
}
|
||||||
if ( nodeAfterSplit ) {
|
if ( nodeAfterSplit ) {
|
||||||
body.insertBefore( el, nodeAfterSplit );
|
root.insertBefore( el, nodeAfterSplit );
|
||||||
} else {
|
} else {
|
||||||
body.appendChild( el );
|
root.appendChild( el );
|
||||||
// Insert blank line below block.
|
// Insert blank line below block.
|
||||||
nodeAfterSplit = this.createDefaultBlock();
|
nodeAfterSplit = this.createDefaultBlock();
|
||||||
body.appendChild( nodeAfterSplit );
|
root.appendChild( nodeAfterSplit );
|
||||||
}
|
}
|
||||||
range.setStart( nodeAfterSplit, 0 );
|
range.setStart( nodeAfterSplit, 0 );
|
||||||
range.setEnd( nodeAfterSplit, 0 );
|
range.setEnd( nodeAfterSplit, 0 );
|
||||||
|
@ -1503,11 +1487,11 @@ proto.insertImage = function ( src, attributes ) {
|
||||||
|
|
||||||
var linkRegExp = /\b((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))|([\w\-.%+]+@(?:[\w\-]+\.)+[A-Z]{2,}\b)/i;
|
var linkRegExp = /\b((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))|([\w\-.%+]+@(?:[\w\-]+\.)+[A-Z]{2,}\b)/i;
|
||||||
|
|
||||||
var addLinks = function ( frag ) {
|
var addLinks = function ( frag, root ) {
|
||||||
var doc = frag.ownerDocument,
|
var doc = frag.ownerDocument,
|
||||||
walker = new TreeWalker( frag, SHOW_TEXT,
|
walker = new TreeWalker( frag, SHOW_TEXT,
|
||||||
function ( node ) {
|
function ( node ) {
|
||||||
return !getNearest( node, 'A' );
|
return !getNearest( node, root, 'A' );
|
||||||
}, false ),
|
}, false ),
|
||||||
node, data, parent, match, index, endIndex, child;
|
node, data, parent, match, index, endIndex, child;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
|
@ -1549,6 +1533,7 @@ proto.insertHTML = function ( html, isPaste ) {
|
||||||
this.saveUndoState( range );
|
this.saveUndoState( range );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
var root = this._root;
|
||||||
var node = frag;
|
var node = frag;
|
||||||
var event = {
|
var event = {
|
||||||
fragment: frag,
|
fragment: frag,
|
||||||
|
@ -1558,14 +1543,14 @@ proto.insertHTML = function ( html, isPaste ) {
|
||||||
defaultPrevented: false
|
defaultPrevented: false
|
||||||
};
|
};
|
||||||
|
|
||||||
addLinks( frag );
|
addLinks( frag, root );
|
||||||
cleanTree( frag );
|
cleanTree( frag );
|
||||||
cleanupBRs( frag );
|
cleanupBRs( frag, root );
|
||||||
removeEmptyInlines( frag );
|
removeEmptyInlines( frag );
|
||||||
frag.normalize();
|
frag.normalize();
|
||||||
|
|
||||||
while ( node = getNextBlock( node ) ) {
|
while ( node = getNextBlock( node, root ) ) {
|
||||||
fixCursor( node );
|
fixCursor( node, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isPaste ) {
|
if ( isPaste ) {
|
||||||
|
@ -1573,7 +1558,7 @@ proto.insertHTML = function ( html, isPaste ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !event.defaultPrevented ) {
|
if ( !event.defaultPrevented ) {
|
||||||
insertTreeFragmentIntoRange( range, event.fragment );
|
insertTreeFragmentIntoRange( range, event.fragment, root );
|
||||||
if ( !canObserveMutations ) {
|
if ( !canObserveMutations ) {
|
||||||
this._docWasChanged();
|
this._docWasChanged();
|
||||||
}
|
}
|
||||||
|
@ -1778,13 +1763,14 @@ proto.removeAllFormatting = function ( range ) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var root = this._root;
|
||||||
var stopNode = range.commonAncestorContainer;
|
var stopNode = range.commonAncestorContainer;
|
||||||
while ( stopNode && !isBlock( stopNode ) ) {
|
while ( stopNode && !isBlock( stopNode ) ) {
|
||||||
stopNode = stopNode.parentNode;
|
stopNode = stopNode.parentNode;
|
||||||
}
|
}
|
||||||
if ( !stopNode ) {
|
if ( !stopNode ) {
|
||||||
expandRangeToBlockBoundaries( range );
|
expandRangeToBlockBoundaries( range, root );
|
||||||
stopNode = this._body;
|
stopNode = root;
|
||||||
}
|
}
|
||||||
if ( stopNode.nodeType === TEXT_NODE ) {
|
if ( stopNode.nodeType === TEXT_NODE ) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -1797,7 +1783,7 @@ proto.removeAllFormatting = function ( range ) {
|
||||||
moveRangeBoundariesUpTree( range, stopNode );
|
moveRangeBoundariesUpTree( range, stopNode );
|
||||||
|
|
||||||
// Split the selection up to the block, or if whole selection in same
|
// Split the selection up to the block, or if whole selection in same
|
||||||
// block, expand range boundaries to ends of block and split up to body.
|
// block, expand range boundaries to ends of block and split up to root.
|
||||||
var doc = stopNode.ownerDocument;
|
var doc = stopNode.ownerDocument;
|
||||||
var startContainer = range.startContainer;
|
var startContainer = range.startContainer;
|
||||||
var startOffset = range.startOffset;
|
var startOffset = range.startOffset;
|
||||||
|
@ -1808,8 +1794,8 @@ proto.removeAllFormatting = function ( range ) {
|
||||||
// in same container.
|
// in same container.
|
||||||
var formattedNodes = doc.createDocumentFragment();
|
var formattedNodes = doc.createDocumentFragment();
|
||||||
var cleanNodes = doc.createDocumentFragment();
|
var cleanNodes = doc.createDocumentFragment();
|
||||||
var nodeAfterSplit = split( endContainer, endOffset, stopNode );
|
var nodeAfterSplit = split( endContainer, endOffset, stopNode, root );
|
||||||
var nodeInSplit = split( startContainer, startOffset, stopNode );
|
var nodeInSplit = split( startContainer, startOffset, stopNode, root );
|
||||||
var nextNode, _range, childNodes;
|
var nextNode, _range, childNodes;
|
||||||
|
|
||||||
// Then replace contents in split with a cleaned version of the same:
|
// Then replace contents in split with a cleaned version of the same:
|
||||||
|
|
|
@ -63,7 +63,7 @@ var onKey = function ( event ) {
|
||||||
// Record undo checkpoint.
|
// Record undo checkpoint.
|
||||||
this.saveUndoState( range );
|
this.saveUndoState( range );
|
||||||
// Delete the selection
|
// Delete the selection
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range, this._root );
|
||||||
this._ensureBottomLine();
|
this._ensureBottomLine();
|
||||||
this.setSelection( range );
|
this.setSelection( range );
|
||||||
this._updatePath( range, true );
|
this._updatePath( range, true );
|
||||||
|
@ -120,17 +120,17 @@ var afterDelete = function ( self, range ) {
|
||||||
parent.removeChild( node );
|
parent.removeChild( node );
|
||||||
// Fix cursor in block
|
// Fix cursor in block
|
||||||
if ( !isBlock( parent ) ) {
|
if ( !isBlock( parent ) ) {
|
||||||
parent = getPreviousBlock( parent );
|
parent = getPreviousBlock( parent, self._root );
|
||||||
}
|
}
|
||||||
fixCursor( parent );
|
fixCursor( parent, self._root );
|
||||||
// Move cursor into text node
|
// Move cursor into text node
|
||||||
moveRangeBoundariesDownTree( range );
|
moveRangeBoundariesDownTree( range );
|
||||||
}
|
}
|
||||||
// If you delete the last character in the sole <div> in Chrome,
|
// If you delete the last character in the sole <div> in Chrome,
|
||||||
// it removes the div and replaces it with just a <br> inside the
|
// it removes the div and replaces it with just a <br> inside the
|
||||||
// body. Detach the <br>; the _ensureBottomLine call will insert a new
|
// root. Detach the <br>; the _ensureBottomLine call will insert a new
|
||||||
// block.
|
// block.
|
||||||
if ( node.nodeName === 'BODY' &&
|
if ( node === self._root &&
|
||||||
( node = node.firstChild ) && node.nodeName === 'BR' ) {
|
( node = node.firstChild ) && node.nodeName === 'BR' ) {
|
||||||
detach( node );
|
detach( node );
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,7 @@ var afterDelete = function ( self, range ) {
|
||||||
|
|
||||||
var keyHandlers = {
|
var keyHandlers = {
|
||||||
enter: function ( self, event, range ) {
|
enter: function ( self, event, range ) {
|
||||||
|
var root = self._root;
|
||||||
var block, parent, nodeAfterSplit;
|
var block, parent, nodeAfterSplit;
|
||||||
|
|
||||||
// We handle this ourselves
|
// We handle this ourselves
|
||||||
|
@ -160,10 +161,10 @@ var keyHandlers = {
|
||||||
// Selected text is overwritten, therefore delete the contents
|
// Selected text is overwritten, therefore delete the contents
|
||||||
// to collapse selection.
|
// to collapse selection.
|
||||||
if ( !range.collapsed ) {
|
if ( !range.collapsed ) {
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
block = getStartBlockOfRange( range );
|
block = getStartBlockOfRange( range, root );
|
||||||
|
|
||||||
// If this is a malformed bit of document or in a table;
|
// If this is a malformed bit of document or in a table;
|
||||||
// just play it safe and insert a <br>.
|
// just play it safe and insert a <br>.
|
||||||
|
@ -176,17 +177,18 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If in a list, we'll split the LI instead.
|
// If in a list, we'll split the LI instead.
|
||||||
if ( parent = getNearest( block, 'LI' ) ) {
|
if ( parent = getNearest( block, root, 'LI' ) ) {
|
||||||
block = parent;
|
block = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !block.textContent ) {
|
if ( !block.textContent ) {
|
||||||
// Break list
|
// Break list
|
||||||
if ( getNearest( block, 'UL' ) || getNearest( block, 'OL' ) ) {
|
if ( getNearest( block, root, 'UL' ) ||
|
||||||
|
getNearest( block, root, 'OL' ) ) {
|
||||||
return self.modifyBlocks( decreaseListLevel, range );
|
return self.modifyBlocks( decreaseListLevel, range );
|
||||||
}
|
}
|
||||||
// Break blockquote
|
// Break blockquote
|
||||||
else if ( getNearest( block, 'BLOCKQUOTE' ) ) {
|
else if ( getNearest( block, root, 'BLOCKQUOTE' ) ) {
|
||||||
return self.modifyBlocks( removeBlockQuote, range );
|
return self.modifyBlocks( removeBlockQuote, range );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,7 +201,7 @@ var keyHandlers = {
|
||||||
// block
|
// block
|
||||||
removeZWS( block );
|
removeZWS( block );
|
||||||
removeEmptyInlines( block );
|
removeEmptyInlines( block );
|
||||||
fixCursor( block );
|
fixCursor( block, root );
|
||||||
|
|
||||||
// Focus cursor
|
// Focus cursor
|
||||||
// If there's a <b>/<i> etc. at the beginning of the split
|
// If there's a <b>/<i> etc. at the beginning of the split
|
||||||
|
@ -242,27 +244,28 @@ var keyHandlers = {
|
||||||
self._updatePath( range, true );
|
self._updatePath( range, true );
|
||||||
},
|
},
|
||||||
backspace: function ( self, event, range ) {
|
backspace: function ( self, event, range ) {
|
||||||
|
var root = self._root;
|
||||||
self._removeZWS();
|
self._removeZWS();
|
||||||
// Record undo checkpoint.
|
// Record undo checkpoint.
|
||||||
self.saveUndoState( range );
|
self.saveUndoState( range );
|
||||||
// If not collapsed, delete contents
|
// If not collapsed, delete contents
|
||||||
if ( !range.collapsed ) {
|
if ( !range.collapsed ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range, root );
|
||||||
afterDelete( self, range );
|
afterDelete( self, range );
|
||||||
}
|
}
|
||||||
// If at beginning of block, merge with previous
|
// If at beginning of block, merge with previous
|
||||||
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
|
else if ( rangeDoesStartAtBlockBoundary( range, root ) ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var current = getStartBlockOfRange( range );
|
var current = getStartBlockOfRange( range, root );
|
||||||
var previous;
|
var previous;
|
||||||
if ( !current ) {
|
if ( !current ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// In case inline data has somehow got between blocks.
|
// In case inline data has somehow got between blocks.
|
||||||
fixContainer( current.parentNode );
|
fixContainer( current.parentNode, root );
|
||||||
// Now get previous block
|
// Now get previous block
|
||||||
previous = getPreviousBlock( current );
|
previous = getPreviousBlock( current, root );
|
||||||
// Must not be at the very beginning of the text area.
|
// Must not be at the very beginning of the text area.
|
||||||
if ( previous ) {
|
if ( previous ) {
|
||||||
// If not editable, just delete whole block.
|
// If not editable, just delete whole block.
|
||||||
|
@ -279,7 +282,7 @@ var keyHandlers = {
|
||||||
current = current.parentNode;
|
current = current.parentNode;
|
||||||
}
|
}
|
||||||
if ( current && ( current = current.nextSibling ) ) {
|
if ( current && ( current = current.nextSibling ) ) {
|
||||||
mergeContainers( current );
|
mergeContainers( current, root );
|
||||||
}
|
}
|
||||||
self.setSelection( range );
|
self.setSelection( range );
|
||||||
}
|
}
|
||||||
|
@ -287,12 +290,12 @@ var keyHandlers = {
|
||||||
// to break lists/blockquote.
|
// to break lists/blockquote.
|
||||||
else if ( current ) {
|
else if ( current ) {
|
||||||
// Break list
|
// Break list
|
||||||
if ( getNearest( current, 'UL' ) ||
|
if ( getNearest( current, root, 'UL' ) ||
|
||||||
getNearest( current, 'OL' ) ) {
|
getNearest( current, root, 'OL' ) ) {
|
||||||
return self.modifyBlocks( decreaseListLevel, range );
|
return self.modifyBlocks( decreaseListLevel, range );
|
||||||
}
|
}
|
||||||
// Break blockquote
|
// Break blockquote
|
||||||
else if ( getNearest( current, 'BLOCKQUOTE' ) ) {
|
else if ( getNearest( current, root, 'BLOCKQUOTE' ) ) {
|
||||||
return self.modifyBlocks( decreaseBlockQuoteLevel, range );
|
return self.modifyBlocks( decreaseBlockQuoteLevel, range );
|
||||||
}
|
}
|
||||||
self.setSelection( range );
|
self.setSelection( range );
|
||||||
|
@ -307,6 +310,7 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'delete': function ( self, event, range ) {
|
'delete': function ( self, event, range ) {
|
||||||
|
var root = self._root;
|
||||||
var current, next, originalRange,
|
var current, next, originalRange,
|
||||||
cursorContainer, cursorOffset, nodeAfterCursor;
|
cursorContainer, cursorOffset, nodeAfterCursor;
|
||||||
self._removeZWS();
|
self._removeZWS();
|
||||||
|
@ -315,20 +319,20 @@ var keyHandlers = {
|
||||||
// If not collapsed, delete contents
|
// If not collapsed, delete contents
|
||||||
if ( !range.collapsed ) {
|
if ( !range.collapsed ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range, root );
|
||||||
afterDelete( self, range );
|
afterDelete( self, range );
|
||||||
}
|
}
|
||||||
// If at end of block, merge next into this block
|
// If at end of block, merge next into this block
|
||||||
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
|
else if ( rangeDoesEndAtBlockBoundary( range, root ) ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
current = getStartBlockOfRange( range );
|
current = getStartBlockOfRange( range, root );
|
||||||
if ( !current ) {
|
if ( !current ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// In case inline data has somehow got between blocks.
|
// In case inline data has somehow got between blocks.
|
||||||
fixContainer( current.parentNode );
|
fixContainer( current.parentNode, root );
|
||||||
// Now get next block
|
// Now get next block
|
||||||
next = getNextBlock( current );
|
next = getNextBlock( current, root );
|
||||||
// Must not be at the very end of the text area.
|
// Must not be at the very end of the text area.
|
||||||
if ( next ) {
|
if ( next ) {
|
||||||
// If not editable, just delete whole block.
|
// If not editable, just delete whole block.
|
||||||
|
@ -345,7 +349,7 @@ var keyHandlers = {
|
||||||
next = next.parentNode;
|
next = next.parentNode;
|
||||||
}
|
}
|
||||||
if ( next && ( next = next.nextSibling ) ) {
|
if ( next && ( next = next.nextSibling ) ) {
|
||||||
mergeContainers( next );
|
mergeContainers( next, root );
|
||||||
}
|
}
|
||||||
self.setSelection( range );
|
self.setSelection( range );
|
||||||
self._updatePath( range, true );
|
self._updatePath( range, true );
|
||||||
|
@ -358,7 +362,7 @@ var keyHandlers = {
|
||||||
// delete it ourselves, because the browser won't if it is not
|
// delete it ourselves, because the browser won't if it is not
|
||||||
// inline.
|
// inline.
|
||||||
originalRange = range.cloneRange();
|
originalRange = range.cloneRange();
|
||||||
moveRangeBoundariesUpTree( range, self._body );
|
moveRangeBoundariesUpTree( range, self._root );
|
||||||
cursorContainer = range.endContainer;
|
cursorContainer = range.endContainer;
|
||||||
cursorOffset = range.endOffset;
|
cursorOffset = range.endOffset;
|
||||||
if ( cursorContainer.nodeType === ELEMENT_NODE ) {
|
if ( cursorContainer.nodeType === ELEMENT_NODE ) {
|
||||||
|
@ -376,11 +380,12 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tab: function ( self, event, range ) {
|
tab: function ( self, event, range ) {
|
||||||
|
var root = self._root;
|
||||||
var node, parent;
|
var node, parent;
|
||||||
self._removeZWS();
|
self._removeZWS();
|
||||||
// If no selection and at start of block
|
// If no selection and at start of block
|
||||||
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range ) ) {
|
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) {
|
||||||
node = getStartBlockOfRange( range );
|
node = getStartBlockOfRange( range, root );
|
||||||
// Iterate through the block's parents
|
// Iterate through the block's parents
|
||||||
while ( parent = node.parentNode ) {
|
while ( parent = node.parentNode ) {
|
||||||
// If we find a UL or OL (so are in a list, node must be an LI)
|
// If we find a UL or OL (so are in a list, node must be an LI)
|
||||||
|
@ -398,12 +403,15 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'shift-tab': function ( self, event, range ) {
|
'shift-tab': function ( self, event, range ) {
|
||||||
|
var root = self._root;
|
||||||
|
var node;
|
||||||
self._removeZWS();
|
self._removeZWS();
|
||||||
// If no selection and at start of block
|
// If no selection and at start of block
|
||||||
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range ) ) {
|
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) {
|
||||||
// Break list
|
// Break list
|
||||||
var node = range.startContainer;
|
node = range.startContainer;
|
||||||
if ( getNearest( node, 'UL' ) || getNearest( node, 'OL' ) ) {
|
if ( getNearest( node, root, 'UL' ) ||
|
||||||
|
getNearest( node, root, 'OL' ) ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
self.modifyBlocks( decreaseListLevel, range );
|
self.modifyBlocks( decreaseListLevel, range );
|
||||||
}
|
}
|
||||||
|
|
145
source/Node.js
145
source/Node.js
|
@ -20,27 +20,6 @@ function every ( nodeList, fn ) {
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
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 !isLeaf( node ) && (
|
|
||||||
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 ) {
|
function isLeaf ( node ) {
|
||||||
return node.nodeType === ELEMENT_NODE &&
|
return node.nodeType === ELEMENT_NODE &&
|
||||||
!!leafNodeNames[ node.nodeName ];
|
!!leafNodeNames[ node.nodeName ];
|
||||||
|
@ -59,48 +38,80 @@ function isContainer ( node ) {
|
||||||
!isInline( node ) && !isBlock( node );
|
!isInline( node ) && !isBlock( node );
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBlockWalker ( node ) {
|
function getBlockWalker ( node, root ) {
|
||||||
var doc = node.ownerDocument,
|
var walker = new TreeWalker( root, SHOW_ELEMENT, isBlock, false );
|
||||||
walker = new TreeWalker(
|
|
||||||
doc.body, SHOW_ELEMENT, isBlock, false );
|
|
||||||
walker.currentNode = node;
|
walker.currentNode = node;
|
||||||
return walker;
|
return walker;
|
||||||
}
|
}
|
||||||
|
function getPreviousBlock ( node, root ) {
|
||||||
|
node = getBlockWalker( node, root ).previousNode();
|
||||||
|
return node !== root ? node : null;
|
||||||
|
}
|
||||||
|
function getNextBlock ( node, root ) {
|
||||||
|
node = getBlockWalker( node, root ).nextNode();
|
||||||
|
return node !== root ? node : null;
|
||||||
|
}
|
||||||
|
|
||||||
function getPreviousBlock ( node ) {
|
function areAlike ( node, node2 ) {
|
||||||
return getBlockWalker( node ).previousNode();
|
return !isLeaf( node ) && (
|
||||||
|
node.nodeType === node2.nodeType &&
|
||||||
|
node.nodeName === node2.nodeName &&
|
||||||
|
node.className === node2.className &&
|
||||||
|
( ( !node.style && !node2.style ) ||
|
||||||
|
node.style.cssText === node2.style.cssText )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
function getNextBlock ( node ) {
|
function hasTagAttributes ( node, tag, attributes ) {
|
||||||
return getBlockWalker( node ).nextNode();
|
if ( node.nodeName !== tag ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for ( var attr in attributes ) {
|
||||||
|
if ( node.getAttribute( attr ) !== attributes[ attr ] ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
function getNearest ( node, tag, attributes ) {
|
function getNearest ( node, root, tag, attributes ) {
|
||||||
do {
|
while ( node && node !== root ) {
|
||||||
if ( hasTagAttributes( node, tag, attributes ) ) {
|
if ( hasTagAttributes( node, tag, attributes ) ) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
} while ( node = node.parentNode );
|
node = node.parentNode;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function isOrContains ( parent, node ) {
|
||||||
|
while ( node ) {
|
||||||
|
if ( node === parent ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function getPath ( node ) {
|
function getPath ( node, root ) {
|
||||||
var parent = node.parentNode,
|
var parent = node.parentNode,
|
||||||
path, id, className, classNames, dir;
|
path, id, className, classNames, dir;
|
||||||
if ( !parent || node.nodeType !== ELEMENT_NODE ) {
|
if ( node === root ) {
|
||||||
path = parent ? getPath( parent ) : '';
|
path = '';
|
||||||
} else {
|
} else {
|
||||||
path = getPath( parent );
|
path = getPath( parent, root );
|
||||||
path += ( path ? '>' : '' ) + node.nodeName;
|
if ( node.nodeType === ELEMENT_NODE ) {
|
||||||
if ( id = node.id ) {
|
path += ( path ? '>' : '' ) + node.nodeName;
|
||||||
path += '#' + id;
|
if ( id = node.id ) {
|
||||||
}
|
path += '#' + id;
|
||||||
if ( className = node.className.trim() ) {
|
}
|
||||||
classNames = className.split( /\s\s*/ );
|
if ( className = node.className.trim() ) {
|
||||||
classNames.sort();
|
classNames = className.split( /\s\s*/ );
|
||||||
path += '.';
|
classNames.sort();
|
||||||
path += classNames.join( '.' );
|
path += '.';
|
||||||
}
|
path += classNames.join( '.' );
|
||||||
if ( dir = node.dir ) {
|
}
|
||||||
path += '[dir=' + dir + ']';
|
if ( dir = node.dir ) {
|
||||||
|
path += '[dir=' + dir + ']';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
@ -158,16 +169,16 @@ function createElement ( doc, tag, props, children ) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixCursor ( node ) {
|
function fixCursor ( node, root ) {
|
||||||
// In Webkit and Gecko, block level elements are collapsed and
|
// In Webkit and Gecko, block level elements are collapsed and
|
||||||
// unfocussable if they have no content. To remedy this, a <BR> must be
|
// 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
|
// inserted. In Opera and IE, we just need a textnode in order for the
|
||||||
// cursor to appear.
|
// cursor to appear.
|
||||||
var doc = node.ownerDocument,
|
var doc = node.ownerDocument,
|
||||||
root = node,
|
originalNode = node,
|
||||||
fixer, child;
|
fixer, child;
|
||||||
|
|
||||||
if ( node.nodeName === 'BODY' ) {
|
if ( node === root ) {
|
||||||
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
||||||
fixer = getSquireInstance( doc ).createDefaultBlock();
|
fixer = getSquireInstance( doc ).createDefaultBlock();
|
||||||
if ( child ) {
|
if ( child ) {
|
||||||
|
@ -227,11 +238,11 @@ function fixCursor ( node ) {
|
||||||
node.appendChild( fixer );
|
node.appendChild( fixer );
|
||||||
}
|
}
|
||||||
|
|
||||||
return root;
|
return originalNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively examine container nodes and wrap any inline children.
|
// Recursively examine container nodes and wrap any inline children.
|
||||||
function fixContainer ( container ) {
|
function fixContainer ( container, root ) {
|
||||||
var children = container.childNodes,
|
var children = container.childNodes,
|
||||||
doc = container.ownerDocument,
|
doc = container.ownerDocument,
|
||||||
wrapper = null,
|
wrapper = null,
|
||||||
|
@ -254,7 +265,7 @@ function fixContainer ( container ) {
|
||||||
wrapper = createElement( doc,
|
wrapper = createElement( doc,
|
||||||
config.blockTag, config.blockAttributes );
|
config.blockTag, config.blockAttributes );
|
||||||
}
|
}
|
||||||
fixCursor( wrapper );
|
fixCursor( wrapper, root );
|
||||||
if ( isBR ) {
|
if ( isBR ) {
|
||||||
container.replaceChild( wrapper, child );
|
container.replaceChild( wrapper, child );
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,20 +276,21 @@ function fixContainer ( container ) {
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
}
|
}
|
||||||
if ( isContainer( child ) ) {
|
if ( isContainer( child ) ) {
|
||||||
fixContainer( child );
|
fixContainer( child, root );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( wrapper ) {
|
if ( wrapper ) {
|
||||||
container.appendChild( fixCursor( wrapper ) );
|
container.appendChild( fixCursor( wrapper, root ) );
|
||||||
}
|
}
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
function split ( node, offset, stopNode ) {
|
function split ( node, offset, stopNode, root ) {
|
||||||
var nodeType = node.nodeType,
|
var nodeType = node.nodeType,
|
||||||
parent, clone, next;
|
parent, clone, next;
|
||||||
if ( nodeType === TEXT_NODE && node !== stopNode ) {
|
if ( nodeType === TEXT_NODE && node !== stopNode ) {
|
||||||
return split( node.parentNode, node.splitText( offset ), stopNode );
|
return split(
|
||||||
|
node.parentNode, node.splitText( offset ), stopNode, root );
|
||||||
}
|
}
|
||||||
if ( nodeType === ELEMENT_NODE ) {
|
if ( nodeType === ELEMENT_NODE ) {
|
||||||
if ( typeof( offset ) === 'number' ) {
|
if ( typeof( offset ) === 'number' ) {
|
||||||
|
@ -301,7 +313,8 @@ function split ( node, offset, stopNode ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintain li numbering if inside a quote.
|
// Maintain li numbering if inside a quote.
|
||||||
if ( node.nodeName === 'OL' && getNearest( node, 'BLOCKQUOTE' ) ) {
|
if ( node.nodeName === 'OL' &&
|
||||||
|
getNearest( node, root, 'BLOCKQUOTE' ) ) {
|
||||||
clone.start = ( +node.start || 1 ) + node.childNodes.length - 1;
|
clone.start = ( +node.start || 1 ) + node.childNodes.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,8 +322,8 @@ function split ( node, offset, stopNode ) {
|
||||||
// of a node lower down the tree!
|
// of a node lower down the tree!
|
||||||
|
|
||||||
// We need something in the element in order for the cursor to appear.
|
// We need something in the element in order for the cursor to appear.
|
||||||
fixCursor( node );
|
fixCursor( node, root );
|
||||||
fixCursor( clone );
|
fixCursor( clone, root );
|
||||||
|
|
||||||
// Inject clone after original node
|
// Inject clone after original node
|
||||||
if ( next = node.nextSibling ) {
|
if ( next = node.nextSibling ) {
|
||||||
|
@ -320,7 +333,7 @@ function split ( node, offset, stopNode ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep on splitting up the tree
|
// Keep on splitting up the tree
|
||||||
return split( parent, clone, stopNode );
|
return split( parent, clone, stopNode, root );
|
||||||
}
|
}
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
@ -425,7 +438,7 @@ function mergeWithBlock ( block, next, range ) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeContainers ( node ) {
|
function mergeContainers ( node, root ) {
|
||||||
var prev = node.previousSibling,
|
var prev = node.previousSibling,
|
||||||
first = node.firstChild,
|
first = node.firstChild,
|
||||||
doc = node.ownerDocument,
|
doc = node.ownerDocument,
|
||||||
|
@ -451,14 +464,14 @@ function mergeContainers ( node ) {
|
||||||
needsFix = !isContainer( node );
|
needsFix = !isContainer( node );
|
||||||
prev.appendChild( empty( node ) );
|
prev.appendChild( empty( node ) );
|
||||||
if ( needsFix ) {
|
if ( needsFix ) {
|
||||||
fixContainer( prev );
|
fixContainer( prev, root );
|
||||||
}
|
}
|
||||||
if ( first ) {
|
if ( first ) {
|
||||||
mergeContainers( first );
|
mergeContainers( first, root );
|
||||||
}
|
}
|
||||||
} else if ( isListItem ) {
|
} else if ( isListItem ) {
|
||||||
prev = createElement( doc, 'DIV' );
|
prev = createElement( doc, 'DIV' );
|
||||||
node.insertBefore( prev, first );
|
node.insertBefore( prev, first );
|
||||||
fixCursor( prev );
|
fixCursor( prev, root );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ var insertNodeInRange = function ( range, node ) {
|
||||||
range.setEnd( endContainer, endOffset );
|
range.setEnd( endContainer, endOffset );
|
||||||
};
|
};
|
||||||
|
|
||||||
var extractContentsOfRange = function ( range, common ) {
|
var extractContentsOfRange = function ( range, common, root ) {
|
||||||
var startContainer = range.startContainer,
|
var startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset,
|
startOffset = range.startOffset,
|
||||||
endContainer = range.endContainer,
|
endContainer = range.endContainer,
|
||||||
|
@ -94,8 +94,8 @@ var extractContentsOfRange = function ( range, common ) {
|
||||||
common = common.parentNode;
|
common = common.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
var endNode = split( endContainer, endOffset, common ),
|
var endNode = split( endContainer, endOffset, common, root ),
|
||||||
startNode = split( startContainer, startOffset, common ),
|
startNode = split( startContainer, startOffset, common, root ),
|
||||||
frag = common.ownerDocument.createDocumentFragment(),
|
frag = common.ownerDocument.createDocumentFragment(),
|
||||||
next, before, after;
|
next, before, after;
|
||||||
|
|
||||||
|
@ -127,12 +127,12 @@ var extractContentsOfRange = function ( range, common ) {
|
||||||
range.setStart( startContainer, startOffset );
|
range.setStart( startContainer, startOffset );
|
||||||
range.collapse( true );
|
range.collapse( true );
|
||||||
|
|
||||||
fixCursor( common );
|
fixCursor( common, root );
|
||||||
|
|
||||||
return frag;
|
return frag;
|
||||||
};
|
};
|
||||||
|
|
||||||
var deleteContentsOfRange = function ( range ) {
|
var deleteContentsOfRange = function ( range, root ) {
|
||||||
// Move boundaries up as much as possible to reduce need to split.
|
// Move boundaries up as much as possible to reduce need to split.
|
||||||
// But we need to check whether we've moved the boundary outside of a
|
// But we need to check whether we've moved the boundary outside of a
|
||||||
// block. If so, the entire block will be removed, so we shouldn't merge
|
// block. If so, the entire block will be removed, so we shouldn't merge
|
||||||
|
@ -145,7 +145,7 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
( isInline( endBlock ) || isBlock( endBlock ) );
|
( isInline( endBlock ) || isBlock( endBlock ) );
|
||||||
|
|
||||||
// Remove selected range
|
// Remove selected range
|
||||||
var frag = extractContentsOfRange( range );
|
var frag = extractContentsOfRange( range, null, root );
|
||||||
|
|
||||||
// Move boundaries back down tree so that they are inside the blocks.
|
// Move boundaries back down tree so that they are inside the blocks.
|
||||||
// If we don't do this, the range may be collapsed to a point between
|
// If we don't do this, the range may be collapsed to a point between
|
||||||
|
@ -154,8 +154,8 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
|
|
||||||
// If we split into two different blocks, merge the blocks.
|
// If we split into two different blocks, merge the blocks.
|
||||||
if ( needsMerge ) {
|
if ( needsMerge ) {
|
||||||
startBlock = getStartBlockOfRange( range );
|
startBlock = getStartBlockOfRange( range, root );
|
||||||
endBlock = getEndBlockOfRange( range );
|
endBlock = getEndBlockOfRange( range, root );
|
||||||
if ( startBlock && endBlock && startBlock !== endBlock ) {
|
if ( startBlock && endBlock && startBlock !== endBlock ) {
|
||||||
mergeWithBlock( startBlock, endBlock, range );
|
mergeWithBlock( startBlock, endBlock, range );
|
||||||
}
|
}
|
||||||
|
@ -163,15 +163,14 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
|
|
||||||
// Ensure block has necessary children
|
// Ensure block has necessary children
|
||||||
if ( startBlock ) {
|
if ( startBlock ) {
|
||||||
fixCursor( startBlock );
|
fixCursor( startBlock, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure body has a block-level element in it.
|
// Ensure root has a block-level element in it.
|
||||||
var body = range.endContainer.ownerDocument.body,
|
var child = root.firstChild;
|
||||||
child = body.firstChild;
|
|
||||||
if ( !child || child.nodeName === 'BR' ) {
|
if ( !child || child.nodeName === 'BR' ) {
|
||||||
fixCursor( body );
|
fixCursor( root, root );
|
||||||
range.selectNodeContents( body.firstChild );
|
range.selectNodeContents( root.firstChild );
|
||||||
} else {
|
} else {
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
}
|
}
|
||||||
|
@ -180,7 +179,7 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
var insertTreeFragmentIntoRange = function ( range, frag ) {
|
var insertTreeFragmentIntoRange = function ( range, frag, root ) {
|
||||||
// Check if it's all inline content
|
// Check if it's all inline content
|
||||||
var allInline = true,
|
var allInline = true,
|
||||||
children = frag.childNodes,
|
children = frag.childNodes,
|
||||||
|
@ -194,7 +193,7 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
||||||
|
|
||||||
// Delete any selected content
|
// Delete any selected content
|
||||||
if ( !range.collapsed ) {
|
if ( !range.collapsed ) {
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move range down into text nodes
|
// Move range down into text nodes
|
||||||
|
@ -206,11 +205,14 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
} else {
|
} else {
|
||||||
// Otherwise...
|
// Otherwise...
|
||||||
// 1. Split up to blockquote (if a parent) or body
|
// 1. Split up to blockquote (if a parent) or root
|
||||||
var splitPoint = range.startContainer,
|
var splitPoint = range.startContainer,
|
||||||
nodeAfterSplit = split( splitPoint, range.startOffset,
|
nodeAfterSplit = split(
|
||||||
getNearest( splitPoint.parentNode, 'BLOCKQUOTE' ) ||
|
splitPoint,
|
||||||
splitPoint.ownerDocument.body ),
|
range.startOffset,
|
||||||
|
getNearest( splitPoint.parentNode, root, 'BLOCKQUOTE' ) || root,
|
||||||
|
root
|
||||||
|
),
|
||||||
nodeBeforeSplit = nodeAfterSplit.previousSibling,
|
nodeBeforeSplit = nodeAfterSplit.previousSibling,
|
||||||
startContainer = nodeBeforeSplit,
|
startContainer = nodeBeforeSplit,
|
||||||
startOffset = startContainer.childNodes.length,
|
startOffset = startContainer.childNodes.length,
|
||||||
|
@ -246,15 +248,15 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
||||||
|
|
||||||
// 3. Fix cursor then insert block(s) in the fragment
|
// 3. Fix cursor then insert block(s) in the fragment
|
||||||
node = frag;
|
node = frag;
|
||||||
while ( node = getNextBlock( node ) ) {
|
while ( node = getNextBlock( node, root ) ) {
|
||||||
fixCursor( node );
|
fixCursor( node, root );
|
||||||
}
|
}
|
||||||
parent.insertBefore( frag, nodeAfterSplit );
|
parent.insertBefore( frag, nodeAfterSplit );
|
||||||
|
|
||||||
// 4. Remove empty nodes created either side of split, then
|
// 4. Remove empty nodes created either side of split, then
|
||||||
// merge containers at the edges.
|
// merge containers at the edges.
|
||||||
next = nodeBeforeSplit.nextSibling;
|
next = nodeBeforeSplit.nextSibling;
|
||||||
node = getPreviousBlock( next );
|
node = getPreviousBlock( next, root );
|
||||||
if ( !/\S/.test( node.textContent ) ) {
|
if ( !/\S/.test( node.textContent ) ) {
|
||||||
do {
|
do {
|
||||||
parent = node.parentNode;
|
parent = node.parentNode;
|
||||||
|
@ -273,12 +275,12 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
||||||
}
|
}
|
||||||
// Merge inserted containers with edges of split
|
// Merge inserted containers with edges of split
|
||||||
if ( isContainer( next ) ) {
|
if ( isContainer( next ) ) {
|
||||||
mergeContainers( next );
|
mergeContainers( next, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
prev = nodeAfterSplit.previousSibling;
|
prev = nodeAfterSplit.previousSibling;
|
||||||
node = isBlock( nodeAfterSplit ) ?
|
node = isBlock( nodeAfterSplit ) ?
|
||||||
nodeAfterSplit : getNextBlock( nodeAfterSplit );
|
nodeAfterSplit : getNextBlock( nodeAfterSplit, root );
|
||||||
if ( !/\S/.test( node.textContent ) ) {
|
if ( !/\S/.test( node.textContent ) ) {
|
||||||
do {
|
do {
|
||||||
parent = node.parentNode;
|
parent = node.parentNode;
|
||||||
|
@ -296,7 +298,7 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
||||||
}
|
}
|
||||||
// Merge inserted containers with edges of split
|
// Merge inserted containers with edges of split
|
||||||
if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
|
if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
|
||||||
mergeContainers( nodeAfterSplit );
|
mergeContainers( nodeAfterSplit, root );
|
||||||
}
|
}
|
||||||
|
|
||||||
range.setStart( startContainer, startOffset );
|
range.setStart( startContainer, startOffset );
|
||||||
|
@ -408,18 +410,18 @@ var moveRangeBoundariesUpTree = function ( range, common ) {
|
||||||
|
|
||||||
// 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.
|
||||||
var getStartBlockOfRange = function ( range ) {
|
var getStartBlockOfRange = function ( range, root ) {
|
||||||
var container = range.startContainer,
|
var container = range.startContainer,
|
||||||
block;
|
block;
|
||||||
|
|
||||||
// If inline, get the containing block.
|
// If inline, get the containing block.
|
||||||
if ( isInline( container ) ) {
|
if ( isInline( container ) ) {
|
||||||
block = getPreviousBlock( container );
|
block = getPreviousBlock( container, root );
|
||||||
} else if ( isBlock( container ) ) {
|
} else if ( isBlock( container ) ) {
|
||||||
block = container;
|
block = container;
|
||||||
} else {
|
} else {
|
||||||
block = getNodeBefore( container, range.startOffset );
|
block = getNodeBefore( container, range.startOffset );
|
||||||
block = getNextBlock( block );
|
block = getNextBlock( block, root );
|
||||||
}
|
}
|
||||||
// Check the block actually intersects the range
|
// Check the block actually intersects the range
|
||||||
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
||||||
|
@ -427,25 +429,24 @@ var getStartBlockOfRange = function ( range ) {
|
||||||
|
|
||||||
// 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.
|
||||||
var getEndBlockOfRange = function ( range ) {
|
var getEndBlockOfRange = function ( range, root ) {
|
||||||
var container = range.endContainer,
|
var container = range.endContainer,
|
||||||
block, child;
|
block, child;
|
||||||
|
|
||||||
// If inline, get the containing block.
|
// If inline, get the containing block.
|
||||||
if ( isInline( container ) ) {
|
if ( isInline( container ) ) {
|
||||||
block = getPreviousBlock( container );
|
block = getPreviousBlock( container, root );
|
||||||
} else if ( isBlock( container ) ) {
|
} else if ( isBlock( container ) ) {
|
||||||
block = container;
|
block = container;
|
||||||
} else {
|
} else {
|
||||||
block = getNodeAfter( container, range.endOffset );
|
block = getNodeAfter( container, range.endOffset );
|
||||||
if ( !block ) {
|
if ( !block ) {
|
||||||
block = container.ownerDocument.body;
|
block = root;
|
||||||
while ( child = block.lastChild ) {
|
while ( child = block.lastChild ) {
|
||||||
block = child;
|
block = child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
block = getPreviousBlock( block );
|
block = getPreviousBlock( block, root );
|
||||||
|
|
||||||
}
|
}
|
||||||
// Check the block actually intersects the range
|
// Check the block actually intersects the range
|
||||||
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
||||||
|
@ -460,7 +461,7 @@ var contentWalker = new TreeWalker( null,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var rangeDoesStartAtBlockBoundary = function ( range ) {
|
var rangeDoesStartAtBlockBoundary = function ( range, root ) {
|
||||||
var startContainer = range.startContainer,
|
var startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset;
|
startOffset = range.startOffset;
|
||||||
|
|
||||||
|
@ -476,12 +477,12 @@ var rangeDoesStartAtBlockBoundary = function ( range ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, look for any previous content in the same block.
|
// Otherwise, look for any previous content in the same block.
|
||||||
contentWalker.root = getStartBlockOfRange( range );
|
contentWalker.root = getStartBlockOfRange( range, root );
|
||||||
|
|
||||||
return !contentWalker.previousNode();
|
return !contentWalker.previousNode();
|
||||||
};
|
};
|
||||||
|
|
||||||
var rangeDoesEndAtBlockBoundary = function ( range ) {
|
var rangeDoesEndAtBlockBoundary = function ( range, root ) {
|
||||||
var endContainer = range.endContainer,
|
var endContainer = range.endContainer,
|
||||||
endOffset = range.endOffset,
|
endOffset = range.endOffset,
|
||||||
length;
|
length;
|
||||||
|
@ -500,14 +501,14 @@ var rangeDoesEndAtBlockBoundary = function ( range ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, look for any further content in the same block.
|
// Otherwise, look for any further content in the same block.
|
||||||
contentWalker.root = getEndBlockOfRange( range );
|
contentWalker.root = getEndBlockOfRange( range, root );
|
||||||
|
|
||||||
return !contentWalker.nextNode();
|
return !contentWalker.nextNode();
|
||||||
};
|
};
|
||||||
|
|
||||||
var expandRangeToBlockBoundaries = function ( range ) {
|
var expandRangeToBlockBoundaries = function ( range, root ) {
|
||||||
var start = getStartBlockOfRange( range ),
|
var start = getStartBlockOfRange( range, root ),
|
||||||
end = getEndBlockOfRange( range ),
|
end = getEndBlockOfRange( range, root ),
|
||||||
parent;
|
parent;
|
||||||
|
|
||||||
if ( start && end ) {
|
if ( start && end ) {
|
||||||
|
|
Loading…
Reference in a new issue