Jelajahi Sumber

Refactor _onInsertContent() logic.

Maciej Gołaszewski 5 tahun lalu
induk
melakukan
fb21bb05a9
1 mengubah file dengan 60 tambahan dan 52 penghapusan
  1. 60 52
      packages/ckeditor5-table/src/tableclipboard.js

+ 60 - 52
packages/ckeditor5-table/src/tableclipboard.js

@@ -11,7 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import TableSelection from './tableselection';
 import { getColumnIndexes, getRowIndexes, isSelectionRectangular } from './utils';
 import TableWalker from './tablewalker';
-import { findAncestor, updateNumericAttribute } from './commands/utils';
+import { findAncestor } from './commands/utils';
 import { cropTableToDimensions } from './tableselection/croptable';
 
 /**
@@ -99,15 +99,16 @@ export default class TableClipboard extends Plugin {
 			return;
 		}
 
-		// We might need to crop table before inserting.
-		let table = getTable( content );
+		// We might need to crop table before inserting so reference might change.
+		let insertedTable = getTableFromContent( content );
 
-		if ( !table ) {
+		if ( !insertedTable ) {
 			return;
 		}
 
 		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.' );
 
@@ -115,20 +116,26 @@ export default class TableClipboard extends Plugin {
 		}
 
 		const tableUtils = this.editor.plugins.get( 'TableUtils' );
-		const rowIndexes = getRowIndexes( selectedTableCells );
-		const columnIndexes = getColumnIndexes( selectedTableCells );
-		const selectionHeight = rowIndexes.last - rowIndexes.first + 1;
-		const selectionWidth = columnIndexes.last - columnIndexes.first + 1;
-		const insertHeight = tableUtils.getRows( table );
-		const insertWidth = tableUtils.getColumns( table );
-		const contentTable = findAncestor( 'table', selectedTableCells[ 0 ] );
 
+		// Currently not handled. The selected table content should be trimmed to a rectangular selection.
+		// See: https://github.com/ckeditor/ckeditor5/issues/6122.
 		if ( !isSelectionRectangular( this.editor.model.document.selection, 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 insertHeight = tableUtils.getRows( insertedTable );
+		const insertWidth = tableUtils.getColumns( insertedTable );
+
+		// The if below is temporal and will be removed when handling this case.
+		// See: https://github.com/ckeditor/ckeditor5/issues/6769.
 		if ( selectionHeight > insertHeight || selectionWidth > insertWidth ) {
 			// @if CK_DEBUG // console.log( 'NOT IMPLEMENTED YET: Pasted table is smaller than selection area.' );
 
@@ -138,81 +145,82 @@ export default class TableClipboard extends Plugin {
 		const model = this.editor.model;
 
 		model.change( writer => {
-			// Pasted table extends selection area.
+			// Crop pasted table if it extends selection area.
 			if ( selectionHeight < insertHeight || selectionWidth < insertWidth ) {
-				table = cropTableToDimensions( table, 0, 0, selectionHeight - 1, selectionWidth - 1, tableUtils, writer );
+				insertedTable = cropTableToDimensions( insertedTable, 0, 0, selectionHeight - 1, selectionWidth - 1, tableUtils, writer );
 			}
 
+			// Stores cells anchors map of inserted table cell as '"row"x"column"' index.
 			const insertionMap = new Map();
 
-			for ( const { column, row, cell } of new TableWalker( table ) ) {
+			for ( const { column, row, cell } of new TableWalker( insertedTable ) ) {
 				insertionMap.set( `${ row }x${ column }`, cell );
 			}
 
+			// Content table to which we insert a table.
+			const contentTable = findAncestor( 'table', selectedTableCells[ 0 ] );
+
+			// 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;
+
 			const tableMap = [ ...new TableWalker( contentTable, {
-				startRow: rowIndexes.first,
-				endRow: rowIndexes.last,
+				startRow: firstRowOfSelection,
+				endRow: lastRowOfSelection,
 				includeSpanned: true
 			} ) ];
 
-			let previousCell;
-
-			const cellsToSelect = [];
-
 			for ( const { column, row, cell, isSpanned } of tableMap ) {
-				// TODO: crete issue for table walker startColumn, endColumn.
-				if ( column < columnIndexes.first || column > columnIndexes.last ) {
-					previousCell = cell;
+				if ( column === 0 ) {
+					previousCellInRow = null;
+				}
+
+				// Could use startColumn, endColumn. See: https://github.com/ckeditor/ckeditor5/issues/6785.
+				if ( column < firstColumnOfSelection || column > lastColumnOfSelection ) {
+					previousCellInRow = cell;
 
 					continue;
 				}
 
-				const toGet = `${ row - rowIndexes.first }x${ column - columnIndexes.first }`;
-				const cellToInsert = insertionMap.get( toGet );
+				// Map current table location to inserted table location.
+				const cellLocationToInsert = `${ row - firstRowOfSelection }x${ column - firstColumnOfSelection }`;
+				const cellToInsert = insertionMap.get( cellLocationToInsert );
 
+				// There is no cell to insert (might be spanned by other cell in a pasted table) so...
 				if ( !cellToInsert ) {
+					// ...if the cell is anchored in current location (not-spanned slot) then remove that cell from content table...
 					if ( !isSpanned ) {
 						writer.remove( writer.createRangeOn( cell ) );
 					}
 
+					// ...and advance to next content table slot.
 					continue;
 				}
 
 				let targetCell = cell;
 
-				if ( isSpanned ) {
-					let insertPosition;
-
-					if ( column === 0 || !previousCell || previousCell.parent.index !== row ) {
-						insertPosition = writer.createPositionAt( contentTable.getChild( row ), 0 );
-					} else {
-						insertPosition = writer.createPositionAfter( previousCell );
-					}
-
-					targetCell = writer.createElement( 'tableCell' );
-
-					writer.insert( targetCell, insertPosition );
-				} else {
-					writer.remove( writer.createRangeIn( cell ) );
+				// Remove cells from anchor slots (not spanned by other cells).
+				if ( !isSpanned ) {
+					writer.remove( writer.createRangeOn( cell ) );
 				}
 
-				updateNumericAttribute( 'colspan', cellToInsert.getAttribute( 'colspan' ), targetCell, writer, 1 );
-				updateNumericAttribute( 'rowspan', cellToInsert.getAttribute( 'rowspan' ), targetCell, writer, 1 );
+				// 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.
+				targetCell = cellToInsert._clone( true );
 
-				const attributesToCopy = Array.from( cellToInsert.getAttributeKeys() )
-					.filter( attribute => attribute !== 'colspan' || attribute !== 'rowspan' );
+				let insertPosition;
 
-				for ( const attribute of attributesToCopy ) {
-					writer.setAttribute( attribute, cellToInsert.getAttribute( attribute ), targetCell );
-				}
-
-				// TODO: use Element._clone to copy full structure.
-				for ( const child of Array.from( cellToInsert.getChildren() ) ) {
-					writer.insert( child, targetCell, 'end' );
+				if ( !previousCellInRow ) {
+					insertPosition = writer.createPositionAt( contentTable.getChild( row ), 0 );
+				} else {
+					insertPosition = writer.createPositionAfter( previousCellInRow );
 				}
 
+				writer.insert( targetCell, insertPosition );
 				cellsToSelect.push( targetCell );
-				previousCell = targetCell;
+				previousCellInRow = targetCell;
 			}
 
 			writer.setSelection( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
@@ -220,7 +228,7 @@ export default class TableClipboard extends Plugin {
 	}
 }
 
-function getTable( content ) {
+function getTableFromContent( content ) {
 	for ( const child of Array.from( content ) ) {
 		if ( child.is( 'table' ) ) {
 			return child;