+ fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' ),
+ isSelectingTable;
+
+ // #### table selection : START
+ // @param {CKEDITOR.dom.range[]} ranges
+ // @param {Boolean} allowPartially Whether a collapsed selection within table is recognized to be a valid selection.
+ // This happens for WebKits on MacOS, when you right click inside the table.
+ function isTableSelection( ranges, allowPartially ) {
+ if ( ranges.length === 0 ) {
+ return false;
+ }
+
+ var node,
+ i;
+
+ function isPartiallySelected( range ) {
+ var startCell = range.startContainer.getAscendant( { td: 1, th: 1 }, true ),
+ endCell = range.endContainer.getAscendant( { td: 1, th: 1 }, true ),
+ trim = CKEDITOR.tools.trim,
+ selected;
+
+ // Check if the selection is inside one cell and we don't have any nested table contents selected.
+ if ( !startCell || !startCell.equals( endCell ) || startCell.findOne( 'td, th, tr, tbody, table' ) ) {
+ return false;
+ }
+
+ selected = range.cloneContents();
+
+ // Empty selection is still partially selected.
+ if ( !selected.getFirst() ) {
+ return true;
+ }
+
+ return trim( selected.getFirst().getText() ) !== trim( startCell.getText() );
+ }
+
+ // Edge case: partially selected text node inside one table cell or cursor inside cell.
+ if ( !allowPartially && ranges.length === 1 &&
+ ( ranges[ 0 ].collapsed || isPartiallySelected( ranges[ 0 ] ) ) ) {
+ return false;
+ }
+
+ for ( i = 0; i < ranges.length; i++ ) {
+ node = ranges[ i ]._getTableElement();
+
+ if ( !node ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // After performing fake table selection, the real selection is limited
+ // to the first selected cell. Therefore to check if the real selection
+ // matches the fake selection, we check if the table cell from fake selection's
+ // first range and real selection's range are the same.
+ // Also if the selection is collapsed, we should check if it's placed inside the table
+ // in which the fake selection is or inside nested table. Such selection occurs after right mouse click.
+ function isRealTableSelection( selection, fakeSelection ) {
+ var ranges = selection.getRanges(),
+ fakeRanges = fakeSelection.getRanges(),
+ table = ranges.length && ranges[ 0 ]._getTableElement() &&
+ ranges[ 0 ]._getTableElement().getAscendant( 'table', true ),
+ fakeTable = fakeRanges.length && fakeRanges[ 0 ]._getTableElement() &&
+ fakeRanges[ 0 ]._getTableElement().getAscendant( 'table', true ),
+ isTableRange = ranges.length === 1 && ranges[ 0 ]._getTableElement() &&
+ ranges[ 0 ]._getTableElement().is( 'table' ),
+ isFakeTableRange = fakeRanges.length === 1 && fakeRanges[ 0 ]._getTableElement() &&
+ fakeRanges[ 0 ]._getTableElement().is( 'table' );
+
+ function isValidTableSelection( table, fakeTable, ranges, fakeRanges ) {
+ var isMenuOpen = ranges.length === 1 && ranges[ 0 ].collapsed,
+ // In case of WebKit on MacOS, when checking real selection, we must allow selection to be partial.
+ // Otherwise the check will fail for table selection with opened context menu.
+ isInTable = isTableSelection( ranges, !!CKEDITOR.env.webkit ) && isTableSelection( fakeRanges );
+
+ return isSameTable( table, fakeTable ) && ( isMenuOpen || isInTable );
+ }
+
+ function isSameTable( table, fakeTable ) {
+ if ( !table || !fakeTable ) {
+ return false;
+ }
+
+ return table.equals( fakeTable ) || fakeTable.contains( table );
+ }
+
+ if ( isValidTableSelection( table, fakeTable, ranges, fakeRanges ) ) {
+ // Edge case: when editor contains only table and that table is selected using selectAll command,
+ // then the selection is not properly refreshed and it must be done manually.
+ if ( isTableRange && !isFakeTableRange ) {
+ fakeSelection.selectRanges( ranges );
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ function getSelectedCells( ranges ) {
+ var cells = [],
+ node,
+ i;
+
+ function getCellsFromElement( element ) {
+ var cells = element.find( 'td, th' ),
+ cellsArray = [],
+ i;
+
+ for ( i = 0; i < cells.count(); i++ ) {
+ cellsArray.push( cells.getItem( i ) );
+ }
+
+ return cellsArray;
+ }
+
+ for ( i = 0; i < ranges.length; i++ ) {
+ node = ranges[ i ]._getTableElement();
+
+ if ( node.is && node.is( { td: 1, th: 1 } ) ) {
+ cells.push( node );
+ } else {
+ cells = cells.concat( getCellsFromElement( node ) );
+ }
+ }
+
+ return cells;
+ }
+
+ // Cells in the same row are separated by tab and the rows are separated by new line, e.g.
+ // Cell 1.1 Cell 1.2
+ // Cell 2.1 Cell 2.2
+ function getTextFromSelectedCells( ranges ) {
+ var cells = getSelectedCells( ranges ),
+ txt = '',
+ currentRow = [],
+ lastRow,
+ i;
+
+ for ( i = 0; i < cells.length; i++ ) {
+ if ( lastRow && !lastRow.equals( cells[ i ].getAscendant( 'tr' ) ) ) {
+ txt += currentRow.join( '\t' ) + '\n';
+ lastRow = cells[ i ].getAscendant( 'tr' );
+ currentRow = [];
+ } else if ( i === 0 ) {
+ lastRow = cells[ i ].getAscendant( 'tr' );
+ }
+
+ currentRow.push( cells[ i ].getText() );
+ }
+
+ txt += currentRow.join( '\t' );
+
+ return txt;
+ }
+
+ function performFakeTableSelection( ranges ) {
+ var editor = this.root.editor,
+ realSelection = editor.getSelection( 1 ),
+ cache;
+
+ // Cleanup after previous selection - e.g. remove hidden sel container.
+ this.reset();
+
+ // Indicate that the table is being fake-selected to prevent infinite loop
+ // inside `selectRanges`.
+ isSelectingTable = true;
+
+ // Cancel selectionchange for the real selection.
+ realSelection.root.once( 'selectionchange', function( evt ) {
+ evt.cancel();
+ }, null, null, 0 );
+
+ // Move real selection to the first selected range.
+ realSelection.selectRanges( [ ranges[ 0 ] ] );
+
+ cache = this._.cache;
+
+ // Caches given ranges.
+ cache.ranges = new CKEDITOR.dom.rangeList( ranges );
+ cache.type = CKEDITOR.SELECTION_TEXT;
+ cache.selectedElement = ranges[ 0 ]._getTableElement();
+
+ // `selectedText` should contain text from all selected data ("plain text table")
+ // to be compatible with Firefox's implementation.
+ cache.selectedText = getTextFromSelectedCells( ranges );
+
+ // Properties that will not be available when isFake.
+ cache.nativeSel = null;
+
+ this.isFake = 1;
+ this.rev = nextRev++;
+
+ // Save this selection, so it can be returned by editor.getSelection().
+ editor._.fakeSelection = this;
+
+ isSelectingTable = false;
+
+ // Fire selectionchange, just like a normal selection.
+ this.root.fire( 'selectionchange' );
+ }
+ // #### table selection : END