|
|
@@ -11,7 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
|
|
|
|
import TableSelection from './tableselection';
|
|
|
import TableWalker from './tablewalker';
|
|
|
-import { getColumnIndexes, getRowIndexes, isSelectionRectangular } from './utils';
|
|
|
+import { getColumnIndexes, getRowIndexes, getSelectionAffectedTableCells, isSelectionRectangular } from './utils';
|
|
|
import { findAncestor } from './commands/utils';
|
|
|
import { cropTableToDimensions } from './tableselection/croptable';
|
|
|
import TableUtils from './tableutils';
|
|
|
@@ -101,10 +101,12 @@ export default class TableClipboard extends Plugin {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const tableSelection = this.editor.plugins.get( TableSelection );
|
|
|
- const selectedTableCells = tableSelection.getSelectedTableCells();
|
|
|
+ const model = this.editor.model;
|
|
|
+ const tableUtils = this.editor.plugins.get( TableUtils );
|
|
|
|
|
|
- if ( !selectedTableCells ) {
|
|
|
+ const selectedTableCells = getSelectionAffectedTableCells( model.document.selection );
|
|
|
+
|
|
|
+ if ( !selectedTableCells.length ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -118,43 +120,28 @@ export default class TableClipboard extends Plugin {
|
|
|
// Override default model.insertContent() handling at this point.
|
|
|
evt.stop();
|
|
|
|
|
|
- // Currently not handled. See: https://github.com/ckeditor/ckeditor5/issues/6121.
|
|
|
- if ( selectedTableCells.length === 1 ) {
|
|
|
- // @if CK_DEBUG // console.log( 'NOT IMPLEMENTED YET: Single table cell is selected.' );
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const tableUtils = this.editor.plugins.get( TableUtils );
|
|
|
-
|
|
|
- // Currently not handled. The selected table content should be trimmed to a rectangular selection.
|
|
|
- // See: https://github.com/ckeditor/ckeditor5/issues/6122.
|
|
|
- if ( !isSelectionRectangular( selectedTableCells, tableUtils ) ) {
|
|
|
- // @if CK_DEBUG // console.log( 'NOT IMPLEMENTED YET: Selection is not rectangular (non-mergeable).' );
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const { last: lastColumnOfSelection, first: firstColumnOfSelection } = getColumnIndexes( selectedTableCells );
|
|
|
- const { first: firstRowOfSelection, last: lastRowOfSelection } = getRowIndexes( selectedTableCells );
|
|
|
-
|
|
|
- const selectionHeight = lastRowOfSelection - firstRowOfSelection + 1;
|
|
|
- const selectionWidth = lastColumnOfSelection - firstColumnOfSelection + 1;
|
|
|
-
|
|
|
- const pasteHeight = tableUtils.getRows( pastedTable );
|
|
|
const pasteWidth = tableUtils.getColumns( pastedTable );
|
|
|
+ const pasteHeight = tableUtils.getRows( pastedTable );
|
|
|
|
|
|
- // The if below is temporal and will be removed when handling this case.
|
|
|
- // See: https://github.com/ckeditor/ckeditor5/issues/6769.
|
|
|
- if ( selectionHeight > pasteHeight || selectionWidth > pasteWidth ) {
|
|
|
- // @if CK_DEBUG // console.log( 'NOT IMPLEMENTED YET: Pasted table is smaller than selection area.' );
|
|
|
+ model.change( writer => {
|
|
|
+ const selectionDimensions = this._prepareTableSelectionForPasting( selectedTableCells, pasteWidth, pasteHeight, writer );
|
|
|
+ const { selectionWidth, selectionHeight } = selectionDimensions;
|
|
|
+
|
|
|
+ // This paste scenario is not handled for now, this will be removed.
|
|
|
+ // See: https://github.com/ckeditor/ckeditor5/issues/6122.
|
|
|
+ // TODO temporary to remove after https://github.com/ckeditor/ckeditor5/issues/6122.
|
|
|
+ if ( selectionDimensions.stop ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- return;
|
|
|
- }
|
|
|
+ // The if below is temporal and will be removed when handling this case.
|
|
|
+ // See: https://github.com/ckeditor/ckeditor5/issues/6769.
|
|
|
+ if ( selectionHeight > pasteHeight || selectionWidth > pasteWidth ) {
|
|
|
+ // @if CK_DEBUG // console.log( 'NOT IMPLEMENTED YET: Pasted table is smaller than selection area.' );
|
|
|
|
|
|
- const model = this.editor.model;
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- model.change( writer => {
|
|
|
// Crop pasted table if it extends selection area.
|
|
|
if ( selectionHeight < pasteHeight || selectionWidth < pasteWidth ) {
|
|
|
const cropDimensions = {
|
|
|
@@ -167,83 +154,154 @@ export default class TableClipboard extends Plugin {
|
|
|
pastedTable = cropTableToDimensions( pastedTable, cropDimensions, writer, tableUtils );
|
|
|
}
|
|
|
|
|
|
- // Holds two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.
|
|
|
- const pastedTableLocationMap = createLocationMap( pastedTable, selectionWidth, selectionHeight );
|
|
|
-
|
|
|
// Content table to which we insert a pasted table.
|
|
|
const selectedTable = findAncestor( 'table', selectedTableCells[ 0 ] );
|
|
|
|
|
|
- const selectedTableMap = [ ...new TableWalker( selectedTable, {
|
|
|
- startRow: firstRowOfSelection,
|
|
|
- endRow: lastRowOfSelection,
|
|
|
- includeSpanned: true
|
|
|
- } ) ];
|
|
|
-
|
|
|
- // Selection must be set to pasted cells (some might be removed or new created).
|
|
|
- const cellsToSelect = [];
|
|
|
-
|
|
|
- // Store previous cell in order to insert a new table cells after it (if required).
|
|
|
- let previousCellInRow;
|
|
|
-
|
|
|
- // Content table replace cells algorithm iterates over a selected table fragment and:
|
|
|
- //
|
|
|
- // - Removes existing table cells at current slot (location).
|
|
|
- // - Inserts cell from a pasted table for a matched slots.
|
|
|
- //
|
|
|
- // This ensures proper table geometry after the paste
|
|
|
- for ( const { row, column, cell, isSpanned } of selectedTableMap ) {
|
|
|
- if ( column === 0 ) {
|
|
|
- previousCellInRow = null;
|
|
|
- }
|
|
|
+ this._replaceSelectedCellsWithPasted( selectedTable, pastedTable, selectionDimensions, writer );
|
|
|
+ } );
|
|
|
+ }
|
|
|
|
|
|
- // Could use startColumn, endColumn. See: https://github.com/ckeditor/ckeditor5/issues/6785.
|
|
|
- if ( column < firstColumnOfSelection || column > lastColumnOfSelection ) {
|
|
|
- // Only update the previousCellInRow for non-spanned slots.
|
|
|
- if ( !isSpanned ) {
|
|
|
- previousCellInRow = cell;
|
|
|
- }
|
|
|
+ _prepareTableSelectionForPasting( selectedTableCells, pasteWidth, pasteHeight, writer ) {
|
|
|
+ const tableUtils = this.editor.plugins.get( TableUtils );
|
|
|
+ const selectedTable = findAncestor( 'table', selectedTableCells[ 0 ] );
|
|
|
|
|
|
- continue;
|
|
|
- }
|
|
|
+ let { first: firstColumnOfSelection, last: lastColumnOfSelection } = getColumnIndexes( selectedTableCells );
|
|
|
+ let { first: firstRowOfSelection, last: lastRowOfSelection } = getRowIndexes( selectedTableCells );
|
|
|
+
|
|
|
+ if ( selectedTableCells.length == 1 ) {
|
|
|
+ lastRowOfSelection += pasteHeight - 1;
|
|
|
+ lastColumnOfSelection += pasteWidth - 1;
|
|
|
+
|
|
|
+ expandTableSize( selectedTable, lastRowOfSelection + 1, lastColumnOfSelection + 1, writer, tableUtils );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Currently not handled. The selected table content should be trimmed to a rectangular selection.
|
|
|
+ // See: https://github.com/ckeditor/ckeditor5/issues/6122.
|
|
|
+ else if ( !isSelectionRectangular( selectedTableCells, tableUtils ) ) {
|
|
|
+ // @if CK_DEBUG // console.log( 'NOT IMPLEMENTED YET: Selection is not rectangular (non-mergeable).' );
|
|
|
|
|
|
- // If the slot is occupied by a cell in a selected table - remove it.
|
|
|
- // The slot of this cell will be either:
|
|
|
- // - Replaced by a pasted table cell.
|
|
|
- // - Spanned by a previously pasted table cell.
|
|
|
+ // TODO this is temporary - https://github.com/ckeditor/ckeditor5/issues/6122
|
|
|
+ prepareLandingPlace();
|
|
|
+
|
|
|
+ // TODO this is temporary stop marker for currently unhandled scenario
|
|
|
+ return { stop: true };
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ firstColumnOfSelection,
|
|
|
+ firstRowOfSelection,
|
|
|
+ lastColumnOfSelection,
|
|
|
+ lastRowOfSelection,
|
|
|
+ selectionHeight: lastRowOfSelection - firstRowOfSelection + 1,
|
|
|
+ selectionWidth: lastColumnOfSelection - firstColumnOfSelection + 1
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ _replaceSelectedCellsWithPasted( selectedTable, pastedTable, selectionDimensions, writer ) {
|
|
|
+ const {
|
|
|
+ firstColumnOfSelection, lastColumnOfSelection, selectionWidth,
|
|
|
+ firstRowOfSelection, lastRowOfSelection, selectionHeight
|
|
|
+ } = selectionDimensions;
|
|
|
+
|
|
|
+ // Holds two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.
|
|
|
+ const pastedTableLocationMap = createLocationMap( pastedTable, selectionWidth, selectionHeight );
|
|
|
+
|
|
|
+ const selectedTableMap = [ ...new TableWalker( selectedTable, {
|
|
|
+ startRow: firstRowOfSelection,
|
|
|
+ endRow: lastRowOfSelection,
|
|
|
+ includeSpanned: true
|
|
|
+ } ) ];
|
|
|
+
|
|
|
+ // Selection must be set to pasted cells (some might be removed or new created).
|
|
|
+ const cellsToSelect = [];
|
|
|
+
|
|
|
+ // Store previous cell in order to insert a new table cells after it (if required).
|
|
|
+ let previousCellInRow;
|
|
|
+
|
|
|
+ // Content table replace cells algorithm iterates over a selected table fragment and:
|
|
|
+ //
|
|
|
+ // - Removes existing table cells at current slot (location).
|
|
|
+ // - Inserts cell from a pasted table for a matched slots.
|
|
|
+ //
|
|
|
+ // This ensures proper table geometry after the paste
|
|
|
+ for ( const { row, column, cell, isSpanned } of selectedTableMap ) {
|
|
|
+ if ( column === 0 ) {
|
|
|
+ previousCellInRow = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Could use startColumn, endColumn. See: https://github.com/ckeditor/ckeditor5/issues/6785.
|
|
|
+ if ( column < firstColumnOfSelection || column > lastColumnOfSelection ) {
|
|
|
+ // Only update the previousCellInRow for non-spanned slots.
|
|
|
if ( !isSpanned ) {
|
|
|
- writer.remove( cell );
|
|
|
+ previousCellInRow = cell;
|
|
|
}
|
|
|
|
|
|
- // Map current table slot location to an pasted table slot location.
|
|
|
- const pastedCell = pastedTableLocationMap[ row - firstRowOfSelection ][ column - firstColumnOfSelection ];
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If the slot is occupied by a cell in a selected table - remove it.
|
|
|
+ // The slot of this cell will be either:
|
|
|
+ // - Replaced by a pasted table cell.
|
|
|
+ // - Spanned by a previously pasted table cell.
|
|
|
+ if ( !isSpanned ) {
|
|
|
+ writer.remove( cell );
|
|
|
+ }
|
|
|
|
|
|
- // There is no cell to insert (might be spanned by other cell in a pasted table) - advance to the next content table slot.
|
|
|
- if ( !pastedCell ) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ // Map current table slot location to an pasted table slot location.
|
|
|
+ const pastedCell = pastedTableLocationMap[ row - firstRowOfSelection ][ column - firstColumnOfSelection ];
|
|
|
|
|
|
- // Clone cell to insert (to duplicate its attributes and children).
|
|
|
- // Cloning is required to support repeating pasted table content when inserting to a bigger selection.
|
|
|
- const cellToInsert = pastedCell._clone( true );
|
|
|
+ // There is no cell to insert (might be spanned by other cell in a pasted table) - advance to the next content table slot.
|
|
|
+ if ( !pastedCell ) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- let insertPosition;
|
|
|
+ // Clone cell to insert (to duplicate its attributes and children).
|
|
|
+ // Cloning is required to support repeating pasted table content when inserting to a bigger selection.
|
|
|
+ const cellToInsert = pastedCell._clone( true );
|
|
|
|
|
|
- if ( !previousCellInRow ) {
|
|
|
- insertPosition = writer.createPositionAt( selectedTable.getChild( row ), 0 );
|
|
|
- } else {
|
|
|
- insertPosition = writer.createPositionAfter( previousCellInRow );
|
|
|
- }
|
|
|
+ let insertPosition;
|
|
|
|
|
|
- writer.insert( cellToInsert, insertPosition );
|
|
|
- cellsToSelect.push( cellToInsert );
|
|
|
- previousCellInRow = cellToInsert;
|
|
|
+ if ( !previousCellInRow ) {
|
|
|
+ insertPosition = writer.createPositionAt( selectedTable.getChild( row ), 0 );
|
|
|
+ } else {
|
|
|
+ insertPosition = writer.createPositionAfter( previousCellInRow );
|
|
|
}
|
|
|
|
|
|
- writer.setSelection( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
|
|
|
+ writer.insert( cellToInsert, insertPosition );
|
|
|
+ cellsToSelect.push( cellToInsert );
|
|
|
+ previousCellInRow = cellToInsert;
|
|
|
+ }
|
|
|
+
|
|
|
+ writer.setSelection( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Expand table (in place) to expected size (rows and columns).
|
|
|
+function expandTableSize( table, rows, columns, writer, tableUtils ) {
|
|
|
+ const tableWidth = tableUtils.getColumns( table );
|
|
|
+ const tableHeight = tableUtils.getRows( table );
|
|
|
+
|
|
|
+ if ( columns > tableWidth ) {
|
|
|
+ tableUtils.insertColumns( table, {
|
|
|
+ batch: writer.batch,
|
|
|
+ at: tableWidth,
|
|
|
+ columns: columns - tableWidth
|
|
|
+ } );
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( rows > tableHeight ) {
|
|
|
+ tableUtils.insertRows( table, {
|
|
|
+ batch: writer.batch,
|
|
|
+ at: tableHeight,
|
|
|
+ rows: rows - tableHeight
|
|
|
} );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// TODO rename to reflect that it's splitting cells to make selection rectangular
|
|
|
+function prepareLandingPlace( ) {
|
|
|
+}
|
|
|
+
|
|
|
function getTableIfOnlyTableInContent( content ) {
|
|
|
// Table passed directly.
|
|
|
if ( content.is( 'table' ) ) {
|