Browse Source

Cells that overlap from headings after table paste should be splitted.

Kuba Niegowski 5 years ago
parent
commit
331c6d0968

+ 25 - 7
packages/ckeditor5-table/src/tableclipboard.js

@@ -316,7 +316,29 @@ function replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selected
 		insertPosition = writer.createPositionAfter( cellToInsert );
 		insertPosition = writer.createPositionAfter( cellToInsert );
 	}
 	}
 
 
-	writer.setSelection( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
+	// 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 );
+
+	if ( selection.firstRow < headingRows && headingRows <= selection.lastRow ) {
+		const columnsLimit = { first: selection.firstColumn, last: selection.lastColumn };
+		const newCells = doHorizontalSplit( selectedTable, headingRows, columnsLimit, writer, selection.firstRow );
+
+		cellsToSelect.push( ...newCells );
+	}
+
+	if ( selection.firstColumn < headingColumns && headingColumns <= selection.lastColumn ) {
+		const rowsLimit = { first: selection.firstRow, last: selection.lastRow };
+		const newCells = doVerticalSplit( selectedTable, headingColumns, rowsLimit, writer );
+
+		cellsToSelect.push( ...newCells );
+	}
+
+	const selectionRanges = cellsToSelect
+		.map( cell => writer.createRangeOn( cell ) )
+		.sort( ( a, b ) => a.start.isBefore( b.start ) ? -1 : 1 );
+
+	writer.setSelection( selectionRanges );
 }
 }
 
 
 // Expand table (in place) to expected size.
 // Expand table (in place) to expected size.
@@ -489,9 +511,7 @@ function doHorizontalSplit( table, splitRow, limitColumns, writer, startRow = 0
 	// Filter out cells that are not touching insides of the rectangular selection.
 	// Filter out cells that are not touching insides of the rectangular selection.
 	const cellsToSplit = overlappingCells.filter( ( { column, cellWidth } ) => isAffectedBySelection( column, cellWidth, limitColumns ) );
 	const cellsToSplit = overlappingCells.filter( ( { column, cellWidth } ) => isAffectedBySelection( column, cellWidth, limitColumns ) );
 
 
-	for ( const { cell } of cellsToSplit ) {
-		splitHorizontally( cell, splitRow, writer );
-	}
+	return cellsToSplit.map( ( { cell } ) => splitHorizontally( cell, splitRow, writer ) );
 }
 }
 
 
 function doVerticalSplit( table, splitColumn, limitRows, writer ) {
 function doVerticalSplit( table, splitColumn, limitRows, writer ) {
@@ -505,9 +525,7 @@ function doVerticalSplit( table, splitColumn, limitRows, writer ) {
 	// Filter out cells that are not touching insides of the rectangular selection.
 	// Filter out cells that are not touching insides of the rectangular selection.
 	const cellsToSplit = overlappingCells.filter( ( { row, cellHeight } ) => isAffectedBySelection( row, cellHeight, limitRows ) );
 	const cellsToSplit = overlappingCells.filter( ( { row, cellHeight } ) => isAffectedBySelection( row, cellHeight, limitRows ) );
 
 
-	for ( const { cell, column } of cellsToSplit ) {
-		splitVertically( cell, column, splitColumn, writer );
-	}
+	return cellsToSplit.map( ( { cell, column } ) => splitVertically( cell, column, splitColumn, writer ) );
 }
 }
 
 
 // Checks if cell at given row (column) is affected by a rectangular selection defined by first/last column (row).
 // Checks if cell at given row (column) is affected by a rectangular selection defined by first/last column (row).

+ 4 - 0
packages/ckeditor5-table/src/utils/common.js

@@ -50,11 +50,15 @@ export function updateNumericAttribute( key, value, item, writer, defaultValue =
  * @param {module:engine/model/writer~Writer} writer The model writer.
  * @param {module:engine/model/writer~Writer} writer The model writer.
  * @param {module:engine/model/position~Position} insertPosition The position at which the table cell should be inserted.
  * @param {module:engine/model/position~Position} insertPosition The position at which the table cell should be inserted.
  * @param {Object} attributes The element attributes.
  * @param {Object} attributes The element attributes.
+ * @returns {module:engine/model/element~Element} Created table cell.
  */
  */
 export function createEmptyTableCell( writer, insertPosition, attributes = {} ) {
 export function createEmptyTableCell( writer, insertPosition, attributes = {} ) {
 	const tableCell = writer.createElement( 'tableCell', attributes );
 	const tableCell = writer.createElement( 'tableCell', attributes );
+
 	writer.insertElement( 'paragraph', tableCell );
 	writer.insertElement( 'paragraph', tableCell );
 	writer.insert( tableCell, insertPosition );
 	writer.insert( tableCell, insertPosition );
+
+	return tableCell;
 }
 }
 
 
 /**
 /**

+ 10 - 2
packages/ckeditor5-table/src/utils/structure.js

@@ -138,6 +138,7 @@ export function getVerticallyOverlappingCells( table, overlapRow, startRow = 0 )
  * @param {module:engine/model/element~Element} tableCell
  * @param {module:engine/model/element~Element} tableCell
  * @param {Number} splitRow
  * @param {Number} splitRow
  * @param {module:engine/model/writer~Writer} writer
  * @param {module:engine/model/writer~Writer} writer
+ * @returns {module:engine/model/element~Element} Created table cell.
  */
  */
 export function splitHorizontally( tableCell, splitRow, writer ) {
 export function splitHorizontally( tableCell, splitRow, writer ) {
 	const tableRow = tableCell.parent;
 	const tableRow = tableCell.parent;
@@ -164,6 +165,7 @@ export function splitHorizontally( tableCell, splitRow, writer ) {
 	const endRow = startRow + newRowspan;
 	const endRow = startRow + newRowspan;
 	const tableMap = [ ...new TableWalker( table, { startRow, endRow, includeAllSlots: true } ) ];
 	const tableMap = [ ...new TableWalker( table, { startRow, endRow, includeAllSlots: true } ) ];
 
 
+	let newCell = null;
 	let columnIndex;
 	let columnIndex;
 
 
 	for ( const tableSlot of tableMap ) {
 	for ( const tableSlot of tableMap ) {
@@ -174,12 +176,14 @@ export function splitHorizontally( tableCell, splitRow, writer ) {
 		}
 		}
 
 
 		if ( columnIndex !== undefined && columnIndex === column && row === endRow ) {
 		if ( columnIndex !== undefined && columnIndex === column && row === endRow ) {
-			createEmptyTableCell( writer, tableSlot.getPositionBefore(), newCellAttributes );
+			newCell = createEmptyTableCell( writer, tableSlot.getPositionBefore(), newCellAttributes );
 		}
 		}
 	}
 	}
 
 
 	// Update the rowspan attribute after updating table.
 	// Update the rowspan attribute after updating table.
 	updateNumericAttribute( 'rowspan', newRowspan, tableCell, writer );
 	updateNumericAttribute( 'rowspan', newRowspan, tableCell, writer );
+
+	return newCell;
 }
 }
 
 
 /**
 /**
@@ -232,6 +236,7 @@ export function getHorizontallyOverlappingCells( table, overlapColumn ) {
  * @param {Number} columnIndex The table cell column index.
  * @param {Number} columnIndex The table cell column index.
  * @param {Number} splitColumn The index of column to split cell on.
  * @param {Number} splitColumn The index of column to split cell on.
  * @param {module:engine/model/writer~Writer} writer
  * @param {module:engine/model/writer~Writer} writer
+ * @returns {module:engine/model/element~Element} Created table cell.
  */
  */
 export function splitVertically( tableCell, columnIndex, splitColumn, writer ) {
 export function splitVertically( tableCell, columnIndex, splitColumn, writer ) {
 	const colspan = parseInt( tableCell.getAttribute( 'colspan' ) );
 	const colspan = parseInt( tableCell.getAttribute( 'colspan' ) );
@@ -250,9 +255,12 @@ export function splitVertically( tableCell, columnIndex, splitColumn, writer ) {
 		newCellAttributes.rowspan = rowspan;
 		newCellAttributes.rowspan = rowspan;
 	}
 	}
 
 
-	createEmptyTableCell( writer, writer.createPositionAfter( tableCell ), newCellAttributes );
+	const newCell = createEmptyTableCell( writer, writer.createPositionAfter( tableCell ), newCellAttributes );
+
 	// Update the colspan attribute after updating table.
 	// Update the colspan attribute after updating table.
 	updateNumericAttribute( 'colspan', newColspan, tableCell, writer );
 	updateNumericAttribute( 'colspan', newColspan, tableCell, writer );
+
+	return newCell;
 }
 }
 
 
 /**
 /**

+ 13 - 2
packages/ckeditor5-table/tests/manual/tablemocking.html

@@ -12,10 +12,16 @@
 		box-sizing: border-box;
 		box-sizing: border-box;
 		margin: 10px 0;
 		margin: 10px 0;
 	}
 	}
-	pre,code {
+	pre, code {
 		font-size: 11px;
 		font-size: 11px;
 		font-family: Menlo, Consolas, Lucida Console, Courier New, dejavu sans mono, monospace;
 		font-family: Menlo, Consolas, Lucida Console, Courier New, dejavu sans mono, monospace;
 	}
 	}
+	#table-tools pre {
+		background: hsl( 0, 0%, 95% );
+		max-height:300px;
+		overflow: auto;
+		padding: 10px;
+	}
 	.diff-add {
 	.diff-add {
 		color: hsl( 120, 70%, 35% );
 		color: hsl( 120, 70%, 35% );
 	}
 	}
@@ -61,4 +67,9 @@
 <div id="editor">
 <div id="editor">
 </div>
 </div>
 
 
-<pre id="ascii-art"></pre>
+<div id="table-tools">
+	<pre id="ascii-art"></pre>
+
+	<h3>Clipboard preview:</h3>
+	<pre id="clipboard"></pre>
+</div>

+ 4 - 0
packages/ckeditor5-table/tests/manual/tablemocking.js

@@ -33,6 +33,10 @@ ClassicEditor
 		const asciiOut = document.getElementById( 'ascii-art' );
 		const asciiOut = document.getElementById( 'ascii-art' );
 		const modelData = document.getElementById( 'model-data' );
 		const modelData = document.getElementById( 'model-data' );
 
 
+		editor.editing.view.document.on( 'paste', ( evt, data ) => {
+			document.getElementById( 'clipboard' ).innerText = data.dataTransfer.getData( 'text/html' ).replace( />(?=<)/g, '>\n' );
+		} );
+
 		document.getElementById( 'clear-content' ).addEventListener( 'click', () => {
 		document.getElementById( 'clear-content' ).addEventListener( 'click', () => {
 			editor.setData( '' );
 			editor.setData( '' );
 		} );
 		} );

+ 0 - 2
packages/ckeditor5-table/tests/manual/tablemocking.md

@@ -22,5 +22,3 @@ setModelData( model, modelTable( [
     [ '40', '41', '42', '43', '44' ]
     [ '40', '41', '42', '43', '44' ]
 ] ) );
 ] ) );
 ```
 ```
-
-**Note:** Cell content is ignored while generating ASCII-art and `modelTableData`.  

+ 69 - 0
packages/ckeditor5-table/tests/tableclipboard-paste.js

@@ -3385,6 +3385,75 @@ describe( 'table clipboard', () => {
 				}
 				}
 			);
 			);
 		} );
 		} );
+
+		describe( 'headings overlapping selected area', () => {
+			// TODO more tests
+
+			it( 'should split cells that overlap from headings', () => {
+				setModelData( model, modelTable( [
+					[ '00[]', '01', '02', '03', '04' ],
+					[ '10', '11', '12', '13', '14' ],
+					[ '20', '21', '22', '23', '24' ],
+					[ '30', '31', '32', '33', '34' ],
+					[ '40', '41', '42', '43', '44' ]
+				], { headingRows: 2, headingColumns: 2 } ) );
+
+				// +----+----+----+----+
+				// | aa           | ad |
+				// +              +----+
+				// |              | bd |
+				// +              +----+
+				// |              | cd |
+				// +----+----+----+----+
+				// | da | db | dc | dd |
+				// +----+----+----+----+
+				pasteTable( [
+					[ { contents: 'aa', colspan: 3, rowspan: 3 }, 'ad' ],
+					[ 'bd' ],
+					[ 'cd' ],
+					[ 'da', 'db', 'dc', 'dd' ]
+				] );
+
+				// +----+----+----+----+----+
+				// | aa      |    | ad | 04 |
+				// +         +    +----+----+
+				// |         |    | bd | 14 |
+				// +----+----+----+----+----+ <-- heading rows
+				// |         |    | cd | 24 |
+				// +----+----+----+----+----+
+				// | da | db | dc | dd | 34 |
+				// +----+----+----+----+----+
+				// | 40 | 41 | 42 | 43 | 44 |
+				// +----+----+----+----+----+
+				//           ^-- heading columns
+				assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+					[ { contents: 'aa', colspan: 2, rowspan: 2 }, { contents: '', rowspan: 2 }, 'ad', '04' ],
+					[ 'bd', '14' ],
+					[ { contents: '', colspan: 2 }, '', 'cd', '24' ],
+					[ 'da', 'db', 'dc', 'dd', '34' ],
+					[ '40', '41', '42', '43', '44' ]
+				], { headingRows: 2, headingColumns: 2 } ) );
+
+				const selectionRanges = Array.from( model.document.selection.getRanges() );
+				const selectedCellsPaths = selectionRanges.map( ( { start } ) => start.path );
+
+				// Note that order of ranges is also important.
+				expect( selectionRanges.length ).to.be.equal( 11 );
+				expect( selectedCellsPaths ).to.deep.equal( [
+					[ 0, 0, 0 ],
+					[ 0, 0, 1 ],
+					[ 0, 0, 2 ],
+					[ 0, 1, 0 ],
+					[ 0, 2, 0 ],
+					[ 0, 2, 1 ],
+					[ 0, 2, 2 ],
+					[ 0, 3, 0 ],
+					[ 0, 3, 1 ],
+					[ 0, 3, 2 ],
+					[ 0, 3, 3 ]
+				] );
+			} );
+		} );
 	} );
 	} );
 
 
 	describe( 'Clipboard integration - paste (content scenarios)', () => {
 	describe( 'Clipboard integration - paste (content scenarios)', () => {