fix table editing

This commit is contained in:
Alex Turchyn authored and Pete Matsyburka committed 2026-10-03 14:05:38 +03:00
1 parent 8f47ec6d4e
commit 4f5e72f983
4 files changed
+67 -25

No files matched your search

+1 -1
View File
@@ -2529,7 +2529,6 @@ export default {
if (nextZoom === this.zoomLevel) return if (nextZoom === this.zoomLevel) return
const container = this.$refs.pagesContainer const container = this.$refs.pagesContainer
const scrollContainer = container.scrollHeight > container.clientHeight ? container : document.scrollingElement
const rect = this.$refs.documents.getBoundingClientRect() const rect = this.$refs.documents.getBoundingClientRect()
this.zoomLevel = nextZoom this.zoomLevel = nextZoom
@@ -2539,6 +2538,7 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
const nextRect = this.$refs.documents.getBoundingClientRect() const nextRect = this.$refs.documents.getBoundingClientRect()
const scrollContainer = container.scrollHeight > container.clientHeight ? container : document.scrollingElement
container.scrollLeft += nextRect.left - clientX + (clientX - rect.left) * nextRect.width / rect.width container.scrollLeft += nextRect.left - clientX + (clientX - rect.left) * nextRect.width / rect.width
scrollContainer.scrollTop += nextRect.top - clientY + (clientY - rect.top) * nextRect.height / rect.height scrollContainer.scrollTop += nextRect.top - clientY + (clientY - rect.top) * nextRect.height / rect.height
@@ -188,15 +188,21 @@ export default {
} }
} }
}, },
watch: {
bodyDom: {
immediate: true,
handler () {
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
this.ensureListsStyle(this.bodyDom)
}
}
},
mounted () { mounted () {
this.isMounted = true this.isMounted = true
this.shadow.adoptedStyleSheets.push(dynamicStylesheet, tiptapStylesheet) this.shadow.adoptedStyleSheets.push(dynamicStylesheet, tiptapStylesheet)
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
this.ensureListsStyle(this.bodyDom)
this.updateContainerWidth() this.updateContainerWidth()
this.resizeObserver = new ResizeObserver(() => { this.resizeObserver = new ResizeObserver(() => {
@@ -1,6 +1,6 @@
import { Editor, Extension, Node, Mark, ResizableNodeView } from '@tiptap/core' import { Editor, Extension, Node, Mark, ResizableNodeView } from '@tiptap/core'
import { Plugin, PluginKey, NodeSelection } from '@tiptap/pm/state' import { Plugin, PluginKey, NodeSelection } from '@tiptap/pm/state'
import { TableMap, tableEditing, findTable, selectedRect, addRowAfter, addRowBefore, addColumnAfter, addColumnBefore, deleteRow, deleteColumn, mergeCells, splitCell } from '@tiptap/pm/tables' import { TableMap, CellSelection, tableEditing, findTable, selectedRect, addRowAfter, addRowBefore, addColumnAfter, addColumnBefore, deleteRow, deleteColumn, mergeCells, splitCell } from '@tiptap/pm/tables'
import { Decoration, DecorationSet } from '@tiptap/pm/view' import { Decoration, DecorationSet } from '@tiptap/pm/view'
import Document from '@tiptap/extension-document' import Document from '@tiptap/extension-document'
import Text from '@tiptap/extension-text' import Text from '@tiptap/extension-text'
@@ -1135,11 +1135,49 @@ const TableCommands = Extension.create({
}, },
addProseMirrorPlugins () { addProseMirrorPlugins () {
const plugin = tableEditing() const plugin = tableEditing()
const { handlePaste } = plugin.props
delete plugin.spec.appendTransaction delete plugin.spec.appendTransaction
plugin.props.handlePaste = (view, event, slice) => {
const table = findTable(view.state.selection.$head)
if (table && !isRegularTable(table.node)) return view.state.selection instanceof CellSelection
const existingCells = table ? tableCellNodes(table.node) : new Set()
const dispatch = (tr) => {
styleNewCells(tr, existingCells)
view.dispatch(tr)
}
return handlePaste({ state: view.state, dispatch }, event, slice)
}
return [plugin] return [plugin]
}, },
addKeyboardShortcuts () {
const clearCells = () => {
const { state, view } = this.editor
if (!(state.selection instanceof CellSelection)) return false
const tr = state.tr
state.selection.forEachCell((cell, pos) => {
const paragraph = cell.firstChild?.type.name === 'paragraph' ? cell.firstChild : null
tr.replaceWith(tr.mapping.map(pos + 1), tr.mapping.map(pos + cell.nodeSize - 1), state.schema.nodes.paragraph.create(paragraph?.attrs))
})
view.dispatch(tr)
return true
}
return { Backspace: clearCells, Delete: clearCells, 'Mod-Backspace': clearCells, 'Mod-Delete': clearCells }
},
addCommands () { addCommands () {
return { return {
addTableRow: (after) => (props) => runTableCommand(after ? addRowAfter : addRowBefore, props), addTableRow: (after) => (props) => runTableCommand(after ? addRowAfter : addRowBefore, props),
@@ -1554,31 +1592,29 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
const RESIZE_HANDLE_PX = 4 const RESIZE_HANDLE_PX = 4
const MIN_SIZE_PX = 16 const MIN_SIZE_PX = 16
function findResizeTarget (event) { function findResizeTarget (view, event) {
const cellDom = event.target.closest?.('td, th') const cellDom = event.target.closest?.('td, th')
if (!cellDom) return null if (!cellDom) return null
const rect = cellDom.getBoundingClientRect() const rect = cellDom.getBoundingClientRect()
const isRow = Math.abs(event.clientY - rect.bottom) <= RESIZE_HANDLE_PX
const isRight = Math.abs(event.clientX - rect.right) <= RESIZE_HANDLE_PX
const isLeft = Math.abs(event.clientX - rect.left) <= RESIZE_HANDLE_PX
if (!isRow && !isRight && !isLeft) return null
const tableDom = cellDom.closest('table') const tableDom = cellDom.closest('table')
const tablePos = tablePosFromDom(view, tableDom)
const cell = TableMap.get(view.state.doc.nodeAt(tablePos)).findCell(view.posAtDOM(cellDom, 0) - tablePos - 2)
if (Math.abs(event.clientY - rect.bottom) <= RESIZE_HANDLE_PX) { if (isRow) {
return { type: 'row', rowDom: cellDom.parentElement, tableDom } return { type: 'row', rowDom: tableDom.rows[cell.bottom - 1], tableDom }
} }
let boundaryCell = null const columnIndex = isRight ? cell.right - 1 : cell.left - 1
if (Math.abs(event.clientX - rect.right) <= RESIZE_HANDLE_PX) { if (columnIndex < 0 || !tableColumnsRow(tableDom)) return null
boundaryCell = cellDom
} else if (Math.abs(event.clientX - rect.left) <= RESIZE_HANDLE_PX) {
boundaryCell = cellDom.previousElementSibling
}
if (!boundaryCell || !tableColumnsRow(tableDom)) return null
let columnIndex = -1
for (let cell = boundaryCell; cell; cell = cell.previousElementSibling) columnIndex += cell.colSpan
return { type: 'column', columnIndex, tableDom } return { type: 'column', columnIndex, tableDom }
} }
@@ -1659,7 +1695,7 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
props: { props: {
handleDOMEvents: { handleDOMEvents: {
mousemove (view, event) { mousemove (view, event) {
const target = view.editable && findResizeTarget(event) const target = view.editable && findResizeTarget(view, event)
view.dom.style.cursor = target ? (target.type === 'column' ? 'col-resize' : 'row-resize') : '' view.dom.style.cursor = target ? (target.type === 'column' ? 'col-resize' : 'row-resize') : ''
@@ -1671,7 +1707,7 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
return false return false
}, },
mousedown (view, event) { mousedown (view, event) {
const target = view.editable && event.button === 0 && findResizeTarget(event) const target = view.editable && event.button === 0 && findResizeTarget(view, event)
if (!target) return false if (!target) return false
@@ -561,12 +561,12 @@ export default {
const cell = '<td style="border: 1px solid #000000; padding: 4pt; vertical-align: top"><p style="margin: 0"></p></td>' const cell = '<td style="border: 1px solid #000000; padding: 4pt; vertical-align: top"><p style="margin: 0"></p></td>'
const html = `<table style="width: 100%; border-collapse: collapse"><tbody>${`<tr>${cell.repeat(cols)}</tr>`.repeat(rows)}</tbody></table>` const html = `<table style="width: 100%; border-collapse: collapse"><tbody>${`<tr>${cell.repeat(cols)}</tr>`.repeat(rows)}</tbody></table>`
const isEmpty = $pos.parent.isTextblock && !$pos.parent.content.size const isEmpty = $pos.parent.isTextblock && !$pos.parent.content.size
const from = isEmpty ? $pos.before() : $pos.after() const from = !$pos.parent.isTextblock ? $pos.pos : isEmpty ? $pos.before() : $pos.after()
const to = isEmpty ? $pos.after() : from const to = isEmpty ? $pos.after() : from
this.editor.chain().focus() this.editor.chain().focus()
.insertContentAt({ from, to }, html) .insertContentAt({ from, to }, html)
.setTextSelection(from + 5) .setTextSelection(from + 4)
.run() .run()
}, },
onImageSelected (event) { onImageSelected (event) {