@monolith-forensics/monolith-ui 1.1.47 → 1.1.49

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.
Files changed (29) hide show
  1. package/dist/Button/Button.d.ts +1 -0
  2. package/dist/Button/Button.js +3 -1
  3. package/dist/DropDownMenu/DropDownMenu.d.ts +6 -10
  4. package/dist/DropDownMenu/DropDownMenu.js +13 -6
  5. package/dist/DropDownMenu/index.d.ts +1 -0
  6. package/dist/DropDownMenu/types.d.ts +9 -0
  7. package/dist/DropDownMenu/types.js +1 -0
  8. package/dist/Grid/Grid.js +2 -1
  9. package/dist/Modal/Modal.d.ts +1 -0
  10. package/dist/Modal/Modal.js +16 -1
  11. package/dist/RichTextEditor/Components/BubbleMenu.d.ts +34 -0
  12. package/dist/RichTextEditor/Components/BubbleMenu.js +276 -0
  13. package/dist/RichTextEditor/Components/SaveBadge.d.ts +6 -0
  14. package/dist/RichTextEditor/Components/SaveBadge.js +16 -0
  15. package/dist/RichTextEditor/Enums/Extensions.d.ts +12 -1
  16. package/dist/RichTextEditor/Enums/Extensions.js +11 -0
  17. package/dist/RichTextEditor/Extensions/BubbleMenuExtension.d.ts +7 -0
  18. package/dist/RichTextEditor/Extensions/BubbleMenuExtension.js +157 -0
  19. package/dist/RichTextEditor/Extensions/SlashCommandList.d.ts +1 -1
  20. package/dist/RichTextEditor/Extensions/SlashCommandList.js +67 -72
  21. package/dist/RichTextEditor/Extensions/getSlashCommand.js +50 -50
  22. package/dist/RichTextEditor/Extensions/getTiptapExtensions.d.ts +2 -0
  23. package/dist/RichTextEditor/Extensions/getTiptapExtensions.js +14 -12
  24. package/dist/RichTextEditor/RichTextEditor.d.ts +2 -0
  25. package/dist/RichTextEditor/RichTextEditor.js +8 -2
  26. package/dist/SelectBox/SelectBox.js +6 -3
  27. package/dist/TagBox/TagBox.js +4 -2
  28. package/dist/index.d.ts +2 -0
  29. package/package.json +3 -1
@@ -13,6 +13,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
13
13
  variant?: Variant;
14
14
  color?: string;
15
15
  disabled?: boolean;
16
+ selected?: boolean;
16
17
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
17
18
  }
18
19
  declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -117,7 +117,7 @@ const StyledButton = styled.button `
117
117
  }
118
118
  }};
119
119
 
120
- background-color: ${({ theme, variant, color }) => {
120
+ background-color: ${({ theme, variant, color, selected }) => {
121
121
  var _a, _b, _c, _d, _e, _f;
122
122
  if (variant === "default")
123
123
  return "transparent";
@@ -144,6 +144,8 @@ const StyledButton = styled.button `
144
144
  return "transparent";
145
145
  case "text":
146
146
  return "transparent";
147
+ case "subtle":
148
+ return selected ? theme.palette.action.hover : "transparent";
147
149
  default:
148
150
  return "transparent";
149
151
  }
@@ -1,20 +1,15 @@
1
- import { ComponentPropsWithoutRef, ComponentType, ReactNode, UIEvent } from "react";
1
+ import { ComponentPropsWithoutRef, ComponentType, ReactElement, ReactNode, UIEvent } from "react";
2
2
  import { ButtonProps } from "../Button";
3
3
  import { Size, Variant } from "../core";
4
+ import { DropDownItem } from "./types";
4
5
  interface StyledContentProps {
5
6
  maxDropdownHeight?: number | string;
6
7
  variant?: Variant;
7
8
  }
8
9
  declare const StyledContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, StyledContentProps>> & string;
9
- interface DropDownItem {
10
- toLowerCase?: () => string;
11
- label: string;
12
- value: string;
13
- data?: any;
14
- }
15
- interface DropDownMenuProps {
10
+ export interface DropDownMenuProps {
16
11
  className?: string;
17
- children?: ReactNode;
12
+ children?: ReactNode | string;
18
13
  data?: DropDownItem[];
19
14
  variant?: Variant;
20
15
  defaultValue?: DropDownItem[];
@@ -30,7 +25,8 @@ interface DropDownMenuProps {
30
25
  arrow?: boolean;
31
26
  searchable?: boolean;
32
27
  dropDownProps?: ComponentPropsWithoutRef<typeof StyledContent>;
28
+ buttonRender?: (props: any) => ReactElement;
33
29
  buttonProps?: ButtonProps;
34
30
  }
35
- declare const DropDownMenu: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<DropDownMenuProps, never>> & string & Omit<({ className, children, data, variant, defaultValue, multiselect, size, TooltipContent, renderOption, onChange, onItemSelect, onScroll, loading, arrow, searchable, dropDownProps, buttonProps, }: DropDownMenuProps) => import("react/jsx-runtime").JSX.Element, keyof import("react").Component<any, {}, any>>;
31
+ declare const DropDownMenu: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<DropDownMenuProps, never>> & string & Omit<({ className, children, data, variant, defaultValue, multiselect, size, TooltipContent, renderOption, onChange, onItemSelect, onScroll, loading, arrow, searchable, dropDownProps, buttonRender, buttonProps, }: DropDownMenuProps) => import("react/jsx-runtime").JSX.Element, keyof import("react").Component<any, {}, any>>;
36
32
  export default DropDownMenu;
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { FloatingFocusManager, FloatingPortal, flip, useFloating, } from "@floating-ui/react";
2
+ import { FloatingFocusManager, FloatingPortal, flip, offset, useFloating, } from "@floating-ui/react";
3
3
  import { forwardRef, useEffect, useRef, useState, } from "react";
4
4
  import styled from "styled-components";
5
5
  import { Button, Tooltip, CheckBox, Input } from "..";
6
6
  import { useDebouncedCallback } from "use-debounce";
7
+ import { ChevronDownIcon } from "lucide-react";
7
8
  const StyledFloatContainer = styled.div `
8
9
  z-index: 1500;
9
10
  `;
@@ -107,7 +108,7 @@ const StyledContent = styled.div `
107
108
  display: none;
108
109
  }
109
110
  `;
110
- const DropDownMenu = styled(({ className, children, data = [], variant = "outlined", defaultValue, multiselect = false, size = "xs", TooltipContent, renderOption, onChange, onItemSelect, onScroll, loading, arrow, searchable, dropDownProps, buttonProps, }) => {
111
+ const DropDownMenu = styled(({ className, children, data = [], variant = "outlined", defaultValue, multiselect = false, size = "xs", TooltipContent, renderOption, onChange, onItemSelect, onScroll, loading, arrow, searchable, dropDownProps, buttonRender, buttonProps, }) => {
111
112
  var _a, _b, _c;
112
113
  const isObjectArray = (_a = Object.keys((data === null || data === void 0 ? void 0 : data[0]) || {})) === null || _a === void 0 ? void 0 : _a.includes("label");
113
114
  const [isOpen, setIsOpen] = useState(false);
@@ -121,7 +122,7 @@ const DropDownMenu = styled(({ className, children, data = [], variant = "outlin
121
122
  placement: "bottom-start",
122
123
  strategy: "absolute",
123
124
  // Handle collisions with the viewport
124
- middleware: [flip()],
125
+ middleware: [flip(), offset(3)],
125
126
  });
126
127
  const handleAddItem = (item) => {
127
128
  setSelected((prev) => {
@@ -171,9 +172,13 @@ const DropDownMenu = styled(({ className, children, data = [], variant = "outlin
171
172
  }, [isOpen, searchable]);
172
173
  const scrollActive = (((_b = scrollContainerRef === null || scrollContainerRef === void 0 ? void 0 : scrollContainerRef.current) === null || _b === void 0 ? void 0 : _b.scrollHeight) || 0) >
173
174
  (((_c = scrollContainerRef === null || scrollContainerRef === void 0 ? void 0 : scrollContainerRef.current) === null || _c === void 0 ? void 0 : _c.clientHeight) || 0);
174
- return (_jsxs("div", { className: className + " mfDropDownMenu", children: [_jsx(Button, Object.assign({ ref: (ref) => {
175
+ return (_jsxs("div", { className: className + " mfDropDownMenu", children: [buttonRender ? (buttonRender({
176
+ onClick: () => setIsOpen(!isOpen),
177
+ buttonRef: refs.setReference,
178
+ isOpen,
179
+ })) : (_jsx(Button, Object.assign({ ref: (ref) => {
175
180
  refs.setReference(ref);
176
- }, onMouseDown: () => setIsOpen(!isOpen), rightSection: arrow ? _jsx("span", { children: "\u25BC" }) : null, size: size }, buttonProps, { children: children })), isOpen && (_jsx(FloatingPortal, { preserveTabOrder: true, children: _jsx(FloatingFocusManager, { context: context, modal: false, children: _jsx(StyledFloatContainer, { ref: refs.setFloating, style: floatingStyles, className: "mfFloating", children: _jsx(StyledContent, Object.assign({ className: "mfFloatingContent", style: { width: 150, maxWidth: 400 }, variant: variant }, dropDownProps, { children: _jsxs(StyledInnerItemContainer, { ref: scrollContainerRef, "data-scroll-active": scrollActive, onScroll: onScroll, children: [loading && _jsx("div", { children: "Loading..." }), searchable && (_jsx(SearchInput, { ref: searchInputRef, variant: "outlined", placeholder: "Search", onChange: handleSearch })), !loading &&
181
+ }, onClick: () => setIsOpen(!isOpen), rightSection: arrow ? _jsx(ChevronDownIcon, { size: 12 }) : null, size: size, variant: variant, selected: isOpen }, buttonProps, { children: children }))), isOpen && (_jsx(FloatingPortal, { preserveTabOrder: true, children: _jsx(FloatingFocusManager, { context: context, modal: false, children: _jsx(StyledFloatContainer, { ref: refs.setFloating, style: floatingStyles, className: "mfFloating", children: _jsx(StyledContent, Object.assign({ className: "mfFloatingContent", style: { width: 150, maxWidth: 400 } }, dropDownProps, { children: _jsxs(StyledInnerItemContainer, { ref: scrollContainerRef, "data-scroll-active": scrollActive, onScroll: onScroll, children: [loading && _jsx("div", { children: "Loading..." }), searchable && (_jsx(SearchInput, { ref: searchInputRef, variant: "outlined", size: size, placeholder: "Search", onChange: handleSearch })), !loading &&
177
182
  data
178
183
  .filter((item) => {
179
184
  var _a, _b, _c;
@@ -191,6 +196,7 @@ const DropDownMenu = styled(({ className, children, data = [], variant = "outlin
191
196
  ? (s === null || s === void 0 ? void 0 : s.value) === (item === null || item === void 0 ? void 0 : item.value)
192
197
  : s === item));
193
198
  return (_jsx(Tooltip, { content: TooltipContent ? (_jsx(TooltipContent, { data: item.data })) : null, side: "left", children: _jsxs(StyledItem, { className: "mfFloatingItem", onClick: (e) => {
199
+ var _a;
194
200
  e.preventDefault();
195
201
  e.stopPropagation();
196
202
  if (multiselect) {
@@ -202,7 +208,8 @@ const DropDownMenu = styled(({ className, children, data = [], variant = "outlin
202
208
  setIsOpen(false);
203
209
  }
204
210
  onItemSelect === null || onItemSelect === void 0 ? void 0 : onItemSelect(item);
205
- }, "data-selected": isSelected, size: size, children: [multiselect && _jsx(CheckBox, { value: isSelected }), _jsx(_Fragment, { children: (renderOption === null || renderOption === void 0 ? void 0 : renderOption(item)) || (item === null || item === void 0 ? void 0 : item.label) || item })] }) }, index));
211
+ (_a = item === null || item === void 0 ? void 0 : item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, item);
212
+ }, "data-selected": isSelected, size: size, children: [multiselect && _jsx(CheckBox, { value: isSelected }), (item === null || item === void 0 ? void 0 : item.leftSection) || null, _jsx(_Fragment, { children: (renderOption === null || renderOption === void 0 ? void 0 : renderOption(item)) || (item === null || item === void 0 ? void 0 : item.label) || item }), (item === null || item === void 0 ? void 0 : item.rightSection) || null] }) }, index));
206
213
  })] }) })) }) }) }))] }));
207
214
  }) ``;
208
215
  export default DropDownMenu;
@@ -1 +1,2 @@
1
1
  export { default } from "./DropDownMenu";
2
+ export type { DropDownItem } from "./types";
@@ -0,0 +1,9 @@
1
+ export type DropDownItem = {
2
+ toLowerCase?: () => string;
3
+ label: string;
4
+ value: string;
5
+ onClick?: (args: any) => void;
6
+ data?: any;
7
+ leftSection?: React.ReactNode;
8
+ rightSection?: React.ReactNode;
9
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/Grid/Grid.js CHANGED
@@ -5,7 +5,8 @@ const Grid = styled(({ className, children }) => {
5
5
  }) `
6
6
  display: grid;
7
7
  grid-template-columns: repeat(${({ col }) => col || 1}, minmax(0, 1fr));
8
- gap: 20px;
8
+ column-gap: 20px;
9
+ row-gap: 15px;
9
10
 
10
11
  width: ${({ width }) => Number.isInteger(width) ? `${width}px` : width || "100%"};
11
12
  height: auto;
@@ -10,5 +10,6 @@ declare const Modal: {
10
10
  ({ children, open, onClose, style, showCloseButton, FocusProps, }: ModalProps): import("react/jsx-runtime").JSX.Element;
11
11
  Title: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, never>> & string;
12
12
  SubTitle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, never>> & string;
13
+ Content: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
13
14
  };
14
15
  export default Modal;
@@ -70,11 +70,20 @@ const StyledClose = styled.div `
70
70
  color: ${(props) => props.theme.palette.text.secondary};
71
71
  `;
72
72
  const StyledButton = styled.button `
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ color: ${(props) => props.theme.palette.text.secondary};
73
77
  background-color: transparent;
78
+ border-radius: 4px;
74
79
  border: none;
75
80
  outline: none;
76
81
  cursor: pointer;
77
- padding: 0;
82
+ padding: 2px;
83
+
84
+ &:hover {
85
+ background-color: ${(props) => props.theme.palette.action.hover};
86
+ }
78
87
  `;
79
88
  const Title = styled.h1 `
80
89
  font-size: 20px;
@@ -87,6 +96,11 @@ const SubTitle = styled.h2 `
87
96
  margin: 0px;
88
97
  color: ${(props) => props.theme.palette.text.secondary};
89
98
  `;
99
+ const ModalContent = styled.div `
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 1rem;
103
+ `;
90
104
  const Modal = ({ children, open = false, onClose, style = {}, showCloseButton = false, FocusProps = {}, }) => {
91
105
  const { refs, floatingStyles, context } = useFloating({
92
106
  open,
@@ -103,4 +117,5 @@ const Modal = ({ children, open = false, onClose, style = {}, showCloseButton =
103
117
  };
104
118
  Modal.Title = Title;
105
119
  Modal.SubTitle = SubTitle;
120
+ Modal.Content = ModalContent;
106
121
  export default Modal;
@@ -0,0 +1,34 @@
1
+ import Extensions from "../Enums/Extensions";
2
+ import { DropDownItem } from "../../DropDownMenu";
3
+ import { ReactElement } from "react";
4
+ import { ButtonProps } from "../../Button";
5
+ import { Editor } from "@tiptap/react";
6
+ import { DropDownMenuProps } from "../../DropDownMenu/DropDownMenu";
7
+ export interface BubbleMenuProps {
8
+ className?: string;
9
+ editor: Editor;
10
+ rect: DOMRect;
11
+ open: boolean;
12
+ onOpen?: (element: HTMLElement) => void;
13
+ customMenuItems?: BubbleItem[];
14
+ }
15
+ interface BubbleMenuDropDownItem extends DropDownItem {
16
+ data?: {
17
+ command?: (editor: Editor, selectedText: string) => void;
18
+ };
19
+ }
20
+ export type BubbleItem = {
21
+ name: Extensions | string;
22
+ icon?: React.FC<any>;
23
+ type?: "button" | "menu";
24
+ label?: string | Element;
25
+ items?: BubbleMenuDropDownItem[];
26
+ arrow?: boolean;
27
+ isActive?: (editor: Editor) => boolean;
28
+ buttonRender?: (props: any) => ReactElement;
29
+ buttonProps?: ButtonProps;
30
+ dropDownProps?: DropDownMenuProps;
31
+ onClick?: (editor: Editor) => void;
32
+ };
33
+ declare const BubbleMenu: React.FC<BubbleMenuProps>;
34
+ export default BubbleMenu;
@@ -0,0 +1,276 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styled from "styled-components";
3
+ import Extensions from "../Enums/Extensions";
4
+ import { BoldIcon, ItalicIcon, UnderlineIcon, CaseSensitiveIcon, ListIcon, ListOrderedIcon, StrikethroughIcon, Heading1Icon, Heading2Icon, Heading3Icon, Heading4Icon, RemoveFormattingIcon, } from "lucide-react";
5
+ import DropDownMenu from "../../DropDownMenu";
6
+ import { FloatingPortal, useFloating } from "@floating-ui/react";
7
+ import { useEffect, useRef } from "react";
8
+ import Button from "../../Button";
9
+ const getMenuItems = (editor, customMenuItems) => {
10
+ var _a, _b, _c, _d, _e, _f, _g;
11
+ const node = (_c = (_b = (_a = editor === null || editor === void 0 ? void 0 : editor.state) === null || _a === void 0 ? void 0 : _a.selection) === null || _b === void 0 ? void 0 : _b.$from) === null || _c === void 0 ? void 0 : _c.parent;
12
+ const pos = (_e = (_d = editor === null || editor === void 0 ? void 0 : editor.state) === null || _d === void 0 ? void 0 : _d.selection) === null || _e === void 0 ? void 0 : _e.$from;
13
+ let withinUnorderedList = false;
14
+ let withinOrderedList = false;
15
+ // Traverse the node ancestors
16
+ pos.path.forEach((node) => {
17
+ var _a, _b;
18
+ if (((_a = node === null || node === void 0 ? void 0 : node.type) === null || _a === void 0 ? void 0 : _a.name) === "bulletList") {
19
+ withinUnorderedList = true;
20
+ }
21
+ else if (((_b = node === null || node === void 0 ? void 0 : node.type) === null || _b === void 0 ? void 0 : _b.name) === "orderedList") {
22
+ withinOrderedList = true;
23
+ }
24
+ });
25
+ const attrs = node === null || node === void 0 ? void 0 : node.attrs;
26
+ let nodeTypeLabel = "Select Type";
27
+ let nodeTypeIcon = null;
28
+ if (withinOrderedList) {
29
+ nodeTypeLabel = "Ordered List";
30
+ nodeTypeIcon = _jsx(ListOrderedIcon, { size: 16 });
31
+ }
32
+ else if (withinUnorderedList) {
33
+ nodeTypeLabel = "Bullet List";
34
+ nodeTypeIcon = _jsx(ListIcon, { size: 16 });
35
+ }
36
+ else if (((_f = node === null || node === void 0 ? void 0 : node.type) === null || _f === void 0 ? void 0 : _f.name) === "paragraph") {
37
+ nodeTypeLabel = "Text";
38
+ nodeTypeIcon = _jsx(CaseSensitiveIcon, { size: 16 });
39
+ }
40
+ else if (((_g = node === null || node === void 0 ? void 0 : node.type) === null || _g === void 0 ? void 0 : _g.name) === "heading") {
41
+ const level = attrs === null || attrs === void 0 ? void 0 : attrs.level;
42
+ nodeTypeLabel = `Heading ${level}`;
43
+ nodeTypeIcon =
44
+ level === 1 ? (_jsx(Heading1Icon, { size: 16 })) : level === 2 ? (_jsx(Heading2Icon, { size: 16 })) : level === 3 ? (_jsx(Heading3Icon, { size: 16 })) : (_jsx(Heading4Icon, { size: 16 }));
45
+ }
46
+ return [
47
+ ...customMenuItems,
48
+ {
49
+ name: "node_type",
50
+ label: nodeTypeLabel,
51
+ type: "menu",
52
+ arrow: true,
53
+ buttonProps: {
54
+ leftSection: nodeTypeIcon,
55
+ style: { fontSize: 11, padding: "4px" },
56
+ },
57
+ items: [
58
+ {
59
+ label: "Text",
60
+ value: "text",
61
+ leftSection: _jsx(CaseSensitiveIcon, { size: 16 }),
62
+ data: {
63
+ command: (editor) => {
64
+ // remove ordered list
65
+ editor
66
+ .chain()
67
+ .focus()
68
+ .liftListItem("listItem")
69
+ .setParagraph()
70
+ .run();
71
+ },
72
+ },
73
+ },
74
+ {
75
+ label: "Heading 1",
76
+ value: "heading_1",
77
+ leftSection: _jsx(Heading1Icon, { size: 16 }),
78
+ data: {
79
+ command: (editor) => {
80
+ editor.chain().focus().setHeading({ level: 1 }).run();
81
+ },
82
+ },
83
+ },
84
+ {
85
+ label: "Heading 2",
86
+ value: "heading_2",
87
+ leftSection: _jsx(Heading2Icon, { size: 16 }),
88
+ data: {
89
+ command: (editor) => {
90
+ editor.chain().focus().setHeading({ level: 2 }).run();
91
+ },
92
+ },
93
+ },
94
+ {
95
+ label: "Heading 3",
96
+ value: "heading_3",
97
+ leftSection: _jsx(Heading3Icon, { size: 16 }),
98
+ data: {
99
+ command: (editor) => {
100
+ editor.chain().focus().setHeading({ level: 3 }).run();
101
+ },
102
+ },
103
+ },
104
+ {
105
+ label: "Heading 4",
106
+ value: "heading_4",
107
+ leftSection: _jsx(Heading4Icon, { size: 16 }),
108
+ data: {
109
+ command: (editor) => {
110
+ editor.chain().focus().setHeading({ level: 4 }).run();
111
+ },
112
+ },
113
+ },
114
+ {
115
+ label: "Bullet List",
116
+ value: "bullet_list",
117
+ leftSection: _jsx(ListIcon, { size: 16 }),
118
+ data: {
119
+ command: (editor) => {
120
+ editor.chain().focus().toggleBulletList().run();
121
+ },
122
+ },
123
+ },
124
+ {
125
+ label: "Ordered List",
126
+ value: "ordered_list",
127
+ leftSection: _jsx(ListOrderedIcon, { size: 16 }),
128
+ data: {
129
+ command: (editor) => {
130
+ editor.chain().focus().toggleOrderedList().run();
131
+ },
132
+ },
133
+ },
134
+ ],
135
+ },
136
+ {
137
+ name: Extensions.Bold,
138
+ icon: BoldIcon,
139
+ type: "button",
140
+ isActive: (editor) => editor.isActive("bold"),
141
+ onClick: (editor) => {
142
+ editor.chain().focus().toggleBold().run();
143
+ },
144
+ },
145
+ {
146
+ name: Extensions.Italic,
147
+ icon: ItalicIcon,
148
+ type: "button",
149
+ isActive: (editor) => editor.isActive("italic"),
150
+ onClick: (editor) => {
151
+ editor.chain().focus().toggleItalic().run();
152
+ },
153
+ },
154
+ {
155
+ name: Extensions.Underline,
156
+ icon: UnderlineIcon,
157
+ type: "button",
158
+ isActive: (editor) => editor.isActive("underline"),
159
+ onClick: (editor) => {
160
+ editor.chain().focus().toggleUnderline().run();
161
+ },
162
+ },
163
+ {
164
+ name: Extensions.Strike,
165
+ icon: StrikethroughIcon,
166
+ type: "button",
167
+ isActive: (editor) => editor.isActive("strike"),
168
+ onClick: (editor) => {
169
+ editor.chain().focus().toggleStrike().run();
170
+ },
171
+ },
172
+ {
173
+ name: Extensions.ClearFormatting,
174
+ icon: RemoveFormattingIcon,
175
+ type: "button",
176
+ onClick: (editor) => {
177
+ editor.chain().focus().setParagraph().run();
178
+ editor.chain().focus().unsetAllMarks().run();
179
+ },
180
+ },
181
+ ];
182
+ };
183
+ const BubbleMenuContent = styled.div `
184
+ position: fixed;
185
+ display: flex;
186
+ justify-content: space-between;
187
+ align-items: center;
188
+ padding: 2px;
189
+ gap: 2px;
190
+
191
+ color: ${({ theme }) => theme.palette.text.primary};
192
+ background-color: ${({ theme }) => theme.palette.input.background};
193
+
194
+ border-radius: 5px;
195
+ border: 1px solid ${(props) => props.theme.palette.divider};
196
+
197
+ font-size: 0.8rem;
198
+
199
+ min-height: 30px;
200
+
201
+ opacity: 0;
202
+ transform: scale(0.25); /* Start at half size */
203
+ animation: fadeInEffect 90ms forwards;
204
+
205
+ /* Animation to handle the fade in */
206
+ @keyframes fadeInEffect {
207
+ 0% {
208
+ opacity: 0;
209
+ transform: scale(0.25);
210
+ }
211
+ 100% {
212
+ opacity: 1;
213
+ transform: scale(1);
214
+ }
215
+ }
216
+ `;
217
+ const BubbleItemButton = styled(Button) `
218
+ font-size: 0.5rem;
219
+ font-weight: 500;
220
+ padding: 4px;
221
+ height: auto;
222
+
223
+ &.is-active {
224
+ opacity: 1;
225
+ color: ${({ theme }) => theme.palette.primary.main};
226
+ }
227
+
228
+ &:hover {
229
+ background-color: ${({ theme }) => theme.palette.action.hover};
230
+ }
231
+ `;
232
+ const BubbleMenu = ({ editor, rect, open, onOpen, customMenuItems = [], }) => {
233
+ var _a;
234
+ const menuRef = useRef(null);
235
+ const { refs, elements } = useFloating();
236
+ useEffect(() => {
237
+ if (open && onOpen) {
238
+ onOpen(elements.floating);
239
+ }
240
+ }, [open, onOpen, elements.floating]);
241
+ const elementWidth = ((_a = elements.floating) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
242
+ const { from, to } = editor.state.selection;
243
+ const selectedText = editor.state.doc.textBetween(from, to);
244
+ let top = (rect === null || rect === void 0 ? void 0 : rect.top) ? rect.top - 50 : 0;
245
+ if (top < 10) {
246
+ top = 10; // add some padding
247
+ }
248
+ let left = (rect === null || rect === void 0 ? void 0 : rect.left) ? rect.left + rect.width / 2 - elementWidth / 2 : 0;
249
+ if (left < 10) {
250
+ left = 10; // add some padding
251
+ }
252
+ // Check if the menu is overflowing on the right
253
+ const rightOverflow = left + elementWidth - window.innerWidth;
254
+ if (rightOverflow > -10) {
255
+ left -= rightOverflow + 10; // add some padding
256
+ }
257
+ return (_jsx(FloatingPortal, { preserveTabOrder: true, children: open && (_jsx(BubbleMenuContent, { ref: (ref) => {
258
+ refs.setFloating(ref);
259
+ menuRef.current = ref;
260
+ }, style: {
261
+ top,
262
+ left,
263
+ }, children: getMenuItems(editor, customMenuItems).map((item) => {
264
+ var _a;
265
+ if (item.type === "button") {
266
+ const isActive = (_a = item.isActive) === null || _a === void 0 ? void 0 : _a.call(item, editor);
267
+ 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));
268
+ }
269
+ if (item.type === "menu") {
270
+ 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); } }, item.dropDownProps, { children: item.icon
271
+ ? (_jsx(item.icon, { size: 14 }))
272
+ : (item.label || item.name) }), item.name));
273
+ }
274
+ }) })) }));
275
+ };
276
+ export default BubbleMenu;
@@ -0,0 +1,6 @@
1
+ declare const SavingBadge: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<{
2
+ className?: string;
3
+ }, never>> & string & Omit<({ className }: {
4
+ className?: string;
5
+ }) => import("react/jsx-runtime").JSX.Element, keyof import("react").Component<any, {}, any>>;
6
+ export default SavingBadge;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styled from "styled-components";
3
+ const SavingBadge = styled(({ className }) => {
4
+ return (_jsx("div", { className: className, children: _jsx("div", { className: "saving-badge", children: _jsx("span", { children: "Saving..." }) }) }));
5
+ }) `
6
+ position: absolute;
7
+ top: 0px;
8
+ left: 10px;
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ font-size: 0.75rem;
13
+ font-weight: 500;
14
+ color: ${({ theme }) => theme.palette.text.secondary};
15
+ `;
16
+ export default SavingBadge;
@@ -1,6 +1,16 @@
1
1
  declare enum Extensions {
2
+ ClearFormatting = "clearFormatting",
2
3
  HorizontalRule = "horizontalRule",
3
4
  Underline = "underline",
5
+ Bold = "bold",
6
+ Italic = "italic",
7
+ Strike = "strike",
8
+ BulletList = "bulletList",
9
+ OrderedList = "orderedList",
10
+ AlignLeft = "alignLeft",
11
+ AlignCenter = "alignCenter",
12
+ AlignRight = "alignRight",
13
+ AlignJustify = "alignJustify",
4
14
  TextAlign = "textAlign",
5
15
  Table = "table",
6
16
  TableCell = "tableCell",
@@ -10,6 +20,7 @@ declare enum Extensions {
10
20
  Focus = "focus",
11
21
  Image = "image",
12
22
  CustomStorage = "storage",
13
- SlashCommand = "slashCommand"
23
+ SlashCommand = "slashCommand",
24
+ BubbleMenu = "bubbleMenu"
14
25
  }
15
26
  export default Extensions;
@@ -1,7 +1,17 @@
1
1
  var Extensions;
2
2
  (function (Extensions) {
3
+ Extensions["ClearFormatting"] = "clearFormatting";
3
4
  Extensions["HorizontalRule"] = "horizontalRule";
4
5
  Extensions["Underline"] = "underline";
6
+ Extensions["Bold"] = "bold";
7
+ Extensions["Italic"] = "italic";
8
+ Extensions["Strike"] = "strike";
9
+ Extensions["BulletList"] = "bulletList";
10
+ Extensions["OrderedList"] = "orderedList";
11
+ Extensions["AlignLeft"] = "alignLeft";
12
+ Extensions["AlignCenter"] = "alignCenter";
13
+ Extensions["AlignRight"] = "alignRight";
14
+ Extensions["AlignJustify"] = "alignJustify";
5
15
  Extensions["TextAlign"] = "textAlign";
6
16
  Extensions["Table"] = "table";
7
17
  Extensions["TableCell"] = "tableCell";
@@ -12,5 +22,6 @@ var Extensions;
12
22
  Extensions["Image"] = "image";
13
23
  Extensions["CustomStorage"] = "storage";
14
24
  Extensions["SlashCommand"] = "slashCommand";
25
+ Extensions["BubbleMenu"] = "bubbleMenu";
15
26
  })(Extensions || (Extensions = {}));
16
27
  export default Extensions;
@@ -0,0 +1,7 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { BubbleItem } from "../Components/BubbleMenu";
3
+ export type BubbleMenuOptions = {
4
+ customMenuItems?: BubbleItem[];
5
+ };
6
+ declare const BubbleMenu: Extension<any, any>;
7
+ export default BubbleMenu;
@@ -0,0 +1,157 @@
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;
@@ -5,5 +5,5 @@ interface SlashCommandListProps {
5
5
  editor: any;
6
6
  range: any;
7
7
  }
8
- declare const SlashCommandList: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<SlashCommandListProps, never>> & string & Omit<({ className, items, command, editor, range }: SlashCommandListProps) => import("react/jsx-runtime").JSX.Element | null, keyof import("react").Component<any, {}, any>>;
8
+ declare const SlashCommandList: ({ items, command, editor, range, }: SlashCommandListProps) => import("react/jsx-runtime").JSX.Element | null;
9
9
  export default SlashCommandList;
@@ -1,6 +1,69 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
3
3
  import styled from "styled-components";
4
+ const SlashCommandListContainer = styled.div `
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: 0.25rem;
8
+ z-index: 16001;
9
+ height: auto;
10
+ max-height: 330px;
11
+ width: 18rem;
12
+ overflow-y: auto;
13
+ border: 1px solid ${({ theme }) => theme.palette.divider};
14
+ border-radius: 0.375rem;
15
+ background-color: ${({ theme }) => theme.palette.background.default};
16
+ padding: 0.5rem;
17
+ transition: all 0.2s ease-in-out;
18
+ `;
19
+ const SlashCommandItem = styled.button `
20
+ display: flex;
21
+ width: 100%;
22
+ align-items: center;
23
+ justify-content: flex-start;
24
+ border-radius: 0.375rem;
25
+ padding: 0.25rem 0.5rem;
26
+ text-align: left;
27
+ font-size: 0.875rem;
28
+ color: ${({ theme }) => theme.palette.text.primary};
29
+ background-color: ${({ theme }) => theme.palette.background.default};
30
+ outline: none;
31
+ border: none;
32
+
33
+ &:hover {
34
+ background-color: ${({ theme }) => theme.palette.action.hover};
35
+ cursor: pointer;
36
+ }
37
+
38
+ &.selected {
39
+ background-color: ${({ theme }) => theme.palette.action.hover};
40
+ }
41
+ `;
42
+ const CommandIcon = styled.div `
43
+ display: flex;
44
+ width: 2.5rem;
45
+ height: 2.5rem;
46
+ align-items: center;
47
+ justify-content: center;
48
+ border-radius: 0.375rem;
49
+ border: 1px solid ${({ theme }) => theme.palette.divider};
50
+ margin-right: 0.5rem;
51
+ `;
52
+ const CommandTitle = styled.div `
53
+ font-weight: 500;
54
+ font-size: 0.8rem;
55
+ margin-bottom: 0.125rem;
56
+ text-overflow: ellipsis;
57
+ overflow: hidden;
58
+ white-space: nowrap;
59
+ `;
60
+ const CommandDescription = styled.div `
61
+ font-size: 0.75rem;
62
+ color: ${({ theme }) => theme.palette.text.secondary};
63
+ text-overflow: ellipsis;
64
+ overflow: hidden;
65
+ white-space: nowrap;
66
+ `;
4
67
  const updateScrollView = (container, item) => {
5
68
  const containerHeight = container.offsetHeight;
6
69
  const itemHeight = item ? item.offsetHeight : 0;
@@ -13,7 +76,7 @@ const updateScrollView = (container, item) => {
13
76
  container.scrollTop += bottom - containerHeight - container.scrollTop + 5;
14
77
  }
15
78
  };
16
- const SlashCommandList = styled(({ className, items, command, editor, range }) => {
79
+ const SlashCommandList = ({ items, command, editor, range, }) => {
17
80
  const [selectedIndex, setSelectedIndex] = useState(0);
18
81
  const selectItem = useCallback((index) => {
19
82
  const item = items[index];
@@ -63,76 +126,8 @@ const SlashCommandList = styled(({ className, items, command, editor, range }) =
63
126
  if (item && container)
64
127
  updateScrollView(container, item);
65
128
  }, [selectedIndex]);
66
- return items.length > 0 ? (_jsx("div", { id: "slash-command", ref: commandListContainer, className: className + " slash-command", children: items.map((item, index) => {
67
- return (_jsxs("button", { className: `slash-command-item ${index === selectedIndex ? "selected" : ""}`, onClick: () => selectItem(index), children: [_jsx("div", { className: "item-icon", children: _jsx(item.icon, { size: 18 }) }), _jsxs("div", { children: [_jsx("div", { className: "item-title", children: item.title }), _jsx("div", { className: "item-description", children: item.description })] })] }, index));
129
+ return items.length > 0 ? (_jsx(SlashCommandListContainer, { ref: commandListContainer, className: "slash-command", children: items.map((item, index) => {
130
+ return (_jsxs(SlashCommandItem, { className: `slash-command-item ${index === selectedIndex ? "selected" : ""}`, onClick: () => selectItem(index), children: [_jsx(CommandIcon, { className: "item-icon", children: _jsx(item.icon, { size: 18 }) }), _jsxs("div", { children: [_jsx(CommandTitle, { className: "item-title", children: item.title }), _jsx(CommandDescription, { className: "item-description", children: item.description })] })] }, index));
68
131
  }) })) : null;
69
- }) `
70
- &.slash-command {
71
- display: flex;
72
- flex-direction: column;
73
- gap: 0.25rem;
74
- z-index: 16001;
75
- height: auto;
76
- max-height: 330px;
77
- width: 18rem;
78
- overflow-y: auto;
79
- border: 1px solid ${({ theme }) => theme.palette.divider};
80
- border-radius: 0.375rem;
81
- background-color: ${({ theme }) => theme.palette.background.default};
82
- padding: 0.5rem;
83
- transition: all 0.2s ease-in-out;
84
- }
85
-
86
- .slash-command-item {
87
- display: flex;
88
- width: 100%;
89
- align-items: center;
90
- justify-content: flex-start;
91
- border-radius: 0.375rem;
92
- padding: 0.25rem 0.5rem;
93
- text-align: left;
94
- font-size: 0.875rem;
95
- color: ${({ theme }) => theme.palette.text.primary};
96
- background-color: ${({ theme }) => theme.palette.background.default};
97
- outline: none;
98
- border: none;
99
-
100
- &:hover {
101
- background-color: ${({ theme }) => theme.palette.action.hover};
102
- cursor: pointer;
103
- }
104
- }
105
-
106
- .slash-command-item.selected {
107
- background-color: ${({ theme }) => theme.palette.action.hover};
108
- }
109
-
110
- .item-icon {
111
- display: flex;
112
- width: 2.5rem;
113
- height: 2.5rem;
114
- align-items: center;
115
- justify-content: center;
116
- border-radius: 0.375rem;
117
- border: 1px solid ${({ theme }) => theme.palette.divider};
118
- margin-right: 0.5rem;
119
- }
120
-
121
- .item-title {
122
- font-weight: 600;
123
- font-size: 0.8rem;
124
- margin-bottom: 0.125rem;
125
- text-overflow: ellipsis;
126
- overflow: hidden;
127
- white-space: nowrap;
128
- }
129
-
130
- .item-description {
131
- font-size: 0.75rem;
132
- color: ${({ theme }) => theme.palette.text.secondary};
133
- text-overflow: ellipsis;
134
- overflow: hidden;
135
- white-space: nowrap;
136
- }
137
- `;
132
+ };
138
133
  export default SlashCommandList;
@@ -18,55 +18,6 @@ import SlashCommandList from "./SlashCommandList";
18
18
  import SlashCommands from "../Enums/SlashCommands";
19
19
  import moment from "moment/moment.js";
20
20
  const SlashCommandPluginKey = new PluginKey("slash-command");
21
- const Command = Extension.create({
22
- name: "slash-command",
23
- priority: 1000,
24
- addKeyboardShortcuts() {
25
- return {
26
- Enter: ({ editor }) => {
27
- // Prevent Enter key when slash command is active
28
- const slashCommandState = SlashCommandPluginKey.getState(editor.state);
29
- if (slashCommandState && slashCommandState.active) {
30
- return true; // Returning true prevents default behavior.
31
- }
32
- return false; // Returning false allows default behavior.
33
- },
34
- ArrowUp: ({ editor }) => {
35
- // Prevent ArrowUp key when slash command is active
36
- const slashCommandState = SlashCommandPluginKey.getState(editor.state);
37
- if (slashCommandState && slashCommandState.active) {
38
- return true; // Returning true prevents default behavior.
39
- }
40
- return false; // Returning false allows default behavior.
41
- },
42
- ArrowDown: ({ editor }) => {
43
- // Prevent ArrowDown key when slash command is active
44
- const slashCommandState = SlashCommandPluginKey.getState(editor.state);
45
- if (slashCommandState && slashCommandState.active) {
46
- return true; // Returning true prevents default behavior.
47
- }
48
- return false; // Returning false allows default behavior.
49
- },
50
- // Add more keys here as needed
51
- };
52
- },
53
- addOptions() {
54
- return {
55
- suggestion: {
56
- char: "/",
57
- pluginKey: SlashCommandPluginKey,
58
- command: ({ editor, range, props }) => {
59
- props.command({ editor, range });
60
- },
61
- },
62
- };
63
- },
64
- addProseMirrorPlugins() {
65
- return [
66
- Suggestion(Object.assign({ editor: this.editor }, this.options.suggestion)),
67
- ];
68
- },
69
- });
70
21
  const getCommandItems = (values, options) => {
71
22
  var _a;
72
23
  if ((_a = options === null || options === void 0 ? void 0 : options.commands) === null || _a === void 0 ? void 0 : _a.includes(SlashCommands.Image)) {
@@ -285,8 +236,57 @@ const renderItems = () => {
285
236
  },
286
237
  };
287
238
  };
239
+ const SlashCommand = Extension.create({
240
+ name: "slash-command",
241
+ priority: 1000,
242
+ addKeyboardShortcuts() {
243
+ return {
244
+ Enter: ({ editor }) => {
245
+ // Prevent Enter key when slash command is active
246
+ const slashCommandState = SlashCommandPluginKey.getState(editor.state);
247
+ if (slashCommandState && slashCommandState.active) {
248
+ return true; // Returning true prevents default behavior.
249
+ }
250
+ return false; // Returning false allows default behavior.
251
+ },
252
+ ArrowUp: ({ editor }) => {
253
+ // Prevent ArrowUp key when slash command is active
254
+ const slashCommandState = SlashCommandPluginKey.getState(editor.state);
255
+ if (slashCommandState && slashCommandState.active) {
256
+ return true; // Returning true prevents default behavior.
257
+ }
258
+ return false; // Returning false allows default behavior.
259
+ },
260
+ ArrowDown: ({ editor }) => {
261
+ // Prevent ArrowDown key when slash command is active
262
+ const slashCommandState = SlashCommandPluginKey.getState(editor.state);
263
+ if (slashCommandState && slashCommandState.active) {
264
+ return true; // Returning true prevents default behavior.
265
+ }
266
+ return false; // Returning false allows default behavior.
267
+ },
268
+ // Add more keys here as needed
269
+ };
270
+ },
271
+ addOptions() {
272
+ return {
273
+ suggestion: {
274
+ char: "/",
275
+ pluginKey: SlashCommandPluginKey,
276
+ command: ({ editor, range, props }) => {
277
+ props.command({ editor, range });
278
+ },
279
+ },
280
+ };
281
+ },
282
+ addProseMirrorPlugins() {
283
+ return [
284
+ Suggestion(Object.assign({ editor: this.editor }, this.options.suggestion)),
285
+ ];
286
+ },
287
+ });
288
288
  const getSlashCommand = (options) => {
289
- return Command.configure({
289
+ return SlashCommand.configure({
290
290
  suggestion: {
291
291
  items: (e) => getCommandItems(e, options),
292
292
  render: renderItems,
@@ -1,10 +1,12 @@
1
1
  import { Extension } from "@tiptap/core";
2
2
  import { HandleImageUpload } from "../Plugins/UploadImagesPlugin";
3
3
  import Extensions from "../Enums/Extensions";
4
+ import { BubbleMenuOptions } from "./BubbleMenuExtension";
4
5
  export type ExtensionType = (typeof Extensions)[keyof typeof Extensions];
5
6
  interface GetTipTapExtensionsProps {
6
7
  extensions?: ExtensionType[];
7
8
  slashCommands?: any[];
9
+ bubbleMenuOptions?: BubbleMenuOptions;
8
10
  handleImageUpload?: HandleImageUpload;
9
11
  }
10
12
  declare const getTipTapExtensions: (props: GetTipTapExtensionsProps) => Extension[];
@@ -13,6 +13,7 @@ import Focus from "@tiptap/extension-focus";
13
13
  import UploadImagesPlugin from "../Plugins/UploadImagesPlugin";
14
14
  import getSlashCommand from "./getSlashCommand";
15
15
  import Extensions from "../Enums/Extensions";
16
+ import BubbleMenu from "./BubbleMenuExtension";
16
17
  const CustomImage = TiptapImage.extend({
17
18
  // Add data-uuid attribute to image
18
19
  addAttributes() {
@@ -42,7 +43,7 @@ const CustomStorage = Extension.create({
42
43
  };
43
44
  },
44
45
  });
45
- const getTipTapExtensions = ({ extensions = [], slashCommands = [], handleImageUpload, }) => {
46
+ const getTipTapExtensions = ({ extensions = [], slashCommands = [], bubbleMenuOptions, handleImageUpload, }) => {
46
47
  return [
47
48
  {
48
49
  name: "starterKit",
@@ -138,20 +139,16 @@ const getTipTapExtensions = ({ extensions = [], slashCommands = [], handleImageU
138
139
  name: Extensions.Placeholder,
139
140
  extension: Placeholder.configure({
140
141
  placeholder: ({ node }) => {
141
- // check if node type is tablcell
142
- // if it is, return empty string
143
- // table cells always contain a paragraph node, so no need to add placeholder to it
144
- // other it shows two placeholders wihtin the cell
145
- if (node.type.name === "tableCell") {
146
- return "";
147
- }
148
- if (!extensions.includes(Extensions.SlashCommand)) {
149
- return "Enter text...";
150
- }
142
+ const text = !extensions.includes(Extensions.SlashCommand)
143
+ ? "Enter text..."
144
+ : "Press '/' for commands...";
151
145
  if (node.type.name === "heading") {
152
146
  return `Heading ${node.attrs.level}`;
153
147
  }
154
- return "Press '/' for commands...";
148
+ if (node.type.name === "paragraph") {
149
+ return text;
150
+ }
151
+ return "";
155
152
  },
156
153
  includeChildren: true,
157
154
  }),
@@ -161,6 +158,11 @@ const getTipTapExtensions = ({ extensions = [], slashCommands = [], handleImageU
161
158
  category: "default",
162
159
  extension: CustomStorage,
163
160
  },
161
+ {
162
+ name: Extensions.BubbleMenu,
163
+ category: "default",
164
+ extension: BubbleMenu.configure(bubbleMenuOptions),
165
+ },
164
166
  {
165
167
  name: Extensions.SlashCommand,
166
168
  extension: getSlashCommand({
@@ -1,6 +1,7 @@
1
1
  import { Editor } from "@tiptap/react";
2
2
  import { ExtensionType } from "./Extensions/getTiptapExtensions";
3
3
  import { HandleImageUpload } from "./Plugins/UploadImagesPlugin";
4
+ import { BubbleMenuOptions } from "./Extensions/BubbleMenuExtension";
4
5
  interface RichTextEditorProps {
5
6
  className?: string;
6
7
  editorInstanceRef?: React.RefObject<Editor>;
@@ -14,6 +15,7 @@ interface RichTextEditorProps {
14
15
  saving?: boolean;
15
16
  onChange?: (value: string) => void;
16
17
  handleImageUpload?: HandleImageUpload;
18
+ bubbleMenuOptions?: BubbleMenuOptions;
17
19
  style?: React.CSSProperties;
18
20
  }
19
21
  declare const RichTextEditor: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<Omit<RichTextEditorProps & import("react").RefAttributes<unknown>, "ref"> & {
@@ -5,7 +5,8 @@ import { EditorContent, useEditor } from "@tiptap/react";
5
5
  import Toolbar from "./Toolbar";
6
6
  import getTipTapExtensions from "./Extensions/getTiptapExtensions";
7
7
  import Extensions from "./Enums/Extensions";
8
- const RichTextEditor = styled(forwardRef(({ className, editorInstanceRef, defaultValue = "", readOnly = false, font, showToolbar = true, saving = false, extensions = [], slashCommands = [], onChange, handleImageUpload, style, }, ref) => {
8
+ import SaveBadge from "./Components/SaveBadge";
9
+ const RichTextEditor = styled(forwardRef(({ className, editorInstanceRef, defaultValue = "", readOnly = false, font, showToolbar = true, saving = false, extensions = [], slashCommands = [], bubbleMenuOptions, onChange, handleImageUpload, style, }, ref) => {
9
10
  // check if image extension is included
10
11
  if (extensions === null || extensions === void 0 ? void 0 : extensions.includes(Extensions.Image)) {
11
12
  // Ensure that handleImageUpload is provided
@@ -19,6 +20,7 @@ const RichTextEditor = styled(forwardRef(({ className, editorInstanceRef, defaul
19
20
  extensions: getTipTapExtensions({
20
21
  extensions,
21
22
  slashCommands,
23
+ bubbleMenuOptions,
22
24
  handleImageUpload,
23
25
  }),
24
26
  onUpdate: ({ editor }) => {
@@ -31,7 +33,7 @@ const RichTextEditor = styled(forwardRef(({ className, editorInstanceRef, defaul
31
33
  _ref.current = editor;
32
34
  }
33
35
  }, [editor]);
34
- return (_jsxs("div", { className: className, children: [showToolbar && _jsx(Toolbar, { editor: editor }), _jsx(EditorContent, { className: "editor-content", editor: editor, "data-font": font || null, style: style })] }));
36
+ return (_jsxs("div", { className: className, children: [showToolbar && _jsx(Toolbar, { editor: editor }), saving && _jsx(SaveBadge, {}), _jsx(EditorContent, { className: "editor-content", editor: editor, "data-font": font || null, style: style })] }));
35
37
  })) `
36
38
  position: relative;
37
39
  display: flex;
@@ -41,6 +43,10 @@ const RichTextEditor = styled(forwardRef(({ className, editorInstanceRef, defaul
41
43
  justify-content: flex-start;
42
44
  align-items: center;
43
45
 
46
+ ::selection {
47
+ background-color: #705324;
48
+ }
49
+
44
50
  .editor-content {
45
51
  height: ${({ height }) => height || "100%"};
46
52
  width: 100%;
@@ -166,7 +166,7 @@ const SelectBox = styled(({ className, data = [], placeholder = "Select...", arr
166
166
  .concat(customItems) // Add custom items to the list
167
167
  .filter((item) => {
168
168
  const itemValue = (isObjectArray ? item.label : item);
169
- return itemValue.toLowerCase().includes(searchValue.toLowerCase());
169
+ return itemValue === null || itemValue === void 0 ? void 0 : itemValue.toLowerCase().includes(searchValue.toLowerCase());
170
170
  })
171
171
  .sort((a, b) => {
172
172
  if (grouped) {
@@ -219,9 +219,9 @@ const SelectBox = styled(({ className, data = [], placeholder = "Select...", arr
219
219
  };
220
220
  const handleClear = (e) => {
221
221
  e.preventDefault();
222
- e.stopPropagation();
223
222
  if (inputRef.current)
224
223
  inputRef.current.value = "";
224
+ setIsOpen(false);
225
225
  handleChangeSelection(null);
226
226
  setSelected(null);
227
227
  setSearchValue("");
@@ -434,7 +434,10 @@ const SelectBox = styled(({ className, data = [], placeholder = "Select...", arr
434
434
  setDropDownHeight(bottomHeight);
435
435
  };
436
436
  }, [topHeight, bottomHeight, isOpen]);
437
- return (_jsxs("div", { className: className, children: [label && (_jsx(FieldLabel, { error: error, asterisk: required, size: size, description: description, children: label })), _jsxs(StyledInputContainer, { ref: refs.setReference, onMouseDown: () => setIsOpen(true), width: width, onKeyDown: handleKeyDown, "data-open": isOpen, children: [_jsx(Input, { ref: inputRef, onChange: debouncedHandleOnChange, onFocus: handleFocus, autoFocus: focused, placeholder: placeholder, size: size, readOnly: !searchable, "data-button-right": arrow || clearable }), clearable && (selected || !!((_h = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _h === void 0 ? void 0 : _h.value)) ? (_jsx(ClearButton, { className: "input-btn", onClick: handleClear })) : arrow ? (_jsx(ArrowButton, { onClick: (e) => {
437
+ return (_jsxs("div", { className: className, children: [label && (_jsx(FieldLabel, { error: error, asterisk: required, size: size, description: description, children: label })), _jsxs(StyledInputContainer, { ref: refs.setReference, onMouseDown: () => setIsOpen(true), width: width, onKeyDown: handleKeyDown, "data-open": isOpen, children: [_jsx(Input, { ref: inputRef, onChange: debouncedHandleOnChange, onFocus: handleFocus, autoFocus: focused, placeholder: placeholder, size: size, readOnly: !searchable, "data-button-right": arrow || clearable }), clearable && (selected || !!((_h = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _h === void 0 ? void 0 : _h.value)) ? (_jsx(ClearButton, { className: "input-btn", onClick: handleClear, onMouseDown: (e) => {
438
+ e.preventDefault();
439
+ e.stopPropagation();
440
+ } })) : arrow ? (_jsx(ArrowButton, { onClick: (e) => {
438
441
  e.preventDefault();
439
442
  // e.stopPropagation();
440
443
  }, onMouseDown: (e) => {
@@ -359,7 +359,6 @@ const TagBox = styled(({ className, data = [], placeholder = "Select tags", arro
359
359
  };
360
360
  const handleClear = (e) => {
361
361
  e.preventDefault();
362
- e.stopPropagation();
363
362
  if (inputRef === null || inputRef === void 0 ? void 0 : inputRef.current)
364
363
  inputRef.current.value = "";
365
364
  onChange === null || onChange === void 0 ? void 0 : onChange([]);
@@ -551,7 +550,10 @@ const TagBox = styled(({ className, data = [], placeholder = "Select tags", arro
551
550
  }
552
551
  update();
553
552
  }, placeholder: placeholder, size: size, readOnly: !(searchable || allowCustomValue) }))] }) }), clearable &&
554
- (selectedItems.length > 0 || !!((_f = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _f === void 0 ? void 0 : _f.value)) ? (_jsx(ClearButton, { onClick: handleClear })) : arrow ? (_jsx(ArrowButton, { onClick: (e) => {
553
+ (selectedItems.length > 0 || !!((_f = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _f === void 0 ? void 0 : _f.value)) ? (_jsx(ClearButton, { onClick: handleClear, onMouseDown: (e) => {
554
+ e.preventDefault();
555
+ e.stopPropagation();
556
+ } })) : arrow ? (_jsx(ArrowButton, { onClick: (e) => {
555
557
  e.preventDefault();
556
558
  }, onMouseDown: (e) => {
557
559
  e.preventDefault();
package/dist/index.d.ts CHANGED
@@ -3,8 +3,10 @@ export { default as Grid } from "./Grid";
3
3
  export { default as TextInput } from "./TextInput";
4
4
  export { default as SelectBox } from "./SelectBox";
5
5
  export { default as Button } from "./Button";
6
+ export type { ButtonProps } from "./Button";
6
7
  export { default as IconButton } from "./IconButton";
7
8
  export { default as DropDownMenu } from "./DropDownMenu";
9
+ export type { DropDownItem } from "./DropDownMenu";
8
10
  export { default as DateInput } from "./DateInput";
9
11
  export { default as TextArea } from "./TextArea";
10
12
  export { default as TextAreaInput } from "./TextAreaInput";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monolith-forensics/monolith-ui",
3
- "version": "1.1.47",
3
+ "version": "1.1.49",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "author": "Matt Danner (Monolith Forensics LLC)",
@@ -44,6 +44,7 @@
44
44
  "moment": "^2.29.1",
45
45
  "overlayscrollbars": "^2.6.0",
46
46
  "overlayscrollbars-react": "^0.5.6",
47
+ "react-dom": "^18.3.1",
47
48
  "react-dropzone": "^14.2.3",
48
49
  "react-icons": "^5.2.1",
49
50
  "react-textarea-autosize": "^8.5.3",
@@ -60,6 +61,7 @@
60
61
  },
61
62
  "devDependencies": {
62
63
  "@types/overlayscrollbars": "^1.12.5",
64
+ "@types/react-dom": "^18.3.0",
63
65
  "@types/react-window": "^1.8.8",
64
66
  "@types/spark-md5": "^3.0.4",
65
67
  "typescript": "^5.5.4"