0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2025-01-03 05:00:13 -05:00

Fix erroneous white-space removal when pasting inline fragment.

This commit is contained in:
Neil Jenkins 2015-06-22 09:59:34 +07:00
parent def56193d4
commit 2a8fb93cd9
6 changed files with 174 additions and 58 deletions

View file

@ -7,6 +7,7 @@
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
var TEXT_NODE = 3; // Node.TEXT_NODE;
var DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
@ -140,7 +141,35 @@ TreeWalker.prototype.previousNode = function () {
}
};
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|FN|EL)|EM|FONT|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TR(?:IKE|ONG)|MALL|AMP)?|U|VAR|WBR)$/;
// Previous node in post-order.
TreeWalker.prototype.previousPONode = function () {
var current = this.currentNode,
root = this.root,
nodeType = this.nodeType,
filter = this.filter,
node;
while ( true ) {
node = current.lastChild;
while ( !node && current ) {
if ( current === root ) {
break;
}
node = current.previousSibling;
if ( !node ) { current = current.parentNode; }
}
if ( !node ) {
return null;
}
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
filter( node ) ) {
this.currentNode = node;
return node;
}
current = node;
}
};
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|U|VAR|WBR)$/;
var leafNodeNames = {
BR: 1,
@ -189,11 +218,13 @@ function isInline ( node ) {
return inlineNodeNames.test( node.nodeName );
}
function isBlock ( node ) {
return node.nodeType === ELEMENT_NODE &&
var type = node.nodeType;
return ( type === ELEMENT_NODE || type === DOCUMENT_FRAGMENT_NODE ) &&
!isInline( node ) && every( node.childNodes, isInline );
}
function isContainer ( node ) {
return node.nodeType === ELEMENT_NODE &&
var type = node.nodeType;
return ( type === ELEMENT_NODE || type === DOCUMENT_FRAGMENT_NODE ) &&
!isInline( node ) && !isBlock( node );
}
@ -1548,8 +1579,6 @@ keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|UL)$/;
var fontSizes = {
1: 10,
2: 13,
@ -1713,6 +1742,14 @@ var stylesRewriters = {
}
};
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|UL)$/;
var blacklist = /^(?:HEAD|META|STYLE)/;
var walker = new TreeWalker( null, SHOW_TEXT|SHOW_ELEMENT, function () {
return true;
});
/*
Two purposes:
@ -1722,17 +1759,14 @@ var stylesRewriters = {
*/
var cleanTree = function cleanTree ( node ) {
var children = node.childNodes,
blockParent, i, l, child, nodeName, nodeType, rewriter, childLength,
startsWithWS, endsWithWS, data;
nonInlineParent, i, l, child, nodeName, nodeType, rewriter, childLength,
startsWithWS, endsWithWS, data, sibling;
blockParent = node;
while ( isInline( blockParent ) ) {
blockParent = blockParent.parentNode;
nonInlineParent = node;
while ( isInline( nonInlineParent ) ) {
nonInlineParent = nonInlineParent.parentNode;
}
if ( !isBlock( blockParent ) ) {
blockParent = null;
}
contentWalker.root = blockParent;
walker.root = nonInlineParent;
for ( i = 0, l = children.length; i < l; i += 1 ) {
child = children[i];
@ -1743,16 +1777,15 @@ var cleanTree = function cleanTree ( node ) {
childLength = child.childNodes.length;
if ( rewriter ) {
child = rewriter( child, node );
} else if ( !allowedBlock.test( nodeName ) &&
!isInline( child ) ) {
} else if ( blacklist.test( nodeName ) ) {
node.removeChild( child );
i -= 1;
if ( nodeName === 'HEAD' || nodeName === 'STYLE' ) {
node.removeChild( child );
l -= 1;
} else {
l += childLength - 1;
node.replaceChild( empty( child ), child );
}
l -= 1;
continue;
} else if ( !allowedBlock.test( nodeName ) && !isInline( child ) ) {
i -= 1;
l += childLength - 1;
node.replaceChild( empty( child ), child );
continue;
}
if ( childLength ) {
@ -1769,16 +1802,41 @@ var cleanTree = function cleanTree ( node ) {
if ( !startsWithWS && !endsWithWS ) {
continue;
}
// Iterate through the nodes; if we hit some other content
// before the start of a new block we don't trim
if ( startsWithWS ) {
contentWalker.currentNode = child;
if ( !blockParent || !contentWalker.previousNode() ) {
walker.currentNode = child;
while ( sibling = walker.previousPONode() ) {
nodeName = sibling.nodeName;
if ( nodeName === 'IMG' ||
( nodeName === '#text' &&
/\S/.test( sibling.data ) ) ) {
break;
}
if ( !isInline( sibling ) ) {
sibling = null;
break;
}
}
if ( !sibling ) {
data = data.replace( /^\s+/g, '' );
}
}
if ( endsWithWS ) {
contentWalker.currentNode = child;
if ( !blockParent || !contentWalker.nextNode() ) {
data = data.replace( /\s+$/g, '' );
walker.currentNode = child;
while ( sibling = walker.nextNode() ) {
if ( nodeName === 'IMG' ||
( nodeName === '#text' &&
/\S/.test( sibling.data ) ) ) {
break;
}
if ( !isInline( sibling ) ) {
sibling = null;
break;
}
}
if ( !sibling ) {
data = data.replace( /^\s+/g, '' );
}
}
if ( data ) {

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,5 @@
/*jshint strict:false, undef:false, unused:false */
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|UL)$/;
var fontSizes = {
1: 10,
2: 13,
@ -165,6 +163,14 @@ var stylesRewriters = {
}
};
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|UL)$/;
var blacklist = /^(?:HEAD|META|STYLE)/;
var walker = new TreeWalker( null, SHOW_TEXT|SHOW_ELEMENT, function () {
return true;
});
/*
Two purposes:
@ -174,17 +180,14 @@ var stylesRewriters = {
*/
var cleanTree = function cleanTree ( node ) {
var children = node.childNodes,
blockParent, i, l, child, nodeName, nodeType, rewriter, childLength,
startsWithWS, endsWithWS, data;
nonInlineParent, i, l, child, nodeName, nodeType, rewriter, childLength,
startsWithWS, endsWithWS, data, sibling;
blockParent = node;
while ( isInline( blockParent ) ) {
blockParent = blockParent.parentNode;
nonInlineParent = node;
while ( isInline( nonInlineParent ) ) {
nonInlineParent = nonInlineParent.parentNode;
}
if ( !isBlock( blockParent ) ) {
blockParent = null;
}
contentWalker.root = blockParent;
walker.root = nonInlineParent;
for ( i = 0, l = children.length; i < l; i += 1 ) {
child = children[i];
@ -195,16 +198,15 @@ var cleanTree = function cleanTree ( node ) {
childLength = child.childNodes.length;
if ( rewriter ) {
child = rewriter( child, node );
} else if ( !allowedBlock.test( nodeName ) &&
!isInline( child ) ) {
} else if ( blacklist.test( nodeName ) ) {
node.removeChild( child );
i -= 1;
if ( nodeName === 'HEAD' || nodeName === 'STYLE' ) {
node.removeChild( child );
l -= 1;
} else {
l += childLength - 1;
node.replaceChild( empty( child ), child );
}
l -= 1;
continue;
} else if ( !allowedBlock.test( nodeName ) && !isInline( child ) ) {
i -= 1;
l += childLength - 1;
node.replaceChild( empty( child ), child );
continue;
}
if ( childLength ) {
@ -221,16 +223,41 @@ var cleanTree = function cleanTree ( node ) {
if ( !startsWithWS && !endsWithWS ) {
continue;
}
// Iterate through the nodes; if we hit some other content
// before the start of a new block we don't trim
if ( startsWithWS ) {
contentWalker.currentNode = child;
if ( !blockParent || !contentWalker.previousNode() ) {
walker.currentNode = child;
while ( sibling = walker.previousPONode() ) {
nodeName = sibling.nodeName;
if ( nodeName === 'IMG' ||
( nodeName === '#text' &&
/\S/.test( sibling.data ) ) ) {
break;
}
if ( !isInline( sibling ) ) {
sibling = null;
break;
}
}
if ( !sibling ) {
data = data.replace( /^\s+/g, '' );
}
}
if ( endsWithWS ) {
contentWalker.currentNode = child;
if ( !blockParent || !contentWalker.nextNode() ) {
data = data.replace( /\s+$/g, '' );
walker.currentNode = child;
while ( sibling = walker.nextNode() ) {
if ( nodeName === 'IMG' ||
( nodeName === '#text' &&
/\S/.test( sibling.data ) ) ) {
break;
}
if ( !isInline( sibling ) ) {
sibling = null;
break;
}
}
if ( !sibling ) {
data = data.replace( /^\s+/g, '' );
}
}
if ( data ) {

View file

@ -3,6 +3,7 @@
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
var TEXT_NODE = 3; // Node.TEXT_NODE;
var DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;

View file

@ -1,6 +1,6 @@
/*jshint strict:false, undef:false, unused:false */
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|FN|EL)|EM|FONT|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TR(?:IKE|ONG)|MALL|AMP)?|U|VAR|WBR)$/;
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|U|VAR|WBR)$/;
var leafNodeNames = {
BR: 1,
@ -49,11 +49,13 @@ function isInline ( node ) {
return inlineNodeNames.test( node.nodeName );
}
function isBlock ( node ) {
return node.nodeType === ELEMENT_NODE &&
var type = node.nodeType;
return ( type === ELEMENT_NODE || type === DOCUMENT_FRAGMENT_NODE ) &&
!isInline( node ) && every( node.childNodes, isInline );
}
function isContainer ( node ) {
return node.nodeType === ELEMENT_NODE &&
var type = node.nodeType;
return ( type === ELEMENT_NODE || type === DOCUMENT_FRAGMENT_NODE ) &&
!isInline( node ) && !isBlock( node );
}

View file

@ -88,3 +88,31 @@ TreeWalker.prototype.previousNode = function () {
current = node;
}
};
// Previous node in post-order.
TreeWalker.prototype.previousPONode = function () {
var current = this.currentNode,
root = this.root,
nodeType = this.nodeType,
filter = this.filter,
node;
while ( true ) {
node = current.lastChild;
while ( !node && current ) {
if ( current === root ) {
break;
}
node = current.previousSibling;
if ( !node ) { current = current.parentNode; }
}
if ( !node ) {
return null;
}
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
filter( node ) ) {
this.currentNode = node;
return node;
}
current = node;
}
};