@monolith-forensics/monolith-ui 1.9.1-dev.0 → 1.9.1-dev.10

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 (46) hide show
  1. package/dist/DropDownMenu/components/MenuItemList.js +32 -12
  2. package/dist/DropDownMenu/components/StyledInnerItemContainer.js +1 -0
  3. package/dist/RichTextEditor/Components/BubbleMenu.d.ts +8 -8
  4. package/dist/RichTextEditor/Components/BubbleMenu.js +198 -93
  5. package/dist/RichTextEditor/Components/CodeBlockCopyButton.d.ts +9 -0
  6. package/dist/RichTextEditor/Components/CodeBlockCopyButton.js +45 -0
  7. package/dist/RichTextEditor/Components/CodeBlockFormatButton.d.ts +10 -0
  8. package/dist/RichTextEditor/Components/CodeBlockFormatButton.js +60 -0
  9. package/dist/RichTextEditor/Components/CodeBlockLanguageSelect.d.ts +6 -0
  10. package/dist/RichTextEditor/Components/CodeBlockLanguageSelect.js +21 -0
  11. package/dist/RichTextEditor/Components/CodeBlockNodeView.d.ts +3 -0
  12. package/dist/RichTextEditor/Components/CodeBlockNodeView.js +27 -0
  13. package/dist/RichTextEditor/Components/CodeBlockWrapButton.d.ts +10 -0
  14. package/dist/RichTextEditor/Components/CodeBlockWrapButton.js +17 -0
  15. package/dist/RichTextEditor/Components/LinkEditor.d.ts +8 -0
  16. package/dist/RichTextEditor/Components/LinkEditor.js +94 -0
  17. package/dist/RichTextEditor/Enums/Controls.d.ts +5 -1
  18. package/dist/RichTextEditor/Enums/Controls.js +4 -0
  19. package/dist/RichTextEditor/Enums/Extensions.d.ts +4 -0
  20. package/dist/RichTextEditor/Enums/Extensions.js +4 -0
  21. package/dist/RichTextEditor/Enums/HighlightColors.d.ts +9 -0
  22. package/dist/RichTextEditor/Enums/HighlightColors.js +10 -0
  23. package/dist/RichTextEditor/Enums/SlashCommands.d.ts +1 -0
  24. package/dist/RichTextEditor/Enums/SlashCommands.js +1 -0
  25. package/dist/RichTextEditor/Extensions/getSlashCommand.js +16 -1
  26. package/dist/RichTextEditor/Extensions/getTiptapExtensions.d.ts +10 -2
  27. package/dist/RichTextEditor/Extensions/getTiptapExtensions.js +158 -31
  28. package/dist/RichTextEditor/Plugins/ImageActionsPlugin.js +3 -73
  29. package/dist/RichTextEditor/Plugins/UploadImagesPlugin.js +1 -0
  30. package/dist/RichTextEditor/RichTextEditor.d.ts +5 -2
  31. package/dist/RichTextEditor/RichTextEditor.js +186 -13
  32. package/dist/RichTextEditor/Toolbar/Control.d.ts +6 -2
  33. package/dist/RichTextEditor/Toolbar/Control.js +13 -6
  34. package/dist/RichTextEditor/Toolbar/Controls.d.ts +2 -0
  35. package/dist/RichTextEditor/Toolbar/Controls.js +14 -0
  36. package/dist/RichTextEditor/Toolbar/ControlsGroup.js +1 -0
  37. package/dist/RichTextEditor/Toolbar/Toolbar.js +62 -9
  38. package/dist/RichTextEditor/Utils/codeBlockUtils.d.ts +20 -0
  39. package/dist/RichTextEditor/Utils/codeBlockUtils.js +137 -0
  40. package/dist/RichTextEditor/Utils/codeUtils.d.ts +3 -0
  41. package/dist/RichTextEditor/Utils/codeUtils.js +12 -0
  42. package/dist/RichTextEditor/Utils/linkUtils.d.ts +19 -0
  43. package/dist/RichTextEditor/Utils/linkUtils.js +57 -0
  44. package/package.json +8 -1
  45. package/dist/RichTextEditor/Extensions/BubbleMenuExtension.d.ts +0 -7
  46. package/dist/RichTextEditor/Extensions/BubbleMenuExtension.js +0 -157
@@ -56,11 +56,18 @@ const buildGroupedDisplayList = (items) => {
56
56
  const ListViewPort = styled.div.attrs({ className: "ListViewPort" }) `
57
57
  display: flex;
58
58
  flex-direction: column;
59
+ flex: 1 1 auto;
60
+ min-height: 0;
59
61
  width: 100%;
60
62
  `;
63
+ const MenuItemListRoot = styled.div `
64
+ display: flex;
65
+ flex-direction: column;
66
+ height: 100%;
67
+ min-height: 0;
68
+ `;
61
69
  const ListScroller = styled.div `
62
70
  scrollbar-color: ${({ theme }) => `${theme.scrollbar.thumb} ${theme.scrollbar.track}`};
63
- scrollbar-gutter: stable;
64
71
  scrollbar-width: thin;
65
72
 
66
73
  &::-webkit-scrollbar {
@@ -234,16 +241,6 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
234
241
  list.addEventListener("scroll", handleOnScroll);
235
242
  return () => list.removeEventListener("scroll", handleOnScroll);
236
243
  }, [dynamicOptionHeight, handleOnScroll, isLoading, renderOption]);
237
- useLayoutEffect(() => {
238
- var _a;
239
- if (targetElm.current) {
240
- const viewPortDimensions = (_a = targetElm.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
241
- setViewPortDimensions({
242
- width: viewPortDimensions.width,
243
- height: viewPortDimensions.height,
244
- });
245
- }
246
- }, [targetElm.current, isLoading]);
247
244
  const overscanCount = 10;
248
245
  const sizeTokens = getControlSizeTokens(size || "sm");
249
246
  const itemHeight = sizeTokens.menuRowHeight;
@@ -267,6 +264,29 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
267
264
  ? estimatedHeight
268
265
  : 200;
269
266
  const width = "100%";
267
+ useLayoutEffect(() => {
268
+ const target = targetElm.current;
269
+ if (!target)
270
+ return;
271
+ const updateDimensions = () => {
272
+ const nextDimensions = target.getBoundingClientRect();
273
+ setViewPortDimensions((currentDimensions) => {
274
+ const width = Math.ceil(nextDimensions.width);
275
+ const height = Math.floor(nextDimensions.height);
276
+ if (currentDimensions.width === width &&
277
+ currentDimensions.height === height) {
278
+ return currentDimensions;
279
+ }
280
+ return { width, height };
281
+ });
282
+ };
283
+ updateDimensions();
284
+ if (typeof ResizeObserver === "undefined")
285
+ return;
286
+ const observer = new ResizeObserver(updateDimensions);
287
+ observer.observe(target);
288
+ return () => observer.disconnect();
289
+ }, [displayItemsKey, isLoading]);
270
290
  const setItemSize = useCallback((index, measuredSize) => {
271
291
  var _a;
272
292
  if (itemSizeMap.current[index] === measuredSize)
@@ -335,7 +355,7 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
335
355
  (item === null || item === void 0 ? void 0 : item.label) ||
336
356
  (typeof item === "string" || typeof item === "number" ? item : null) }, index));
337
357
  };
338
- return (_jsxs("div", { children: [searchable && (_jsx(SearchInput, { variant: "outlined", size: size, placeholder: "Search", defaultValue: searchValue, onChange: (e) => {
358
+ return (_jsxs(MenuItemListRoot, { children: [searchable && (_jsx(SearchInput, { variant: "outlined", size: size, placeholder: "Search", defaultValue: searchValue, onChange: (e) => {
339
359
  if (!manualSearch) {
340
360
  handleSearch(e);
341
361
  }
@@ -5,5 +5,6 @@ export const StyledInnerItemContainer = styled.div.attrs({
5
5
  display: flex;
6
6
  flex-direction: column;
7
7
  flex: 1 1 auto;
8
+ min-height: 0;
8
9
  overflow: hidden;
9
10
  `;
@@ -1,14 +1,11 @@
1
1
  import { Extensions } from "../Enums";
2
2
  import { DropDownItem, DropDownMenuProps } from "../../DropDownMenu";
3
- import { ReactElement } from "react";
3
+ import { ReactElement, ReactNode } from "react";
4
4
  import { ButtonProps } from "../../Button";
5
5
  import { Editor } from "@tiptap/react";
6
- export interface BubbleMenuProps {
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 {
@@ -20,7 +17,7 @@ export type BubbleItem = {
20
17
  name: Extensions | string;
21
18
  icon?: React.FC<any>;
22
19
  type: "menu";
23
- label?: string | Element;
20
+ label?: ReactNode;
24
21
  items: BubbleMenuDropDownItem[];
25
22
  arrow?: boolean;
26
23
  isActive?: (editor: Editor) => boolean;
@@ -32,12 +29,15 @@ export type BubbleItem = {
32
29
  name: Extensions | string;
33
30
  icon?: React.FC<any>;
34
31
  type: "button";
35
- label?: string | Element;
32
+ label?: ReactNode;
36
33
  arrow?: boolean;
37
34
  isActive?: (editor: Editor) => boolean;
38
35
  buttonRender?: (props: any) => ReactElement;
39
36
  buttonProps?: Partial<ButtonProps>;
40
37
  onClick?: (editor: Editor) => void;
41
38
  };
42
- declare const BubbleMenu: React.FC<BubbleMenuProps>;
39
+ export type BubbleMenuOptions = {
40
+ customMenuItems?: BubbleItem[];
41
+ };
42
+ declare const BubbleMenu: React.FC<BubbleMenuContentProps>;
43
43
  export default BubbleMenu;
@@ -1,14 +1,21 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import styled, { useTheme } from "styled-components";
3
3
  import { Extensions } from "../Enums";
4
- import { BoldIcon, ItalicIcon, UnderlineIcon, CaseSensitiveIcon, ListIcon, ListOrderedIcon, StrikethroughIcon, Heading1Icon, Heading2Icon, Heading3Icon, Heading4Icon, RemoveFormattingIcon, SquircleIcon, } from "lucide-react";
4
+ import { BoldIcon, CodeIcon, ItalicIcon, UnderlineIcon, CaseSensitiveIcon, ListIcon, ListOrderedIcon, SquareCodeIcon, StrikethroughIcon, Heading1Icon, Heading2Icon, Heading3Icon, Heading4Icon, HighlighterIcon, LinkIcon, PaletteIcon, 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";
6
+ import { useEffect, useState } from "react";
8
7
  import { Button } from "../../Button";
9
8
  import TextColors from "../Enums/TextColors";
10
- const getMenuItems = (editor, customMenuItems, theme) => {
11
- var _a, _b, _c, _d, _e, _f, _g;
9
+ import HighlightColors from "../Enums/HighlightColors";
10
+ import LinkEditor from "./LinkEditor";
11
+ import { hasInlineCode, toggleInlineCode } from "../Utils/codeUtils";
12
+ import CodeBlockLanguageSelect from "./CodeBlockLanguageSelect";
13
+ import CodeBlockCopyButton from "./CodeBlockCopyButton";
14
+ import CodeBlockWrapButton from "./CodeBlockWrapButton";
15
+ import CodeBlockFormatButton from "./CodeBlockFormatButton";
16
+ import { hasSyntaxHighlightedCodeBlock, toggleCodeBlock, } from "../Utils/codeBlockUtils";
17
+ const getMenuItems = (editor, customMenuItems, theme, openLinkEditor) => {
18
+ var _a, _b, _c, _d, _e, _f, _g, _h;
12
19
  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;
13
20
  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;
14
21
  let withinUnorderedList = false;
@@ -24,6 +31,11 @@ const getMenuItems = (editor, customMenuItems, theme) => {
24
31
  }
25
32
  });
26
33
  const attrs = node === null || node === void 0 ? void 0 : node.attrs;
34
+ const supportsSyntaxCodeBlock = hasSyntaxHighlightedCodeBlock(editor);
35
+ const supportsInlineCode = hasInlineCode(editor);
36
+ const supportsColor = Boolean(editor.extensionManager.extensions.find((extension) => extension.name === "color"));
37
+ const supportsHighlight = Boolean(editor.extensionManager.extensions.find((extension) => extension.name === "highlight"));
38
+ const supportsLink = Boolean(editor.extensionManager.extensions.find((extension) => extension.name === "link"));
27
39
  let nodeTypeLabel = "Select Type";
28
40
  let nodeTypeIcon = null;
29
41
  if (withinOrderedList) {
@@ -38,7 +50,11 @@ const getMenuItems = (editor, customMenuItems, theme) => {
38
50
  nodeTypeLabel = "Text";
39
51
  nodeTypeIcon = _jsx(CaseSensitiveIcon, { size: 16 });
40
52
  }
41
- else if (((_g = node === null || node === void 0 ? void 0 : node.type) === null || _g === void 0 ? void 0 : _g.name) === "heading") {
53
+ else if (((_g = node === null || node === void 0 ? void 0 : node.type) === null || _g === void 0 ? void 0 : _g.name) === "codeBlock") {
54
+ nodeTypeLabel = "Code Block";
55
+ nodeTypeIcon = _jsx(SquareCodeIcon, { size: 16 });
56
+ }
57
+ else if (((_h = node === null || node === void 0 ? void 0 : node.type) === null || _h === void 0 ? void 0 : _h.name) === "heading") {
42
58
  const level = attrs === null || attrs === void 0 ? void 0 : attrs.level;
43
59
  nodeTypeLabel = `Heading ${level}`;
44
60
  nodeTypeIcon =
@@ -50,7 +66,6 @@ const getMenuItems = (editor, customMenuItems, theme) => {
50
66
  name: "node_type",
51
67
  label: nodeTypeLabel,
52
68
  type: "menu",
53
- arrow: true,
54
69
  buttonProps: {
55
70
  leftSection: nodeTypeIcon,
56
71
  style: { fontSize: 11, padding: "4px" },
@@ -62,13 +77,16 @@ const getMenuItems = (editor, customMenuItems, theme) => {
62
77
  data: {
63
78
  Icon: CaseSensitiveIcon,
64
79
  command: (editor) => {
65
- // remove ordered list
66
- editor
67
- .chain()
68
- .focus()
69
- .liftListItem("listItem")
70
- .setParagraph()
71
- .run();
80
+ if (editor.isActive("bulletList") || editor.isActive("orderedList")) {
81
+ editor
82
+ .chain()
83
+ .focus()
84
+ .liftListItem("listItem")
85
+ .setParagraph()
86
+ .run();
87
+ return;
88
+ }
89
+ editor.chain().focus().setParagraph().run();
72
90
  },
73
91
  },
74
92
  },
@@ -132,50 +150,120 @@ const getMenuItems = (editor, customMenuItems, theme) => {
132
150
  },
133
151
  },
134
152
  },
153
+ ...(supportsSyntaxCodeBlock
154
+ ? [
155
+ {
156
+ label: "Code Block",
157
+ value: "code_block",
158
+ data: {
159
+ Icon: SquareCodeIcon,
160
+ command: (editor) => {
161
+ toggleCodeBlock(editor);
162
+ },
163
+ },
164
+ },
165
+ ]
166
+ : []),
135
167
  ],
136
168
  dropDownProps: {
137
169
  renderOption: (item) => (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [_jsx(item.data.Icon, { size: 16 }), item.label] })),
138
170
  },
139
171
  },
140
- {
141
- name: "color",
142
- label: "Color",
143
- type: "menu",
144
- arrow: true,
145
- buttonProps: {
146
- // leftSection: nodeTypeIcon,
147
- style: { fontSize: 11, padding: "4px" },
148
- },
149
- items: [
172
+ ...(supportsColor
173
+ ? [
150
174
  {
151
- label: "Default",
152
- value: "default",
153
- onClick: () => {
154
- editor === null || editor === void 0 ? void 0 : editor.chain().focus().unsetColor().run();
175
+ name: "color",
176
+ label: _jsx(PaletteIcon, { size: 14 }),
177
+ type: "menu",
178
+ buttonProps: {
179
+ style: { fontSize: 11, padding: "4px" },
180
+ },
181
+ items: [
182
+ {
183
+ label: "Default",
184
+ value: "default",
185
+ onClick: () => {
186
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().unsetColor().run();
187
+ },
188
+ },
189
+ ...Object.keys(TextColors).map((color) => {
190
+ const colorKey = color;
191
+ return {
192
+ label: color,
193
+ value: TextColors[colorKey],
194
+ onClick: () => {
195
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().setColor(TextColors[colorKey]).run();
196
+ },
197
+ };
198
+ }),
199
+ ],
200
+ dropDownProps: {
201
+ renderOption: (item) => (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [_jsx(SquircleIcon, { size: 12, color: item.value === "default"
202
+ ? theme.palette.text.primary
203
+ : item.value, style: {
204
+ backgroundColor: item.value === "default"
205
+ ? theme.palette.text.primary
206
+ : item.value,
207
+ borderRadius: "3px",
208
+ } }), item.label] })),
155
209
  },
156
210
  },
157
- ...Object.keys(TextColors).map((color) => {
158
- const colorKey = color;
159
- return {
160
- label: color,
161
- value: TextColors[colorKey],
162
- onClick: () => {
163
- editor === null || editor === void 0 ? void 0 : editor.chain().focus().setColor(TextColors[colorKey]).run();
211
+ ]
212
+ : []),
213
+ ...(supportsHighlight
214
+ ? [
215
+ {
216
+ name: Extensions.Highlight,
217
+ label: _jsx(HighlighterIcon, { size: 14 }),
218
+ type: "menu",
219
+ buttonProps: {
220
+ style: { fontSize: 11, padding: "4px" },
221
+ },
222
+ items: [
223
+ {
224
+ label: "Default",
225
+ value: "default",
226
+ onClick: () => {
227
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().unsetHighlight().run();
228
+ },
164
229
  },
165
- };
166
- }),
167
- ],
168
- dropDownProps: {
169
- renderOption: (item) => (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [_jsx(SquircleIcon, { size: 12, color: item.value === "default"
170
- ? theme.palette.text.primary
171
- : item.value, style: {
172
- backgroundColor: item.value === "default"
173
- ? theme.palette.text.primary
174
- : item.value,
175
- borderRadius: "3px",
176
- } }), item.label] })),
177
- },
178
- },
230
+ ...Object.keys(HighlightColors).map((color) => {
231
+ const colorKey = color;
232
+ return {
233
+ label: color,
234
+ value: HighlightColors[colorKey],
235
+ onClick: () => {
236
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().setHighlight({ color: HighlightColors[colorKey] }).run();
237
+ },
238
+ };
239
+ }),
240
+ ],
241
+ dropDownProps: {
242
+ renderOption: (item) => (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [_jsx(SquircleIcon, { size: 12, color: item.value === "default"
243
+ ? theme.palette.text.primary
244
+ : item.value, style: {
245
+ backgroundColor: item.value === "default"
246
+ ? "transparent"
247
+ : item.value,
248
+ borderRadius: "3px",
249
+ } }), item.label] })),
250
+ },
251
+ },
252
+ ]
253
+ : []),
254
+ ...(supportsLink
255
+ ? [
256
+ {
257
+ name: Extensions.Link,
258
+ icon: LinkIcon,
259
+ type: "button",
260
+ isActive: (editor) => editor.isActive("link"),
261
+ onClick: () => {
262
+ openLinkEditor();
263
+ },
264
+ },
265
+ ]
266
+ : []),
179
267
  {
180
268
  name: Extensions.Bold,
181
269
  icon: BoldIcon,
@@ -212,6 +300,32 @@ const getMenuItems = (editor, customMenuItems, theme) => {
212
300
  editor.chain().focus().toggleStrike().run();
213
301
  },
214
302
  },
303
+ ...(supportsInlineCode
304
+ ? [
305
+ {
306
+ name: Extensions.Code,
307
+ icon: CodeIcon,
308
+ type: "button",
309
+ isActive: (editor) => editor.isActive("code"),
310
+ onClick: (editor) => {
311
+ toggleInlineCode(editor);
312
+ },
313
+ },
314
+ ]
315
+ : []),
316
+ ...(supportsSyntaxCodeBlock
317
+ ? [
318
+ {
319
+ name: Extensions.CodeBlock,
320
+ icon: SquareCodeIcon,
321
+ type: "button",
322
+ isActive: (editor) => editor.isActive("codeBlock"),
323
+ onClick: (editor) => {
324
+ toggleCodeBlock(editor);
325
+ },
326
+ },
327
+ ]
328
+ : []),
215
329
  {
216
330
  name: Extensions.ClearFormatting,
217
331
  icon: RemoveFormattingIcon,
@@ -224,12 +338,11 @@ const getMenuItems = (editor, customMenuItems, theme) => {
224
338
  ];
225
339
  };
226
340
  const BubbleMenuContent = styled.div `
227
- position: fixed;
228
341
  display: flex;
229
342
  justify-content: space-between;
230
343
  align-items: center;
231
344
  padding: 2px;
232
- gap: 2px;
345
+ gap: 4px;
233
346
 
234
347
  color: ${({ theme }) => theme.palette.text.primary};
235
348
  background-color: ${({ theme }) => theme.palette.input.background};
@@ -272,51 +385,43 @@ const BubbleItemButton = styled(Button) `
272
385
  background-color: ${({ theme }) => theme.palette.action.hover};
273
386
  }
274
387
  `;
275
- const BubbleMenu = ({ editor, rect, open, onOpen, customMenuItems = [], }) => {
276
- var _a;
277
- const menuRef = useRef(null);
278
- const { refs, elements } = useFloating();
388
+ const CodeBlockBubbleTools = ({ editor, theme, }) => {
389
+ const nodeTypeMenu = getMenuItems(editor, [], theme, () => undefined).find((item) => item.name === "node_type");
390
+ return (_jsxs(_Fragment, { children: [(nodeTypeMenu === null || nodeTypeMenu === void 0 ? void 0 : nodeTypeMenu.type) === "menu" && (_jsx(DropDownMenu, Object.assign({ data: nodeTypeMenu.items, size: "xs", arrow: nodeTypeMenu.arrow, buttonProps: nodeTypeMenu.buttonProps, variant: "subtle", buttonRender: nodeTypeMenu.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, ""); }, dropDownProps: {
391
+ style: { width: 135 },
392
+ } }, nodeTypeMenu.dropDownProps, { children: nodeTypeMenu.icon
393
+ ? (_jsx(nodeTypeMenu.icon, { size: 14 }))
394
+ : (nodeTypeMenu.label || nodeTypeMenu.name) }))), _jsx(CodeBlockLanguageSelect, { editor: editor }), _jsx(CodeBlockWrapButton, { editor: editor }), _jsx(CodeBlockFormatButton, { editor: editor }), _jsx(CodeBlockCopyButton, { editor: editor })] }));
395
+ };
396
+ const BubbleMenu = ({ className, editor, customMenuItems = [], }) => {
279
397
  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;
398
+ const [linkEditorOpen, setLinkEditorOpen] = useState(false);
286
399
  const { from, to } = editor.state.selection;
287
400
  const selectedText = editor.state.doc.textBetween(from, to, "\n", "\n");
288
- let top = (rect === null || rect === void 0 ? void 0 : rect.top) ? rect.top - 50 : 0;
289
- if (top < 10) {
290
- top = 10; // add some padding
291
- }
292
- let left = (rect === null || rect === void 0 ? void 0 : rect.left) ? rect.left + rect.width / 2 - elementWidth / 2 : 0;
293
- if (left < 10) {
294
- left = 10; // add some padding
401
+ const isLinkSelection = editor.isActive("link");
402
+ const isCodeBlockSelection = editor.isActive("codeBlock") && hasSyntaxHighlightedCodeBlock(editor);
403
+ useEffect(() => {
404
+ setLinkEditorOpen(false);
405
+ }, [from, to]);
406
+ if (isLinkSelection || linkEditorOpen) {
407
+ return (_jsx(BubbleMenuContent, { className: className, children: _jsx(LinkEditor, { editor: editor, autoFocus: true, onClose: () => setLinkEditorOpen(false) }) }));
295
408
  }
296
- // Check if the menu is overflowing on the right
297
- const rightOverflow = left + elementWidth - window.innerWidth;
298
- if (rightOverflow > -10) {
299
- left -= rightOverflow + 10; // add some padding
409
+ if (isCodeBlockSelection) {
410
+ return (_jsx(BubbleMenuContent, { className: className, children: _jsx(CodeBlockBubbleTools, { editor: editor, theme: theme }) }));
300
411
  }
301
- return (_jsx(FloatingPortal, { preserveTabOrder: true, children: open && (_jsx(BubbleMenuContent, { ref: (ref) => {
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
- }) })) }));
412
+ return (_jsx(BubbleMenuContent, { className: className, children: getMenuItems(editor, customMenuItems, theme, () => setLinkEditorOpen(true)).map((item) => {
413
+ var _a;
414
+ if (item.type === "button") {
415
+ const isActive = (_a = item.isActive) === null || _a === void 0 ? void 0 : _a.call(item, editor);
416
+ 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));
417
+ }
418
+ if (item.type === "menu") {
419
+ 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: {
420
+ style: { width: 135 },
421
+ } }, item.dropDownProps, { children: item.icon
422
+ ? (_jsx(item.icon, { size: 14 }))
423
+ : (item.label || item.name) }), item.name));
424
+ }
425
+ }) }));
321
426
  };
322
427
  export default BubbleMenu;
@@ -0,0 +1,9 @@
1
+ import { Editor } from "@tiptap/react";
2
+ type CodeBlockCopyButtonProps = {
3
+ className?: string;
4
+ editor?: Editor | null;
5
+ text?: string;
6
+ size?: "xs" | "sm";
7
+ };
8
+ declare const CodeBlockCopyButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<CodeBlockCopyButtonProps, never>> & string & Omit<({ className, editor, text, size }: CodeBlockCopyButtonProps) => import("react/jsx-runtime").JSX.Element, keyof import("react").Component<any, {}, any>>;
9
+ export default CodeBlockCopyButton;
@@ -0,0 +1,45 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { useEffect, useState } from "react";
12
+ import styled from "styled-components";
13
+ import { CheckIcon, CopyIcon } from "lucide-react";
14
+ import { Button } from "../../Button";
15
+ import { copyCodeBlockText, getActiveCodeBlockText, } from "../Utils/codeBlockUtils";
16
+ const CodeBlockCopyButton = styled(({ className, editor, text, size = "xs" }) => {
17
+ const [copied, setCopied] = useState(false);
18
+ const [copyFailed, setCopyFailed] = useState(false);
19
+ const code = text !== null && text !== void 0 ? text : getActiveCodeBlockText(editor || null);
20
+ useEffect(() => {
21
+ if (!copied && !copyFailed)
22
+ return;
23
+ const timeout = window.setTimeout(() => {
24
+ setCopied(false);
25
+ setCopyFailed(false);
26
+ }, 1400);
27
+ return () => window.clearTimeout(timeout);
28
+ }, [copied, copyFailed]);
29
+ return (_jsx(Button, { className: className, size: size, variant: "outlined", title: copyFailed ? "Unable to copy" : copied ? "Copied" : "Copy code", "aria-label": copyFailed ? "Unable to copy code" : copied ? "Copied" : "Copy code", disabled: !code, onClick: (event) => __awaiter(void 0, void 0, void 0, function* () {
30
+ event.preventDefault();
31
+ event.stopPropagation();
32
+ try {
33
+ yield copyCodeBlockText(code);
34
+ setCopied(true);
35
+ setCopyFailed(false);
36
+ }
37
+ catch (_a) {
38
+ setCopied(false);
39
+ setCopyFailed(true);
40
+ }
41
+ }), children: copied ? _jsx(CheckIcon, { size: 14 }) : _jsx(CopyIcon, { size: 14 }) }));
42
+ }) `
43
+ padding: 3px;
44
+ `;
45
+ export default CodeBlockCopyButton;
@@ -0,0 +1,10 @@
1
+ import { Editor } from "@tiptap/react";
2
+ type CodeBlockFormatButtonProps = {
3
+ className?: string;
4
+ editor?: Editor | null;
5
+ language?: string;
6
+ onFormat?: () => Promise<void>;
7
+ size?: "xs" | "sm";
8
+ };
9
+ declare const CodeBlockFormatButton: ({ className, editor, language, onFormat, size, }: CodeBlockFormatButtonProps) => import("react/jsx-runtime").JSX.Element;
10
+ export default CodeBlockFormatButton;
@@ -0,0 +1,60 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { useEffect, useState } from "react";
12
+ import { CheckIcon, TriangleAlertIcon, WandSparklesIcon } from "lucide-react";
13
+ import { Button } from "../../Button";
14
+ import { canFormatCodeBlockLanguage, formatActiveCodeBlock, getCodeBlockLanguage, } from "../Utils/codeBlockUtils";
15
+ const CodeBlockFormatButton = ({ className, editor, language, onFormat, size = "xs", }) => {
16
+ const [formatted, setFormatted] = useState(false);
17
+ const [formatFailed, setFormatFailed] = useState(false);
18
+ const codeLanguage = language !== null && language !== void 0 ? language : getCodeBlockLanguage(editor || null);
19
+ const canFormat = canFormatCodeBlockLanguage(codeLanguage);
20
+ useEffect(() => {
21
+ if (!formatted && !formatFailed)
22
+ return;
23
+ const timeout = window.setTimeout(() => {
24
+ setFormatted(false);
25
+ setFormatFailed(false);
26
+ }, 1400);
27
+ return () => window.clearTimeout(timeout);
28
+ }, [formatted, formatFailed]);
29
+ return (_jsx(Button, { className: className, size: size, variant: "outlined", title: !canFormat
30
+ ? "Formatting is not supported for this language"
31
+ : formatFailed
32
+ ? "Unable to format code"
33
+ : formatted
34
+ ? "Formatted"
35
+ : "Format code", "aria-label": !canFormat
36
+ ? "Formatting is not supported for this language"
37
+ : formatFailed
38
+ ? "Unable to format code"
39
+ : formatted
40
+ ? "Formatted"
41
+ : "Format code", disabled: !canFormat || (!onFormat && !(editor === null || editor === void 0 ? void 0 : editor.isActive("codeBlock"))), onClick: (event) => __awaiter(void 0, void 0, void 0, function* () {
42
+ event.preventDefault();
43
+ event.stopPropagation();
44
+ try {
45
+ if (onFormat) {
46
+ yield onFormat();
47
+ }
48
+ else {
49
+ yield formatActiveCodeBlock(editor || null);
50
+ }
51
+ setFormatted(true);
52
+ setFormatFailed(false);
53
+ }
54
+ catch (_a) {
55
+ setFormatted(false);
56
+ setFormatFailed(true);
57
+ }
58
+ }), children: formatted ? (_jsx(CheckIcon, { size: 14 })) : formatFailed ? (_jsx(TriangleAlertIcon, { size: 14 })) : (_jsx(WandSparklesIcon, { size: 14 })) }));
59
+ };
60
+ export default CodeBlockFormatButton;
@@ -0,0 +1,6 @@
1
+ import { Editor } from "@tiptap/react";
2
+ type CodeBlockLanguageSelectProps = {
3
+ editor: Editor | null;
4
+ };
5
+ declare const CodeBlockLanguageSelect: ({ editor }: CodeBlockLanguageSelectProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default CodeBlockLanguageSelect;