@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
@@ -66,84 +66,14 @@ const getImageBlob = (src) => __awaiter(void 0, void 0, void 0, function* () {
66
66
  }
67
67
  return response.blob();
68
68
  });
69
- const clipboardPngType = "image/png";
70
- const canWriteClipboardType = (ClipboardItemCtor, type) => {
71
- if (!type)
72
- return false;
73
- if (typeof ClipboardItemCtor.supports === "function") {
74
- return ClipboardItemCtor.supports(type);
75
- }
76
- return type === clipboardPngType;
77
- };
78
- const loadImageSource = (src) => new Promise((resolve, reject) => {
79
- const image = new Image();
80
- image.crossOrigin = "anonymous";
81
- image.onload = () => resolve(image);
82
- image.onerror = () => {
83
- reject(new Error("Unable to prepare image for clipboard."));
84
- };
85
- image.src = src;
86
- });
87
- const renderImageSourceToPngBlob = (src) => __awaiter(void 0, void 0, void 0, function* () {
88
- const image = yield loadImageSource(src);
89
- const width = image.naturalWidth || image.width;
90
- const height = image.naturalHeight || image.height;
91
- if (!width || !height) {
92
- throw new Error("Unable to prepare image for clipboard.");
93
- }
94
- const canvas = document.createElement("canvas");
95
- canvas.width = width;
96
- canvas.height = height;
97
- const context = canvas.getContext("2d");
98
- if (!context) {
99
- throw new Error("Unable to prepare image for clipboard.");
100
- }
101
- context.drawImage(image, 0, 0, width, height);
102
- return new Promise((resolve, reject) => {
103
- canvas.toBlob((blob) => {
104
- if (blob) {
105
- resolve(blob);
106
- }
107
- else {
108
- reject(new Error("Unable to prepare image for clipboard."));
109
- }
110
- }, clipboardPngType);
111
- });
112
- });
113
- const convertBlobToClipboardPng = (blob, fallbackSrc) => __awaiter(void 0, void 0, void 0, function* () {
114
- const objectUrl = URL.createObjectURL(blob);
115
- try {
116
- return yield renderImageSourceToPngBlob(objectUrl);
117
- }
118
- catch (error) {
119
- if (!fallbackSrc || fallbackSrc === objectUrl)
120
- throw error;
121
- return renderImageSourceToPngBlob(fallbackSrc);
122
- }
123
- finally {
124
- URL.revokeObjectURL(objectUrl);
125
- }
126
- });
127
- const getClipboardImageBlob = (image, ClipboardItemCtor) => __awaiter(void 0, void 0, void 0, function* () {
128
- const src = image.currentSrc || image.src;
129
- const blob = yield getImageBlob(src);
130
- const type = blob.type || "";
131
- if (canWriteClipboardType(ClipboardItemCtor, type)) {
132
- return { blob, type };
133
- }
134
- return {
135
- blob: yield convertBlobToClipboardPng(blob, src),
136
- type: clipboardPngType,
137
- };
138
- });
139
69
  const copyImage = (image) => __awaiter(void 0, void 0, void 0, function* () {
140
70
  var _a;
141
- const ClipboardItemCtor = window
142
- .ClipboardItem;
71
+ const ClipboardItemCtor = window.ClipboardItem;
143
72
  if (!((_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.write) || !ClipboardItemCtor) {
144
73
  throw new Error("Image copying is not supported by this browser.");
145
74
  }
146
- const { blob, type } = yield getClipboardImageBlob(image, ClipboardItemCtor);
75
+ const blob = yield getImageBlob(image.src);
76
+ const type = blob.type || "image/png";
147
77
  yield navigator.clipboard.write([
148
78
  new ClipboardItemCtor({
149
79
  [type]: blob,
@@ -113,6 +113,7 @@ export const startImageUpload = (file, view, pos, handleImageUpload) => {
113
113
  src: imageSrc,
114
114
  alt: `${id}.png`,
115
115
  "data-uuid": id,
116
+ crossorigin: "anonymous",
116
117
  title: `Filename: ${id}.png`,
117
118
  });
118
119
  const insertTransaction = view.state.tr
@@ -1,12 +1,15 @@
1
+ import "highlight.js/styles/github-dark.css";
1
2
  import { Editor } from "@tiptap/react";
2
- import { ExtensionType } from "./Extensions/getTiptapExtensions";
3
+ import { ExtensionPreset, ExtensionType } from "./Extensions/getTiptapExtensions";
3
4
  import { HandleImageUrlUpload, HandleImageUpload } from "./Plugins/UploadImagesPlugin";
4
- import { BubbleMenuOptions } from "./Extensions/BubbleMenuExtension";
5
+ import { BubbleMenuOptions } from "./Components/BubbleMenu";
5
6
  import { ToolbarOptions } from "./Toolbar/Toolbar";
6
7
  type RichTextEditorProps = {
7
8
  className?: string;
8
9
  editorInstanceRef?: React.RefObject<Editor | null>;
9
10
  extensions?: ExtensionType[];
11
+ disabledExtensions?: ExtensionType[];
12
+ extensionPreset?: ExtensionPreset;
10
13
  slashCommands?: any[];
11
14
  defaultValue?: string;
12
15
  value?: string;
@@ -8,17 +8,23 @@ 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
+ import "highlight.js/styles/github-dark.css";
13
14
  import { EditorContent, useEditor } from "@tiptap/react";
15
+ import { BubbleMenu as TiptapBubbleMenu, } from "@tiptap/react/menus";
16
+ import { isTextSelection } from "@tiptap/core";
14
17
  import { DOMParser as ProseMirrorDOMParser } from "@tiptap/pm/model";
18
+ import { TextSelection } from "@tiptap/pm/state";
15
19
  import { Toolbar } from "./Toolbar";
16
- import getTipTapExtensions from "./Extensions/getTiptapExtensions";
17
- import { Extensions, SlashCommands } from "./Enums";
20
+ import getTipTapExtensions, { resolveExtensions, } from "./Extensions/getTiptapExtensions";
21
+ import { Controls, Extensions, SlashCommands } from "./Enums";
18
22
  import { addImagePlaceholder, removeImagePlaceholder, startImageUpload, } from "./Plugins/UploadImagesPlugin";
19
23
  import SaveBadge from "./Components/SaveBadge";
24
+ import BubbleMenuContent from "./Components/BubbleMenu";
20
25
  import Fonts from "./Enums/Fonts";
21
26
  import RichTextEditorContext from "./Contexts/RichTextEditorContext";
27
+ import { getLinkAttributesAtPosition, getLinkRangeAtPosition, openLink, } from "./Utils/linkUtils";
22
28
  const getImageFilesFromClipboard = (clipboardData) => {
23
29
  return Array.from(clipboardData.files).filter((file) => file.type.includes("image/"));
24
30
  };
@@ -317,6 +323,78 @@ const StyledContent = styled.div `
317
323
  margin: 0;
318
324
  line-height: 1.5rem;
319
325
  }
326
+ .editor-inline-code,
327
+ :not(pre) > code {
328
+ padding: 0.1rem 0.25rem;
329
+ border: 1px solid ${({ theme }) => theme.palette.divider};
330
+ border-radius: 4px;
331
+ background-color: ${({ theme }) => theme.palette.action.hover};
332
+ color: ${({ theme }) => theme.palette.text.secondary};
333
+ font-family:
334
+ ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, Consolas,
335
+ "Liberation Mono", "Courier New", monospace;
336
+ font-size: 0.9em;
337
+ box-decoration-break: clone;
338
+ }
339
+ .editor-code-block {
340
+ position: relative;
341
+ margin: 0.5rem 0;
342
+ padding: 0.875rem 1rem;
343
+ overflow-x: auto;
344
+ border: 1px solid #30363d;
345
+ border-radius: 6px;
346
+ background-color: #0d1117;
347
+ color: #c9d1d9;
348
+ font-family:
349
+ ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, Consolas,
350
+ "Liberation Mono", "Courier New", monospace;
351
+ font-size: 0.85rem;
352
+ line-height: 1.45rem;
353
+ white-space: pre;
354
+
355
+ &[data-wrap="true"] {
356
+ white-space: pre-wrap;
357
+ word-break: break-word;
358
+ overflow-wrap: anywhere;
359
+ }
360
+
361
+ code {
362
+ display: block;
363
+ min-width: max-content;
364
+ padding: 0;
365
+ border: 0;
366
+ background-color: transparent;
367
+ color: inherit;
368
+ font: inherit;
369
+ }
370
+
371
+ &[data-wrap="true"] code {
372
+ min-width: 0;
373
+ white-space: inherit;
374
+ }
375
+
376
+ .editor-code-block-actions {
377
+ display: flex;
378
+ position: absolute;
379
+ top: 0.4rem;
380
+ right: 0.4rem;
381
+ z-index: 1;
382
+ gap: 0.25rem;
383
+ opacity: 0;
384
+ transition: opacity 120ms ease-in-out;
385
+ }
386
+
387
+ .editor-code-block-action {
388
+ background-color: #161b22;
389
+ color: #c9d1d9;
390
+ border-color: #30363d;
391
+ }
392
+
393
+ &:hover .editor-code-block-actions,
394
+ &:focus-within .editor-code-block-actions {
395
+ opacity: 1;
396
+ }
397
+ }
320
398
  ul {
321
399
  margin: 0;
322
400
  }
@@ -330,8 +408,6 @@ const StyledContent = styled.div `
330
408
  color: ${({ theme }) => theme.palette.text.primary};
331
409
  text-decoration: underline;
332
410
  cursor: pointer;
333
- // Set title attribute
334
- title: "Click to open link";
335
411
  }
336
412
  img {
337
413
  max-width: 100%;
@@ -543,16 +619,96 @@ const StyledContent = styled.div `
543
619
  margin: 0 0.125rem;
544
620
  }
545
621
  `;
546
- export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = "", value, readOnly = false, font, showToolbar = true, saving = false, extensions = [], slashCommands = [], bubbleMenuOptions, toolbarOptions, autoFocus, onChange, handleImageUpload, handleImageUrlUpload, style, }) => {
622
+ export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = "", value, readOnly = false, font, showToolbar = true, saving = false, disabledExtensions = [], extensionPreset = "basic", extensions = [], slashCommands = [], bubbleMenuOptions, toolbarOptions, autoFocus, onChange, handleImageUpload, handleImageUrlUpload, style, }) => {
623
+ const resolvedExtensions = useMemo(() => resolveExtensions({
624
+ disabledExtensions,
625
+ extensionPreset,
626
+ extensions,
627
+ }), [disabledExtensions, extensionPreset, extensions]);
628
+ const resolvedExtensionSet = useMemo(() => new Set(resolvedExtensions), [resolvedExtensions]);
629
+ const resolvedSlashCommands = useMemo(() => slashCommands.filter((command) => {
630
+ if (command === SlashCommands.CodeBlock) {
631
+ return resolvedExtensionSet.has(Extensions.CodeBlock);
632
+ }
633
+ return true;
634
+ }), [resolvedExtensionSet, slashCommands]);
635
+ const resolvedToolbarOptions = useMemo(() => {
636
+ if (!(toolbarOptions === null || toolbarOptions === void 0 ? void 0 : toolbarOptions.controls))
637
+ return toolbarOptions;
638
+ const controlExtensionMap = {
639
+ [Controls.BOLD]: Extensions.Bold,
640
+ [Controls.ITALIC]: Extensions.Italic,
641
+ [Controls.UNDERLINE]: Extensions.Underline,
642
+ [Controls.STRIKE]: Extensions.Strike,
643
+ [Controls.CODE]: Extensions.Code,
644
+ [Controls.CODE_BLOCK]: Extensions.CodeBlock,
645
+ [Controls.BULLET_LIST]: Extensions.BulletList,
646
+ [Controls.ORDERED_LIST]: Extensions.OrderedList,
647
+ [Controls.COLOR]: Extensions.Color,
648
+ [Controls.HIGHLIGHT]: Extensions.Highlight,
649
+ [Controls.LINK]: Extensions.Link,
650
+ [Controls.TEXT_ALIGN_LEFT]: Extensions.TextAlign,
651
+ [Controls.TEXT_ALIGN_CENTER]: Extensions.TextAlign,
652
+ [Controls.TEXT_ALIGN_RIGHT]: Extensions.TextAlign,
653
+ [Controls.TEXT_ALIGN_JUSTIFIED]: Extensions.TextAlign,
654
+ };
655
+ return Object.assign(Object.assign({}, toolbarOptions), { controls: toolbarOptions.controls.filter((control) => {
656
+ if (typeof control !== "string")
657
+ return true;
658
+ const extension = controlExtensionMap[control];
659
+ return !extension || resolvedExtensionSet.has(extension);
660
+ }) });
661
+ }, [resolvedExtensionSet, toolbarOptions]);
547
662
  const isControlled = value !== undefined;
548
- const hasImageExtension = extensions.includes(Extensions.Image);
549
- const hasSlashCommandExtension = extensions.includes(Extensions.SlashCommand);
550
- const hasImageSlashCommand = hasSlashCommandExtension && slashCommands.includes(SlashCommands.Image);
663
+ const hasImageExtension = resolvedExtensionSet.has(Extensions.Image);
664
+ const hasSlashCommandExtension = resolvedExtensionSet.has(Extensions.SlashCommand);
665
+ const hasBubbleMenuExtension = resolvedExtensionSet.has(Extensions.BubbleMenu);
666
+ const hasImageSlashCommand = hasSlashCommandExtension &&
667
+ resolvedSlashCommands.includes(SlashCommands.Image);
551
668
  const onChangeRef = useRef(onChange);
669
+ const bubbleMenuPortalRef = useRef(null);
552
670
  const [fontState, setFontState] = useState(font || Fonts.DEFAULT);
553
671
  useEffect(() => {
554
672
  onChangeRef.current = onChange;
555
673
  }, [onChange]);
674
+ const getBubbleMenuPortalRoot = useCallback(() => {
675
+ if (bubbleMenuPortalRef.current) {
676
+ return bubbleMenuPortalRef.current;
677
+ }
678
+ const portal = document.createElement("div");
679
+ portal.setAttribute("data-monolith-bubble-menu-portal", "");
680
+ document.body.appendChild(portal);
681
+ bubbleMenuPortalRef.current = portal;
682
+ return portal;
683
+ }, []);
684
+ useEffect(() => {
685
+ return () => {
686
+ var _a;
687
+ (_a = bubbleMenuPortalRef.current) === null || _a === void 0 ? void 0 : _a.remove();
688
+ bubbleMenuPortalRef.current = null;
689
+ };
690
+ }, []);
691
+ const shouldShowBubbleMenu = useCallback(({ editor, element, view, state, from, to }) => {
692
+ const { selection } = state;
693
+ const isChildOfMenu = element.contains(document.activeElement);
694
+ const hasEditorFocus = view.hasFocus() || isChildOfMenu;
695
+ const selectedText = state.doc.textBetween(from, to).trim();
696
+ const isEmptyTextBlock = !selectedText && isTextSelection(state.selection);
697
+ if (!hasEditorFocus ||
698
+ selection.empty ||
699
+ isEmptyTextBlock ||
700
+ !editor.isEditable) {
701
+ return false;
702
+ }
703
+ return true;
704
+ }, []);
705
+ const bubbleMenuPositionOptions = useMemo(() => ({
706
+ strategy: "fixed",
707
+ placement: "top",
708
+ offset: 8,
709
+ flip: false,
710
+ shift: { padding: 10 },
711
+ }), []);
556
712
  if (hasImageSlashCommand && !hasImageExtension) {
557
713
  throw new Error("Extensions.Image is required when using the Image slash command.");
558
714
  }
@@ -566,12 +722,29 @@ export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = ""
566
722
  editable: !readOnly,
567
723
  shouldRerenderOnTransaction: true,
568
724
  extensions: getTipTapExtensions({
569
- extensions,
570
- slashCommands,
571
- bubbleMenuOptions,
725
+ disabledExtensions,
726
+ extensions: resolvedExtensions,
727
+ slashCommands: resolvedSlashCommands,
572
728
  handleImageUpload,
573
729
  }),
574
730
  editorProps: {
731
+ handleClick: (view, pos, event) => {
732
+ const linkRange = getLinkRangeAtPosition(view.state, pos);
733
+ const linkAttributes = getLinkAttributesAtPosition(view.state, pos);
734
+ if (!linkRange || !(linkAttributes === null || linkAttributes === void 0 ? void 0 : linkAttributes.href))
735
+ return false;
736
+ const mouseEvent = event;
737
+ mouseEvent.preventDefault();
738
+ mouseEvent.stopPropagation();
739
+ const shouldOpenLink = mouseEvent.metaKey || mouseEvent.ctrlKey || !view.editable;
740
+ if (shouldOpenLink) {
741
+ openLink(linkAttributes.href);
742
+ return true;
743
+ }
744
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, linkRange.from, linkRange.to)));
745
+ view.focus();
746
+ return true;
747
+ },
575
748
  handlePaste: (view, event) => {
576
749
  if (!hasImageExtension || !handleImageUpload)
577
750
  return false;
@@ -632,5 +805,5 @@ export const RichTextEditor = ({ className, editorInstanceRef, defaultValue = ""
632
805
  return (_jsx(StyledContent, { className: className, children: _jsxs(RichTextEditorContext.Provider, { value: {
633
806
  font: fontState,
634
807
  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 })] }) }));
808
+ }, children: [showToolbar && (_jsx(Toolbar, { editor: editor, toolbarOptions: resolvedToolbarOptions })), saving && _jsx(SaveBadge, {}), editor && hasBubbleMenuExtension && (_jsx(TiptapBubbleMenu, { editor: editor, pluginKey: "bubbleMenu", updateDelay: 200, 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
809
  };
@@ -1,13 +1,17 @@
1
1
  import { Editor } from "@tiptap/react";
2
+ import { ButtonProps } from "../../Button";
2
3
  export type ControlProps = {
3
4
  className?: string;
4
5
  editor: Editor | null;
5
6
  isActive?: any;
6
- operation: {
7
+ operation?: {
7
8
  name: string;
8
9
  attributes?: any;
9
10
  };
11
+ onClick?: (editor: Editor | null) => void;
12
+ disabled?: boolean;
10
13
  label: string;
11
14
  icon: any;
15
+ size?: ButtonProps["size"];
12
16
  };
13
- export declare const Control: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<ControlProps, never>> & string & Omit<({ className, editor, isActive, operation, label, icon: Icon, }: ControlProps) => import("react/jsx-runtime").JSX.Element, keyof import("react").Component<any, {}, any>>;
17
+ export declare const Control: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<ControlProps, never>> & string & Omit<({ className, editor, isActive, operation, onClick, disabled, label, icon: Icon, size, }: ControlProps) => import("react/jsx-runtime").JSX.Element, keyof import("react").Component<any, {}, any>>;
@@ -1,23 +1,30 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import styled from "styled-components";
3
3
  import Labels from "./Labels";
4
- export const Control = styled(({ className, editor, isActive, operation, label, icon: Icon, }) => {
4
+ import { Button } from "../../Button";
5
+ export const Control = styled(({ className, editor, isActive, operation, onClick, disabled, label, icon: Icon, size = "xs", }) => {
5
6
  var _a;
6
7
  const _label = Labels[label];
7
8
  const active = (isActive === null || isActive === void 0 ? void 0 : isActive.name)
8
9
  ? (_a = editor === null || editor === void 0 ? void 0 : editor.isActive) === null || _a === void 0 ? void 0 : _a.call(editor, isActive.name, isActive.attributes)
9
10
  : false;
10
- return (_jsx("button", { className: className + (active ? " active" : ""), "aria-label": _label, "data-active": active, title: _label, onClick: () => {
11
+ return (_jsx(Button, { className: className + (active ? " active" : ""), "aria-label": _label, "data-active": active, title: _label, disabled: disabled, onClick: () => {
12
+ if (onClick) {
13
+ onClick(editor);
14
+ return;
15
+ }
16
+ if (!operation)
17
+ return;
11
18
  const focus = editor === null || editor === void 0 ? void 0 : editor.chain().focus();
12
19
  focus[operation.name](operation.attributes).run();
13
- }, children: _jsx(Icon, { size: "16px" }) }));
20
+ }, size: size, children: _jsx(Icon, { size: "16px" }) }));
14
21
  }) `
15
22
  display: flex;
16
23
  justify-content: center;
17
24
  align-items: center;
18
- width: 1.5rem;
19
- height: 1.5rem;
20
- padding: 0px;
25
+
26
+ padding: 5px;
27
+
21
28
  background-color: transparent;
22
29
  cursor: pointer;
23
30
  color: ${({ theme }) => theme.palette.text.primary};
@@ -8,6 +8,8 @@ export declare const BoldControl: ({ editor }: ControlProps) => import("react/js
8
8
  export declare const ItalicControl: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
9
9
  export declare const UnderlineControl: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
10
10
  export declare const StrikeThroughControl: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
11
+ export declare const CodeControl: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
12
+ export declare const CodeBlockControl: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
11
13
  export declare const Heading1Control: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
12
14
  export declare const Heading2Control: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
13
15
  export declare const Heading3Control: ({ editor }: ControlProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { IconBold, IconItalic, IconUnderline, IconStrikethrough, IconH1, IconH2, IconH3, IconH4, IconList, IconListNumbers, IconAlignLeft, IconAlignRight, IconAlignCenter, IconAlignJustified, IconCornerUpLeft, IconCornerUpRight, } from "@tabler/icons-react";
3
+ import { CodeIcon } from "lucide-react";
4
+ import { SquareCodeIcon } from "lucide-react";
3
5
  import { Control } from "./Control";
6
+ import { hasInlineCode, toggleInlineCode } from "../Utils/codeUtils";
7
+ import { hasSyntaxHighlightedCodeBlock, toggleCodeBlock, } from "../Utils/codeBlockUtils";
4
8
  export const UndoControl = ({ editor }) => {
5
9
  return (_jsx(Control, { editor: editor, label: "undoControlLabel", operation: {
6
10
  name: "undo",
@@ -39,6 +43,16 @@ export const StrikeThroughControl = ({ editor }) => {
39
43
  name: "toggleStrike",
40
44
  }, icon: IconStrikethrough }));
41
45
  };
46
+ export const CodeControl = ({ editor }) => {
47
+ return (_jsx(Control, { editor: editor, label: "codeControlLabel", isActive: {
48
+ name: "code",
49
+ }, onClick: toggleInlineCode, disabled: !hasInlineCode(editor), icon: CodeIcon }));
50
+ };
51
+ export const CodeBlockControl = ({ editor }) => {
52
+ return (_jsx(Control, { editor: editor, label: "codeBlockControlLabel", isActive: {
53
+ name: "codeBlock",
54
+ }, onClick: toggleCodeBlock, disabled: !hasSyntaxHighlightedCodeBlock(editor), icon: SquareCodeIcon }));
55
+ };
42
56
  export const Heading1Control = ({ editor }) => {
43
57
  return (_jsx(Control, { editor: editor, label: "h1ControlLabel", isActive: {
44
58
  name: "heading",
@@ -9,6 +9,7 @@ const ControlsGroup = styled(forwardRef(({ className, children }, ref) => {
9
9
  })) `
10
10
  & {
11
11
  display: flex;
12
+ flex: 0 0 auto;
12
13
  justify-content: flex-start;
13
14
  align-items: center;
14
15
  }
@@ -1,33 +1,42 @@
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 ControlsGroup from "./ControlsGroup";
4
- import { UndoControl, RedoControl, BoldControl, ItalicControl, UnderlineControl, StrikeThroughControl, Heading1Control, Heading2Control, Heading3Control, Heading4Control, BulletListControl, OrderedListControl, AlignLeftControl, AlignCenterControl, AlignRightControl, AlignJustifiedControl, } from "./Controls";
4
+ import { UndoControl, RedoControl, BoldControl, ItalicControl, UnderlineControl, StrikeThroughControl, CodeControl, CodeBlockControl, Heading1Control, Heading2Control, Heading3Control, Heading4Control, BulletListControl, OrderedListControl, AlignLeftControl, AlignCenterControl, AlignRightControl, AlignJustifiedControl, } from "./Controls";
5
5
  import { Controls } from "../Enums";
6
6
  import { DropDownMenu } from "../../DropDownMenu";
7
7
  import Fonts from "../Enums/Fonts";
8
- import { useContext } from "react";
8
+ import { useContext, useState } from "react";
9
9
  import RichTextEditorContext from "../Contexts/RichTextEditorContext";
10
10
  import { Button } from "../../Button";
11
+ import { Popover } from "../../Popover";
11
12
  import TextColors from "../Enums/TextColors";
12
- import { SquircleIcon } from "lucide-react";
13
+ import HighlightColors from "../Enums/HighlightColors";
14
+ import LinkEditor from "../Components/LinkEditor";
15
+ import CodeBlockLanguageSelect from "../Components/CodeBlockLanguageSelect";
16
+ import CodeBlockWrapButton from "../Components/CodeBlockWrapButton";
17
+ import CodeBlockFormatButton from "../Components/CodeBlockFormatButton";
18
+ import { hasSyntaxHighlightedCodeBlock } from "../Utils/codeBlockUtils";
19
+ import { HighlighterIcon, LinkIcon, PaletteIcon, SquircleIcon, TypeIcon, } from "lucide-react";
13
20
  export const Toolbar = styled(({ className, editor, toolbarOptions }) => {
14
21
  var _a;
15
22
  const theme = useTheme();
16
23
  const { controls } = toolbarOptions || {};
24
+ const [linkPopoverOpen, setLinkPopoverOpen] = useState(false);
17
25
  const customItems = controls === null || controls === void 0 ? void 0 : controls.filter((control) => typeof control !== "string" &&
18
26
  (control.type === "menu" || control.type === "button"));
19
27
  const { font, setFont } = useContext(RichTextEditorContext);
28
+ const showCodeBlockLanguageSelect = (editor === null || editor === void 0 ? void 0 : editor.isActive("codeBlock")) && hasSyntaxHighlightedCodeBlock(editor);
20
29
  return (_jsxs("div", { className: className, children: [(_a = customItems === null || customItems === void 0 ? void 0 : customItems.map) === null || _a === void 0 ? void 0 : _a.call(customItems, (item, index) => {
21
30
  var _a, _b;
22
31
  if (item.type === "button") {
23
- return (_jsx(Button, Object.assign({}, item.options, { children: (_a = item === null || item === void 0 ? void 0 : item.options) === null || _a === void 0 ? void 0 : _a.label }), index));
32
+ return (_jsx(Button, Object.assign({ size: "xs" }, item.options, { children: (_a = item === null || item === void 0 ? void 0 : item.options) === null || _a === void 0 ? void 0 : _a.label }), index));
24
33
  }
25
34
  else if (item.type === "menu") {
26
35
  return (_jsx(DropDownMenu, Object.assign({ dropDownProps: {
27
36
  style: {
28
37
  width: 135,
29
38
  },
30
- } }, item.options, { children: (_b = item === null || item === void 0 ? void 0 : item.options) === null || _b === void 0 ? void 0 : _b.label }), index));
39
+ } }, item.options, { size: "xs", children: (_b = item === null || item === void 0 ? void 0 : item.options) === null || _b === void 0 ? void 0 : _b.label }), index));
31
40
  }
32
41
  }), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.FONT)) && (_jsx(DropDownMenu, { data: Object.values(Fonts).map((font) => ({
33
42
  label: font,
@@ -35,12 +44,13 @@ export const Toolbar = styled(({ className, editor, toolbarOptions }) => {
35
44
  onClick: () => {
36
45
  setFont(font);
37
46
  },
38
- })), size: "xxs", variant: "outlined", arrow: true, dropDownProps: {
47
+ })), size: "xs", variant: "outlined", dropDownProps: {
39
48
  style: {
40
49
  width: 135,
41
50
  },
42
51
  }, buttonProps: {
43
52
  title: "Select Font",
53
+ leftSection: _jsx(TypeIcon, { size: 12 }),
44
54
  }, children: (font || Fonts.DEFAULT) })), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.UNDO)) && _jsx(UndoControl, { editor: editor }), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.REDO)) && _jsx(RedoControl, { editor: editor })] }), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.COLOR)) && (_jsx(DropDownMenu, { data: [
45
55
  {
46
56
  label: "Default",
@@ -66,20 +76,63 @@ export const Toolbar = styled(({ className, editor, toolbarOptions }) => {
66
76
  ? theme.palette.text.primary
67
77
  : item.value,
68
78
  borderRadius: "3px",
69
- } }), item.label] })), size: "xxs", variant: "outlined", arrow: true, buttonProps: {
79
+ } }), item.label] })), size: "xs", variant: "outlined", buttonProps: {
70
80
  title: "Select Color",
81
+ style: { padding: "1px 6px" },
82
+ }, dropDownProps: {
83
+ style: {
84
+ width: 120,
85
+ },
86
+ }, children: _jsx(PaletteIcon, { size: 14 }) })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HIGHLIGHT)) && (_jsx(DropDownMenu, { data: [
87
+ {
88
+ label: "Default",
89
+ value: "default",
90
+ onClick: () => {
91
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().unsetHighlight().run();
92
+ },
93
+ },
94
+ ...Object.keys(HighlightColors).map((color) => {
95
+ const colorKey = color;
96
+ return {
97
+ label: color,
98
+ value: HighlightColors[colorKey],
99
+ onClick: () => {
100
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().setHighlight({ color: HighlightColors[colorKey] }).run();
101
+ },
102
+ };
103
+ }),
104
+ ], renderOption: (item) => (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [_jsx(SquircleIcon, { size: 12, color: item.value === "default"
105
+ ? theme.palette.text.primary
106
+ : item.value, style: {
107
+ backgroundColor: item.value === "default"
108
+ ? "transparent"
109
+ : item.value,
110
+ borderRadius: "3px",
111
+ } }), item.label] })), size: "xs", variant: "outlined", buttonProps: {
112
+ title: "Select Highlight",
113
+ style: { padding: "1px 6px" },
71
114
  }, dropDownProps: {
72
115
  style: {
73
116
  width: 100,
117
+ height: 210,
74
118
  },
75
- }, children: "Color" })), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.BOLD)) && _jsx(BoldControl, { editor: editor }), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.ITALIC)) && (_jsx(ItalicControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.UNDERLINE)) && (_jsx(UnderlineControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.STRIKE)) && (_jsx(StrikeThroughControl, { editor: editor }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_1)) && (_jsx(Heading1Control, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_2)) && (_jsx(Heading2Control, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_3)) && (_jsx(Heading3Control, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_4)) && (_jsx(Heading4Control, { editor: editor }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.BULLET_LIST)) && (_jsx(BulletListControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.ORDERED_LIST)) && (_jsx(OrderedListControl, { editor: editor }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_LEFT)) && (_jsx(AlignLeftControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_CENTER)) && (_jsx(AlignCenterControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_RIGHT)) && (_jsx(AlignRightControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_JUSTIFIED)) && (_jsx(AlignJustifiedControl, { editor: editor }))] })] }));
119
+ }, children: _jsx(HighlighterIcon, { size: 14 }) })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.LINK)) && (_jsxs(Popover, { opened: linkPopoverOpen, onChange: setLinkPopoverOpen, position: "bottom", width: 330, trapFocus: true, children: [_jsx(Popover.Target, { children: _jsx(Button, { size: "xs", variant: "outlined", title: "Link", "aria-label": "Link", selected: linkPopoverOpen || Boolean(editor === null || editor === void 0 ? void 0 : editor.isActive("link")), disabled: !editor, style: { padding: "1px 6px" }, children: _jsx(LinkIcon, { size: 14 }) }) }), _jsx(Popover.Dropdown, { children: editor && (_jsx(LinkEditor, { editor: editor, autoFocus: linkPopoverOpen, onClose: () => setLinkPopoverOpen(false) })) })] })), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.BOLD)) && _jsx(BoldControl, { editor: editor }), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.ITALIC)) && (_jsx(ItalicControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.UNDERLINE)) && (_jsx(UnderlineControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.STRIKE)) && (_jsx(StrikeThroughControl, { editor: editor }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.CODE)) && _jsx(CodeControl, { editor: editor }), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.CODE_BLOCK)) && (_jsx(CodeBlockControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.CODE_BLOCK)) &&
120
+ showCodeBlockLanguageSelect && (_jsxs(_Fragment, { children: [_jsx(CodeBlockLanguageSelect, { editor: editor }), _jsx(CodeBlockWrapButton, { editor: editor }), _jsx(CodeBlockFormatButton, { editor: editor })] }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_1)) && (_jsx(Heading1Control, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_2)) && (_jsx(Heading2Control, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_3)) && (_jsx(Heading3Control, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.HEADING_4)) && (_jsx(Heading4Control, { editor: editor }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.BULLET_LIST)) && (_jsx(BulletListControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.ORDERED_LIST)) && (_jsx(OrderedListControl, { editor: editor }))] }), _jsxs(ControlsGroup, { children: [(controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_LEFT)) && (_jsx(AlignLeftControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_CENTER)) && (_jsx(AlignCenterControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_RIGHT)) && (_jsx(AlignRightControl, { editor: editor })), (controls === null || controls === void 0 ? void 0 : controls.includes(Controls.TEXT_ALIGN_JUSTIFIED)) && (_jsx(AlignJustifiedControl, { editor: editor }))] })] }));
76
121
  }) `
77
122
  display: flex;
78
123
  flex-direction: row;
124
+ flex-wrap: wrap;
79
125
  justify-content: center;
80
126
  align-items: center;
81
127
  gap: 5px;
128
+ row-gap: 6px;
129
+ width: 100%;
82
130
  margin-bottom: 10px;
83
131
  border-radius: 5px 5px 0 0;
84
132
  border: 1px solid transparent;
133
+
134
+ > button,
135
+ > .mfui-DropDownMenu {
136
+ flex: 0 0 auto;
137
+ }
85
138
  `;
@@ -0,0 +1,20 @@
1
+ import { Editor } from "@tiptap/react";
2
+ export type CodeBlockLanguage = {
3
+ label: string;
4
+ value: string;
5
+ };
6
+ export declare const DEFAULT_CODE_BLOCK_LANGUAGE = "plaintext";
7
+ export declare const CODE_BLOCK_LANGUAGES: CodeBlockLanguage[];
8
+ export declare const getCodeBlockLanguage: (editor: Editor | null) => string;
9
+ export declare const getCodeBlockLanguageOption: (language?: string) => CodeBlockLanguage;
10
+ export declare const canFormatCodeBlockLanguage: (language?: string) => boolean;
11
+ export declare const formatCodeBlockText: (text: string, language?: string) => Promise<string>;
12
+ export declare const hasSyntaxHighlightedCodeBlock: (editor: Editor | null) => boolean;
13
+ export declare const setCodeBlockLanguage: (editor: Editor | null, language: string) => void;
14
+ export declare const getCodeBlockWrap: (editor: Editor | null) => boolean;
15
+ export declare const toggleCodeBlockWrap: (editor: Editor | null) => void;
16
+ export declare const toggleCodeBlock: (editor: Editor | null) => void;
17
+ export declare const getActiveCodeBlockText: (editor: Editor | null) => string;
18
+ export declare const replaceCodeBlockContent: (editor: Editor, from: number, to: number, text: string) => boolean;
19
+ export declare const formatActiveCodeBlock: (editor: Editor | null) => Promise<void>;
20
+ export declare const copyCodeBlockText: (text: string) => Promise<void>;