diff --git a/.changeset/five-melons-travel.md b/.changeset/five-melons-travel.md new file mode 100644 index 00000000..dc7f90ac --- /dev/null +++ b/.changeset/five-melons-travel.md @@ -0,0 +1,6 @@ +--- +'@editablejs/deserializer': patch +'@editablejs/plugin-table': patch +--- + +fix: fix issues about copy tables from other html page which may cause page collapse and wrong enter press respond and missing table header and missing last empty cell. diff --git a/.gitignore b/.gitignore index 9de386c8..e792c2ff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ node_modules .pnp .pnp.js - +.history # testing coverage diff --git a/packages/deserializer/src/html.ts b/packages/deserializer/src/html.ts index 6a90ed64..946769eb 100644 --- a/packages/deserializer/src/html.ts +++ b/packages/deserializer/src/html.ts @@ -1,4 +1,4 @@ -import { Editor, Descendant, Element, Text, DOMNode, isDOMText } from '@editablejs/models' +import { DOMNode, Descendant, Editor, Element, Text, isDOMText } from '@editablejs/models' export interface HTMLDeserializerOptions { element?: Omit diff --git a/packages/plugins/list/src/task/plugin/with-task-list.tsx b/packages/plugins/list/src/task/plugin/with-task-list.tsx index 967b8409..7f178288 100644 --- a/packages/plugins/list/src/task/plugin/with-task-list.tsx +++ b/packages/plugins/list/src/task/plugin/with-task-list.tsx @@ -1,7 +1,7 @@ -import { RenderElementProps, ElementAttributes, Editable, Hotkey } from '@editablejs/editor' -import { Transforms, List } from '@editablejs/models' -import tw, { styled, css, theme } from 'twin.macro' -import { ListStyles, ListLabelStyles, renderList } from '../../styles' +import { Editable, ElementAttributes, Hotkey, RenderElementProps } from '@editablejs/editor' +import { List, Transforms } from '@editablejs/models' +import tw, { css, styled, theme } from 'twin.macro' +import { ListLabelStyles, ListStyles, renderList } from '../../styles' import { DATA_TASK_CHECKED_KEY, TASK_LIST_KEY } from '../constants' import { TaskList } from '../interfaces/task-list' import { TaskListHotkey, TaskListOptions } from '../options' @@ -70,6 +70,8 @@ const TaskElement = ({ checked, onChange }: TaskProps) => { ) } +// 如果不期望任务列表选中后出现下划线,去掉 &[data-task-checked='true'] {下面的 ${tw`line-through`}, +// 本次revert,保留原项目设计,后期通过脚本实现调整 const StyledTask = styled(ListStyles)` &[data-task-checked='true'] { ${tw`line-through`} diff --git a/packages/plugins/mark/src/deserializer/html.ts b/packages/plugins/mark/src/deserializer/html.ts index 74a35e13..0dd46ed1 100644 --- a/packages/plugins/mark/src/deserializer/html.ts +++ b/packages/plugins/mark/src/deserializer/html.ts @@ -29,7 +29,11 @@ export const withMarkHTMLDeserializerTransform: HTMLDeserializerWithTransform = ) { mark.underline = true } - if (node.nodeName === 'S' || style.textDecoration === 'line-through') { + if ( + node.nodeName === 'S' || + node.nodeName === 'STRIKE' || + style.textDecoration === 'line-through' + ) { mark.strikethrough = true } if (node.nodeName === 'CODE' || style.fontFamily === 'monospace') { diff --git a/packages/plugins/table/src/cell/deserializer/html.ts b/packages/plugins/table/src/cell/deserializer/html.ts index 7d3987b9..b9bd3e15 100644 --- a/packages/plugins/table/src/cell/deserializer/html.ts +++ b/packages/plugins/table/src/cell/deserializer/html.ts @@ -1,19 +1,30 @@ -import { HTMLDeserializerWithTransform } from '@editablejs/deserializer/html' -import { Descendant, isDOMHTMLElement } from '@editablejs/models' +import { + HTMLDeserializerOptions, + HTMLDeserializerWithTransform, +} from '@editablejs/deserializer/html' +import { Descendant, Editor, isDOMHTMLElement, Text } from '@editablejs/models' import { TABLE_CELL_KEY } from '../constants' -import { TableCell } from '../interfaces/table-cell' -export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransform = ( - next, - serializer, -) => { +export interface TableCellHTMLDeserializerOptions extends HTMLDeserializerOptions { + editor: Editor +} +export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransform< + TableCellHTMLDeserializerOptions +> = (next, deserializer, { editor }) => { return (node, options = {}) => { const { text } = options - if (isDOMHTMLElement(node) && node.nodeName === 'TD') { + if (isDOMHTMLElement(node) && ['TD', 'TH'].includes(node.nodeName)) { const children: Descendant[] = [] + let isFontWeight = false + if (node.nodeName === 'TH') { + isFontWeight = true + } for (const child of node.childNodes) { - const content = serializer.transform(child, { - text, + const content = deserializer.transform(child, { + text: { + ...text, + bold: isFontWeight ? true : undefined, + }, matchNewline: true, }) children.push(...content) @@ -22,12 +33,46 @@ export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransfo children.push({ children: [{ text: '' }] }) } const { colSpan, rowSpan } = node as HTMLTableCellElement - const cell: TableCell = { - type: TABLE_CELL_KEY, - children, - colspan: colSpan, - rowspan: rowSpan, + + const notBlocks: Descendant[] = [] + const newChildren: Descendant[] = [] + + // 如果不是block,那么就把notBlocks里的内容放到一个新的child里 + const appendNotBlocks = () => { + if (notBlocks.length > 0) { + const newChild = { type: 'paragraph', children: notBlocks } + newChildren.push(newChild) + notBlocks.length = 0 + } + } + children.forEach(child => { + // 2024/01/31 10:31:42@需求ID: 产品工作站代码优化@ZhaiCongrui/GW00247400:处理有的带有 链接 的单元格,编辑时回车光标跳到下个单元格的问题 + // child 的 type 为 link时,会有此类问题,所以单独处理 + if (!Editor.isBlock(editor, child)) { + notBlocks.push(child) + } else { + if (notBlocks.length > 0) { + appendNotBlocks() + } + newChildren.push(child) + } + }) + if (notBlocks.length > 0) { + appendNotBlocks() } + const span = node.getAttribute('span')?.split(',').map(Number) + const cell = span + ? { + type: TABLE_CELL_KEY, + children: newChildren, + span, + } + : { + type: TABLE_CELL_KEY, + children: newChildren, + colspan: colSpan, + rowspan: rowSpan, + } return [cell] } return next(node, options) diff --git a/packages/plugins/table/src/cell/plugin/with-table-cell.tsx b/packages/plugins/table/src/cell/plugin/with-table-cell.tsx index bed89d41..f33c5bfd 100644 --- a/packages/plugins/table/src/cell/plugin/with-table-cell.tsx +++ b/packages/plugins/table/src/cell/plugin/with-table-cell.tsx @@ -1,7 +1,7 @@ import { Editable } from '@editablejs/editor' import { GridCell, Node } from '@editablejs/models' -import { setOptions, TableCellOptions } from '../options' import { CellInnerStyles, CellStyles } from '../../components/styles' +import { TableCellOptions, setOptions } from '../options' import { TableCellEditor } from './table-cell-editor' export const withTableCell = (editor: T, options: TableCellOptions = {}) => { diff --git a/packages/plugins/table/src/components/action.tsx b/packages/plugins/table/src/components/action.tsx index 26937ba8..bb0e4bba 100644 --- a/packages/plugins/table/src/components/action.tsx +++ b/packages/plugins/table/src/components/action.tsx @@ -1,7 +1,16 @@ -import { cancellablePromise, Editable, useCancellablePromises, Slot } from '@editablejs/editor' -import { Transforms, Grid, Editor } from '@editablejs/models' -import * as React from 'react' +import { Editable, Slot, cancellablePromise, useCancellablePromises } from '@editablejs/editor' +import { Editor, Grid, Transforms } from '@editablejs/models' import { Icon } from '@editablejs/ui' +import * as React from 'react' +import { TABLE_CELL_KEY } from '../cell/constants' +import { defaultTableMinColWidth } from '../cell/options' +import { TableDrag, useTableDragTo, useTableDragging } from '../hooks/use-drag' +import { TableRow } from '../row' +import { TABLE_ROW_KEY } from '../row/constants' +import { defaultTableMinRowHeight } from '../row/options' +import { RowStore } from '../row/store' +import { useTableOptions } from '../table/options' +import { adaptiveExpandColumnWidthInContainer } from '../table/utils' import { ColsInsertIconStyles, ColsInsertLineStyles, @@ -16,15 +25,6 @@ import { RowsSplitLineStyles, RowsSplitStyles, } from './styles' -import { TableDrag, useTableDragging, useTableDragTo } from '../hooks/use-drag' -import { TABLE_CELL_KEY } from '../cell/constants' -import { TableRow } from '../row' -import { TABLE_ROW_KEY } from '../row/constants' -import { useTableOptions } from '../table/options' -import { defaultTableMinColWidth } from '../cell/options' -import { defaultTableMinRowHeight } from '../row/options' -import { adaptiveExpandColumnWidthInContainer } from '../table/utils' -import { RowStore } from '../row/store' const TYPE_COL = 'col' const TYPE_ROW = 'row' @@ -229,13 +229,126 @@ const SplitActionDefault: React.FC = ({ const cancellablePromisesApi = useCancellablePromises() const handleDragSplitUp = React.useCallback(() => { + if (!dragRef.current) return + const { type: type2 } = dragRef.current + const path = Editable.findPath(editor, table) + + if (type2 === TYPE_COL) { + const newGrid = Grid.above(editor, path) + if (!newGrid) return + const { children: rows } = newGrid[0] + let contentHeight = 0 + const heightArray: number[] = [] + for (let i = 0; i < rows.length; i++) { + const row = rows[i] + const trRow = Editable.toDOMNode(editor, row) + // 2024/01/19 10:40:43 @guoxiaona/GW00234847:遍历trRow,判断所有子元素中rowSpan为1且colSpan为1,且style中的display不为none的子元素 + const trRowChildrenArray = Array.from(trRow.children) + let child: any = null + trRowChildrenArray.forEach((item: any) => { + const rowspan = item.rowSpan + const colspan = item.colSpan + const style = item.style + const display = style.display + if (rowspan === 1 && colspan === 1 && display !== 'none') { + child = item + } + }) + if (!child) continue + const rect = child.getBoundingClientRect() + contentHeight = Math.max(rect.height, minRowHeight) + heightArray.push(contentHeight) + } + // 2024/01/19 10:41:10 @guoxiaona/GW00234847:heightArray中当前数之前所有数值的和 + const heightArrayMapOnlyPrev = heightArray.map((item, index) => { + let sum = 0 + for (let i = 0; i < index; i++) { + sum += heightArray[i] + } + return sum + }) + // 2024/01/19 10:41:27 @guoxiaona/GW00234847:heightArray中当前数和之前所有数值的和 + const heightArrayMapAllPrev = heightArray.map((item, index) => { + let sum = 0 + for (let i = 0; i <= index; i++) { + sum += heightArray[i] + } + return sum + }) + + const cld = Editable.toDOMNode(editor, rows[0]).firstElementChild + + // 2024/01/19 10:41:43 @guoxiaona/GW00234847:获取child的祖先节点table所在的节点的父节点的第二个子节点 + const t = cld?.closest('table') + const tableParent = t?.parentElement + const tableParentChildrenArray = Array.from(tableParent!.children) + const tableTopBorder = tableParentChildrenArray?.[0] + const tableLeftBorder = tableParentChildrenArray?.[1] + // 2024/01/19 10:42:07 @guoxiaona/GW00234847:获取tableLeftBorder中所有子元素带有属性data-table-row的,并按照该属性值放到一个数组中borderHeightArray + const borderHeightArray: number[] = [] + const tableLeftBorderChildrenArray = Array.from(tableLeftBorder?.children) + const tableLeftBorderPerRowArray: any[] = [] + tableLeftBorderChildrenArray.forEach((item: any) => { + if (item.dataset.tableRow) { + tableLeftBorderPerRowArray.push(item) + // 2024/01/19 10:42:28 @guoxiaona/GW00234847:需要从item中获取当前style中的height值,并放入borderHeightArray中 + const style = item.style + const height = Number(style.height.replace('px', '')) + borderHeightArray.push(height) + } + }) + // 2024/01/19 10:42:47 @guoxiaona/GW00234847:检测heightArray和borderHeightArray对应下标的数值相差是否在10(行高大于10)以内,如果是,则不做任何处理,否则更新当前行对应的高度 + let ifRowHeightUpdated = false + heightArray.forEach((item, index) => { + const borderHeight = borderHeightArray[index] + const itemNumber = Number(item) + const diff = Math.abs(borderHeight - itemNumber) + // 2024/01/19 10:43:20 @guoxiaona/GW00234847:在这里更新当前行及后面行的高度及top值 + if (diff > 10 || ifRowHeightUpdated) { + ifRowHeightUpdated = true + // 2024/01/19 10:43:33 @guoxiaona/GW00234847:调整当前tableLeftBorderPerRowArray[index]的高度为heightArray[index] + 1,top为heightArrayMapOnlyPrev[index] + const currentRow = tableLeftBorderPerRowArray[index] + const currentRowStyle = currentRow.style + currentRowStyle.height = `${itemNumber + 1}px` + currentRowStyle.top = `${heightArrayMapOnlyPrev[index]}px` + // 2024/01/19 10:43:47 @guoxiaona/GW00234847:调整当前tableLeftBorderPerRowArray[index]后面两个兄弟元素的top值为heightArrayMapAllPrev[index] - 1 + // 2024/01/19 10:44:00 @guoxiaona/GW00234847:需要重新获取后面两个兄弟元素,这两个兄弟元素没在tableLeftBorderPerRowArray[index]里 + const nextSibling = currentRow.nextElementSibling + const nextSiblingStyle = nextSibling.style + nextSiblingStyle.top = `${heightArrayMapAllPrev[index] - 1}px` + const nextNextSibling = nextSibling.nextElementSibling + const nextNextSiblingStyle = nextNextSibling.style + nextNextSiblingStyle.top = `${heightArrayMapAllPrev[index] - 1}px` + } + }) + // 2024/01/19 10:44:13 @guoxiaona/GW00234847:如果行高调整过,则需要对应调整列的高度为heightArrayMapAllPrev的最后一个元素的值 + 9 + if (ifRowHeightUpdated) { + // 2024/01/19 10:44:25 @guoxiaona/GW00234847:获取tableTopBorder中所有子元素带有属性data-table-col的子元素,并放到一个数组中tableTopBorderPerColArray + const tableTopBorderChildrenArray = Array.from(tableTopBorder?.children) + const tableTopBorderPerColArray: any[] = [] + tableTopBorderChildrenArray.forEach((item: any) => { + if (item.dataset.tableCol) { + tableTopBorderPerColArray.push(item) + } + }) + // 2024/01/19 10:44:46 @guoxiaona/GW00234847:遍历tableTopBorderPerColArray中每一个元素的兄弟节点的兄弟节点,找到后,将高度调整为heightArrayMapAllPrev的最后一个元素的值 + 9 + tableTopBorderPerColArray.forEach(item => { + const nextNextSibling = item.nextElementSibling.nextElementSibling + const nextNextSiblingStyle = nextNextSibling.style + nextNextSiblingStyle.height = `${ + heightArrayMapAllPrev[heightArrayMapAllPrev.length - 1] + 9 + }px` + }) + } + } + dragRef.current = null isDrag.current = false setHover(false) cancellablePromisesApi.clearPendingPromises() window.removeEventListener('mousemove', handleDragSplitMove) window.removeEventListener('mouseup', handleDragSplitUp) - }, [cancellablePromisesApi, dragRef, handleDragSplitMove]) + }, [cancellablePromisesApi, dragRef, handleDragSplitMove, editor, minRowHeight, table]) const handleMouseDown = (e: React.MouseEvent) => { e.preventDefault() diff --git a/packages/plugins/table/src/row/deserializer/html.ts b/packages/plugins/table/src/row/deserializer/html.ts index d456ed7e..0c919e03 100644 --- a/packages/plugins/table/src/row/deserializer/html.ts +++ b/packages/plugins/table/src/row/deserializer/html.ts @@ -5,8 +5,8 @@ import { import { Editor, isDOMHTMLElement } from '@editablejs/models' import { TableCell } from '../../cell' import { TABLE_ROW_KEY } from '../constants' -import { getOptions } from '../options' import { TableRow } from '../interfaces/table-row' +import { getOptions } from '../options' export interface TableRowHTMLDeserializerOptions extends HTMLDeserializerOptions { editor: Editor @@ -17,7 +17,7 @@ export const withTableRowHTMLDeserializerTransform: HTMLDeserializerWithTransfor > = (next, serializer, { editor }) => { return (node, options = {}) => { const { text } = options - if (isDOMHTMLElement(node) && ['TR', 'TH'].includes(node.tagName)) { + if (isDOMHTMLElement(node) && ['TR'].includes(node.tagName)) { const options = getOptions(editor) const h = (node as HTMLElement).style.height const height = parseInt(!h ? '0' : h, 10) diff --git a/packages/plugins/table/src/table/deserializer/html.ts b/packages/plugins/table/src/table/deserializer/html.ts index 7ebe321a..b69710cc 100644 --- a/packages/plugins/table/src/table/deserializer/html.ts +++ b/packages/plugins/table/src/table/deserializer/html.ts @@ -8,7 +8,7 @@ import { TableRow } from '../../row' import { TABLE_KEY } from '../constants' import { Table } from '../interfaces/table' import { getOptions } from '../options' -import { calculateAverageColumnWidthInContainer } from '../utils' +import { supplementMergeCells } from './utils' export interface TableHTMLDeserializerOptions extends HTMLDeserializerOptions { editor: Editor @@ -20,29 +20,56 @@ export const withTableHTMLDeserializerTransform: HTMLDeserializerWithTransform< return (node, options = {}) => { const { text } = options if (isDOMHTMLElement(node) && node.nodeName === 'TABLE') { + const tableElement = node as HTMLTableElement + const style = tableElement.getAttribute('style') ?? '' + const ignoreTable = + style.includes('display: none') || + style.includes('visibility: hidden') || + style.includes('mso-ignore:table') + if (ignoreTable) { + return [] + } + supplementMergeCells(tableElement) const children: TableRow[] = [] - for (const child of node.childNodes) { + for (const child of tableElement.childNodes) { children.push(...(serializer.transform(child, { text, matchNewline: true }) as TableRow[])) } const { minColWidth = defaultTableMinColWidth } = getOptions(editor) + // start update col Taylor + const container = document.createElement('div') + container.style.visibility = 'hidden' + container.style.position = 'absolute' + + container.appendChild(tableElement) + + document.body.appendChild(container) + if (tableElement.rows.length === 0) { + return [] + } + const firstRow = tableElement.rows[0] + const newColgroup = document.createElement('colgroup') + for (let i = 0; i < firstRow.cells.length; i++) { + const cell = firstRow.cells[i] + const colspan = cell.colSpan + for (let j = 0; j < colspan; j++) { + const col = document.createElement('col') + const width = cell.offsetWidth + col.style.width = `${width}px` + newColgroup.appendChild(col) + } + } + const colgroup = tableElement.querySelector('colgroup') + if (colgroup) { + node.removeChild(colgroup) + } + tableElement.insertBefore(newColgroup, tableElement.firstChild) + document.body.removeChild(container) + + // the end update col Taylor const colsWidth = Array.from(node.querySelectorAll('col')).map(c => { const w = c.width || c.style.width - return Math.min(parseInt(w === '' ? '0' : w, 10), minColWidth) + return Math.max(parseInt(w === '' ? '0' : w, 10), minColWidth) }) - const colCount = children[0].children.length - if (colsWidth.length === 0) { - colsWidth.push( - ...calculateAverageColumnWidthInContainer(editor, { - cols: colCount, - minWidth: minColWidth, - getWidth: width => width - 1, - }), - ) - } else if (colsWidth.length < colCount) { - // TODO - } else if (colsWidth.length > colCount) { - // TODO - } const table: Table = { type: TABLE_KEY, diff --git a/packages/plugins/table/src/table/deserializer/utils.ts b/packages/plugins/table/src/table/deserializer/utils.ts new file mode 100644 index 00000000..f7c52d44 --- /dev/null +++ b/packages/plugins/table/src/table/deserializer/utils.ts @@ -0,0 +1,125 @@ +/** + * Supplement the missing rows and columns of the merged cells + */ +export const supplementMergeCells = (tableElement: HTMLTableElement) => { + trimStartTr(tableElement) + fixNumberTr(tableElement) + + for (let rowIndex = 0; rowIndex < tableElement.rows.length; rowIndex++) { + const row = tableElement.rows[rowIndex] + for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { + const cell = row.cells[cellIndex] + const style = cell.getAttribute('style') ?? '' + // mso-ignore:rowspan mso-ignore:colspan often appears in word, indicating that merged cells are ignored + const ignoreRowspan = style.includes('mso-ignore:rowspan') + const ignoreColspan = style.includes('mso-ignore:colspan') + const colspanValue = cell.getAttribute('colspan') + const colspan = colspanValue && !ignoreColspan ? parseInt(colspanValue) : 1 + const rowspanValue = cell.getAttribute('rowspan') + const rowspan = rowspanValue && !ignoreRowspan ? parseInt(rowspanValue) : 1 + + if (colspan > 1 || rowspan > 1) { + for (let i = 0; i < rowspan; i++) { + const r = i + rowIndex + if (!tableElement.rows[r]) { + const newRow = tableElement.insertRow(r) + for (let j = 0; j < cellIndex; j++) { + newRow.insertCell(j) + } + } + for (let j = 0; j < colspan; j++) { + const c = j + cellIndex + if (!tableElement.rows[r].cells[c]) { + const cell = tableElement.rows[r].insertCell(c) + cell.setAttribute('span', `${rowIndex},${cellIndex}`) + } + } + } + } + } + } +} + +const trimStartTr = (tableElement: HTMLTableElement) => { + const rows = tableElement.rows + if (rows.length > 0) { + const firstRow = rows[0] + if (firstRow.cells.length === 0) { + tableElement.deleteRow(0) + } + } +} + +const fixNumberTr = (tableElement: HTMLTableElement) => { + const rows = tableElement.rows + const rowCount = rows?.length || 0 + let colCounts: Array = [] + let firstColCount: number = 0 // 第一列的单元格个数 + let cellCounts = [] // 每行单元格个数 + let totalCellCounts = 0 // 总单元格个数 + let emptyCounts = 0 // 跨行合并缺损的单元格 + // 已经存在一行中的 td 的最大数,最终算出来的最大列数一定要大于等于这个值 + let maxCellCounts = 0 // 在不确定是否缺少tr时,先拿到已经存在的td,和一些关键信息 + + for (let r = 0; r < rowCount; r++) { + const row = rows[r] + const cells = row.cells + let cellCountThisRow = 0 + + for (let c = 0; c < cells.length; c++) { + const { rowSpan, colSpan } = cells[c] + totalCellCounts += rowSpan * colSpan + cellCountThisRow += colSpan + if (rowSpan > 1) { + emptyCounts += (rowSpan - 1) * colSpan + } + } + cellCounts[r] = cellCountThisRow + if (r === 0) { + firstColCount = cellCountThisRow + } + maxCellCounts = Math.max(cellCountThisRow, maxCellCounts) + } + // number拷贝的一定是首行列数能被单元格总数整除 + const isNumber1 = totalCellCounts / firstColCount // number拷贝的一定是首行列数最大 + const isNumber2 = firstColCount === maxCellCounts + const isNumber = isNumber1 && isNumber2 // 判断是否是 number, 是因为 number 需要考虑先修复省略的 tr,否则后面修复出来就会有问题 + + if (isNumber) { + let lossCellCounts = 0 + cellCounts.forEach(cellCount => { + lossCellCounts += maxCellCounts - cellCount + }) + + if (lossCellCounts !== emptyCounts) { + const missCellCounts = emptyCounts - lossCellCounts + if (missCellCounts / maxCellCounts) { + let lossRowIndex = [] // 记录哪一行缺 tr + + for (let _r = 0; _r < rowCount; _r++) { + const _row = rows[_r] + const _cells = _row.cells + let realRow: number = _r + lossRowIndex.length + + while (colCounts[realRow] === maxCellCounts) { + lossRowIndex.push(realRow) + realRow++ + } + + for (let _c2 = 0; _c2 < _cells.length; _c2++) { + const { rowSpan, colSpan } = _cells[_c2] + if (rowSpan > 1) { + for (let rr = 1; rr < rowSpan; rr++) { + colCounts[realRow + rr] = (colCounts[realRow + rr] || 0) + colSpan + } + } + } + } + + lossRowIndex.forEach(row => { + tableElement.insertRow(row) + }) + } + } + } +}