@monolith-forensics/monolith-ui 1.9.1-dev.1 → 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.
- package/dist/DropDownMenu/components/MenuItemList.js +32 -12
- package/dist/DropDownMenu/components/StyledInnerItemContainer.js +1 -0
- package/dist/RichTextEditor/Components/BubbleMenu.d.ts +8 -8
- package/dist/RichTextEditor/Components/BubbleMenu.js +198 -93
- package/dist/RichTextEditor/Components/CodeBlockCopyButton.d.ts +9 -0
- package/dist/RichTextEditor/Components/CodeBlockCopyButton.js +45 -0
- package/dist/RichTextEditor/Components/CodeBlockFormatButton.d.ts +10 -0
- package/dist/RichTextEditor/Components/CodeBlockFormatButton.js +60 -0
- package/dist/RichTextEditor/Components/CodeBlockLanguageSelect.d.ts +6 -0
- package/dist/RichTextEditor/Components/CodeBlockLanguageSelect.js +21 -0
- package/dist/RichTextEditor/Components/CodeBlockNodeView.d.ts +3 -0
- package/dist/RichTextEditor/Components/CodeBlockNodeView.js +27 -0
- package/dist/RichTextEditor/Components/CodeBlockWrapButton.d.ts +10 -0
- package/dist/RichTextEditor/Components/CodeBlockWrapButton.js +17 -0
- package/dist/RichTextEditor/Components/LinkEditor.d.ts +8 -0
- package/dist/RichTextEditor/Components/LinkEditor.js +94 -0
- package/dist/RichTextEditor/Enums/Controls.d.ts +5 -1
- package/dist/RichTextEditor/Enums/Controls.js +4 -0
- package/dist/RichTextEditor/Enums/Extensions.d.ts +4 -0
- package/dist/RichTextEditor/Enums/Extensions.js +4 -0
- package/dist/RichTextEditor/Enums/HighlightColors.d.ts +9 -0
- package/dist/RichTextEditor/Enums/HighlightColors.js +10 -0
- package/dist/RichTextEditor/Enums/SlashCommands.d.ts +1 -0
- package/dist/RichTextEditor/Enums/SlashCommands.js +1 -0
- package/dist/RichTextEditor/Extensions/getSlashCommand.js +16 -1
- package/dist/RichTextEditor/Extensions/getTiptapExtensions.d.ts +10 -2
- package/dist/RichTextEditor/Extensions/getTiptapExtensions.js +158 -31
- package/dist/RichTextEditor/Plugins/ImageActionsPlugin.js +6 -109
- package/dist/RichTextEditor/Plugins/UploadImagesPlugin.js +1 -0
- package/dist/RichTextEditor/RichTextEditor.d.ts +5 -2
- package/dist/RichTextEditor/RichTextEditor.js +186 -13
- package/dist/RichTextEditor/Toolbar/Control.d.ts +6 -2
- package/dist/RichTextEditor/Toolbar/Control.js +13 -6
- package/dist/RichTextEditor/Toolbar/Controls.d.ts +2 -0
- package/dist/RichTextEditor/Toolbar/Controls.js +14 -0
- package/dist/RichTextEditor/Toolbar/ControlsGroup.js +1 -0
- package/dist/RichTextEditor/Toolbar/Toolbar.js +62 -9
- package/dist/RichTextEditor/Utils/codeBlockUtils.d.ts +20 -0
- package/dist/RichTextEditor/Utils/codeBlockUtils.js +137 -0
- package/dist/RichTextEditor/Utils/codeUtils.d.ts +3 -0
- package/dist/RichTextEditor/Utils/codeUtils.js +12 -0
- package/dist/RichTextEditor/Utils/linkUtils.d.ts +19 -0
- package/dist/RichTextEditor/Utils/linkUtils.js +57 -0
- package/package.json +8 -1
- package/dist/RichTextEditor/Extensions/BubbleMenuExtension.d.ts +0 -7
- package/dist/RichTextEditor/Extensions/BubbleMenuExtension.js +0 -157
|
@@ -60,123 +60,20 @@ const getImageFilename = (image) => {
|
|
|
60
60
|
return "image.png";
|
|
61
61
|
};
|
|
62
62
|
const getImageBlob = (src) => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
-
const response = yield fetch(src
|
|
64
|
-
mode: "cors",
|
|
65
|
-
credentials: "omit",
|
|
66
|
-
});
|
|
63
|
+
const response = yield fetch(src);
|
|
67
64
|
if (!response.ok) {
|
|
68
65
|
throw new Error("Unable to load image.");
|
|
69
66
|
}
|
|
70
|
-
return
|
|
71
|
-
blob: yield response.blob(),
|
|
72
|
-
contentType: response.headers.get("content-type") || "",
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
const clipboardPngType = "image/png";
|
|
76
|
-
const imageMimeTypesByExtension = {
|
|
77
|
-
gif: "image/gif",
|
|
78
|
-
jpg: "image/jpeg",
|
|
79
|
-
jpeg: "image/jpeg",
|
|
80
|
-
png: clipboardPngType,
|
|
81
|
-
svg: "image/svg+xml",
|
|
82
|
-
webp: "image/webp",
|
|
83
|
-
};
|
|
84
|
-
const normalizeMimeType = (type) => type.toLowerCase().split(";")[0].trim();
|
|
85
|
-
const getImageMimeTypeFromSource = (src) => {
|
|
86
|
-
var _a;
|
|
87
|
-
try {
|
|
88
|
-
const url = new URL(src);
|
|
89
|
-
const filename = url.pathname.split("/").filter(Boolean).pop();
|
|
90
|
-
const extension = (_a = filename === null || filename === void 0 ? void 0 : filename.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
91
|
-
return extension ? imageMimeTypesByExtension[extension] || "" : "";
|
|
92
|
-
}
|
|
93
|
-
catch (_b) {
|
|
94
|
-
const dataUrlMatch = src.match(/^data:([^;,]+)/);
|
|
95
|
-
return (dataUrlMatch === null || dataUrlMatch === void 0 ? void 0 : dataUrlMatch[1]) || "";
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
const canWriteClipboardType = (ClipboardItemCtor, type) => {
|
|
99
|
-
if (!type)
|
|
100
|
-
return false;
|
|
101
|
-
if (typeof ClipboardItemCtor.supports === "function") {
|
|
102
|
-
return ClipboardItemCtor.supports(type);
|
|
103
|
-
}
|
|
104
|
-
return type === clipboardPngType;
|
|
105
|
-
};
|
|
106
|
-
const loadImageSource = (src) => new Promise((resolve, reject) => {
|
|
107
|
-
const image = new Image();
|
|
108
|
-
image.crossOrigin = "anonymous";
|
|
109
|
-
image.onload = () => resolve(image);
|
|
110
|
-
image.onerror = () => {
|
|
111
|
-
reject(new Error("Unable to prepare image for clipboard."));
|
|
112
|
-
};
|
|
113
|
-
image.src = src;
|
|
114
|
-
});
|
|
115
|
-
const renderImageSourceToPngBlob = (src) => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
-
const image = yield loadImageSource(src);
|
|
117
|
-
const width = image.naturalWidth || image.width;
|
|
118
|
-
const height = image.naturalHeight || image.height;
|
|
119
|
-
if (!width || !height) {
|
|
120
|
-
throw new Error("Unable to prepare image for clipboard.");
|
|
121
|
-
}
|
|
122
|
-
const canvas = document.createElement("canvas");
|
|
123
|
-
canvas.width = width;
|
|
124
|
-
canvas.height = height;
|
|
125
|
-
const context = canvas.getContext("2d");
|
|
126
|
-
if (!context) {
|
|
127
|
-
throw new Error("Unable to prepare image for clipboard.");
|
|
128
|
-
}
|
|
129
|
-
context.drawImage(image, 0, 0, width, height);
|
|
130
|
-
return new Promise((resolve, reject) => {
|
|
131
|
-
canvas.toBlob((blob) => {
|
|
132
|
-
if (blob) {
|
|
133
|
-
resolve(blob);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
reject(new Error("Unable to prepare image for clipboard."));
|
|
137
|
-
}
|
|
138
|
-
}, clipboardPngType);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
const convertBlobToClipboardPng = (blob, fallbackSrc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
143
|
-
try {
|
|
144
|
-
return yield renderImageSourceToPngBlob(objectUrl);
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
if (!fallbackSrc || fallbackSrc === objectUrl)
|
|
148
|
-
throw error;
|
|
149
|
-
return renderImageSourceToPngBlob(fallbackSrc);
|
|
150
|
-
}
|
|
151
|
-
finally {
|
|
152
|
-
URL.revokeObjectURL(objectUrl);
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
const getClipboardImageBlob = (image, ClipboardItemCtor) => __awaiter(void 0, void 0, void 0, function* () {
|
|
156
|
-
const src = image.currentSrc || image.src;
|
|
157
|
-
const { blob, contentType } = yield getImageBlob(src);
|
|
158
|
-
const type = normalizeMimeType(blob.type) ||
|
|
159
|
-
normalizeMimeType(contentType) ||
|
|
160
|
-
getImageMimeTypeFromSource(src);
|
|
161
|
-
if (canWriteClipboardType(ClipboardItemCtor, type)) {
|
|
162
|
-
return {
|
|
163
|
-
blob: blob.type === type ? blob : new Blob([blob], { type }),
|
|
164
|
-
type,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
blob: yield convertBlobToClipboardPng(blob, src),
|
|
169
|
-
type: clipboardPngType,
|
|
170
|
-
};
|
|
67
|
+
return response.blob();
|
|
171
68
|
});
|
|
172
69
|
const copyImage = (image) => __awaiter(void 0, void 0, void 0, function* () {
|
|
173
70
|
var _a;
|
|
174
|
-
const ClipboardItemCtor = window
|
|
175
|
-
.ClipboardItem;
|
|
71
|
+
const ClipboardItemCtor = window.ClipboardItem;
|
|
176
72
|
if (!((_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.write) || !ClipboardItemCtor) {
|
|
177
73
|
throw new Error("Image copying is not supported by this browser.");
|
|
178
74
|
}
|
|
179
|
-
const
|
|
75
|
+
const blob = yield getImageBlob(image.src);
|
|
76
|
+
const type = blob.type || "image/png";
|
|
180
77
|
yield navigator.clipboard.write([
|
|
181
78
|
new ClipboardItemCtor({
|
|
182
79
|
[type]: blob,
|
|
@@ -188,7 +85,7 @@ const downloadImage = (image) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
188
85
|
let href = image.src;
|
|
189
86
|
let objectUrl = null;
|
|
190
87
|
try {
|
|
191
|
-
const
|
|
88
|
+
const blob = yield getImageBlob(image.src);
|
|
192
89
|
objectUrl = URL.createObjectURL(blob);
|
|
193
90
|
href = objectUrl;
|
|
194
91
|
}
|
|
@@ -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 "./
|
|
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 =
|
|
549
|
-
const hasSlashCommandExtension =
|
|
550
|
-
const
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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",
|