mirror of https://github.com/fantasticit/think.git
tiptap: fix bubble menu
This commit is contained in:
parent
a06c795360
commit
7e0d145ce9
|
@ -16,7 +16,7 @@ export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper id="js-bannber-container" className={cls(styles.wrap)}>
|
<NodeViewWrapper id="js-callout-container" className={cls(styles.wrap)}>
|
||||||
<div
|
<div
|
||||||
className={cls(styles.innerWrap, 'render-wrapper')}
|
className={cls(styles.innerWrap, 'render-wrapper')}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -39,9 +39,15 @@ export const CalloutBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
<BubbleMenu
|
<BubbleMenu
|
||||||
className={'bubble-menu'}
|
className={'bubble-menu'}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey="calloyt-bubble-menu"
|
pluginKey="callout-bubble-menu"
|
||||||
shouldShow={() => editor.isActive(Callout.name)}
|
shouldShow={() => editor.isActive(Callout.name)}
|
||||||
matchRenderContainer={(node) => node && node.id === 'js-bannber-container'}
|
getRenderContainer={(node) => {
|
||||||
|
let container = node;
|
||||||
|
while (container && container.id !== 'js-callout-container') {
|
||||||
|
container = container.parentElement;
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Space spacing={4}>
|
<Space spacing={4}>
|
||||||
<Tooltip content="复制">
|
<Tooltip content="复制">
|
||||||
|
|
|
@ -18,7 +18,13 @@ export const CodeBlockBubbleMenu = ({ editor }) => {
|
||||||
pluginKey="code-block-bubble-menu"
|
pluginKey="code-block-bubble-menu"
|
||||||
shouldShow={() => editor.isActive(CodeBlock.name)}
|
shouldShow={() => editor.isActive(CodeBlock.name)}
|
||||||
tippyOptions={{ maxWidth: 'calc(100vw - 100px)' }}
|
tippyOptions={{ maxWidth: 'calc(100vw - 100px)' }}
|
||||||
matchRenderContainer={(node: HTMLElement) => node && node.classList && node.classList.contains('node-codeBlock')}
|
getRenderContainer={(node) => {
|
||||||
|
let container = node;
|
||||||
|
while (container && container.classList && !container.classList.contains('node-codeBlock')) {
|
||||||
|
container = container.parentElement;
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Space spacing={4}>
|
<Space spacing={4}>
|
||||||
<Tooltip content="复制">
|
<Tooltip content="复制">
|
||||||
|
|
|
@ -79,7 +79,14 @@ export const ImageBubbleMenu = ({ editor }) => {
|
||||||
tippyOptions={{
|
tippyOptions={{
|
||||||
maxWidth: 'calc(100vw - 100px)',
|
maxWidth: 'calc(100vw - 100px)',
|
||||||
}}
|
}}
|
||||||
matchRenderContainer={(node) => node && node.id === 'js-resizeable-container'}
|
getRenderContainer={(node) => {
|
||||||
|
try {
|
||||||
|
const inner = node.querySelector('#js-resizeable-container');
|
||||||
|
return inner as HTMLElement;
|
||||||
|
} catch (e) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Space spacing={4}>
|
<Space spacing={4}>
|
||||||
<Tooltip content="复制">
|
<Tooltip content="复制">
|
||||||
|
|
|
@ -30,7 +30,14 @@ export const MindBubbleMenu = ({ editor }) => {
|
||||||
pluginKey="mind-bubble-menu"
|
pluginKey="mind-bubble-menu"
|
||||||
shouldShow={() => editor.isActive(Mind.name)}
|
shouldShow={() => editor.isActive(Mind.name)}
|
||||||
tippyOptions={{ maxWidth: 'calc(100vw - 100px)' }}
|
tippyOptions={{ maxWidth: 'calc(100vw - 100px)' }}
|
||||||
matchRenderContainer={(node) => node && node.id === 'js-resizeable-container'}
|
getRenderContainer={(node) => {
|
||||||
|
try {
|
||||||
|
const inner = node.querySelector('#js-resizeable-container');
|
||||||
|
return inner as HTMLElement;
|
||||||
|
} catch (e) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Space spacing={4}>
|
<Space spacing={4}>
|
||||||
<Tooltip content="复制">
|
<Tooltip content="复制">
|
||||||
|
|
|
@ -46,7 +46,6 @@ export const TableBubbleMenu = ({ editor }) => {
|
||||||
tippyOptions={{
|
tippyOptions={{
|
||||||
maxWidth: 'calc(100vw - 100px)',
|
maxWidth: 'calc(100vw - 100px)',
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
offset: [0, 20],
|
|
||||||
}}
|
}}
|
||||||
shouldShow={() => {
|
shouldShow={() => {
|
||||||
return editor.isActive(Table.name);
|
return editor.isActive(Table.name);
|
||||||
|
@ -56,7 +55,7 @@ export const TableBubbleMenu = ({ editor }) => {
|
||||||
while (container.tagName !== 'TABLE') {
|
while (container.tagName !== 'TABLE') {
|
||||||
container = container.parentElement;
|
container = container.parentElement;
|
||||||
}
|
}
|
||||||
return container;
|
return container.parentElement;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Space spacing={4}>
|
<Space spacing={4}>
|
||||||
|
|
|
@ -1,173 +0,0 @@
|
||||||
import { Editor, isNodeSelection, posToDOMRect, Range } from '@tiptap/core';
|
|
||||||
import tippy, { Instance, Props } from 'tippy.js';
|
|
||||||
import { EditorView } from 'prosemirror-view';
|
|
||||||
import { EditorState } from 'prosemirror-state';
|
|
||||||
|
|
||||||
export type FloatMenuViewOptions = {
|
|
||||||
editor: Editor;
|
|
||||||
getReferenceClientRect?: (props: { editor: Editor; range: Range; oldState?: EditorState }) => DOMRect;
|
|
||||||
shouldShow: (props: { editor: Editor; range: Range; oldState?: EditorState }, instance: FloatMenuView) => boolean;
|
|
||||||
init: (dom: HTMLElement, editor: Editor) => void;
|
|
||||||
update?: (
|
|
||||||
dom: HTMLElement,
|
|
||||||
props: {
|
|
||||||
editor: Editor;
|
|
||||||
oldState?: EditorState;
|
|
||||||
range: Range;
|
|
||||||
show: () => void;
|
|
||||||
hide: () => void;
|
|
||||||
}
|
|
||||||
) => void;
|
|
||||||
tippyOptions?: Partial<Props>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class FloatMenuView {
|
|
||||||
public editor: Editor;
|
|
||||||
public parentNode: null | HTMLElement;
|
|
||||||
public container: null | HTMLElement;
|
|
||||||
private dom: HTMLElement;
|
|
||||||
private popup: Instance;
|
|
||||||
private _update: FloatMenuViewOptions['update'];
|
|
||||||
private shouldShow: FloatMenuViewOptions['shouldShow'];
|
|
||||||
private tippyOptions: FloatMenuViewOptions['tippyOptions'];
|
|
||||||
private getReferenceClientRect: NonNullable<FloatMenuViewOptions['getReferenceClientRect']> = ({ editor, range }) => {
|
|
||||||
const { view, state } = editor;
|
|
||||||
if (this.parentNode) {
|
|
||||||
return this.parentNode.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
if (isNodeSelection(state.selection)) {
|
|
||||||
const node = view.nodeDOM(range.from) as HTMLElement;
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
return node.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rangeRect = posToDOMRect(view, range.from, range.to);
|
|
||||||
|
|
||||||
if (this.container) {
|
|
||||||
const containerRect = this.container.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (rangeRect.width > containerRect.width) {
|
|
||||||
return containerRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rangeRect;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: FloatMenuViewOptions) {
|
|
||||||
this.editor = props.editor;
|
|
||||||
this.shouldShow = props.shouldShow;
|
|
||||||
this.tippyOptions = props.tippyOptions || {};
|
|
||||||
if (props.getReferenceClientRect) {
|
|
||||||
this.getReferenceClientRect = props.getReferenceClientRect;
|
|
||||||
}
|
|
||||||
this._update = props.update;
|
|
||||||
this.dom = document.createElement('div');
|
|
||||||
|
|
||||||
// init
|
|
||||||
props.init(this.dom, this.editor);
|
|
||||||
|
|
||||||
// popup
|
|
||||||
this.createPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
setConatiner(el) {
|
|
||||||
this.container = el;
|
|
||||||
// this.popup?.setProps({
|
|
||||||
// appendTo: el,
|
|
||||||
// });
|
|
||||||
// this.popup?.
|
|
||||||
}
|
|
||||||
|
|
||||||
createPopup() {
|
|
||||||
const { element: editorElement } = this.editor.options;
|
|
||||||
const editorIsAttached = !!editorElement.parentElement;
|
|
||||||
|
|
||||||
if (this.popup || !editorIsAttached) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.popup = tippy(editorElement, {
|
|
||||||
getReferenceClientRect: null,
|
|
||||||
content: this.dom,
|
|
||||||
interactive: true,
|
|
||||||
trigger: 'manual',
|
|
||||||
placement: 'top',
|
|
||||||
hideOnClick: 'toggle',
|
|
||||||
...Object.assign({ zIndex: 99 }, this.tippyOptions),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(view: EditorView, oldState?: EditorState) {
|
|
||||||
const { state, composing } = view;
|
|
||||||
const { doc, selection } = state;
|
|
||||||
const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
|
|
||||||
|
|
||||||
if (composing || isSame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createPopup();
|
|
||||||
|
|
||||||
const { ranges } = selection;
|
|
||||||
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
|
||||||
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
|
||||||
|
|
||||||
const shouldShow = this.shouldShow?.(
|
|
||||||
{
|
|
||||||
editor: this.editor,
|
|
||||||
oldState,
|
|
||||||
range: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shouldShow) {
|
|
||||||
this.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._update?.(this.dom, {
|
|
||||||
editor: this.editor,
|
|
||||||
oldState,
|
|
||||||
range: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
},
|
|
||||||
show: this.show.bind(this),
|
|
||||||
hide: this.hide.bind(this),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.popup.setProps({
|
|
||||||
getReferenceClientRect: () => {
|
|
||||||
return this.getReferenceClientRect({
|
|
||||||
editor: this.editor,
|
|
||||||
oldState,
|
|
||||||
range: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
|
||||||
this.popup?.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this.popup?.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
this.popup?.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue