8
0
Просмотр исходного кода

Merge pull request #6748 from ckeditor/i/6549

Other (table): Adding a new row in the table copies the structure of the selected row. Closes #6549.
Maciej 5 лет назад
Родитель
Сommit
9f2091158e

+ 1 - 1
packages/ckeditor5-table/src/commands/insertrowcommand.js

@@ -78,6 +78,6 @@ export default class InsertRowCommand extends Command {
 		const row = insertAbove ? rowIndexes.first : rowIndexes.last;
 		const row = insertAbove ? rowIndexes.first : rowIndexes.last;
 		const table = findAncestor( 'table', affectedTableCells[ 0 ] );
 		const table = findAncestor( 'table', affectedTableCells[ 0 ] );
 
 
-		tableUtils.insertRows( table, { rows: 1, at: insertAbove ? row : row + 1 } );
+		tableUtils.insertRows( table, { at: insertAbove ? row : row + 1, copyStructureFromAbove: !insertAbove } );
 	}
 	}
 }
 }

+ 47 - 20
packages/ckeditor5-table/src/tableutils.js

@@ -116,12 +116,19 @@ export default class TableUtils extends Plugin {
 	 * @param {Object} options
 	 * @param {Object} options
 	 * @param {Number} [options.at=0] The row index at which the rows will be inserted.
 	 * @param {Number} [options.at=0] The row index at which the rows will be inserted.
 	 * @param {Number} [options.rows=1] The number of rows to insert.
 	 * @param {Number} [options.rows=1] The number of rows to insert.
+	 * @param {Boolean|undefined} [options.copyStructureFromAbove] The flag for copying row structure. Note that
+	 * the row structure will not be copied if this option is not provided.
 	 */
 	 */
 	insertRows( table, options = {} ) {
 	insertRows( table, options = {} ) {
 		const model = this.editor.model;
 		const model = this.editor.model;
 
 
 		const insertAt = options.at || 0;
 		const insertAt = options.at || 0;
 		const rowsToInsert = options.rows || 1;
 		const rowsToInsert = options.rows || 1;
+		const isCopyStructure = options.copyStructureFromAbove !== undefined;
+		const copyStructureFrom = options.copyStructureFromAbove ? insertAt - 1 : insertAt;
+
+		const rows = this.getRows( table );
+		const columns = this.getColumns( table );
 
 
 		model.change( writer => {
 		model.change( writer => {
 			const headingRows = table.getAttribute( 'headingRows' ) || 0;
 			const headingRows = table.getAttribute( 'headingRows' ) || 0;
@@ -131,38 +138,58 @@ export default class TableUtils extends Plugin {
 				writer.setAttribute( 'headingRows', headingRows + rowsToInsert, table );
 				writer.setAttribute( 'headingRows', headingRows + rowsToInsert, table );
 			}
 			}
 
 
-			// Inserting at the end and at the beginning of a table doesn't require to calculate anything special.
-			if ( insertAt === 0 || insertAt === table.childCount ) {
-				createEmptyRows( writer, table, insertAt, rowsToInsert, this.getColumns( table ) );
+			// Inserting at the end or at the beginning of a table doesn't require to calculate anything special.
+			if ( !isCopyStructure && ( insertAt === 0 || insertAt === rows ) ) {
+				createEmptyRows( writer, table, insertAt, rowsToInsert, columns );
 
 
 				return;
 				return;
 			}
 			}
 
 
-			// Iterate over all rows above inserted rows in order to check for rowspanned cells.
-			const tableIterator = new TableWalker( table, { endRow: insertAt } );
+			// Iterate over all the rows above the inserted rows in order to check for the row-spanned cells.
+			const walkerEndRow = isCopyStructure ? Math.max( insertAt, copyStructureFrom ) : insertAt;
+			const tableIterator = new TableWalker( table, { endRow: walkerEndRow } );
+
+			// Store spans of the reference row to reproduce it's structure. This array is column number indexed.
+			const rowColSpansMap = new Array( columns ).fill( 1 );
 
 
-			// Will hold number of cells needed to insert in created rows.
-			// The number might be different then table cell width when there are rowspanned cells.
-			let cellsToInsert = 0;
+			for ( const { row, column, rowspan, colspan, cell } of tableIterator ) {
+				const lastCellRow = row + rowspan - 1;
 
 
-			for ( const { row, rowspan, colspan, cell } of tableIterator ) {
-				const isBeforeInsertedRow = row < insertAt;
-				const overlapsInsertedRow = row + rowspan > insertAt;
+				const isOverlappingInsertedRow = row < insertAt && insertAt <= lastCellRow;
+				const isReferenceRow = row <= copyStructureFrom && copyStructureFrom <= lastCellRow;
 
 
-				if ( isBeforeInsertedRow && overlapsInsertedRow ) {
-					// This cell overlaps inserted rows so we need to expand it further.
+				// If the cell is row-spanned and overlaps the inserted row, then reserve space for it in the row map.
+				if ( isOverlappingInsertedRow ) {
+					// This cell overlaps the inserted rows so we need to expand it further.
 					writer.setAttribute( 'rowspan', rowspan + rowsToInsert, cell );
 					writer.setAttribute( 'rowspan', rowspan + rowsToInsert, cell );
-				}
 
 
-				// Calculate how many cells to insert based on the width of cells in a row at insert position.
-				// It might be lower then table width as some cells might overlaps inserted row.
-				// In the table above the cell 'a' overlaps inserted row so only two empty cells are need to be created.
-				if ( row === insertAt ) {
-					cellsToInsert += colspan;
+					// Mark this cell with negative number to indicate how many cells should be skipped when adding the new cells.
+					rowColSpansMap[ column ] = -colspan;
+				}
+				// Store the colspan from reference row.
+				else if ( isCopyStructure && isReferenceRow ) {
+					rowColSpansMap[ column ] = colspan;
 				}
 				}
 			}
 			}
 
 
-			createEmptyRows( writer, table, insertAt, rowsToInsert, cellsToInsert );
+			for ( let rowIndex = 0; rowIndex < rowsToInsert; rowIndex++ ) {
+				const tableRow = writer.createElement( 'tableRow' );
+
+				writer.insert( tableRow, table, insertAt );
+
+				for ( let cellIndex = 0; cellIndex < rowColSpansMap.length; cellIndex++ ) {
+					const colspan = rowColSpansMap[ cellIndex ];
+					const insertPosition = writer.createPositionAt( tableRow, 'end' );
+
+					// Insert the empty cell only if this slot is not row-spanned from any other cell.
+					if ( colspan > 0 ) {
+						createEmptyTableCell( writer, insertPosition, colspan > 1 ? { colspan } : null );
+					}
+
+					// Skip the col-spanned slots, there won't be any cells.
+					cellIndex += Math.abs( colspan ) - 1;
+				}
+			}
 		} );
 		} );
 	}
 	}
 
 

+ 54 - 0
packages/ckeditor5-table/tests/commands/insertrowcommand.js

@@ -232,6 +232,33 @@ describe( 'InsertRowCommand', () => {
 					[ '', '' ]
 					[ '', '' ]
 				] ) );
 				] ) );
 			} );
 			} );
+
+			it( 'should copy the row structure from the selected row', () => {
+				// +----+----+----+
+				// | 00 | 01      |
+				// +----+----+----+
+				// | 10 | 11 | 12 |
+				// +----+----+----+
+				setData( model, modelTable( [
+					[ '[]00', { contents: '01', colspan: 2 } ],
+					[ '10', '11', '12' ]
+				] ) );
+
+				command.execute();
+
+				// +----+----+----+
+				// | 00 | 01      |
+				// +----+----+----+
+				// |    |         |
+				// +----+----+----+
+				// | 10 | 11 | 12 |
+				// +----+----+----+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00', { contents: '01', colspan: 2 } ],
+					[ '', { contents: '', colspan: 2 } ],
+					[ '10', '11', '12' ]
+				] ) );
+			} );
 		} );
 		} );
 	} );
 	} );
 
 
@@ -367,6 +394,33 @@ describe( 'InsertRowCommand', () => {
 					[ 0, 0 ]
 					[ 0, 0 ]
 				] );
 				] );
 			} );
 			} );
+
+			it( 'should copy the row structure from the selected row', () => {
+				// +----+----+----+
+				// | 00 | 01      |
+				// +----+----+----+
+				// | 10 | 11 | 12 |
+				// +----+----+----+
+				setData( model, modelTable( [
+					[ '[]00', { contents: '01', colspan: 2 } ],
+					[ '10', '11', '12' ]
+				] ) );
+
+				command.execute();
+
+				// +----+----+----+
+				// |    |         |
+				// +----+----+----+
+				// | 00 | 01      |
+				// +----+----+----+
+				// | 10 | 11 | 12 |
+				// +----+----+----+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '', { contents: '', colspan: 2 } ],
+					[ '00', { contents: '01', colspan: 2 } ],
+					[ '10', '11', '12' ]
+				] ) );
+			} );
 		} );
 		} );
 	} );
 	} );
 } );
 } );

+ 82 - 0
packages/ckeditor5-table/tests/tableutils.js

@@ -219,6 +219,88 @@ describe( 'TableUtils', () => {
 				[ '', '' ]
 				[ '', '' ]
 			] ) );
 			] ) );
 		} );
 		} );
+
+		describe( 'with copyStructureFrom enabled', () => {
+			beforeEach( () => {
+				// +----+----+----+----+----+----+
+				// | 00 | 01      | 03 | 04 | 05 |
+				// +----+         +    +----+----+
+				// | 10 |         |    | 14      |
+				// +----+----+----+----+----+----+
+				setData( model, modelTable( [
+					[ '00', { contents: '01', colspan: 2, rowspan: 2 }, { contents: '03', rowspan: 2 }, '04', '05' ],
+					[ '10', { contents: '14', colspan: 2 } ]
+				] ) );
+			} );
+
+			it( 'should copy structure from the first row', () => {
+				tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 0, rows: 1, copyStructureFromAbove: false } );
+
+				// +----+----+----+----+----+----+
+				// |    |         |    |    |    |
+				// +----+----+----+----+----+----+
+				// | 00 | 01      | 03 | 04 | 05 |
+				// +----+         +    +----+----+
+				// | 10 |         |    | 14      |
+				// +----+----+----+----+----+----+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '', { contents: '', colspan: 2 }, '', '', '' ],
+					[ '00', { contents: '01', colspan: 2, rowspan: 2 }, { contents: '03', rowspan: 2 }, '04', '05' ],
+					[ '10', { contents: '14', colspan: 2 } ]
+				] ) );
+			} );
+
+			it( 'should copy structure from the first row and properly handle row-spanned cells', () => {
+				tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1, rows: 1, copyStructureFromAbove: true } );
+
+				// +----+----+----+----+----+----+
+				// | 00 | 01      | 03 | 04 | 05 |
+				// +----+         +    +----+----+
+				// |    |         |    |    |    |
+				// +----+         +    +----+----+
+				// | 10 |         |    | 14      |
+				// +----+----+----+----+----+----+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00', { contents: '01', colspan: 2, rowspan: 3 }, { contents: '03', rowspan: 3 }, '04', '05' ],
+					[ '', '', '' ],
+					[ '10', { contents: '14', colspan: 2 } ]
+				] ) );
+			} );
+
+			it( 'should copy structure from the last row', () => {
+				tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2, rows: 1, copyStructureFromAbove: true } );
+
+				// +----+----+----+----+----+----+
+				// | 00 | 01      | 03 | 04 | 05 |
+				// +----+         +    +----+----+
+				// | 10 |         |    | 14      |
+				// +----+----+----+----+----+----+
+				// |    |         |    |         |
+				// +----+----+----+----+----+----+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00', { contents: '01', colspan: 2, rowspan: 2 }, { contents: '03', rowspan: 2 }, '04', '05' ],
+					[ '10', { contents: '14', colspan: 2 } ],
+					[ '', { contents: '', colspan: 2 }, '', { contents: '', colspan: 2 } ]
+				] ) );
+			} );
+
+			it( 'should copy structure from the last row and properly handle row-spanned cells', () => {
+				tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1, rows: 1, copyStructureFromAbove: false } );
+
+				// +----+----+----+----+----+----+
+				// | 00 | 01      | 03 | 04 | 05 |
+				// +----+         +    +----+----+
+				// |    |         |    |         |
+				// +----+         +    +----+----+
+				// | 10 |         |    | 14      |
+				// +----+----+----+----+----+----+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00', { contents: '01', colspan: 2, rowspan: 3 }, { contents: '03', rowspan: 3 }, '04', '05' ],
+					[ '', { contents: '', colspan: 2 } ],
+					[ '10', { contents: '14', colspan: 2 } ]
+				] ) );
+			} );
+		} );
 	} );
 	} );
 
 
 	describe( 'insertColumns()', () => {
 	describe( 'insertColumns()', () => {