Skip to content

Commit

Permalink
Merge branch 't/16755' into major
Browse files Browse the repository at this point in the history
  • Loading branch information
mlewand committed May 19, 2017
2 parents 1f64928 + 3f8f748 commit 3c032f9
Show file tree
Hide file tree
Showing 110 changed files with 2,551 additions and 2,265 deletions.
1 change: 1 addition & 0 deletions config.js
Expand Up @@ -62,6 +62,7 @@ CKEDITOR.editorConfig = function( config ) {
'stylescombo,' +
'tab,' +
'table,' +
'tableselection,' +
'tabletools,' +
'templates,' +
'toolbar,' +
Expand Down
56 changes: 56 additions & 0 deletions core/dom/range.js
Expand Up @@ -2897,6 +2897,62 @@ CKEDITOR.dom.range = function( root ) {
}
};

/**
* Merges every subsequent range in given set, returning a smaller array of ranges.
*
* Note that each range in the returned value will be enlarged with `CKEDITOR.ENLARGE_ELEMENT` value.
*
* @since 4.7.0
* @static
* @param {CKEDITOR.dom.range[]} ranges
* @returns {CKEDITOR.dom.range[]} Set of merged ranges.
* @member CKEDITOR.dom.range
*/
CKEDITOR.dom.range.mergeRanges = function( ranges ) {
return CKEDITOR.tools.array.reduce( ranges, function( ret, rng ) {
// Last range ATM.
var lastRange = ret[ ret.length - 1 ],
isContinuation = false;

// Make a clone, we don't want to modify input.
rng = rng.clone();
rng.enlarge( CKEDITOR.ENLARGE_ELEMENT );

if ( lastRange ) {
// The trick is to create a range spanning the gap between the two ranges. Then iterate over
// each node found in this gap. If it contains anything other than whitespace, then it means it
// is not a continuation.
var gapRange = new CKEDITOR.dom.range( rng.root ),
walker = new CKEDITOR.dom.walker( gapRange ),
isWhitespace = CKEDITOR.dom.walker.whitespaces(),
nodeInBetween;

gapRange.setStart( lastRange.endContainer, lastRange.endOffset );
gapRange.setEnd( rng.startContainer, rng.startOffset );

nodeInBetween = walker.next();

while ( isWhitespace( nodeInBetween ) || rng.endContainer.equals( nodeInBetween ) ) {
// We don't care about whitespaces, and range container. Also we skip the endContainer,
// as it will also be provided by the iterator (as it visits it's opening tag).
nodeInBetween = walker.next();
}

// Simply, if anything has been found there's a content in between the two.
isContinuation = !nodeInBetween;
}

if ( isContinuation ) {
// If last range ends, where the current range starts, then let's merge it.
lastRange.setEnd( rng.endContainer, rng.endOffset );
} else {
// In other case just push cur range into the stack.
ret.push( rng );
}

return ret;
}, [] );
};

} )();

Expand Down
175 changes: 1 addition & 174 deletions core/selection.js
Expand Up @@ -165,14 +165,6 @@
return txt;
}

function clearCellInRange( range ) {
if ( range.getEnclosedNode() ) {
return range.getEnclosedNode().setText( '' );
}

range.deleteContents();
}

function performFakeTableSelection( ranges ) {
var editor = this.root.editor,
realSelection = editor.getSelection( 1 ),
Expand Down Expand Up @@ -218,163 +210,6 @@
// Fire selectionchange, just like a normal selection.
this.root.fire( 'selectionchange' );
}

// Handle left, up, right, down, delete and backspace keystrokes inside table fake selection.
function getTableOnKeyDownListener( editor ) {
var keystrokes = {
37: 1, // Left Arrow
38: 1, // Up Arrow
39: 1, // Right Arrow,
40: 1, // Down Arrow
8: 1, // Backspace
46: 1 // Delete
},
tags = CKEDITOR.tools.extend( { table: 1 }, CKEDITOR.dtd.$tableContent );

delete tags.td;
delete tags.th;

// Called when removing empty subseleciton of the table.
// It should not allow for removing part of table, e.g. when user attempts to remove 2 cells
// out of 4 in row. It should however remove whole row or table, if it was fully selected.
function deleteEmptyTablePart( node, ranges ) {
if ( !ranges.length ) {
return null;
}

var rng = editor.createRange(),
mergedRanges = CKEDITOR.plugins.tabletools.mergeRanges( ranges );

// Enlarge each range, so that it wraps over tr.
CKEDITOR.tools.array.forEach( mergedRanges, function( mergedRange ) {
mergedRange.enlarge( CKEDITOR.ENLARGE_ELEMENT );
} );

var boundaryNodes = mergedRanges[ 0 ].getBoundaryNodes(),
startNode = boundaryNodes.startNode,
endNode = boundaryNodes.endNode;

if ( startNode && startNode.is && startNode.is( tags ) ) {
// A node that will receive selection after the firstRangeContainedNode is removed.
var boundaryTable = startNode.getAscendant( 'table', true ),
targetNode = startNode.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT, boundaryTable ),
selectBeginning = false,
matchingElement = function( elem ) {
// We're interested in matching only td/th but not contained by the startNode since it will be removed.
// Technically none of startNode children should be visited but it will due to #12191.
return !startNode.contains( elem ) && elem.is && elem.is( 'td', 'th' );
};

while ( targetNode && !matchingElement( targetNode ) ) {
targetNode = targetNode.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT, boundaryTable );
}

if ( !targetNode && !endNode.is( 'table' ) && endNode.getNext() ) {
// Special case: say we were removing the first row, so there are no more tds before, check if there's a cell after removed row.
targetNode = endNode.getNext().findOne( 'td, th' );
// In that particular case we want to select beginning.
selectBeginning = true;
}

if ( !targetNode ) {
// As a last resort of defence we'll put the selection before (about to be) removed table.
rng.setStartBefore( startNode.getAscendant( 'table', true ) );
rng.collapse( true );
} else {
rng[ 'moveToElementEdit' + ( selectBeginning ? 'Start' : 'End' ) ]( targetNode );
}

mergedRanges[ 0 ].deleteContents();

return [ rng ];
}

// By default return a collapsed selection in a first cell.
if ( startNode ) {
rng.moveToElementEditablePosition( startNode );
return [ rng ];
}
}

return function( evt ) {
// Use getKey directly in order to ignore modifiers.
// Justification: http://dev.ckeditor.com/ticket/11861#comment:13
var keystroke = evt.data.getKey(),
selection,
toStart = keystroke === 37 || keystroke == 38,
ranges,
firstCell,
lastCell,
i;

// Handle only left/right/del/bspace keys.
if ( !keystrokes[ keystroke ] ) {
return;
}

selection = editor.getSelection();

if ( !selection || !selection.isInTable() || !selection.isFake ) {
return;
}

ranges = selection.getRanges();
firstCell = ranges[ 0 ]._getTableElement();
lastCell = ranges[ ranges.length - 1 ]._getTableElement();

evt.data.preventDefault();
evt.cancel();

if ( keystroke > 8 && keystroke < 46 ) {
// Arrows.
ranges[ 0 ].moveToElementEditablePosition( toStart ? firstCell : lastCell, !toStart );
selection.selectRanges( [ ranges[ 0 ] ] );
} else {
// Delete.
for ( i = 0; i < ranges.length; i++ ) {
clearCellInRange( ranges[ i ] );
}

var newRanges = deleteEmptyTablePart( firstCell, ranges );

if ( newRanges ) {
ranges = newRanges;
} else {
// If no new range was returned fallback to selecting first cell.
ranges[ 0 ].moveToElementEditablePosition( firstCell );
}

selection.selectRanges( ranges );
editor.fire( 'saveSnapshot' );
}
};
}

function getTableOnKeyPressListener( editor ) {
return function( evt ) {
var selection = editor.getSelection(),
ranges,
firstCell,
i;

// We must check if the event really did not produce any character as it's fired for all keys in Gecko.
if ( !selection || !selection.isInTable() || !selection.isFake || !evt.data.$.charCode ||
evt.data.getKeystroke() & CKEDITOR.CTRL ) {
return;
}

ranges = selection.getRanges();
firstCell = ranges[ 0 ].getEnclosedNode().getAscendant( { td: 1, th: 1 }, true );

for ( i = 0; i < ranges.length; i++ ) {
clearCellInRange( ranges[ i ] );
}

ranges[ 0 ].moveToElementEditablePosition( firstCell );
selection.selectRanges( [ ranges[ 0 ] ] );
};
}

// #### table selection : END

// #### checkSelectionChange : START
Expand All @@ -389,7 +224,6 @@

if ( sel ) {
realSel = this.getSelection( 1 );
//realSel && console.log( realSel.getRanges()[ 0] , isRealTableSelection( realSel.getRanges(), sel.getRanges() ) );
// If real (not locked/stored) selection was moved from hidden container
// or is not a table one, then the fake-selection must be invalidated.
if ( !realSel || ( !realSel.isHidden() && !isRealTableSelection( realSel, sel ) ) ) {
Expand Down Expand Up @@ -1137,13 +971,6 @@
}, null, null, -1 );
}

// Automatically select non-editable element when navigating into
// it by left/right or backspace/del keys.
if ( editor.config.tableImprovements ) {
editable.attachListener( editable, 'keydown', getTableOnKeyDownListener( editor ), null, null, -1 );
editable.attachListener( editable, 'keypress', getTableOnKeyPressListener( editor ), null, null, -1 );
}

editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 );

function moveRangeToPoint( range, x, y ) {
Expand Down Expand Up @@ -2441,7 +2268,7 @@
*
* editor.getSelection().isInTable();
*
* @since
* @since 4.7.0
* @returns {Boolean}
*/
isInTable: function() {
Expand Down

0 comments on commit 3c032f9

Please sign in to comment.