Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix copy table related issues #169

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/five-melons-travel.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
node_modules
.pnp
.pnp.js

.history
# testing
coverage

Expand Down
2 changes: 1 addition & 1 deletion packages/deserializer/src/html.ts
Original file line number Diff line number Diff line change
@@ -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<Element, 'children'>
Expand Down
10 changes: 6 additions & 4 deletions packages/plugins/list/src/task/plugin/with-task-list.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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`}
Expand Down
6 changes: 5 additions & 1 deletion packages/plugins/mark/src/deserializer/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
75 changes: 60 additions & 15 deletions packages/plugins/table/src/cell/deserializer/html.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/table/src/cell/plugin/with-table-cell.tsx
Original file line number Diff line number Diff line change
@@ -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 = <T extends Editable>(editor: T, options: TableCellOptions = {}) => {
Expand Down
139 changes: 126 additions & 13 deletions packages/plugins/table/src/components/action.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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'
Expand Down Expand Up @@ -229,13 +229,126 @@ const SplitActionDefault: React.FC<TableActionProps> = ({
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()
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/table/src/row/deserializer/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Loading