ソースを参照

Table pasting can be extended by decorated method.

Kuba Niegowski 5 年 前
コミット
92cec9bc1d
1 ファイル変更134 行追加104 行削除
  1. 134 104
      packages/ckeditor5-table/src/tableclipboard.js

+ 134 - 104
packages/ckeditor5-table/src/tableclipboard.js

@@ -56,6 +56,8 @@ export default class TableClipboard extends Plugin {
 		this.listenTo( viewDocument, 'copy', ( evt, data ) => this._onCopyCut( evt, data ) );
 		this.listenTo( viewDocument, 'cut', ( evt, data ) => this._onCopyCut( evt, data ) );
 		this.listenTo( editor.model, 'insertContent', ( evt, args ) => this._onInsertContent( evt, ...args ), { priority: 'high' } );
+
+		this.decorate( '_replaceTableSlotCell' );
 	}
 
 	/**
@@ -164,7 +166,7 @@ export default class TableClipboard extends Plugin {
 			// Content table to which we insert a pasted table.
 			const selectedTable = selectedTableCells[ 0 ].findAncestor( 'table' );
 
-			const cellsToSelect = replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selectedTable, selection, writer );
+			const cellsToSelect = this._replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selectedTable, selection, writer );
 
 			if ( this.editor.plugins.get( 'TableSelection' ).isEnabled ) {
 				// Selection ranges must be sorted because the first and last selection ranges are considered
@@ -178,6 +180,137 @@ export default class TableClipboard extends Plugin {
 			}
 		} );
 	}
+
+	/**
+	 * Replaces the part of selectedTable with pastedTable.
+	 *
+	 * @private
+	 * @param {module:engine/model/element~Element} pastedTable
+	 * @param {Object} pastedDimensions
+	 * @param {Number} pastedDimensions.height
+	 * @param {Number} pastedDimensions.width
+	 * @param {module:engine/model/element~Element} selectedTable
+	 * @param {Object} selection
+	 * @param {Number} selection.firstColumn
+	 * @param {Number} selection.firstRow
+	 * @param {Number} selection.lastColumn
+	 * @param {Number} selection.lastRow
+	 * @param {module:engine/model/writer~Writer} writer
+	 * @returns {Array.<module:engine/model/element~Element>}
+	 */
+	_replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selectedTable, selection, writer ) {
+		const { width: pastedWidth, height: pastedHeight } = pastedDimensions;
+
+		// Holds two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.
+		const pastedTableLocationMap = createLocationMap( pastedTable, pastedWidth, pastedHeight );
+
+		const selectedTableMap = [ ...new TableWalker( selectedTable, {
+			startRow: selection.firstRow,
+			endRow: selection.lastRow,
+			startColumn: selection.firstColumn,
+			endColumn: selection.lastColumn,
+			includeAllSlots: true
+		} ) ];
+
+		// Selection must be set to pasted cells (some might be removed or new created).
+		const cellsToSelect = [];
+
+		// Store next cell insert position.
+		let insertPosition;
+
+		// 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 tableSlot of selectedTableMap ) {
+			const { row, column } = tableSlot;
+
+			// Save the insert position for current row start.
+			if ( column === selection.firstColumn ) {
+				insertPosition = tableSlot.getPositionBefore();
+			}
+
+			// Map current table slot location to an pasted table slot location.
+			const pastedRow = row - selection.firstRow;
+			const pastedColumn = column - selection.firstColumn;
+			const pastedCell = pastedTableLocationMap[ pastedRow % pastedHeight ][ pastedColumn % pastedWidth ];
+
+			// 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 ? writer.cloneElement( pastedCell ) : null;
+
+			// Replace the cell from the current slot with new table cell.
+			const newTableCell = this._replaceTableSlotCell( tableSlot, cellToInsert, insertPosition, writer );
+
+			// The cell was only removed.
+			if ( !newTableCell ) {
+				continue;
+			}
+
+			// Trim the cell if it's row/col-spans would exceed selection area.
+			trimTableCellIfNeeded( newTableCell, row, column, selection.lastRow, selection.lastColumn, writer );
+
+			cellsToSelect.push( newTableCell );
+
+			insertPosition = writer.createPositionAfter( newTableCell );
+		}
+
+		// If there are any headings, all the cells that overlap from heading must be splitted.
+		const headingRows = parseInt( selectedTable.getAttribute( 'headingRows' ) || 0 );
+		const headingColumns = parseInt( selectedTable.getAttribute( 'headingColumns' ) || 0 );
+
+		const areHeadingRowsIntersectingSelection = selection.firstRow < headingRows && headingRows <= selection.lastRow;
+		const areHeadingColumnsIntersectingSelection = selection.firstColumn < headingColumns && headingColumns <= selection.lastColumn;
+
+		if ( areHeadingRowsIntersectingSelection ) {
+			const columnsLimit = { first: selection.firstColumn, last: selection.lastColumn };
+			const newCells = doHorizontalSplit( selectedTable, headingRows, columnsLimit, writer, selection.firstRow );
+
+			cellsToSelect.push( ...newCells );
+		}
+
+		if ( areHeadingColumnsIntersectingSelection ) {
+			const rowsLimit = { first: selection.firstRow, last: selection.lastRow };
+			const newCells = doVerticalSplit( selectedTable, headingColumns, rowsLimit, writer );
+
+			cellsToSelect.push( ...newCells );
+		}
+
+		return cellsToSelect;
+	}
+
+	/**
+	 * Replaces a single table slot.
+	 *
+	 * @private
+	 * @param {module:table/tablewalker~TableSlot} tableSlot
+	 * @param {module:engine/model/element~Element} cellToInsert
+	 * @param {module:engine/model/position~Position} insertPosition
+	 * @param {module:engine/model/writer~Writer} writer
+	 * @returns {module:engine/model/element~Element|null} Inserted table cell or null if slot should remain empty.
+	 */
+	_replaceTableSlotCell( tableSlot, cellToInsert, insertPosition, writer ) {
+		const { cell, isAnchor } = tableSlot;
+
+		// 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 ( isAnchor ) {
+			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 ( !cellToInsert ) {
+			return null;
+		}
+
+		writer.insert( cellToInsert, insertPosition );
+
+		return cellToInsert;
+	}
 }
 
 // Prepares a table for pasting and returns adjusted selection dimensions.
@@ -246,109 +379,6 @@ function prepareTableForPasting( selectedTableCells, pastedDimensions, writer, t
 	return selection;
 }
 
-// Replaces the part of selectedTable with pastedTable.
-//
-// @param {module:engine/model/element~Element} pastedTable
-// @param {Object} pastedDimensions
-// @param {Number} pastedDimensions.height
-// @param {Number} pastedDimensions.width
-// @param {module:engine/model/element~Element} selectedTable
-// @param {Object} selection
-// @param {Number} selection.firstColumn
-// @param {Number} selection.firstRow
-// @param {Number} selection.lastColumn
-// @param {Number} selection.lastRow
-// @param {module:engine/model/writer~Writer} writer
-// @returns {Array.<module:engine/model/element~Element>}
-function replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selectedTable, selection, writer ) {
-	const { width: pastedWidth, height: pastedHeight } = pastedDimensions;
-
-	// Holds two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.
-	const pastedTableLocationMap = createLocationMap( pastedTable, pastedWidth, pastedHeight );
-
-	const selectedTableMap = [ ...new TableWalker( selectedTable, {
-		startRow: selection.firstRow,
-		endRow: selection.lastRow,
-		startColumn: selection.firstColumn,
-		endColumn: selection.lastColumn,
-		includeAllSlots: true
-	} ) ];
-
-	// Selection must be set to pasted cells (some might be removed or new created).
-	const cellsToSelect = [];
-
-	// Store next cell insert position.
-	let insertPosition;
-
-	// 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 tableSlot of selectedTableMap ) {
-		const { row, column, cell, isAnchor } = tableSlot;
-
-		// Save the insert position for current row start.
-		if ( column === selection.firstColumn ) {
-			insertPosition = tableSlot.getPositionBefore();
-		}
-
-		// 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 ( isAnchor ) {
-			writer.remove( cell );
-		}
-
-		// Map current table slot location to an pasted table slot location.
-		const pastedRow = row - selection.firstRow;
-		const pastedColumn = column - selection.firstColumn;
-		const pastedCell = pastedTableLocationMap[ pastedRow % pastedHeight ][ pastedColumn % pastedWidth ];
-
-		// 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;
-		}
-
-		// 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 = writer.cloneElement( pastedCell );
-
-		// Trim the cell if it's row/col-spans would exceed selection area.
-		trimTableCellIfNeeded( cellToInsert, row, column, selection.lastRow, selection.lastColumn, writer );
-
-		writer.insert( cellToInsert, insertPosition );
-		cellsToSelect.push( cellToInsert );
-
-		insertPosition = writer.createPositionAfter( cellToInsert );
-	}
-
-	// If there are any headings, all the cells that overlap from heading must be splitted.
-	const headingRows = parseInt( selectedTable.getAttribute( 'headingRows' ) || 0 );
-	const headingColumns = parseInt( selectedTable.getAttribute( 'headingColumns' ) || 0 );
-
-	const areHeadingRowsIntersectingSelection = selection.firstRow < headingRows && headingRows <= selection.lastRow;
-	const areHeadingColumnsIntersectingSelection = selection.firstColumn < headingColumns && headingColumns <= selection.lastColumn;
-
-	if ( areHeadingRowsIntersectingSelection ) {
-		const columnsLimit = { first: selection.firstColumn, last: selection.lastColumn };
-		const newCells = doHorizontalSplit( selectedTable, headingRows, columnsLimit, writer, selection.firstRow );
-
-		cellsToSelect.push( ...newCells );
-	}
-
-	if ( areHeadingColumnsIntersectingSelection ) {
-		const rowsLimit = { first: selection.firstRow, last: selection.lastRow };
-		const newCells = doVerticalSplit( selectedTable, headingColumns, rowsLimit, writer );
-
-		cellsToSelect.push( ...newCells );
-	}
-
-	return cellsToSelect;
-}
-
 // Expand table (in place) to expected size.
 function expandTableSize( table, expectedHeight, expectedWidth, tableUtils ) {
 	const tableWidth = tableUtils.getColumns( table );