From eea5f0356ebfcec6c8d6a880af376da7d8b00013 Mon Sep 17 00:00:00 2001 From: "TECH\\GW00234847" Date: Fri, 19 Jan 2024 16:52:18 +0800 Subject: [PATCH 01/12] fix: fix table tr style with specific style and row height after drag column --- .../table/src/cell/deserializer/html.ts | 2 +- .../plugins/table/src/components/action.tsx | 158 ++++++++++++++++-- .../table/src/row/deserializer/html.ts | 4 +- 3 files changed, 149 insertions(+), 15 deletions(-) diff --git a/packages/plugins/table/src/cell/deserializer/html.ts b/packages/plugins/table/src/cell/deserializer/html.ts index 7d3987b9..8216c8d7 100644 --- a/packages/plugins/table/src/cell/deserializer/html.ts +++ b/packages/plugins/table/src/cell/deserializer/html.ts @@ -9,7 +9,7 @@ export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransfo ) => { return (node, options = {}) => { const { text } = options - if (isDOMHTMLElement(node) && node.nodeName === 'TD') { + if (isDOMHTMLElement(node) && ['TD', 'TH'].includes(node.nodeName)) { const children: Descendant[] = [] for (const child of node.childNodes) { const content = serializer.transform(child, { diff --git a/packages/plugins/table/src/components/action.tsx b/packages/plugins/table/src/components/action.tsx index 26937ba8..67a56e03 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' @@ -199,6 +199,23 @@ const SplitActionDefault: React.FC = ({ } newColsWidth[start] = width Transforms.setNodes(editor, { colsWidth: newColsWidth }, { at: path }) + // // 这里需要循环遍历每一列的高度RowStore.getContentHeight(row),并重新更新列高度 RowStore.setContentHeight(row, contentHeight) 和Transforms.setNodes(editor, { height: h }, { at: path.concat(start) }) + // const newGrid = Grid.above(editor, path) + // if (!newGrid) return + // const { children: rows } = newGrid[0] + // let contentHeight = 0 + // for (let i = 0; i < rows.length; i++) { + // const row = rows[i] + // const ch = RowStore.getContentHeight(row) + // const child = Editable.toDOMNode(editor, row).firstElementChild + // if (!child) continue + // const rect = child.getBoundingClientRect() + // contentHeight = Math.max(contentHeight, rect.height + 2, minRowHeight) + // if (ch !== contentHeight) { + // RowStore.setContentHeight(row, contentHeight) + // Transforms.setNodes(editor, { height: contentHeight }, { at: path.concat(i) }) + // } + // } } else if (type === TYPE_ROW) { const row = table.children[start] const { height, children: cells } = row @@ -229,6 +246,123 @@ 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对应下标的数值相差是否在5以内,如果是,则不做任何处理,否则更新当前行对应的高度 + 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] + 16 + }px`; + }); + } + } + dragRef.current = null isDrag.current = false setHover(false) 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) From 9fab6b99d79784eb1926a388d7c723c376ffcda7 Mon Sep 17 00:00:00 2001 From: "TECH\\GW00234847" Date: Fri, 26 Jan 2024 16:32:18 +0800 Subject: [PATCH 02/12] fix: fix copy table cell span issue and task list line-through removed --- .gitignore | 2 +- packages/deserializer/src/html.ts | 73 ++++++++++++++++++- .../list/src/task/plugin/with-task-list.tsx | 10 +-- .../table/src/cell/deserializer/html.ts | 30 +++++++- .../table/src/cell/plugin/with-table-cell.tsx | 4 +- 5 files changed, 106 insertions(+), 13 deletions(-) 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..46edebaa 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 @@ -79,6 +79,77 @@ export const HTMLDeserializer = { for (const { transform, options } of transforms) { HTMLDeserializerEditor.with(transform, options) } + + + // handle table cell merging + // 对node的children进行遍历,寻找里面的children,判断children里是否有table,并对table做处理 + // 如果有table,那么就对table里的cell进行遍历,判断cell的colspan和rowspan是否为1 + // 如果不为1,那么就对cell的colspan和rowspan进行处理,使其都为1 + // 如果为1,那么就不做处理 + // 如果cell的colspan和rowspan都为1,那么就不做处理 + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child.nodeName === 'TABLE') { + for (let rowIndex = 0; rowIndex < child.rows.length; rowIndex++) { + const row = child.rows[rowIndex]; + + for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { + const cell = row.cells[cellIndex]; + if (rowIndex === 0 && cell.nodeName === 'TH') { + cell.style.fontWeight = '700'; + } + const colspan = cell.getAttribute('colspan') ?? 1 + const rowspan = cell.getAttribute('rowspan') ?? 1 + if (colspan > 1) { + for (let i = 1; i < colspan; i++) { + const newCell = document.createElement('TD') + // 设置当前newCell的colspan和rowspan都为1 + newCell.setAttribute('colspan', '1') + newCell.setAttribute('rowspan', '1') + // 设置当前newCell的文本为displaynone + newCell.textContent = 'displaynone||||||' + rowIndex + '||||||' + cellIndex + row.insertBefore(newCell, cell.nextSibling) + } + } + if (rowspan > 1) { + for (let i = 1; i < rowspan; i++) { + // 获取当前行的下i行 + const nextRow = child.rows[rowIndex + i] + // 获取当前行的下i行的第cellIndex个cell + for (let i = 0; i < colspan; i++) { + const newCell = nextRow.insertCell(cellIndex); + // 设置当前newCell的colspan和rowspan都为1 + newCell.setAttribute('colspan', '1') + newCell.setAttribute('rowspan', '1') + // 设置当前newCell的文本为displaynone + newCell.textContent = 'displaynone||||||' + rowIndex + '||||||' + cellIndex + } + } + } + } + } + } + // 解析child的innerHTML,并循环遍历内部的所有strike,并增加style:text-decoration:line-through; + if (child.innerHTML.indexOf(' 0) { + strikes[0].remove(); + } + } + } return HTMLDeserializerEditor.transform(node, options) }, 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..a53dfee2 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,9 +70,9 @@ const TaskElement = ({ checked, onChange }: TaskProps) => { ) } +// 不期望任务列表选中后出现下划线,去掉 &[data-task-checked='true'] {下面的 ${tw`line-through`} const StyledTask = styled(ListStyles)` &[data-task-checked='true'] { - ${tw`line-through`} ${TaskCheckboxInnerStyles} { background-color: ${theme('colors.primary')}; diff --git a/packages/plugins/table/src/cell/deserializer/html.ts b/packages/plugins/table/src/cell/deserializer/html.ts index 8216c8d7..8703535e 100644 --- a/packages/plugins/table/src/cell/deserializer/html.ts +++ b/packages/plugins/table/src/cell/deserializer/html.ts @@ -1,18 +1,17 @@ import { HTMLDeserializerWithTransform } from '@editablejs/deserializer/html' import { Descendant, isDOMHTMLElement } from '@editablejs/models' import { TABLE_CELL_KEY } from '../constants' -import { TableCell } from '../interfaces/table-cell' export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransform = ( next, - serializer, + deserializer, ) => { return (node, options = {}) => { const { text } = options if (isDOMHTMLElement(node) && ['TD', 'TH'].includes(node.nodeName)) { const children: Descendant[] = [] for (const child of node.childNodes) { - const content = serializer.transform(child, { + const content = deserializer.transform(child, { text, matchNewline: true, }) @@ -22,7 +21,30 @@ export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransfo children.push({ children: [{ text: '' }] }) } const { colSpan, rowSpan } = node as HTMLTableCellElement - const cell: TableCell = { + // 遍历children,每个子元素再次放入到children中 + let ifHiddenCell = false; + const spanArray = []; + children.forEach(child => { + if (child.children === undefined) { + if (child.text.indexOf('displaynone||||||') > -1) { + ifHiddenCell = true; + const startRow = child.text.split('||||||')[1]; + const startCol = child.text.split('||||||')[2]; + spanArray.push(startRow - 0); + spanArray.push(startCol - 0); + } + const tempChild = [{...child}]; + //把child的所有属性移除 + Object.keys(child).forEach(key => delete child[key]); + child.children = tempChild; + } + }); + + const cell = ifHiddenCell ? { + type: TABLE_CELL_KEY, + children, + span: spanArray, + } : { type: TABLE_CELL_KEY, children, colspan: colSpan, 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..daaa392d 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 = {}) => { @@ -22,7 +22,7 @@ export const withTableCell = (editor: T, options: TableCellO -1 ? 'none' : '' }} {...rest} > {children} From d730e669c088e530cf17cc9fe425dc2a29e76cea Mon Sep 17 00:00:00 2001 From: "TECH\\GW00234847" Date: Tue, 30 Jan 2024 15:40:00 +0800 Subject: [PATCH 03/12] fix: fix missing last empty cell and wrong colgroup caused issue --- packages/deserializer/src/html.ts | 23 +++++++++++++- .../table/src/table/deserializer/html.ts | 30 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/deserializer/src/html.ts b/packages/deserializer/src/html.ts index 46edebaa..fcb5afeb 100644 --- a/packages/deserializer/src/html.ts +++ b/packages/deserializer/src/html.ts @@ -91,8 +91,18 @@ export const HTMLDeserializer = { for (let i = 0; i < children.length; i++) { const child = children[i]; if (child.nodeName === 'TABLE') { + let colCount = 0; for (let rowIndex = 0; rowIndex < child.rows.length; rowIndex++) { const row = child.rows[rowIndex]; + // 计算第一行的列数,用colspan累加 + if (rowIndex === 0) { + for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { + const cell = row.cells[cellIndex]; + const colspan = cell.getAttribute('colspan') ?? 1 + colCount += colspan - 0; + } + } + for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { const cell = row.cells[cellIndex]; @@ -128,6 +138,17 @@ export const HTMLDeserializer = { } } } + let cellsLength = row.cells.length; + if (cellsLength < colCount) { + for (let i = 0; i < colCount - cellsLength; i++) { + const newCell = row.insertCell(); + // 设置当前newCell的colspan和rowspan都为1 + newCell.setAttribute('colspan', '1') + newCell.setAttribute('rowspan', '1') + // 设置当前newCell的文本为displaynone + newCell.textContent = ''; + } + } } } // 解析child的innerHTML,并循环遍历内部的所有strike,并增加style:text-decoration:line-through; @@ -142,7 +163,7 @@ export const HTMLDeserializer = { span.style.setProperty(property, strike.style.getPropertyValue(property)); } span.style.textDecoration = 'line-through'; - // 将当前元素作为兄弟元素插入到strike后面,并把strike隐藏掉 + // 将当前元素作为兄弟元素插入到strike后面 strike.insertAdjacentElement('afterend', span); } while (strikes.length > 0) { diff --git a/packages/plugins/table/src/table/deserializer/html.ts b/packages/plugins/table/src/table/deserializer/html.ts index 7ebe321a..e7c16814 100644 --- a/packages/plugins/table/src/table/deserializer/html.ts +++ b/packages/plugins/table/src/table/deserializer/html.ts @@ -25,9 +25,37 @@ export const withTableHTMLDeserializerTransform: HTMLDeserializerWithTransform< children.push(...(serializer.transform(child, { text, matchNewline: true }) as TableRow[])) } const { minColWidth = defaultTableMinColWidth } = getOptions(editor) + // start update col Taylor + let container = document.createElement('div'); + container.style.visibility = 'hidden'; + container.style.position = 'absolute'; + + container.appendChild(node); + const colgroup = node.querySelector('colgroup'); + if (colgroup) { + node.removeChild(colgroup); + } + document.body.appendChild(container); + let firstRow = node.rows[0]; + if (firstRow) { + let colgroup = document.createElement('colgroup'); + for (let i = 0; i < firstRow.cells.length; i++) { + let cell = firstRow.cells[i]; + let colspan = cell.colSpan; + for (let j = 0; j < colspan; j++) { + let col = document.createElement('col'); + let width = colspan > 1 ? 90 : cell.offsetWidth; + col.style.width = `${width}px`; + colgroup.appendChild(col); + } + } + node.insertBefore(colgroup, node.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) { From 24df484646636eef33d5fce8ed8bc1fad328568e Mon Sep 17 00:00:00 2001 From: "BDDC-CND1273FNG\\Administrator" Date: Sun, 18 Feb 2024 08:47:25 +0800 Subject: [PATCH 04/12] fix: fix copy table link issue --- .../table/src/cell/deserializer/html.ts | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/plugins/table/src/cell/deserializer/html.ts b/packages/plugins/table/src/cell/deserializer/html.ts index 8703535e..5261c341 100644 --- a/packages/plugins/table/src/cell/deserializer/html.ts +++ b/packages/plugins/table/src/cell/deserializer/html.ts @@ -22,34 +22,45 @@ export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransfo } const { colSpan, rowSpan } = node as HTMLTableCellElement // 遍历children,每个子元素再次放入到children中 - let ifHiddenCell = false; - const spanArray = []; + let ifHiddenCell = false + const spanArray: number[] = [] children.forEach(child => { + // 2024/01/31 10:31:42@需求ID: 产品工作站代码优化@ZhaiCongrui/GW00247400:处理有的带有 链接 的单元格,编辑时回车光标跳到下个单元格的问题 + // child 的 type 为 link时,会有此类问题,所以单独处理 + if (child.type === 'link') { + const copyChild = { ...child } + Reflect.ownKeys(child).forEach(i => delete child[i]) + child.type = 'paragraph' + child.children = [] + child.children[0] = copyChild + } if (child.children === undefined) { if (child.text.indexOf('displaynone||||||') > -1) { - ifHiddenCell = true; - const startRow = child.text.split('||||||')[1]; - const startCol = child.text.split('||||||')[2]; - spanArray.push(startRow - 0); - spanArray.push(startCol - 0); + ifHiddenCell = true + const startRow = child.text.split('||||||')[1] + const startCol = child.text.split('||||||')[2] + spanArray.push(startRow - 0) + spanArray.push(startCol - 0) } - const tempChild = [{...child}]; + const tempChild = [{ ...child }] //把child的所有属性移除 - Object.keys(child).forEach(key => delete child[key]); - child.children = tempChild; + Object.keys(child).forEach(key => delete child[key]) + child.children = tempChild } - }); + }) - const cell = ifHiddenCell ? { - type: TABLE_CELL_KEY, - children, - span: spanArray, - } : { - type: TABLE_CELL_KEY, - children, - colspan: colSpan, - rowspan: rowSpan, - } + const cell = ifHiddenCell + ? { + type: TABLE_CELL_KEY, + children, + span: spanArray, + } + : { + type: TABLE_CELL_KEY, + children, + colspan: colSpan, + rowspan: rowSpan, + } return [cell] } return next(node, options) From f80c51290b4a2982056867a81ad8efeb3f51e82c Mon Sep 17 00:00:00 2001 From: "BDDC-CND1273FNG\\Administrator" Date: Mon, 19 Feb 2024 08:27:27 +0800 Subject: [PATCH 05/12] revert: revert line through from to-do task list --- packages/plugins/list/src/task/plugin/with-task-list.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 a53dfee2..7f178288 100644 --- a/packages/plugins/list/src/task/plugin/with-task-list.tsx +++ b/packages/plugins/list/src/task/plugin/with-task-list.tsx @@ -70,9 +70,11 @@ const TaskElement = ({ checked, onChange }: TaskProps) => { ) } -// 不期望任务列表选中后出现下划线,去掉 &[data-task-checked='true'] {下面的 ${tw`line-through`} +// 如果不期望任务列表选中后出现下划线,去掉 &[data-task-checked='true'] {下面的 ${tw`line-through`}, +// 本次revert,保留原项目设计,后期通过脚本实现调整 const StyledTask = styled(ListStyles)` &[data-task-checked='true'] { + ${tw`line-through`} ${TaskCheckboxInnerStyles} { background-color: ${theme('colors.primary')}; From 425ae8de399e2546176997109b60362e9da9ef08 Mon Sep 17 00:00:00 2001 From: "BDDC-CND1273FNG\\Administrator" Date: Mon, 19 Feb 2024 08:45:00 +0800 Subject: [PATCH 06/12] fix: remove useless code and update annotation --- .../plugins/table/src/components/action.tsx | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/packages/plugins/table/src/components/action.tsx b/packages/plugins/table/src/components/action.tsx index 67a56e03..b4b392d6 100644 --- a/packages/plugins/table/src/components/action.tsx +++ b/packages/plugins/table/src/components/action.tsx @@ -199,23 +199,6 @@ const SplitActionDefault: React.FC = ({ } newColsWidth[start] = width Transforms.setNodes(editor, { colsWidth: newColsWidth }, { at: path }) - // // 这里需要循环遍历每一列的高度RowStore.getContentHeight(row),并重新更新列高度 RowStore.setContentHeight(row, contentHeight) 和Transforms.setNodes(editor, { height: h }, { at: path.concat(start) }) - // const newGrid = Grid.above(editor, path) - // if (!newGrid) return - // const { children: rows } = newGrid[0] - // let contentHeight = 0 - // for (let i = 0; i < rows.length; i++) { - // const row = rows[i] - // const ch = RowStore.getContentHeight(row) - // const child = Editable.toDOMNode(editor, row).firstElementChild - // if (!child) continue - // const rect = child.getBoundingClientRect() - // contentHeight = Math.max(contentHeight, rect.height + 2, minRowHeight) - // if (ch !== contentHeight) { - // RowStore.setContentHeight(row, contentHeight) - // Transforms.setNodes(editor, { height: contentHeight }, { at: path.concat(i) }) - // } - // } } else if (type === TYPE_ROW) { const row = table.children[start] const { height, children: cells } = row @@ -316,7 +299,7 @@ const SplitActionDefault: React.FC = ({ borderHeightArray.push(height); } }); - // 2024/01/19 10:42:47 @guoxiaona/GW00234847:检测heightArray和borderHeightArray对应下标的数值相差是否在5以内,如果是,则不做任何处理,否则更新当前行对应的高度 + // 2024/01/19 10:42:47 @guoxiaona/GW00234847:检测heightArray和borderHeightArray对应下标的数值相差是否在10(行高大于10)以内,如果是,则不做任何处理,否则更新当前行对应的高度 let ifRowHeightUpdated = false; heightArray.forEach((item, index) => { const borderHeight = borderHeightArray[index]; @@ -357,7 +340,7 @@ const SplitActionDefault: React.FC = ({ const nextNextSibling = item.nextElementSibling.nextElementSibling; const nextNextSiblingStyle = nextNextSibling.style; nextNextSiblingStyle.height = `${ - heightArrayMapAllPrev[heightArrayMapAllPrev.length - 1] + 16 + heightArrayMapAllPrev[heightArrayMapAllPrev.length - 1] + 9 }px`; }); } From 0236599a7ad4d2a44c03e01c50e28919ab044bff Mon Sep 17 00:00:00 2001 From: "BDDC-CND1273FNG\\Administrator" Date: Mon, 19 Feb 2024 14:39:15 +0800 Subject: [PATCH 07/12] fix: update annotation for deserializer of html --- packages/deserializer/src/html.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/deserializer/src/html.ts b/packages/deserializer/src/html.ts index fcb5afeb..7acfdd2b 100644 --- a/packages/deserializer/src/html.ts +++ b/packages/deserializer/src/html.ts @@ -145,7 +145,7 @@ export const HTMLDeserializer = { // 设置当前newCell的colspan和rowspan都为1 newCell.setAttribute('colspan', '1') newCell.setAttribute('rowspan', '1') - // 设置当前newCell的文本为displaynone + // 设置当前newCell的文本为空 newCell.textContent = ''; } } From c90c3c62bb7b206d09638caada1319d5348973d1 Mon Sep 17 00:00:00 2001 From: "BDDC-CND1273FNG\\Administrator" Date: Mon, 19 Feb 2024 15:40:41 +0800 Subject: [PATCH 08/12] fix: set colgroup default width to offsetWidth --- packages/plugins/table/src/table/deserializer/html.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/table/src/table/deserializer/html.ts b/packages/plugins/table/src/table/deserializer/html.ts index e7c16814..dd9ef979 100644 --- a/packages/plugins/table/src/table/deserializer/html.ts +++ b/packages/plugins/table/src/table/deserializer/html.ts @@ -44,7 +44,7 @@ export const withTableHTMLDeserializerTransform: HTMLDeserializerWithTransform< let colspan = cell.colSpan; for (let j = 0; j < colspan; j++) { let col = document.createElement('col'); - let width = colspan > 1 ? 90 : cell.offsetWidth; + let width = cell.offsetWidth; col.style.width = `${width}px`; colgroup.appendChild(col); } From 9a260a3ba1f98546e9171d6755117c1c5699afac Mon Sep 17 00:00:00 2001 From: "BDDC-CND1273FNG\\Administrator" Date: Tue, 20 Feb 2024 16:33:05 +0800 Subject: [PATCH 09/12] feat: add changes after lint and changeset --- .changeset/five-melons-travel.md | 6 + .../plugins/table/src/components/action.tsx | 146 +++++++++--------- 2 files changed, 77 insertions(+), 75 deletions(-) create mode 100644 .changeset/five-melons-travel.md 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/packages/plugins/table/src/components/action.tsx b/packages/plugins/table/src/components/action.tsx index b4b392d6..bb0e4bba 100644 --- a/packages/plugins/table/src/components/action.tsx +++ b/packages/plugins/table/src/components/action.tsx @@ -229,120 +229,116 @@ 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 (!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[] = []; + 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); + 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; + 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; + 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); + }) + 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; + let sum = 0 for (let i = 0; i < index; i++) { - sum += heightArray[i]; + sum += heightArray[i] } - return sum; - }); + return sum + }) // 2024/01/19 10:41:27 @guoxiaona/GW00234847:heightArray中当前数和之前所有数值的和 const heightArrayMapAllPrev = heightArray.map((item, index) => { - let sum = 0; + let sum = 0 for (let i = 0; i <= index; i++) { - sum += heightArray[i]; + sum += heightArray[i] } - return sum; - }); + return sum + }) - const cld = Editable.toDOMNode(editor, rows[0]).firstElementChild; + 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]; + 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[] = []; + const borderHeightArray: number[] = [] + const tableLeftBorderChildrenArray = Array.from(tableLeftBorder?.children) + const tableLeftBorderPerRowArray: any[] = [] tableLeftBorderChildrenArray.forEach((item: any) => { if (item.dataset.tableRow) { - tableLeftBorderPerRowArray.push(item); + 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); + 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; + let ifRowHeightUpdated = false heightArray.forEach((item, index) => { - const borderHeight = borderHeightArray[index]; - const itemNumber = Number(item); - const diff = Math.abs(borderHeight - itemNumber); + 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; + 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`; + 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`; + 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[] = []; + const tableTopBorderChildrenArray = Array.from(tableTopBorder?.children) + const tableTopBorderPerColArray: any[] = [] tableTopBorderChildrenArray.forEach((item: any) => { if (item.dataset.tableCol) { - tableTopBorderPerColArray.push(item); + 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; + tableTopBorderPerColArray.forEach(item => { + const nextNextSibling = item.nextElementSibling.nextElementSibling + const nextNextSiblingStyle = nextNextSibling.style nextNextSiblingStyle.height = `${ heightArrayMapAllPrev[heightArrayMapAllPrev.length - 1] + 9 - }px`; - }); + }px` + }) } } @@ -352,7 +348,7 @@ const SplitActionDefault: React.FC = ({ 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() From be5d856786f7d5860ab3abbc32a4805a9fd4cbf4 Mon Sep 17 00:00:00 2001 From: "TECH\\GW00234847" Date: Fri, 1 Mar 2024 13:53:30 +0800 Subject: [PATCH 10/12] feat: remove strike from root --- packages/deserializer/src/html.ts | 49 +++++++++---------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/packages/deserializer/src/html.ts b/packages/deserializer/src/html.ts index 7acfdd2b..ad250dfd 100644 --- a/packages/deserializer/src/html.ts +++ b/packages/deserializer/src/html.ts @@ -79,35 +79,33 @@ export const HTMLDeserializer = { for (const { transform, options } of transforms) { HTMLDeserializerEditor.with(transform, options) } - - + // handle table cell merging // 对node的children进行遍历,寻找里面的children,判断children里是否有table,并对table做处理 // 如果有table,那么就对table里的cell进行遍历,判断cell的colspan和rowspan是否为1 // 如果不为1,那么就对cell的colspan和rowspan进行处理,使其都为1 // 如果为1,那么就不做处理 // 如果cell的colspan和rowspan都为1,那么就不做处理 - const children = node.children; + const children = node.children for (let i = 0; i < children.length; i++) { - const child = children[i]; + const child = children[i] if (child.nodeName === 'TABLE') { - let colCount = 0; + let colCount = 0 for (let rowIndex = 0; rowIndex < child.rows.length; rowIndex++) { - const row = child.rows[rowIndex]; + const row = child.rows[rowIndex] // 计算第一行的列数,用colspan累加 if (rowIndex === 0) { for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { - const cell = row.cells[cellIndex]; + const cell = row.cells[cellIndex] const colspan = cell.getAttribute('colspan') ?? 1 - colCount += colspan - 0; + colCount += colspan - 0 } } - - + for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { - const cell = row.cells[cellIndex]; + const cell = row.cells[cellIndex] if (rowIndex === 0 && cell.nodeName === 'TH') { - cell.style.fontWeight = '700'; + cell.style.fontWeight = '700' } const colspan = cell.getAttribute('colspan') ?? 1 const rowspan = cell.getAttribute('rowspan') ?? 1 @@ -128,7 +126,7 @@ export const HTMLDeserializer = { const nextRow = child.rows[rowIndex + i] // 获取当前行的下i行的第cellIndex个cell for (let i = 0; i < colspan; i++) { - const newCell = nextRow.insertCell(cellIndex); + const newCell = nextRow.insertCell(cellIndex) // 设置当前newCell的colspan和rowspan都为1 newCell.setAttribute('colspan', '1') newCell.setAttribute('rowspan', '1') @@ -138,38 +136,19 @@ export const HTMLDeserializer = { } } } - let cellsLength = row.cells.length; + let cellsLength = row.cells.length if (cellsLength < colCount) { for (let i = 0; i < colCount - cellsLength; i++) { - const newCell = row.insertCell(); + const newCell = row.insertCell() // 设置当前newCell的colspan和rowspan都为1 newCell.setAttribute('colspan', '1') newCell.setAttribute('rowspan', '1') // 设置当前newCell的文本为空 - newCell.textContent = ''; + newCell.textContent = '' } } } } - // 解析child的innerHTML,并循环遍历内部的所有strike,并增加style:text-decoration:line-through; - if (child.innerHTML.indexOf(' 0) { - strikes[0].remove(); - } - } } return HTMLDeserializerEditor.transform(node, options) From 294351fe2a812074694bd0725b976544a213a0a2 Mon Sep 17 00:00:00 2001 From: "TECH\\GW00234847" Date: Fri, 1 Mar 2024 16:46:04 +0800 Subject: [PATCH 11/12] fix: add strike decoration --- packages/plugins/mark/src/deserializer/html.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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') { From b68d2ad93a2573d8084d6a9b7c9b2ec427ad7594 Mon Sep 17 00:00:00 2001 From: YiYang Date: Fri, 1 Mar 2024 16:59:26 +0800 Subject: [PATCH 12/12] refactor: fix table --- packages/deserializer/src/html.ts | 71 ---------- .../table/src/cell/deserializer/html.ts | 78 ++++++----- .../table/src/cell/plugin/with-table-cell.tsx | 2 +- .../table/src/table/deserializer/html.ts | 75 ++++++----- .../table/src/table/deserializer/utils.ts | 125 ++++++++++++++++++ 5 files changed, 208 insertions(+), 143 deletions(-) create mode 100644 packages/plugins/table/src/table/deserializer/utils.ts diff --git a/packages/deserializer/src/html.ts b/packages/deserializer/src/html.ts index ad250dfd..946769eb 100644 --- a/packages/deserializer/src/html.ts +++ b/packages/deserializer/src/html.ts @@ -80,77 +80,6 @@ export const HTMLDeserializer = { HTMLDeserializerEditor.with(transform, options) } - // handle table cell merging - // 对node的children进行遍历,寻找里面的children,判断children里是否有table,并对table做处理 - // 如果有table,那么就对table里的cell进行遍历,判断cell的colspan和rowspan是否为1 - // 如果不为1,那么就对cell的colspan和rowspan进行处理,使其都为1 - // 如果为1,那么就不做处理 - // 如果cell的colspan和rowspan都为1,那么就不做处理 - const children = node.children - for (let i = 0; i < children.length; i++) { - const child = children[i] - if (child.nodeName === 'TABLE') { - let colCount = 0 - for (let rowIndex = 0; rowIndex < child.rows.length; rowIndex++) { - const row = child.rows[rowIndex] - // 计算第一行的列数,用colspan累加 - if (rowIndex === 0) { - for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { - const cell = row.cells[cellIndex] - const colspan = cell.getAttribute('colspan') ?? 1 - colCount += colspan - 0 - } - } - - for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { - const cell = row.cells[cellIndex] - if (rowIndex === 0 && cell.nodeName === 'TH') { - cell.style.fontWeight = '700' - } - const colspan = cell.getAttribute('colspan') ?? 1 - const rowspan = cell.getAttribute('rowspan') ?? 1 - if (colspan > 1) { - for (let i = 1; i < colspan; i++) { - const newCell = document.createElement('TD') - // 设置当前newCell的colspan和rowspan都为1 - newCell.setAttribute('colspan', '1') - newCell.setAttribute('rowspan', '1') - // 设置当前newCell的文本为displaynone - newCell.textContent = 'displaynone||||||' + rowIndex + '||||||' + cellIndex - row.insertBefore(newCell, cell.nextSibling) - } - } - if (rowspan > 1) { - for (let i = 1; i < rowspan; i++) { - // 获取当前行的下i行 - const nextRow = child.rows[rowIndex + i] - // 获取当前行的下i行的第cellIndex个cell - for (let i = 0; i < colspan; i++) { - const newCell = nextRow.insertCell(cellIndex) - // 设置当前newCell的colspan和rowspan都为1 - newCell.setAttribute('colspan', '1') - newCell.setAttribute('rowspan', '1') - // 设置当前newCell的文本为displaynone - newCell.textContent = 'displaynone||||||' + rowIndex + '||||||' + cellIndex - } - } - } - } - let cellsLength = row.cells.length - if (cellsLength < colCount) { - for (let i = 0; i < colCount - cellsLength; i++) { - const newCell = row.insertCell() - // 设置当前newCell的colspan和rowspan都为1 - newCell.setAttribute('colspan', '1') - newCell.setAttribute('rowspan', '1') - // 设置当前newCell的文本为空 - newCell.textContent = '' - } - } - } - } - } - return HTMLDeserializerEditor.transform(node, options) }, } diff --git a/packages/plugins/table/src/cell/deserializer/html.ts b/packages/plugins/table/src/cell/deserializer/html.ts index 5261c341..b9bd3e15 100644 --- a/packages/plugins/table/src/cell/deserializer/html.ts +++ b/packages/plugins/table/src/cell/deserializer/html.ts @@ -1,18 +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' -export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransform = ( - next, - deserializer, -) => { +export interface TableCellHTMLDeserializerOptions extends HTMLDeserializerOptions { + editor: Editor +} +export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransform< + TableCellHTMLDeserializerOptions +> = (next, deserializer, { editor }) => { return (node, options = {}) => { const { text } = options 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 = deserializer.transform(child, { - text, + text: { + ...text, + bold: isFontWeight ? true : undefined, + }, matchNewline: true, }) children.push(...content) @@ -21,43 +33,43 @@ export const withTableCellHTMLDeserializerTransform: HTMLDeserializerWithTransfo children.push({ children: [{ text: '' }] }) } const { colSpan, rowSpan } = node as HTMLTableCellElement - // 遍历children,每个子元素再次放入到children中 - let ifHiddenCell = false - const spanArray: number[] = [] + + 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 (child.type === 'link') { - const copyChild = { ...child } - Reflect.ownKeys(child).forEach(i => delete child[i]) - child.type = 'paragraph' - child.children = [] - child.children[0] = copyChild - } - if (child.children === undefined) { - if (child.text.indexOf('displaynone||||||') > -1) { - ifHiddenCell = true - const startRow = child.text.split('||||||')[1] - const startCol = child.text.split('||||||')[2] - spanArray.push(startRow - 0) - spanArray.push(startCol - 0) + if (!Editor.isBlock(editor, child)) { + notBlocks.push(child) + } else { + if (notBlocks.length > 0) { + appendNotBlocks() } - const tempChild = [{ ...child }] - //把child的所有属性移除 - Object.keys(child).forEach(key => delete child[key]) - child.children = tempChild + newChildren.push(child) } }) - - const cell = ifHiddenCell + if (notBlocks.length > 0) { + appendNotBlocks() + } + const span = node.getAttribute('span')?.split(',').map(Number) + const cell = span ? { type: TABLE_CELL_KEY, - children, - span: spanArray, + children: newChildren, + span, } : { type: TABLE_CELL_KEY, - children, + children: newChildren, colspan: colSpan, rowspan: rowSpan, } 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 daaa392d..f33c5bfd 100644 --- a/packages/plugins/table/src/cell/plugin/with-table-cell.tsx +++ b/packages/plugins/table/src/cell/plugin/with-table-cell.tsx @@ -22,7 +22,7 @@ export const withTableCell = (editor: T, options: TableCellO -1 ? 'none' : '' }} + style={{ ...style, display: element.span ? 'none' : '' }} {...rest} > {children} diff --git a/packages/plugins/table/src/table/deserializer/html.ts b/packages/plugins/table/src/table/deserializer/html.ts index dd9ef979..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,57 +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 - let container = document.createElement('div'); - container.style.visibility = 'hidden'; - container.style.position = 'absolute'; + const container = document.createElement('div') + container.style.visibility = 'hidden' + container.style.position = 'absolute' - container.appendChild(node); - const colgroup = node.querySelector('colgroup'); - if (colgroup) { - node.removeChild(colgroup); + container.appendChild(tableElement) + + document.body.appendChild(container) + if (tableElement.rows.length === 0) { + return [] } - document.body.appendChild(container); - let firstRow = node.rows[0]; - if (firstRow) { - let colgroup = document.createElement('colgroup'); - for (let i = 0; i < firstRow.cells.length; i++) { - let cell = firstRow.cells[i]; - let colspan = cell.colSpan; - for (let j = 0; j < colspan; j++) { - let col = document.createElement('col'); - let width = cell.offsetWidth; - col.style.width = `${width}px`; - colgroup.appendChild(col); - } + 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) } - node.insertBefore(colgroup, node.firstChild); } - document.body.removeChild(container); + 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.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) + }) + } + } + } +}