0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2024-12-22 07:13:08 -05:00

Fix formatting commands when selection has no text nodes

Fixes #60
This commit is contained in:
Neil Jenkins 2015-04-08 15:33:27 +07:00
parent 1b9606452e
commit e6ae25b589
3 changed files with 81 additions and 57 deletions

View file

@ -1700,7 +1700,7 @@ proto._addFormat = function ( tag, attributes, range ) {
// If the range is collapsed we simply insert the node by wrapping
// it round the range and focus it.
var el, walker, startContainer, endContainer, startOffset, endOffset,
textNode, needsFormat;
node, needsFormat;
if ( range.collapsed ) {
el = fixCursor( this.createElement( tag, attributes ) );
@ -1712,16 +1712,21 @@ proto._addFormat = function ( tag, attributes, range ) {
// partially selected nodes) and if they're not already formatted
// correctly we wrap them in the appropriate tag.
else {
// We don't want to apply formatting twice so we check each text
// node to see if it has an ancestor with the formatting already.
// Create an iterator to walk over all the text nodes under this
// ancestor which are in the range and not already formatted
// correctly.
//
// In Blink/WebKit, empty blocks may have no text nodes, just a <br>.
// Therefore we wrap this in the tag as well, as this will then cause it
// to apply when the user types something in the block, which is
// presumably what was intended.
walker = new TreeWalker(
range.commonAncestorContainer,
SHOW_TEXT,
SHOW_TEXT|SHOW_ELEMENT,
function ( node ) {
return isNodeContainedInRange( range, node, true );
return ( node.nodeType === TEXT_NODE ||
node.nodeName === 'BR' ) &&
isNodeContainedInRange( range, node, true );
},
false
);
@ -1733,41 +1738,53 @@ proto._addFormat = function ( tag, attributes, range ) {
endContainer = range.endContainer;
endOffset = range.endOffset;
// Make sure we start inside a text node.
// Make sure we start with a valid node.
walker.currentNode = startContainer;
if ( startContainer.nodeType !== TEXT_NODE ) {
if ( !walker.filter( startContainer ) ) {
startContainer = walker.nextNode();
startOffset = 0;
}
// If there are no interesting nodes in the selection, abort
if ( !startContainer ) {
return range;
}
do {
textNode = walker.currentNode;
needsFormat = !getNearest( textNode, tag, attributes );
node = walker.currentNode;
needsFormat = !getNearest( node, tag, attributes );
if ( needsFormat ) {
if ( textNode === endContainer &&
textNode.length > endOffset ) {
textNode.splitText( endOffset );
// <br> can never be a container node, so must have a text node
// if node == (end|start)Container
if ( node === endContainer && node.length > endOffset ) {
node.splitText( endOffset );
}
if ( textNode === startContainer && startOffset ) {
textNode = textNode.splitText( startOffset );
if ( node === startContainer && startOffset ) {
node = node.splitText( startOffset );
if ( endContainer === startContainer ) {
endContainer = textNode;
endContainer = node;
endOffset -= startOffset;
}
startContainer = textNode;
startContainer = node;
startOffset = 0;
}
el = this.createElement( tag, attributes );
replaceWith( textNode, el );
el.appendChild( textNode );
replaceWith( node, el );
el.appendChild( node );
}
} while ( walker.nextNode() );
// Make sure we finish inside a text node. Otherwise offset may have
// changed.
// If we don't finish inside a text node, offset may have changed.
if ( endContainer.nodeType !== TEXT_NODE ) {
endContainer = textNode;
endOffset = textNode.length;
if ( node.nodeType === TEXT_NODE ) {
endContainer = node;
endOffset = node.length;
} else {
// If <br>, we must have just wrapped it, so it must have only
// one child
endContainer = node.parentNode;
endOffset = 1;
}
}
// Now set the selection to as it was before
@ -2557,17 +2574,7 @@ var cleanupBRs = function ( root ) {
proto._ensureBottomLine = function () {
var body = this._body,
last;
// Safari (+others?) adds white-space text nodes to the end of <body>
// for no apparent reason. Remove them, since they're semantically
// meaningless.
while ( last = body.lastChild ) {
if ( last.nodeType === TEXT_NODE && !notWS.test( last.data ) ) {
body.removeChild( last );
} else {
break;
}
}
last = body.lastElementChild;
if ( !last || last.nodeName !== this.defaultBlockTag || !isBlock( last ) ) {
body.appendChild( this.createDefaultBlock() );
}

File diff suppressed because one or more lines are too long

View file

@ -610,7 +610,7 @@ proto._addFormat = function ( tag, attributes, range ) {
// If the range is collapsed we simply insert the node by wrapping
// it round the range and focus it.
var el, walker, startContainer, endContainer, startOffset, endOffset,
textNode, needsFormat;
node, needsFormat;
if ( range.collapsed ) {
el = fixCursor( this.createElement( tag, attributes ) );
@ -622,16 +622,21 @@ proto._addFormat = function ( tag, attributes, range ) {
// partially selected nodes) and if they're not already formatted
// correctly we wrap them in the appropriate tag.
else {
// We don't want to apply formatting twice so we check each text
// node to see if it has an ancestor with the formatting already.
// Create an iterator to walk over all the text nodes under this
// ancestor which are in the range and not already formatted
// correctly.
//
// In Blink/WebKit, empty blocks may have no text nodes, just a <br>.
// Therefore we wrap this in the tag as well, as this will then cause it
// to apply when the user types something in the block, which is
// presumably what was intended.
walker = new TreeWalker(
range.commonAncestorContainer,
SHOW_TEXT,
SHOW_TEXT|SHOW_ELEMENT,
function ( node ) {
return isNodeContainedInRange( range, node, true );
return ( node.nodeType === TEXT_NODE ||
node.nodeName === 'BR' ) &&
isNodeContainedInRange( range, node, true );
},
false
);
@ -643,41 +648,53 @@ proto._addFormat = function ( tag, attributes, range ) {
endContainer = range.endContainer;
endOffset = range.endOffset;
// Make sure we start inside a text node.
// Make sure we start with a valid node.
walker.currentNode = startContainer;
if ( startContainer.nodeType !== TEXT_NODE ) {
if ( !walker.filter( startContainer ) ) {
startContainer = walker.nextNode();
startOffset = 0;
}
// If there are no interesting nodes in the selection, abort
if ( !startContainer ) {
return range;
}
do {
textNode = walker.currentNode;
needsFormat = !getNearest( textNode, tag, attributes );
node = walker.currentNode;
needsFormat = !getNearest( node, tag, attributes );
if ( needsFormat ) {
if ( textNode === endContainer &&
textNode.length > endOffset ) {
textNode.splitText( endOffset );
// <br> can never be a container node, so must have a text node
// if node == (end|start)Container
if ( node === endContainer && node.length > endOffset ) {
node.splitText( endOffset );
}
if ( textNode === startContainer && startOffset ) {
textNode = textNode.splitText( startOffset );
if ( node === startContainer && startOffset ) {
node = node.splitText( startOffset );
if ( endContainer === startContainer ) {
endContainer = textNode;
endContainer = node;
endOffset -= startOffset;
}
startContainer = textNode;
startContainer = node;
startOffset = 0;
}
el = this.createElement( tag, attributes );
replaceWith( textNode, el );
el.appendChild( textNode );
replaceWith( node, el );
el.appendChild( node );
}
} while ( walker.nextNode() );
// Make sure we finish inside a text node. Otherwise offset may have
// changed.
// If we don't finish inside a text node, offset may have changed.
if ( endContainer.nodeType !== TEXT_NODE ) {
endContainer = textNode;
endOffset = textNode.length;
if ( node.nodeType === TEXT_NODE ) {
endContainer = node;
endOffset = node.length;
} else {
// If <br>, we must have just wrapped it, so it must have only
// one child
endContainer = node.parentNode;
endOffset = 1;
}
}
// Now set the selection to as it was before