From 8c58090a41f1a2946b70eae140e7d3b62e53c3c9 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Mon, 28 Nov 2022 16:41:23 +0800 Subject: [PATCH] close #211 --- .../src/tiptap/core/extensions/table-cell.tsx | 2 +- .../tiptap/core/extensions/table-header.ts | 2 +- .../src/tiptap/core/extensions/table.ts | 171 ++++++++++++------ .../src/tiptap/core/menus/table/bubble.tsx | 2 +- 4 files changed, 119 insertions(+), 58 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/table-cell.tsx b/packages/client/src/tiptap/core/extensions/table-cell.tsx index aa4b09b8..984285f9 100644 --- a/packages/client/src/tiptap/core/extensions/table-cell.tsx +++ b/packages/client/src/tiptap/core/extensions/table-cell.tsx @@ -46,7 +46,7 @@ export const TableCell = Node.create({ }, }, colwidth: { - default: null, + default: [150], parseHTML: (element) => { const colwidth = element.getAttribute('colwidth'); const value = colwidth ? [parseInt(colwidth, 10)] : null; diff --git a/packages/client/src/tiptap/core/extensions/table-header.ts b/packages/client/src/tiptap/core/extensions/table-header.ts index 02e9cf99..18301029 100644 --- a/packages/client/src/tiptap/core/extensions/table-header.ts +++ b/packages/client/src/tiptap/core/extensions/table-header.ts @@ -13,7 +13,7 @@ export const TableHeader = BuiltInTableHeader.extend({ default: 1, }, colwidth: { - default: null, + default: [150], parseHTML: (element) => { const colwidth = element.getAttribute('colwidth'); const value = colwidth ? colwidth.split(',').map((item) => parseInt(item, 10)) : null; diff --git a/packages/client/src/tiptap/core/extensions/table.ts b/packages/client/src/tiptap/core/extensions/table.ts index 9ffe7b25..a785a203 100644 --- a/packages/client/src/tiptap/core/extensions/table.ts +++ b/packages/client/src/tiptap/core/extensions/table.ts @@ -1,60 +1,121 @@ import BuiltInTable from '@tiptap/extension-table'; -import { Plugin } from 'prosemirror-state'; -import { tableEditing } from 'prosemirror-tables'; -import { Decoration, DecorationSet } from 'prosemirror-view'; +import { Node as ProseMirrorNode } from 'prosemirror-model'; +import { NodeView } from 'prosemirror-view'; + +function updateColumns( + node: ProseMirrorNode, + colgroup: Element, + table: HTMLElement, + cellMinWidth: number, + overrideCol?: number, + overrideValue?: any +) { + let totalWidth = 0; + let fixedWidth = true; + let nextDOM = colgroup.firstChild as HTMLElement; + const row = node.firstChild; + + for (let i = 0, col = 0; i < row.childCount; i += 1) { + const { colspan, colwidth } = row.child(i).attrs; + + for (let j = 0; j < colspan; j += 1, col += 1) { + const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j]; + const cssWidth = hasWidth ? `${hasWidth}px` : ''; + + totalWidth += hasWidth || cellMinWidth; + + if (!hasWidth) { + fixedWidth = false; + } + + if (!nextDOM) { + colgroup.appendChild(document.createElement('col')).style.width = cssWidth; + } else { + if (nextDOM.style.width !== cssWidth) { + nextDOM.style.width = cssWidth; + } + + nextDOM = nextDOM.nextSibling as HTMLElement; + } + } + } + + while (nextDOM) { + const after = nextDOM.nextSibling as HTMLElement; + + nextDOM.parentNode.removeChild(nextDOM); + nextDOM = after; + } + + if (fixedWidth) { + table.style.width = `${totalWidth}px`; + table.style.minWidth = ''; + } else { + table.style.width = ''; + table.style.minWidth = `${totalWidth}px`; + } +} + +class TableView implements NodeView { + node: ProseMirrorNode; + + cellMinWidth: number; + + dom: HTMLElement; + + scrollDom: HTMLElement; + + table: HTMLElement; + + colgroup: HTMLElement; + + contentDOM: HTMLElement; + + constructor(node: ProseMirrorNode, cellMinWidth: number) { + this.node = node; + this.cellMinWidth = cellMinWidth; + this.dom = document.createElement('div'); + this.dom.className = 'tableWrapper'; + + this.scrollDom = document.createElement('div'); + this.scrollDom.className = 'scrollWrapper'; + this.dom.appendChild(this.scrollDom); + + this.table = this.scrollDom.appendChild(document.createElement('table')); + this.colgroup = this.table.appendChild(document.createElement('colgroup')); + updateColumns(node, this.colgroup, this.table, cellMinWidth); + this.contentDOM = this.table.appendChild(document.createElement('tbody')); + } + + update(node: ProseMirrorNode) { + if (node.type !== this.node.type) { + return false; + } + + this.node = node; + updateColumns(node, this.colgroup, this.table, this.cellMinWidth); + + return true; + } + + ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) { + return ( + mutation.type === 'attributes' && (mutation.target === this.table || this.colgroup.contains(mutation.target)) + ); + } +} export const Table = BuiltInTable.extend({ - renderHTML() { - return [ - 'div', - { class: 'node-table' }, - ['div', { class: `scrollable` }, ['table', { class: `think-table render-wrapper` }, ['tbody', 0]]], - ]; - }, - - addProseMirrorPlugins() { - const { isEditable } = this.editor; - - return [ - tableEditing(), - new Plugin({ - props: { - decorations: (state) => { - const { doc } = state; - const decorations: Decoration[] = []; - let index = 0; - - doc.descendants((node, pos) => { - if (node.type.name !== this.name) return; - - const elements = document.getElementsByClassName('think-table'); - const table = elements[index]; - - if (!table) return; - - if (!isEditable) { - table.classList.add('is-readonly'); - } - - const element = table.parentElement; - const shadowRight = !!(element && element.scrollWidth > element.clientWidth); - - if (shadowRight) { - decorations.push( - Decoration.widget(pos + 1, () => { - const shadow = document.createElement('div'); - shadow.className = `scrollable-shadow right ${isEditable ? 'is-editable' : ''}`; - return shadow; - }) - ); - } - index++; - }); - - return DecorationSet.create(doc, decorations); - }, - }, - }), - ]; + // @ts-ignore + addOptions() { + return { + HTMLAttributes: {}, + resizable: true, + handleWidth: 5, + cellMinWidth: 25, + View: TableView, + lastColumnResizable: true, + allowTableNodeSelection: false, + }; }, }).configure({ resizable: true }); diff --git a/packages/client/src/tiptap/core/menus/table/bubble.tsx b/packages/client/src/tiptap/core/menus/table/bubble.tsx index be1a0bfc..09ec7504 100644 --- a/packages/client/src/tiptap/core/menus/table/bubble.tsx +++ b/packages/client/src/tiptap/core/menus/table/bubble.tsx @@ -34,7 +34,7 @@ export const TableBubbleMenu = ({ editor }) => { while (container && container.tagName !== 'TABLE') { container = container.parentElement; } - return container.parentElement; + return container; }, []); const copyMe = useCallback(() => copyNode(Table.name, editor), [editor]); const deleteMe = useCallback(() => {