@react-email/editor 0.0.0-experimental.6 → 0.0.0-experimental.8
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/index.d.mts +426 -93
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +427 -94
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1036 -188
- package/dist/index.mjs +1008 -189
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -3
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
import {
|
|
2
|
+
import { StarterKit } from "@tiptap/starter-kit";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
import * as ReactEmailComponents from "@react-email/components";
|
|
4
5
|
import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
|
|
5
6
|
import CodeBlock$1 from "@tiptap/extension-code-block";
|
|
@@ -8,6 +9,11 @@ import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
|
8
9
|
import { fromHtml } from "hast-util-from-html";
|
|
9
10
|
import Prism from "prismjs";
|
|
10
11
|
import TipTapPlaceholder from "@tiptap/extension-placeholder";
|
|
12
|
+
import { useCurrentEditor, useEditorState } from "@tiptap/react";
|
|
13
|
+
import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code, CodeIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, StrikethroughIcon, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
|
|
14
|
+
import * as React from "react";
|
|
15
|
+
import * as Popover from "@radix-ui/react-popover";
|
|
16
|
+
import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
|
|
11
17
|
|
|
12
18
|
//#region src/core/email-node.ts
|
|
13
19
|
var EmailNode = class EmailNode extends Node {
|
|
@@ -39,6 +45,48 @@ var EmailNode = class EmailNode extends Node {
|
|
|
39
45
|
}
|
|
40
46
|
};
|
|
41
47
|
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/core/event-bus.ts
|
|
50
|
+
const EVENT_PREFIX = "@react-email/editor:";
|
|
51
|
+
var EditorEventBus = class {
|
|
52
|
+
prefixEventName(eventName) {
|
|
53
|
+
return `${EVENT_PREFIX}${String(eventName)}`;
|
|
54
|
+
}
|
|
55
|
+
dispatch(eventName, payload, options) {
|
|
56
|
+
const target = options?.target ?? window;
|
|
57
|
+
const prefixedEventName = this.prefixEventName(eventName);
|
|
58
|
+
const event = new CustomEvent(prefixedEventName, {
|
|
59
|
+
detail: payload,
|
|
60
|
+
bubbles: false,
|
|
61
|
+
cancelable: false
|
|
62
|
+
});
|
|
63
|
+
target.dispatchEvent(event);
|
|
64
|
+
}
|
|
65
|
+
on(eventName, handler, options) {
|
|
66
|
+
const target = options?.target ?? window;
|
|
67
|
+
const prefixedEventName = this.prefixEventName(eventName);
|
|
68
|
+
const abortController = new AbortController();
|
|
69
|
+
const wrappedHandler = (event) => {
|
|
70
|
+
const customEvent = event;
|
|
71
|
+
const result = handler(customEvent.detail);
|
|
72
|
+
if (result instanceof Promise) result.catch((error) => {
|
|
73
|
+
console.error(`Error in async event handler for ${prefixedEventName}:`, {
|
|
74
|
+
event: customEvent.detail,
|
|
75
|
+
error
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
target.addEventListener(prefixedEventName, wrappedHandler, {
|
|
80
|
+
...options,
|
|
81
|
+
signal: abortController.signal
|
|
82
|
+
});
|
|
83
|
+
return { unsubscribe: () => {
|
|
84
|
+
abortController.abort();
|
|
85
|
+
} };
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const editorEventBus = new EditorEventBus();
|
|
89
|
+
|
|
42
90
|
//#endregion
|
|
43
91
|
//#region src/extensions/alignment-attribute.tsx
|
|
44
92
|
const AlignmentAttribute = Extension.create({
|
|
@@ -373,12 +421,12 @@ const Body = EmailNode.create({
|
|
|
373
421
|
0
|
|
374
422
|
];
|
|
375
423
|
},
|
|
376
|
-
renderToReactEmail({ children, node,
|
|
424
|
+
renderToReactEmail({ children, node, style }) {
|
|
377
425
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
378
426
|
return /* @__PURE__ */ jsx("div", {
|
|
379
427
|
className: node.attrs?.class || void 0,
|
|
380
428
|
style: {
|
|
381
|
-
...
|
|
429
|
+
...style,
|
|
382
430
|
...inlineStyles
|
|
383
431
|
},
|
|
384
432
|
children
|
|
@@ -534,7 +582,7 @@ const Button = EmailNode.create({
|
|
|
534
582
|
}
|
|
535
583
|
};
|
|
536
584
|
},
|
|
537
|
-
renderToReactEmail({ children, node,
|
|
585
|
+
renderToReactEmail({ children, node, style }) {
|
|
538
586
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
539
587
|
return /* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Column, {
|
|
540
588
|
align: node.attrs?.align || node.attrs?.alignment,
|
|
@@ -542,8 +590,7 @@ const Button = EmailNode.create({
|
|
|
542
590
|
className: node.attrs?.class || void 0,
|
|
543
591
|
href: node.attrs?.href,
|
|
544
592
|
style: {
|
|
545
|
-
...
|
|
546
|
-
...styles.button,
|
|
593
|
+
...style,
|
|
547
594
|
...inlineStyles
|
|
548
595
|
},
|
|
549
596
|
children
|
|
@@ -772,6 +819,25 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
|
|
|
772
819
|
]
|
|
773
820
|
];
|
|
774
821
|
},
|
|
822
|
+
addKeyboardShortcuts() {
|
|
823
|
+
return {
|
|
824
|
+
...this.parent?.(),
|
|
825
|
+
"Mod-a": ({ editor }) => {
|
|
826
|
+
const { state } = editor;
|
|
827
|
+
const { selection } = state;
|
|
828
|
+
const { $from } = selection;
|
|
829
|
+
for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
|
|
830
|
+
const blockStart = $from.start(depth);
|
|
831
|
+
const blockEnd = $from.end(depth);
|
|
832
|
+
if (selection.from === blockStart && selection.to === blockEnd) return false;
|
|
833
|
+
const tr = state.tr.setSelection(TextSelection.create(state.doc, blockStart, blockEnd));
|
|
834
|
+
editor.view.dispatch(tr);
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
},
|
|
775
841
|
addProseMirrorPlugins() {
|
|
776
842
|
return [...this.parent?.() || [], PrismPlugin({
|
|
777
843
|
name: this.name,
|
|
@@ -779,7 +845,7 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
|
|
|
779
845
|
defaultTheme: this.options.defaultTheme
|
|
780
846
|
})];
|
|
781
847
|
}
|
|
782
|
-
}), ({ node,
|
|
848
|
+
}), ({ node, style }) => {
|
|
783
849
|
const language = node.attrs?.language ? `${node.attrs.language}` : "javascript";
|
|
784
850
|
const userTheme = ReactEmailComponents[node.attrs?.theme];
|
|
785
851
|
const theme = userTheme ? {
|
|
@@ -803,179 +869,9 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
|
|
|
803
869
|
theme,
|
|
804
870
|
style: {
|
|
805
871
|
width: "auto",
|
|
806
|
-
...
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
//#endregion
|
|
812
|
-
//#region src/extensions/columns.tsx
|
|
813
|
-
const COLUMN_PARENT_TYPES = [
|
|
814
|
-
"twoColumns",
|
|
815
|
-
"threeColumns",
|
|
816
|
-
"fourColumns"
|
|
817
|
-
];
|
|
818
|
-
const COLUMN_PARENT_SET = new Set(COLUMN_PARENT_TYPES);
|
|
819
|
-
const MAX_COLUMNS_DEPTH = 3;
|
|
820
|
-
function getColumnsDepth(doc, from) {
|
|
821
|
-
const $from = doc.resolve(from);
|
|
822
|
-
let depth = 0;
|
|
823
|
-
for (let d = $from.depth; d > 0; d--) if (COLUMN_PARENT_SET.has($from.node(d).type.name)) depth++;
|
|
824
|
-
return depth;
|
|
825
|
-
}
|
|
826
|
-
const VARIANTS = [
|
|
827
|
-
{
|
|
828
|
-
name: "twoColumns",
|
|
829
|
-
columnCount: 2,
|
|
830
|
-
content: "columnsColumn columnsColumn",
|
|
831
|
-
dataType: "two-columns"
|
|
832
|
-
},
|
|
833
|
-
{
|
|
834
|
-
name: "threeColumns",
|
|
835
|
-
columnCount: 3,
|
|
836
|
-
content: "columnsColumn columnsColumn columnsColumn",
|
|
837
|
-
dataType: "three-columns"
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
name: "fourColumns",
|
|
841
|
-
columnCount: 4,
|
|
842
|
-
content: "columnsColumn{4}",
|
|
843
|
-
dataType: "four-columns"
|
|
844
|
-
}
|
|
845
|
-
];
|
|
846
|
-
const NODE_TYPE_MAP = {
|
|
847
|
-
2: "twoColumns",
|
|
848
|
-
3: "threeColumns",
|
|
849
|
-
4: "fourColumns"
|
|
850
|
-
};
|
|
851
|
-
function createColumnsNode(config, includeCommands) {
|
|
852
|
-
return EmailNode.create({
|
|
853
|
-
name: config.name,
|
|
854
|
-
group: "block",
|
|
855
|
-
content: config.content,
|
|
856
|
-
isolating: true,
|
|
857
|
-
defining: true,
|
|
858
|
-
addAttributes() {
|
|
859
|
-
return createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]);
|
|
860
|
-
},
|
|
861
|
-
parseHTML() {
|
|
862
|
-
return [{ tag: `div[data-type="${config.dataType}"]` }];
|
|
863
|
-
},
|
|
864
|
-
renderHTML({ HTMLAttributes }) {
|
|
865
|
-
return [
|
|
866
|
-
"div",
|
|
867
|
-
mergeAttributes({
|
|
868
|
-
"data-type": config.dataType,
|
|
869
|
-
class: "node-columns"
|
|
870
|
-
}, HTMLAttributes),
|
|
871
|
-
0
|
|
872
|
-
];
|
|
873
|
-
},
|
|
874
|
-
...includeCommands && { addCommands() {
|
|
875
|
-
return { insertColumns: (count) => ({ commands, state }) => {
|
|
876
|
-
if (getColumnsDepth(state.doc, state.selection.from) >= MAX_COLUMNS_DEPTH) return false;
|
|
877
|
-
const nodeType = NODE_TYPE_MAP[count];
|
|
878
|
-
const children = Array.from({ length: count }, () => ({
|
|
879
|
-
type: "columnsColumn",
|
|
880
|
-
content: [{
|
|
881
|
-
type: "paragraph",
|
|
882
|
-
content: []
|
|
883
|
-
}]
|
|
884
|
-
}));
|
|
885
|
-
return commands.insertContent({
|
|
886
|
-
type: nodeType,
|
|
887
|
-
content: children
|
|
888
|
-
});
|
|
889
|
-
} };
|
|
890
|
-
} },
|
|
891
|
-
renderToReactEmail({ children, node, styles }) {
|
|
892
|
-
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
893
|
-
return /* @__PURE__ */ jsx(Row, {
|
|
894
|
-
className: node.attrs?.class || void 0,
|
|
895
|
-
style: {
|
|
896
|
-
...styles.reset,
|
|
897
|
-
...inlineStyles
|
|
898
|
-
},
|
|
899
|
-
children
|
|
900
|
-
});
|
|
872
|
+
...style
|
|
901
873
|
}
|
|
902
874
|
});
|
|
903
|
-
}
|
|
904
|
-
const TwoColumns = createColumnsNode(VARIANTS[0], true);
|
|
905
|
-
const ThreeColumns = createColumnsNode(VARIANTS[1], false);
|
|
906
|
-
const FourColumns = createColumnsNode(VARIANTS[2], false);
|
|
907
|
-
const ColumnsColumn = EmailNode.create({
|
|
908
|
-
name: "columnsColumn",
|
|
909
|
-
group: "columnsColumn",
|
|
910
|
-
content: "block+",
|
|
911
|
-
isolating: true,
|
|
912
|
-
addAttributes() {
|
|
913
|
-
return { ...createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]) };
|
|
914
|
-
},
|
|
915
|
-
parseHTML() {
|
|
916
|
-
return [{ tag: "div[data-type=\"column\"]" }];
|
|
917
|
-
},
|
|
918
|
-
renderHTML({ HTMLAttributes }) {
|
|
919
|
-
return [
|
|
920
|
-
"div",
|
|
921
|
-
mergeAttributes({
|
|
922
|
-
"data-type": "column",
|
|
923
|
-
class: "node-column"
|
|
924
|
-
}, HTMLAttributes),
|
|
925
|
-
0
|
|
926
|
-
];
|
|
927
|
-
},
|
|
928
|
-
addKeyboardShortcuts() {
|
|
929
|
-
return {
|
|
930
|
-
Backspace: ({ editor }) => {
|
|
931
|
-
const { state } = editor;
|
|
932
|
-
const { selection } = state;
|
|
933
|
-
const { empty, $from } = selection;
|
|
934
|
-
if (!empty) return false;
|
|
935
|
-
for (let depth = $from.depth; depth >= 1; depth--) {
|
|
936
|
-
if ($from.pos !== $from.start(depth)) break;
|
|
937
|
-
const indexInParent = $from.index(depth - 1);
|
|
938
|
-
if (indexInParent === 0) continue;
|
|
939
|
-
const prevNode = $from.node(depth - 1).child(indexInParent - 1);
|
|
940
|
-
if (COLUMN_PARENT_SET.has(prevNode.type.name)) {
|
|
941
|
-
const deleteFrom = $from.before(depth) - prevNode.nodeSize;
|
|
942
|
-
const deleteTo = $from.before(depth);
|
|
943
|
-
editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));
|
|
944
|
-
return true;
|
|
945
|
-
}
|
|
946
|
-
break;
|
|
947
|
-
}
|
|
948
|
-
return false;
|
|
949
|
-
},
|
|
950
|
-
"Mod-a": ({ editor }) => {
|
|
951
|
-
const { state } = editor;
|
|
952
|
-
const { $from } = state.selection;
|
|
953
|
-
for (let d = $from.depth; d > 0; d--) {
|
|
954
|
-
if ($from.node(d).type.name !== "columnsColumn") continue;
|
|
955
|
-
const columnStart = $from.start(d);
|
|
956
|
-
const columnEnd = $from.end(d);
|
|
957
|
-
const { from, to } = state.selection;
|
|
958
|
-
if (from === columnStart && to === columnEnd) return false;
|
|
959
|
-
editor.view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, columnStart, columnEnd)));
|
|
960
|
-
return true;
|
|
961
|
-
}
|
|
962
|
-
return false;
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
},
|
|
966
|
-
renderToReactEmail({ children, node, styles }) {
|
|
967
|
-
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
968
|
-
const width = node.attrs?.width;
|
|
969
|
-
return /* @__PURE__ */ jsx(Column, {
|
|
970
|
-
className: node.attrs?.class || void 0,
|
|
971
|
-
style: {
|
|
972
|
-
...styles.reset,
|
|
973
|
-
...inlineStyles,
|
|
974
|
-
...width ? { width } : {}
|
|
975
|
-
},
|
|
976
|
-
children
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
875
|
});
|
|
980
876
|
|
|
981
877
|
//#endregion
|
|
@@ -1010,12 +906,12 @@ const Div = EmailNode.create({
|
|
|
1010
906
|
addAttributes() {
|
|
1011
907
|
return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
|
|
1012
908
|
},
|
|
1013
|
-
renderToReactEmail({ children, node,
|
|
909
|
+
renderToReactEmail({ children, node, style }) {
|
|
1014
910
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1015
911
|
return /* @__PURE__ */ jsx("div", {
|
|
1016
912
|
className: node.attrs?.class || void 0,
|
|
1017
913
|
style: {
|
|
1018
|
-
...
|
|
914
|
+
...style,
|
|
1019
915
|
...inlineStyles
|
|
1020
916
|
},
|
|
1021
917
|
children
|
|
@@ -1266,14 +1162,14 @@ const Section = EmailNode.create({
|
|
|
1266
1162
|
});
|
|
1267
1163
|
} };
|
|
1268
1164
|
},
|
|
1269
|
-
renderToReactEmail({ children, node,
|
|
1165
|
+
renderToReactEmail({ children, node, style }) {
|
|
1270
1166
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1271
1167
|
const textAlign = node.attrs?.align || node.attrs?.alignment;
|
|
1272
1168
|
return /* @__PURE__ */ jsx(Section$1, {
|
|
1273
1169
|
className: node.attrs?.class || void 0,
|
|
1274
1170
|
align: textAlign,
|
|
1275
1171
|
style: {
|
|
1276
|
-
...
|
|
1172
|
+
...style,
|
|
1277
1173
|
...inlineStyles,
|
|
1278
1174
|
...getTextAlignment(textAlign)
|
|
1279
1175
|
},
|
|
@@ -1406,7 +1302,7 @@ const Table = EmailNode.create({
|
|
|
1406
1302
|
]
|
|
1407
1303
|
];
|
|
1408
1304
|
},
|
|
1409
|
-
renderToReactEmail({ children, node,
|
|
1305
|
+
renderToReactEmail({ children, node, style }) {
|
|
1410
1306
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1411
1307
|
const alignment = node.attrs?.align || node.attrs?.alignment;
|
|
1412
1308
|
const width = node.attrs?.width;
|
|
@@ -1417,7 +1313,7 @@ const Table = EmailNode.create({
|
|
|
1417
1313
|
return /* @__PURE__ */ jsx(Section$1, {
|
|
1418
1314
|
className: node.attrs?.class || void 0,
|
|
1419
1315
|
align: alignment,
|
|
1420
|
-
style: resolveConflictingStyles(
|
|
1316
|
+
style: resolveConflictingStyles(style, {
|
|
1421
1317
|
...inlineStyles,
|
|
1422
1318
|
...centeringStyles
|
|
1423
1319
|
}),
|
|
@@ -1458,12 +1354,12 @@ const TableRow = EmailNode.create({
|
|
|
1458
1354
|
0
|
|
1459
1355
|
];
|
|
1460
1356
|
},
|
|
1461
|
-
renderToReactEmail({ children, node,
|
|
1357
|
+
renderToReactEmail({ children, node, style }) {
|
|
1462
1358
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1463
1359
|
return /* @__PURE__ */ jsx("tr", {
|
|
1464
1360
|
className: node.attrs?.class || void 0,
|
|
1465
1361
|
style: {
|
|
1466
|
-
...
|
|
1362
|
+
...style,
|
|
1467
1363
|
...inlineStyles
|
|
1468
1364
|
},
|
|
1469
1365
|
children
|
|
@@ -1503,13 +1399,13 @@ const TableCell = EmailNode.create({
|
|
|
1503
1399
|
0
|
|
1504
1400
|
];
|
|
1505
1401
|
},
|
|
1506
|
-
renderToReactEmail({ children, node,
|
|
1402
|
+
renderToReactEmail({ children, node, style }) {
|
|
1507
1403
|
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1508
1404
|
return /* @__PURE__ */ jsx(Column, {
|
|
1509
1405
|
className: node.attrs?.class || void 0,
|
|
1510
1406
|
align: node.attrs?.align || node.attrs?.alignment,
|
|
1511
1407
|
style: {
|
|
1512
|
-
...
|
|
1408
|
+
...style,
|
|
1513
1409
|
...inlineStyles
|
|
1514
1410
|
},
|
|
1515
1411
|
children
|
|
@@ -1553,5 +1449,928 @@ const TableHeader = Node.create({
|
|
|
1553
1449
|
});
|
|
1554
1450
|
|
|
1555
1451
|
//#endregion
|
|
1556
|
-
|
|
1452
|
+
//#region src/extensions/uppercase.ts
|
|
1453
|
+
const Uppercase = Mark.create({
|
|
1454
|
+
name: "uppercase",
|
|
1455
|
+
addOptions() {
|
|
1456
|
+
return { HTMLAttributes: {} };
|
|
1457
|
+
},
|
|
1458
|
+
parseHTML() {
|
|
1459
|
+
return [{
|
|
1460
|
+
tag: "span",
|
|
1461
|
+
getAttrs: (node) => {
|
|
1462
|
+
if (node.style.textTransform === "uppercase") return {};
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
}];
|
|
1466
|
+
},
|
|
1467
|
+
renderHTML({ HTMLAttributes }) {
|
|
1468
|
+
return [
|
|
1469
|
+
"span",
|
|
1470
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
|
|
1471
|
+
0
|
|
1472
|
+
];
|
|
1473
|
+
},
|
|
1474
|
+
addCommands() {
|
|
1475
|
+
return {
|
|
1476
|
+
setUppercase: () => ({ commands }) => {
|
|
1477
|
+
return commands.setMark(this.name);
|
|
1478
|
+
},
|
|
1479
|
+
toggleUppercase: () => ({ commands }) => {
|
|
1480
|
+
return commands.toggleMark(this.name);
|
|
1481
|
+
},
|
|
1482
|
+
unsetUppercase: () => ({ commands }) => {
|
|
1483
|
+
return commands.unsetMark(this.name);
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
//#endregion
|
|
1490
|
+
//#region src/extensions/columns.tsx
|
|
1491
|
+
const COLUMN_PARENT_TYPES = [
|
|
1492
|
+
"twoColumns",
|
|
1493
|
+
"threeColumns",
|
|
1494
|
+
"fourColumns"
|
|
1495
|
+
];
|
|
1496
|
+
const COLUMN_PARENT_SET = new Set(COLUMN_PARENT_TYPES);
|
|
1497
|
+
const MAX_COLUMNS_DEPTH = 3;
|
|
1498
|
+
function getColumnsDepth(doc, from) {
|
|
1499
|
+
const $from = doc.resolve(from);
|
|
1500
|
+
let depth = 0;
|
|
1501
|
+
for (let d = $from.depth; d > 0; d--) if (COLUMN_PARENT_SET.has($from.node(d).type.name)) depth++;
|
|
1502
|
+
return depth;
|
|
1503
|
+
}
|
|
1504
|
+
const VARIANTS = [
|
|
1505
|
+
{
|
|
1506
|
+
name: "twoColumns",
|
|
1507
|
+
columnCount: 2,
|
|
1508
|
+
content: "columnsColumn columnsColumn",
|
|
1509
|
+
dataType: "two-columns"
|
|
1510
|
+
},
|
|
1511
|
+
{
|
|
1512
|
+
name: "threeColumns",
|
|
1513
|
+
columnCount: 3,
|
|
1514
|
+
content: "columnsColumn columnsColumn columnsColumn",
|
|
1515
|
+
dataType: "three-columns"
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
name: "fourColumns",
|
|
1519
|
+
columnCount: 4,
|
|
1520
|
+
content: "columnsColumn{4}",
|
|
1521
|
+
dataType: "four-columns"
|
|
1522
|
+
}
|
|
1523
|
+
];
|
|
1524
|
+
const NODE_TYPE_MAP = {
|
|
1525
|
+
2: "twoColumns",
|
|
1526
|
+
3: "threeColumns",
|
|
1527
|
+
4: "fourColumns"
|
|
1528
|
+
};
|
|
1529
|
+
function createColumnsNode(config, includeCommands) {
|
|
1530
|
+
return EmailNode.create({
|
|
1531
|
+
name: config.name,
|
|
1532
|
+
group: "block",
|
|
1533
|
+
content: config.content,
|
|
1534
|
+
isolating: true,
|
|
1535
|
+
defining: true,
|
|
1536
|
+
addAttributes() {
|
|
1537
|
+
return createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]);
|
|
1538
|
+
},
|
|
1539
|
+
parseHTML() {
|
|
1540
|
+
return [{ tag: `div[data-type="${config.dataType}"]` }];
|
|
1541
|
+
},
|
|
1542
|
+
renderHTML({ HTMLAttributes }) {
|
|
1543
|
+
return [
|
|
1544
|
+
"div",
|
|
1545
|
+
mergeAttributes({
|
|
1546
|
+
"data-type": config.dataType,
|
|
1547
|
+
class: "node-columns"
|
|
1548
|
+
}, HTMLAttributes),
|
|
1549
|
+
0
|
|
1550
|
+
];
|
|
1551
|
+
},
|
|
1552
|
+
...includeCommands && { addCommands() {
|
|
1553
|
+
return { insertColumns: (count) => ({ commands, state }) => {
|
|
1554
|
+
if (getColumnsDepth(state.doc, state.selection.from) >= MAX_COLUMNS_DEPTH) return false;
|
|
1555
|
+
const nodeType = NODE_TYPE_MAP[count];
|
|
1556
|
+
const children = Array.from({ length: count }, () => ({
|
|
1557
|
+
type: "columnsColumn",
|
|
1558
|
+
content: [{
|
|
1559
|
+
type: "paragraph",
|
|
1560
|
+
content: []
|
|
1561
|
+
}]
|
|
1562
|
+
}));
|
|
1563
|
+
return commands.insertContent({
|
|
1564
|
+
type: nodeType,
|
|
1565
|
+
content: children
|
|
1566
|
+
});
|
|
1567
|
+
} };
|
|
1568
|
+
} },
|
|
1569
|
+
renderToReactEmail({ children, node, style }) {
|
|
1570
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1571
|
+
return /* @__PURE__ */ jsx(Row, {
|
|
1572
|
+
className: node.attrs?.class || void 0,
|
|
1573
|
+
style: {
|
|
1574
|
+
...style,
|
|
1575
|
+
...inlineStyles
|
|
1576
|
+
},
|
|
1577
|
+
children
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
const TwoColumns = createColumnsNode(VARIANTS[0], true);
|
|
1583
|
+
const ThreeColumns = createColumnsNode(VARIANTS[1], false);
|
|
1584
|
+
const FourColumns = createColumnsNode(VARIANTS[2], false);
|
|
1585
|
+
const ColumnsColumn = EmailNode.create({
|
|
1586
|
+
name: "columnsColumn",
|
|
1587
|
+
group: "columnsColumn",
|
|
1588
|
+
content: "block+",
|
|
1589
|
+
isolating: true,
|
|
1590
|
+
addAttributes() {
|
|
1591
|
+
return { ...createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]) };
|
|
1592
|
+
},
|
|
1593
|
+
parseHTML() {
|
|
1594
|
+
return [{ tag: "div[data-type=\"column\"]" }];
|
|
1595
|
+
},
|
|
1596
|
+
renderHTML({ HTMLAttributes }) {
|
|
1597
|
+
return [
|
|
1598
|
+
"div",
|
|
1599
|
+
mergeAttributes({
|
|
1600
|
+
"data-type": "column",
|
|
1601
|
+
class: "node-column"
|
|
1602
|
+
}, HTMLAttributes),
|
|
1603
|
+
0
|
|
1604
|
+
];
|
|
1605
|
+
},
|
|
1606
|
+
addKeyboardShortcuts() {
|
|
1607
|
+
return {
|
|
1608
|
+
Backspace: ({ editor }) => {
|
|
1609
|
+
const { state } = editor;
|
|
1610
|
+
const { selection } = state;
|
|
1611
|
+
const { empty, $from } = selection;
|
|
1612
|
+
if (!empty) return false;
|
|
1613
|
+
for (let depth = $from.depth; depth >= 1; depth--) {
|
|
1614
|
+
if ($from.pos !== $from.start(depth)) break;
|
|
1615
|
+
const indexInParent = $from.index(depth - 1);
|
|
1616
|
+
if (indexInParent === 0) continue;
|
|
1617
|
+
const prevNode = $from.node(depth - 1).child(indexInParent - 1);
|
|
1618
|
+
if (COLUMN_PARENT_SET.has(prevNode.type.name)) {
|
|
1619
|
+
const deleteFrom = $from.before(depth) - prevNode.nodeSize;
|
|
1620
|
+
const deleteTo = $from.before(depth);
|
|
1621
|
+
editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));
|
|
1622
|
+
return true;
|
|
1623
|
+
}
|
|
1624
|
+
break;
|
|
1625
|
+
}
|
|
1626
|
+
return false;
|
|
1627
|
+
},
|
|
1628
|
+
"Mod-a": ({ editor }) => {
|
|
1629
|
+
const { state } = editor;
|
|
1630
|
+
const { $from } = state.selection;
|
|
1631
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
1632
|
+
if ($from.node(d).type.name !== "columnsColumn") continue;
|
|
1633
|
+
const columnStart = $from.start(d);
|
|
1634
|
+
const columnEnd = $from.end(d);
|
|
1635
|
+
const { from, to } = state.selection;
|
|
1636
|
+
if (from === columnStart && to === columnEnd) return false;
|
|
1637
|
+
editor.view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, columnStart, columnEnd)));
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
},
|
|
1644
|
+
renderToReactEmail({ children, node, style }) {
|
|
1645
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1646
|
+
const width = node.attrs?.width;
|
|
1647
|
+
return /* @__PURE__ */ jsx(Column, {
|
|
1648
|
+
className: node.attrs?.class || void 0,
|
|
1649
|
+
style: {
|
|
1650
|
+
...style,
|
|
1651
|
+
...inlineStyles,
|
|
1652
|
+
...width ? { width } : {}
|
|
1653
|
+
},
|
|
1654
|
+
children
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
//#endregion
|
|
1660
|
+
//#region src/extensions/index.ts
|
|
1661
|
+
const coreExtensions = [
|
|
1662
|
+
StarterKit.configure({
|
|
1663
|
+
undoRedo: false,
|
|
1664
|
+
heading: false,
|
|
1665
|
+
link: false,
|
|
1666
|
+
underline: false,
|
|
1667
|
+
trailingNode: false,
|
|
1668
|
+
bold: false,
|
|
1669
|
+
gapcursor: false,
|
|
1670
|
+
listItem: {},
|
|
1671
|
+
bulletList: { HTMLAttributes: { class: "node-bulletList" } },
|
|
1672
|
+
paragraph: { HTMLAttributes: { class: "node-paragraph" } },
|
|
1673
|
+
orderedList: { HTMLAttributes: { class: "node-orderedList" } },
|
|
1674
|
+
blockquote: { HTMLAttributes: { class: "node-blockquote" } },
|
|
1675
|
+
codeBlock: false,
|
|
1676
|
+
code: { HTMLAttributes: {
|
|
1677
|
+
class: "node-inlineCode",
|
|
1678
|
+
spellcheck: "false"
|
|
1679
|
+
} },
|
|
1680
|
+
horizontalRule: false,
|
|
1681
|
+
dropcursor: {
|
|
1682
|
+
color: "#61a8f8",
|
|
1683
|
+
class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
|
|
1684
|
+
width: 4
|
|
1685
|
+
}
|
|
1686
|
+
}),
|
|
1687
|
+
CodeBlockPrism.configure({
|
|
1688
|
+
defaultLanguage: "javascript",
|
|
1689
|
+
HTMLAttributes: { class: "prism node-codeBlock" }
|
|
1690
|
+
}),
|
|
1691
|
+
Placeholder,
|
|
1692
|
+
PreviewText,
|
|
1693
|
+
Bold,
|
|
1694
|
+
Sup,
|
|
1695
|
+
Uppercase,
|
|
1696
|
+
PreservedStyle,
|
|
1697
|
+
Table,
|
|
1698
|
+
TableRow,
|
|
1699
|
+
TableCell,
|
|
1700
|
+
TableHeader,
|
|
1701
|
+
Body,
|
|
1702
|
+
Div,
|
|
1703
|
+
Button,
|
|
1704
|
+
Section,
|
|
1705
|
+
AlignmentAttribute.configure({ types: [
|
|
1706
|
+
"heading",
|
|
1707
|
+
"paragraph",
|
|
1708
|
+
"image",
|
|
1709
|
+
"blockquote",
|
|
1710
|
+
"codeBlock",
|
|
1711
|
+
"bulletList",
|
|
1712
|
+
"orderedList",
|
|
1713
|
+
"listItem",
|
|
1714
|
+
"button",
|
|
1715
|
+
"youtube",
|
|
1716
|
+
"twitter",
|
|
1717
|
+
"table",
|
|
1718
|
+
"tableRow",
|
|
1719
|
+
"tableCell",
|
|
1720
|
+
"tableHeader",
|
|
1721
|
+
"columnsColumn"
|
|
1722
|
+
] }),
|
|
1723
|
+
StyleAttribute.configure({ types: [
|
|
1724
|
+
"heading",
|
|
1725
|
+
"paragraph",
|
|
1726
|
+
"image",
|
|
1727
|
+
"blockquote",
|
|
1728
|
+
"codeBlock",
|
|
1729
|
+
"bulletList",
|
|
1730
|
+
"orderedList",
|
|
1731
|
+
"listItem",
|
|
1732
|
+
"button",
|
|
1733
|
+
"youtube",
|
|
1734
|
+
"twitter",
|
|
1735
|
+
"horizontalRule",
|
|
1736
|
+
"footer",
|
|
1737
|
+
"section",
|
|
1738
|
+
"div",
|
|
1739
|
+
"body",
|
|
1740
|
+
"table",
|
|
1741
|
+
"tableRow",
|
|
1742
|
+
"tableCell",
|
|
1743
|
+
"tableHeader",
|
|
1744
|
+
"columnsColumn",
|
|
1745
|
+
"link"
|
|
1746
|
+
] }),
|
|
1747
|
+
ClassAttribute.configure({ types: [
|
|
1748
|
+
"heading",
|
|
1749
|
+
"paragraph",
|
|
1750
|
+
"image",
|
|
1751
|
+
"blockquote",
|
|
1752
|
+
"bulletList",
|
|
1753
|
+
"orderedList",
|
|
1754
|
+
"listItem",
|
|
1755
|
+
"button",
|
|
1756
|
+
"youtube",
|
|
1757
|
+
"twitter",
|
|
1758
|
+
"horizontalRule",
|
|
1759
|
+
"footer",
|
|
1760
|
+
"section",
|
|
1761
|
+
"div",
|
|
1762
|
+
"body",
|
|
1763
|
+
"table",
|
|
1764
|
+
"tableRow",
|
|
1765
|
+
"tableCell",
|
|
1766
|
+
"tableHeader",
|
|
1767
|
+
"columnsColumn",
|
|
1768
|
+
"link"
|
|
1769
|
+
] }),
|
|
1770
|
+
MaxNesting.configure({
|
|
1771
|
+
maxDepth: 50,
|
|
1772
|
+
nodeTypes: [
|
|
1773
|
+
"section",
|
|
1774
|
+
"bulletList",
|
|
1775
|
+
"orderedList"
|
|
1776
|
+
]
|
|
1777
|
+
})
|
|
1778
|
+
];
|
|
1779
|
+
|
|
1780
|
+
//#endregion
|
|
1781
|
+
//#region src/utils/set-text-alignment.ts
|
|
1782
|
+
function setTextAlignment(editor, alignment) {
|
|
1783
|
+
const { from, to } = editor.state.selection;
|
|
1784
|
+
const tr = editor.state.tr;
|
|
1785
|
+
editor.state.doc.nodesBetween(from, to, (node, pos) => {
|
|
1786
|
+
if (node.isTextblock) {
|
|
1787
|
+
const prop = "align" in node.attrs ? "align" : "alignment";
|
|
1788
|
+
tr.setNodeMarkup(pos, null, {
|
|
1789
|
+
...node.attrs,
|
|
1790
|
+
[prop]: alignment
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
editor.view.dispatch(tr);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
//#endregion
|
|
1798
|
+
//#region src/ui/bubble-menu/context.tsx
|
|
1799
|
+
const BubbleMenuContext = React.createContext(null);
|
|
1800
|
+
function useBubbleMenuContext() {
|
|
1801
|
+
const context = React.useContext(BubbleMenuContext);
|
|
1802
|
+
if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
|
|
1803
|
+
return context;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
//#endregion
|
|
1807
|
+
//#region src/ui/bubble-menu/item.tsx
|
|
1808
|
+
function BubbleMenuItem({ name, isActive, onCommand, className, children,...rest }) {
|
|
1809
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1810
|
+
type: "button",
|
|
1811
|
+
"aria-label": name,
|
|
1812
|
+
"aria-pressed": isActive,
|
|
1813
|
+
className,
|
|
1814
|
+
"data-re-bubble-menu-item": "",
|
|
1815
|
+
"data-item": name,
|
|
1816
|
+
...isActive ? { "data-active": "" } : {},
|
|
1817
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1818
|
+
onClick: onCommand,
|
|
1819
|
+
...rest,
|
|
1820
|
+
children
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
//#endregion
|
|
1825
|
+
//#region src/ui/bubble-menu/align-center.tsx
|
|
1826
|
+
function BubbleMenuAlignCenter({ className, children }) {
|
|
1827
|
+
const { editor } = useBubbleMenuContext();
|
|
1828
|
+
return /* @__PURE__ */ jsx(BubbleMenuItem, {
|
|
1829
|
+
name: "align-center",
|
|
1830
|
+
isActive: useEditorState({
|
|
1831
|
+
editor,
|
|
1832
|
+
selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
|
|
1833
|
+
}),
|
|
1834
|
+
onCommand: () => setTextAlignment(editor, "center"),
|
|
1835
|
+
className,
|
|
1836
|
+
children: children ?? /* @__PURE__ */ jsx(AlignCenterIcon, {})
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
//#endregion
|
|
1841
|
+
//#region src/ui/bubble-menu/align-left.tsx
|
|
1842
|
+
function BubbleMenuAlignLeft({ className, children }) {
|
|
1843
|
+
const { editor } = useBubbleMenuContext();
|
|
1844
|
+
return /* @__PURE__ */ jsx(BubbleMenuItem, {
|
|
1845
|
+
name: "align-left",
|
|
1846
|
+
isActive: useEditorState({
|
|
1847
|
+
editor,
|
|
1848
|
+
selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
|
|
1849
|
+
}),
|
|
1850
|
+
onCommand: () => setTextAlignment(editor, "left"),
|
|
1851
|
+
className,
|
|
1852
|
+
children: children ?? /* @__PURE__ */ jsx(AlignLeftIcon, {})
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
//#endregion
|
|
1857
|
+
//#region src/ui/bubble-menu/align-right.tsx
|
|
1858
|
+
function BubbleMenuAlignRight({ className, children }) {
|
|
1859
|
+
const { editor } = useBubbleMenuContext();
|
|
1860
|
+
return /* @__PURE__ */ jsx(BubbleMenuItem, {
|
|
1861
|
+
name: "align-right",
|
|
1862
|
+
isActive: useEditorState({
|
|
1863
|
+
editor,
|
|
1864
|
+
selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
|
|
1865
|
+
}),
|
|
1866
|
+
onCommand: () => setTextAlignment(editor, "right"),
|
|
1867
|
+
className,
|
|
1868
|
+
children: children ?? /* @__PURE__ */ jsx(AlignRightIcon, {})
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
//#endregion
|
|
1873
|
+
//#region src/ui/bubble-menu/create-mark-bubble-item.tsx
|
|
1874
|
+
function createMarkBubbleItem(config) {
|
|
1875
|
+
function MarkBubbleItem({ className, children }) {
|
|
1876
|
+
const { editor } = useBubbleMenuContext();
|
|
1877
|
+
const isActive = useEditorState({
|
|
1878
|
+
editor,
|
|
1879
|
+
selector: ({ editor: editor$1 }) => {
|
|
1880
|
+
if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
|
|
1881
|
+
return editor$1?.isActive(config.activeName) ?? false;
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
const handleCommand = () => {
|
|
1885
|
+
const chain = editor.chain().focus();
|
|
1886
|
+
const method = chain[config.command];
|
|
1887
|
+
if (method) method.call(chain).run();
|
|
1888
|
+
};
|
|
1889
|
+
return /* @__PURE__ */ jsx(BubbleMenuItem, {
|
|
1890
|
+
name: config.name,
|
|
1891
|
+
isActive,
|
|
1892
|
+
onCommand: handleCommand,
|
|
1893
|
+
className,
|
|
1894
|
+
children: children ?? config.icon
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
|
|
1898
|
+
return MarkBubbleItem;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
//#endregion
|
|
1902
|
+
//#region src/ui/bubble-menu/bold.tsx
|
|
1903
|
+
const BubbleMenuBold = createMarkBubbleItem({
|
|
1904
|
+
name: "bold",
|
|
1905
|
+
activeName: "bold",
|
|
1906
|
+
command: "toggleBold",
|
|
1907
|
+
icon: /* @__PURE__ */ jsx(BoldIcon, {})
|
|
1908
|
+
});
|
|
1909
|
+
|
|
1910
|
+
//#endregion
|
|
1911
|
+
//#region src/ui/bubble-menu/code.tsx
|
|
1912
|
+
const BubbleMenuCode = createMarkBubbleItem({
|
|
1913
|
+
name: "code",
|
|
1914
|
+
activeName: "code",
|
|
1915
|
+
command: "toggleCode",
|
|
1916
|
+
icon: /* @__PURE__ */ jsx(CodeIcon, {})
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1919
|
+
//#endregion
|
|
1920
|
+
//#region src/ui/bubble-menu/group.tsx
|
|
1921
|
+
function BubbleMenuItemGroup({ className, children }) {
|
|
1922
|
+
return /* @__PURE__ */ jsx("fieldset", {
|
|
1923
|
+
className,
|
|
1924
|
+
"data-re-bubble-menu-group": "",
|
|
1925
|
+
children
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
//#endregion
|
|
1930
|
+
//#region src/ui/bubble-menu/italic.tsx
|
|
1931
|
+
const BubbleMenuItalic = createMarkBubbleItem({
|
|
1932
|
+
name: "italic",
|
|
1933
|
+
activeName: "italic",
|
|
1934
|
+
command: "toggleItalic",
|
|
1935
|
+
icon: /* @__PURE__ */ jsx(ItalicIcon, {})
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
//#endregion
|
|
1939
|
+
//#region src/ui/bubble-menu/utils.ts
|
|
1940
|
+
const SAFE_PROTOCOLS = new Set([
|
|
1941
|
+
"http:",
|
|
1942
|
+
"https:",
|
|
1943
|
+
"mailto:",
|
|
1944
|
+
"tel:"
|
|
1945
|
+
]);
|
|
1946
|
+
/**
|
|
1947
|
+
* Basic URL validation and auto-prefixing.
|
|
1948
|
+
* Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
|
|
1949
|
+
* Returns the valid URL string or null.
|
|
1950
|
+
*/
|
|
1951
|
+
function getUrlFromString(str) {
|
|
1952
|
+
if (str === "#") return str;
|
|
1953
|
+
try {
|
|
1954
|
+
const url = new URL(str);
|
|
1955
|
+
if (SAFE_PROTOCOLS.has(url.protocol)) return str;
|
|
1956
|
+
return null;
|
|
1957
|
+
} catch {}
|
|
1958
|
+
try {
|
|
1959
|
+
if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
|
|
1960
|
+
} catch {}
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
//#endregion
|
|
1965
|
+
//#region src/ui/bubble-menu/link-selector.tsx
|
|
1966
|
+
function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children }) {
|
|
1967
|
+
const { editor } = useBubbleMenuContext();
|
|
1968
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
1969
|
+
const editorState = useEditorState({
|
|
1970
|
+
editor,
|
|
1971
|
+
selector: ({ editor: editor$1 }) => ({
|
|
1972
|
+
isLinkActive: editor$1?.isActive("link") ?? false,
|
|
1973
|
+
hasLink: Boolean(editor$1?.getAttributes("link").href),
|
|
1974
|
+
currentHref: editor$1?.getAttributes("link").href || ""
|
|
1975
|
+
})
|
|
1976
|
+
});
|
|
1977
|
+
React.useEffect(() => {
|
|
1978
|
+
const subscription = editorEventBus.on("bubble-menu:add-link", () => {
|
|
1979
|
+
setIsOpen(true);
|
|
1980
|
+
});
|
|
1981
|
+
return () => {
|
|
1982
|
+
setIsOpen(false);
|
|
1983
|
+
subscription.unsubscribe();
|
|
1984
|
+
};
|
|
1985
|
+
}, []);
|
|
1986
|
+
if (!editorState) return null;
|
|
1987
|
+
const handleOpenLink = () => {
|
|
1988
|
+
setIsOpen(!isOpen);
|
|
1989
|
+
};
|
|
1990
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1991
|
+
"data-re-link-selector": "",
|
|
1992
|
+
...isOpen ? { "data-open": "" } : {},
|
|
1993
|
+
...editorState.hasLink ? { "data-has-link": "" } : {},
|
|
1994
|
+
className,
|
|
1995
|
+
children: [showToggle && /* @__PURE__ */ jsx("button", {
|
|
1996
|
+
type: "button",
|
|
1997
|
+
"aria-expanded": isOpen,
|
|
1998
|
+
"aria-haspopup": "true",
|
|
1999
|
+
"aria-label": "Add link",
|
|
2000
|
+
"aria-pressed": editorState.isLinkActive && editorState.hasLink,
|
|
2001
|
+
"data-re-link-selector-trigger": "",
|
|
2002
|
+
onClick: handleOpenLink,
|
|
2003
|
+
children: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
2004
|
+
}), isOpen && /* @__PURE__ */ jsx(LinkForm, {
|
|
2005
|
+
editor,
|
|
2006
|
+
currentHref: editorState.currentHref,
|
|
2007
|
+
validateUrl,
|
|
2008
|
+
onLinkApply,
|
|
2009
|
+
onLinkRemove,
|
|
2010
|
+
setIsOpen,
|
|
2011
|
+
children
|
|
2012
|
+
})]
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
|
|
2016
|
+
const inputRef = React.useRef(null);
|
|
2017
|
+
const formRef = React.useRef(null);
|
|
2018
|
+
const displayHref = currentHref === "#" ? "" : currentHref;
|
|
2019
|
+
const [inputValue, setInputValue] = React.useState(displayHref);
|
|
2020
|
+
React.useEffect(() => {
|
|
2021
|
+
const timeoutId = setTimeout(() => {
|
|
2022
|
+
inputRef.current?.focus();
|
|
2023
|
+
}, 0);
|
|
2024
|
+
return () => clearTimeout(timeoutId);
|
|
2025
|
+
}, []);
|
|
2026
|
+
React.useEffect(() => {
|
|
2027
|
+
const handleKeyDown = (event) => {
|
|
2028
|
+
if (event.key === "Escape") {
|
|
2029
|
+
if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
|
|
2030
|
+
setIsOpen(false);
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
const handleClickOutside = (event) => {
|
|
2034
|
+
if (formRef.current && !formRef.current.contains(event.target)) {
|
|
2035
|
+
const form = formRef.current;
|
|
2036
|
+
const submitEvent = new Event("submit", {
|
|
2037
|
+
bubbles: true,
|
|
2038
|
+
cancelable: true
|
|
2039
|
+
});
|
|
2040
|
+
form.dispatchEvent(submitEvent);
|
|
2041
|
+
setIsOpen(false);
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
2045
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
2046
|
+
return () => {
|
|
2047
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
2048
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
2049
|
+
};
|
|
2050
|
+
}, [editor, setIsOpen]);
|
|
2051
|
+
function handleSubmit(e) {
|
|
2052
|
+
e.preventDefault();
|
|
2053
|
+
const value = inputValue.trim();
|
|
2054
|
+
if (value === "") {
|
|
2055
|
+
setLinkHref(editor, "");
|
|
2056
|
+
setIsOpen(false);
|
|
2057
|
+
focusEditor(editor);
|
|
2058
|
+
onLinkRemove?.();
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
const finalValue = (validateUrl ?? getUrlFromString)(value);
|
|
2062
|
+
if (!finalValue) {
|
|
2063
|
+
setLinkHref(editor, "");
|
|
2064
|
+
setIsOpen(false);
|
|
2065
|
+
focusEditor(editor);
|
|
2066
|
+
onLinkRemove?.();
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
setLinkHref(editor, finalValue);
|
|
2070
|
+
setIsOpen(false);
|
|
2071
|
+
focusEditor(editor);
|
|
2072
|
+
onLinkApply?.(finalValue);
|
|
2073
|
+
}
|
|
2074
|
+
function handleUnlink(e) {
|
|
2075
|
+
e.stopPropagation();
|
|
2076
|
+
setLinkHref(editor, "");
|
|
2077
|
+
setIsOpen(false);
|
|
2078
|
+
focusEditor(editor);
|
|
2079
|
+
onLinkRemove?.();
|
|
2080
|
+
}
|
|
2081
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
2082
|
+
ref: formRef,
|
|
2083
|
+
"data-re-link-selector-form": "",
|
|
2084
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2085
|
+
onClick: (e) => e.stopPropagation(),
|
|
2086
|
+
onKeyDown: (e) => e.stopPropagation(),
|
|
2087
|
+
onSubmit: handleSubmit,
|
|
2088
|
+
children: [
|
|
2089
|
+
/* @__PURE__ */ jsx("input", {
|
|
2090
|
+
ref: inputRef,
|
|
2091
|
+
"data-re-link-selector-input": "",
|
|
2092
|
+
value: inputValue,
|
|
2093
|
+
onFocus: (e) => e.stopPropagation(),
|
|
2094
|
+
onChange: (e) => setInputValue(e.target.value),
|
|
2095
|
+
placeholder: "Paste a link",
|
|
2096
|
+
type: "text"
|
|
2097
|
+
}),
|
|
2098
|
+
children,
|
|
2099
|
+
displayHref ? /* @__PURE__ */ jsx("button", {
|
|
2100
|
+
type: "button",
|
|
2101
|
+
"aria-label": "Remove link",
|
|
2102
|
+
"data-re-link-selector-unlink": "",
|
|
2103
|
+
onClick: handleUnlink,
|
|
2104
|
+
children: /* @__PURE__ */ jsx(UnlinkIcon, {})
|
|
2105
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
2106
|
+
type: "submit",
|
|
2107
|
+
"aria-label": "Apply link",
|
|
2108
|
+
"data-re-link-selector-apply": "",
|
|
2109
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2110
|
+
children: /* @__PURE__ */ jsx(Check, {})
|
|
2111
|
+
})
|
|
2112
|
+
]
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
function setLinkHref(editor, href) {
|
|
2116
|
+
if (href.length === 0) {
|
|
2117
|
+
editor.chain().unsetLink().run();
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
const { from, to } = editor.state.selection;
|
|
2121
|
+
if (from === to) {
|
|
2122
|
+
editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
|
|
2123
|
+
from,
|
|
2124
|
+
to
|
|
2125
|
+
}).run();
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
editor.chain().setLink({ href }).run();
|
|
2129
|
+
}
|
|
2130
|
+
function focusEditor(editor) {
|
|
2131
|
+
setTimeout(() => {
|
|
2132
|
+
editor.commands.focus();
|
|
2133
|
+
}, 0);
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
//#endregion
|
|
2137
|
+
//#region src/ui/bubble-menu/node-selector.tsx
|
|
2138
|
+
const NodeSelectorContext = React.createContext(null);
|
|
2139
|
+
function useNodeSelectorContext() {
|
|
2140
|
+
const context = React.useContext(NodeSelectorContext);
|
|
2141
|
+
if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
|
|
2142
|
+
return context;
|
|
2143
|
+
}
|
|
2144
|
+
function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
|
|
2145
|
+
const { editor } = useBubbleMenuContext();
|
|
2146
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
|
|
2147
|
+
const isControlled = controlledOpen !== void 0;
|
|
2148
|
+
const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
|
|
2149
|
+
const setIsOpen = React.useCallback((value) => {
|
|
2150
|
+
if (!isControlled) setUncontrolledOpen(value);
|
|
2151
|
+
onOpenChange?.(value);
|
|
2152
|
+
}, [isControlled, onOpenChange]);
|
|
2153
|
+
const editorState = useEditorState({
|
|
2154
|
+
editor,
|
|
2155
|
+
selector: ({ editor: editor$1 }) => ({
|
|
2156
|
+
isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
|
|
2157
|
+
isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
|
|
2158
|
+
isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
|
|
2159
|
+
isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
|
|
2160
|
+
isBulletListActive: editor$1?.isActive("bulletList") ?? false,
|
|
2161
|
+
isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
|
|
2162
|
+
isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
|
|
2163
|
+
isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
|
|
2164
|
+
})
|
|
2165
|
+
});
|
|
2166
|
+
const allItems = React.useMemo(() => [
|
|
2167
|
+
{
|
|
2168
|
+
name: "Text",
|
|
2169
|
+
icon: TextIcon,
|
|
2170
|
+
command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
|
|
2171
|
+
isActive: editorState?.isParagraphActive ?? false
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
name: "Title",
|
|
2175
|
+
icon: Heading1,
|
|
2176
|
+
command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
|
|
2177
|
+
isActive: editorState?.isHeading1Active ?? false
|
|
2178
|
+
},
|
|
2179
|
+
{
|
|
2180
|
+
name: "Subtitle",
|
|
2181
|
+
icon: Heading2,
|
|
2182
|
+
command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
|
|
2183
|
+
isActive: editorState?.isHeading2Active ?? false
|
|
2184
|
+
},
|
|
2185
|
+
{
|
|
2186
|
+
name: "Heading",
|
|
2187
|
+
icon: Heading3,
|
|
2188
|
+
command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
|
|
2189
|
+
isActive: editorState?.isHeading3Active ?? false
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
name: "Bullet List",
|
|
2193
|
+
icon: List,
|
|
2194
|
+
command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
|
|
2195
|
+
isActive: editorState?.isBulletListActive ?? false
|
|
2196
|
+
},
|
|
2197
|
+
{
|
|
2198
|
+
name: "Numbered List",
|
|
2199
|
+
icon: ListOrdered,
|
|
2200
|
+
command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
|
|
2201
|
+
isActive: editorState?.isOrderedListActive ?? false
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
name: "Quote",
|
|
2205
|
+
icon: TextQuote,
|
|
2206
|
+
command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
|
|
2207
|
+
isActive: editorState?.isBlockquoteActive ?? false
|
|
2208
|
+
},
|
|
2209
|
+
{
|
|
2210
|
+
name: "Code",
|
|
2211
|
+
icon: Code,
|
|
2212
|
+
command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
|
|
2213
|
+
isActive: editorState?.isCodeBlockActive ?? false
|
|
2214
|
+
}
|
|
2215
|
+
], [editor, editorState]);
|
|
2216
|
+
const items = React.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
|
|
2217
|
+
const activeItem = React.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
|
|
2218
|
+
const contextValue = React.useMemo(() => ({
|
|
2219
|
+
items,
|
|
2220
|
+
activeItem,
|
|
2221
|
+
isOpen,
|
|
2222
|
+
setIsOpen
|
|
2223
|
+
}), [
|
|
2224
|
+
items,
|
|
2225
|
+
activeItem,
|
|
2226
|
+
isOpen,
|
|
2227
|
+
setIsOpen
|
|
2228
|
+
]);
|
|
2229
|
+
if (!editorState || items.length === 0) return null;
|
|
2230
|
+
return /* @__PURE__ */ jsx(NodeSelectorContext.Provider, {
|
|
2231
|
+
value: contextValue,
|
|
2232
|
+
children: /* @__PURE__ */ jsx(Popover.Root, {
|
|
2233
|
+
open: isOpen,
|
|
2234
|
+
onOpenChange: setIsOpen,
|
|
2235
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2236
|
+
"data-re-node-selector": "",
|
|
2237
|
+
...isOpen ? { "data-open": "" } : {},
|
|
2238
|
+
className,
|
|
2239
|
+
children
|
|
2240
|
+
})
|
|
2241
|
+
})
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
function NodeSelectorTrigger({ className, children }) {
|
|
2245
|
+
const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
|
|
2246
|
+
return /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2247
|
+
"data-re-node-selector-trigger": "",
|
|
2248
|
+
className,
|
|
2249
|
+
onClick: () => setIsOpen(!isOpen),
|
|
2250
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: activeItem.name }), /* @__PURE__ */ jsx(ChevronDown, {})] })
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
function NodeSelectorContent({ className, align = "start", children }) {
|
|
2254
|
+
const { items, setIsOpen } = useNodeSelectorContext();
|
|
2255
|
+
return /* @__PURE__ */ jsx(Popover.Content, {
|
|
2256
|
+
align,
|
|
2257
|
+
"data-re-node-selector-content": "",
|
|
2258
|
+
className,
|
|
2259
|
+
children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
|
|
2260
|
+
const Icon = item.icon;
|
|
2261
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2262
|
+
type: "button",
|
|
2263
|
+
"data-re-node-selector-item": "",
|
|
2264
|
+
...item.isActive ? { "data-active": "" } : {},
|
|
2265
|
+
onClick: () => {
|
|
2266
|
+
item.command();
|
|
2267
|
+
setIsOpen(false);
|
|
2268
|
+
},
|
|
2269
|
+
children: [
|
|
2270
|
+
/* @__PURE__ */ jsx(Icon, {}),
|
|
2271
|
+
/* @__PURE__ */ jsx("span", { children: item.name }),
|
|
2272
|
+
item.isActive && /* @__PURE__ */ jsx(Check, {})
|
|
2273
|
+
]
|
|
2274
|
+
}, item.name);
|
|
2275
|
+
})
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
|
|
2279
|
+
return /* @__PURE__ */ jsxs(NodeSelectorRoot, {
|
|
2280
|
+
omit,
|
|
2281
|
+
open,
|
|
2282
|
+
onOpenChange,
|
|
2283
|
+
className,
|
|
2284
|
+
children: [/* @__PURE__ */ jsx(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ jsx(NodeSelectorContent, {})]
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
//#endregion
|
|
2289
|
+
//#region src/ui/bubble-menu/root.tsx
|
|
2290
|
+
function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset = 8, onHide, className, children }) {
|
|
2291
|
+
const { editor } = useCurrentEditor();
|
|
2292
|
+
if (!editor) return null;
|
|
2293
|
+
return /* @__PURE__ */ jsx(BubbleMenu$1, {
|
|
2294
|
+
editor,
|
|
2295
|
+
"data-re-bubble-menu": "",
|
|
2296
|
+
shouldShow: ({ editor: editor$1, view }) => {
|
|
2297
|
+
for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
|
|
2298
|
+
if (view.dom.classList.contains("dragging")) return false;
|
|
2299
|
+
return editor$1.view.state.selection.content().size > 0;
|
|
2300
|
+
},
|
|
2301
|
+
options: {
|
|
2302
|
+
placement,
|
|
2303
|
+
offset,
|
|
2304
|
+
onHide
|
|
2305
|
+
},
|
|
2306
|
+
className,
|
|
2307
|
+
children: /* @__PURE__ */ jsx(BubbleMenuContext.Provider, {
|
|
2308
|
+
value: { editor },
|
|
2309
|
+
children
|
|
2310
|
+
})
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
//#endregion
|
|
2315
|
+
//#region src/ui/bubble-menu/separator.tsx
|
|
2316
|
+
function BubbleMenuSeparator({ className }) {
|
|
2317
|
+
return /* @__PURE__ */ jsx("hr", {
|
|
2318
|
+
className,
|
|
2319
|
+
"data-re-bubble-menu-separator": ""
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
//#endregion
|
|
2324
|
+
//#region src/ui/bubble-menu/strike.tsx
|
|
2325
|
+
const BubbleMenuStrike = createMarkBubbleItem({
|
|
2326
|
+
name: "strike",
|
|
2327
|
+
activeName: "strike",
|
|
2328
|
+
command: "toggleStrike",
|
|
2329
|
+
icon: /* @__PURE__ */ jsx(StrikethroughIcon, {})
|
|
2330
|
+
});
|
|
2331
|
+
|
|
2332
|
+
//#endregion
|
|
2333
|
+
//#region src/ui/bubble-menu/underline.tsx
|
|
2334
|
+
const BubbleMenuUnderline = createMarkBubbleItem({
|
|
2335
|
+
name: "underline",
|
|
2336
|
+
activeName: "underline",
|
|
2337
|
+
command: "toggleUnderline",
|
|
2338
|
+
icon: /* @__PURE__ */ jsx(UnderlineIcon, {})
|
|
2339
|
+
});
|
|
2340
|
+
|
|
2341
|
+
//#endregion
|
|
2342
|
+
//#region src/ui/bubble-menu/uppercase.tsx
|
|
2343
|
+
const BubbleMenuUppercase = createMarkBubbleItem({
|
|
2344
|
+
name: "uppercase",
|
|
2345
|
+
activeName: "uppercase",
|
|
2346
|
+
command: "toggleUppercase",
|
|
2347
|
+
icon: /* @__PURE__ */ jsx(CaseUpperIcon, {})
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
//#endregion
|
|
2351
|
+
//#region src/ui/bubble-menu/index.ts
|
|
2352
|
+
const BubbleMenu = {
|
|
2353
|
+
Root: BubbleMenuRoot,
|
|
2354
|
+
ItemGroup: BubbleMenuItemGroup,
|
|
2355
|
+
Separator: BubbleMenuSeparator,
|
|
2356
|
+
Item: BubbleMenuItem,
|
|
2357
|
+
Bold: BubbleMenuBold,
|
|
2358
|
+
Italic: BubbleMenuItalic,
|
|
2359
|
+
Underline: BubbleMenuUnderline,
|
|
2360
|
+
Strike: BubbleMenuStrike,
|
|
2361
|
+
Code: BubbleMenuCode,
|
|
2362
|
+
Uppercase: BubbleMenuUppercase,
|
|
2363
|
+
AlignLeft: BubbleMenuAlignLeft,
|
|
2364
|
+
AlignCenter: BubbleMenuAlignCenter,
|
|
2365
|
+
AlignRight: BubbleMenuAlignRight,
|
|
2366
|
+
NodeSelector: Object.assign(BubbleMenuNodeSelector, {
|
|
2367
|
+
Root: NodeSelectorRoot,
|
|
2368
|
+
Trigger: NodeSelectorTrigger,
|
|
2369
|
+
Content: NodeSelectorContent
|
|
2370
|
+
}),
|
|
2371
|
+
LinkSelector: BubbleMenuLinkSelector
|
|
2372
|
+
};
|
|
2373
|
+
|
|
2374
|
+
//#endregion
|
|
2375
|
+
export { AlignmentAttribute, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, Button, COLUMN_PARENT_TYPES, ClassAttribute, CodeBlockPrism, ColumnsColumn, Div, EmailNode, FourColumns, MAX_COLUMNS_DEPTH, MaxNesting, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, Placeholder, PreservedStyle, PreviewText, Section, StyleAttribute, Sup, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, coreExtensions, editorEventBus, getColumnsDepth, processStylesForUnlink, setTextAlignment };
|
|
1557
2376
|
//# sourceMappingURL=index.mjs.map
|