Skip to content

Commit

Permalink
Merge branch 't/11183'
Browse files Browse the repository at this point in the history
  • Loading branch information
Reinmar committed Dec 3, 2013
2 parents a1baa1d + f966e67 commit ed52709
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Fixed Issues:
* [#11202](http://dev.ckeditor.com/ticket/11202): Fixed: No newline at BB-code mode.
* [#10890](http://dev.ckeditor.com/ticket/10890): Fixed: Error thrown when pressing *Delete* key in a list item.
* [#10055](http://dev.ckeditor.com/ticket/10055): [IE8-10] Fixed: *Delete* pressed on selected image causes browser to go back.
* [#11183](http://dev.ckeditor.com/ticket/11183): Fixed: Inserting line or table in multiple rows selection causes browser crash. Additionally, the [`editor.insertElement`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) method does not insert element into every range of a selection any more.


## CKEditor 4.3
Expand Down
140 changes: 117 additions & 23 deletions core/editable.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@
// Remove the original contents, merge split nodes.
range.deleteContents( 1 );

// If range is placed in inermediate element (not td or th), we need to do three things:
// * fill emptied <td/th>s with if browser needs them,
// * remove empty text nodes so IE8 won't crash (http://dev.ckeditor.com/ticket/11183#comment:8),
// * fix structure and move range into the <td/th> element.
if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.is( { tr:1,table:1,tbody:1,thead:1,tfoot:1 } ) )
fixTableAfterContentsDeletion( range );

// If we're inserting a block at dtd-violated position, split
// the parent blocks until we reach blockLimit.
var current, dtd;
Expand Down Expand Up @@ -303,48 +310,32 @@
var editor = this.editor,
enterMode = editor.activeEnterMode,
selection = editor.getSelection(),
ranges = selection.getRanges(),
range = selection.getRanges()[ 0 ],
elementName = element.getName(),
isBlock = CKEDITOR.dtd.$block[ elementName ],
clone, lastElement, range;
isBlock = CKEDITOR.dtd.$block[ elementName ];

// Prepare for the insertion.
beforeInsert( this );

// Insert the element into all ranges by cloning.
for ( var i = ranges.length; i--; ) {
range = ranges[ i ];

// Clone is an element for the first range.
clone = !i && element || element.clone( 1 );

// Put the clone into a particular range.
// Save the last **successfully inserted** element reference
// so we can make the selection later.
if ( this.insertElementIntoRange( clone, range ) && !lastElement )
lastElement = clone;
}

if ( lastElement ) {
range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END );
// Insert element into first range only and ignore the rest (#11183).
if ( this.insertElementIntoRange( element, range ) ) {
range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END );

// If we're inserting a block element, the new cursor position must be
// optimized. (#3100,#5436,#8950)
if ( isBlock ) {

// Find next, meaningful element.
var next = lastElement.getNext( function( node ) {
var next = element.getNext( function( node ) {
return isNotEmpty( node ) && !isBogus( node );
} );

if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.is( CKEDITOR.dtd.$block ) ) {

// If the next one is a text block, move cursor to the start of it's content.
if ( next.getDtd()[ '#' ] )
range.moveToElementEditStart( next );
// Otherwise move cursor to the before end of the last element.
else
range.moveToElementEditEnd( lastElement );
range.moveToElementEditEnd( element );
}
// Open a new line if the block is inserted at the end of parent.
else if ( !next && enterMode != CKEDITOR.ENTER_BR ) {
Expand Down Expand Up @@ -1810,6 +1801,109 @@
}, 0 );
}

// 1. Fixes a range which is a result of deleteContents() and is placed in an intermediate element (see dtd.$intermediate),
// inside a table. A goal is to find a closest <td> or <th> element and when this fails, recreate the structure of the table.
// 2. Fixes empty cells by appending bogus <br>s or deleting empty text nodes in IE<=8 case.
var fixTableAfterContentsDeletion = (function() {
// Creates an element walker which can only "go deeper". It won't
// move out from any element. Therefore it can be used to find <td>x</td> in cases like:
// <table><tbody><tr><td>x</td></tr></tbody>^<tfoot>...
function getFixTableSelectionWalker( testRange ) {
var walker = new CKEDITOR.dom.walker( testRange );
walker.guard = function( node, isMovingOut ) {
if ( isMovingOut )
return false;
if ( node.type == CKEDITOR.NODE_ELEMENT )
return node.is( CKEDITOR.dtd.$tableContent );
};
walker.evaluator = function( node ) {
return node.type == CKEDITOR.NODE_ELEMENT;
};

return walker;
}

function fixTableStructure( element, newElementName, appendToStart ) {
var temp = element.getDocument().createElement( newElementName );
element.append( temp, appendToStart );
return temp;
}

// Fix empty cells. This means:
// * add bogus <br> if browser needs it
// * remove empty text nodes on IE8, because it will crash (http://dev.ckeditor.com/ticket/11183#comment:8).
function fixEmptyCells( cells ) {
var i = cells.count(),
cell;

for ( i; i-- > 0; ) {
cell = cells.getItem( i );

if ( !CKEDITOR.tools.trim( cell.getHtml() ) ) {
cell.appendBogus();
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && cell.getChildCount() )
cell.getFirst().remove();
}
}
}

return function( range ) {
var container = range.startContainer,
table = container.getAscendant( 'table', 1 ),
testRange,
walker,
deeperSibling,
doc = range.document,
appendToStart = false;

fixEmptyCells( table.getElementsByTag( 'td' ) );
fixEmptyCells( table.getElementsByTag( 'th' ) );

// Look left.
testRange = range.clone();
testRange.setStart( container, 0 );
deeperSibling = getFixTableSelectionWalker( testRange ).lastBackward();

// If left is empty, look right.
if ( !deeperSibling ) {
testRange = range.clone();
testRange.setEndAt( container, CKEDITOR.POSITION_BEFORE_END );
deeperSibling = getFixTableSelectionWalker( testRange ).lastForward();
appendToStart = true;
}

// If there's no deeper nested element in both direction - container is empty - we'll use it then.
if ( !deeperSibling )
deeperSibling = container;

// Fix structure...

// We found a table what means that it's empty - remove it completely.
if ( deeperSibling.is( 'table' ) ) {
range.setStartAt( deeperSibling, CKEDITOR.POSITION_BEFORE_START );
range.collapse( true );
deeperSibling.remove();
return;
}

// Found an empty txxx element - append tr.
if ( deeperSibling.is( { tbody:1,thead:1,tfoot:1 } ) )
deeperSibling = fixTableStructure( deeperSibling, 'tr', appendToStart );

// Found an empty tr element - append td/th.
if ( deeperSibling.is( 'tr' ) )
deeperSibling = fixTableStructure( deeperSibling, deeperSibling.getParent().is( 'thead' ) ? 'th' : 'td', appendToStart );

// To avoid setting selection after bogus, remove it from the current cell.
// We can safely do that, because we'll insert element into that cell.
var bogus = deeperSibling.getBogus();
if ( bogus )
bogus.remove();

range.moveToPosition( deeperSibling, appendToStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
}
})();

})();

/**
Expand Down

0 comments on commit ed52709

Please sign in to comment.