From ea34e23422d3874c570646a2414b2c3ee8ab1520 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Mon, 21 Mar 2022 00:39:55 +0800 Subject: [PATCH] feat: improve tiptap --- .../tiptap/components/table/index.tsx | 18 -- .../src/components/tiptap/extensions/table.ts | 11 +- .../components/tiptap/extensions/tableCell.ts | 120 ------------- .../tiptap/extensions/tableCell.tsx | 162 ++++++++++++++++++ .../tiptap/extensions/tableHeader.ts | 94 ---------- .../tiptap/extensions/tableHeader.tsx | 116 +++++++++++++ .../{trailingNode.tsx => trailingNode.ts} | 0 .../client/src/components/tiptap/menubar.tsx | 10 +- .../src/components/tiptap/menus/banner.tsx | 2 +- ...ase-bubble-menu.tsx => baseBubbleMenu.tsx} | 2 +- .../menus/{base-insert.tsx => baseInsert.tsx} | 0 .../menus/{base-menu.tsx => baseMenu.tsx} | 0 .../bubbleMenuPlugin.tsx} | 0 .../{bubble-menu => bubbleMenu}/index.tsx | 2 +- .../{font-size.tsx => fontSize.tsx} | 0 .../src/components/tiptap/menus/image.tsx | 2 +- .../src/components/tiptap/menus/link.tsx | 2 +- .../{media-insert.tsx => mediaInsert.tsx} | 0 .../src/components/tiptap/menus/table.tsx | 4 +- .../src/components/tiptap/services/dom.ts | 20 +++ .../tiptap/services/markdown/index.ts | 6 +- .../components/tiptap/views/floatMenuView.tsx | 150 ++++++++++++++++ .../src/components/tiptap/views/tableView.tsx | 103 +++++++++++ .../client/src/components/tooltip/index.tsx | 17 +- packages/client/src/styles/extension.scss | 22 ++- packages/client/src/styles/prosemirror.scss | 17 +- 26 files changed, 626 insertions(+), 254 deletions(-) delete mode 100644 packages/client/src/components/tiptap/components/table/index.tsx delete mode 100644 packages/client/src/components/tiptap/extensions/tableCell.ts create mode 100644 packages/client/src/components/tiptap/extensions/tableCell.tsx delete mode 100644 packages/client/src/components/tiptap/extensions/tableHeader.ts create mode 100644 packages/client/src/components/tiptap/extensions/tableHeader.tsx rename packages/client/src/components/tiptap/extensions/{trailingNode.tsx => trailingNode.ts} (100%) rename packages/client/src/components/tiptap/menus/{base-bubble-menu.tsx => baseBubbleMenu.tsx} (97%) rename packages/client/src/components/tiptap/menus/{base-insert.tsx => baseInsert.tsx} (100%) rename packages/client/src/components/tiptap/menus/{base-menu.tsx => baseMenu.tsx} (100%) rename packages/client/src/components/tiptap/menus/components/{bubble-menu/bubble-menu-plugin.tsx => bubbleMenu/bubbleMenuPlugin.tsx} (100%) rename packages/client/src/components/tiptap/menus/components/{bubble-menu => bubbleMenu}/index.tsx (98%) rename packages/client/src/components/tiptap/menus/components/{font-size.tsx => fontSize.tsx} (100%) rename packages/client/src/components/tiptap/menus/{media-insert.tsx => mediaInsert.tsx} (100%) create mode 100644 packages/client/src/components/tiptap/views/floatMenuView.tsx create mode 100644 packages/client/src/components/tiptap/views/tableView.tsx diff --git a/packages/client/src/components/tiptap/components/table/index.tsx b/packages/client/src/components/tiptap/components/table/index.tsx deleted file mode 100644 index 852790bb..00000000 --- a/packages/client/src/components/tiptap/components/table/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'; -import { Space, Popover, Tag, Input } from '@douyinfe/semi-ui'; - -export const TableWrapper = ({ editor, node, updateAttributes }) => { - const isEditable = editor.isEditable; - const { color, text } = node.attrs; - const content = {text || '点击设置状态'}; - - console.log(node.attrs); - - return ( - - - -
-
- ); -}; diff --git a/packages/client/src/components/tiptap/extensions/table.ts b/packages/client/src/components/tiptap/extensions/table.ts index 4023b259..83aeb7ed 100644 --- a/packages/client/src/components/tiptap/extensions/table.ts +++ b/packages/client/src/components/tiptap/extensions/table.ts @@ -1,5 +1,14 @@ import { Table as BuiltInTable } from '@tiptap/extension-table'; +import { TableView } from '../views/tableView'; -export const Table = BuiltInTable.configure({ +export const Table = BuiltInTable.extend({ + // @ts-ignore + addOptions() { + return { + ...this.parent?.(), + View: TableView, + }; + }, +}).configure({ resizable: true, }); diff --git a/packages/client/src/components/tiptap/extensions/tableCell.ts b/packages/client/src/components/tiptap/extensions/tableCell.ts deleted file mode 100644 index 12d1df9a..00000000 --- a/packages/client/src/components/tiptap/extensions/tableCell.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { TableCell as BuiltInTable } from '@tiptap/extension-table-cell'; -import { Plugin, PluginKey } from 'prosemirror-state'; -import { Decoration, DecorationSet } from 'prosemirror-view'; -import { - getCellsInColumn, - isRowSelected, - isTableSelected, - selectRow, - selectTable, -} from '../services/table'; - -export const TableCell = BuiltInTable.extend({ - addProseMirrorPlugins() { - return [ - new Plugin({ - key: new PluginKey(`${this.name}FloatMenu`), - // view: () => - // new FloatMenuView({ - // editor: this.editor, - // // has one selected should show - // shouldShow: ({ editor }) => { - // if (!editor.isEditable) { - // return false; - // } - // const cells = getCellsInColumn(0)(editor.state.selection); - // return !!cells?.some((cell, index) => - // isRowSelected(index)(editor.state.selection) - // ); - // }, - // init: (dom, editor) => { - // const insertTop = buttonView({ - // id: "insert-top", - // name: this.options.dictionary.insertTop, - // icon: DoubleUp({}), - // }); - // insertTop.button.addEventListener("click", () => { - // editor.chain().addRowBefore().run(); - // }); - // const insertBottom = buttonView({ - // id: "insert-bottom", - // name: this.options.dictionary.insertBottom, - // icon: DoubleDown({}), - // }); - // insertBottom.button.addEventListener("click", () => { - // editor.chain().addRowAfter().run(); - // }); - // const remove = buttonView({ - // name: this.options.dictionary.delete, - // icon: Delete({}), - // }); - // remove.button.addEventListener("click", () => { - // if (isTableSelected(editor.state.selection)) { - // editor.chain().deleteTable().run(); - // } else { - // editor.chain().deleteRow().run(); - // } - // }); - - // dom.append(insertTop.button); - // dom.append(insertBottom.button); - // dom.append(remove.button); - // }, - // }), - props: { - decorations: (state) => { - const { doc, selection } = state; - const decorations: Decoration[] = []; - const cells = getCellsInColumn(0)(selection); - - if (cells) { - cells.forEach(({ pos }, index) => { - if (index === 0) { - decorations.push( - Decoration.widget(pos + 1, () => { - const grip = document.createElement('a'); - grip.classList.add('grip-table'); - if (isTableSelected(selection)) { - grip.classList.add('selected'); - } - grip.addEventListener('mousedown', (event) => { - event.preventDefault(); - event.stopImmediatePropagation(); - this.editor.view.dispatch(selectTable(this.editor.state.tr)); - }); - return grip; - }) - ); - } - decorations.push( - Decoration.widget(pos + 1, () => { - const rowSelected = isRowSelected(index)(selection); - const grip = document.createElement('a'); - grip.classList.add('grip-row'); - if (rowSelected) { - grip.classList.add('selected'); - } - if (index === 0) { - grip.classList.add('first'); - } - if (index === cells.length - 1) { - grip.classList.add('last'); - } - grip.addEventListener('mousedown', (event) => { - event.preventDefault(); - event.stopImmediatePropagation(); - this.editor.view.dispatch(selectRow(index)(this.editor.state.tr)); - }); - return grip; - }) - ); - }); - } - - return DecorationSet.create(doc, decorations); - }, - }, - }), - ]; - }, -}); diff --git a/packages/client/src/components/tiptap/extensions/tableCell.tsx b/packages/client/src/components/tiptap/extensions/tableCell.tsx new file mode 100644 index 00000000..b93e9cfb --- /dev/null +++ b/packages/client/src/components/tiptap/extensions/tableCell.tsx @@ -0,0 +1,162 @@ +import ReactDOM from 'react-dom'; +import { Button } from '@douyinfe/semi-ui'; +import { IconDelete, IconPlus } from '@douyinfe/semi-icons'; +import { TableCell as BuiltInTableCell } from '@tiptap/extension-table-cell'; +import { Tooltip } from 'components/tooltip'; +import { Plugin, PluginKey } from 'prosemirror-state'; +import { Decoration, DecorationSet } from 'prosemirror-view'; +import { + getCellsInRow, + getCellsInColumn, + isRowSelected, + isTableSelected, + selectRow, + selectTable, +} from '../services/table'; +import { elementInViewport } from '../services/dom'; +import { FloatMenuView } from '../views/floatMenuView'; + +export const TableCell = BuiltInTableCell.extend({ + addProseMirrorPlugins() { + const extensionThis = this; + let selectedRowIndex = -1; + + return [ + new Plugin({ + key: new PluginKey(`${this.name}FloatMenu`), + view: () => + new FloatMenuView({ + editor: this.editor, + shouldShow: ({ editor }, floatMenuView) => { + if (!editor.isEditable) { + return false; + } + const cells = getCellsInColumn(0)(editor.state.selection); + if (selectedRowIndex > -1) { + const rowCells = getCellsInRow(selectedRowIndex)(editor.state.selection); + if (rowCells && rowCells[0]) { + const node = editor.view.nodeDOM(rowCells[0].pos) as HTMLElement; + if (node) { + const el = node.querySelector('a.grip-row') as HTMLElement; + if (el) { + console.log({ el }); + floatMenuView.parentNode = el; + // const intersectionObserver = new IntersectionObserver(function (entries) { + // console.log('ob'); + // if (entries[0].intersectionRatio <= 0) { + // floatMenuView.hide(); + // } + // }); + // intersectionObserver.observe(el); + } + } + } + } + return !!cells?.some((cell, index) => isRowSelected(index)(editor.state.selection)); + }, + init: (dom, editor) => { + dom.classList.add('table-controller-wrapper'); + dom.classList.add('row'); + ReactDOM.render( + <> + +