Przeglądaj źródła

Refactor utility methods.

Maciej Gołaszewski 5 lat temu
rodzic
commit
c67e84d0f8

+ 7 - 2
packages/ckeditor5-table/src/commands/setheaderrowcommand.js

@@ -10,7 +10,7 @@
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import Command from '@ckeditor/ckeditor5-core/src/command';
 
 
 import { findAncestor, updateNumericAttribute } from './utils';
 import { findAncestor, updateNumericAttribute } from './utils';
-import { cutCellsHorizontallyAt, getRowIndexes, getSelectionAffectedTableCells } from '../utils';
+import { getHorizontallyOverlappingCells, getRowIndexes, getSelectionAffectedTableCells, splitHorizontally } from '../utils';
 
 
 /**
 /**
  * The header row command.
  * The header row command.
@@ -76,7 +76,12 @@ export default class SetHeaderRowCommand extends Command {
 			if ( headingRowsToSet ) {
 			if ( headingRowsToSet ) {
 				// Changing heading rows requires to check if any of a heading cell is overlapping vertically the table head.
 				// Changing heading rows requires to check if any of a heading cell is overlapping vertically the table head.
 				// Any table cell that has a rowspan attribute > 1 will not exceed the table head so we need to fix it in rows below.
 				// Any table cell that has a rowspan attribute > 1 will not exceed the table head so we need to fix it in rows below.
-				cutCellsHorizontallyAt( table, headingRowsToSet, currentHeadingRows, writer );
+				const startRow = headingRowsToSet > currentHeadingRows ? currentHeadingRows : 0;
+				const overlappingCells = getHorizontallyOverlappingCells( table, headingRowsToSet, startRow );
+
+				for ( const { cell } of overlappingCells ) {
+					splitHorizontally( cell, headingRowsToSet, writer );
+				}
 			}
 			}
 
 
 			updateNumericAttribute( 'headingRows', headingRowsToSet, table, writer, 0 );
 			updateNumericAttribute( 'headingRows', headingRowsToSet, table, writer, 0 );

+ 57 - 11
packages/ckeditor5-table/src/tableclipboard.js

@@ -11,7 +11,15 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
 import TableSelection from './tableselection';
 import TableSelection from './tableselection';
 import TableWalker from './tablewalker';
 import TableWalker from './tablewalker';
-import { cutCellsHorizontallyAt, cutCellsVerticallyAt, getColumnIndexes, getRowIndexes, isSelectionRectangular } from './utils';
+import {
+	getColumnIndexes,
+	getRowIndexes,
+	getHorizontallyOverlappingCells,
+	getVerticallyOverlappingCells,
+	isSelectionRectangular,
+	splitHorizontally,
+	splitVertically
+} from './utils';
 import { findAncestor } from './commands/utils';
 import { findAncestor } from './commands/utils';
 import { cropTableToDimensions } from './tableselection/croptable';
 import { cropTableToDimensions } from './tableselection/croptable';
 import TableUtils from './tableutils';
 import TableUtils from './tableutils';
@@ -133,7 +141,7 @@ export default class TableClipboard extends Plugin {
 			// Currently not handled. The selected table content should be trimmed to a rectangular selection.
 			// Currently not handled. The selected table content should be trimmed to a rectangular selection.
 			// See: https://github.com/ckeditor/ckeditor5/issues/6122.
 			// See: https://github.com/ckeditor/ckeditor5/issues/6122.
 			if ( !isSelectionRectangular( selectedTableCells, tableUtils ) ) {
 			if ( !isSelectionRectangular( selectedTableCells, tableUtils ) ) {
-				prepareLandingPlace( selectedTableCells, writer );
+				makeSelectedCellsRectangular( selectedTableCells, writer );
 			}
 			}
 
 
 			const { last: lastColumnOfSelection, first: firstColumnOfSelection } = getColumnIndexes( selectedTableCells );
 			const { last: lastColumnOfSelection, first: firstColumnOfSelection } = getColumnIndexes( selectedTableCells );
@@ -298,21 +306,59 @@ function createLocationMap( table, width, height ) {
 	return map;
 	return map;
 }
 }
 
 
-function prepareLandingPlace( selectedTableCells, writer ) {
+// Make selected cell rectangular by splitting the cells that stand out from a rectangular selection.
+function makeSelectedCellsRectangular( selectedTableCells, writer ) {
 	const table = findAncestor( 'table', selectedTableCells[ 0 ] );
 	const table = findAncestor( 'table', selectedTableCells[ 0 ] );
 
 
-	const { first: firstRow, last: lastRow } = getRowIndexes( selectedTableCells );
-	const { first: firstColumn, last: lastColumn } = getColumnIndexes( selectedTableCells );
+	const rowIndexes = getRowIndexes( selectedTableCells );
+	const columnIndexes = getColumnIndexes( selectedTableCells );
+	const { first: firstRow, last: lastRow } = rowIndexes;
+	const { first: firstColumn, last: lastColumn } = columnIndexes;
 
 
-	if ( firstColumn > 0 ) {
-		cutCellsVerticallyAt( table, firstColumn, 0, writer, { firstRow, firstColumn: 0, lastRow, lastColumn: 1000 } );
+	doVerticalSplit( table, firstColumn, rowIndexes, writer ); // TODO: Could use startColumn = 0.
+	doVerticalSplit( table, lastColumn + 1, rowIndexes, writer ); // TODO: Could use startColumn = firstColumn.
+
+	doHorizontalSplit( table, firstRow, columnIndexes, writer, 0 );
+	doHorizontalSplit( table, lastRow + 1, columnIndexes, writer, firstRow );
+}
+
+function doHorizontalSplit( table, splitRow, columnIndexes, writer, startRow ) {
+	if ( splitRow < 1 ) {
+		return;
 	}
 	}
 
 
-	cutCellsVerticallyAt( table, lastColumn + 1, firstColumn, writer, { firstRow, firstColumn: 0, lastRow, lastColumn: 1000 } );
+	const overlappingCells = getHorizontallyOverlappingCells( table, splitRow, startRow );
+
+	const { first, last } = columnIndexes;
+	const cellsToSplit = overlappingCells.filter( ( { column, colspan } ) => isBetweenColumns( column, colspan, first, last ) );
 
 
-	if ( firstRow > 0 ) {
-		cutCellsHorizontallyAt( table, firstRow, 0, writer, { firstRow: 0, firstColumn, lastRow: 1000, lastColumn } );
+	for ( const { cell } of cellsToSplit ) {
+		splitHorizontally( cell, splitRow, writer );
 	}
 	}
+}
+
+function doVerticalSplit( table, splitColumn, rowIndexes, writer ) {
+	if ( splitColumn < 1 ) {
+		return;
+	}
+
+	const overlappingCells = getVerticallyOverlappingCells( table, splitColumn );
+
+	const { first, last } = rowIndexes;
+	const cellsToSplit = overlappingCells.filter( ( { row, rowspan } ) => isBetweenRows( row, rowspan, first, last ) );
+
+	for ( const { cell, column } of cellsToSplit ) {
+		splitVertically( cell, column, splitColumn, writer );
+	}
+}
+
+function isBetweenRows( row, rowspan, first, last ) {
+	const endRowOfCell = row + rowspan - 1;
+	return first <= endRowOfCell && endRowOfCell <= last;
+}
+
+function isBetweenColumns( column, colspan, first, last ) {
+	const endColumnOfCell = column + colspan - 1;
 
 
-	cutCellsHorizontallyAt( table, lastRow + 1, firstRow, writer, { firstRow: 0, firstColumn, lastRow: 1000, lastColumn } );
+	return first <= endColumnOfCell && endColumnOfCell <= last;
 }
 }

+ 83 - 94
packages/ckeditor5-table/src/utils.js

@@ -188,7 +188,7 @@ export function getColumnIndexes( tableCells ) {
  *   │ a │ b │ c │ d │
  *   │ a │ b │ c │ d │
  *   ├───┴───┼───┤   │
  *   ├───┴───┼───┤   │
  *   │ e     │ f │   │
  *   │ e     │ f │   │
- *          ├───┼───┤
+ *          ├───┼───┤
  *   │       │ g │ h │
  *   │       │ g │ h │
  *   └───────┴───┴───┘
  *   └───────┴───┴───┘
  *
  *
@@ -247,88 +247,63 @@ export function isSelectionRectangular( selectedTableCells, tableUtils ) {
 	return areaOfValidSelection == areaOfSelectedCells;
 	return areaOfValidSelection == areaOfSelectedCells;
 }
 }
 
 
-// TODO: refactor it to a better, general util.
-export function cutCellsHorizontallyAt( table, headingRowsToSet, currentHeadingRows, writer, boundingBox ) {
-	const overlappingCells = getHorizontallyOverlappingCells( table, headingRowsToSet, currentHeadingRows );
-
-	let cellsToSplit;
-
-	if ( boundingBox === undefined ) {
-		cellsToSplit = overlappingCells;
-	} else {
-		cellsToSplit = overlappingCells.filter( filterToBoundingBox( boundingBox ) );
-	}
-
-	for ( const { cell } of cellsToSplit ) {
-		splitHorizontally( cell, headingRowsToSet, writer );
-	}
-}
-
-// TODO: refactor it to a better, general util.
-export function cutCellsVerticallyAt( table, headingColumnsToSet, currentHeadingColumns, writer, boundingBox ) {
-	const overlappingCells = getVerticallyOverlappingCells( table, headingColumnsToSet, currentHeadingColumns );
-
-	let cellsToSplit;
-
-	if ( boundingBox === undefined ) {
-		cellsToSplit = overlappingCells;
-	} else {
-		cellsToSplit = overlappingCells.filter( filterToBoundingBox( boundingBox ) );
-	}
-
-	for ( const { cell, column } of cellsToSplit ) {
-		splitVertically( cell, column, headingColumnsToSet, writer );
-	}
-}
-
-// TODO: better fit to bounding box to match criteria.. should check also spans because sometimes we need to split them.
-function filterToBoundingBox( boundingBox ) {
-	const { firstRow, firstColumn, lastRow, lastColumn } = boundingBox;
-
-	return ( { row, column, colspan, rowspan } ) => {
-		return ( ( firstRow <= row + rowspan - 1 ) && ( row + rowspan - 1 <= lastRow ) ) &&
-			( firstColumn <= column + colspan - 1 && column + colspan - 1 <= lastColumn );
-	};
-}
-
-// Returns cells that span beyond the new heading section.
-//
-// @param {module:engine/model/element~Element} table The table to check.
-// @param {Number} headingRowsToSet New heading rows attribute.
-// @param {Number} currentHeadingRows Current heading rows attribute.
-// @returns {Array.<module:engine/model/element~Element>}
-function getHorizontallyOverlappingCells( table, headingRowsToSet, currentHeadingRows ) {
+/**
+ * Returns cells that starts below and overlaps a given row.
+ *
+ * In a table below, passing `overlapRow = 3`
+ *
+ *       ┌───┬───┬───┬───┬───┐
+ *    0  │ a │ b │ c │ d │ e │
+ *       │   ├───┼───┼───┼───┤
+ *    1  │   │ f │ g │ h │ i │
+ *       ├───┤   ├───┼───┤   │
+ *    2  │ j │   │ k │ l │   │
+ *       │   │   │   ├───┼───┤
+ *    3  │   │   │   │ m │ n │  <- overlap row to check
+ *       ├───┼───┤   │   ├───│
+ *    4  │ o │ p │   │   │ q │
+ *       └───┴───┴───┴───┴───┘
+ *
+ * will return cells: "j", "f", "k".
+ *
+ * @param {module:engine/model/element~Element} table The table to check.
+ * @param {Number} overlapRow
+ * @param {Number} [startRow=0] A row to start analysis if it is known where.
+ * @returns {Array.<module:table/tablewalker~TableWalkerValue>}
+ */
+export function getHorizontallyOverlappingCells( table, overlapRow, startRow = 0 ) {
 	const cellsToSplit = [];
 	const cellsToSplit = [];
 
 
-	const startAnalysisRow = headingRowsToSet > currentHeadingRows ? currentHeadingRows : 0;
 	// We're analyzing only when headingRowsToSet > 0.
 	// We're analyzing only when headingRowsToSet > 0.
-	const endAnalysisRow = headingRowsToSet - 1;
+	const endAnalysisRow = overlapRow - 1;
 
 
-	const tableWalker = new TableWalker( table, { startRow: startAnalysisRow, endRow: endAnalysisRow } );
+	const tableWalker = new TableWalker( table, { startRow, endRow: endAnalysisRow } );
 
 
-	for ( const twv of tableWalker ) {
-		const { row, rowspan } = twv;
+	for ( const slotInfo of tableWalker ) {
+		const { row, rowspan } = slotInfo;
 
 
-		if ( rowspan > 1 && row + rowspan > headingRowsToSet ) {
-			cellsToSplit.push( twv );
+		if ( rowspan > 1 && row + rowspan > overlapRow ) {
+			cellsToSplit.push( slotInfo );
 		}
 		}
 	}
 	}
 
 
 	return cellsToSplit;
 	return cellsToSplit;
 }
 }
 
 
-// Splits the table cell horizontally.
-//
-// @param {module:engine/model/element~Element} tableCell
-// @param {Number} headingRows
-// @param {module:engine/model/writer~Writer} writer
-function splitHorizontally( tableCell, headingRows, writer ) {
+/**
+ * Splits the table cell horizontally.
+ *
+ * @param {module:engine/model/element~Element} tableCell
+ * @param {Number} splitRow
+ * @param {module:engine/model/writer~Writer} writer
+ */
+export function splitHorizontally( tableCell, splitRow, writer ) {
 	const tableRow = tableCell.parent;
 	const tableRow = tableCell.parent;
 	const table = tableRow.parent;
 	const table = tableRow.parent;
 	const rowIndex = tableRow.index;
 	const rowIndex = tableRow.index;
 
 
 	const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) );
 	const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) );
-	const newRowspan = headingRows - rowIndex;
+	const newRowspan = splitRow - rowIndex;
 
 
 	const attributes = {};
 	const attributes = {};
 
 
@@ -367,45 +342,59 @@ function splitHorizontally( tableCell, headingRows, writer ) {
 	updateNumericAttribute( 'rowspan', newRowspan, tableCell, writer );
 	updateNumericAttribute( 'rowspan', newRowspan, tableCell, writer );
 }
 }
 
 
-// Returns cells that span beyond the new heading section.
-//
-// @param {module:engine/model/element~Element} table The table to check.
-// @param {Number} headingColumnsToSet New heading columns attribute.
-// @param {Number} currentHeadingColumns Current heading columns attribute.
-// @returns {Array.<module:engine/model/element~Element>}
-function getVerticallyOverlappingCells( table, headingColumnsToSet, currentHeadingColumns ) {
+/**
+ * Returns cells that starts before and overlaps a given column.
+ *
+ * In a table below, passing `overlapColumn = 3`
+ *
+ *      0   1   2   3   4
+ *    ┌───────┬───────┬───┐
+ *    │ a     │ b     │ c │
+ *    │───┬───┴───────┼───┤
+ *    │ d │ e         │ f │
+ *    ├───┼───┬───────┴───┤
+ *    │ g │ h │ i         │
+ *    ├───┼───┼───┬───────┤
+ *    │ j │ k │ l │ m     │
+ *    ├───┼───┴───┼───┬───┤
+ *    │ n │ o     │ p │ q │
+ *    └───┴───────┴───┴───┘
+ *                  ^
+ *                  Overlap column to check
+ *
+ * will return cells: "b", "e", "i".
+ *
+ * @param {module:engine/model/element~Element} table The table to check.
+ * @param {Number} overlapColumn New heading columns attribute.
+ * @returns {Array.<module:table/tablewalker~TableWalkerValue>}
+ */
+export function getVerticallyOverlappingCells( table, overlapColumn ) {
 	const cellsToSplit = [];
 	const cellsToSplit = [];
 
 
-	const startAnalysisColumn = headingColumnsToSet > currentHeadingColumns ? currentHeadingColumns : 0;
-	// We're analyzing only when headingColumnsToSet > 0.
-	const endAnalysisColumn = headingColumnsToSet - 1;
-
-	// todo: end/start column
 	const tableWalker = new TableWalker( table );
 	const tableWalker = new TableWalker( table );
 
 
-	for ( const twv of tableWalker ) {
-		const { column, colspan } = twv;
-		// Skip slots outside the cropped area.
-		// Could use startColumn, endColumn. See: https://github.com/ckeditor/ckeditor5/issues/6785.
-		if ( startAnalysisColumn > column || column > endAnalysisColumn ) {
-			continue;
-		}
-		if ( colspan > 1 && column + colspan > headingColumnsToSet ) {
-			cellsToSplit.push( twv );
+	for ( const slotInfo of tableWalker ) {
+		const { column, colspan } = slotInfo;
+		const endColumn = column + colspan;
+
+		if ( column < overlapColumn && overlapColumn < endColumn ) {
+			cellsToSplit.push( slotInfo );
 		}
 		}
 	}
 	}
 
 
 	return cellsToSplit;
 	return cellsToSplit;
 }
 }
 
 
-// Splits the table cell vertically.
-//
-// @param {module:engine/model/element~Element} tableCell
-// @param {Number} headingColumns
-// @param {module:engine/model/writer~Writer} writer
-function splitVertically( tableCell, columnIndex, headingColumns, writer ) {
+/**
+ * Splits the table cell vertically.
+ *
+ * @param {module:engine/model/element~Element} tableCell
+ * @param {Number} splitColumn
+ * @param {module:engine/model/writer~Writer} writer
+ */
+export function splitVertically( tableCell, columnIndex, splitColumn, writer ) {
 	const colspan = parseInt( tableCell.getAttribute( 'colspan' ) );
 	const colspan = parseInt( tableCell.getAttribute( 'colspan' ) );
-	const newColspan = headingColumns - columnIndex;
+	const newColspan = splitColumn - columnIndex;
 
 
 	const attributes = {};
 	const attributes = {};