UNPKG

4.2 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5import { Command } from 'ckeditor5/src/core';
6import TableUtils from '../tableutils';
7import { updateNumericAttribute } from '../utils/common';
8import { removeEmptyRowsColumns } from '../utils/structure';
9/**
10 * The merge cells command.
11 *
12 * The command is registered by {@link module:table/tableediting~TableEditing} as the `'mergeTableCells'` editor command.
13 *
14 * For example, to merge selected table cells:
15 *
16 * ```ts
17 * editor.execute( 'mergeTableCells' );
18 * ```
19 */
20export default class MergeCellsCommand extends Command {
21 /**
22 * @inheritDoc
23 */
24 refresh() {
25 const tableUtils = this.editor.plugins.get(TableUtils);
26 const selectedTableCells = tableUtils.getSelectedTableCells(this.editor.model.document.selection);
27 this.isEnabled = tableUtils.isSelectionRectangular(selectedTableCells);
28 }
29 /**
30 * Executes the command.
31 *
32 * @fires execute
33 */
34 execute() {
35 const model = this.editor.model;
36 const tableUtils = this.editor.plugins.get(TableUtils);
37 model.change(writer => {
38 const selectedTableCells = tableUtils.getSelectedTableCells(model.document.selection);
39 // All cells will be merged into the first one.
40 const firstTableCell = selectedTableCells.shift();
41 // Update target cell dimensions.
42 const { mergeWidth, mergeHeight } = getMergeDimensions(firstTableCell, selectedTableCells, tableUtils);
43 updateNumericAttribute('colspan', mergeWidth, firstTableCell, writer);
44 updateNumericAttribute('rowspan', mergeHeight, firstTableCell, writer);
45 for (const tableCell of selectedTableCells) {
46 mergeTableCells(tableCell, firstTableCell, writer);
47 }
48 const table = firstTableCell.findAncestor('table');
49 // Remove rows and columns that become empty (have no anchored cells).
50 removeEmptyRowsColumns(table, tableUtils);
51 writer.setSelection(firstTableCell, 'in');
52 });
53 }
54}
55/**
56 * Merges two table cells. It will ensure that after merging cells with empty paragraphs the resulting table cell will only have one
57 * paragraph. If one of the merged table cells is empty, the merged table cell will have contents of the non-empty table cell.
58 * If both are empty, the merged table cell will have only one empty paragraph.
59 */
60function mergeTableCells(cellBeingMerged, targetCell, writer) {
61 if (!isEmpty(cellBeingMerged)) {
62 if (isEmpty(targetCell)) {
63 writer.remove(writer.createRangeIn(targetCell));
64 }
65 writer.move(writer.createRangeIn(cellBeingMerged), writer.createPositionAt(targetCell, 'end'));
66 }
67 // Remove merged table cell.
68 writer.remove(cellBeingMerged);
69}
70/**
71 * Checks if the passed table cell contains an empty paragraph.
72 */
73function isEmpty(tableCell) {
74 const firstTableChild = tableCell.getChild(0);
75 return tableCell.childCount == 1 && firstTableChild.is('element', 'paragraph') && firstTableChild.isEmpty;
76}
77function getMergeDimensions(firstTableCell, selectedTableCells, tableUtils) {
78 let maxWidthOffset = 0;
79 let maxHeightOffset = 0;
80 for (const tableCell of selectedTableCells) {
81 const { row, column } = tableUtils.getCellLocation(tableCell);
82 maxWidthOffset = getMaxOffset(tableCell, column, maxWidthOffset, 'colspan');
83 maxHeightOffset = getMaxOffset(tableCell, row, maxHeightOffset, 'rowspan');
84 }
85 // Update table cell span attribute and merge set selection on a merged contents.
86 const { row: firstCellRow, column: firstCellColumn } = tableUtils.getCellLocation(firstTableCell);
87 const mergeWidth = maxWidthOffset - firstCellColumn;
88 const mergeHeight = maxHeightOffset - firstCellRow;
89 return { mergeWidth, mergeHeight };
90}
91function getMaxOffset(tableCell, start, currentMaxOffset, which) {
92 const dimensionValue = parseInt(tableCell.getAttribute(which) || '1');
93 return Math.max(currentMaxOffset, start + dimensionValue);
94}