@react-email/editor 0.0.0-experimental.15 → 0.0.0-experimental.16
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.cjs +738 -215
- package/dist/index.d.cts +204 -87
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +164 -47
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +724 -221
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -5
package/dist/index.mjs
CHANGED
|
@@ -1,54 +1,34 @@
|
|
|
1
|
-
import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
import { StarterKit } from "@tiptap/starter-kit";
|
|
3
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
1
|
import * as ReactEmailComponents from "@react-email/components";
|
|
5
|
-
import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
|
|
2
|
+
import { Body as Body$1, Button as Button$1, CodeBlock, Column, Head, Html, Preview, Row, Section as Section$1, pretty, render, toPlainText } from "@react-email/components";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Extension, Mark, Node as Node$1, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
5
|
+
import { UndoRedo } from "@tiptap/extensions";
|
|
6
|
+
import { ReactRenderer, useCurrentEditor, useEditor as useEditor$1, useEditorState } from "@tiptap/react";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
9
|
+
import { StarterKit } from "@tiptap/starter-kit";
|
|
10
|
+
import BlockquoteBase from "@tiptap/extension-blockquote";
|
|
11
|
+
import BulletListBase from "@tiptap/extension-bullet-list";
|
|
12
|
+
import CodeBase from "@tiptap/extension-code";
|
|
6
13
|
import CodeBlock$1 from "@tiptap/extension-code-block";
|
|
7
14
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
15
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
9
16
|
import { fromHtml } from "hast-util-from-html";
|
|
10
17
|
import Prism from "prismjs";
|
|
18
|
+
import HardBreakBase from "@tiptap/extension-hard-break";
|
|
19
|
+
import ItalicBase from "@tiptap/extension-italic";
|
|
20
|
+
import ListItemBase from "@tiptap/extension-list-item";
|
|
21
|
+
import OrderedListBase from "@tiptap/extension-ordered-list";
|
|
22
|
+
import ParagraphBase from "@tiptap/extension-paragraph";
|
|
11
23
|
import TipTapPlaceholder from "@tiptap/extension-placeholder";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
24
|
+
import StrikeBase from "@tiptap/extension-strike";
|
|
25
|
+
import { generateJSON } from "@tiptap/html";
|
|
26
|
+
import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code as Code$1, CodeIcon, Columns2, Columns3, Columns4, ExternalLinkIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, MousePointer, PencilIcon, Rows2, SplitSquareVertical, SquareCode, StrikethroughIcon, Text, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
|
|
16
27
|
import * as Popover from "@radix-ui/react-popover";
|
|
17
28
|
import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
|
|
18
29
|
import Suggestion from "@tiptap/suggestion";
|
|
19
30
|
import tippy from "tippy.js";
|
|
20
31
|
|
|
21
|
-
//#region src/core/email-node.ts
|
|
22
|
-
var EmailNode = class EmailNode extends Node {
|
|
23
|
-
constructor(config) {
|
|
24
|
-
super(config);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Create a new Node instance
|
|
28
|
-
* @param config - Node configuration object or a function that returns a configuration object
|
|
29
|
-
*/
|
|
30
|
-
static create(config) {
|
|
31
|
-
return new EmailNode(typeof config === "function" ? config() : config);
|
|
32
|
-
}
|
|
33
|
-
static from(node, renderToReactEmail) {
|
|
34
|
-
const customNode = EmailNode.create({});
|
|
35
|
-
Object.assign(customNode, { ...node });
|
|
36
|
-
customNode.config = {
|
|
37
|
-
...node.config,
|
|
38
|
-
renderToReactEmail
|
|
39
|
-
};
|
|
40
|
-
return customNode;
|
|
41
|
-
}
|
|
42
|
-
configure(options) {
|
|
43
|
-
return super.configure(options);
|
|
44
|
-
}
|
|
45
|
-
extend(extendedConfig) {
|
|
46
|
-
const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
|
|
47
|
-
return super.extend(resolvedConfig);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
32
|
//#region src/core/event-bus.ts
|
|
53
33
|
const EVENT_PREFIX = "@react-email/editor:";
|
|
54
34
|
var EditorEventBus = class {
|
|
@@ -90,139 +70,6 @@ var EditorEventBus = class {
|
|
|
90
70
|
};
|
|
91
71
|
const editorEventBus = new EditorEventBus();
|
|
92
72
|
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/extensions/alignment-attribute.tsx
|
|
95
|
-
const AlignmentAttribute = Extension.create({
|
|
96
|
-
name: "alignmentAttribute",
|
|
97
|
-
addOptions() {
|
|
98
|
-
return {
|
|
99
|
-
types: [],
|
|
100
|
-
alignments: [
|
|
101
|
-
"left",
|
|
102
|
-
"center",
|
|
103
|
-
"right",
|
|
104
|
-
"justify"
|
|
105
|
-
]
|
|
106
|
-
};
|
|
107
|
-
},
|
|
108
|
-
addGlobalAttributes() {
|
|
109
|
-
return [{
|
|
110
|
-
types: this.options.types,
|
|
111
|
-
attributes: { alignment: {
|
|
112
|
-
parseHTML: (element) => {
|
|
113
|
-
const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
|
|
114
|
-
if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
|
|
115
|
-
return null;
|
|
116
|
-
},
|
|
117
|
-
renderHTML: (attributes) => {
|
|
118
|
-
if (attributes.alignment === "left") return {};
|
|
119
|
-
return { alignment: attributes.alignment };
|
|
120
|
-
}
|
|
121
|
-
} }
|
|
122
|
-
}];
|
|
123
|
-
},
|
|
124
|
-
addCommands() {
|
|
125
|
-
return { setAlignment: (alignment) => ({ commands }) => {
|
|
126
|
-
if (!this.options.alignments.includes(alignment)) return false;
|
|
127
|
-
return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
|
|
128
|
-
} };
|
|
129
|
-
},
|
|
130
|
-
addKeyboardShortcuts() {
|
|
131
|
-
return {
|
|
132
|
-
Enter: () => {
|
|
133
|
-
const { from } = this.editor.state.selection;
|
|
134
|
-
const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
|
|
135
|
-
if (currentAlignment) requestAnimationFrame(() => {
|
|
136
|
-
this.editor.commands.setAlignment(currentAlignment);
|
|
137
|
-
});
|
|
138
|
-
return false;
|
|
139
|
-
},
|
|
140
|
-
"Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
|
|
141
|
-
"Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
|
|
142
|
-
"Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
|
|
143
|
-
"Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/utils/attribute-helpers.ts
|
|
150
|
-
/**
|
|
151
|
-
* Creates TipTap attribute definitions for a list of HTML attributes.
|
|
152
|
-
* Each attribute will have the same pattern:
|
|
153
|
-
* - default: null
|
|
154
|
-
* - parseHTML: extracts the attribute from the element
|
|
155
|
-
* - renderHTML: conditionally renders the attribute if it has a value
|
|
156
|
-
*
|
|
157
|
-
* @param attributeNames - Array of HTML attribute names to create definitions for
|
|
158
|
-
* @returns Object with TipTap attribute definitions
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* const attrs = createStandardAttributes(['class', 'id', 'title']);
|
|
162
|
-
* // Returns:
|
|
163
|
-
* // {
|
|
164
|
-
* // class: {
|
|
165
|
-
* // default: null,
|
|
166
|
-
* // parseHTML: (element) => element.getAttribute('class'),
|
|
167
|
-
* // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
|
|
168
|
-
* // },
|
|
169
|
-
* // ...
|
|
170
|
-
* // }
|
|
171
|
-
*/
|
|
172
|
-
function createStandardAttributes(attributeNames) {
|
|
173
|
-
return Object.fromEntries(attributeNames.map((attr) => [attr, {
|
|
174
|
-
default: null,
|
|
175
|
-
parseHTML: (element) => element.getAttribute(attr),
|
|
176
|
-
renderHTML: (attributes) => {
|
|
177
|
-
if (!attributes[attr]) return {};
|
|
178
|
-
return { [attr]: attributes[attr] };
|
|
179
|
-
}
|
|
180
|
-
}]));
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Common HTML attributes used across multiple extensions.
|
|
184
|
-
* These preserve attributes during HTML import and editing for better
|
|
185
|
-
* fidelity when importing existing email templates.
|
|
186
|
-
*/
|
|
187
|
-
const COMMON_HTML_ATTRIBUTES = [
|
|
188
|
-
"id",
|
|
189
|
-
"class",
|
|
190
|
-
"title",
|
|
191
|
-
"lang",
|
|
192
|
-
"dir",
|
|
193
|
-
"data-id"
|
|
194
|
-
];
|
|
195
|
-
/**
|
|
196
|
-
* Layout-specific HTML attributes used for positioning and sizing.
|
|
197
|
-
*/
|
|
198
|
-
const LAYOUT_ATTRIBUTES = [
|
|
199
|
-
"align",
|
|
200
|
-
"width",
|
|
201
|
-
"height"
|
|
202
|
-
];
|
|
203
|
-
/**
|
|
204
|
-
* Table-specific HTML attributes used for table layout and styling.
|
|
205
|
-
*/
|
|
206
|
-
const TABLE_ATTRIBUTES = [
|
|
207
|
-
"border",
|
|
208
|
-
"cellpadding",
|
|
209
|
-
"cellspacing"
|
|
210
|
-
];
|
|
211
|
-
/**
|
|
212
|
-
* Table cell-specific HTML attributes.
|
|
213
|
-
*/
|
|
214
|
-
const TABLE_CELL_ATTRIBUTES = [
|
|
215
|
-
"valign",
|
|
216
|
-
"bgcolor",
|
|
217
|
-
"colspan",
|
|
218
|
-
"rowspan"
|
|
219
|
-
];
|
|
220
|
-
/**
|
|
221
|
-
* Table header cell-specific HTML attributes.
|
|
222
|
-
* These are additional attributes that only apply to <th> elements.
|
|
223
|
-
*/
|
|
224
|
-
const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
|
|
225
|
-
|
|
226
73
|
//#endregion
|
|
227
74
|
//#region src/utils/styles.ts
|
|
228
75
|
const WHITE_SPACE_REGEX = /\s+/;
|
|
@@ -392,6 +239,342 @@ function resolveConflictingStyles(resetStyles, inlineStyles) {
|
|
|
392
239
|
};
|
|
393
240
|
}
|
|
394
241
|
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/core/serializer/default-base-template.tsx
|
|
244
|
+
function DefaultBaseTemplate({ children, previewText }) {
|
|
245
|
+
return /* @__PURE__ */ jsxs(Html, { children: [
|
|
246
|
+
/* @__PURE__ */ jsxs(Head, { children: [
|
|
247
|
+
/* @__PURE__ */ jsx("meta", {
|
|
248
|
+
content: "width=device-width",
|
|
249
|
+
name: "viewport"
|
|
250
|
+
}),
|
|
251
|
+
/* @__PURE__ */ jsx("meta", {
|
|
252
|
+
content: "IE=edge",
|
|
253
|
+
httpEquiv: "X-UA-Compatible"
|
|
254
|
+
}),
|
|
255
|
+
/* @__PURE__ */ jsx("meta", { name: "x-apple-disable-message-reformatting" }),
|
|
256
|
+
/* @__PURE__ */ jsx("meta", {
|
|
257
|
+
content: "telephone=no,address=no,email=no,date=no,url=no",
|
|
258
|
+
name: "format-detection"
|
|
259
|
+
})
|
|
260
|
+
] }),
|
|
261
|
+
previewText && previewText !== "" && /* @__PURE__ */ jsx(Preview, { children: previewText }),
|
|
262
|
+
/* @__PURE__ */ jsx(Body$1, { children: /* @__PURE__ */ jsx(Section$1, {
|
|
263
|
+
width: "100%",
|
|
264
|
+
align: "center",
|
|
265
|
+
children: /* @__PURE__ */ jsx(Section$1, {
|
|
266
|
+
style: { width: "100%" },
|
|
267
|
+
children
|
|
268
|
+
})
|
|
269
|
+
}) })
|
|
270
|
+
] });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/core/serializer/email-mark.ts
|
|
275
|
+
var EmailMark = class EmailMark extends Mark {
|
|
276
|
+
constructor(config) {
|
|
277
|
+
super(config);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Create a new Mark instance
|
|
281
|
+
* @param config - Mark configuration object or a function that returns a configuration object
|
|
282
|
+
*/
|
|
283
|
+
static create(config) {
|
|
284
|
+
return new EmailMark(typeof config === "function" ? config() : config);
|
|
285
|
+
}
|
|
286
|
+
static from(mark, renderToReactEmail) {
|
|
287
|
+
const customMark = EmailMark.create({});
|
|
288
|
+
Object.assign(customMark, { ...mark });
|
|
289
|
+
customMark.config = {
|
|
290
|
+
...mark.config,
|
|
291
|
+
renderToReactEmail
|
|
292
|
+
};
|
|
293
|
+
return customMark;
|
|
294
|
+
}
|
|
295
|
+
configure(options) {
|
|
296
|
+
return super.configure(options);
|
|
297
|
+
}
|
|
298
|
+
extend(extendedConfig) {
|
|
299
|
+
const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
|
|
300
|
+
return super.extend(resolvedConfig);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/core/serializer/email-node.ts
|
|
306
|
+
var EmailNode = class EmailNode extends Node$1 {
|
|
307
|
+
constructor(config) {
|
|
308
|
+
super(config);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Create a new Node instance
|
|
312
|
+
* @param config - Node configuration object or a function that returns a configuration object
|
|
313
|
+
*/
|
|
314
|
+
static create(config) {
|
|
315
|
+
return new EmailNode(typeof config === "function" ? config() : config);
|
|
316
|
+
}
|
|
317
|
+
static from(node, renderToReactEmail) {
|
|
318
|
+
const customNode = EmailNode.create({});
|
|
319
|
+
Object.assign(customNode, { ...node });
|
|
320
|
+
customNode.config = {
|
|
321
|
+
...node.config,
|
|
322
|
+
renderToReactEmail
|
|
323
|
+
};
|
|
324
|
+
return customNode;
|
|
325
|
+
}
|
|
326
|
+
configure(options) {
|
|
327
|
+
return super.configure(options);
|
|
328
|
+
}
|
|
329
|
+
extend(extendedConfig) {
|
|
330
|
+
const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
|
|
331
|
+
return super.extend(resolvedConfig);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/core/serializer/compose-react-email.tsx
|
|
337
|
+
const MARK_ORDER = {
|
|
338
|
+
preservedStyle: 0,
|
|
339
|
+
italic: 1,
|
|
340
|
+
strike: 2,
|
|
341
|
+
underline: 3,
|
|
342
|
+
link: 4,
|
|
343
|
+
bold: 5,
|
|
344
|
+
code: 6
|
|
345
|
+
};
|
|
346
|
+
const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
|
|
347
|
+
function getOrderedMarks(marks) {
|
|
348
|
+
if (!marks) return [];
|
|
349
|
+
return [...marks].sort((a, b) => (MARK_ORDER[a.type] ?? Number.MAX_SAFE_INTEGER) - (MARK_ORDER[b.type] ?? Number.MAX_SAFE_INTEGER));
|
|
350
|
+
}
|
|
351
|
+
const composeReactEmail = async ({ editor, preview }) => {
|
|
352
|
+
const data = editor.getJSON();
|
|
353
|
+
const extensions = editor.extensionManager.extensions;
|
|
354
|
+
const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
|
|
355
|
+
const emailNodeComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailNode).map((extension) => [extension.name, extension.config.renderToReactEmail]));
|
|
356
|
+
const emailMarkComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailMark).map((extension) => [extension.name, extension.config.renderToReactEmail]));
|
|
357
|
+
function renderMark(mark, node, children, depth) {
|
|
358
|
+
const markStyle = serializerPlugin?.getNodeStyles({
|
|
359
|
+
type: mark.type,
|
|
360
|
+
attrs: mark.attrs ?? {}
|
|
361
|
+
}, depth, editor) ?? {};
|
|
362
|
+
const markRenderer = emailMarkComponentRegistry[mark.type];
|
|
363
|
+
if (markRenderer) return markRenderer({
|
|
364
|
+
mark,
|
|
365
|
+
node,
|
|
366
|
+
style: markStyle,
|
|
367
|
+
children
|
|
368
|
+
});
|
|
369
|
+
return children;
|
|
370
|
+
}
|
|
371
|
+
function parseContent(content, depth = 0) {
|
|
372
|
+
if (!content) return;
|
|
373
|
+
return content.map((node, index) => {
|
|
374
|
+
const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
|
|
375
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
376
|
+
if (node.type && emailNodeComponentRegistry[node.type]) {
|
|
377
|
+
const Component = emailNodeComponentRegistry[node.type];
|
|
378
|
+
const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
|
|
379
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
380
|
+
node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
|
|
381
|
+
...node,
|
|
382
|
+
attrs: {
|
|
383
|
+
...node.attrs,
|
|
384
|
+
width: inlineStyles.width
|
|
385
|
+
}
|
|
386
|
+
} : node,
|
|
387
|
+
style,
|
|
388
|
+
children: parseContent(node.content, childDepth)
|
|
389
|
+
}, index);
|
|
390
|
+
}
|
|
391
|
+
switch (node.type) {
|
|
392
|
+
case "text": {
|
|
393
|
+
let wrappedText = node.text;
|
|
394
|
+
getOrderedMarks(node.marks).forEach((mark) => {
|
|
395
|
+
wrappedText = renderMark(mark, node, wrappedText, depth);
|
|
396
|
+
});
|
|
397
|
+
const textAttributes = node.marks?.find((mark) => mark.type === "textStyle")?.attrs;
|
|
398
|
+
return /* @__PURE__ */ jsx("span", {
|
|
399
|
+
style: {
|
|
400
|
+
...textAttributes,
|
|
401
|
+
...style
|
|
402
|
+
},
|
|
403
|
+
children: wrappedText
|
|
404
|
+
}, index);
|
|
405
|
+
}
|
|
406
|
+
default: return null;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
const unformattedHtml = await render(/* @__PURE__ */ jsx(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
|
|
411
|
+
previewText: preview,
|
|
412
|
+
editor,
|
|
413
|
+
children: parseContent(data.content)
|
|
414
|
+
}));
|
|
415
|
+
const [prettyHtml, text] = await Promise.all([pretty(unformattedHtml), toPlainText(unformattedHtml)]);
|
|
416
|
+
return {
|
|
417
|
+
html: prettyHtml,
|
|
418
|
+
text
|
|
419
|
+
};
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/extensions/alignment-attribute.tsx
|
|
424
|
+
const AlignmentAttribute = Extension.create({
|
|
425
|
+
name: "alignmentAttribute",
|
|
426
|
+
addOptions() {
|
|
427
|
+
return {
|
|
428
|
+
types: [],
|
|
429
|
+
alignments: [
|
|
430
|
+
"left",
|
|
431
|
+
"center",
|
|
432
|
+
"right",
|
|
433
|
+
"justify"
|
|
434
|
+
]
|
|
435
|
+
};
|
|
436
|
+
},
|
|
437
|
+
addGlobalAttributes() {
|
|
438
|
+
return [{
|
|
439
|
+
types: this.options.types,
|
|
440
|
+
attributes: { alignment: {
|
|
441
|
+
parseHTML: (element) => {
|
|
442
|
+
const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
|
|
443
|
+
if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
|
|
444
|
+
return null;
|
|
445
|
+
},
|
|
446
|
+
renderHTML: (attributes) => {
|
|
447
|
+
if (attributes.alignment === "left") return {};
|
|
448
|
+
return { alignment: attributes.alignment };
|
|
449
|
+
}
|
|
450
|
+
} }
|
|
451
|
+
}];
|
|
452
|
+
},
|
|
453
|
+
addCommands() {
|
|
454
|
+
return { setAlignment: (alignment) => ({ commands }) => {
|
|
455
|
+
if (!this.options.alignments.includes(alignment)) return false;
|
|
456
|
+
return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
|
|
457
|
+
} };
|
|
458
|
+
},
|
|
459
|
+
addKeyboardShortcuts() {
|
|
460
|
+
return {
|
|
461
|
+
Enter: () => {
|
|
462
|
+
const { from } = this.editor.state.selection;
|
|
463
|
+
const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
|
|
464
|
+
if (currentAlignment) requestAnimationFrame(() => {
|
|
465
|
+
this.editor.commands.setAlignment(currentAlignment);
|
|
466
|
+
});
|
|
467
|
+
return false;
|
|
468
|
+
},
|
|
469
|
+
"Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
|
|
470
|
+
"Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
|
|
471
|
+
"Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
|
|
472
|
+
"Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/utils/get-text-alignment.ts
|
|
479
|
+
function getTextAlignment(alignment) {
|
|
480
|
+
switch (alignment) {
|
|
481
|
+
case "left": return { textAlign: "left" };
|
|
482
|
+
case "center": return { textAlign: "center" };
|
|
483
|
+
case "right": return { textAlign: "right" };
|
|
484
|
+
default: return {};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/extensions/blockquote.tsx
|
|
490
|
+
const Blockquote = EmailNode.from(BlockquoteBase, ({ children, node, style }) => /* @__PURE__ */ jsx("blockquote", {
|
|
491
|
+
className: node.attrs?.class || void 0,
|
|
492
|
+
style: {
|
|
493
|
+
...style,
|
|
494
|
+
...inlineCssToJs(node.attrs?.style),
|
|
495
|
+
...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
|
|
496
|
+
},
|
|
497
|
+
children
|
|
498
|
+
}));
|
|
499
|
+
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/utils/attribute-helpers.ts
|
|
502
|
+
/**
|
|
503
|
+
* Creates TipTap attribute definitions for a list of HTML attributes.
|
|
504
|
+
* Each attribute will have the same pattern:
|
|
505
|
+
* - default: null
|
|
506
|
+
* - parseHTML: extracts the attribute from the element
|
|
507
|
+
* - renderHTML: conditionally renders the attribute if it has a value
|
|
508
|
+
*
|
|
509
|
+
* @param attributeNames - Array of HTML attribute names to create definitions for
|
|
510
|
+
* @returns Object with TipTap attribute definitions
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* const attrs = createStandardAttributes(['class', 'id', 'title']);
|
|
514
|
+
* // Returns:
|
|
515
|
+
* // {
|
|
516
|
+
* // class: {
|
|
517
|
+
* // default: null,
|
|
518
|
+
* // parseHTML: (element) => element.getAttribute('class'),
|
|
519
|
+
* // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
|
|
520
|
+
* // },
|
|
521
|
+
* // ...
|
|
522
|
+
* // }
|
|
523
|
+
*/
|
|
524
|
+
function createStandardAttributes(attributeNames) {
|
|
525
|
+
return Object.fromEntries(attributeNames.map((attr) => [attr, {
|
|
526
|
+
default: null,
|
|
527
|
+
parseHTML: (element) => element.getAttribute(attr),
|
|
528
|
+
renderHTML: (attributes) => {
|
|
529
|
+
if (!attributes[attr]) return {};
|
|
530
|
+
return { [attr]: attributes[attr] };
|
|
531
|
+
}
|
|
532
|
+
}]));
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Common HTML attributes used across multiple extensions.
|
|
536
|
+
* These preserve attributes during HTML import and editing for better
|
|
537
|
+
* fidelity when importing existing email templates.
|
|
538
|
+
*/
|
|
539
|
+
const COMMON_HTML_ATTRIBUTES = [
|
|
540
|
+
"id",
|
|
541
|
+
"class",
|
|
542
|
+
"title",
|
|
543
|
+
"lang",
|
|
544
|
+
"dir",
|
|
545
|
+
"data-id"
|
|
546
|
+
];
|
|
547
|
+
/**
|
|
548
|
+
* Layout-specific HTML attributes used for positioning and sizing.
|
|
549
|
+
*/
|
|
550
|
+
const LAYOUT_ATTRIBUTES = [
|
|
551
|
+
"align",
|
|
552
|
+
"width",
|
|
553
|
+
"height"
|
|
554
|
+
];
|
|
555
|
+
/**
|
|
556
|
+
* Table-specific HTML attributes used for table layout and styling.
|
|
557
|
+
*/
|
|
558
|
+
const TABLE_ATTRIBUTES = [
|
|
559
|
+
"border",
|
|
560
|
+
"cellpadding",
|
|
561
|
+
"cellspacing"
|
|
562
|
+
];
|
|
563
|
+
/**
|
|
564
|
+
* Table cell-specific HTML attributes.
|
|
565
|
+
*/
|
|
566
|
+
const TABLE_CELL_ATTRIBUTES = [
|
|
567
|
+
"valign",
|
|
568
|
+
"bgcolor",
|
|
569
|
+
"colspan",
|
|
570
|
+
"rowspan"
|
|
571
|
+
];
|
|
572
|
+
/**
|
|
573
|
+
* Table header cell-specific HTML attributes.
|
|
574
|
+
* These are additional attributes that only apply to <th> elements.
|
|
575
|
+
*/
|
|
576
|
+
const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
|
|
577
|
+
|
|
395
578
|
//#endregion
|
|
396
579
|
//#region src/extensions/body.tsx
|
|
397
580
|
const Body = EmailNode.create({
|
|
@@ -438,7 +621,7 @@ const Body = EmailNode.create({
|
|
|
438
621
|
});
|
|
439
622
|
|
|
440
623
|
//#endregion
|
|
441
|
-
//#region src/extensions/bold.
|
|
624
|
+
//#region src/extensions/bold.tsx
|
|
442
625
|
/**
|
|
443
626
|
* Matches bold text via `**` as input.
|
|
444
627
|
*/
|
|
@@ -459,7 +642,7 @@ const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
|
|
|
459
642
|
* This extension allows you to mark text as bold.
|
|
460
643
|
* @see https://tiptap.dev/api/marks/bold
|
|
461
644
|
*/
|
|
462
|
-
const Bold =
|
|
645
|
+
const Bold = EmailMark.create({
|
|
463
646
|
name: "bold",
|
|
464
647
|
addOptions() {
|
|
465
648
|
return { HTMLAttributes: {} };
|
|
@@ -484,6 +667,12 @@ const Bold = Mark.create({
|
|
|
484
667
|
0
|
|
485
668
|
];
|
|
486
669
|
},
|
|
670
|
+
renderToReactEmail({ children, style }) {
|
|
671
|
+
return /* @__PURE__ */ jsx("strong", {
|
|
672
|
+
style,
|
|
673
|
+
children
|
|
674
|
+
});
|
|
675
|
+
},
|
|
487
676
|
addCommands() {
|
|
488
677
|
return {
|
|
489
678
|
setBold: () => ({ commands }) => {
|
|
@@ -523,6 +712,17 @@ const Bold = Mark.create({
|
|
|
523
712
|
}
|
|
524
713
|
});
|
|
525
714
|
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/extensions/bullet-list.tsx
|
|
717
|
+
const BulletList = EmailNode.from(BulletListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ul", {
|
|
718
|
+
className: node.attrs?.class || void 0,
|
|
719
|
+
style: {
|
|
720
|
+
...style,
|
|
721
|
+
...inlineCssToJs(node.attrs?.style)
|
|
722
|
+
},
|
|
723
|
+
children
|
|
724
|
+
}));
|
|
725
|
+
|
|
526
726
|
//#endregion
|
|
527
727
|
//#region src/extensions/button.tsx
|
|
528
728
|
const Button = EmailNode.create({
|
|
@@ -644,6 +844,16 @@ const ClassAttribute = Extension.create({
|
|
|
644
844
|
}
|
|
645
845
|
});
|
|
646
846
|
|
|
847
|
+
//#endregion
|
|
848
|
+
//#region src/extensions/code.tsx
|
|
849
|
+
const Code = EmailMark.from(CodeBase, ({ children, node, style }) => /* @__PURE__ */ jsx("code", {
|
|
850
|
+
style: {
|
|
851
|
+
...style,
|
|
852
|
+
...inlineCssToJs(node.attrs?.style)
|
|
853
|
+
},
|
|
854
|
+
children
|
|
855
|
+
}));
|
|
856
|
+
|
|
647
857
|
//#endregion
|
|
648
858
|
//#region src/utils/prism-utils.ts
|
|
649
859
|
const publicURL = "/styles/prism";
|
|
@@ -922,6 +1132,29 @@ const Div = EmailNode.create({
|
|
|
922
1132
|
}
|
|
923
1133
|
});
|
|
924
1134
|
|
|
1135
|
+
//#endregion
|
|
1136
|
+
//#region src/extensions/hard-break.tsx
|
|
1137
|
+
const HardBreak = EmailNode.from(HardBreakBase, () => /* @__PURE__ */ jsx("br", {}));
|
|
1138
|
+
|
|
1139
|
+
//#endregion
|
|
1140
|
+
//#region src/extensions/italic.tsx
|
|
1141
|
+
const Italic = EmailMark.from(ItalicBase, ({ children, style }) => /* @__PURE__ */ jsx("em", {
|
|
1142
|
+
style,
|
|
1143
|
+
children
|
|
1144
|
+
}));
|
|
1145
|
+
|
|
1146
|
+
//#endregion
|
|
1147
|
+
//#region src/extensions/list-item.tsx
|
|
1148
|
+
const ListItem = EmailNode.from(ListItemBase, ({ children, node, style }) => /* @__PURE__ */ jsx("li", {
|
|
1149
|
+
className: node.attrs?.class || void 0,
|
|
1150
|
+
style: {
|
|
1151
|
+
...style,
|
|
1152
|
+
...inlineCssToJs(node.attrs?.style),
|
|
1153
|
+
...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
|
|
1154
|
+
},
|
|
1155
|
+
children
|
|
1156
|
+
}));
|
|
1157
|
+
|
|
925
1158
|
//#endregion
|
|
926
1159
|
//#region src/extensions/max-nesting.ts
|
|
927
1160
|
const MaxNesting = Extension.create({
|
|
@@ -997,6 +1230,33 @@ const MaxNesting = Extension.create({
|
|
|
997
1230
|
}
|
|
998
1231
|
});
|
|
999
1232
|
|
|
1233
|
+
//#endregion
|
|
1234
|
+
//#region src/extensions/ordered-list.tsx
|
|
1235
|
+
const OrderedList = EmailNode.from(OrderedListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ol", {
|
|
1236
|
+
className: node.attrs?.class || void 0,
|
|
1237
|
+
start: node.attrs?.start,
|
|
1238
|
+
style: {
|
|
1239
|
+
...style,
|
|
1240
|
+
...inlineCssToJs(node.attrs?.style)
|
|
1241
|
+
},
|
|
1242
|
+
children
|
|
1243
|
+
}));
|
|
1244
|
+
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region src/extensions/paragraph.tsx
|
|
1247
|
+
const Paragraph = EmailNode.from(ParagraphBase, ({ children, node, style }) => {
|
|
1248
|
+
const isEmpty = !node.content || node.content.length === 0;
|
|
1249
|
+
return /* @__PURE__ */ jsx("p", {
|
|
1250
|
+
className: node.attrs?.class || void 0,
|
|
1251
|
+
style: {
|
|
1252
|
+
...style,
|
|
1253
|
+
...inlineCssToJs(node.attrs?.style),
|
|
1254
|
+
...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
|
|
1255
|
+
},
|
|
1256
|
+
children: isEmpty ? /* @__PURE__ */ jsx("br", {}) : children
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1000
1260
|
//#endregion
|
|
1001
1261
|
//#region src/extensions/placeholder.ts
|
|
1002
1262
|
const Placeholder = TipTapPlaceholder.configure({
|
|
@@ -1008,8 +1268,8 @@ const Placeholder = TipTapPlaceholder.configure({
|
|
|
1008
1268
|
});
|
|
1009
1269
|
|
|
1010
1270
|
//#endregion
|
|
1011
|
-
//#region src/extensions/preserved-style.
|
|
1012
|
-
const PreservedStyle =
|
|
1271
|
+
//#region src/extensions/preserved-style.tsx
|
|
1272
|
+
const PreservedStyle = EmailMark.create({
|
|
1013
1273
|
name: "preservedStyle",
|
|
1014
1274
|
addAttributes() {
|
|
1015
1275
|
return { style: {
|
|
@@ -1038,6 +1298,12 @@ const PreservedStyle = Mark.create({
|
|
|
1038
1298
|
mergeAttributes(HTMLAttributes),
|
|
1039
1299
|
0
|
|
1040
1300
|
];
|
|
1301
|
+
},
|
|
1302
|
+
renderToReactEmail({ children, mark }) {
|
|
1303
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1304
|
+
style: mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : void 0,
|
|
1305
|
+
children
|
|
1306
|
+
});
|
|
1041
1307
|
}
|
|
1042
1308
|
});
|
|
1043
1309
|
const LINK_INDICATOR_STYLES = [
|
|
@@ -1083,7 +1349,7 @@ function processStylesForUnlink(styleString) {
|
|
|
1083
1349
|
|
|
1084
1350
|
//#endregion
|
|
1085
1351
|
//#region src/extensions/preview-text.ts
|
|
1086
|
-
const PreviewText = Node.create({
|
|
1352
|
+
const PreviewText = Node$1.create({
|
|
1087
1353
|
name: "previewText",
|
|
1088
1354
|
group: "block",
|
|
1089
1355
|
selectable: false,
|
|
@@ -1122,17 +1388,6 @@ const PreviewText = Node.create({
|
|
|
1122
1388
|
}
|
|
1123
1389
|
});
|
|
1124
1390
|
|
|
1125
|
-
//#endregion
|
|
1126
|
-
//#region src/utils/get-text-alignment.ts
|
|
1127
|
-
function getTextAlignment(alignment) {
|
|
1128
|
-
switch (alignment) {
|
|
1129
|
-
case "left": return { textAlign: "left" };
|
|
1130
|
-
case "center": return { textAlign: "center" };
|
|
1131
|
-
case "right": return { textAlign: "right" };
|
|
1132
|
-
default: return {};
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
1391
|
//#endregion
|
|
1137
1392
|
//#region src/extensions/section.tsx
|
|
1138
1393
|
const Section = EmailNode.create({
|
|
@@ -1181,6 +1436,13 @@ const Section = EmailNode.create({
|
|
|
1181
1436
|
}
|
|
1182
1437
|
});
|
|
1183
1438
|
|
|
1439
|
+
//#endregion
|
|
1440
|
+
//#region src/extensions/strike.tsx
|
|
1441
|
+
const Strike = EmailMark.from(StrikeBase, ({ children, style }) => /* @__PURE__ */ jsx("s", {
|
|
1442
|
+
style,
|
|
1443
|
+
children
|
|
1444
|
+
}));
|
|
1445
|
+
|
|
1184
1446
|
//#endregion
|
|
1185
1447
|
//#region src/extensions/style-attribute.tsx
|
|
1186
1448
|
const StyleAttribute = Extension.create({
|
|
@@ -1230,12 +1492,12 @@ const StyleAttribute = Extension.create({
|
|
|
1230
1492
|
});
|
|
1231
1493
|
|
|
1232
1494
|
//#endregion
|
|
1233
|
-
//#region src/extensions/sup.
|
|
1495
|
+
//#region src/extensions/sup.tsx
|
|
1234
1496
|
/**
|
|
1235
1497
|
* This extension allows you to mark text as superscript.
|
|
1236
1498
|
* @see https://tiptap.dev/api/marks/superscript
|
|
1237
1499
|
*/
|
|
1238
|
-
const Sup =
|
|
1500
|
+
const Sup = EmailMark.create({
|
|
1239
1501
|
name: "sup",
|
|
1240
1502
|
addOptions() {
|
|
1241
1503
|
return { HTMLAttributes: {} };
|
|
@@ -1250,6 +1512,12 @@ const Sup = Mark.create({
|
|
|
1250
1512
|
0
|
|
1251
1513
|
];
|
|
1252
1514
|
},
|
|
1515
|
+
renderToReactEmail({ children, style }) {
|
|
1516
|
+
return /* @__PURE__ */ jsx("sup", {
|
|
1517
|
+
style,
|
|
1518
|
+
children
|
|
1519
|
+
});
|
|
1520
|
+
},
|
|
1253
1521
|
addCommands() {
|
|
1254
1522
|
return {
|
|
1255
1523
|
setSup: () => ({ commands }) => {
|
|
@@ -1415,7 +1683,7 @@ const TableCell = EmailNode.create({
|
|
|
1415
1683
|
});
|
|
1416
1684
|
}
|
|
1417
1685
|
});
|
|
1418
|
-
const TableHeader = Node.create({
|
|
1686
|
+
const TableHeader = Node$1.create({
|
|
1419
1687
|
name: "tableHeader",
|
|
1420
1688
|
group: "tableCell",
|
|
1421
1689
|
content: "block+",
|
|
@@ -1452,8 +1720,8 @@ const TableHeader = Node.create({
|
|
|
1452
1720
|
});
|
|
1453
1721
|
|
|
1454
1722
|
//#endregion
|
|
1455
|
-
//#region src/extensions/uppercase.
|
|
1456
|
-
const Uppercase =
|
|
1723
|
+
//#region src/extensions/uppercase.tsx
|
|
1724
|
+
const Uppercase = EmailMark.create({
|
|
1457
1725
|
name: "uppercase",
|
|
1458
1726
|
addOptions() {
|
|
1459
1727
|
return { HTMLAttributes: {} };
|
|
@@ -1474,6 +1742,15 @@ const Uppercase = Mark.create({
|
|
|
1474
1742
|
0
|
|
1475
1743
|
];
|
|
1476
1744
|
},
|
|
1745
|
+
renderToReactEmail({ children, style }) {
|
|
1746
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1747
|
+
style: {
|
|
1748
|
+
...style,
|
|
1749
|
+
textTransform: "uppercase"
|
|
1750
|
+
},
|
|
1751
|
+
children
|
|
1752
|
+
});
|
|
1753
|
+
},
|
|
1477
1754
|
addCommands() {
|
|
1478
1755
|
return {
|
|
1479
1756
|
setUppercase: () => ({ commands }) => {
|
|
@@ -1669,17 +1946,17 @@ const coreExtensions = [
|
|
|
1669
1946
|
underline: false,
|
|
1670
1947
|
trailingNode: false,
|
|
1671
1948
|
bold: false,
|
|
1949
|
+
italic: false,
|
|
1950
|
+
strike: false,
|
|
1951
|
+
code: false,
|
|
1952
|
+
paragraph: false,
|
|
1953
|
+
bulletList: false,
|
|
1954
|
+
orderedList: false,
|
|
1955
|
+
listItem: false,
|
|
1956
|
+
blockquote: false,
|
|
1957
|
+
hardBreak: false,
|
|
1672
1958
|
gapcursor: false,
|
|
1673
|
-
listItem: {},
|
|
1674
|
-
bulletList: { HTMLAttributes: { class: "node-bulletList" } },
|
|
1675
|
-
paragraph: { HTMLAttributes: { class: "node-paragraph" } },
|
|
1676
|
-
orderedList: { HTMLAttributes: { class: "node-orderedList" } },
|
|
1677
|
-
blockquote: { HTMLAttributes: { class: "node-blockquote" } },
|
|
1678
1959
|
codeBlock: false,
|
|
1679
|
-
code: { HTMLAttributes: {
|
|
1680
|
-
class: "node-inlineCode",
|
|
1681
|
-
spellcheck: "false"
|
|
1682
|
-
} },
|
|
1683
1960
|
horizontalRule: false,
|
|
1684
1961
|
dropcursor: {
|
|
1685
1962
|
color: "#61a8f8",
|
|
@@ -1691,9 +1968,21 @@ const coreExtensions = [
|
|
|
1691
1968
|
defaultLanguage: "javascript",
|
|
1692
1969
|
HTMLAttributes: { class: "prism node-codeBlock" }
|
|
1693
1970
|
}),
|
|
1971
|
+
Code.configure({ HTMLAttributes: {
|
|
1972
|
+
class: "node-inlineCode",
|
|
1973
|
+
spellcheck: "false"
|
|
1974
|
+
} }),
|
|
1975
|
+
Paragraph.configure({ HTMLAttributes: { class: "node-paragraph" } }),
|
|
1976
|
+
BulletList.configure({ HTMLAttributes: { class: "node-bulletList" } }),
|
|
1977
|
+
OrderedList.configure({ HTMLAttributes: { class: "node-orderedList" } }),
|
|
1978
|
+
Blockquote.configure({ HTMLAttributes: { class: "node-blockquote" } }),
|
|
1979
|
+
ListItem,
|
|
1980
|
+
HardBreak,
|
|
1981
|
+
Italic,
|
|
1694
1982
|
Placeholder,
|
|
1695
1983
|
PreviewText,
|
|
1696
1984
|
Bold,
|
|
1985
|
+
Strike,
|
|
1697
1986
|
Sup,
|
|
1698
1987
|
Uppercase,
|
|
1699
1988
|
PreservedStyle,
|
|
@@ -1780,6 +2069,221 @@ const coreExtensions = [
|
|
|
1780
2069
|
})
|
|
1781
2070
|
];
|
|
1782
2071
|
|
|
2072
|
+
//#endregion
|
|
2073
|
+
//#region src/core/create-drop-handler.ts
|
|
2074
|
+
function createDropHandler({ onPaste, onUploadImage }) {
|
|
2075
|
+
return (view, event, _slice, moved) => {
|
|
2076
|
+
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
|
|
2077
|
+
event.preventDefault();
|
|
2078
|
+
const file = event.dataTransfer.files[0];
|
|
2079
|
+
if (onPaste?.(file, view)) return true;
|
|
2080
|
+
if (file.type.includes("image/") && onUploadImage) {
|
|
2081
|
+
onUploadImage(file, view, (view.posAtCoords({
|
|
2082
|
+
left: event.clientX,
|
|
2083
|
+
top: event.clientY
|
|
2084
|
+
})?.pos || 0) - 1);
|
|
2085
|
+
return true;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return false;
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
//#endregion
|
|
2093
|
+
//#region src/utils/paste-sanitizer.ts
|
|
2094
|
+
/**
|
|
2095
|
+
* Sanitizes pasted HTML.
|
|
2096
|
+
* - From editor (has node-* classes): pass through as-is
|
|
2097
|
+
* - From external: strip all styles/classes, keep only semantic HTML
|
|
2098
|
+
*/
|
|
2099
|
+
/**
|
|
2100
|
+
* Detects content from the Resend editor by checking for node-* class names.
|
|
2101
|
+
*/
|
|
2102
|
+
const EDITOR_CLASS_PATTERN = /class="[^"]*node-/;
|
|
2103
|
+
/**
|
|
2104
|
+
* Attributes to preserve on specific elements for EXTERNAL content.
|
|
2105
|
+
* Only functional attributes - NO style or class.
|
|
2106
|
+
*/
|
|
2107
|
+
const PRESERVED_ATTRIBUTES = {
|
|
2108
|
+
a: [
|
|
2109
|
+
"href",
|
|
2110
|
+
"target",
|
|
2111
|
+
"rel"
|
|
2112
|
+
],
|
|
2113
|
+
img: [
|
|
2114
|
+
"src",
|
|
2115
|
+
"alt",
|
|
2116
|
+
"width",
|
|
2117
|
+
"height"
|
|
2118
|
+
],
|
|
2119
|
+
td: ["colspan", "rowspan"],
|
|
2120
|
+
th: [
|
|
2121
|
+
"colspan",
|
|
2122
|
+
"rowspan",
|
|
2123
|
+
"scope"
|
|
2124
|
+
],
|
|
2125
|
+
table: [
|
|
2126
|
+
"border",
|
|
2127
|
+
"cellpadding",
|
|
2128
|
+
"cellspacing"
|
|
2129
|
+
],
|
|
2130
|
+
"*": ["id"]
|
|
2131
|
+
};
|
|
2132
|
+
function isFromEditor(html) {
|
|
2133
|
+
return EDITOR_CLASS_PATTERN.test(html);
|
|
2134
|
+
}
|
|
2135
|
+
function sanitizePastedHtml(html) {
|
|
2136
|
+
if (isFromEditor(html)) return html;
|
|
2137
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
2138
|
+
sanitizeNode(doc.body);
|
|
2139
|
+
return doc.body.innerHTML;
|
|
2140
|
+
}
|
|
2141
|
+
function sanitizeNode(node) {
|
|
2142
|
+
if (node.nodeType === Node.ELEMENT_NODE) sanitizeElement(node);
|
|
2143
|
+
for (const child of Array.from(node.childNodes)) sanitizeNode(child);
|
|
2144
|
+
}
|
|
2145
|
+
function sanitizeElement(el) {
|
|
2146
|
+
const allowedForTag = PRESERVED_ATTRIBUTES[el.tagName.toLowerCase()] || [];
|
|
2147
|
+
const allowedGlobal = PRESERVED_ATTRIBUTES["*"] || [];
|
|
2148
|
+
const allowed = new Set([...allowedForTag, ...allowedGlobal]);
|
|
2149
|
+
const attributesToRemove = [];
|
|
2150
|
+
for (const attr of Array.from(el.attributes)) {
|
|
2151
|
+
if (attr.name.startsWith("data-")) {
|
|
2152
|
+
attributesToRemove.push(attr.name);
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
if (!allowed.has(attr.name)) attributesToRemove.push(attr.name);
|
|
2156
|
+
}
|
|
2157
|
+
for (const attr of attributesToRemove) el.removeAttribute(attr);
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
//#endregion
|
|
2161
|
+
//#region src/core/create-paste-handler.ts
|
|
2162
|
+
function createPasteHandler({ onPaste, onUploadImage, extensions }) {
|
|
2163
|
+
return (view, event, slice) => {
|
|
2164
|
+
const text = event.clipboardData?.getData("text/plain");
|
|
2165
|
+
if (text && onPaste?.(text, view)) {
|
|
2166
|
+
event.preventDefault();
|
|
2167
|
+
return true;
|
|
2168
|
+
}
|
|
2169
|
+
if (event.clipboardData?.files?.[0]) {
|
|
2170
|
+
const file = event.clipboardData.files[0];
|
|
2171
|
+
if (onPaste?.(file, view)) {
|
|
2172
|
+
event.preventDefault();
|
|
2173
|
+
return true;
|
|
2174
|
+
}
|
|
2175
|
+
if (file.type.includes("image/") && onUploadImage) {
|
|
2176
|
+
const pos = view.state.selection.from;
|
|
2177
|
+
onUploadImage(file, view, pos);
|
|
2178
|
+
return true;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* If the coming content has a single child, we can assume
|
|
2183
|
+
* it's a plain text and doesn't need to be parsed and
|
|
2184
|
+
* be introduced in a new line
|
|
2185
|
+
*/
|
|
2186
|
+
if (slice.content.childCount === 1) return false;
|
|
2187
|
+
if (event.clipboardData?.getData?.("text/html")) {
|
|
2188
|
+
event.preventDefault();
|
|
2189
|
+
const jsonContent = generateJSON(sanitizePastedHtml(event.clipboardData.getData("text/html")), extensions);
|
|
2190
|
+
const node = view.state.schema.nodeFromJSON(jsonContent);
|
|
2191
|
+
const transaction = view.state.tr.replaceSelectionWith(node, false);
|
|
2192
|
+
view.dispatch(transaction);
|
|
2193
|
+
return true;
|
|
2194
|
+
}
|
|
2195
|
+
return false;
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
//#endregion
|
|
2200
|
+
//#region src/core/is-document-visually-empty.ts
|
|
2201
|
+
function isDocumentVisuallyEmpty(doc) {
|
|
2202
|
+
let nonGlobalNodeCount = 0;
|
|
2203
|
+
let firstNonGlobalNode = null;
|
|
2204
|
+
for (let index = 0; index < doc.childCount; index += 1) {
|
|
2205
|
+
const node = doc.child(index);
|
|
2206
|
+
if (node.type.name === "globalContent") continue;
|
|
2207
|
+
nonGlobalNodeCount += 1;
|
|
2208
|
+
if (firstNonGlobalNode === null) firstNonGlobalNode = {
|
|
2209
|
+
type: node.type,
|
|
2210
|
+
textContent: node.textContent,
|
|
2211
|
+
childCount: node.content.childCount
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
if (nonGlobalNodeCount === 0) return true;
|
|
2215
|
+
if (nonGlobalNodeCount !== 1) return false;
|
|
2216
|
+
return firstNonGlobalNode?.type.name === "paragraph" && firstNonGlobalNode.textContent.trim().length === 0 && firstNonGlobalNode.childCount === 0;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
//#endregion
|
|
2220
|
+
//#region src/core/use-editor.ts
|
|
2221
|
+
const COLLABORATION_EXTENSION_NAMES = new Set(["liveblocksExtension", "collaboration"]);
|
|
2222
|
+
function hasCollaborationExtension(exts) {
|
|
2223
|
+
return exts.some((ext) => COLLABORATION_EXTENSION_NAMES.has(ext.name));
|
|
2224
|
+
}
|
|
2225
|
+
function useEditor({ content, extensions = [], onUpdate, onPaste, onUploadImage, onReady, editable = true, ...rest }) {
|
|
2226
|
+
const [contentError, setContentError] = React.useState(null);
|
|
2227
|
+
const isCollaborative = hasCollaborationExtension(extensions);
|
|
2228
|
+
const effectiveExtensions = React.useMemo(() => [
|
|
2229
|
+
...coreExtensions,
|
|
2230
|
+
...isCollaborative ? [] : [UndoRedo],
|
|
2231
|
+
...extensions
|
|
2232
|
+
], [extensions, isCollaborative]);
|
|
2233
|
+
const editor = useEditor$1({
|
|
2234
|
+
content: isCollaborative ? void 0 : content,
|
|
2235
|
+
extensions: effectiveExtensions,
|
|
2236
|
+
immediatelyRender: false,
|
|
2237
|
+
enableContentCheck: true,
|
|
2238
|
+
onContentError({ editor: editor$1, error, disableCollaboration }) {
|
|
2239
|
+
disableCollaboration();
|
|
2240
|
+
setContentError(error);
|
|
2241
|
+
console.error(error);
|
|
2242
|
+
editor$1.setEditable(false);
|
|
2243
|
+
},
|
|
2244
|
+
onCreate({ editor: editor$1 }) {
|
|
2245
|
+
onReady?.(editor$1);
|
|
2246
|
+
},
|
|
2247
|
+
onUpdate({ editor: editor$1, transaction }) {
|
|
2248
|
+
onUpdate?.(editor$1, transaction);
|
|
2249
|
+
},
|
|
2250
|
+
editorProps: {
|
|
2251
|
+
handleDOMEvents: { click: (view, event) => {
|
|
2252
|
+
if (!view.editable) {
|
|
2253
|
+
if (event.target.closest("a")) {
|
|
2254
|
+
event.preventDefault();
|
|
2255
|
+
return true;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return false;
|
|
2259
|
+
} },
|
|
2260
|
+
handlePaste: createPasteHandler({
|
|
2261
|
+
onPaste,
|
|
2262
|
+
onUploadImage,
|
|
2263
|
+
extensions: effectiveExtensions
|
|
2264
|
+
}),
|
|
2265
|
+
handleDrop: createDropHandler({
|
|
2266
|
+
onPaste,
|
|
2267
|
+
onUploadImage
|
|
2268
|
+
})
|
|
2269
|
+
},
|
|
2270
|
+
...rest
|
|
2271
|
+
});
|
|
2272
|
+
return {
|
|
2273
|
+
editor,
|
|
2274
|
+
isEditorEmpty: useEditorState({
|
|
2275
|
+
editor,
|
|
2276
|
+
selector: (context) => {
|
|
2277
|
+
if (!context.editor) return true;
|
|
2278
|
+
return isDocumentVisuallyEmpty(context.editor.state.doc);
|
|
2279
|
+
}
|
|
2280
|
+
}) ?? true,
|
|
2281
|
+
extensions: effectiveExtensions,
|
|
2282
|
+
contentError,
|
|
2283
|
+
isCollaborative
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
|
|
1783
2287
|
//#endregion
|
|
1784
2288
|
//#region src/utils/set-text-alignment.ts
|
|
1785
2289
|
function setTextAlignment(editor, alignment) {
|
|
@@ -2219,7 +2723,7 @@ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, class
|
|
|
2219
2723
|
},
|
|
2220
2724
|
{
|
|
2221
2725
|
name: "Code",
|
|
2222
|
-
icon: Code,
|
|
2726
|
+
icon: Code$1,
|
|
2223
2727
|
command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
|
|
2224
2728
|
isActive: editorState?.isCodeBlockActive ?? false
|
|
2225
2729
|
}
|
|
@@ -2934,13 +3438,12 @@ function groupByCategory(items) {
|
|
|
2934
3438
|
return ordered;
|
|
2935
3439
|
}
|
|
2936
3440
|
function CommandItem({ item, selected, onSelect }) {
|
|
2937
|
-
const Icon = item.icon;
|
|
2938
3441
|
return /* @__PURE__ */ jsxs("button", {
|
|
2939
3442
|
"data-re-slash-command-item": "",
|
|
2940
3443
|
"data-selected": selected || void 0,
|
|
2941
3444
|
onClick: onSelect,
|
|
2942
3445
|
type: "button",
|
|
2943
|
-
children: [
|
|
3446
|
+
children: [item.icon, /* @__PURE__ */ jsx("span", { children: item.title })]
|
|
2944
3447
|
});
|
|
2945
3448
|
}
|
|
2946
3449
|
function CommandList({ items, command, query, ref }) {
|
|
@@ -3015,11 +3518,11 @@ function CommandList({ items, command, query, ref }) {
|
|
|
3015
3518
|
}
|
|
3016
3519
|
|
|
3017
3520
|
//#endregion
|
|
3018
|
-
//#region src/ui/slash-command/commands.
|
|
3521
|
+
//#region src/ui/slash-command/commands.tsx
|
|
3019
3522
|
const TEXT = {
|
|
3020
3523
|
title: "Text",
|
|
3021
3524
|
description: "Plain text block",
|
|
3022
|
-
icon: Text,
|
|
3525
|
+
icon: /* @__PURE__ */ jsx(Text, { size: 20 }),
|
|
3023
3526
|
category: "Text",
|
|
3024
3527
|
searchTerms: ["p", "paragraph"],
|
|
3025
3528
|
command: ({ editor, range }) => {
|
|
@@ -3029,7 +3532,7 @@ const TEXT = {
|
|
|
3029
3532
|
const H1 = {
|
|
3030
3533
|
title: "Title",
|
|
3031
3534
|
description: "Large heading",
|
|
3032
|
-
icon: Heading1,
|
|
3535
|
+
icon: /* @__PURE__ */ jsx(Heading1, { size: 20 }),
|
|
3033
3536
|
category: "Text",
|
|
3034
3537
|
searchTerms: [
|
|
3035
3538
|
"title",
|
|
@@ -3044,7 +3547,7 @@ const H1 = {
|
|
|
3044
3547
|
const H2 = {
|
|
3045
3548
|
title: "Subtitle",
|
|
3046
3549
|
description: "Medium heading",
|
|
3047
|
-
icon: Heading2,
|
|
3550
|
+
icon: /* @__PURE__ */ jsx(Heading2, { size: 20 }),
|
|
3048
3551
|
category: "Text",
|
|
3049
3552
|
searchTerms: [
|
|
3050
3553
|
"subtitle",
|
|
@@ -3058,7 +3561,7 @@ const H2 = {
|
|
|
3058
3561
|
const H3 = {
|
|
3059
3562
|
title: "Heading",
|
|
3060
3563
|
description: "Small heading",
|
|
3061
|
-
icon: Heading3,
|
|
3564
|
+
icon: /* @__PURE__ */ jsx(Heading3, { size: 20 }),
|
|
3062
3565
|
category: "Text",
|
|
3063
3566
|
searchTerms: [
|
|
3064
3567
|
"subtitle",
|
|
@@ -3072,7 +3575,7 @@ const H3 = {
|
|
|
3072
3575
|
const BULLET_LIST = {
|
|
3073
3576
|
title: "Bullet list",
|
|
3074
3577
|
description: "Unordered list",
|
|
3075
|
-
icon: List,
|
|
3578
|
+
icon: /* @__PURE__ */ jsx(List, { size: 20 }),
|
|
3076
3579
|
category: "Text",
|
|
3077
3580
|
searchTerms: ["unordered", "point"],
|
|
3078
3581
|
command: ({ editor, range }) => {
|
|
@@ -3082,7 +3585,7 @@ const BULLET_LIST = {
|
|
|
3082
3585
|
const NUMBERED_LIST = {
|
|
3083
3586
|
title: "Numbered list",
|
|
3084
3587
|
description: "Ordered list",
|
|
3085
|
-
icon: ListOrdered,
|
|
3588
|
+
icon: /* @__PURE__ */ jsx(ListOrdered, { size: 20 }),
|
|
3086
3589
|
category: "Text",
|
|
3087
3590
|
searchTerms: ["ordered"],
|
|
3088
3591
|
command: ({ editor, range }) => {
|
|
@@ -3092,7 +3595,7 @@ const NUMBERED_LIST = {
|
|
|
3092
3595
|
const QUOTE = {
|
|
3093
3596
|
title: "Quote",
|
|
3094
3597
|
description: "Block quote",
|
|
3095
|
-
icon: TextQuote,
|
|
3598
|
+
icon: /* @__PURE__ */ jsx(TextQuote, { size: 20 }),
|
|
3096
3599
|
category: "Text",
|
|
3097
3600
|
searchTerms: ["blockquote"],
|
|
3098
3601
|
command: ({ editor, range }) => {
|
|
@@ -3102,7 +3605,7 @@ const QUOTE = {
|
|
|
3102
3605
|
const CODE = {
|
|
3103
3606
|
title: "Code block",
|
|
3104
3607
|
description: "Code snippet",
|
|
3105
|
-
icon: SquareCode,
|
|
3608
|
+
icon: /* @__PURE__ */ jsx(SquareCode, { size: 20 }),
|
|
3106
3609
|
category: "Text",
|
|
3107
3610
|
searchTerms: ["codeblock"],
|
|
3108
3611
|
command: ({ editor, range }) => {
|
|
@@ -3112,7 +3615,7 @@ const CODE = {
|
|
|
3112
3615
|
const BUTTON = {
|
|
3113
3616
|
title: "Button",
|
|
3114
3617
|
description: "Clickable button",
|
|
3115
|
-
icon: MousePointer,
|
|
3618
|
+
icon: /* @__PURE__ */ jsx(MousePointer, { size: 20 }),
|
|
3116
3619
|
category: "Layout",
|
|
3117
3620
|
searchTerms: ["button"],
|
|
3118
3621
|
command: ({ editor, range }) => {
|
|
@@ -3122,7 +3625,7 @@ const BUTTON = {
|
|
|
3122
3625
|
const DIVIDER = {
|
|
3123
3626
|
title: "Divider",
|
|
3124
3627
|
description: "Horizontal separator",
|
|
3125
|
-
icon: SplitSquareVertical,
|
|
3628
|
+
icon: /* @__PURE__ */ jsx(SplitSquareVertical, { size: 20 }),
|
|
3126
3629
|
category: "Layout",
|
|
3127
3630
|
searchTerms: [
|
|
3128
3631
|
"hr",
|
|
@@ -3136,7 +3639,7 @@ const DIVIDER = {
|
|
|
3136
3639
|
const SECTION = {
|
|
3137
3640
|
title: "Section",
|
|
3138
3641
|
description: "Content section",
|
|
3139
|
-
icon: Rows2,
|
|
3642
|
+
icon: /* @__PURE__ */ jsx(Rows2, { size: 20 }),
|
|
3140
3643
|
category: "Layout",
|
|
3141
3644
|
searchTerms: [
|
|
3142
3645
|
"section",
|
|
@@ -3150,7 +3653,7 @@ const SECTION = {
|
|
|
3150
3653
|
const TWO_COLUMNS = {
|
|
3151
3654
|
title: "2 columns",
|
|
3152
3655
|
description: "Two column layout",
|
|
3153
|
-
icon: Columns2,
|
|
3656
|
+
icon: /* @__PURE__ */ jsx(Columns2, { size: 20 }),
|
|
3154
3657
|
category: "Layout",
|
|
3155
3658
|
searchTerms: [
|
|
3156
3659
|
"columns",
|
|
@@ -3171,7 +3674,7 @@ const TWO_COLUMNS = {
|
|
|
3171
3674
|
const THREE_COLUMNS = {
|
|
3172
3675
|
title: "3 columns",
|
|
3173
3676
|
description: "Three column layout",
|
|
3174
|
-
icon: Columns3,
|
|
3677
|
+
icon: /* @__PURE__ */ jsx(Columns3, { size: 20 }),
|
|
3175
3678
|
category: "Layout",
|
|
3176
3679
|
searchTerms: [
|
|
3177
3680
|
"columns",
|
|
@@ -3191,7 +3694,7 @@ const THREE_COLUMNS = {
|
|
|
3191
3694
|
const FOUR_COLUMNS = {
|
|
3192
3695
|
title: "4 columns",
|
|
3193
3696
|
description: "Four column layout",
|
|
3194
|
-
icon: Columns4,
|
|
3697
|
+
icon: /* @__PURE__ */ jsx(Columns4, { size: 20 }),
|
|
3195
3698
|
category: "Layout",
|
|
3196
3699
|
searchTerms: [
|
|
3197
3700
|
"columns",
|
|
@@ -3343,5 +3846,5 @@ function createSlashCommand(options) {
|
|
|
3343
3846
|
const SlashCommand = createSlashCommand();
|
|
3344
3847
|
|
|
3345
3848
|
//#endregion
|
|
3346
|
-
export { AlignmentAttribute, BULLET_LIST, BUTTON, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, EmailNode, FOUR_COLUMNS, FourColumns, H1, H2, H3, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, coreExtensions, createSlashCommand, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, isAtMaxColumnsDepth, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useImageBubbleMenuContext, useLinkBubbleMenuContext };
|
|
3849
|
+
export { AlignmentAttribute, BULLET_LIST, BUTTON, Blockquote, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, BulletList, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, Code, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, EmailNode, FOUR_COLUMNS, FourColumns, H1, H2, H3, HardBreak, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, Italic, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, ListItem, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, OrderedList, Paragraph, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, Strike, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, composeReactEmail, coreExtensions, createSlashCommand, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, isAtMaxColumnsDepth, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useEditor, useImageBubbleMenuContext, useLinkBubbleMenuContext };
|
|
3347
3850
|
//# sourceMappingURL=index.mjs.map
|