mirror of https://github.com/fantasticit/think.git
tiptap: fix bubble menu
parent
a06c795360
commit
7e0d145ce9
|
@ -16,7 +16,7 @@ export const CalloutWrapper = ({ editor, node, updateAttributes }) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<NodeViewWrapper id="js-bannber-container" className={cls(styles.wrap)}>
|
||||
<NodeViewWrapper id="js-callout-container" className={cls(styles.wrap)}>
|
||||
<div
|
||||
className={cls(styles.innerWrap, 'render-wrapper')}
|
||||
style={{
|
||||
|
|
|
@ -39,9 +39,15 @@ export const CalloutBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
|
|||
<BubbleMenu
|
||||
className={'bubble-menu'}
|
||||
editor={editor}
|
||||
pluginKey="calloyt-bubble-menu"
|
||||
pluginKey="callout-bubble-menu"
|
||||
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}>
|
||||
<Tooltip content="复制">
|
||||
|
|
|
@ -18,7 +18,13 @@ export const CodeBlockBubbleMenu = ({ editor }) => {
|
|||
pluginKey="code-block-bubble-menu"
|
||||
shouldShow={() => editor.isActive(CodeBlock.name)}
|
||||
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}>
|
||||
<Tooltip content="复制">
|
||||
|
|
|
@ -79,7 +79,14 @@ export const ImageBubbleMenu = ({ editor }) => {
|
|||
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}>
|
||||
<Tooltip content="复制">
|
||||
|
|
|
@ -30,7 +30,14 @@ export const MindBubbleMenu = ({ editor }) => {
|
|||
pluginKey="mind-bubble-menu"
|
||||
shouldShow={() => editor.isActive(Mind.name)}
|
||||
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}>
|
||||
<Tooltip content="复制">
|
||||
|
|
|
@ -46,7 +46,6 @@ export const TableBubbleMenu = ({ editor }) => {
|
|||
tippyOptions={{
|
||||
maxWidth: 'calc(100vw - 100px)',
|
||||
placement: 'bottom',
|
||||
offset: [0, 20],
|
||||
}}
|
||||
shouldShow={() => {
|
||||
return editor.isActive(Table.name);
|
||||
|
@ -56,7 +55,7 @@ export const TableBubbleMenu = ({ editor }) => {
|
|||
while (container.tagName !== 'TABLE') {
|
||||
container = container.parentElement;
|
||||
}
|
||||
return container;
|
||||
return container.parentElement;
|
||||
}}
|
||||
>
|
||||
<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