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

Move cursor outside of <a> when inserting HTML

When links are pasted into the editor the cursor ends up at the
end of the text node inside the parent <a> element. Any text
entered is then appended to the end of the link text. Chrome
automatically moves the cursor after the end of <a> elements when
additional text is inserted, so this change enforces the same
behaviour in other browsers.

Resolves LP 55607264
https://app.liquidplanner.com/space/14822/projects/show/55607264
This commit is contained in:
Nicholas Wylie 2020-06-03 14:53:05 +10:00 committed by Neil Jenkins
parent 8c6e52c120
commit bb3cd05c64
5 changed files with 54 additions and 23 deletions

View file

@ -1096,6 +1096,9 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
} }
while ( true ) { while ( true ) {
if ( endContainer === endMax || endContainer === root ) {
break;
}
if ( maySkipBR && if ( maySkipBR &&
endContainer.nodeType !== TEXT_NODE && endContainer.nodeType !== TEXT_NODE &&
endContainer.childNodes[ endOffset ] && endContainer.childNodes[ endOffset ] &&
@ -1103,9 +1106,7 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
endOffset += 1; endOffset += 1;
maySkipBR = false; maySkipBR = false;
} }
if ( endContainer === endMax || if ( endOffset !== getLength( endContainer ) ) {
endContainer === root ||
endOffset !== getLength( endContainer ) ) {
break; break;
} }
parent = endContainer.parentNode; parent = endContainer.parentNode;
@ -1117,6 +1118,20 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
range.setEnd( endContainer, endOffset ); range.setEnd( endContainer, endOffset );
}; };
var moveRangeBoundaryOutOf = function ( range, nodeName, root ) {
var parent = getNearest( range.endContainer, root, 'A' );
if ( parent ) {
var clone = range.cloneRange();
parent = parent.parentNode;
moveRangeBoundariesUpTree( clone, parent, parent, root );
if ( clone.endContainer === parent ) {
range.setStart( clone.endContainer, clone.endOffset );
range.setEnd( clone.endContainer, clone.endOffset );
}
}
return range;
};
// 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, root ) { var getStartBlockOfRange = function ( range, root ) {
@ -1468,12 +1483,7 @@ var handleEnter = function ( self, shiftKey, range ) {
// just play it safe and insert a <br>. // just play it safe and insert a <br>.
if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) { if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
// If inside an <a>, move focus out // If inside an <a>, move focus out
parent = getNearest( range.endContainer, root, 'A' ); moveRangeBoundaryOutOf( range, 'A', root );
if ( parent ) {
parent = parent.parentNode;
moveRangeBoundariesUpTree( range, parent, parent, root );
range.collapse( false );
}
insertNodeInRange( range, self.createElement( 'BR' ) ); insertNodeInRange( range, self.createElement( 'BR' ) );
range.collapse( false ); range.collapse( false );
self.setSelection( range ); self.setSelection( range );
@ -4435,6 +4445,12 @@ proto.insertHTML = function ( html, isPaste ) {
this._docWasChanged(); this._docWasChanged();
} }
range.collapse( false ); range.collapse( false );
// After inserting the fragment, check whether the cursor is inside
// an <a> element and if so if there is an equivalent cursor
// position after the <a> element. If there is, move it there.
moveRangeBoundaryOutOf( range, 'A', root );
this._ensureBottomLine(); this._ensureBottomLine();
} }

File diff suppressed because one or more lines are too long

View file

@ -1898,6 +1898,12 @@ proto.insertHTML = function ( html, isPaste ) {
this._docWasChanged(); this._docWasChanged();
} }
range.collapse( false ); range.collapse( false );
// After inserting the fragment, check whether the cursor is inside
// an <a> element and if so if there is an equivalent cursor
// position after the <a> element. If there is, move it there.
moveRangeBoundaryOutOf( range, 'A', root );
this._ensureBottomLine(); this._ensureBottomLine();
} }

View file

@ -227,12 +227,7 @@ var handleEnter = function ( self, shiftKey, range ) {
// just play it safe and insert a <br>. // just play it safe and insert a <br>.
if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) { if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
// If inside an <a>, move focus out // If inside an <a>, move focus out
parent = getNearest( range.endContainer, root, 'A' ); moveRangeBoundaryOutOf( range, 'A', root );
if ( parent ) {
parent = parent.parentNode;
moveRangeBoundariesUpTree( range, parent, parent, root );
range.collapse( false );
}
insertNodeInRange( range, self.createElement( 'BR' ) ); insertNodeInRange( range, self.createElement( 'BR' ) );
range.collapse( false ); range.collapse( false );
self.setSelection( range ); self.setSelection( range );
@ -587,7 +582,7 @@ const changeIndentationLevel = function ( methodIfInQuote, methodIfInList ) {
return function ( self, event ) { return function ( self, event ) {
event.preventDefault(); event.preventDefault();
var path = self.getPath(); var path = self.getPath();
if ( /(?:^|>)BLOCKQUOTE/.test( path ) || if ( /(?:^|>)BLOCKQUOTE/.test( path ) ||
!/(?:^|>)[OU]L/.test( path ) ) { !/(?:^|>)[OU]L/.test( path ) ) {
self[ methodIfInQuote ](); self[ methodIfInQuote ]();
} else { } else {
@ -604,9 +599,9 @@ keyHandlers[ ctrlKey + 'shift-5' ] = mapKeyToFormat( 'SUB', { tag: 'SUP' } );
keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } ); keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } );
keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' ); keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' ); keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
keyHandlers[ ctrlKey + '[' ] = keyHandlers[ ctrlKey + '[' ] =
changeIndentationLevel( 'decreaseQuoteLevel', 'decreaseListLevel' ); changeIndentationLevel( 'decreaseQuoteLevel', 'decreaseListLevel' );
keyHandlers[ ctrlKey + ']' ] = keyHandlers[ ctrlKey + ']' ] =
changeIndentationLevel( 'increaseQuoteLevel', 'increaseListLevel' ); changeIndentationLevel( 'increaseQuoteLevel', 'increaseListLevel' );
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' ); keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' ); keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );

View file

@ -411,6 +411,9 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
} }
while ( true ) { while ( true ) {
if ( endContainer === endMax || endContainer === root ) {
break;
}
if ( maySkipBR && if ( maySkipBR &&
endContainer.nodeType !== TEXT_NODE && endContainer.nodeType !== TEXT_NODE &&
endContainer.childNodes[ endOffset ] && endContainer.childNodes[ endOffset ] &&
@ -418,9 +421,7 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
endOffset += 1; endOffset += 1;
maySkipBR = false; maySkipBR = false;
} }
if ( endContainer === endMax || if ( endOffset !== getLength( endContainer ) ) {
endContainer === root ||
endOffset !== getLength( endContainer ) ) {
break; break;
} }
parent = endContainer.parentNode; parent = endContainer.parentNode;
@ -432,6 +433,20 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
range.setEnd( endContainer, endOffset ); range.setEnd( endContainer, endOffset );
}; };
var moveRangeBoundaryOutOf = function ( range, nodeName, root ) {
var parent = getNearest( range.endContainer, root, 'A' );
if ( parent ) {
var clone = range.cloneRange();
parent = parent.parentNode;
moveRangeBoundariesUpTree( clone, parent, parent, root );
if ( clone.endContainer === parent ) {
range.setStart( clone.endContainer, clone.endOffset );
range.setEnd( clone.endContainer, clone.endOffset );
}
}
return range;
};
// 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, root ) { var getStartBlockOfRange = function ( range, root ) {