tiptap: improve table

pull/44/head
fantasticit 2022-05-09 15:07:23 +08:00
parent b4764e11b7
commit a06c795360
11 changed files with 434 additions and 386 deletions

View File

@ -1,20 +1,7 @@
import { mergeAttributes } from '@tiptap/core';
import { TableCell as BuiltInTableCell } from '@tiptap/extension-table-cell';
import ReactDOM from 'react-dom';
import { Button } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Plugin } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import {
getCellsInRow,
getCellsInColumn,
isRowSelected,
isTableSelected,
selectRow,
selectTable,
} from 'tiptap/prose-utils';
import { FloatMenuView } from 'tiptap/views/float-menu';
import { getCellsInColumn, selectTable, isRowSelected, isTableSelected, selectRow } from 'tiptap/prose-utils';
export const TableCell = BuiltInTableCell.extend({
addAttributes() {
@ -40,173 +27,68 @@ export const TableCell = BuiltInTableCell.extend({
};
},
renderHTML({ HTMLAttributes }) {
let totalWidth = 0;
let fixedWidth = true;
addProseMirrorPlugins() {
return [
new Plugin({
props: {
decorations: (state) => {
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInColumn(0)(selection);
if (HTMLAttributes.colwidth) {
HTMLAttributes.colwidth.forEach((col) => {
if (!col) {
fixedWidth = false;
} else {
totalWidth += col;
if (cells) {
cells.forEach(({ pos }, index) => {
if (index === 0) {
decorations.push(
Decoration.widget(pos + 1, () => {
let className = 'grip-table';
const selected = isTableSelected(selection);
if (selected) {
className += ' selected';
}
const grip = document.createElement('a');
grip.className = className;
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectTable(this.editor.state.tr));
// this.options.onSelectTable(state);
});
return grip;
})
);
}
decorations.push(
Decoration.widget(pos + 1, () => {
const rowSelected = isRowSelected(index)(selection);
let className = 'grip-row';
if (rowSelected) {
className += ' selected';
}
if (index === 0) {
className += ' first';
}
if (index === cells.length - 1) {
className += ' last';
}
const grip = document.createElement('a');
grip.className = className;
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
});
return grip;
})
);
});
} else {
fixedWidth = false;
}
if (fixedWidth && totalWidth > 0) {
HTMLAttributes.style = `width: ${totalWidth}px;`;
} else if (totalWidth && totalWidth > 0) {
HTMLAttributes.style = `min-width: ${totalWidth}px`;
} else {
HTMLAttributes.style = null;
}
return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
// addProseMirrorPlugins() {
// const extensionThis = this;
// let selectedRowIndex = -1;
// return [
// new Plugin({
// key: new PluginKey(`${this.name}FloatMenu`),
// view: () =>
// new FloatMenuView({
// editor: this.editor,
// tippyOptions: {
// zIndex: 100,
// offset: [-28, 0],
// },
// shouldShow: ({ editor }, floatMenuView) => {
// if (!editor.isEditable) {
// return false;
// }
// if (isTableSelected(editor.state.selection)) {
// 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) {
// floatMenuView.parentNode = el;
// }
// }
// }
// }
// return !!cells?.some((cell, index) => isRowSelected(index)(editor.state.selection));
// },
// init: (dom, editor) => {
// dom.classList.add('bubble-memu-table-cell');
// dom.classList.add('row');
// ReactDOM.render(
// <>
// <Tooltip content="向前插入一行" position="left">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addRowBefore().run();
// }}
// />
// </Tooltip>
// <Tooltip content="删除当前行" position="left">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconDelete />}
// onClick={() => {
// editor.chain().deleteRow().run();
// }}
// />
// </Tooltip>
// <Tooltip content="向后插入一行" position="left" hideOnClick>
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addRowAfter().run();
// }}
// />
// </Tooltip>
// </>,
// dom
// );
// },
// }),
// props: {
// decorations: (state) => {
// if (!extensionThis.editor.isEditable) {
// return;
// }
// 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();
// selectedRowIndex = -1;
// 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();
// selectedRowIndex = index;
// this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
// });
// return grip;
// })
// );
// });
// }
// return DecorationSet.create(doc, decorations);
// },
// },
// }),
// ];
// },
});

View File

@ -1,13 +1,7 @@
import { mergeAttributes } from '@tiptap/core';
import { TableHeader as BuiltInTableHeader } from '@tiptap/extension-table-header';
import ReactDOM from 'react-dom';
import { Button, Space } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Plugin } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCellsInRow, isColumnSelected, isTableSelected, selectColumn } from 'tiptap/prose-utils';
import { FloatMenuView } from '../views/float-menu';
import { getCellsInRow, isColumnSelected, selectColumn } from 'tiptap/prose-utils';
export const TableHeader = BuiltInTableHeader.extend({
addAttributes() {
@ -33,144 +27,46 @@ export const TableHeader = BuiltInTableHeader.extend({
};
},
renderHTML({ HTMLAttributes }) {
let totalWidth = 0;
let fixedWidth = true;
addProseMirrorPlugins() {
return [
new Plugin({
props: {
decorations: (state) => {
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInRow(0)(selection);
if (HTMLAttributes.colwidth) {
HTMLAttributes.colwidth.forEach((col) => {
if (!col) {
fixedWidth = false;
} else {
totalWidth += col;
if (cells) {
cells.forEach(({ pos }, index) => {
decorations.push(
Decoration.widget(pos + 1, () => {
const colSelected = isColumnSelected(index)(selection);
let className = 'grip-column';
if (colSelected) {
className += ' selected';
}
if (index === 0) {
className += ' first';
} else if (index === cells.length - 1) {
className += ' last';
}
const grip = document.createElement('a');
grip.className = className;
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
});
return grip;
})
);
});
} else {
fixedWidth = false;
}
if (fixedWidth && totalWidth > 0) {
HTMLAttributes.style = `width: ${totalWidth}px;`;
} else if (totalWidth && totalWidth > 0) {
HTMLAttributes.style = `min-width: ${totalWidth}px`;
} else {
HTMLAttributes.style = null;
}
return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
// addProseMirrorPlugins() {
// const extensionThis = this;
// return [
// new Plugin({
// key: new PluginKey(`${this.name}FloatMenu`),
// view: () =>
// new FloatMenuView({
// editor: this.editor,
// tippyOptions: {
// zIndex: 100,
// },
// shouldShow: ({ editor }, floatMenuView) => {
// if (!editor.isEditable) {
// return false;
// }
// const selection = editor.state.selection;
// if (isTableSelected(selection)) {
// return false;
// }
// const cells = getCellsInRow(0)(selection);
// if (cells && cells[0]) {
// const node = editor.view.nodeDOM(cells[0].pos) as HTMLElement;
// floatMenuView.setConatiner(node.parentElement.parentElement.parentElement.parentElement);
// }
// return !!cells?.some((cell, index) => isColumnSelected(index)(selection));
// },
// init: (dom, editor) => {
// dom.classList.add('bubble-memu-table-cell');
// ReactDOM.render(
// <Space>
// <Tooltip content="向前插入一列">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addColumnBefore().run();
// }}
// />
// </Tooltip>
// <Tooltip content="删除当前列">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconDelete />}
// onClick={() => {
// editor.chain().deleteColumn().run();
// }}
// />
// </Tooltip>
// <Tooltip content="向后插入一列" hideOnClick>
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addColumnAfter().run();
// }}
// />
// </Tooltip>
// </Space>,
// dom
// );
// },
// }),
// props: {
// decorations: (state) => {
// if (!extensionThis.editor.isEditable) {
// return;
// }
// const { doc, selection } = state;
// const decorations: Decoration[] = [];
// const cells = getCellsInRow(0)(selection);
// if (cells) {
// cells.forEach(({ pos }, index) => {
// decorations.push(
// Decoration.widget(pos + 1, () => {
// const colSelected = isColumnSelected(index)(selection);
// const grip = document.createElement('a');
// grip.classList.add('grip-column');
// if (colSelected) {
// grip.classList.add('selected');
// }
// if (index === 0) {
// grip.classList.add('first');
// } else if (index === cells.length - 1) {
// grip.classList.add('last');
// }
// grip.addEventListener('mousedown', (event) => {
// event.preventDefault();
// event.stopImmediatePropagation();
// this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
// });
// return grip;
// })
// );
// });
// }
// return DecorationSet.create(doc, decorations);
// },
// },
// }),
// ];
// },
});

View File

@ -1,3 +1,53 @@
import BuiltInTable from '@tiptap/extension-table';
import { Decoration, DecorationSet } from 'prosemirror-view';
import { tableEditing } from 'prosemirror-tables';
import { Plugin } from 'prosemirror-state';
export const Table = BuiltInTable.configure({ resizable: true });
export const Table = BuiltInTable.extend({
renderHTML() {
return [
'div',
{ class: 'scrollable-wrapper' },
['div', { class: 'scrollable' }, ['table', { class: 'rme-table' }, ['tbody', 0]]],
];
},
addProseMirrorPlugins() {
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('rme-table');
const table = elements[index];
if (!table) return;
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';
return shadow;
})
);
}
index++;
});
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
}).configure({ resizable: true });

View File

@ -1,30 +1,62 @@
.ProseMirror {
.tableWrapper {
max-width: 100%;
padding: 0;
margin: 0;
overflow: auto;
.scrollable-wrapper {
position: relative;
margin: 0.5em 0;
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.scrollable {
padding-left: 1em;
margin-left: -1em;
overflow: auto hidden;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
transition: border 250ms ease-in-out 0s;
}
.scrollable-shadow {
position: absolute;
top: 0;
bottom: 0;
left: -1em;
width: 16px;
transition: box-shadow 250ms ease-in-out 0s;
border-width: 0 0 0 1em;
border-style: solid;
border-color: transparent;
border-image: initial;
pointer-events: none;
&.left {
box-shadow: 16px 0 16px -16px inset rgb(0 0 0 / 25%);
border-left: 1em solid red;
}
&.right {
right: 0;
left: auto;
box-shadow: rgb(0 0 0 / 25%) -16px 0 16px -16px inset;
}
}
table {
width: 100%;
max-width: 100%;
margin: 0.75em 0 0;
overflow: hidden;
margin-top: 1em;
border-radius: 4px;
border-collapse: collapse;
table-layout: fixed;
box-sizing: border-box;
border-color: var(--semi-color-fill-2);
td,
th {
position: relative;
box-sizing: border-box;
min-width: 1em;
padding: 3px 5px;
border-width: 1px;
border-style: solid;
border-color: var(--semi-color-fill-2);
overflow: visible;
min-width: 100px;
padding: 4px 8px;
text-align: left;
vertical-align: top;
border: 1px solid rgb(232 235 237);
border-color: var(--semi-color-fill-2);
> * {
margin-bottom: 0;
@ -42,6 +74,60 @@
background: var(--semi-color-info-light-hover);
}
.grip-column {
position: absolute;
top: -1em;
left: 0;
z-index: 10;
display: block;
width: 100%;
height: 0.7em;
margin-bottom: 3px;
cursor: pointer;
background: #ced4da;
&:hover,
&.selected {
background: var(--semi-color-info);
}
}
.grip-row {
position: absolute;
top: 0;
left: -1em;
z-index: 10;
display: block;
width: 0.7em;
height: 100%;
margin-right: 3px;
cursor: pointer;
background: #ced4da;
&:hover,
&.selected {
background: var(--semi-color-info);
}
}
.grip-table {
position: absolute;
top: -1em;
left: -1em;
z-index: 10;
display: block;
width: 0.8em;
height: 0.8em;
cursor: pointer;
background: #ced4da;
border-radius: 50%;
&:hover,
&.selected {
background: var(--semi-color-info);
}
}
.column-resize-handle {
position: absolute;
top: 0;
@ -51,10 +137,6 @@
pointer-events: none;
background-color: #adf;
}
p {
margin: 0;
}
}
}

View File

@ -43,11 +43,21 @@ export const TableBubbleMenu = ({ editor }) => {
className={'bubble-menu bubble-menu-table'}
editor={editor}
pluginKey="table-bubble-menu"
shouldShow={() => editor.isActive(Table.name)}
tippyOptions={{
maxWidth: 'calc(100vw - 100px)',
placement: 'bottom',
offset: [0, 20],
}}
shouldShow={() => {
return editor.isActive(Table.name);
}}
getRenderContainer={(node) => {
let container = node;
while (container.tagName !== 'TABLE') {
container = container.parentElement;
}
return container;
}}
matchRenderContainer={(node: HTMLElement) => node && node.tagName === 'TABLE'}
>
<Space spacing={4}>
<Tooltip content="复制">

View File

@ -0,0 +1,57 @@
import React, { useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui';
import { IconAddColumnBefore, IconAddColumnAfter, IconDeleteColumn } from 'components/icons';
import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from 'tiptap/editor/views/bubble-menu';
import { TableRow } from 'tiptap/core/extensions/table-row';
import { isTableSelected } from 'tiptap/prose-utils';
export const TableColBubbleMenu = ({ editor }) => {
const addColumnBefore = useCallback(() => editor.chain().focus().addColumnBefore().run(), [editor]);
const addColumnAfter = useCallback(() => editor.chain().focus().addColumnAfter().run(), [editor]);
const deleteColumn = useCallback(() => editor.chain().focus().deleteColumn().run(), [editor]);
return (
<BubbleMenu
className={'bubble-menu bubble-menu-table'}
editor={editor}
pluginKey="table-col-bubble-menu"
tippyOptions={{
offset: [0, 20],
}}
shouldShow={({ node, state }) => {
if (!node || isTableSelected(state.selection)) return false;
const gripColumn = node.querySelector('a.grip-column.selected');
return editor.isActive(TableRow.name) && !!gripColumn;
}}
getRenderContainer={(node) => {
return node;
}}
>
<Space spacing={4}>
<Tooltip content="向前插入一列">
<Button
onClick={addColumnBefore}
icon={<IconAddColumnBefore />}
type="tertiary"
theme="borderless"
size="small"
/>
</Tooltip>
<Tooltip content="向后插入一列">
<Button
onClick={addColumnAfter}
icon={<IconAddColumnAfter />}
type="tertiary"
theme="borderless"
size="small"
/>
</Tooltip>
<Tooltip content="删除当前列" hideOnClick>
<Button onClick={deleteColumn} icon={<IconDeleteColumn />} type="tertiary" theme="borderless" size="small" />
</Tooltip>
</Space>
</BubbleMenu>
);
};

View File

@ -1,7 +1,15 @@
import React from 'react';
import { Editor } from 'tiptap/editor';
import { TableBubbleMenu } from './bubble';
import { TableRowBubbleMenu } from './row-bubble';
import { TableColBubbleMenu } from './col-bubble';
export const Table: React.FC<{ editor: Editor }> = ({ editor }) => {
return <TableBubbleMenu editor={editor} />;
return (
<>
<TableBubbleMenu editor={editor} />
<TableRowBubbleMenu editor={editor} />
<TableColBubbleMenu editor={editor} />
</>
);
};

View File

@ -0,0 +1,47 @@
import React, { useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui';
import { IconAddRowBefore, IconAddRowAfter, IconDeleteRow } from 'components/icons';
import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from 'tiptap/editor/views/bubble-menu';
import { TableRow } from 'tiptap/core/extensions/table-row';
import { isTableSelected } from 'tiptap/prose-utils';
export const TableRowBubbleMenu = ({ editor }) => {
const addRowBefore = useCallback(() => editor.chain().focus().addRowBefore().run(), [editor]);
const addRowAfter = useCallback(() => editor.chain().focus().addRowAfter().run(), [editor]);
const deleteRow = useCallback(() => editor.chain().focus().deleteRow().run(), [editor]);
return (
<BubbleMenu
className={'bubble-menu bubble-menu-table'}
editor={editor}
pluginKey="table-row-bubble-menu"
tippyOptions={{
placement: 'left',
offset: [0, 20],
}}
shouldShow={({ node, state }) => {
if (!node || isTableSelected(state.selection)) return false;
const gripRow = node.querySelector('a.grip-row.selected');
return editor.isActive(TableRow.name) && !!gripRow;
}}
getRenderContainer={(node) => {
return node;
}}
>
<Space vertical spacing={4}>
<Tooltip content="向前插入一行">
<Button onClick={addRowBefore} icon={<IconAddRowBefore />} type="tertiary" theme="borderless" size="small" />
</Tooltip>
<Tooltip content="向后插入一行">
<Button onClick={addRowAfter} icon={<IconAddRowAfter />} type="tertiary" theme="borderless" size="small" />
</Tooltip>
<Tooltip content="删除当前行" hideOnClick>
<Button onClick={deleteRow} icon={<IconDeleteRow />} type="tertiary" theme="borderless" size="small" />
</Tooltip>
</Space>
</BubbleMenu>
);
};

View File

@ -11,6 +11,7 @@ export interface BubbleMenuPluginProps {
shouldShow?:
| ((props: {
editor: Editor;
node?: HTMLElement;
view?: EditorView;
state?: EditorState;
oldState?: EditorState;
@ -20,6 +21,7 @@ export interface BubbleMenuPluginProps {
| null;
renderContainerSelector?: string;
matchRenderContainer?: (node: HTMLElement) => boolean;
getRenderContainer?: (node: HTMLElement) => HTMLElement;
}
export type BubbleMenuViewProps = BubbleMenuPluginProps & {
@ -39,10 +41,9 @@ export class BubbleMenuView {
public tippyOptions?: Partial<Props>;
public renderContainerSelector?: string;
public matchRenderContainer?: BubbleMenuPluginProps['matchRenderContainer'];
// public renderContainerSelector?: string;
// public matchRenderContainer?: BubbleMenuPluginProps['matchRenderContainer'];
public getRenderContainer?: BubbleMenuPluginProps['getRenderContainer'];
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({ view, state, from, to }) => {
const { doc, selection } = state;
const { empty } = selection;
@ -65,14 +66,17 @@ export class BubbleMenuView {
view,
tippyOptions = {},
shouldShow,
renderContainerSelector,
matchRenderContainer,
// renderContainerSelector,
// matchRenderContainer,
getRenderContainer,
}: BubbleMenuViewProps) {
this.editor = editor;
this.element = element;
this.view = view;
this.renderContainerSelector = renderContainerSelector;
this.matchRenderContainer = matchRenderContainer;
// this.renderContainerSelector = renderContainerSelector;
// this.matchRenderContainer = matchRenderContainer;
this.getRenderContainer = getRenderContainer;
if (shouldShow) {
this.shouldShow = shouldShow;
@ -82,8 +86,8 @@ export class BubbleMenuView {
capture: true,
});
this.view.dom.addEventListener('dragstart', this.dragstartHandler);
this.editor.on('focus', this.focusHandler);
this.editor.on('blur', this.blurHandler);
// this.editor.on('focus', this.focusHandler);
// this.editor.on('blur', this.blurHandler);
this.tippyOptions = tippyOptions || {};
// Detaches menu content from its current parent
this.element.remove();
@ -167,12 +171,14 @@ export class BubbleMenuView {
const { ranges } = selection;
const from = Math.min(...ranges.map((range) => range.$from.pos));
const to = Math.max(...ranges.map((range) => range.$to.pos));
const node = view.domAtPos(from).node as HTMLElement;
const shouldShow =
this.editor.isEditable &&
this.shouldShow?.({
editor: this.editor,
view,
node,
state,
oldState,
from,
@ -187,34 +193,43 @@ export class BubbleMenuView {
this.tippy?.setProps({
getReferenceClientRect: () => {
if (isNodeSelection(state.selection)) {
let node = view.nodeDOM(from) as HTMLElement;
const node = view.nodeDOM(from) as HTMLElement;
if (this.matchRenderContainer) {
while (node && !this.matchRenderContainer(node)) {
node = node.firstElementChild as HTMLElement;
if (this.getRenderContainer) {
return this.getRenderContainer(node).getBoundingClientRect();
}
if (node) {
return node.getBoundingClientRect();
}
// if (this.matchRenderContainer) {
// while (node && !this.matchRenderContainer(node)) {
// node = node.firstElementChild as HTMLElement;
// }
// if (node) {
// return node.getBoundingClientRect();
// }
// }
// if (node) {
// return node.getBoundingClientRect();
// }
}
if (node) {
return node.getBoundingClientRect();
}
if (this.getRenderContainer) {
const node = view.domAtPos(from).node as HTMLElement;
return this.getRenderContainer(node).getBoundingClientRect();
}
if (this.matchRenderContainer) {
let node = view.domAtPos(from).node as HTMLElement;
// if (this.matchRenderContainer) {
// let node = view.domAtPos(from).node as HTMLElement;
while (node && !this.matchRenderContainer(node)) {
node = node.parentElement;
}
// while (node && !this.matchRenderContainer(node)) {
// node = node.parentElement;
// }
if (node) {
return node.getBoundingClientRect();
}
}
// if (node) {
// return node.getBoundingClientRect();
// }
// }
return posToDOMRect(view, from, to);
},

View File

@ -26,8 +26,9 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
editor,
tippyOptions = {},
shouldShow = null,
renderContainerSelector,
matchRenderContainer,
// renderContainerSelector,
// matchRenderContainer,
getRenderContainer,
} = props;
const plugin = BubbleMenuPlugin({
@ -36,8 +37,9 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
element,
tippyOptions,
shouldShow,
renderContainerSelector,
matchRenderContainer,
// renderContainerSelector,
// matchRenderContainer,
getRenderContainer,
});
editor.registerPlugin(plugin);

View File

@ -27,7 +27,6 @@
"constants/*": ["constants/*"],
"helpers/*": ["helpers/*"],
"tiptap/*": ["tiptap/*"],
"tiptap-v2/*": ["tiptap-v2/*"],
"event/*": ["event/*"]
}
},