1 |
|
2 |
|
3 |
|
4 |
|
5 | import { Command } from 'ckeditor5/src/core';
|
6 | import TableWalker from '../tablewalker';
|
7 | import { isHeadingColumnCell } from '../utils/common';
|
8 | import { removeEmptyRowsColumns } from '../utils/structure';
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | export default class MergeCellCommand extends Command {
|
28 | |
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | constructor(editor, options) {
|
36 | super(editor);
|
37 | this.direction = options.direction;
|
38 | this.isHorizontal = this.direction == 'right' || this.direction == 'left';
|
39 | }
|
40 | |
41 |
|
42 |
|
43 | refresh() {
|
44 | const cellToMerge = this._getMergeableCell();
|
45 | this.value = cellToMerge;
|
46 | this.isEnabled = !!cellToMerge;
|
47 | }
|
48 | |
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | execute() {
|
56 | const model = this.editor.model;
|
57 | const doc = model.document;
|
58 | const tableUtils = this.editor.plugins.get('TableUtils');
|
59 | const tableCell = tableUtils.getTableCellsContainingSelection(doc.selection)[0];
|
60 | const cellToMerge = this.value;
|
61 | const direction = this.direction;
|
62 | model.change(writer => {
|
63 | const isMergeNext = direction == 'right' || direction == 'down';
|
64 |
|
65 | const cellToExpand = (isMergeNext ? tableCell : cellToMerge);
|
66 | const cellToRemove = (isMergeNext ? cellToMerge : tableCell);
|
67 |
|
68 | const removedTableCellRow = cellToRemove.parent;
|
69 | mergeTableCells(cellToRemove, cellToExpand, writer);
|
70 | const spanAttribute = this.isHorizontal ? 'colspan' : 'rowspan';
|
71 | const cellSpan = parseInt(tableCell.getAttribute(spanAttribute) || '1');
|
72 | const cellToMergeSpan = parseInt(cellToMerge.getAttribute(spanAttribute) || '1');
|
73 |
|
74 | writer.setAttribute(spanAttribute, cellSpan + cellToMergeSpan, cellToExpand);
|
75 | writer.setSelection(writer.createRangeIn(cellToExpand));
|
76 | const tableUtils = this.editor.plugins.get('TableUtils');
|
77 | const table = removedTableCellRow.findAncestor('table');
|
78 |
|
79 | removeEmptyRowsColumns(table, tableUtils);
|
80 | });
|
81 | }
|
82 | |
83 |
|
84 |
|
85 | _getMergeableCell() {
|
86 | const model = this.editor.model;
|
87 | const doc = model.document;
|
88 | const tableUtils = this.editor.plugins.get('TableUtils');
|
89 | const tableCell = tableUtils.getTableCellsContainingSelection(doc.selection)[0];
|
90 | if (!tableCell) {
|
91 | return;
|
92 | }
|
93 |
|
94 | const cellToMerge = this.isHorizontal ?
|
95 | getHorizontalCell(tableCell, this.direction, tableUtils) :
|
96 | getVerticalCell(tableCell, this.direction, tableUtils);
|
97 | if (!cellToMerge) {
|
98 | return;
|
99 | }
|
100 |
|
101 | const spanAttribute = this.isHorizontal ? 'rowspan' : 'colspan';
|
102 | const span = parseInt(tableCell.getAttribute(spanAttribute) || '1');
|
103 | const cellToMergeSpan = parseInt(cellToMerge.getAttribute(spanAttribute) || '1');
|
104 | if (cellToMergeSpan === span) {
|
105 | return cellToMerge;
|
106 | }
|
107 | }
|
108 | }
|
109 |
|
110 |
|
111 |
|
112 | function getHorizontalCell(tableCell, direction, tableUtils) {
|
113 | const tableRow = tableCell.parent;
|
114 | const table = tableRow.parent;
|
115 | const horizontalCell = direction == 'right' ? tableCell.nextSibling : tableCell.previousSibling;
|
116 | const hasHeadingColumns = (table.getAttribute('headingColumns') || 0) > 0;
|
117 | if (!horizontalCell) {
|
118 | return;
|
119 | }
|
120 |
|
121 | const cellOnLeft = (direction == 'right' ? tableCell : horizontalCell);
|
122 | const cellOnRight = (direction == 'right' ? horizontalCell : tableCell);
|
123 |
|
124 | const { column: leftCellColumn } = tableUtils.getCellLocation(cellOnLeft);
|
125 | const { column: rightCellColumn } = tableUtils.getCellLocation(cellOnRight);
|
126 | const leftCellSpan = parseInt(cellOnLeft.getAttribute('colspan') || '1');
|
127 | const isCellOnLeftInHeadingColumn = isHeadingColumnCell(tableUtils, cellOnLeft);
|
128 | const isCellOnRightInHeadingColumn = isHeadingColumnCell(tableUtils, cellOnRight);
|
129 |
|
130 | if (hasHeadingColumns && isCellOnLeftInHeadingColumn != isCellOnRightInHeadingColumn) {
|
131 | return;
|
132 | }
|
133 |
|
134 | const cellsAreTouching = leftCellColumn + leftCellSpan === rightCellColumn;
|
135 |
|
136 | return cellsAreTouching ? horizontalCell : undefined;
|
137 | }
|
138 |
|
139 |
|
140 |
|
141 | function getVerticalCell(tableCell, direction, tableUtils) {
|
142 | const tableRow = tableCell.parent;
|
143 | const table = tableRow.parent;
|
144 | const rowIndex = table.getChildIndex(tableRow);
|
145 |
|
146 | if ((direction == 'down' && rowIndex === tableUtils.getRows(table) - 1) || (direction == 'up' && rowIndex === 0)) {
|
147 | return null;
|
148 | }
|
149 | const rowspan = parseInt(tableCell.getAttribute('rowspan') || '1');
|
150 | const headingRows = table.getAttribute('headingRows') || 0;
|
151 | const isMergeWithBodyCell = direction == 'down' && (rowIndex + rowspan) === headingRows;
|
152 | const isMergeWithHeadCell = direction == 'up' && rowIndex === headingRows;
|
153 |
|
154 | if (headingRows && (isMergeWithBodyCell || isMergeWithHeadCell)) {
|
155 | return null;
|
156 | }
|
157 | const currentCellRowSpan = parseInt(tableCell.getAttribute('rowspan') || '1');
|
158 | const rowOfCellToMerge = direction == 'down' ? rowIndex + currentCellRowSpan : rowIndex;
|
159 | const tableMap = [...new TableWalker(table, { endRow: rowOfCellToMerge })];
|
160 | const currentCellData = tableMap.find(value => value.cell === tableCell);
|
161 | const mergeColumn = currentCellData.column;
|
162 | const cellToMergeData = tableMap.find(({ row, cellHeight, column }) => {
|
163 | if (column !== mergeColumn) {
|
164 | return false;
|
165 | }
|
166 | if (direction == 'down') {
|
167 |
|
168 | return row === rowOfCellToMerge;
|
169 | }
|
170 | else {
|
171 |
|
172 | return rowOfCellToMerge === row + cellHeight;
|
173 | }
|
174 | });
|
175 | return cellToMergeData && cellToMergeData.cell ? cellToMergeData.cell : null;
|
176 | }
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | function mergeTableCells(cellToRemove, cellToExpand, writer) {
|
183 | if (!isEmpty(cellToRemove)) {
|
184 | if (isEmpty(cellToExpand)) {
|
185 | writer.remove(writer.createRangeIn(cellToExpand));
|
186 | }
|
187 | writer.move(writer.createRangeIn(cellToRemove), writer.createPositionAt(cellToExpand, 'end'));
|
188 | }
|
189 |
|
190 | writer.remove(cellToRemove);
|
191 | }
|
192 |
|
193 |
|
194 |
|
195 | function isEmpty(tableCell) {
|
196 | const firstTableChild = tableCell.getChild(0);
|
197 | return tableCell.childCount == 1 && firstTableChild.is('element', 'paragraph') && firstTableChild.isEmpty;
|
198 | }
|