mergecellscommand.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module table/commands/mergecellscommand
  7. */
  8. import Command from '@ckeditor/ckeditor5-core/src/command';
  9. import TableUtils from '../tableutils';
  10. import { getSelectedTableCells, isSelectionRectangular } from '../utils/selection';
  11. import { findAncestor, updateNumericAttribute } from '../utils/common';
  12. /**
  13. * The merge cells command.
  14. *
  15. * The command is registered by {@link module:table/tableediting~TableEditing} as the `'mergeTableCells'` editor command.
  16. *
  17. * For example, to merge selected table cells:
  18. *
  19. * editor.execute( 'mergeTableCells' );
  20. *
  21. * @extends module:core/command~Command
  22. */
  23. export default class MergeCellsCommand extends Command {
  24. /**
  25. * @inheritDoc
  26. */
  27. refresh() {
  28. const selectedTableCells = getSelectedTableCells( this.editor.model.document.selection );
  29. this.isEnabled = isSelectionRectangular( selectedTableCells, this.editor.plugins.get( TableUtils ) );
  30. }
  31. /**
  32. * Executes the command.
  33. *
  34. * @fires execute
  35. */
  36. execute() {
  37. const model = this.editor.model;
  38. const tableUtils = this.editor.plugins.get( TableUtils );
  39. model.change( writer => {
  40. const selectedTableCells = getSelectedTableCells( model.document.selection );
  41. // All cells will be merged into the first one.
  42. const firstTableCell = selectedTableCells.shift();
  43. // Set the selection in cell that other cells are being merged to prevent model-selection-range-intersects error in undo.
  44. // See https://github.com/ckeditor/ckeditor5/issues/6634.
  45. // May be fixed by: https://github.com/ckeditor/ckeditor5/issues/6639.
  46. writer.setSelection( firstTableCell, 0 );
  47. // Update target cell dimensions.
  48. const { mergeWidth, mergeHeight } = getMergeDimensions( firstTableCell, selectedTableCells, tableUtils );
  49. updateNumericAttribute( 'colspan', mergeWidth, firstTableCell, writer );
  50. updateNumericAttribute( 'rowspan', mergeHeight, firstTableCell, writer );
  51. const emptyRowsIndexes = [];
  52. for ( const tableCell of selectedTableCells ) {
  53. const tableRow = tableCell.parent;
  54. mergeTableCells( tableCell, firstTableCell, writer );
  55. if ( !tableRow.childCount ) {
  56. emptyRowsIndexes.push( tableRow.index );
  57. }
  58. }
  59. if ( emptyRowsIndexes.length ) {
  60. const table = findAncestor( 'table', firstTableCell );
  61. emptyRowsIndexes.reverse().forEach( row => tableUtils.removeRows( table, { at: row, batch: writer.batch } ) );
  62. }
  63. writer.setSelection( firstTableCell, 'in' );
  64. } );
  65. }
  66. }
  67. // Merges two table cells. It will ensure that after merging cells with empty paragraphs the resulting table cell will only have one
  68. // paragraph. If one of the merged table cells is empty, the merged table cell will have contents of the non-empty table cell.
  69. // If both are empty, the merged table cell will have only one empty paragraph.
  70. //
  71. // @param {module:engine/model/element~Element} cellBeingMerged
  72. // @param {module:engine/model/element~Element} targetCell
  73. // @param {module:engine/model/writer~Writer} writer
  74. function mergeTableCells( cellBeingMerged, targetCell, writer ) {
  75. if ( !isEmpty( cellBeingMerged ) ) {
  76. if ( isEmpty( targetCell ) ) {
  77. writer.remove( writer.createRangeIn( targetCell ) );
  78. }
  79. writer.move( writer.createRangeIn( cellBeingMerged ), writer.createPositionAt( targetCell, 'end' ) );
  80. }
  81. // Remove merged table cell.
  82. writer.remove( cellBeingMerged );
  83. }
  84. // Checks if the passed table cell contains an empty paragraph.
  85. //
  86. // @param {module:engine/model/element~Element} tableCell
  87. // @returns {Boolean}
  88. function isEmpty( tableCell ) {
  89. return tableCell.childCount == 1 && tableCell.getChild( 0 ).is( 'paragraph' ) && tableCell.getChild( 0 ).isEmpty;
  90. }
  91. function getMergeDimensions( firstTableCell, selectedTableCells, tableUtils ) {
  92. let maxWidthOffset = 0;
  93. let maxHeightOffset = 0;
  94. for ( const tableCell of selectedTableCells ) {
  95. const { row, column } = tableUtils.getCellLocation( tableCell );
  96. maxWidthOffset = getMaxOffset( tableCell, column, maxWidthOffset, 'colspan' );
  97. maxHeightOffset = getMaxOffset( tableCell, row, maxHeightOffset, 'rowspan' );
  98. }
  99. // Update table cell span attribute and merge set selection on a merged contents.
  100. const { row: firstCellRow, column: firstCellColumn } = tableUtils.getCellLocation( firstTableCell );
  101. const mergeWidth = maxWidthOffset - firstCellColumn;
  102. const mergeHeight = maxHeightOffset - firstCellRow;
  103. return { mergeWidth, mergeHeight };
  104. }
  105. function getMaxOffset( tableCell, start, currentMaxOffset, which ) {
  106. const dimensionValue = parseInt( tableCell.getAttribute( which ) || 1 );
  107. return Math.max( currentMaxOffset, start + dimensionValue );
  108. }