@monolith-forensics/monolith-ui 1.9.1-dev.7 → 1.9.1-dev.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/RichTextEditor/Components/BubbleMenu.d.ts +5 -5
- package/dist/RichTextEditor/Components/BubbleMenu.js +15 -46
- package/dist/RichTextEditor/Extensions/getTiptapExtensions.d.ts +0 -2
- package/dist/RichTextEditor/Extensions/getTiptapExtensions.js +1 -7
- package/dist/RichTextEditor/Plugins/ImageActionsPlugin.js +1 -1
- package/dist/RichTextEditor/RichTextEditor.d.ts +1 -1
- package/dist/RichTextEditor/RichTextEditor.js +45 -3
- package/package.json +1 -1
- package/dist/RichTextEditor/Extensions/BubbleMenuExtension.d.ts +0 -7
- package/dist/RichTextEditor/Extensions/BubbleMenuExtension.js +0 -157
|
@@ -3,12 +3,9 @@ import { DropDownItem, DropDownMenuProps } from "../../DropDownMenu";
|
|
|
3
3
|
import { ReactElement } from "react";
|
|
4
4
|
import { ButtonProps } from "../../Button";
|
|
5
5
|
import { Editor } from "@tiptap/react";
|
|
6
|
-
export interface
|
|
6
|
+
export interface BubbleMenuContentProps {
|
|
7
7
|
className?: string;
|
|
8
8
|
editor: Editor;
|
|
9
|
-
rect: DOMRect;
|
|
10
|
-
open: boolean;
|
|
11
|
-
onOpen?: (element: HTMLElement) => void;
|
|
12
9
|
customMenuItems?: BubbleItem[];
|
|
13
10
|
}
|
|
14
11
|
interface BubbleMenuDropDownItem extends DropDownItem {
|
|
@@ -39,5 +36,8 @@ export type BubbleItem = {
|
|
|
39
36
|
buttonProps?: Partial<ButtonProps>;
|
|
40
37
|
onClick?: (editor: Editor) => void;
|
|
41
38
|
};
|
|
42
|
-
|
|
39
|
+
export type BubbleMenuOptions = {
|
|
40
|
+
customMenuItems?: BubbleItem[];
|
|
41
|
+
};
|
|
42
|
+
declare const BubbleMenu: React.FC<BubbleMenuContentProps>;
|
|
43
43
|
export default BubbleMenu;
|
|
@@ -3,8 +3,6 @@ import styled, { useTheme } from "styled-components";
|
|
|
3
3
|
import { Extensions } from "../Enums";
|
|
4
4
|
import { BoldIcon, ItalicIcon, UnderlineIcon, CaseSensitiveIcon, ListIcon, ListOrderedIcon, StrikethroughIcon, Heading1Icon, Heading2Icon, Heading3Icon, Heading4Icon, RemoveFormattingIcon, SquircleIcon, } from "lucide-react";
|
|
5
5
|
import { DropDownMenu, } from "../../DropDownMenu";
|
|
6
|
-
import { FloatingPortal, useFloating } from "@floating-ui/react";
|
|
7
|
-
import { useEffect, useRef } from "react";
|
|
8
6
|
import { Button } from "../../Button";
|
|
9
7
|
import TextColors from "../Enums/TextColors";
|
|
10
8
|
const getMenuItems = (editor, customMenuItems, theme) => {
|
|
@@ -224,7 +222,6 @@ const getMenuItems = (editor, customMenuItems, theme) => {
|
|
|
224
222
|
];
|
|
225
223
|
};
|
|
226
224
|
const BubbleMenuContent = styled.div `
|
|
227
|
-
position: fixed;
|
|
228
225
|
display: flex;
|
|
229
226
|
justify-content: space-between;
|
|
230
227
|
align-items: center;
|
|
@@ -272,51 +269,23 @@ const BubbleItemButton = styled(Button) `
|
|
|
272
269
|
background-color: ${({ theme }) => theme.palette.action.hover};
|
|
273
270
|
}
|
|
274
271
|
`;
|
|
275
|
-
const BubbleMenu = ({
|
|
276
|
-
var _a;
|
|
277
|
-
const menuRef = useRef(null);
|
|
278
|
-
const { refs, elements } = useFloating();
|
|
272
|
+
const BubbleMenu = ({ className, editor, customMenuItems = [], }) => {
|
|
279
273
|
const theme = useTheme();
|
|
280
|
-
useEffect(() => {
|
|
281
|
-
if (open && onOpen) {
|
|
282
|
-
onOpen(elements.floating);
|
|
283
|
-
}
|
|
284
|
-
}, [open, onOpen, elements.floating]);
|
|
285
|
-
const elementWidth = ((_a = elements.floating) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
|
|
286
274
|
const { from, to } = editor.state.selection;
|
|
287
275
|
const selectedText = editor.state.doc.textBetween(from, to, "\n", "\n");
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
refs.setFloating(ref);
|
|
303
|
-
menuRef.current = ref;
|
|
304
|
-
}, style: {
|
|
305
|
-
top,
|
|
306
|
-
left,
|
|
307
|
-
}, children: getMenuItems(editor, customMenuItems, theme).map((item) => {
|
|
308
|
-
var _a;
|
|
309
|
-
if (item.type === "button") {
|
|
310
|
-
const isActive = (_a = item.isActive) === null || _a === void 0 ? void 0 : _a.call(item, editor);
|
|
311
|
-
return (_jsx(BubbleItemButton, { variant: "subtle", onClick: () => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, editor); }, color: isActive ? "primary" : undefined, selected: isActive, children: item.icon && _jsx(item.icon, { size: 14 }) }, item.name));
|
|
312
|
-
}
|
|
313
|
-
if (item.type === "menu") {
|
|
314
|
-
return (_jsx(DropDownMenu, Object.assign({ data: item.items, size: "xs", arrow: item.arrow, buttonProps: item.buttonProps, variant: "subtle", buttonRender: item.buttonRender, onItemSelect: (item) => { var _a, _b; return (_b = (_a = item === null || item === void 0 ? void 0 : item.data) === null || _a === void 0 ? void 0 : _a.command) === null || _b === void 0 ? void 0 : _b.call(_a, editor, selectedText); }, dropDownProps: {
|
|
315
|
-
style: { width: 135 },
|
|
316
|
-
} }, item.dropDownProps, { children: item.icon
|
|
317
|
-
? (_jsx(item.icon, { size: 14 }))
|
|
318
|
-
: (item.label || item.name) }), item.name));
|
|
319
|
-
}
|
|
320
|
-
}) })) }));
|
|
276
|
+
return (_jsx(BubbleMenuContent, { className: className, children: getMenuItems(editor, customMenuItems, theme).map((item) => {
|
|
277
|
+
var _a;
|
|
278
|
+
if (item.type === "button") {
|
|
279
|
+
const isActive = (_a = item.isActive) === null || _a === void 0 ? void 0 : _a.call(item, editor);
|
|
280
|
+
return (_jsx(BubbleItemButton, { variant: "subtle", onClick: () => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, editor); }, color: isActive ? "primary" : undefined, selected: isActive, children: item.icon && _jsx(item.icon, { size: 14 }) }, item.name));
|
|
281
|
+
}
|
|
282
|
+
if (item.type === "menu") {
|
|
283
|
+
return (_jsx(DropDownMenu, Object.assign({ data: item.items, size: "xs", arrow: item.arrow, buttonProps: item.buttonProps, variant: "subtle", buttonRender: item.buttonRender, onItemSelect: (item) => { var _a, _b; return (_b = (_a = item === null || item === void 0 ? void 0 : item.data) === null || _a === void 0 ? void 0 : _a.command) === null || _b === void 0 ? void 0 : _b.call(_a, editor, selectedText); }, dropDownProps: {
|
|
284
|
+
style: { width: 135 },
|
|
285
|
+
} }, item.dropDownProps, { children: item.icon
|
|
286
|
+
? (_jsx(item.icon, { size: 14 }))
|
|
287
|
+
: (item.label || item.name) }), item.name));
|
|
288
|
+
}
|
|
289
|
+
}) }));
|
|
321
290
|
};
|
|
322
291
|
export default BubbleMenu;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Extension } from "@tiptap/core";
|
|
2
2
|
import { HandleImageUpload } from "../Plugins";
|
|
3
3
|
import { Extensions } from "../Enums";
|
|
4
|
-
import { BubbleMenuOptions } from "./BubbleMenuExtension";
|
|
5
4
|
export type ExtensionType = (typeof Extensions)[keyof typeof Extensions];
|
|
6
5
|
interface GetTipTapExtensionsProps {
|
|
7
6
|
extensions?: ExtensionType[];
|
|
8
7
|
slashCommands?: any[];
|
|
9
|
-
bubbleMenuOptions?: BubbleMenuOptions;
|
|
10
8
|
handleImageUpload?: HandleImageUpload;
|
|
11
9
|
}
|
|
12
10
|
declare const getTipTapExtensions: (props: GetTipTapExtensionsProps) => Extension[];
|
|
@@ -10,7 +10,6 @@ import { InputRule, Extension } from "@tiptap/core";
|
|
|
10
10
|
import { ImageActionsPlugin, UploadImagesPlugin, } from "../Plugins";
|
|
11
11
|
import getSlashCommand from "./getSlashCommand";
|
|
12
12
|
import { Extensions } from "../Enums";
|
|
13
|
-
import BubbleMenu from "./BubbleMenuExtension";
|
|
14
13
|
const CustomImage = TiptapImage.extend({
|
|
15
14
|
// Add data-uuid attribute to image
|
|
16
15
|
addAttributes() {
|
|
@@ -42,7 +41,7 @@ const CustomStorage = Extension.create({
|
|
|
42
41
|
};
|
|
43
42
|
},
|
|
44
43
|
});
|
|
45
|
-
const getTipTapExtensions = ({ extensions = [], slashCommands = [],
|
|
44
|
+
const getTipTapExtensions = ({ extensions = [], slashCommands = [], handleImageUpload, }) => {
|
|
46
45
|
return [
|
|
47
46
|
{
|
|
48
47
|
name: "starterKit",
|
|
@@ -162,11 +161,6 @@ const getTipTapExtensions = ({ extensions = [], slashCommands = [], bubbleMenuOp
|
|
|
162
161
|
category: "default",
|
|
163
162
|
extension: CustomStorage,
|
|
164
163
|
},
|
|
165
|
-
{
|
|
166
|
-
name: Extensions.BubbleMenu,
|
|
167
|
-
category: "default",
|
|
168
|
-
extension: BubbleMenu.configure(bubbleMenuOptions),
|
|
169
|
-
},
|
|
170
164
|
{
|
|
171
165
|
name: Extensions.SlashCommand,
|
|
172
166
|
extension: getSlashCommand({
|
|
@@ -60,7 +60,7 @@ const getImageFilename = (image) => {
|
|
|
60
60
|
return "image.png";
|
|
61
61
|
};
|
|
62
62
|
const getImageBlob = (src) => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
-
const response = yield fetch(src
|
|
63
|
+
const response = yield fetch(src);
|
|
64
64
|
if (!response.ok) {
|
|
65
65
|
throw new Error("Unable to load image.");
|
|
66
66
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Editor } from "@tiptap/react";
|
|
2
2
|
import { ExtensionType } from "./Extensions/getTiptapExtensions";
|
|
3
3
|
import { HandleImageUrlUpload, HandleImageUpload } from "./Plugins/UploadImagesPlugin";
|
|
4
|
-
import { BubbleMenuOptions } from "./
|
|
4
|
+
import { BubbleMenuOptions } from "./Components/BubbleMenu";
|
|
5
5
|
import { ToolbarOptions } from "./Toolbar/Toolbar";
|
|
6
6
|
type RichTextEditorProps = {
|
|
7
7
|
className?: string;
|
|
@@ -8,15 +8,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
-
import { useEffect, useRef, useState } from "react";
|
|
11
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
12
12
|
import styled from "styled-components";
|
|
13
13
|
import { EditorContent, useEditor } from "@tiptap/react";
|
|
14
|
+
import { BubbleMenu as TiptapBubbleMenu, } from "@tiptap/react/menus";
|
|
15
|
+
import { isTextSelection } from "@tiptap/core";
|
|
14
16
|
import { DOMParser as ProseMirrorDOMParser } from "@tiptap/pm/model";
|
|
15
17
|
import { Toolbar } from "./Toolbar";
|
|
16
18
|
import getTipTapExtensions from "./Extensions/getTiptapExtensions";
|
|
17
19
|
import { Extensions, SlashCommands } from "./Enums";
|
|
18
20
|
import { addImagePlaceholder, removeImagePlaceholder, startImageUpload, } from "./Plugins/UploadImagesPlugin";
|
|
19
21
|
import SaveBadge from "./Components/SaveBadge";
|
|
22
|
+
import BubbleMenuContent from "./Components/BubbleMenu";
|
|
20
23
|
import Fonts from "./Enums/Fonts";
|
|
21
24
|
import RichTextEditorContext from "./Contexts/RichTextEditorContext";
|
|
22
25
|
const getImageFilesFromClipboard = (clipboardData) => {
|
|
@@ -547,12 +550,52 @@ export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = ""
|
|
|
547
550
|
const isControlled = value !== undefined;
|
|
548
551
|
const hasImageExtension = extensions.includes(Extensions.Image);
|
|
549
552
|
const hasSlashCommandExtension = extensions.includes(Extensions.SlashCommand);
|
|
553
|
+
const hasBubbleMenuExtension = extensions.includes(Extensions.BubbleMenu);
|
|
550
554
|
const hasImageSlashCommand = hasSlashCommandExtension && slashCommands.includes(SlashCommands.Image);
|
|
551
555
|
const onChangeRef = useRef(onChange);
|
|
556
|
+
const bubbleMenuPortalRef = useRef(null);
|
|
552
557
|
const [fontState, setFontState] = useState(font || Fonts.DEFAULT);
|
|
553
558
|
useEffect(() => {
|
|
554
559
|
onChangeRef.current = onChange;
|
|
555
560
|
}, [onChange]);
|
|
561
|
+
const getBubbleMenuPortalRoot = useCallback(() => {
|
|
562
|
+
if (bubbleMenuPortalRef.current) {
|
|
563
|
+
return bubbleMenuPortalRef.current;
|
|
564
|
+
}
|
|
565
|
+
const portal = document.createElement("div");
|
|
566
|
+
portal.setAttribute("data-monolith-bubble-menu-portal", "");
|
|
567
|
+
document.body.appendChild(portal);
|
|
568
|
+
bubbleMenuPortalRef.current = portal;
|
|
569
|
+
return portal;
|
|
570
|
+
}, []);
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
return () => {
|
|
573
|
+
var _a;
|
|
574
|
+
(_a = bubbleMenuPortalRef.current) === null || _a === void 0 ? void 0 : _a.remove();
|
|
575
|
+
bubbleMenuPortalRef.current = null;
|
|
576
|
+
};
|
|
577
|
+
}, []);
|
|
578
|
+
const shouldShowBubbleMenu = useCallback(({ editor, element, view, state, from, to }) => {
|
|
579
|
+
const { selection } = state;
|
|
580
|
+
const isChildOfMenu = element.contains(document.activeElement);
|
|
581
|
+
const hasEditorFocus = view.hasFocus() || isChildOfMenu;
|
|
582
|
+
const selectedText = state.doc.textBetween(from, to).trim();
|
|
583
|
+
const isEmptyTextBlock = !selectedText && isTextSelection(state.selection);
|
|
584
|
+
if (!hasEditorFocus ||
|
|
585
|
+
selection.empty ||
|
|
586
|
+
isEmptyTextBlock ||
|
|
587
|
+
!editor.isEditable) {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
}, []);
|
|
592
|
+
const bubbleMenuPositionOptions = useMemo(() => ({
|
|
593
|
+
strategy: "fixed",
|
|
594
|
+
placement: "top",
|
|
595
|
+
offset: 8,
|
|
596
|
+
flip: false,
|
|
597
|
+
shift: { padding: 10 },
|
|
598
|
+
}), []);
|
|
556
599
|
if (hasImageSlashCommand && !hasImageExtension) {
|
|
557
600
|
throw new Error("Extensions.Image is required when using the Image slash command.");
|
|
558
601
|
}
|
|
@@ -568,7 +611,6 @@ export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = ""
|
|
|
568
611
|
extensions: getTipTapExtensions({
|
|
569
612
|
extensions,
|
|
570
613
|
slashCommands,
|
|
571
|
-
bubbleMenuOptions,
|
|
572
614
|
handleImageUpload,
|
|
573
615
|
}),
|
|
574
616
|
editorProps: {
|
|
@@ -632,5 +674,5 @@ export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = ""
|
|
|
632
674
|
return (_jsx(StyledContent, { className: className, children: _jsxs(RichTextEditorContext.Provider, { value: {
|
|
633
675
|
font: fontState,
|
|
634
676
|
setFont: setFontState,
|
|
635
|
-
}, children: [showToolbar && (_jsx(Toolbar, { editor: editor, toolbarOptions: toolbarOptions })), saving && _jsx(SaveBadge, {}), _jsx(EditorContent, { className: "editor-content", editor: editor, "data-font": fontState || null, style: style })] }) }));
|
|
677
|
+
}, children: [showToolbar && (_jsx(Toolbar, { editor: editor, toolbarOptions: toolbarOptions })), saving && _jsx(SaveBadge, {}), editor && hasBubbleMenuExtension && (_jsx(TiptapBubbleMenu, { editor: editor, pluginKey: "bubbleMenu", updateDelay: 150, appendTo: getBubbleMenuPortalRoot, shouldShow: shouldShowBubbleMenu, options: bubbleMenuPositionOptions, children: _jsx(BubbleMenuContent, { editor: editor, customMenuItems: bubbleMenuOptions === null || bubbleMenuOptions === void 0 ? void 0 : bubbleMenuOptions.customMenuItems }) })), _jsx(EditorContent, { className: "editor-content", editor: editor, "data-font": fontState || null, style: style })] }) }));
|
|
636
678
|
};
|
package/package.json
CHANGED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { Extension, isNodeSelection, isTextSelection, posToDOMRect, } from "@tiptap/core";
|
|
2
|
-
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
3
|
-
import { ReactRenderer } from "@tiptap/react";
|
|
4
|
-
import BubbleMenuComponent from "../Components/BubbleMenu";
|
|
5
|
-
class Menu {
|
|
6
|
-
constructor({ view, editor, customMenuItems, }) {
|
|
7
|
-
this.mousedownHandler = (event) => {
|
|
8
|
-
this.preventShow = true;
|
|
9
|
-
};
|
|
10
|
-
this.mouseUpHandler = (event) => {
|
|
11
|
-
this.preventShow = false;
|
|
12
|
-
this.update(this.editor.view);
|
|
13
|
-
};
|
|
14
|
-
this.focusHandler = () => {
|
|
15
|
-
// this.editor.commands.setTextSelection({ from: 0, to: 0 });
|
|
16
|
-
// we use `setTimeout` to make sure `selection` is already updated
|
|
17
|
-
setTimeout(() => this.update(this.editor.view));
|
|
18
|
-
};
|
|
19
|
-
this.blurHandler = ({ event }) => {
|
|
20
|
-
var _a;
|
|
21
|
-
if (this.preventShow) {
|
|
22
|
-
this.preventShow = false;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if ((event === null || event === void 0 ? void 0 : event.relatedTarget) &&
|
|
26
|
-
((_a = this.floating) === null || _a === void 0 ? void 0 : _a.contains(event === null || event === void 0 ? void 0 : event.relatedTarget))) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// clear text selection
|
|
30
|
-
// this.editor.commands.setTextSelection({ from: 0, to: 0 });
|
|
31
|
-
this.hide();
|
|
32
|
-
};
|
|
33
|
-
this.editor = editor;
|
|
34
|
-
this.view = view;
|
|
35
|
-
this.rect = null;
|
|
36
|
-
this.preventShow = false;
|
|
37
|
-
this.floating = null;
|
|
38
|
-
this.isOpen = false;
|
|
39
|
-
// create and mount react component
|
|
40
|
-
if (!this.component) {
|
|
41
|
-
this.component = new ReactRenderer(BubbleMenuComponent, {
|
|
42
|
-
props: {
|
|
43
|
-
editor: this.editor,
|
|
44
|
-
open: false,
|
|
45
|
-
onOpen: (ref) => {
|
|
46
|
-
this.floating = ref;
|
|
47
|
-
},
|
|
48
|
-
customMenuItems,
|
|
49
|
-
},
|
|
50
|
-
editor: this.editor,
|
|
51
|
-
});
|
|
52
|
-
document.body.appendChild(this.component.element);
|
|
53
|
-
}
|
|
54
|
-
// don't show the bubble during selection of text
|
|
55
|
-
this.view.dom.addEventListener("mousedown", this.mousedownHandler, {
|
|
56
|
-
capture: true,
|
|
57
|
-
});
|
|
58
|
-
this.view.dom.addEventListener("mouseup", this.mouseUpHandler);
|
|
59
|
-
this.editor.on("blur", this.blurHandler);
|
|
60
|
-
this.editor.on("focus", this.focusHandler);
|
|
61
|
-
}
|
|
62
|
-
update(view, oldState) {
|
|
63
|
-
var _a;
|
|
64
|
-
const { state, composing } = view;
|
|
65
|
-
const { doc, selection } = state;
|
|
66
|
-
const { empty, ranges } = selection;
|
|
67
|
-
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
|
68
|
-
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
|
69
|
-
const selectionChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.selection.eq(view.state.selection));
|
|
70
|
-
const docChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.doc.eq(view.state.doc));
|
|
71
|
-
const isSame = !selectionChanged && !docChanged;
|
|
72
|
-
if (composing || isSame) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Sometime check for `empty` is not enough.
|
|
76
|
-
// Doubleclick an empty paragraph returns a node size of 2.
|
|
77
|
-
// So we check also for an empty text size.
|
|
78
|
-
const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
|
|
79
|
-
// When clicking on a element inside the bubble menu the editor "blur" event
|
|
80
|
-
// is called and the bubble menu item is focussed. In this case we should
|
|
81
|
-
// consider the menu as part of the editor and keep showing the menu
|
|
82
|
-
const isChildOfMenu = (_a = this === null || this === void 0 ? void 0 : this.floating) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement);
|
|
83
|
-
const hasEditorFocus = view.hasFocus() || isChildOfMenu;
|
|
84
|
-
if (!hasEditorFocus ||
|
|
85
|
-
empty ||
|
|
86
|
-
isEmptyTextBlock ||
|
|
87
|
-
!this.editor.isEditable ||
|
|
88
|
-
this.preventShow) {
|
|
89
|
-
this.hide();
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
// only set position when it is not already open
|
|
93
|
-
// otherwise the menu will jump around when the selection changes or text formatting is applied
|
|
94
|
-
if (!this.isOpen) {
|
|
95
|
-
if (isNodeSelection(state.selection)) {
|
|
96
|
-
let node = view.nodeDOM(from);
|
|
97
|
-
const nodeViewWrapper = node.dataset.nodeViewWrapper
|
|
98
|
-
? node
|
|
99
|
-
: node.querySelector("[data-node-view-wrapper]");
|
|
100
|
-
if (nodeViewWrapper) {
|
|
101
|
-
node = nodeViewWrapper.firstChild;
|
|
102
|
-
}
|
|
103
|
-
if (node) {
|
|
104
|
-
this.rect = node.getBoundingClientRect();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
this.rect = posToDOMRect(view, from, to);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
this.show();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
show() {
|
|
115
|
-
if (this.component) {
|
|
116
|
-
this.component.updateProps({ open: true, rect: this.rect });
|
|
117
|
-
this.isOpen = true;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
hide() {
|
|
121
|
-
if (this.component) {
|
|
122
|
-
this.component.updateProps({ open: false, rect: null });
|
|
123
|
-
this.isOpen = false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
destroy() {
|
|
127
|
-
if (this.component) {
|
|
128
|
-
this.view.dom.removeEventListener("mousedown", this.mousedownHandler);
|
|
129
|
-
this.view.dom.removeEventListener("mouseup", this.mouseUpHandler);
|
|
130
|
-
this.component.destroy();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const BubbleMenu = Extension.create({
|
|
135
|
-
name: "bubbleMenu",
|
|
136
|
-
addOptions() {
|
|
137
|
-
return {
|
|
138
|
-
bubbleMenuOptions: {},
|
|
139
|
-
};
|
|
140
|
-
},
|
|
141
|
-
addProseMirrorPlugins() {
|
|
142
|
-
return [
|
|
143
|
-
new Plugin({
|
|
144
|
-
key: new PluginKey("bubbleMenu"),
|
|
145
|
-
view: (view) => {
|
|
146
|
-
var _a;
|
|
147
|
-
return new Menu({
|
|
148
|
-
view,
|
|
149
|
-
editor: this.editor,
|
|
150
|
-
customMenuItems: ((_a = this === null || this === void 0 ? void 0 : this.options) === null || _a === void 0 ? void 0 : _a.customMenuItems) || [],
|
|
151
|
-
});
|
|
152
|
-
},
|
|
153
|
-
}),
|
|
154
|
-
];
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
export default BubbleMenu;
|