@seafile/sdoc-editor 0.3.15 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/assets/css/simple-editor.css +1 -1
  2. package/dist/basic-sdk/extension/commons/insert-element-dialog/index.js +5 -1
  3. package/dist/basic-sdk/extension/constants/index.js +1 -1
  4. package/dist/basic-sdk/extension/constants/menus-config.js +6 -0
  5. package/dist/basic-sdk/extension/core/queries/index.js +5 -2
  6. package/dist/basic-sdk/extension/plugins/table/constants/index.js +1 -1
  7. package/dist/basic-sdk/extension/plugins/table/dialogs/index.js +3 -0
  8. package/dist/basic-sdk/extension/plugins/table/dialogs/split-cell-setting-dialog.js +99 -0
  9. package/dist/basic-sdk/extension/plugins/table/helpers.js +324 -1
  10. package/dist/basic-sdk/extension/plugins/table/menu/active-table-menu/combine-cells.js +32 -0
  11. package/dist/basic-sdk/extension/plugins/table/menu/active-table-menu/index.css +0 -4
  12. package/dist/basic-sdk/extension/plugins/table/menu/active-table-menu/index.js +6 -0
  13. package/dist/basic-sdk/extension/plugins/table/menu/table-context-menu/index.js +31 -4
  14. package/dist/basic-sdk/extension/plugins/table/render/index.css +1 -11
  15. package/dist/basic-sdk/extension/plugins/table/render/index.js +35 -7
  16. package/dist/basic-sdk/extension/plugins/table/render/render-cell.js +34 -15
  17. package/dist/basic-sdk/extension/plugins/table/render/render-row.js +5 -118
  18. package/dist/basic-sdk/extension/plugins/table/render/resize-handlers/{resize-handler.js → column-resize-handler.js} +2 -2
  19. package/dist/basic-sdk/extension/plugins/table/render/resize-handlers/index.js +17 -4
  20. package/dist/basic-sdk/extension/plugins/table/render/resize-handlers/row-resize-handler.js +89 -0
  21. package/dist/basic-sdk/extension/plugins/table/render/table-header/rows-header/row-header.js +12 -13
  22. package/package.json +1 -1
  23. package/public/locales/en/sdoc-editor.json +7 -1
  24. package/public/locales/zh_CN/sdoc-editor.json +7 -1
  25. /package/dist/basic-sdk/extension/plugins/table/{dialog → dialogs}/custom-table-size-dialog/index.css +0 -0
  26. /package/dist/basic-sdk/extension/plugins/table/{dialog → dialogs}/custom-table-size-dialog/index.js +0 -0
  27. /package/dist/basic-sdk/extension/plugins/table/{dialog → dialogs}/custom-table-size-dialog/number-input.js +0 -0
@@ -1,6 +1,6 @@
1
1
  /* reset common css */
2
2
  .sdoc-editor-page-wrapper .dropdown-item,
3
- .sdoc-context-menu .dropdown-item {
3
+ .sdoc-context-menu .dropdown-item:not(:disabled) {
4
4
  color: #212529;
5
5
  }
6
6
 
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState, useRef } from 'react';
2
- import CustomTableSizeDialog from '../../plugins/table/dialog/custom-table-size-dialog';
2
+ import { CustomTableSizeDialog, SplitCellSettingDialog } from '../../plugins/table/dialogs';
3
3
  import AddLinkDialog from '../../plugins/link/dialog/add-link-dialog';
4
4
  import SelectFileDialog from '../select-file-dialog/index.js';
5
5
  import EventBus from '../../../utils/event-bus';
@@ -77,6 +77,10 @@ const InsertElementDialog = _ref => {
77
77
  {
78
78
  return /*#__PURE__*/React.createElement(CustomTableSizeDialog, props);
79
79
  }
80
+ case ELEMENT_TYPE.TABLE_CELL:
81
+ {
82
+ return /*#__PURE__*/React.createElement(SplitCellSettingDialog, props);
83
+ }
80
84
  case ELEMENT_TYPE.LINK:
81
85
  {
82
86
  return /*#__PURE__*/React.createElement(AddLinkDialog, props);
@@ -5,7 +5,7 @@ export { DEFAULT_COLORS, STANDARD_COLORS, DEFAULT_RECENT_USED_LIST, DEFAULT_FONT
5
5
  export { FONT_SIZE, DEFAULT_FONT, FONT, GOOGLE_FONT_CLASS, RECENT_USED_FONTS_KEY, SDOC_FONT_SIZE } from './font';
6
6
  export { DIFF_TYPE, ADDED_STYLE, DELETED_STYLE } from './diff-view';
7
7
  export { KEYBOARD, MAC_HOTKEYS, WIN_HOTKEYS } from './keyboard';
8
- export { UNDO, REDO, TEXT_STYLE, TEXT_STYLE_MAP, TEXT_STYLE_MORE, TEXT_ALIGN, REMOVE_TABLE, CLEAR_FORMAT, MENUS_CONFIG_MAP, SIDE_TRANSFORM_MENUS_CONFIG, SIDE_INSERT_MENUS_CONFIG } from './menus-config';
8
+ export { UNDO, REDO, TEXT_STYLE, TEXT_STYLE_MAP, TEXT_STYLE_MORE, TEXT_ALIGN, REMOVE_TABLE, COMBINE_CELL, CLEAR_FORMAT, MENUS_CONFIG_MAP, SIDE_TRANSFORM_MENUS_CONFIG, SIDE_INSERT_MENUS_CONFIG } from './menus-config';
9
9
  export const HEADERS = [HEADER1, HEADER2, HEADER3, HEADER4, HEADER5, HEADER6];
10
10
  export const HEADER_TITLE_MAP = {
11
11
  [TITLE]: 'Title',
@@ -4,6 +4,7 @@ export const UNDO = 'undo';
4
4
  export const REDO = 'redo';
5
5
  export const CLEAR_FORMAT = 'clear_format';
6
6
  export const REMOVE_TABLE = 'remove_table';
7
+ export const COMBINE_CELL = 'combine_cell';
7
8
 
8
9
  // text style
9
10
  export const TEXT_STYLE = 'text_style';
@@ -89,6 +90,11 @@ export const MENUS_CONFIG_MAP = {
89
90
  iconClass: 'sdocfont sdoc-delete-table',
90
91
  text: 'Delete_table'
91
92
  },
93
+ [COMBINE_CELL]: {
94
+ id: "sdoc_".concat(COMBINE_CELL),
95
+ iconClass: 'sdocfont sdoc-merge-cell',
96
+ text: 'Combine_cell'
97
+ },
92
98
  [TEXT_STYLE]: [{
93
99
  id: ITALIC,
94
100
  iconClass: 'sdocfont sdoc-italic',
@@ -218,14 +218,17 @@ export const getAboveBlockNode = (editor, options) => {
218
218
  }));
219
219
  };
220
220
  export const getPrevNode = editor => {
221
- const [lowerNode, lowerPath] = getAboveNode(editor, {
221
+ const lowerNodeEntry = getAboveNode(editor, {
222
222
  mode: 'lowest',
223
223
  match: n => Element.isElement(n) && Editor.isBlock(editor, n)
224
224
  });
225
- const [heightNode, heightPath] = getAboveNode(editor, {
225
+ const highNodeEntry = getAboveNode(editor, {
226
226
  mode: 'highest',
227
227
  match: n => Element.isElement(n) && Editor.isBlock(editor, n)
228
228
  });
229
+ if (!lowerNodeEntry || !highNodeEntry) return null;
230
+ const [heightNode, heightPath] = highNodeEntry;
231
+ const [lowerNode, lowerPath] = lowerNodeEntry;
229
232
  let prevNode = null;
230
233
  try {
231
234
  prevNode = Editor.previous(editor, {
@@ -1,4 +1,4 @@
1
- export const TABLE_MAX_ROWS = 50;
1
+ export const TABLE_MAX_ROWS = 500;
2
2
  export const TABLE_MAX_COLUMNS = 50;
3
3
  export const EMPTY_SELECTED_RANGE = {
4
4
  minRowIndex: -1,
@@ -0,0 +1,3 @@
1
+ import CustomTableSizeDialog from './custom-table-size-dialog';
2
+ import SplitCellSettingDialog from './split-cell-setting-dialog';
3
+ export { CustomTableSizeDialog, SplitCellSettingDialog };
@@ -0,0 +1,99 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert, Row, Col, FormGroup, Label, Input } from 'reactstrap';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { getSelectedInfo, splitCell } from '../helpers';
5
+ const SplitCellSettingDialog = _ref => {
6
+ let {
7
+ editor,
8
+ closeDialog
9
+ } = _ref;
10
+ const {
11
+ t
12
+ } = useTranslation();
13
+ const {
14
+ cell
15
+ } = getSelectedInfo(editor);
16
+ const {
17
+ rowspan = 1,
18
+ colspan = 1
19
+ } = cell;
20
+ const [rowNumber, setRowNumber] = useState(rowspan);
21
+ const [columnNumber, setColumnNumber] = useState(colspan);
22
+ const CELL_SPLIT_MAX_ROWS = rowspan;
23
+ const CELL_SPLIT_MAX_COLUMNS = colspan;
24
+ const [errorMessage, setErrorMessage] = useState('');
25
+ const onRowNumberChange = useCallback(e => {
26
+ setRowNumber(e.target.value);
27
+ // eslint-disable-next-line react-hooks/exhaustive-deps
28
+ }, []);
29
+ const onColumnNumberChange = useCallback(e => {
30
+ setColumnNumber(e.target.value);
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
+ }, []);
33
+ const handleSubmit = useCallback(() => {
34
+ const parsedRowNumber = parseInt(rowNumber);
35
+ const parsedColumnNumber = parseInt(columnNumber);
36
+ if (!parsedRowNumber || !parsedColumnNumber || parsedRowNumber < 0 || parsedColumnNumber < 0) {
37
+ setErrorMessage(t('Please_enter_a_non-negative_integer'));
38
+ return false;
39
+ }
40
+ let targetRowNumber = parsedRowNumber;
41
+ let targetColumnNumber = parsedColumnNumber;
42
+ if (parsedRowNumber > CELL_SPLIT_MAX_ROWS) {
43
+ targetRowNumber = CELL_SPLIT_MAX_ROWS;
44
+ setRowNumber(targetRowNumber);
45
+ setErrorMessage(t('The_maximum_row_number_is_{number}').replace('{number}', CELL_SPLIT_MAX_ROWS));
46
+ return false;
47
+ }
48
+ if (parsedColumnNumber > CELL_SPLIT_MAX_COLUMNS) {
49
+ targetColumnNumber = CELL_SPLIT_MAX_COLUMNS;
50
+ setColumnNumber(targetColumnNumber);
51
+ setErrorMessage(t('The_maximum_column_number_is_{number}').replace('{number}', CELL_SPLIT_MAX_COLUMNS));
52
+ return false;
53
+ }
54
+ splitCell(editor, targetRowNumber, targetColumnNumber);
55
+ closeDialog();
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, [rowNumber, columnNumber]);
58
+ return /*#__PURE__*/React.createElement(Modal, {
59
+ isOpen: true,
60
+ autoFocus: false,
61
+ toggle: closeDialog,
62
+ zIndex: 1071,
63
+ returnFocusAfterClose: true
64
+ }, /*#__PURE__*/React.createElement(ModalHeader, {
65
+ toggle: closeDialog
66
+ }, t('Split_cell')), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement(Col, {
67
+ md: 6
68
+ }, /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(Label, {
69
+ for: "row-number"
70
+ }, t('Row_number')), /*#__PURE__*/React.createElement(Input, {
71
+ id: "row-number",
72
+ name: "row-number",
73
+ type: "number",
74
+ min: 1,
75
+ value: rowNumber,
76
+ onChange: onRowNumberChange
77
+ }))), /*#__PURE__*/React.createElement(Col, {
78
+ md: 6
79
+ }, /*#__PURE__*/React.createElement(FormGroup, null, /*#__PURE__*/React.createElement(Label, {
80
+ for: "column-number"
81
+ }, t('Column_number')), /*#__PURE__*/React.createElement(Input, {
82
+ id: "column-number",
83
+ name: "column-number",
84
+ type: "number",
85
+ min: 1,
86
+ value: columnNumber,
87
+ onChange: onColumnNumberChange
88
+ })))), errorMessage && /*#__PURE__*/React.createElement(Alert, {
89
+ className: "mt-2 mb-0",
90
+ color: "danger"
91
+ }, t(errorMessage))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
92
+ color: "secondary",
93
+ onClick: closeDialog
94
+ }, t('Cancel')), /*#__PURE__*/React.createElement(Button, {
95
+ color: "primary",
96
+ onClick: handleSubmit
97
+ }, t('Submit'))));
98
+ };
99
+ export default SplitCellSettingDialog;
@@ -41,6 +41,18 @@ export const isTableMenuDisabled = (editor, readonly) => {
41
41
  if (match) return true;
42
42
  return false;
43
43
  };
44
+ export const isCombineCellsDisabled = (editor, readonly) => {
45
+ if (readonly) return true;
46
+ const {
47
+ selection,
48
+ tableSelectedRange
49
+ } = editor;
50
+ if (!selection) return true;
51
+ if (!ObjectUtils.isSameObject(tableSelectedRange, EMPTY_SELECTED_RANGE)) {
52
+ return false;
53
+ }
54
+ return true;
55
+ };
44
56
  export const generateTableCell = () => {
45
57
  return {
46
58
  id: slugid.nice(),
@@ -99,9 +111,13 @@ export const generateEmptyTable = (editor, tableProps) => {
99
111
  type: ELEMENT_TYPE.TABLE,
100
112
  children: children,
101
113
  columns,
102
- style: {
114
+ ui: {
103
115
  alternate_highlight,
104
116
  alternate_highlight_color
117
+ },
118
+ style: {
119
+ gridTemplateColumns: "repeat(".concat(colsCount, ", ").concat(columnWidth, "px)"),
120
+ gridAutoRows: "minmax(".concat(TABLE_ROW_MIN_HEIGHT, "px, auto)")
105
121
  }
106
122
  };
107
123
  };
@@ -219,6 +235,61 @@ export const insertTableRow = function (editor, table, rowIndex) {
219
235
  });
220
236
  const focusPath = [...targetPath, 0];
221
237
  focusEditor(editor, focusPath);
238
+
239
+ // handle cells with the rowspan > 1
240
+ if (position === TABLE_ELEMENT_POSITION.AFTER) {
241
+ handleCombinedCellsAfterInsertTableRow(editor, tablePath, table, rowIndex);
242
+ }
243
+ };
244
+ export const handleCombinedCellsAfterInsertTableRow = (editor, tablePath, table, rowIndex) => {
245
+ // important background info: the new row is inserted after rowIndex
246
+ const cells = table.children[rowIndex].children;
247
+ for (let i = 0, len = cells.length; i < len; i++) {
248
+ const {
249
+ is_combined,
250
+ rowspan,
251
+ colspan
252
+ } = cells[i];
253
+ if (is_combined) {
254
+ for (let ri = rowIndex - 1; ri >= 0; ri--) {
255
+ const {
256
+ is_combined: ri_is_combined,
257
+ rowspan: ri_rowspan,
258
+ colspan: ri_colspan
259
+ } = table.children[ri].children[i];
260
+ if (!ri_is_combined && ri + ri_rowspan - 1 > rowIndex) {
261
+ Transforms.setNodes(editor, {
262
+ rowspan: ri_rowspan + 1
263
+ }, {
264
+ at: [...tablePath, ri, i]
265
+ });
266
+ for (let j = 0; j < ri_colspan; j++) {
267
+ Transforms.setNodes(editor, {
268
+ 'is_combined': true
269
+ }, {
270
+ at: [...tablePath, rowIndex + 1, i + j]
271
+ });
272
+ }
273
+ break;
274
+ }
275
+ }
276
+ } else {
277
+ if (rowspan > 1) {
278
+ Transforms.setNodes(editor, {
279
+ rowspan: rowspan + 1
280
+ }, {
281
+ at: [...tablePath, rowIndex, i]
282
+ });
283
+ for (let j = 0; j < colspan; j++) {
284
+ Transforms.setNodes(editor, {
285
+ 'is_combined': true
286
+ }, {
287
+ at: [...tablePath, rowIndex + 1, i + j]
288
+ });
289
+ }
290
+ }
291
+ }
292
+ }
222
293
  };
223
294
  export const insertTableColumn = function (editor, table, columnIndex) {
224
295
  let position = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : TABLE_ELEMENT_POSITION.AFTER;
@@ -238,6 +309,60 @@ export const insertTableColumn = function (editor, table, columnIndex) {
238
309
  }
239
310
  const focusPath = [...tablePath, 0, newCellIndex, 0];
240
311
  focusEditor(editor, focusPath);
312
+
313
+ // handle cells with the colspan > 1
314
+ if (position === TABLE_ELEMENT_POSITION.AFTER) {
315
+ handleCombinedCellsAfterInsertTableColumn(editor, tablePath, table, columnIndex);
316
+ }
317
+ };
318
+ export const handleCombinedCellsAfterInsertTableColumn = (editor, tablePath, table, columnIndex) => {
319
+ // important background info: the new column is inserted after columnIndex
320
+ for (let i = 0, len = table.children.length; i < len; i++) {
321
+ const {
322
+ is_combined,
323
+ rowspan,
324
+ colspan
325
+ } = table.children[i].children[columnIndex];
326
+ if (is_combined) {
327
+ for (let ci = columnIndex - 1; ci >= 0; ci--) {
328
+ const {
329
+ is_combined: ci_is_combined,
330
+ rowspan: ci_rowspan,
331
+ colspan: ci_colspan
332
+ } = table.children[i].children[ci];
333
+ if (!ci_is_combined && ci + ci_colspan - 1 > columnIndex) {
334
+ Transforms.setNodes(editor, {
335
+ colspan: ci_colspan + 1
336
+ }, {
337
+ at: [...tablePath, i, ci]
338
+ });
339
+ for (let j = 0; j < ci_rowspan; j++) {
340
+ Transforms.setNodes(editor, {
341
+ 'is_combined': true
342
+ }, {
343
+ at: [...tablePath, i + j, columnIndex + 1]
344
+ });
345
+ }
346
+ break;
347
+ }
348
+ }
349
+ } else {
350
+ if (colspan > 1) {
351
+ Transforms.setNodes(editor, {
352
+ colspan: colspan + 1
353
+ }, {
354
+ at: [...tablePath, i, columnIndex]
355
+ });
356
+ for (let j = 0; j < rowspan; j++) {
357
+ Transforms.setNodes(editor, {
358
+ 'is_combined': true
359
+ }, {
360
+ at: [...tablePath, i + j, columnIndex + 1]
361
+ });
362
+ }
363
+ }
364
+ }
365
+ }
241
366
  };
242
367
  export const insertTableElement = function (editor, type) {
243
368
  let position = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : TABLE_ELEMENT_POSITION.AFTER;
@@ -260,6 +385,12 @@ export const insertTableElement = function (editor, type) {
260
385
  Transforms.insertNodes(editor, row, {
261
386
  at: targetPath
262
387
  });
388
+ // handle combined cells
389
+ if (!(rowIndex == 0 && position === TABLE_ELEMENT_POSITION.BEFORE)) {
390
+ const targetRowIndex = position === TABLE_ELEMENT_POSITION.AFTER ? rowIndex : rowIndex - 1;
391
+ const currentTable = getSelectedNodeByType(editor, ELEMENT_TYPE.TABLE);
392
+ handleCombinedCellsAfterInsertTableRow(editor, tablePath, currentTable, targetRowIndex);
393
+ }
263
394
  }
264
395
  const focusPath = [...targetPath, cellIndex];
265
396
  focusEditor(editor, focusPath);
@@ -279,12 +410,121 @@ export const insertTableElement = function (editor, type) {
279
410
  at: newCellPath
280
411
  });
281
412
  }
413
+ // handle combined cells
414
+ if (!(cellIndex == 0 && position === TABLE_ELEMENT_POSITION.BEFORE)) {
415
+ const targetColumnIndex = position === TABLE_ELEMENT_POSITION.AFTER ? cellIndex : cellIndex - 1;
416
+ const currentTable = getSelectedNodeByType(editor, ELEMENT_TYPE.TABLE);
417
+ handleCombinedCellsAfterInsertTableColumn(editor, tablePath, currentTable, targetColumnIndex);
418
+ }
282
419
  }
283
420
  const focusPath = [...tablePath, rowIndex, cellIndex + 1, 0];
284
421
  focusEditor(editor, focusPath);
285
422
  return;
286
423
  }
287
424
  };
425
+ export const combineCells = editor => {
426
+ const {
427
+ tablePath
428
+ } = getSelectedInfo(editor);
429
+ const {
430
+ minColIndex,
431
+ maxColIndex,
432
+ minRowIndex,
433
+ maxRowIndex
434
+ } = editor.tableSelectedRange;
435
+ let newCellContent = [];
436
+ for (let i = minRowIndex; i < maxRowIndex + 1; i++) {
437
+ for (let j = minColIndex; j < maxColIndex + 1; j++) {
438
+ let nodePath = [...tablePath, i, j];
439
+ let node = Editor.node(editor, nodePath);
440
+ if (node[0].is_combined) {
441
+ continue;
442
+ }
443
+ Transforms.setNodes(editor, {
444
+ 'is_combined': true
445
+ }, {
446
+ at: nodePath
447
+ });
448
+ newCellContent = newCellContent.concat(node[0].children);
449
+ }
450
+ }
451
+ const targetCellPath = [...tablePath, minRowIndex, minColIndex];
452
+ const newCell = generateTableCell();
453
+ newCell.children = newCellContent;
454
+ newCell.rowspan = maxRowIndex - minRowIndex + 1;
455
+ newCell.colspan = maxColIndex - minColIndex + 1;
456
+ // keep row.chilren.length not changed
457
+ Transforms.removeNodes(editor, {
458
+ at: targetCellPath
459
+ });
460
+ Transforms.insertNodes(editor, newCell, {
461
+ at: targetCellPath
462
+ });
463
+ focusEditor(editor, targetCellPath);
464
+
465
+ // for clicking the 'combine cell' icon in the toolbar
466
+ const eventBus = EventBus.getInstance();
467
+ eventBus.dispatch(INTERNAL_EVENT.CANCEL_TABLE_SELECT_RANGE);
468
+ };
469
+ export const splitCell = (editor, rowNumber, columnNumber) => {
470
+ if (rowNumber == 1 && columnNumber == 1) {
471
+ return;
472
+ }
473
+ const {
474
+ cell,
475
+ rowIndex,
476
+ cellIndex,
477
+ cellPath,
478
+ tablePath
479
+ } = getSelectedInfo(editor);
480
+ const {
481
+ rowspan,
482
+ colspan
483
+ } = cell;
484
+ const rowspanBase = Math.floor(rowspan / rowNumber);
485
+ const rowspanLeft = rowspan % rowNumber;
486
+ const colspanBase = Math.floor(colspan / columnNumber);
487
+ const colspanLeft = colspan % columnNumber;
488
+ const cellNumber = rowNumber * columnNumber;
489
+ const dataBlockNumber = Math.ceil(cell.children.length / cellNumber);
490
+ let firstNewCell;
491
+ let rowspanSum = 0;
492
+ for (let i = 0; i < rowNumber; i++) {
493
+ let newRowSpan = rowspanBase + (i + 1 <= rowspanLeft ? 1 : 0);
494
+ let colspanSum = 0;
495
+ for (let j = 0; j < columnNumber; j++) {
496
+ const newCell = generateTableCell();
497
+ let startIndex = (i * columnNumber + j) * dataBlockNumber;
498
+ if (startIndex < cell.children.length) {
499
+ let endIndex = Math.min(startIndex + dataBlockNumber, cell.children.length);
500
+ newCell.children = cell.children.slice(startIndex, endIndex);
501
+ }
502
+ newCell.rowspan = newRowSpan;
503
+ newCell.colspan = colspanBase + (j + 1 <= colspanLeft ? 1 : 0);
504
+ const newRowIndex = rowIndex + rowspanSum;
505
+ const newCellIndex = cellIndex + colspanSum;
506
+ const targetCellPath = [...tablePath, newRowIndex, newCellIndex];
507
+ if (i == 0 && j == 0) {
508
+ firstNewCell = newCell;
509
+ } else {
510
+ Transforms.removeNodes(editor, {
511
+ at: targetCellPath
512
+ });
513
+ Transforms.insertNodes(editor, newCell, {
514
+ at: targetCellPath
515
+ });
516
+ }
517
+ colspanSum += newCell.colspan;
518
+ }
519
+ rowspanSum += newRowSpan;
520
+ }
521
+ Transforms.removeNodes(editor, {
522
+ at: cellPath
523
+ });
524
+ Transforms.insertNodes(editor, firstNewCell, {
525
+ at: cellPath
526
+ });
527
+ };
288
528
  export const removeTable = (editor, path) => {
289
529
  let validPath = path;
290
530
  if (!validPath) {
@@ -336,6 +576,7 @@ export const removeTableElement = (editor, type) => {
336
576
  focusEditor(editor, focusPath);
337
577
  return;
338
578
  }
579
+ handleCombinedCellsBeforeDeleteTableRow(editor, tablePath, table, rowIndex);
339
580
  Transforms.removeNodes(editor, {
340
581
  at: rowPath
341
582
  });
@@ -378,6 +619,7 @@ export const removeTableElement = (editor, type) => {
378
619
  const newColumns = columns.slice(0);
379
620
  newColumns.splice(cellIndex, 1);
380
621
  updateColumnWidth(editor, table, newColumns);
622
+ handleCombinedCellsBeforeDeleteTableColumn(editor, tablePath, table, cellIndex);
381
623
  for (let i = 0; i < tableSize[0]; i++) {
382
624
  const cellPath = [...tablePath, i, cellIndex];
383
625
  Transforms.removeNodes(editor, {
@@ -390,6 +632,87 @@ export const removeTableElement = (editor, type) => {
390
632
  return;
391
633
  }
392
634
  };
635
+
636
+ // handle combined cells before deleting a row
637
+ export const handleCombinedCellsBeforeDeleteTableRow = (editor, tablePath, table, rowIndex) => {
638
+ const cells = table.children[rowIndex].children;
639
+ for (let i = 0, len = cells.length; i < len; i++) {
640
+ const {
641
+ is_combined,
642
+ rowspan,
643
+ colspan
644
+ } = cells[i];
645
+ if (is_combined) {
646
+ for (let ri = rowIndex - 1; ri >= 0; ri--) {
647
+ const {
648
+ is_combined: ri_is_combined,
649
+ rowspan: ri_rowspan
650
+ } = table.children[ri].children[i];
651
+ if (!ri_is_combined && ri + ri_rowspan - 1 >= rowIndex) {
652
+ Transforms.setNodes(editor, {
653
+ rowspan: ri_rowspan - 1
654
+ }, {
655
+ at: [...tablePath, ri, i]
656
+ });
657
+ break;
658
+ }
659
+ }
660
+ } else {
661
+ if (rowspan > 1) {
662
+ const targetCellPath = [...tablePath, rowIndex + 1, i];
663
+ const newCell = generateTableCell();
664
+ newCell.rowspan = rowspan - 1;
665
+ newCell.colspan = colspan;
666
+ Transforms.removeNodes(editor, {
667
+ at: targetCellPath
668
+ });
669
+ Transforms.insertNodes(editor, newCell, {
670
+ at: targetCellPath
671
+ });
672
+ }
673
+ }
674
+ }
675
+ };
676
+
677
+ // handle combined cells before deleting a column
678
+ export const handleCombinedCellsBeforeDeleteTableColumn = (editor, tablePath, table, columnIndex) => {
679
+ for (let i = 0, len = table.children.length; i < len; i++) {
680
+ const {
681
+ is_combined,
682
+ rowspan,
683
+ colspan
684
+ } = table.children[i].children[columnIndex];
685
+ if (is_combined) {
686
+ for (let ci = columnIndex - 1; ci >= 0; ci--) {
687
+ const {
688
+ is_combined: ci_is_combined,
689
+ colspan: ci_colspan
690
+ } = table.children[i].children[ci];
691
+ if (!ci_is_combined && ci + ci_colspan - 1 >= columnIndex) {
692
+ Transforms.setNodes(editor, {
693
+ colspan: ci_colspan - 1
694
+ }, {
695
+ at: [...tablePath, i, ci]
696
+ });
697
+ break;
698
+ }
699
+ }
700
+ } else {
701
+ if (colspan > 1) {
702
+ const targetCellPath = [...tablePath, i, columnIndex + 1];
703
+ const newCell = generateTableCell();
704
+ newCell.rowspan = rowspan;
705
+ newCell.colspan = colspan - 1;
706
+ Transforms.removeNodes(editor, {
707
+ at: targetCellPath
708
+ });
709
+ Transforms.insertNodes(editor, newCell, {
710
+ at: targetCellPath
711
+ });
712
+ }
713
+ }
714
+ }
715
+ };
393
716
  export const setTableSelectedRange = (editor, range) => {
394
717
  if (range) {
395
718
  editor.tableSelectedRange = range;
@@ -0,0 +1,32 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import React, { useCallback, useEffect, useState } from 'react';
3
+ import { MenuItem } from '../../../../commons';
4
+ import { MENUS_CONFIG_MAP, COMBINE_CELL } from '../../../../constants';
5
+ import { isCombineCellsDisabled, combineCells } from '../../helpers';
6
+ const CombineCells = _ref => {
7
+ let {
8
+ isRichEditor,
9
+ className,
10
+ editor,
11
+ readonly
12
+ } = _ref;
13
+ const [disabled, setDisabled] = useState(false);
14
+ useEffect(() => {
15
+ const disabled = isCombineCellsDisabled(editor, readonly);
16
+ setDisabled(disabled);
17
+ }, [editor, editor.tableSelectedRange, readonly]);
18
+ const _combineCells = useCallback(() => {
19
+ if (readonly) return;
20
+ combineCells(editor);
21
+ }, [editor, readonly]);
22
+ const menuConfig = MENUS_CONFIG_MAP[COMBINE_CELL];
23
+ const props = _objectSpread(_objectSpread({
24
+ isRichEditor,
25
+ disabled: disabled,
26
+ isActive: false
27
+ }, menuConfig), {}, {
28
+ onMouseDown: _combineCells
29
+ });
30
+ return /*#__PURE__*/React.createElement(MenuItem, props);
31
+ };
32
+ export default CombineCells;
@@ -1,7 +1,3 @@
1
- .sdoc-table-menu-group.menu-group .menu-group-item:not(.sdoc-remove-table) {
2
- width: 36px;
3
- }
4
-
5
1
  .sdoc-table-menu-group .sdoc-menu-with-dropdown .sdoc-menu-with-dropdown-icon {
6
2
  width: 24px;
7
3
  }
@@ -4,6 +4,7 @@ import { MenuGroup } from '../../../../commons';
4
4
  import { isAllInTable } from '../../helpers';
5
5
  import CellBackgroundColorMenu from './cell-bg-color-menu';
6
6
  import CellTextAlignMenu from './cell-text-align-menu';
7
+ import CombineCells from './combine-cells';
7
8
  import RemoveTable from './remove-table-menu';
8
9
  import TableColumnMenu from './table-column-menu';
9
10
  import TableRowMenu from './table-row-menu';
@@ -32,6 +33,11 @@ const ActiveTableMenu = _ref => {
32
33
  isRichEditor: isRichEditor,
33
34
  className: className,
34
35
  readonly: readonly
36
+ }), /*#__PURE__*/React.createElement(CombineCells, {
37
+ editor: editor,
38
+ isRichEditor: isRichEditor,
39
+ className: className,
40
+ readonly: readonly
35
41
  }), /*#__PURE__*/React.createElement(RemoveTable, {
36
42
  editor: editor,
37
43
  isRichEditor: isRichEditor,