@react-email/editor 0.0.0-experimental.4 → 0.0.0-experimental.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/core/index.cjs +9 -0
  2. package/dist/core/index.d.cts +2 -0
  3. package/dist/core/index.d.mts +3 -0
  4. package/dist/core/index.mjs +4 -0
  5. package/dist/create-paste-handler-B8BtjBk3.d.cts +14 -0
  6. package/dist/create-paste-handler-B8BtjBk3.d.cts.map +1 -0
  7. package/dist/create-paste-handler-CGR738bC.d.mts +14 -0
  8. package/dist/create-paste-handler-CGR738bC.d.mts.map +1 -0
  9. package/dist/event-bus-CHEzOS_O.mjs +329 -0
  10. package/dist/event-bus-CHEzOS_O.mjs.map +1 -0
  11. package/dist/event-bus-fb8U7hrl.cjs +450 -0
  12. package/dist/extension-DyY8_bh4.mjs +1110 -0
  13. package/dist/extension-DyY8_bh4.mjs.map +1 -0
  14. package/dist/extension-w5VaUeSw.cjs +1235 -0
  15. package/dist/extensions/index.cjs +51 -0
  16. package/dist/extensions/index.d.cts +399 -0
  17. package/dist/extensions/index.d.cts.map +1 -0
  18. package/dist/extensions/index.d.mts +400 -0
  19. package/dist/extensions/index.d.mts.map +1 -0
  20. package/dist/extensions/index.mjs +5 -0
  21. package/dist/extensions-BvfmaKCn.mjs +2088 -0
  22. package/dist/extensions-BvfmaKCn.mjs.map +1 -0
  23. package/dist/extensions-CkjPj2JO.cjs +2369 -0
  24. package/dist/global-content-D_WYaFgX.mjs +78 -0
  25. package/dist/global-content-D_WYaFgX.mjs.map +1 -0
  26. package/dist/global-content-bJgotqmA.cjs +89 -0
  27. package/dist/index-C4KcMQ0R.d.cts +161 -0
  28. package/dist/index-C4KcMQ0R.d.cts.map +1 -0
  29. package/dist/index-CxX7W63O.d.mts +161 -0
  30. package/dist/index-CxX7W63O.d.mts.map +1 -0
  31. package/dist/index.cjs +74 -0
  32. package/dist/index.css +832 -0
  33. package/dist/index.css.map +1 -0
  34. package/dist/index.d.cts +33 -0
  35. package/dist/index.d.cts.map +1 -0
  36. package/dist/index.d.mts +31 -277
  37. package/dist/index.d.mts.map +1 -1
  38. package/dist/index.mjs +64 -1377
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/plugins/index.cjs +23 -0
  41. package/dist/plugins/index.d.cts +191 -0
  42. package/dist/plugins/index.d.cts.map +1 -0
  43. package/dist/plugins/index.d.mts +191 -0
  44. package/dist/plugins/index.d.mts.map +1 -0
  45. package/dist/plugins/index.mjs +3 -0
  46. package/dist/root-CkYaJZpj.mjs +2316 -0
  47. package/dist/root-CkYaJZpj.mjs.map +1 -0
  48. package/dist/root-Gu08xybW.cjs +2832 -0
  49. package/dist/set-text-alignment-Cv72txmv.cjs +24 -0
  50. package/dist/set-text-alignment-OA8IMWmO.mjs +19 -0
  51. package/dist/set-text-alignment-OA8IMWmO.mjs.map +1 -0
  52. package/dist/styles-C-cCyJCn.cjs +211 -0
  53. package/dist/styles-_TMw3YxC.mjs +194 -0
  54. package/dist/styles-_TMw3YxC.mjs.map +1 -0
  55. package/dist/ui/bubble-menu/bubble-menu.css +285 -0
  56. package/dist/ui/index.cjs +147 -0
  57. package/dist/ui/index.d.cts +939 -0
  58. package/dist/ui/index.d.cts.map +1 -0
  59. package/dist/ui/index.d.mts +939 -0
  60. package/dist/ui/index.d.mts.map +1 -0
  61. package/dist/ui/index.mjs +60 -0
  62. package/dist/ui/index.mjs.map +1 -0
  63. package/dist/ui/slash-command/slash-command.css +44 -0
  64. package/dist/ui/themes/default.css +830 -0
  65. package/dist/utils/index.cjs +3 -0
  66. package/dist/utils/index.d.cts +7 -0
  67. package/dist/utils/index.d.cts.map +1 -0
  68. package/dist/utils/index.d.mts +7 -0
  69. package/dist/utils/index.d.mts.map +1 -0
  70. package/dist/utils/index.mjs +3 -0
  71. package/package.json +109 -21
  72. package/dist/index.d.ts +0 -279
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/index.js +0 -1436
package/dist/index.mjs CHANGED
@@ -1,1387 +1,74 @@
1
- import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
2
- import { jsx } from "react/jsx-runtime";
3
- import * as ReactEmailComponents from "@react-email/components";
4
- import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
5
- import CodeBlock$1 from "@tiptap/extension-code-block";
6
- import { Plugin, PluginKey } from "@tiptap/pm/state";
7
- import { Decoration, DecorationSet } from "@tiptap/pm/view";
8
- import { fromHtml } from "hast-util-from-html";
9
- import Prism from "prismjs";
10
- import TipTapPlaceholder from "@tiptap/extension-placeholder";
11
-
12
- //#region src/core/email-node.ts
13
- var EmailNode = class EmailNode extends Node {
14
- constructor(config) {
15
- super(config);
16
- }
17
- /**
18
- * Create a new Node instance
19
- * @param config - Node configuration object or a function that returns a configuration object
20
- */
21
- static create(config) {
22
- return new EmailNode(typeof config === "function" ? config() : config);
23
- }
24
- static from(node, renderToReactEmail) {
25
- const customNode = EmailNode.create({});
26
- Object.assign(customNode, { ...node });
27
- customNode.config = {
28
- ...node.config,
29
- renderToReactEmail
30
- };
31
- return customNode;
32
- }
33
- configure(options) {
34
- return super.configure(options);
35
- }
36
- extend(extendedConfig) {
37
- const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
38
- return super.extend(resolvedConfig);
39
- }
40
- };
41
-
42
- //#endregion
43
- //#region src/extensions/alignment-attribute.tsx
44
- const AlignmentAttribute = Extension.create({
45
- name: "alignmentAttribute",
46
- addOptions() {
47
- return {
48
- types: [],
49
- alignments: [
50
- "left",
51
- "center",
52
- "right",
53
- "justify"
54
- ]
55
- };
56
- },
57
- addGlobalAttributes() {
58
- return [{
59
- types: this.options.types,
60
- attributes: { alignment: {
61
- parseHTML: (element) => {
62
- const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
63
- if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
64
- return null;
65
- },
66
- renderHTML: (attributes) => {
67
- if (attributes.alignment === "left") return {};
68
- return { alignment: attributes.alignment };
69
- }
70
- } }
71
- }];
72
- },
73
- addCommands() {
74
- return { setAlignment: (alignment) => ({ commands }) => {
75
- if (!this.options.alignments.includes(alignment)) return false;
76
- return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
77
- } };
78
- },
79
- addKeyboardShortcuts() {
80
- return {
81
- Enter: () => {
82
- const { from } = this.editor.state.selection;
83
- const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
84
- if (currentAlignment) requestAnimationFrame(() => {
85
- this.editor.commands.setAlignment(currentAlignment);
86
- });
87
- return false;
88
- },
89
- "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
90
- "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
91
- "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
92
- "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
93
- };
94
- }
95
- });
96
-
97
- //#endregion
98
- //#region src/utils/attribute-helpers.ts
99
- /**
100
- * Creates TipTap attribute definitions for a list of HTML attributes.
101
- * Each attribute will have the same pattern:
102
- * - default: null
103
- * - parseHTML: extracts the attribute from the element
104
- * - renderHTML: conditionally renders the attribute if it has a value
105
- *
106
- * @param attributeNames - Array of HTML attribute names to create definitions for
107
- * @returns Object with TipTap attribute definitions
108
- *
109
- * @example
110
- * const attrs = createStandardAttributes(['class', 'id', 'title']);
111
- * // Returns:
112
- * // {
113
- * // class: {
114
- * // default: null,
115
- * // parseHTML: (element) => element.getAttribute('class'),
116
- * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
117
- * // },
118
- * // ...
119
- * // }
120
- */
121
- function createStandardAttributes(attributeNames) {
122
- return Object.fromEntries(attributeNames.map((attr) => [attr, {
123
- default: null,
124
- parseHTML: (element) => element.getAttribute(attr),
125
- renderHTML: (attributes) => {
126
- if (!attributes[attr]) return {};
127
- return { [attr]: attributes[attr] };
128
- }
129
- }]));
130
- }
131
- /**
132
- * Common HTML attributes used across multiple extensions.
133
- * These preserve attributes during HTML import and editing for better
134
- * fidelity when importing existing email templates.
135
- */
136
- const COMMON_HTML_ATTRIBUTES = [
137
- "id",
138
- "class",
139
- "title",
140
- "lang",
141
- "dir",
142
- "data-id"
143
- ];
144
- /**
145
- * Layout-specific HTML attributes used for positioning and sizing.
146
- */
147
- const LAYOUT_ATTRIBUTES = [
148
- "align",
149
- "width",
150
- "height"
151
- ];
152
- /**
153
- * Table-specific HTML attributes used for table layout and styling.
154
- */
155
- const TABLE_ATTRIBUTES = [
156
- "border",
157
- "cellpadding",
158
- "cellspacing"
159
- ];
160
- /**
161
- * Table cell-specific HTML attributes.
162
- */
163
- const TABLE_CELL_ATTRIBUTES = [
164
- "valign",
165
- "bgcolor",
166
- "colspan",
167
- "rowspan"
168
- ];
169
- /**
170
- * Table header cell-specific HTML attributes.
171
- * These are additional attributes that only apply to <th> elements.
172
- */
173
- const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
174
-
175
- //#endregion
176
- //#region src/utils/styles.ts
177
- const WHITE_SPACE_REGEX = /\s+/;
178
- const inlineCssToJs = (inlineStyle, options = {}) => {
179
- const styleObject = {};
180
- if (!inlineStyle || inlineStyle === "" || typeof inlineStyle === "object") return styleObject;
181
- inlineStyle.split(";").forEach((style) => {
182
- if (style.trim()) {
183
- const [key, value] = style.split(":");
184
- const valueTrimmed = value?.trim();
185
- if (!valueTrimmed) return;
186
- const formattedKey = key.trim().replace(/-\w/g, (match) => match[1].toUpperCase());
187
- styleObject[formattedKey] = options?.removeUnit ? valueTrimmed.replace(/px|%/g, "") : valueTrimmed;
188
- }
189
- });
190
- return styleObject;
191
- };
192
- /**
193
- * Expands CSS shorthand properties (margin, padding) into their longhand equivalents.
194
- * This prevents shorthand properties from overriding specific longhand properties in email clients.
195
- *
196
- * @param styles - Style object that may contain shorthand properties
197
- * @returns New style object with shorthand properties expanded to longhand
198
- *
199
- * @example
200
- * expandShorthandProperties({ margin: '0', paddingTop: '10px' })
201
- * // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }
202
- */
203
- function expandShorthandProperties(styles) {
204
- if (!styles || typeof styles !== "object") return {};
205
- const expanded = {};
206
- for (const key in styles) {
207
- const value = styles[key];
208
- if (value === void 0 || value === null || value === "") continue;
209
- switch (key) {
210
- case "margin": {
211
- const values = parseShorthandValue(value);
212
- expanded.marginTop = values.top;
213
- expanded.marginRight = values.right;
214
- expanded.marginBottom = values.bottom;
215
- expanded.marginLeft = values.left;
216
- break;
217
- }
218
- case "padding": {
219
- const values = parseShorthandValue(value);
220
- expanded.paddingTop = values.top;
221
- expanded.paddingRight = values.right;
222
- expanded.paddingBottom = values.bottom;
223
- expanded.paddingLeft = values.left;
224
- break;
225
- }
226
- case "border": {
227
- const values = convertBorderValue(value);
228
- expanded.borderStyle = values.style;
229
- expanded.borderWidth = values.width;
230
- expanded.borderColor = values.color;
231
- break;
232
- }
233
- case "borderTopLeftRadius":
234
- case "borderTopRightRadius":
235
- case "borderBottomLeftRadius":
236
- case "borderBottomRightRadius":
237
- expanded[key] = value;
238
- if (styles.borderTopLeftRadius && styles.borderTopRightRadius && styles.borderBottomLeftRadius && styles.borderBottomRightRadius) {
239
- const values = [
240
- styles.borderTopLeftRadius,
241
- styles.borderTopRightRadius,
242
- styles.borderBottomLeftRadius,
243
- styles.borderBottomRightRadius
244
- ];
245
- if (new Set(values).size === 1) expanded.borderRadius = values[0];
246
- }
247
- break;
248
- default: expanded[key] = value;
249
- }
250
- }
251
- return expanded;
252
- }
253
- /**
254
- * Parses CSS shorthand value (1-4 values) into individual side values.
255
- * Follows CSS specification for shorthand property value parsing.
256
- *
257
- * @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')
258
- * @returns Object with top, right, bottom, left values
259
- */
260
- function parseShorthandValue(value) {
261
- const stringValue = String(value).trim();
262
- const parts = stringValue.split(WHITE_SPACE_REGEX);
263
- const len = parts.length;
264
- if (len === 1) return {
265
- top: parts[0],
266
- right: parts[0],
267
- bottom: parts[0],
268
- left: parts[0]
269
- };
270
- if (len === 2) return {
271
- top: parts[0],
272
- right: parts[1],
273
- bottom: parts[0],
274
- left: parts[1]
275
- };
276
- if (len === 3) return {
277
- top: parts[0],
278
- right: parts[1],
279
- bottom: parts[2],
280
- left: parts[1]
281
- };
282
- if (len === 4) return {
283
- top: parts[0],
284
- right: parts[1],
285
- bottom: parts[2],
286
- left: parts[3]
287
- };
288
- return {
289
- top: stringValue,
290
- right: stringValue,
291
- bottom: stringValue,
292
- left: stringValue
293
- };
294
- }
295
- function convertBorderValue(value) {
296
- const stringValue = String(value).trim();
297
- const parts = stringValue.split(WHITE_SPACE_REGEX);
298
- switch (parts.length) {
299
- case 1: return {
300
- style: "solid",
301
- width: parts[0],
302
- color: "black"
303
- };
304
- case 2: return {
305
- style: parts[1],
306
- width: parts[0],
307
- color: "black"
308
- };
309
- case 3: return {
310
- style: parts[1],
311
- width: parts[0],
312
- color: parts[2]
313
- };
314
- case 4: return {
315
- style: parts[1],
316
- width: parts[0],
317
- color: parts[2]
318
- };
319
- default: return {
320
- style: "solid",
321
- width: stringValue,
322
- color: "black"
323
- };
324
- }
325
- }
326
- /**
327
- * Resolves conflicts between reset styles and inline styles by expanding
328
- * shorthand properties (margin, padding) to longhand before merging.
329
- * This prevents shorthand properties from overriding specific longhand properties.
330
- *
331
- * @param resetStyles - Base reset styles that may contain shorthand properties
332
- * @param inlineStyles - Inline styles that should override reset styles
333
- * @returns Merged styles with inline styles taking precedence
334
- */
335
- function resolveConflictingStyles(resetStyles, inlineStyles) {
336
- const expandedResetStyles = expandShorthandProperties(resetStyles);
337
- const expandedInlineStyles = expandShorthandProperties(inlineStyles);
338
- return {
339
- ...expandedResetStyles,
340
- ...expandedInlineStyles
341
- };
342
- }
343
-
344
- //#endregion
345
- //#region src/extensions/body.tsx
346
- const Body = EmailNode.create({
347
- name: "body",
348
- group: "block",
349
- content: "block+",
350
- defining: true,
351
- isolating: true,
352
- addAttributes() {
353
- return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
354
- },
355
- parseHTML() {
356
- return [{
357
- tag: "body",
358
- getAttrs: (node) => {
359
- if (typeof node === "string") return false;
360
- const element = node;
361
- const attrs = {};
362
- Array.from(element.attributes).forEach((attr) => {
363
- attrs[attr.name] = attr.value;
364
- });
365
- return attrs;
366
- }
367
- }];
368
- },
369
- renderHTML({ HTMLAttributes }) {
370
- return [
371
- "div",
372
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
373
- 0
374
- ];
375
- },
376
- renderToReactEmail({ children, node, styles }) {
377
- const inlineStyles = inlineCssToJs(node.attrs?.style);
378
- return /* @__PURE__ */ jsx("div", {
379
- className: node.attrs?.class || void 0,
380
- style: {
381
- ...styles.reset,
382
- ...inlineStyles
383
- },
384
- children
385
- });
386
- }
387
- });
388
-
389
- //#endregion
390
- //#region src/extensions/bold.ts
391
- /**
392
- * Matches bold text via `**` as input.
393
- */
394
- const starInputRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
395
- /**
396
- * Matches bold text via `**` while pasting.
397
- */
398
- const starPasteRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
399
- /**
400
- * Matches bold text via `__` as input.
401
- */
402
- const underscoreInputRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
403
- /**
404
- * Matches bold text via `__` while pasting.
405
- */
406
- const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
407
- /**
408
- * This extension allows you to mark text as bold.
409
- * @see https://tiptap.dev/api/marks/bold
410
- */
411
- const Bold = Mark.create({
412
- name: "bold",
413
- addOptions() {
414
- return { HTMLAttributes: {} };
415
- },
416
- parseHTML() {
417
- return [
418
- { tag: "strong" },
419
- {
420
- tag: "b",
421
- getAttrs: (node) => node.style.fontWeight !== "normal" && null
422
- },
423
- {
424
- style: "font-weight=400",
425
- clearMark: (mark) => mark.type.name === this.name
426
- }
427
- ];
428
- },
429
- renderHTML({ HTMLAttributes }) {
430
- return [
431
- "strong",
432
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
433
- 0
434
- ];
435
- },
436
- addCommands() {
437
- return {
438
- setBold: () => ({ commands }) => {
439
- return commands.setMark(this.name);
440
- },
441
- toggleBold: () => ({ commands }) => {
442
- return commands.toggleMark(this.name);
443
- },
444
- unsetBold: () => ({ commands }) => {
445
- return commands.unsetMark(this.name);
446
- }
447
- };
448
- },
449
- addKeyboardShortcuts() {
450
- return {
451
- "Mod-b": () => this.editor.commands.toggleBold(),
452
- "Mod-B": () => this.editor.commands.toggleBold()
453
- };
454
- },
455
- addInputRules() {
456
- return [markInputRule({
457
- find: starInputRegex,
458
- type: this.type
459
- }), markInputRule({
460
- find: underscoreInputRegex,
461
- type: this.type
462
- })];
463
- },
464
- addPasteRules() {
465
- return [markPasteRule({
466
- find: starPasteRegex,
467
- type: this.type
468
- }), markPasteRule({
469
- find: underscorePasteRegex,
470
- type: this.type
471
- })];
472
- }
473
- });
474
-
475
- //#endregion
476
- //#region src/extensions/button.tsx
477
- const Button = EmailNode.create({
478
- name: "button",
479
- group: "block",
480
- content: "inline*",
481
- defining: true,
482
- draggable: true,
483
- marks: "bold",
484
- addAttributes() {
485
- return {
486
- class: { default: "button" },
487
- href: { default: "#" },
488
- alignment: { default: "left" }
489
- };
490
- },
491
- parseHTML() {
492
- return [{
493
- tag: "a[data-id=\"react-email-button\"]",
494
- getAttrs: (node) => {
495
- if (typeof node === "string") return false;
496
- const element = node;
497
- const attrs = {};
498
- Array.from(element.attributes).forEach((attr) => {
499
- attrs[attr.name] = attr.value;
500
- });
501
- return attrs;
502
- }
503
- }];
504
- },
505
- renderHTML({ HTMLAttributes }) {
506
- return [
507
- "div",
508
- mergeAttributes({ class: `align-${HTMLAttributes?.alignment}` }),
509
- [
510
- "a",
511
- mergeAttributes({
512
- class: `node-button ${HTMLAttributes?.class}`,
513
- style: HTMLAttributes?.style,
514
- "data-id": "react-email-button",
515
- "data-href": HTMLAttributes?.href
516
- }),
517
- 0
518
- ]
519
- ];
520
- },
521
- addCommands() {
522
- return {
523
- updateButton: (attributes) => ({ commands }) => {
524
- return commands.updateAttributes("button", attributes);
525
- },
526
- setButton: () => ({ commands }) => {
527
- return commands.insertContent({
528
- type: "button",
529
- content: [{
530
- type: "text",
531
- text: "Button"
532
- }]
533
- });
534
- }
535
- };
536
- },
537
- renderToReactEmail({ children, node, styles }) {
538
- const inlineStyles = inlineCssToJs(node.attrs?.style);
539
- return /* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Column, {
540
- align: node.attrs?.align || node.attrs?.alignment,
541
- children: /* @__PURE__ */ jsx(Button$1, {
542
- className: node.attrs?.class || void 0,
543
- href: node.attrs?.href,
544
- style: {
545
- ...styles.reset,
546
- ...styles.button,
547
- ...inlineStyles
548
- },
549
- children
550
- })
551
- }) });
552
- }
553
- });
554
-
555
- //#endregion
556
- //#region src/extensions/class-attribute.tsx
557
- const ClassAttribute = Extension.create({
558
- name: "classAttribute",
559
- addOptions() {
560
- return {
561
- types: [],
562
- class: []
563
- };
564
- },
565
- addGlobalAttributes() {
566
- return [{
567
- types: this.options.types,
568
- attributes: { class: {
569
- default: "",
570
- parseHTML: (element) => element.className || "",
571
- renderHTML: (attributes) => {
572
- return attributes.class ? { class: attributes.class } : {};
573
- }
574
- } }
575
- }];
576
- },
577
- addCommands() {
578
- return {
579
- unsetClass: () => ({ commands }) => {
580
- return this.options.types.every((type) => commands.resetAttributes(type, "class"));
581
- },
582
- setClass: (classList) => ({ commands }) => {
583
- return this.options.types.every((type) => commands.updateAttributes(type, { class: classList }));
584
- }
585
- };
586
- },
587
- addKeyboardShortcuts() {
588
- return { Enter: ({ editor }) => {
589
- requestAnimationFrame(() => {
590
- editor.commands.resetAttributes("paragraph", "class");
591
- });
592
- return false;
593
- } };
594
- }
595
- });
596
-
597
- //#endregion
598
- //#region src/utils/prism-utils.ts
599
- const publicURL = "/styles/prism";
600
- function loadPrismTheme(theme) {
601
- const link = document.createElement("link");
602
- link.rel = "stylesheet";
603
- link.href = `${publicURL}/prism-${theme}.css`;
604
- link.setAttribute("data-prism-theme", "");
605
- document.head.appendChild(link);
606
- }
607
- function removePrismTheme() {
608
- const existingTheme = document.querySelectorAll("link[rel=\"stylesheet\"][data-prism-theme]");
609
- if (existingTheme.length > 0) existingTheme.forEach((cssLinkTag) => {
610
- cssLinkTag.remove();
611
- });
612
- }
613
- function hasPrismThemeLoaded(theme) {
614
- return !!document.querySelector(`link[rel="stylesheet"][data-prism-theme][href="${publicURL}/prism-${theme}.css"]`);
615
- }
616
-
617
- //#endregion
618
- //#region src/extensions/prism-plugin.ts
619
- const PRISM_LANGUAGE_LOADED_META = "prismLanguageLoaded";
620
- function parseNodes(nodes, className = []) {
621
- return nodes.flatMap((node) => {
622
- const classes = [...className, ...node.properties ? node.properties.className : []];
623
- if (node.children) return parseNodes(node.children, classes);
624
- return {
625
- text: node.value ?? "",
626
- classes
627
- };
628
- });
629
- }
630
- function getHighlightNodes(html) {
631
- return fromHtml(html, { fragment: true }).children;
632
- }
633
- function registeredLang(aliasOrLanguage) {
634
- const allSupportLang = Object.keys(Prism.languages).filter((id) => typeof Prism.languages[id] === "object");
635
- return Boolean(allSupportLang.find((x) => x === aliasOrLanguage));
636
- }
637
- function getDecorations({ doc, name, defaultLanguage, defaultTheme, loadingLanguages, onLanguageLoaded }) {
638
- const decorations = [];
639
- findChildren(doc, (node) => node.type.name === name).forEach((block) => {
640
- let from = block.pos + 1;
641
- const language = block.node.attrs.language || defaultLanguage;
642
- const theme = block.node.attrs.theme || defaultTheme;
643
- let html = "";
644
- try {
645
- if (!registeredLang(language) && !loadingLanguages.has(language)) {
646
- loadingLanguages.add(language);
647
- import(`prismjs/components/prism-${language}`).then(() => {
648
- loadingLanguages.delete(language);
649
- onLanguageLoaded(language);
650
- }).catch(() => {
651
- loadingLanguages.delete(language);
652
- });
653
- }
654
- if (!hasPrismThemeLoaded(theme)) loadPrismTheme(theme);
655
- html = Prism.highlight(block.node.textContent, Prism.languages[language], language);
656
- } catch {
657
- html = Prism.highlight(block.node.textContent, Prism.languages.javascript, "js");
658
- }
659
- parseNodes(getHighlightNodes(html)).forEach((node) => {
660
- const to = from + node.text.length;
661
- if (node.classes.length) {
662
- const decoration = Decoration.inline(from, to, { class: node.classes.join(" ") });
663
- decorations.push(decoration);
664
- }
665
- from = to;
666
- });
667
- });
668
- return DecorationSet.create(doc, decorations);
669
- }
670
- function PrismPlugin({ name, defaultLanguage, defaultTheme }) {
671
- if (!defaultLanguage) throw Error("You must specify the defaultLanguage parameter");
672
- const loadingLanguages = /* @__PURE__ */ new Set();
673
- let pluginView = null;
674
- const onLanguageLoaded = (language) => {
675
- if (pluginView) pluginView.dispatch(pluginView.state.tr.setMeta(PRISM_LANGUAGE_LOADED_META, language));
676
- };
677
- const prismjsPlugin = new Plugin({
678
- key: new PluginKey("prism"),
679
- view(view) {
680
- pluginView = view;
681
- return { destroy() {
682
- pluginView = null;
683
- } };
1
+ import { U as createPasteHandler, V as composeReactEmail, W as createDropHandler, t as StarterKit } from "./extensions-BvfmaKCn.mjs";
2
+ import { t as EmailTheming } from "./extension-DyY8_bh4.mjs";
3
+ import { J as BubbleMenuButtonDefault, O as BubbleMenuImageDefault, S as BubbleMenuLinkDefault, j as BubbleMenuDefault, t as SlashCommandRoot } from "./root-CkYaJZpj.mjs";
4
+ import { UndoRedo } from "@tiptap/extensions";
5
+ import { EditorProvider, useCurrentEditor } from "@tiptap/react";
6
+ import { forwardRef, useImperativeHandle, useMemo } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+
9
+ //#region src/email-editor/email-editor.tsx
10
+ function RefBridge({ editorRef }) {
11
+ const { editor } = useCurrentEditor();
12
+ useImperativeHandle(editorRef, () => ({
13
+ export: async () => {
14
+ if (!editor) return {
15
+ html: "",
16
+ text: ""
17
+ };
18
+ return composeReactEmail({ editor });
684
19
  },
685
- state: {
686
- init: (_, { doc }) => {
687
- return getDecorations({
688
- doc,
689
- name,
690
- defaultLanguage,
691
- defaultTheme,
692
- loadingLanguages,
693
- onLanguageLoaded
694
- });
695
- },
696
- apply: (transaction, decorationSet, oldState, newState) => {
697
- const oldNodeName = oldState.selection.$head.parent.type.name;
698
- const newNodeName = newState.selection.$head.parent.type.name;
699
- const oldNodes = findChildren(oldState.doc, (node) => node.type.name === name);
700
- const newNodes = findChildren(newState.doc, (node) => node.type.name === name);
701
- if (transaction.getMeta(PRISM_LANGUAGE_LOADED_META) || transaction.docChanged && ([oldNodeName, newNodeName].includes(name) || newNodes.length !== oldNodes.length || transaction.steps.some((step) => {
702
- const rangeStep = step;
703
- return rangeStep.from !== void 0 && rangeStep.to !== void 0 && oldNodes.some((node) => {
704
- return node.pos >= rangeStep.from && node.pos + node.node.nodeSize <= rangeStep.to;
705
- });
706
- }))) return getDecorations({
707
- doc: transaction.doc,
708
- name,
709
- defaultLanguage,
710
- defaultTheme,
711
- loadingLanguages,
712
- onLanguageLoaded
713
- });
714
- return decorationSet.map(transaction.mapping, transaction.doc);
715
- }
20
+ getJSON: () => editor?.getJSON() ?? {
21
+ type: "doc",
22
+ content: []
716
23
  },
717
- props: { decorations(state) {
718
- return prismjsPlugin.getState(state);
719
- } },
720
- destroy() {
721
- pluginView = null;
722
- removePrismTheme();
723
- }
724
- });
725
- return prismjsPlugin;
726
- }
727
-
728
- //#endregion
729
- //#region src/extensions/code-block.tsx
730
- const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
731
- addOptions() {
732
- return {
733
- languageClassPrefix: "language-",
734
- exitOnTripleEnter: false,
735
- exitOnArrowDown: false,
736
- enableTabIndentation: true,
737
- tabSize: 2,
738
- defaultLanguage: "javascript",
739
- defaultTheme: "default",
740
- HTMLAttributes: {}
741
- };
742
- },
743
- addAttributes() {
744
- return {
745
- ...this.parent?.(),
746
- language: {
747
- default: this.options.defaultLanguage,
748
- parseHTML: (element) => {
749
- if (!element) return null;
750
- const { languageClassPrefix } = this.options;
751
- if (!languageClassPrefix) return null;
752
- const language = [...element.firstElementChild?.classList || []].filter((className) => className.startsWith(languageClassPrefix || "")).map((className) => className.replace(languageClassPrefix, ""))[0];
753
- if (!language) return null;
754
- return language;
755
- },
756
- rendered: false
757
- },
758
- theme: {
759
- default: this.options.defaultTheme,
760
- rendered: false
761
- }
762
- };
763
- },
764
- renderHTML({ node, HTMLAttributes }) {
24
+ getHTML: () => editor?.getHTML() ?? "",
25
+ editor
26
+ }), [editor]);
27
+ return null;
28
+ }
29
+ const EmailEditor = forwardRef(({ content, onChange, onUploadImage, onReady, theme = "basic", editable = true, placeholder, bubbleMenu, extensions: extensionsProp, className }, ref) => {
30
+ const extensions = useMemo(() => {
31
+ if (extensionsProp) return extensionsProp;
765
32
  return [
766
- "pre",
767
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { class: node.attrs.language ? `${this.options.languageClassPrefix}${node.attrs.language}` : null }, { "data-theme": node.attrs.theme }),
768
- [
769
- "code",
770
- { class: node.attrs.language ? `${this.options.languageClassPrefix}${node.attrs.language} node-codeTag` : "node-codeTag" },
771
- 0
772
- ]
33
+ StarterKit.configure({ Placeholder: placeholder ? { placeholder } : void 0 }),
34
+ UndoRedo,
35
+ EmailTheming.configure({ theme })
773
36
  ];
774
- },
775
- addProseMirrorPlugins() {
776
- return [...this.parent?.() || [], PrismPlugin({
777
- name: this.name,
778
- defaultLanguage: this.options.defaultLanguage,
779
- defaultTheme: this.options.defaultTheme
780
- })];
781
- }
782
- }), ({ node, styles }) => {
783
- const language = node.attrs?.language ? `${node.attrs.language}` : "javascript";
784
- const userTheme = ReactEmailComponents[node.attrs?.theme];
785
- const theme = userTheme ? {
786
- ...userTheme,
787
- base: {
788
- ...userTheme.base,
789
- borderRadius: "0.125rem",
790
- padding: "0.75rem 1rem"
791
- }
792
- } : { base: {
793
- color: "#1e293b",
794
- background: "#f1f5f9",
795
- lineHeight: "1.5",
796
- fontFamily: "\"Fira Code\", \"Fira Mono\", Menlo, Consolas, \"DejaVu Sans Mono\", monospace",
797
- padding: "0.75rem 1rem",
798
- borderRadius: "0.125rem"
799
- } };
800
- return /* @__PURE__ */ jsx(CodeBlock, {
801
- code: node.content?.[0]?.text ?? "",
802
- language,
37
+ }, [
38
+ extensionsProp,
803
39
  theme,
804
- style: {
805
- width: "auto",
806
- ...styles.codeBlock
807
- }
808
- });
809
- });
810
-
811
- //#endregion
812
- //#region src/extensions/div.tsx
813
- const Div = EmailNode.create({
814
- name: "div",
815
- group: "block",
816
- content: "block+",
817
- defining: true,
818
- isolating: true,
819
- parseHTML() {
820
- return [{
821
- tag: "div:not([data-type])",
822
- getAttrs: (node) => {
823
- if (typeof node === "string") return false;
824
- const element = node;
825
- const attrs = {};
826
- Array.from(element.attributes).forEach((attr) => {
827
- attrs[attr.name] = attr.value;
828
- });
829
- return attrs;
830
- }
831
- }];
832
- },
833
- renderHTML({ HTMLAttributes }) {
834
- return [
835
- "div",
836
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
837
- 0
838
- ];
839
- },
840
- addAttributes() {
841
- return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
842
- },
843
- renderToReactEmail({ children, node, styles }) {
844
- const inlineStyles = inlineCssToJs(node.attrs?.style);
845
- return /* @__PURE__ */ jsx("div", {
846
- className: node.attrs?.class || void 0,
847
- style: {
848
- ...styles.reset,
849
- ...inlineStyles
850
- },
851
- children
852
- });
853
- }
854
- });
855
-
856
- //#endregion
857
- //#region src/extensions/max-nesting.ts
858
- const MaxNesting = Extension.create({
859
- name: "maxNesting",
860
- addOptions() {
861
- return {
862
- maxDepth: 3,
863
- nodeTypes: void 0
864
- };
865
- },
866
- addProseMirrorPlugins() {
867
- const { maxDepth, nodeTypes } = this.options;
868
- if (typeof maxDepth !== "number" || maxDepth < 1) throw new Error("maxDepth must be a positive number");
869
- return [new Plugin({
870
- key: new PluginKey("maxNesting"),
871
- appendTransaction(transactions, _oldState, newState) {
872
- if (!transactions.some((tr$1) => tr$1.docChanged)) return null;
873
- const rangesToLift = [];
874
- newState.doc.descendants((node, pos) => {
875
- let depth = 0;
876
- let currentPos = pos;
877
- let currentNode = node;
878
- while (currentNode && depth <= maxDepth) {
879
- if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
880
- const $pos = newState.doc.resolve(currentPos);
881
- if ($pos.depth === 0) break;
882
- currentPos = $pos.before($pos.depth);
883
- currentNode = newState.doc.nodeAt(currentPos);
884
- }
885
- if (depth > maxDepth) {
886
- const $pos = newState.doc.resolve(pos);
887
- if ($pos.depth > 0) {
888
- const range = $pos.blockRange();
889
- if (range && "canReplace" in newState.schema.nodes.doc && typeof newState.schema.nodes.doc.canReplace === "function" && newState.schema.nodes.doc.canReplace(range.start - 1, range.end + 1, newState.doc.slice(range.start, range.end).content)) rangesToLift.push({
890
- range,
891
- target: range.start - 1
892
- });
893
- }
894
- }
895
- });
896
- if (rangesToLift.length === 0) return null;
897
- const tr = newState.tr;
898
- for (let i = rangesToLift.length - 1; i >= 0; i--) {
899
- const { range, target } = rangesToLift[i];
900
- tr.lift(range, target);
901
- }
902
- return tr;
903
- },
904
- filterTransaction(tr) {
905
- if (!tr.docChanged) return true;
906
- let wouldCreateDeepNesting = false;
907
- const newDoc = tr.doc;
908
- newDoc.descendants((node, pos) => {
909
- if (wouldCreateDeepNesting) return false;
910
- let depth = 0;
911
- let currentPos = pos;
912
- let currentNode = node;
913
- while (currentNode && depth <= maxDepth) {
914
- if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
915
- const $pos = newDoc.resolve(currentPos);
916
- if ($pos.depth === 0) break;
917
- currentPos = $pos.before($pos.depth);
918
- currentNode = newDoc.nodeAt(currentPos);
919
- }
920
- if (depth > maxDepth) {
921
- wouldCreateDeepNesting = true;
922
- return false;
923
- }
924
- });
925
- return !wouldCreateDeepNesting;
926
- }
927
- })];
928
- }
929
- });
930
-
931
- //#endregion
932
- //#region src/extensions/placeholder.ts
933
- const Placeholder = TipTapPlaceholder.configure({
934
- placeholder: ({ node }) => {
935
- if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
936
- return "Press '/' for commands";
937
- },
938
- includeChildren: true
939
- });
940
-
941
- //#endregion
942
- //#region src/extensions/preserved-style.ts
943
- const PreservedStyle = Mark.create({
944
- name: "preservedStyle",
945
- addAttributes() {
946
- return { style: {
947
- default: null,
948
- parseHTML: (element) => element.getAttribute("style"),
949
- renderHTML: (attributes) => {
950
- if (!attributes.style) return {};
951
- return { style: attributes.style };
952
- }
953
- } };
954
- },
955
- parseHTML() {
956
- return [{
957
- tag: "span[style]",
958
- getAttrs: (element) => {
959
- if (typeof element === "string") return false;
960
- const style = element.getAttribute("style");
961
- if (style && hasPreservableStyles(style)) return { style };
962
- return false;
963
- }
964
- }];
965
- },
966
- renderHTML({ HTMLAttributes }) {
967
- return [
968
- "span",
969
- mergeAttributes(HTMLAttributes),
970
- 0
971
- ];
972
- }
973
- });
974
- const LINK_INDICATOR_STYLES = [
975
- "color",
976
- "text-decoration",
977
- "text-decoration-line",
978
- "text-decoration-color",
979
- "text-decoration-style"
980
- ];
981
- function parseStyleString(styleString) {
982
- const temp = document.createElement("div");
983
- temp.style.cssText = styleString;
984
- return temp.style;
985
- }
986
- function hasBackground(style) {
987
- const bgColor = style.backgroundColor;
988
- const bg = style.background;
989
- if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
990
- if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
991
- return false;
992
- }
993
- function hasPreservableStyles(styleString) {
994
- return processStylesForUnlink(styleString) !== null;
995
- }
996
- /**
997
- * Processes styles when unlinking:
998
- * - Has background (button-like): preserve all styles
999
- * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1000
- */
1001
- function processStylesForUnlink(styleString) {
1002
- if (!styleString) return null;
1003
- const style = parseStyleString(styleString);
1004
- if (hasBackground(style)) return styleString;
1005
- const filtered = [];
1006
- for (let i = 0; i < style.length; i++) {
1007
- const prop = style[i];
1008
- if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1009
- const value = style.getPropertyValue(prop);
1010
- if (value) filtered.push(`${prop}: ${value}`);
1011
- }
1012
- return filtered.length > 0 ? filtered.join("; ") : null;
1013
- }
1014
-
1015
- //#endregion
1016
- //#region src/extensions/preview-text.ts
1017
- const PreviewText = Node.create({
1018
- name: "previewText",
1019
- group: "block",
1020
- selectable: false,
1021
- draggable: false,
1022
- atom: true,
1023
- addOptions() {
1024
- return { HTMLAttributes: {} };
1025
- },
1026
- addStorage() {
1027
- return { previewText: null };
1028
- },
1029
- renderHTML() {
1030
- return ["div", { style: "display: none" }];
1031
- },
1032
- parseHTML() {
1033
- return [{
1034
- tag: "div[data-skip-in-text=\"true\"]",
1035
- getAttrs: (node) => {
1036
- if (typeof node === "string") return false;
1037
- const element = node;
1038
- let directText = "";
1039
- for (const child of element.childNodes) if (child.nodeType === 3) directText += child.textContent || "";
1040
- const cleanText = directText.trim();
1041
- if (cleanText) this.storage.previewText = cleanText;
1042
- return false;
1043
- }
1044
- }, {
1045
- tag: "span.preheader",
1046
- getAttrs: (node) => {
1047
- if (typeof node === "string") return false;
1048
- const preheaderText = node.textContent?.trim();
1049
- if (preheaderText) this.storage.previewText = preheaderText;
1050
- return false;
1051
- }
1052
- }];
1053
- }
1054
- });
1055
-
1056
- //#endregion
1057
- //#region src/utils/get-text-alignment.ts
1058
- function getTextAlignment(alignment) {
1059
- switch (alignment) {
1060
- case "left": return { textAlign: "left" };
1061
- case "center": return { textAlign: "center" };
1062
- case "right": return { textAlign: "right" };
1063
- default: return {};
1064
- }
1065
- }
1066
-
1067
- //#endregion
1068
- //#region src/extensions/section.tsx
1069
- const Section = EmailNode.create({
1070
- name: "section",
1071
- group: "block",
1072
- content: "block+",
1073
- isolating: true,
1074
- defining: true,
1075
- parseHTML() {
1076
- return [{ tag: "section[data-type=\"section\"]" }];
1077
- },
1078
- renderHTML({ HTMLAttributes }) {
1079
- return [
1080
- "section",
1081
- mergeAttributes({
1082
- "data-type": "section",
1083
- class: "node-section"
1084
- }, HTMLAttributes),
1085
- 0
1086
- ];
1087
- },
1088
- addCommands() {
1089
- return { insertSection: () => ({ commands }) => {
1090
- return commands.insertContent({
1091
- type: this.name,
1092
- content: [{
1093
- type: "paragraph",
1094
- content: []
1095
- }]
1096
- });
1097
- } };
1098
- },
1099
- renderToReactEmail({ children, node, styles }) {
1100
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1101
- const textAlign = node.attrs?.align || node.attrs?.alignment;
1102
- return /* @__PURE__ */ jsx(Section$1, {
1103
- className: node.attrs?.class || void 0,
1104
- align: textAlign,
1105
- style: {
1106
- ...styles.section,
1107
- ...inlineStyles,
1108
- ...getTextAlignment(textAlign)
1109
- },
1110
- children
1111
- });
1112
- }
1113
- });
1114
-
1115
- //#endregion
1116
- //#region src/extensions/style-attribute.tsx
1117
- const StyleAttribute = Extension.create({
1118
- name: "styleAttribute",
1119
- priority: 101,
1120
- addOptions() {
1121
- return {
1122
- types: [],
1123
- style: []
1124
- };
1125
- },
1126
- addGlobalAttributes() {
1127
- return [{
1128
- types: this.options.types,
1129
- attributes: { style: {
1130
- default: "",
1131
- parseHTML: (element) => element.getAttribute("style") || "",
1132
- renderHTML: (attributes) => {
1133
- return { style: attributes.style ?? "" };
1134
- }
1135
- } }
1136
- }];
1137
- },
1138
- addCommands() {
1139
- return {
1140
- unsetStyle: () => ({ commands }) => {
1141
- return this.options.types.every((type) => commands.resetAttributes(type, "style"));
1142
- },
1143
- setStyle: (style) => ({ commands }) => {
1144
- return this.options.types.every((type) => commands.updateAttributes(type, { style }));
1145
- }
1146
- };
1147
- },
1148
- addKeyboardShortcuts() {
1149
- return { Enter: ({ editor }) => {
1150
- const { state } = editor.view;
1151
- const { selection } = state;
1152
- const { $from } = selection;
1153
- const textBefore = $from.nodeBefore?.text || "";
1154
- if (textBefore.includes("{{") || textBefore.includes("{{{")) return false;
1155
- requestAnimationFrame(() => {
1156
- editor.commands.resetAttributes("paragraph", "style");
1157
- });
1158
- return false;
1159
- } };
1160
- }
1161
- });
1162
-
1163
- //#endregion
1164
- //#region src/extensions/sup.ts
1165
- /**
1166
- * This extension allows you to mark text as superscript.
1167
- * @see https://tiptap.dev/api/marks/superscript
1168
- */
1169
- const Sup = Mark.create({
1170
- name: "sup",
1171
- addOptions() {
1172
- return { HTMLAttributes: {} };
1173
- },
1174
- parseHTML() {
1175
- return [{ tag: "sup" }];
1176
- },
1177
- renderHTML({ HTMLAttributes }) {
1178
- return [
1179
- "sup",
1180
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1181
- 0
1182
- ];
1183
- },
1184
- addCommands() {
1185
- return {
1186
- setSup: () => ({ commands }) => {
1187
- return commands.setMark(this.name);
1188
- },
1189
- toggleSup: () => ({ commands }) => {
1190
- return commands.toggleMark(this.name);
1191
- },
1192
- unsetSup: () => ({ commands }) => {
1193
- return commands.unsetMark(this.name);
1194
- }
1195
- };
1196
- }
1197
- });
1198
-
1199
- //#endregion
1200
- //#region src/extensions/table.tsx
1201
- const Table = EmailNode.create({
1202
- name: "table",
1203
- group: "block",
1204
- content: "tableRow+",
1205
- isolating: true,
1206
- tableRole: "table",
1207
- addAttributes() {
1208
- return { ...createStandardAttributes([
1209
- ...TABLE_ATTRIBUTES,
1210
- ...LAYOUT_ATTRIBUTES,
1211
- ...COMMON_HTML_ATTRIBUTES
1212
- ]) };
1213
- },
1214
- parseHTML() {
1215
- return [{
1216
- tag: "table",
1217
- getAttrs: (node) => {
1218
- if (typeof node === "string") return false;
1219
- const element = node;
1220
- const attrs = {};
1221
- Array.from(element.attributes).forEach((attr) => {
1222
- attrs[attr.name] = attr.value;
1223
- });
1224
- return attrs;
1225
- }
1226
- }];
1227
- },
1228
- renderHTML({ HTMLAttributes }) {
1229
- return [
1230
- "table",
1231
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1232
- [
1233
- "tbody",
1234
- {},
1235
- 0
1236
- ]
1237
- ];
1238
- },
1239
- renderToReactEmail({ children, node, styles }) {
1240
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1241
- const alignment = node.attrs?.align || node.attrs?.alignment;
1242
- const width = node.attrs?.width;
1243
- const centeringStyles = alignment === "center" ? {
1244
- marginLeft: "auto",
1245
- marginRight: "auto"
1246
- } : {};
1247
- return /* @__PURE__ */ jsx(Section$1, {
1248
- className: node.attrs?.class || void 0,
1249
- align: alignment,
1250
- style: resolveConflictingStyles(styles.reset, {
1251
- ...inlineStyles,
1252
- ...centeringStyles
40
+ placeholder
41
+ ]);
42
+ return /* @__PURE__ */ jsxs(EditorProvider, {
43
+ extensions,
44
+ content,
45
+ editable,
46
+ immediatelyRender: false,
47
+ editorProps: useMemo(() => ({
48
+ handlePaste: createPasteHandler({
49
+ onUploadImage,
50
+ extensions
1253
51
  }),
1254
- ...width !== void 0 ? { width } : {},
1255
- children
1256
- });
1257
- }
1258
- });
1259
- const TableRow = EmailNode.create({
1260
- name: "tableRow",
1261
- group: "tableRow",
1262
- content: "(tableCell | tableHeader)+",
1263
- addAttributes() {
1264
- return { ...createStandardAttributes([
1265
- ...TABLE_CELL_ATTRIBUTES,
1266
- ...LAYOUT_ATTRIBUTES,
1267
- ...COMMON_HTML_ATTRIBUTES
1268
- ]) };
1269
- },
1270
- parseHTML() {
1271
- return [{
1272
- tag: "tr",
1273
- getAttrs: (node) => {
1274
- if (typeof node === "string") return false;
1275
- const element = node;
1276
- const attrs = {};
1277
- Array.from(element.attributes).forEach((attr) => {
1278
- attrs[attr.name] = attr.value;
1279
- });
1280
- return attrs;
1281
- }
1282
- }];
1283
- },
1284
- renderHTML({ HTMLAttributes }) {
1285
- return [
1286
- "tr",
1287
- HTMLAttributes,
1288
- 0
1289
- ];
1290
- },
1291
- renderToReactEmail({ children, node, styles }) {
1292
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1293
- return /* @__PURE__ */ jsx("tr", {
1294
- className: node.attrs?.class || void 0,
1295
- style: {
1296
- ...styles.reset,
1297
- ...inlineStyles
1298
- },
1299
- children
1300
- });
1301
- }
1302
- });
1303
- const TableCell = EmailNode.create({
1304
- name: "tableCell",
1305
- group: "tableCell",
1306
- content: "block+",
1307
- isolating: true,
1308
- addAttributes() {
1309
- return { ...createStandardAttributes([
1310
- ...TABLE_CELL_ATTRIBUTES,
1311
- ...LAYOUT_ATTRIBUTES,
1312
- ...COMMON_HTML_ATTRIBUTES
1313
- ]) };
1314
- },
1315
- parseHTML() {
1316
- return [{
1317
- tag: "td",
1318
- getAttrs: (node) => {
1319
- if (typeof node === "string") return false;
1320
- const element = node;
1321
- const attrs = {};
1322
- Array.from(element.attributes).forEach((attr) => {
1323
- attrs[attr.name] = attr.value;
1324
- });
1325
- return attrs;
1326
- }
1327
- }];
1328
- },
1329
- renderHTML({ HTMLAttributes }) {
1330
- return [
1331
- "td",
1332
- HTMLAttributes,
1333
- 0
1334
- ];
1335
- },
1336
- renderToReactEmail({ children, node, styles }) {
1337
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1338
- return /* @__PURE__ */ jsx(Column, {
1339
- className: node.attrs?.class || void 0,
1340
- align: node.attrs?.align || node.attrs?.alignment,
1341
- style: {
1342
- ...styles.reset,
1343
- ...inlineStyles
1344
- },
1345
- children
1346
- });
1347
- }
1348
- });
1349
- const TableHeader = Node.create({
1350
- name: "tableHeader",
1351
- group: "tableCell",
1352
- content: "block+",
1353
- isolating: true,
1354
- addAttributes() {
1355
- return { ...createStandardAttributes([
1356
- ...TABLE_HEADER_ATTRIBUTES,
1357
- ...TABLE_CELL_ATTRIBUTES,
1358
- ...LAYOUT_ATTRIBUTES,
1359
- ...COMMON_HTML_ATTRIBUTES
1360
- ]) };
1361
- },
1362
- parseHTML() {
1363
- return [{
1364
- tag: "th",
1365
- getAttrs: (node) => {
1366
- if (typeof node === "string") return false;
1367
- const element = node;
1368
- const attrs = {};
1369
- Array.from(element.attributes).forEach((attr) => {
1370
- attrs[attr.name] = attr.value;
1371
- });
1372
- return attrs;
1373
- }
1374
- }];
1375
- },
1376
- renderHTML({ HTMLAttributes }) {
1377
- return [
1378
- "th",
1379
- HTMLAttributes,
1380
- 0
1381
- ];
1382
- }
52
+ handleDrop: createDropHandler({ onUploadImage })
53
+ }), [onUploadImage, extensions]),
54
+ editorContainerProps: { className },
55
+ onCreate: ({ editor }) => onReady?.(editor),
56
+ onUpdate: ({ editor }) => onChange?.(editor),
57
+ children: [
58
+ /* @__PURE__ */ jsx(RefBridge, { editorRef: ref }),
59
+ /* @__PURE__ */ jsx(BubbleMenuDefault, {
60
+ hideWhenActiveNodes: bubbleMenu?.hideWhenActiveNodes ?? ["button"],
61
+ hideWhenActiveMarks: bubbleMenu?.hideWhenActiveMarks ?? ["link"]
62
+ }),
63
+ /* @__PURE__ */ jsx(BubbleMenuLinkDefault, {}),
64
+ /* @__PURE__ */ jsx(BubbleMenuButtonDefault, {}),
65
+ /* @__PURE__ */ jsx(BubbleMenuImageDefault, {}),
66
+ /* @__PURE__ */ jsx(SlashCommandRoot, {})
67
+ ]
68
+ }, theme);
1383
69
  });
70
+ EmailEditor.displayName = "EmailEditor";
1384
71
 
1385
72
  //#endregion
1386
- export { AlignmentAttribute, Body, Bold, Button, ClassAttribute, CodeBlockPrism, Div, EmailNode, MaxNesting, Placeholder, PreservedStyle, PreviewText, Section, StyleAttribute, Sup, Table, TableCell, TableHeader, TableRow, processStylesForUnlink };
73
+ export { EmailEditor };
1387
74
  //# sourceMappingURL=index.mjs.map