@react-email/editor 0.0.0-experimental.15 → 0.0.0-experimental.17

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 CHANGED
@@ -25,11 +25,22 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  }) : target, mod));
26
26
 
27
27
  //#endregion
28
- let _tiptap_core = require("@tiptap/core");
29
- let _tiptap_starter_kit = require("@tiptap/starter-kit");
30
- let react_jsx_runtime = require("react/jsx-runtime");
31
28
  let _react_email_components = require("@react-email/components");
32
29
  _react_email_components = __toESM(_react_email_components);
30
+ let react_jsx_runtime = require("react/jsx-runtime");
31
+ let _tiptap_core = require("@tiptap/core");
32
+ let _tiptap_extensions = require("@tiptap/extensions");
33
+ let _tiptap_react = require("@tiptap/react");
34
+ let react = require("react");
35
+ react = __toESM(react);
36
+ let _tiptap_starter_kit = require("@tiptap/starter-kit");
37
+ _tiptap_starter_kit = __toESM(_tiptap_starter_kit);
38
+ let _tiptap_extension_blockquote = require("@tiptap/extension-blockquote");
39
+ _tiptap_extension_blockquote = __toESM(_tiptap_extension_blockquote);
40
+ let _tiptap_extension_bullet_list = require("@tiptap/extension-bullet-list");
41
+ _tiptap_extension_bullet_list = __toESM(_tiptap_extension_bullet_list);
42
+ let _tiptap_extension_code = require("@tiptap/extension-code");
43
+ _tiptap_extension_code = __toESM(_tiptap_extension_code);
33
44
  let _tiptap_extension_code_block = require("@tiptap/extension-code-block");
34
45
  _tiptap_extension_code_block = __toESM(_tiptap_extension_code_block);
35
46
  let _tiptap_pm_state = require("@tiptap/pm/state");
@@ -37,12 +48,27 @@ let _tiptap_pm_view = require("@tiptap/pm/view");
37
48
  let hast_util_from_html = require("hast-util-from-html");
38
49
  let prismjs = require("prismjs");
39
50
  prismjs = __toESM(prismjs);
51
+ let _tiptap_extension_horizontal_rule = require("@tiptap/extension-horizontal-rule");
52
+ _tiptap_extension_horizontal_rule = __toESM(_tiptap_extension_horizontal_rule);
53
+ let _tiptap_extension_hard_break = require("@tiptap/extension-hard-break");
54
+ _tiptap_extension_hard_break = __toESM(_tiptap_extension_hard_break);
55
+ let _tiptap_extension_heading = require("@tiptap/extension-heading");
56
+ let _tiptap_extension_italic = require("@tiptap/extension-italic");
57
+ _tiptap_extension_italic = __toESM(_tiptap_extension_italic);
58
+ let _tiptap_extension_link = require("@tiptap/extension-link");
59
+ _tiptap_extension_link = __toESM(_tiptap_extension_link);
60
+ let _tiptap_extension_list_item = require("@tiptap/extension-list-item");
61
+ _tiptap_extension_list_item = __toESM(_tiptap_extension_list_item);
62
+ let _tiptap_extension_ordered_list = require("@tiptap/extension-ordered-list");
63
+ _tiptap_extension_ordered_list = __toESM(_tiptap_extension_ordered_list);
64
+ let _tiptap_extension_paragraph = require("@tiptap/extension-paragraph");
65
+ _tiptap_extension_paragraph = __toESM(_tiptap_extension_paragraph);
40
66
  let _tiptap_extension_placeholder = require("@tiptap/extension-placeholder");
41
67
  _tiptap_extension_placeholder = __toESM(_tiptap_extension_placeholder);
42
- let _tiptap_react = require("@tiptap/react");
68
+ let _tiptap_extension_strike = require("@tiptap/extension-strike");
69
+ _tiptap_extension_strike = __toESM(_tiptap_extension_strike);
70
+ let _tiptap_html = require("@tiptap/html");
43
71
  let lucide_react = require("lucide-react");
44
- let react = require("react");
45
- react = __toESM(react);
46
72
  let _radix_ui_react_popover = require("@radix-ui/react-popover");
47
73
  _radix_ui_react_popover = __toESM(_radix_ui_react_popover);
48
74
  let _tiptap_react_menus = require("@tiptap/react/menus");
@@ -51,37 +77,6 @@ _tiptap_suggestion = __toESM(_tiptap_suggestion);
51
77
  let tippy_js = require("tippy.js");
52
78
  tippy_js = __toESM(tippy_js);
53
79
 
54
- //#region src/core/email-node.ts
55
- var EmailNode = class EmailNode extends _tiptap_core.Node {
56
- constructor(config) {
57
- super(config);
58
- }
59
- /**
60
- * Create a new Node instance
61
- * @param config - Node configuration object or a function that returns a configuration object
62
- */
63
- static create(config) {
64
- return new EmailNode(typeof config === "function" ? config() : config);
65
- }
66
- static from(node, renderToReactEmail) {
67
- const customNode = EmailNode.create({});
68
- Object.assign(customNode, { ...node });
69
- customNode.config = {
70
- ...node.config,
71
- renderToReactEmail
72
- };
73
- return customNode;
74
- }
75
- configure(options) {
76
- return super.configure(options);
77
- }
78
- extend(extendedConfig) {
79
- const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
80
- return super.extend(resolvedConfig);
81
- }
82
- };
83
-
84
- //#endregion
85
80
  //#region src/core/event-bus.ts
86
81
  const EVENT_PREFIX = "@react-email/editor:";
87
82
  var EditorEventBus = class {
@@ -123,139 +118,6 @@ var EditorEventBus = class {
123
118
  };
124
119
  const editorEventBus = new EditorEventBus();
125
120
 
126
- //#endregion
127
- //#region src/extensions/alignment-attribute.tsx
128
- const AlignmentAttribute = _tiptap_core.Extension.create({
129
- name: "alignmentAttribute",
130
- addOptions() {
131
- return {
132
- types: [],
133
- alignments: [
134
- "left",
135
- "center",
136
- "right",
137
- "justify"
138
- ]
139
- };
140
- },
141
- addGlobalAttributes() {
142
- return [{
143
- types: this.options.types,
144
- attributes: { alignment: {
145
- parseHTML: (element) => {
146
- const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
147
- if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
148
- return null;
149
- },
150
- renderHTML: (attributes) => {
151
- if (attributes.alignment === "left") return {};
152
- return { alignment: attributes.alignment };
153
- }
154
- } }
155
- }];
156
- },
157
- addCommands() {
158
- return { setAlignment: (alignment) => ({ commands }) => {
159
- if (!this.options.alignments.includes(alignment)) return false;
160
- return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
161
- } };
162
- },
163
- addKeyboardShortcuts() {
164
- return {
165
- Enter: () => {
166
- const { from } = this.editor.state.selection;
167
- const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
168
- if (currentAlignment) requestAnimationFrame(() => {
169
- this.editor.commands.setAlignment(currentAlignment);
170
- });
171
- return false;
172
- },
173
- "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
174
- "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
175
- "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
176
- "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
177
- };
178
- }
179
- });
180
-
181
- //#endregion
182
- //#region src/utils/attribute-helpers.ts
183
- /**
184
- * Creates TipTap attribute definitions for a list of HTML attributes.
185
- * Each attribute will have the same pattern:
186
- * - default: null
187
- * - parseHTML: extracts the attribute from the element
188
- * - renderHTML: conditionally renders the attribute if it has a value
189
- *
190
- * @param attributeNames - Array of HTML attribute names to create definitions for
191
- * @returns Object with TipTap attribute definitions
192
- *
193
- * @example
194
- * const attrs = createStandardAttributes(['class', 'id', 'title']);
195
- * // Returns:
196
- * // {
197
- * // class: {
198
- * // default: null,
199
- * // parseHTML: (element) => element.getAttribute('class'),
200
- * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
201
- * // },
202
- * // ...
203
- * // }
204
- */
205
- function createStandardAttributes(attributeNames) {
206
- return Object.fromEntries(attributeNames.map((attr) => [attr, {
207
- default: null,
208
- parseHTML: (element) => element.getAttribute(attr),
209
- renderHTML: (attributes) => {
210
- if (!attributes[attr]) return {};
211
- return { [attr]: attributes[attr] };
212
- }
213
- }]));
214
- }
215
- /**
216
- * Common HTML attributes used across multiple extensions.
217
- * These preserve attributes during HTML import and editing for better
218
- * fidelity when importing existing email templates.
219
- */
220
- const COMMON_HTML_ATTRIBUTES = [
221
- "id",
222
- "class",
223
- "title",
224
- "lang",
225
- "dir",
226
- "data-id"
227
- ];
228
- /**
229
- * Layout-specific HTML attributes used for positioning and sizing.
230
- */
231
- const LAYOUT_ATTRIBUTES = [
232
- "align",
233
- "width",
234
- "height"
235
- ];
236
- /**
237
- * Table-specific HTML attributes used for table layout and styling.
238
- */
239
- const TABLE_ATTRIBUTES = [
240
- "border",
241
- "cellpadding",
242
- "cellspacing"
243
- ];
244
- /**
245
- * Table cell-specific HTML attributes.
246
- */
247
- const TABLE_CELL_ATTRIBUTES = [
248
- "valign",
249
- "bgcolor",
250
- "colspan",
251
- "rowspan"
252
- ];
253
- /**
254
- * Table header cell-specific HTML attributes.
255
- * These are additional attributes that only apply to <th> elements.
256
- */
257
- const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
258
-
259
121
  //#endregion
260
122
  //#region src/utils/styles.ts
261
123
  const WHITE_SPACE_REGEX = /\s+/;
@@ -412,18 +274,354 @@ function convertBorderValue(value) {
412
274
  * shorthand properties (margin, padding) to longhand before merging.
413
275
  * This prevents shorthand properties from overriding specific longhand properties.
414
276
  *
415
- * @param resetStyles - Base reset styles that may contain shorthand properties
416
- * @param inlineStyles - Inline styles that should override reset styles
417
- * @returns Merged styles with inline styles taking precedence
277
+ * @param resetStyles - Base reset styles that may contain shorthand properties
278
+ * @param inlineStyles - Inline styles that should override reset styles
279
+ * @returns Merged styles with inline styles taking precedence
280
+ */
281
+ function resolveConflictingStyles(resetStyles, inlineStyles) {
282
+ const expandedResetStyles = expandShorthandProperties(resetStyles);
283
+ const expandedInlineStyles = expandShorthandProperties(inlineStyles);
284
+ return {
285
+ ...expandedResetStyles,
286
+ ...expandedInlineStyles
287
+ };
288
+ }
289
+
290
+ //#endregion
291
+ //#region src/core/serializer/default-base-template.tsx
292
+ function DefaultBaseTemplate({ children, previewText }) {
293
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_react_email_components.Html, { children: [
294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_react_email_components.Head, { children: [
295
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
296
+ content: "width=device-width",
297
+ name: "viewport"
298
+ }),
299
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
300
+ content: "IE=edge",
301
+ httpEquiv: "X-UA-Compatible"
302
+ }),
303
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", { name: "x-apple-disable-message-reformatting" }),
304
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
305
+ content: "telephone=no,address=no,email=no,date=no,url=no",
306
+ name: "format-detection"
307
+ })
308
+ ] }),
309
+ previewText && previewText !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Preview, { children: previewText }),
310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Body, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Section, {
311
+ width: "100%",
312
+ align: "center",
313
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Section, {
314
+ style: { width: "100%" },
315
+ children
316
+ })
317
+ }) })
318
+ ] });
319
+ }
320
+
321
+ //#endregion
322
+ //#region src/core/serializer/email-mark.ts
323
+ var EmailMark = class EmailMark extends _tiptap_core.Mark {
324
+ constructor(config) {
325
+ super(config);
326
+ }
327
+ /**
328
+ * Create a new Mark instance
329
+ * @param config - Mark configuration object or a function that returns a configuration object
330
+ */
331
+ static create(config) {
332
+ return new EmailMark(typeof config === "function" ? config() : config);
333
+ }
334
+ static from(mark, renderToReactEmail) {
335
+ const customMark = EmailMark.create({});
336
+ Object.assign(customMark, { ...mark });
337
+ customMark.config = {
338
+ ...mark.config,
339
+ renderToReactEmail
340
+ };
341
+ return customMark;
342
+ }
343
+ configure(options) {
344
+ return super.configure(options);
345
+ }
346
+ extend(extendedConfig) {
347
+ const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
348
+ return super.extend(resolvedConfig);
349
+ }
350
+ };
351
+
352
+ //#endregion
353
+ //#region src/core/serializer/email-node.ts
354
+ var EmailNode = class EmailNode extends _tiptap_core.Node {
355
+ constructor(config) {
356
+ super(config);
357
+ }
358
+ /**
359
+ * Create a new Node instance
360
+ * @param config - Node configuration object or a function that returns a configuration object
361
+ */
362
+ static create(config) {
363
+ return new EmailNode(typeof config === "function" ? config() : config);
364
+ }
365
+ static from(node, renderToReactEmail) {
366
+ const customNode = EmailNode.create({});
367
+ Object.assign(customNode, { ...node });
368
+ customNode.config = {
369
+ ...node.config,
370
+ renderToReactEmail
371
+ };
372
+ return customNode;
373
+ }
374
+ configure(options) {
375
+ return super.configure(options);
376
+ }
377
+ extend(extendedConfig) {
378
+ const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
379
+ return super.extend(resolvedConfig);
380
+ }
381
+ };
382
+
383
+ //#endregion
384
+ //#region src/core/serializer/compose-react-email.tsx
385
+ const MARK_ORDER = {
386
+ preservedStyle: 0,
387
+ italic: 1,
388
+ strike: 2,
389
+ underline: 3,
390
+ link: 4,
391
+ bold: 5,
392
+ code: 6
393
+ };
394
+ const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
395
+ function getOrderedMarks(marks) {
396
+ if (!marks) return [];
397
+ return [...marks].sort((a, b) => (MARK_ORDER[a.type] ?? Number.MAX_SAFE_INTEGER) - (MARK_ORDER[b.type] ?? Number.MAX_SAFE_INTEGER));
398
+ }
399
+ const composeReactEmail = async ({ editor, preview }) => {
400
+ const data = editor.getJSON();
401
+ const extensions = editor.extensionManager.extensions;
402
+ const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
403
+ const emailNodeComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailNode).map((extension) => [extension.name, extension.config.renderToReactEmail]));
404
+ const emailMarkComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailMark).map((extension) => [extension.name, extension.config.renderToReactEmail]));
405
+ function renderMark(mark, node, children, depth) {
406
+ const markStyle = serializerPlugin?.getNodeStyles({
407
+ type: mark.type,
408
+ attrs: mark.attrs ?? {}
409
+ }, depth, editor) ?? {};
410
+ const markRenderer = emailMarkComponentRegistry[mark.type];
411
+ if (markRenderer) return markRenderer({
412
+ mark,
413
+ node,
414
+ style: markStyle,
415
+ children
416
+ });
417
+ return children;
418
+ }
419
+ function parseContent(content, depth = 0) {
420
+ if (!content) return;
421
+ return content.map((node, index) => {
422
+ const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
423
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
424
+ if (node.type && emailNodeComponentRegistry[node.type]) {
425
+ const Component = emailNodeComponentRegistry[node.type];
426
+ const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
427
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
428
+ node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
429
+ ...node,
430
+ attrs: {
431
+ ...node.attrs,
432
+ width: inlineStyles.width
433
+ }
434
+ } : node,
435
+ style,
436
+ children: parseContent(node.content, childDepth)
437
+ }, index);
438
+ }
439
+ switch (node.type) {
440
+ case "text": {
441
+ let wrappedText = node.text;
442
+ getOrderedMarks(node.marks).forEach((mark) => {
443
+ wrappedText = renderMark(mark, node, wrappedText, depth);
444
+ });
445
+ const textAttributes = node.marks?.find((mark) => mark.type === "textStyle")?.attrs;
446
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
447
+ style: {
448
+ ...textAttributes,
449
+ ...style
450
+ },
451
+ children: wrappedText
452
+ }, index);
453
+ }
454
+ default: return null;
455
+ }
456
+ });
457
+ }
458
+ const unformattedHtml = await (0, _react_email_components.render)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
459
+ previewText: preview,
460
+ editor,
461
+ children: parseContent(data.content)
462
+ }));
463
+ const [prettyHtml, text] = await Promise.all([(0, _react_email_components.pretty)(unformattedHtml), (0, _react_email_components.toPlainText)(unformattedHtml)]);
464
+ return {
465
+ html: prettyHtml,
466
+ text
467
+ };
468
+ };
469
+
470
+ //#endregion
471
+ //#region src/extensions/alignment-attribute.tsx
472
+ const AlignmentAttribute = _tiptap_core.Extension.create({
473
+ name: "alignmentAttribute",
474
+ addOptions() {
475
+ return {
476
+ types: [],
477
+ alignments: [
478
+ "left",
479
+ "center",
480
+ "right",
481
+ "justify"
482
+ ]
483
+ };
484
+ },
485
+ addGlobalAttributes() {
486
+ return [{
487
+ types: this.options.types,
488
+ attributes: { alignment: {
489
+ parseHTML: (element) => {
490
+ const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
491
+ if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
492
+ return null;
493
+ },
494
+ renderHTML: (attributes) => {
495
+ if (attributes.alignment === "left") return {};
496
+ return { alignment: attributes.alignment };
497
+ }
498
+ } }
499
+ }];
500
+ },
501
+ addCommands() {
502
+ return { setAlignment: (alignment) => ({ commands }) => {
503
+ if (!this.options.alignments.includes(alignment)) return false;
504
+ return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
505
+ } };
506
+ },
507
+ addKeyboardShortcuts() {
508
+ return {
509
+ Enter: () => {
510
+ const { from } = this.editor.state.selection;
511
+ const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
512
+ if (currentAlignment) requestAnimationFrame(() => {
513
+ this.editor.commands.setAlignment(currentAlignment);
514
+ });
515
+ return false;
516
+ },
517
+ "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
518
+ "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
519
+ "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
520
+ "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
521
+ };
522
+ }
523
+ });
524
+
525
+ //#endregion
526
+ //#region src/utils/get-text-alignment.ts
527
+ function getTextAlignment(alignment) {
528
+ switch (alignment) {
529
+ case "left": return { textAlign: "left" };
530
+ case "center": return { textAlign: "center" };
531
+ case "right": return { textAlign: "right" };
532
+ default: return {};
533
+ }
534
+ }
535
+
536
+ //#endregion
537
+ //#region src/extensions/blockquote.tsx
538
+ const Blockquote = EmailNode.from(_tiptap_extension_blockquote.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("blockquote", {
539
+ className: node.attrs?.class || void 0,
540
+ style: {
541
+ ...style,
542
+ ...inlineCssToJs(node.attrs?.style),
543
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
544
+ },
545
+ children
546
+ }));
547
+
548
+ //#endregion
549
+ //#region src/utils/attribute-helpers.ts
550
+ /**
551
+ * Creates TipTap attribute definitions for a list of HTML attributes.
552
+ * Each attribute will have the same pattern:
553
+ * - default: null
554
+ * - parseHTML: extracts the attribute from the element
555
+ * - renderHTML: conditionally renders the attribute if it has a value
556
+ *
557
+ * @param attributeNames - Array of HTML attribute names to create definitions for
558
+ * @returns Object with TipTap attribute definitions
559
+ *
560
+ * @example
561
+ * const attrs = createStandardAttributes(['class', 'id', 'title']);
562
+ * // Returns:
563
+ * // {
564
+ * // class: {
565
+ * // default: null,
566
+ * // parseHTML: (element) => element.getAttribute('class'),
567
+ * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
568
+ * // },
569
+ * // ...
570
+ * // }
418
571
  */
419
- function resolveConflictingStyles(resetStyles, inlineStyles) {
420
- const expandedResetStyles = expandShorthandProperties(resetStyles);
421
- const expandedInlineStyles = expandShorthandProperties(inlineStyles);
422
- return {
423
- ...expandedResetStyles,
424
- ...expandedInlineStyles
425
- };
572
+ function createStandardAttributes(attributeNames) {
573
+ return Object.fromEntries(attributeNames.map((attr) => [attr, {
574
+ default: null,
575
+ parseHTML: (element) => element.getAttribute(attr),
576
+ renderHTML: (attributes) => {
577
+ if (!attributes[attr]) return {};
578
+ return { [attr]: attributes[attr] };
579
+ }
580
+ }]));
426
581
  }
582
+ /**
583
+ * Common HTML attributes used across multiple extensions.
584
+ * These preserve attributes during HTML import and editing for better
585
+ * fidelity when importing existing email templates.
586
+ */
587
+ const COMMON_HTML_ATTRIBUTES = [
588
+ "id",
589
+ "class",
590
+ "title",
591
+ "lang",
592
+ "dir",
593
+ "data-id"
594
+ ];
595
+ /**
596
+ * Layout-specific HTML attributes used for positioning and sizing.
597
+ */
598
+ const LAYOUT_ATTRIBUTES = [
599
+ "align",
600
+ "width",
601
+ "height"
602
+ ];
603
+ /**
604
+ * Table-specific HTML attributes used for table layout and styling.
605
+ */
606
+ const TABLE_ATTRIBUTES = [
607
+ "border",
608
+ "cellpadding",
609
+ "cellspacing"
610
+ ];
611
+ /**
612
+ * Table cell-specific HTML attributes.
613
+ */
614
+ const TABLE_CELL_ATTRIBUTES = [
615
+ "valign",
616
+ "bgcolor",
617
+ "colspan",
618
+ "rowspan"
619
+ ];
620
+ /**
621
+ * Table header cell-specific HTML attributes.
622
+ * These are additional attributes that only apply to <th> elements.
623
+ */
624
+ const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
427
625
 
428
626
  //#endregion
429
627
  //#region src/extensions/body.tsx
@@ -471,7 +669,7 @@ const Body = EmailNode.create({
471
669
  });
472
670
 
473
671
  //#endregion
474
- //#region src/extensions/bold.ts
672
+ //#region src/extensions/bold.tsx
475
673
  /**
476
674
  * Matches bold text via `**` as input.
477
675
  */
@@ -492,7 +690,7 @@ const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
492
690
  * This extension allows you to mark text as bold.
493
691
  * @see https://tiptap.dev/api/marks/bold
494
692
  */
495
- const Bold = _tiptap_core.Mark.create({
693
+ const Bold = EmailMark.create({
496
694
  name: "bold",
497
695
  addOptions() {
498
696
  return { HTMLAttributes: {} };
@@ -517,6 +715,12 @@ const Bold = _tiptap_core.Mark.create({
517
715
  0
518
716
  ];
519
717
  },
718
+ renderToReactEmail({ children, style }) {
719
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
720
+ style,
721
+ children
722
+ });
723
+ },
520
724
  addCommands() {
521
725
  return {
522
726
  setBold: () => ({ commands }) => {
@@ -556,6 +760,17 @@ const Bold = _tiptap_core.Mark.create({
556
760
  }
557
761
  });
558
762
 
763
+ //#endregion
764
+ //#region src/extensions/bullet-list.tsx
765
+ const BulletList = EmailNode.from(_tiptap_extension_bullet_list.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
766
+ className: node.attrs?.class || void 0,
767
+ style: {
768
+ ...style,
769
+ ...inlineCssToJs(node.attrs?.style)
770
+ },
771
+ children
772
+ }));
773
+
559
774
  //#endregion
560
775
  //#region src/extensions/button.tsx
561
776
  const Button = EmailNode.create({
@@ -677,6 +892,16 @@ const ClassAttribute = _tiptap_core.Extension.create({
677
892
  }
678
893
  });
679
894
 
895
+ //#endregion
896
+ //#region src/extensions/code.tsx
897
+ const Code = EmailMark.from(_tiptap_extension_code.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
898
+ style: {
899
+ ...style,
900
+ ...inlineCssToJs(node.attrs?.style)
901
+ },
902
+ children
903
+ }));
904
+
680
905
  //#endregion
681
906
  //#region src/utils/prism-utils.ts
682
907
  const publicURL = "/styles/prism";
@@ -911,16 +1136,314 @@ const CodeBlockPrism = EmailNode.from(_tiptap_extension_code_block.default.exten
911
1136
  });
912
1137
 
913
1138
  //#endregion
914
- //#region src/extensions/div.tsx
915
- const Div = EmailNode.create({
916
- name: "div",
917
- group: "block",
918
- content: "block+",
919
- defining: true,
920
- isolating: true,
1139
+ //#region src/extensions/div.tsx
1140
+ const Div = EmailNode.create({
1141
+ name: "div",
1142
+ group: "block",
1143
+ content: "block+",
1144
+ defining: true,
1145
+ isolating: true,
1146
+ parseHTML() {
1147
+ return [{
1148
+ tag: "div:not([data-type])",
1149
+ getAttrs: (node) => {
1150
+ if (typeof node === "string") return false;
1151
+ const element = node;
1152
+ const attrs = {};
1153
+ Array.from(element.attributes).forEach((attr) => {
1154
+ attrs[attr.name] = attr.value;
1155
+ });
1156
+ return attrs;
1157
+ }
1158
+ }];
1159
+ },
1160
+ renderHTML({ HTMLAttributes }) {
1161
+ return [
1162
+ "div",
1163
+ (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
1164
+ 0
1165
+ ];
1166
+ },
1167
+ addAttributes() {
1168
+ return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
1169
+ },
1170
+ renderToReactEmail({ children, node, style }) {
1171
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1172
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1173
+ className: node.attrs?.class || void 0,
1174
+ style: {
1175
+ ...style,
1176
+ ...inlineStyles
1177
+ },
1178
+ children
1179
+ });
1180
+ }
1181
+ });
1182
+
1183
+ //#endregion
1184
+ //#region src/extensions/divider.tsx
1185
+ const Divider = EmailNode.from(_tiptap_extension_horizontal_rule.default.extend({
1186
+ addAttributes() {
1187
+ return { class: { default: "divider" } };
1188
+ },
1189
+ addInputRules() {
1190
+ return [new _tiptap_core.InputRule({
1191
+ find: /^(?:---|—-|___\s|\*\*\*\s)$/,
1192
+ handler: ({ state, range }) => {
1193
+ const attributes = {};
1194
+ const { tr } = state;
1195
+ const start = range.from;
1196
+ const end = range.to;
1197
+ tr.insert(start - 1, this.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
1198
+ }
1199
+ })];
1200
+ },
1201
+ addNodeView() {
1202
+ return (0, _tiptap_react.ReactNodeViewRenderer)((props) => {
1203
+ const node = props.node;
1204
+ const { class: className, ...rest } = node.attrs;
1205
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react.NodeViewWrapper, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Hr, {
1206
+ ...rest,
1207
+ className: "node-hr",
1208
+ style: inlineCssToJs(node.attrs.style)
1209
+ }) });
1210
+ });
1211
+ }
1212
+ }), ({ node, style }) => {
1213
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Hr, {
1214
+ className: node.attrs?.class || void 0,
1215
+ style: {
1216
+ ...style,
1217
+ ...inlineCssToJs(node.attrs?.style)
1218
+ }
1219
+ });
1220
+ });
1221
+
1222
+ //#endregion
1223
+ //#region src/extensions/global-content.ts
1224
+ const GLOBAL_CONTENT_NODE_TYPE = "globalContent";
1225
+ let cachedGlobalPosition = null;
1226
+ function findGlobalContentPositions(doc) {
1227
+ const positions = [];
1228
+ doc.descendants((node, position) => {
1229
+ if (node.type.name === GLOBAL_CONTENT_NODE_TYPE) positions.push(position);
1230
+ });
1231
+ return positions;
1232
+ }
1233
+ function getCachedGlobalContentPosition(doc) {
1234
+ if (cachedGlobalPosition != null) try {
1235
+ if (doc.nodeAt(cachedGlobalPosition)?.type.name === GLOBAL_CONTENT_NODE_TYPE) return cachedGlobalPosition;
1236
+ } catch {
1237
+ cachedGlobalPosition = null;
1238
+ }
1239
+ cachedGlobalPosition = findGlobalContentPositions(doc)[0] ?? null;
1240
+ return cachedGlobalPosition;
1241
+ }
1242
+ function getGlobalContent(key, editor) {
1243
+ const position = getCachedGlobalContentPosition(editor.state.doc);
1244
+ if (cachedGlobalPosition == null) return null;
1245
+ return editor.state.doc.nodeAt(position)?.attrs.data[key] ?? null;
1246
+ }
1247
+ const GlobalContent = _tiptap_core.Node.create({
1248
+ name: GLOBAL_CONTENT_NODE_TYPE,
1249
+ addOptions() {
1250
+ return {
1251
+ key: GLOBAL_CONTENT_NODE_TYPE,
1252
+ data: {}
1253
+ };
1254
+ },
1255
+ group: "block",
1256
+ selectable: false,
1257
+ draggable: false,
1258
+ atom: true,
1259
+ addAttributes() {
1260
+ return { data: { default: this.options.data } };
1261
+ },
1262
+ parseHTML() {
1263
+ return [{ tag: `div[data-type="${this.name}"]` }];
1264
+ },
1265
+ renderHTML({ HTMLAttributes }) {
1266
+ return ["div", (0, _tiptap_core.mergeAttributes)(HTMLAttributes, {
1267
+ "data-type": this.name,
1268
+ style: "width: 100%; height: 1px; visibility: hidden;"
1269
+ })];
1270
+ },
1271
+ addCommands() {
1272
+ return { setGlobalContent: (key, value) => ({ tr, dispatch }) => {
1273
+ const ensureGlobalPosition = () => {
1274
+ const positions = findGlobalContentPositions(tr.doc);
1275
+ for (let i = positions.length - 1; i > 0; i--) tr.delete(positions[i], positions[i] + 1);
1276
+ const pos = positions[0] ?? -1;
1277
+ if (pos >= 0) cachedGlobalPosition = pos;
1278
+ else {
1279
+ cachedGlobalPosition = 0;
1280
+ tr.insert(0, this.type.create());
1281
+ }
1282
+ };
1283
+ if (dispatch) {
1284
+ ensureGlobalPosition();
1285
+ if (cachedGlobalPosition == null) return false;
1286
+ tr.setNodeAttribute(cachedGlobalPosition, "data", {
1287
+ ...tr.doc.nodeAt(cachedGlobalPosition)?.attrs.data,
1288
+ [key]: value
1289
+ });
1290
+ }
1291
+ return true;
1292
+ } };
1293
+ }
1294
+ });
1295
+
1296
+ //#endregion
1297
+ //#region src/extensions/hard-break.tsx
1298
+ const HardBreak = EmailNode.from(_tiptap_extension_hard_break.default, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}));
1299
+
1300
+ //#endregion
1301
+ //#region src/extensions/heading.tsx
1302
+ const Heading = EmailNode.from(_tiptap_extension_heading.Heading.extend({ addNodeView() {
1303
+ return (0, _tiptap_react.ReactNodeViewRenderer)(({ node }) => {
1304
+ const level = node.attrs.level ?? 1;
1305
+ const { class: className, ...rest } = node.attrs;
1306
+ const attrs = {
1307
+ ...rest,
1308
+ className: `node-h${level} ${className}`,
1309
+ style: inlineCssToJs(node.attrs.style)
1310
+ };
1311
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react.NodeViewWrapper, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Heading, {
1312
+ as: `h${level}`,
1313
+ ...attrs,
1314
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react.NodeViewContent, {})
1315
+ }) });
1316
+ });
1317
+ } }), ({ children, node, style }) => {
1318
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Heading, {
1319
+ as: `h${node.attrs?.level ?? 1}`,
1320
+ className: node.attrs?.class || void 0,
1321
+ style: {
1322
+ ...style,
1323
+ ...inlineCssToJs(node.attrs?.style),
1324
+ ...getTextAlignment(node.attrs?.align ?? node.attrs?.alignment)
1325
+ },
1326
+ children
1327
+ });
1328
+ });
1329
+
1330
+ //#endregion
1331
+ //#region src/extensions/italic.tsx
1332
+ const Italic = EmailMark.from(_tiptap_extension_italic.default, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", {
1333
+ style,
1334
+ children
1335
+ }));
1336
+
1337
+ //#endregion
1338
+ //#region src/extensions/preserved-style.tsx
1339
+ const PreservedStyle = EmailMark.create({
1340
+ name: "preservedStyle",
1341
+ addAttributes() {
1342
+ return { style: {
1343
+ default: null,
1344
+ parseHTML: (element) => element.getAttribute("style"),
1345
+ renderHTML: (attributes) => {
1346
+ if (!attributes.style) return {};
1347
+ return { style: attributes.style };
1348
+ }
1349
+ } };
1350
+ },
1351
+ parseHTML() {
1352
+ return [{
1353
+ tag: "span[style]",
1354
+ getAttrs: (element) => {
1355
+ if (typeof element === "string") return false;
1356
+ const style = element.getAttribute("style");
1357
+ if (style && hasPreservableStyles(style)) return { style };
1358
+ return false;
1359
+ }
1360
+ }];
1361
+ },
1362
+ renderHTML({ HTMLAttributes }) {
1363
+ return [
1364
+ "span",
1365
+ (0, _tiptap_core.mergeAttributes)(HTMLAttributes),
1366
+ 0
1367
+ ];
1368
+ },
1369
+ renderToReactEmail({ children, mark }) {
1370
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1371
+ style: mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : void 0,
1372
+ children
1373
+ });
1374
+ }
1375
+ });
1376
+ const LINK_INDICATOR_STYLES = [
1377
+ "color",
1378
+ "text-decoration",
1379
+ "text-decoration-line",
1380
+ "text-decoration-color",
1381
+ "text-decoration-style"
1382
+ ];
1383
+ function parseStyleString(styleString) {
1384
+ const temp = document.createElement("div");
1385
+ temp.style.cssText = styleString;
1386
+ return temp.style;
1387
+ }
1388
+ function hasBackground(style) {
1389
+ const bgColor = style.backgroundColor;
1390
+ const bg = style.background;
1391
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1392
+ if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1393
+ return false;
1394
+ }
1395
+ function hasPreservableStyles(styleString) {
1396
+ return processStylesForUnlink(styleString) !== null;
1397
+ }
1398
+ /**
1399
+ * Processes styles when unlinking:
1400
+ * - Has background (button-like): preserve all styles
1401
+ * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1402
+ */
1403
+ function processStylesForUnlink(styleString) {
1404
+ if (!styleString) return null;
1405
+ const style = parseStyleString(styleString);
1406
+ if (hasBackground(style)) return styleString;
1407
+ const filtered = [];
1408
+ for (let i = 0; i < style.length; i++) {
1409
+ const prop = style[i];
1410
+ if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1411
+ const value = style.getPropertyValue(prop);
1412
+ if (value) filtered.push(`${prop}: ${value}`);
1413
+ }
1414
+ return filtered.length > 0 ? filtered.join("; ") : null;
1415
+ }
1416
+
1417
+ //#endregion
1418
+ //#region src/extensions/link.tsx
1419
+ const Link = EmailMark.from(_tiptap_extension_link.default, ({ children, mark, style }) => {
1420
+ const linkMarkStyle = mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : {};
1421
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Link, {
1422
+ href: mark.attrs?.href ?? void 0,
1423
+ rel: mark.attrs?.rel ?? void 0,
1424
+ style: {
1425
+ ...style,
1426
+ ...linkMarkStyle
1427
+ },
1428
+ target: mark.attrs?.target ?? void 0,
1429
+ ...mark.attrs?.["ses:no-track"] ? { "ses:no-track": mark.attrs["ses:no-track"] } : {},
1430
+ children
1431
+ });
1432
+ }).extend({
921
1433
  parseHTML() {
922
1434
  return [{
923
- tag: "div:not([data-type])",
1435
+ tag: "a[target]:not([data-id=\"react-email-button\"])",
1436
+ getAttrs: (node) => {
1437
+ if (typeof node === "string") return false;
1438
+ const element = node;
1439
+ const attrs = {};
1440
+ Array.from(element.attributes).forEach((attr) => {
1441
+ attrs[attr.name] = attr.value;
1442
+ });
1443
+ return attrs;
1444
+ }
1445
+ }, {
1446
+ tag: "a[href]:not([data-id=\"react-email-button\"])",
924
1447
  getAttrs: (node) => {
925
1448
  if (typeof node === "string") return false;
926
1449
  const element = node;
@@ -932,29 +1455,51 @@ const Div = EmailNode.create({
932
1455
  }
933
1456
  }];
934
1457
  },
935
- renderHTML({ HTMLAttributes }) {
936
- return [
937
- "div",
938
- (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
939
- 0
940
- ];
941
- },
942
1458
  addAttributes() {
943
- return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
1459
+ return {
1460
+ ...this.parent?.(),
1461
+ "ses:no-track": {
1462
+ default: null,
1463
+ parseHTML: (element) => element.getAttribute("ses:no-track")
1464
+ }
1465
+ };
944
1466
  },
945
- renderToReactEmail({ children, node, style }) {
946
- const inlineStyles = inlineCssToJs(node.attrs?.style);
947
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
948
- className: node.attrs?.class || void 0,
949
- style: {
950
- ...style,
951
- ...inlineStyles
952
- },
953
- children
954
- });
1467
+ addCommands() {
1468
+ return {
1469
+ ...this.parent?.(),
1470
+ unsetLink: () => ({ state, chain }) => {
1471
+ const { from } = state.selection;
1472
+ const linkStyle = state.doc.resolve(from).marks().find((m) => m.type.name === "link")?.attrs?.style ?? null;
1473
+ const preservedStyle = processStylesForUnlink(linkStyle);
1474
+ const shouldRemoveUnderline = preservedStyle !== linkStyle;
1475
+ if (preservedStyle) {
1476
+ const cmd = chain().extendMarkRange("link").unsetMark("link").setMark("preservedStyle", { style: preservedStyle });
1477
+ return shouldRemoveUnderline ? cmd.unsetMark("underline").run() : cmd.run();
1478
+ }
1479
+ return chain().extendMarkRange("link").unsetMark("link").unsetMark("underline").run();
1480
+ }
1481
+ };
1482
+ },
1483
+ addKeyboardShortcuts() {
1484
+ return { "Mod-k": () => {
1485
+ editorEventBus.dispatch("bubble-menu:add-link", void 0);
1486
+ return this.editor.chain().focus().toggleLink({ href: "" }).run();
1487
+ } };
955
1488
  }
956
1489
  });
957
1490
 
1491
+ //#endregion
1492
+ //#region src/extensions/list-item.tsx
1493
+ const ListItem = EmailNode.from(_tiptap_extension_list_item.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1494
+ className: node.attrs?.class || void 0,
1495
+ style: {
1496
+ ...style,
1497
+ ...inlineCssToJs(node.attrs?.style),
1498
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1499
+ },
1500
+ children
1501
+ }));
1502
+
958
1503
  //#endregion
959
1504
  //#region src/extensions/max-nesting.ts
960
1505
  const MaxNesting = _tiptap_core.Extension.create({
@@ -1030,6 +1575,33 @@ const MaxNesting = _tiptap_core.Extension.create({
1030
1575
  }
1031
1576
  });
1032
1577
 
1578
+ //#endregion
1579
+ //#region src/extensions/ordered-list.tsx
1580
+ const OrderedList = EmailNode.from(_tiptap_extension_ordered_list.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1581
+ className: node.attrs?.class || void 0,
1582
+ start: node.attrs?.start,
1583
+ style: {
1584
+ ...style,
1585
+ ...inlineCssToJs(node.attrs?.style)
1586
+ },
1587
+ children
1588
+ }));
1589
+
1590
+ //#endregion
1591
+ //#region src/extensions/paragraph.tsx
1592
+ const Paragraph = EmailNode.from(_tiptap_extension_paragraph.default, ({ children, node, style }) => {
1593
+ const isEmpty = !node.content || node.content.length === 0;
1594
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1595
+ className: node.attrs?.class || void 0,
1596
+ style: {
1597
+ ...style,
1598
+ ...inlineCssToJs(node.attrs?.style),
1599
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1600
+ },
1601
+ children: isEmpty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}) : children
1602
+ });
1603
+ });
1604
+
1033
1605
  //#endregion
1034
1606
  //#region src/extensions/placeholder.ts
1035
1607
  const Placeholder = _tiptap_extension_placeholder.default.configure({
@@ -1040,80 +1612,6 @@ const Placeholder = _tiptap_extension_placeholder.default.configure({
1040
1612
  includeChildren: true
1041
1613
  });
1042
1614
 
1043
- //#endregion
1044
- //#region src/extensions/preserved-style.ts
1045
- const PreservedStyle = _tiptap_core.Mark.create({
1046
- name: "preservedStyle",
1047
- addAttributes() {
1048
- return { style: {
1049
- default: null,
1050
- parseHTML: (element) => element.getAttribute("style"),
1051
- renderHTML: (attributes) => {
1052
- if (!attributes.style) return {};
1053
- return { style: attributes.style };
1054
- }
1055
- } };
1056
- },
1057
- parseHTML() {
1058
- return [{
1059
- tag: "span[style]",
1060
- getAttrs: (element) => {
1061
- if (typeof element === "string") return false;
1062
- const style = element.getAttribute("style");
1063
- if (style && hasPreservableStyles(style)) return { style };
1064
- return false;
1065
- }
1066
- }];
1067
- },
1068
- renderHTML({ HTMLAttributes }) {
1069
- return [
1070
- "span",
1071
- (0, _tiptap_core.mergeAttributes)(HTMLAttributes),
1072
- 0
1073
- ];
1074
- }
1075
- });
1076
- const LINK_INDICATOR_STYLES = [
1077
- "color",
1078
- "text-decoration",
1079
- "text-decoration-line",
1080
- "text-decoration-color",
1081
- "text-decoration-style"
1082
- ];
1083
- function parseStyleString(styleString) {
1084
- const temp = document.createElement("div");
1085
- temp.style.cssText = styleString;
1086
- return temp.style;
1087
- }
1088
- function hasBackground(style) {
1089
- const bgColor = style.backgroundColor;
1090
- const bg = style.background;
1091
- if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1092
- if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1093
- return false;
1094
- }
1095
- function hasPreservableStyles(styleString) {
1096
- return processStylesForUnlink(styleString) !== null;
1097
- }
1098
- /**
1099
- * Processes styles when unlinking:
1100
- * - Has background (button-like): preserve all styles
1101
- * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1102
- */
1103
- function processStylesForUnlink(styleString) {
1104
- if (!styleString) return null;
1105
- const style = parseStyleString(styleString);
1106
- if (hasBackground(style)) return styleString;
1107
- const filtered = [];
1108
- for (let i = 0; i < style.length; i++) {
1109
- const prop = style[i];
1110
- if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1111
- const value = style.getPropertyValue(prop);
1112
- if (value) filtered.push(`${prop}: ${value}`);
1113
- }
1114
- return filtered.length > 0 ? filtered.join("; ") : null;
1115
- }
1116
-
1117
1615
  //#endregion
1118
1616
  //#region src/extensions/preview-text.ts
1119
1617
  const PreviewText = _tiptap_core.Node.create({
@@ -1155,17 +1653,6 @@ const PreviewText = _tiptap_core.Node.create({
1155
1653
  }
1156
1654
  });
1157
1655
 
1158
- //#endregion
1159
- //#region src/utils/get-text-alignment.ts
1160
- function getTextAlignment(alignment) {
1161
- switch (alignment) {
1162
- case "left": return { textAlign: "left" };
1163
- case "center": return { textAlign: "center" };
1164
- case "right": return { textAlign: "right" };
1165
- default: return {};
1166
- }
1167
- }
1168
-
1169
1656
  //#endregion
1170
1657
  //#region src/extensions/section.tsx
1171
1658
  const Section = EmailNode.create({
@@ -1214,6 +1701,13 @@ const Section = EmailNode.create({
1214
1701
  }
1215
1702
  });
1216
1703
 
1704
+ //#endregion
1705
+ //#region src/extensions/strike.tsx
1706
+ const Strike = EmailMark.from(_tiptap_extension_strike.default, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("s", {
1707
+ style,
1708
+ children
1709
+ }));
1710
+
1217
1711
  //#endregion
1218
1712
  //#region src/extensions/style-attribute.tsx
1219
1713
  const StyleAttribute = _tiptap_core.Extension.create({
@@ -1263,12 +1757,12 @@ const StyleAttribute = _tiptap_core.Extension.create({
1263
1757
  });
1264
1758
 
1265
1759
  //#endregion
1266
- //#region src/extensions/sup.ts
1760
+ //#region src/extensions/sup.tsx
1267
1761
  /**
1268
1762
  * This extension allows you to mark text as superscript.
1269
1763
  * @see https://tiptap.dev/api/marks/superscript
1270
1764
  */
1271
- const Sup = _tiptap_core.Mark.create({
1765
+ const Sup = EmailMark.create({
1272
1766
  name: "sup",
1273
1767
  addOptions() {
1274
1768
  return { HTMLAttributes: {} };
@@ -1283,6 +1777,12 @@ const Sup = _tiptap_core.Mark.create({
1283
1777
  0
1284
1778
  ];
1285
1779
  },
1780
+ renderToReactEmail({ children, style }) {
1781
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("sup", {
1782
+ style,
1783
+ children
1784
+ });
1785
+ },
1286
1786
  addCommands() {
1287
1787
  return {
1288
1788
  setSup: () => ({ commands }) => {
@@ -1485,8 +1985,8 @@ const TableHeader = _tiptap_core.Node.create({
1485
1985
  });
1486
1986
 
1487
1987
  //#endregion
1488
- //#region src/extensions/uppercase.ts
1489
- const Uppercase = _tiptap_core.Mark.create({
1988
+ //#region src/extensions/uppercase.tsx
1989
+ const Uppercase = EmailMark.create({
1490
1990
  name: "uppercase",
1491
1991
  addOptions() {
1492
1992
  return { HTMLAttributes: {} };
@@ -1507,6 +2007,15 @@ const Uppercase = _tiptap_core.Mark.create({
1507
2007
  0
1508
2008
  ];
1509
2009
  },
2010
+ renderToReactEmail({ children, style }) {
2011
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2012
+ style: {
2013
+ ...style,
2014
+ textTransform: "uppercase"
2015
+ },
2016
+ children
2017
+ });
2018
+ },
1510
2019
  addCommands() {
1511
2020
  return {
1512
2021
  setUppercase: () => ({ commands }) => {
@@ -1694,39 +2203,23 @@ const ColumnsColumn = EmailNode.create({
1694
2203
 
1695
2204
  //#endregion
1696
2205
  //#region src/extensions/index.ts
1697
- const coreExtensions = [
1698
- _tiptap_starter_kit.StarterKit.configure({
1699
- undoRedo: false,
1700
- heading: false,
1701
- link: false,
1702
- underline: false,
1703
- trailingNode: false,
1704
- bold: false,
1705
- gapcursor: false,
1706
- listItem: {},
1707
- bulletList: { HTMLAttributes: { class: "node-bulletList" } },
1708
- paragraph: { HTMLAttributes: { class: "node-paragraph" } },
1709
- orderedList: { HTMLAttributes: { class: "node-orderedList" } },
1710
- blockquote: { HTMLAttributes: { class: "node-blockquote" } },
1711
- codeBlock: false,
1712
- code: { HTMLAttributes: {
1713
- class: "node-inlineCode",
1714
- spellcheck: "false"
1715
- } },
1716
- horizontalRule: false,
1717
- dropcursor: {
1718
- color: "#61a8f8",
1719
- class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
1720
- width: 4
1721
- }
1722
- }),
1723
- CodeBlockPrism.configure({
1724
- defaultLanguage: "javascript",
1725
- HTMLAttributes: { class: "prism node-codeBlock" }
1726
- }),
2206
+ const starterKitExtensions = {
2207
+ CodeBlockPrism,
2208
+ Code,
2209
+ Paragraph,
2210
+ BulletList,
2211
+ OrderedList,
2212
+ Blockquote,
2213
+ ListItem,
2214
+ HardBreak,
2215
+ Italic,
1727
2216
  Placeholder,
1728
2217
  PreviewText,
1729
2218
  Bold,
2219
+ Strike,
2220
+ Heading,
2221
+ Divider,
2222
+ Link,
1730
2223
  Sup,
1731
2224
  Uppercase,
1732
2225
  PreservedStyle,
@@ -1738,80 +2231,378 @@ const coreExtensions = [
1738
2231
  Div,
1739
2232
  Button,
1740
2233
  Section,
1741
- AlignmentAttribute.configure({ types: [
1742
- "heading",
1743
- "paragraph",
1744
- "image",
1745
- "blockquote",
1746
- "codeBlock",
1747
- "bulletList",
1748
- "orderedList",
1749
- "listItem",
1750
- "button",
1751
- "youtube",
1752
- "twitter",
1753
- "table",
1754
- "tableRow",
1755
- "tableCell",
1756
- "tableHeader",
1757
- "columnsColumn"
1758
- ] }),
1759
- StyleAttribute.configure({ types: [
1760
- "heading",
1761
- "paragraph",
1762
- "image",
1763
- "blockquote",
1764
- "codeBlock",
1765
- "bulletList",
1766
- "orderedList",
1767
- "listItem",
1768
- "button",
1769
- "youtube",
1770
- "twitter",
1771
- "horizontalRule",
1772
- "footer",
1773
- "section",
1774
- "div",
1775
- "body",
1776
- "table",
1777
- "tableRow",
1778
- "tableCell",
1779
- "tableHeader",
1780
- "columnsColumn",
1781
- "link"
1782
- ] }),
1783
- ClassAttribute.configure({ types: [
1784
- "heading",
1785
- "paragraph",
1786
- "image",
1787
- "blockquote",
1788
- "bulletList",
1789
- "orderedList",
1790
- "listItem",
1791
- "button",
1792
- "youtube",
1793
- "twitter",
1794
- "horizontalRule",
1795
- "footer",
1796
- "section",
1797
- "div",
1798
- "body",
1799
- "table",
1800
- "tableRow",
1801
- "tableCell",
1802
- "tableHeader",
1803
- "columnsColumn",
1804
- "link"
1805
- ] }),
1806
- MaxNesting.configure({
1807
- maxDepth: 50,
1808
- nodeTypes: [
1809
- "section",
1810
- "bulletList",
1811
- "orderedList"
1812
- ]
1813
- })
1814
- ];
2234
+ GlobalContent,
2235
+ AlignmentAttribute,
2236
+ StyleAttribute,
2237
+ ClassAttribute,
2238
+ MaxNesting
2239
+ };
2240
+ const StarterKit = _tiptap_core.Extension.create({
2241
+ name: "reactEmailStarterKit",
2242
+ addOptions() {
2243
+ return {
2244
+ TiptapStarterKit: {},
2245
+ CodeBlockPrism: {
2246
+ defaultLanguage: "javascript",
2247
+ HTMLAttributes: { class: "prism node-codeBlock" }
2248
+ },
2249
+ Code: { HTMLAttributes: {
2250
+ class: "node-inlineCode",
2251
+ spellcheck: "false"
2252
+ } },
2253
+ Paragraph: { HTMLAttributes: { class: "node-paragraph" } },
2254
+ BulletList: { HTMLAttributes: { class: "node-bulletList" } },
2255
+ OrderedList: { HTMLAttributes: { class: "node-orderedList" } },
2256
+ Blockquote: { HTMLAttributes: { class: "node-blockquote" } },
2257
+ ListItem: {},
2258
+ HardBreak: {},
2259
+ Italic: {},
2260
+ Placeholder: {},
2261
+ PreviewText: {},
2262
+ Bold: {},
2263
+ Strike: {},
2264
+ Heading: {},
2265
+ Divider: {},
2266
+ Link: {},
2267
+ Sup: {},
2268
+ Uppercase: {},
2269
+ PreservedStyle: {},
2270
+ Table: {},
2271
+ TableRow: {},
2272
+ TableCell: {},
2273
+ TableHeader: {},
2274
+ Body: {},
2275
+ Div: {},
2276
+ Button: {},
2277
+ Section: {},
2278
+ GlobalContent: {},
2279
+ AlignmentAttribute: { types: [
2280
+ "heading",
2281
+ "paragraph",
2282
+ "image",
2283
+ "blockquote",
2284
+ "codeBlock",
2285
+ "bulletList",
2286
+ "orderedList",
2287
+ "listItem",
2288
+ "button",
2289
+ "youtube",
2290
+ "twitter",
2291
+ "table",
2292
+ "tableRow",
2293
+ "tableCell",
2294
+ "tableHeader",
2295
+ "columnsColumn"
2296
+ ] },
2297
+ StyleAttribute: { types: [
2298
+ "heading",
2299
+ "paragraph",
2300
+ "image",
2301
+ "blockquote",
2302
+ "codeBlock",
2303
+ "bulletList",
2304
+ "orderedList",
2305
+ "listItem",
2306
+ "button",
2307
+ "youtube",
2308
+ "twitter",
2309
+ "horizontalRule",
2310
+ "footer",
2311
+ "section",
2312
+ "div",
2313
+ "body",
2314
+ "table",
2315
+ "tableRow",
2316
+ "tableCell",
2317
+ "tableHeader",
2318
+ "columnsColumn",
2319
+ "link"
2320
+ ] },
2321
+ ClassAttribute: { types: [
2322
+ "heading",
2323
+ "paragraph",
2324
+ "image",
2325
+ "blockquote",
2326
+ "bulletList",
2327
+ "orderedList",
2328
+ "listItem",
2329
+ "button",
2330
+ "youtube",
2331
+ "twitter",
2332
+ "horizontalRule",
2333
+ "footer",
2334
+ "section",
2335
+ "div",
2336
+ "body",
2337
+ "table",
2338
+ "tableRow",
2339
+ "tableCell",
2340
+ "tableHeader",
2341
+ "columnsColumn",
2342
+ "link"
2343
+ ] },
2344
+ MaxNesting: {
2345
+ maxDepth: 50,
2346
+ nodeTypes: [
2347
+ "section",
2348
+ "bulletList",
2349
+ "orderedList"
2350
+ ]
2351
+ }
2352
+ };
2353
+ },
2354
+ addExtensions() {
2355
+ const extensions = [];
2356
+ if (this.options.TiptapStarterKit !== false) extensions.push(_tiptap_starter_kit.default.configure({
2357
+ undoRedo: false,
2358
+ heading: false,
2359
+ link: false,
2360
+ underline: false,
2361
+ trailingNode: false,
2362
+ bold: false,
2363
+ italic: false,
2364
+ strike: false,
2365
+ code: false,
2366
+ paragraph: false,
2367
+ bulletList: false,
2368
+ orderedList: false,
2369
+ listItem: false,
2370
+ blockquote: false,
2371
+ hardBreak: false,
2372
+ gapcursor: false,
2373
+ codeBlock: false,
2374
+ horizontalRule: false,
2375
+ dropcursor: {
2376
+ color: "#61a8f8",
2377
+ class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
2378
+ width: 4
2379
+ },
2380
+ ...this.options.TiptapStarterKit
2381
+ }));
2382
+ for (const [name, extension] of Object.entries(starterKitExtensions)) {
2383
+ const key = name;
2384
+ const extensionOptions = this.options[key];
2385
+ if (extensionOptions !== false) extensions.push(extension.configure(extensionOptions));
2386
+ }
2387
+ return extensions;
2388
+ }
2389
+ });
2390
+
2391
+ //#endregion
2392
+ //#region src/core/create-drop-handler.ts
2393
+ function createDropHandler({ onPaste, onUploadImage }) {
2394
+ return (view, event, _slice, moved) => {
2395
+ if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
2396
+ event.preventDefault();
2397
+ const file = event.dataTransfer.files[0];
2398
+ if (onPaste?.(file, view)) return true;
2399
+ if (file.type.includes("image/") && onUploadImage) {
2400
+ onUploadImage(file, view, (view.posAtCoords({
2401
+ left: event.clientX,
2402
+ top: event.clientY
2403
+ })?.pos || 0) - 1);
2404
+ return true;
2405
+ }
2406
+ }
2407
+ return false;
2408
+ };
2409
+ }
2410
+
2411
+ //#endregion
2412
+ //#region src/utils/paste-sanitizer.ts
2413
+ /**
2414
+ * Sanitizes pasted HTML.
2415
+ * - From editor (has node-* classes): pass through as-is
2416
+ * - From external: strip all styles/classes, keep only semantic HTML
2417
+ */
2418
+ /**
2419
+ * Detects content from the Resend editor by checking for node-* class names.
2420
+ */
2421
+ const EDITOR_CLASS_PATTERN = /class="[^"]*node-/;
2422
+ /**
2423
+ * Attributes to preserve on specific elements for EXTERNAL content.
2424
+ * Only functional attributes - NO style or class.
2425
+ */
2426
+ const PRESERVED_ATTRIBUTES = {
2427
+ a: [
2428
+ "href",
2429
+ "target",
2430
+ "rel"
2431
+ ],
2432
+ img: [
2433
+ "src",
2434
+ "alt",
2435
+ "width",
2436
+ "height"
2437
+ ],
2438
+ td: ["colspan", "rowspan"],
2439
+ th: [
2440
+ "colspan",
2441
+ "rowspan",
2442
+ "scope"
2443
+ ],
2444
+ table: [
2445
+ "border",
2446
+ "cellpadding",
2447
+ "cellspacing"
2448
+ ],
2449
+ "*": ["id"]
2450
+ };
2451
+ function isFromEditor(html) {
2452
+ return EDITOR_CLASS_PATTERN.test(html);
2453
+ }
2454
+ function sanitizePastedHtml(html) {
2455
+ if (isFromEditor(html)) return html;
2456
+ const doc = new DOMParser().parseFromString(html, "text/html");
2457
+ sanitizeNode(doc.body);
2458
+ return doc.body.innerHTML;
2459
+ }
2460
+ function sanitizeNode(node) {
2461
+ if (node.nodeType === Node.ELEMENT_NODE) sanitizeElement(node);
2462
+ for (const child of Array.from(node.childNodes)) sanitizeNode(child);
2463
+ }
2464
+ function sanitizeElement(el) {
2465
+ const allowedForTag = PRESERVED_ATTRIBUTES[el.tagName.toLowerCase()] || [];
2466
+ const allowedGlobal = PRESERVED_ATTRIBUTES["*"] || [];
2467
+ const allowed = new Set([...allowedForTag, ...allowedGlobal]);
2468
+ const attributesToRemove = [];
2469
+ for (const attr of Array.from(el.attributes)) {
2470
+ if (attr.name.startsWith("data-")) {
2471
+ attributesToRemove.push(attr.name);
2472
+ continue;
2473
+ }
2474
+ if (!allowed.has(attr.name)) attributesToRemove.push(attr.name);
2475
+ }
2476
+ for (const attr of attributesToRemove) el.removeAttribute(attr);
2477
+ }
2478
+
2479
+ //#endregion
2480
+ //#region src/core/create-paste-handler.ts
2481
+ function createPasteHandler({ onPaste, onUploadImage, extensions }) {
2482
+ return (view, event, slice) => {
2483
+ const text = event.clipboardData?.getData("text/plain");
2484
+ if (text && onPaste?.(text, view)) {
2485
+ event.preventDefault();
2486
+ return true;
2487
+ }
2488
+ if (event.clipboardData?.files?.[0]) {
2489
+ const file = event.clipboardData.files[0];
2490
+ if (onPaste?.(file, view)) {
2491
+ event.preventDefault();
2492
+ return true;
2493
+ }
2494
+ if (file.type.includes("image/") && onUploadImage) {
2495
+ const pos = view.state.selection.from;
2496
+ onUploadImage(file, view, pos);
2497
+ return true;
2498
+ }
2499
+ }
2500
+ /**
2501
+ * If the coming content has a single child, we can assume
2502
+ * it's a plain text and doesn't need to be parsed and
2503
+ * be introduced in a new line
2504
+ */
2505
+ if (slice.content.childCount === 1) return false;
2506
+ if (event.clipboardData?.getData?.("text/html")) {
2507
+ event.preventDefault();
2508
+ const jsonContent = (0, _tiptap_html.generateJSON)(sanitizePastedHtml(event.clipboardData.getData("text/html")), extensions);
2509
+ const node = view.state.schema.nodeFromJSON(jsonContent);
2510
+ const transaction = view.state.tr.replaceSelectionWith(node, false);
2511
+ view.dispatch(transaction);
2512
+ return true;
2513
+ }
2514
+ return false;
2515
+ };
2516
+ }
2517
+
2518
+ //#endregion
2519
+ //#region src/core/is-document-visually-empty.ts
2520
+ function isDocumentVisuallyEmpty(doc) {
2521
+ let nonGlobalNodeCount = 0;
2522
+ let firstNonGlobalNode = null;
2523
+ for (let index = 0; index < doc.childCount; index += 1) {
2524
+ const node = doc.child(index);
2525
+ if (node.type.name === "globalContent") continue;
2526
+ nonGlobalNodeCount += 1;
2527
+ if (firstNonGlobalNode === null) firstNonGlobalNode = {
2528
+ type: node.type,
2529
+ textContent: node.textContent,
2530
+ childCount: node.content.childCount
2531
+ };
2532
+ }
2533
+ if (nonGlobalNodeCount === 0) return true;
2534
+ if (nonGlobalNodeCount !== 1) return false;
2535
+ return firstNonGlobalNode?.type.name === "paragraph" && firstNonGlobalNode.textContent.trim().length === 0 && firstNonGlobalNode.childCount === 0;
2536
+ }
2537
+
2538
+ //#endregion
2539
+ //#region src/core/use-editor.ts
2540
+ const COLLABORATION_EXTENSION_NAMES = new Set(["liveblocksExtension", "collaboration"]);
2541
+ function hasCollaborationExtension(exts) {
2542
+ return exts.some((ext) => COLLABORATION_EXTENSION_NAMES.has(ext.name));
2543
+ }
2544
+ function useEditor({ content, extensions = [], onUpdate, onPaste, onUploadImage, onReady, editable = true, ...rest }) {
2545
+ const [contentError, setContentError] = react.useState(null);
2546
+ const isCollaborative = hasCollaborationExtension(extensions);
2547
+ const effectiveExtensions = react.useMemo(() => [
2548
+ StarterKit,
2549
+ ...isCollaborative ? [] : [_tiptap_extensions.UndoRedo],
2550
+ ...extensions
2551
+ ], [extensions, isCollaborative]);
2552
+ const editor = (0, _tiptap_react.useEditor)({
2553
+ content: isCollaborative ? void 0 : content,
2554
+ extensions: effectiveExtensions,
2555
+ editable,
2556
+ immediatelyRender: false,
2557
+ enableContentCheck: true,
2558
+ onContentError({ editor: editor$1, error, disableCollaboration }) {
2559
+ disableCollaboration();
2560
+ setContentError(error);
2561
+ console.error(error);
2562
+ editor$1.setEditable(false);
2563
+ },
2564
+ onCreate({ editor: editor$1 }) {
2565
+ onReady?.(editor$1);
2566
+ },
2567
+ onUpdate({ editor: editor$1, transaction }) {
2568
+ onUpdate?.(editor$1, transaction);
2569
+ },
2570
+ editorProps: {
2571
+ handleDOMEvents: { click: (view, event) => {
2572
+ if (!view.editable) {
2573
+ if (event.target.closest("a")) {
2574
+ event.preventDefault();
2575
+ return true;
2576
+ }
2577
+ }
2578
+ return false;
2579
+ } },
2580
+ handlePaste: createPasteHandler({
2581
+ onPaste,
2582
+ onUploadImage,
2583
+ extensions: effectiveExtensions
2584
+ }),
2585
+ handleDrop: createDropHandler({
2586
+ onPaste,
2587
+ onUploadImage
2588
+ })
2589
+ },
2590
+ ...rest
2591
+ });
2592
+ return {
2593
+ editor,
2594
+ isEditorEmpty: (0, _tiptap_react.useEditorState)({
2595
+ editor,
2596
+ selector: (context) => {
2597
+ if (!context.editor) return true;
2598
+ return isDocumentVisuallyEmpty(context.editor.state.doc);
2599
+ }
2600
+ }) ?? true,
2601
+ extensions: effectiveExtensions,
2602
+ contentError,
2603
+ isCollaborative
2604
+ };
2605
+ }
1815
2606
 
1816
2607
  //#endregion
1817
2608
  //#region src/utils/set-text-alignment.ts
@@ -2967,13 +3758,12 @@ function groupByCategory(items) {
2967
3758
  return ordered;
2968
3759
  }
2969
3760
  function CommandItem({ item, selected, onSelect }) {
2970
- const Icon = item.icon;
2971
3761
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2972
3762
  "data-re-slash-command-item": "",
2973
3763
  "data-selected": selected || void 0,
2974
3764
  onClick: onSelect,
2975
3765
  type: "button",
2976
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 20 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.title })]
3766
+ children: [item.icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.title })]
2977
3767
  });
2978
3768
  }
2979
3769
  function CommandList({ items, command, query, ref }) {
@@ -3048,11 +3838,11 @@ function CommandList({ items, command, query, ref }) {
3048
3838
  }
3049
3839
 
3050
3840
  //#endregion
3051
- //#region src/ui/slash-command/commands.ts
3841
+ //#region src/ui/slash-command/commands.tsx
3052
3842
  const TEXT = {
3053
3843
  title: "Text",
3054
3844
  description: "Plain text block",
3055
- icon: lucide_react.Text,
3845
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Text, { size: 20 }),
3056
3846
  category: "Text",
3057
3847
  searchTerms: ["p", "paragraph"],
3058
3848
  command: ({ editor, range }) => {
@@ -3062,7 +3852,7 @@ const TEXT = {
3062
3852
  const H1 = {
3063
3853
  title: "Title",
3064
3854
  description: "Large heading",
3065
- icon: lucide_react.Heading1,
3855
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heading1, { size: 20 }),
3066
3856
  category: "Text",
3067
3857
  searchTerms: [
3068
3858
  "title",
@@ -3077,7 +3867,7 @@ const H1 = {
3077
3867
  const H2 = {
3078
3868
  title: "Subtitle",
3079
3869
  description: "Medium heading",
3080
- icon: lucide_react.Heading2,
3870
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heading2, { size: 20 }),
3081
3871
  category: "Text",
3082
3872
  searchTerms: [
3083
3873
  "subtitle",
@@ -3091,7 +3881,7 @@ const H2 = {
3091
3881
  const H3 = {
3092
3882
  title: "Heading",
3093
3883
  description: "Small heading",
3094
- icon: lucide_react.Heading3,
3884
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heading3, { size: 20 }),
3095
3885
  category: "Text",
3096
3886
  searchTerms: [
3097
3887
  "subtitle",
@@ -3105,7 +3895,7 @@ const H3 = {
3105
3895
  const BULLET_LIST = {
3106
3896
  title: "Bullet list",
3107
3897
  description: "Unordered list",
3108
- icon: lucide_react.List,
3898
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.List, { size: 20 }),
3109
3899
  category: "Text",
3110
3900
  searchTerms: ["unordered", "point"],
3111
3901
  command: ({ editor, range }) => {
@@ -3115,7 +3905,7 @@ const BULLET_LIST = {
3115
3905
  const NUMBERED_LIST = {
3116
3906
  title: "Numbered list",
3117
3907
  description: "Ordered list",
3118
- icon: lucide_react.ListOrdered,
3908
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ListOrdered, { size: 20 }),
3119
3909
  category: "Text",
3120
3910
  searchTerms: ["ordered"],
3121
3911
  command: ({ editor, range }) => {
@@ -3125,7 +3915,7 @@ const NUMBERED_LIST = {
3125
3915
  const QUOTE = {
3126
3916
  title: "Quote",
3127
3917
  description: "Block quote",
3128
- icon: lucide_react.TextQuote,
3918
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.TextQuote, { size: 20 }),
3129
3919
  category: "Text",
3130
3920
  searchTerms: ["blockquote"],
3131
3921
  command: ({ editor, range }) => {
@@ -3135,7 +3925,7 @@ const QUOTE = {
3135
3925
  const CODE = {
3136
3926
  title: "Code block",
3137
3927
  description: "Code snippet",
3138
- icon: lucide_react.SquareCode,
3928
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.SquareCode, { size: 20 }),
3139
3929
  category: "Text",
3140
3930
  searchTerms: ["codeblock"],
3141
3931
  command: ({ editor, range }) => {
@@ -3145,7 +3935,7 @@ const CODE = {
3145
3935
  const BUTTON = {
3146
3936
  title: "Button",
3147
3937
  description: "Clickable button",
3148
- icon: lucide_react.MousePointer,
3938
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.MousePointer, { size: 20 }),
3149
3939
  category: "Layout",
3150
3940
  searchTerms: ["button"],
3151
3941
  command: ({ editor, range }) => {
@@ -3155,7 +3945,7 @@ const BUTTON = {
3155
3945
  const DIVIDER = {
3156
3946
  title: "Divider",
3157
3947
  description: "Horizontal separator",
3158
- icon: lucide_react.SplitSquareVertical,
3948
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.SplitSquareVertical, { size: 20 }),
3159
3949
  category: "Layout",
3160
3950
  searchTerms: [
3161
3951
  "hr",
@@ -3169,7 +3959,7 @@ const DIVIDER = {
3169
3959
  const SECTION = {
3170
3960
  title: "Section",
3171
3961
  description: "Content section",
3172
- icon: lucide_react.Rows2,
3962
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Rows2, { size: 20 }),
3173
3963
  category: "Layout",
3174
3964
  searchTerms: [
3175
3965
  "section",
@@ -3183,7 +3973,7 @@ const SECTION = {
3183
3973
  const TWO_COLUMNS = {
3184
3974
  title: "2 columns",
3185
3975
  description: "Two column layout",
3186
- icon: lucide_react.Columns2,
3976
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns2, { size: 20 }),
3187
3977
  category: "Layout",
3188
3978
  searchTerms: [
3189
3979
  "columns",
@@ -3204,7 +3994,7 @@ const TWO_COLUMNS = {
3204
3994
  const THREE_COLUMNS = {
3205
3995
  title: "3 columns",
3206
3996
  description: "Three column layout",
3207
- icon: lucide_react.Columns3,
3997
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns3, { size: 20 }),
3208
3998
  category: "Layout",
3209
3999
  searchTerms: [
3210
4000
  "columns",
@@ -3224,7 +4014,7 @@ const THREE_COLUMNS = {
3224
4014
  const FOUR_COLUMNS = {
3225
4015
  title: "4 columns",
3226
4016
  description: "Four column layout",
3227
- icon: lucide_react.Columns4,
4017
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns4, { size: 20 }),
3228
4018
  category: "Layout",
3229
4019
  searchTerms: [
3230
4020
  "columns",
@@ -3379,6 +4169,7 @@ const SlashCommand = createSlashCommand();
3379
4169
  exports.AlignmentAttribute = AlignmentAttribute;
3380
4170
  exports.BULLET_LIST = BULLET_LIST;
3381
4171
  exports.BUTTON = BUTTON;
4172
+ exports.Blockquote = Blockquote;
3382
4173
  exports.Body = Body;
3383
4174
  exports.Bold = Bold;
3384
4175
  exports.BubbleMenu = BubbleMenu;
@@ -3398,6 +4189,7 @@ exports.BubbleMenuSeparator = BubbleMenuSeparator;
3398
4189
  exports.BubbleMenuStrike = BubbleMenuStrike;
3399
4190
  exports.BubbleMenuUnderline = BubbleMenuUnderline;
3400
4191
  exports.BubbleMenuUppercase = BubbleMenuUppercase;
4192
+ exports.BulletList = BulletList;
3401
4193
  exports.Button = Button;
3402
4194
  exports.ButtonBubbleMenu = ButtonBubbleMenu;
3403
4195
  exports.ButtonBubbleMenuDefault = ButtonBubbleMenuDefault;
@@ -3407,6 +4199,7 @@ exports.ButtonBubbleMenuToolbar = ButtonBubbleMenuToolbar;
3407
4199
  exports.CODE = CODE;
3408
4200
  exports.COLUMN_PARENT_TYPES = COLUMN_PARENT_TYPES;
3409
4201
  exports.ClassAttribute = ClassAttribute;
4202
+ exports.Code = Code;
3410
4203
  exports.CodeBlockPrism = CodeBlockPrism;
3411
4204
  exports.ColumnsColumn = ColumnsColumn;
3412
4205
  exports.CommandList = CommandList;
@@ -3415,14 +4208,17 @@ exports.Div = Div;
3415
4208
  exports.EmailNode = EmailNode;
3416
4209
  exports.FOUR_COLUMNS = FOUR_COLUMNS;
3417
4210
  exports.FourColumns = FourColumns;
4211
+ exports.GlobalContent = GlobalContent;
3418
4212
  exports.H1 = H1;
3419
4213
  exports.H2 = H2;
3420
4214
  exports.H3 = H3;
4215
+ exports.HardBreak = HardBreak;
3421
4216
  exports.ImageBubbleMenu = ImageBubbleMenu;
3422
4217
  exports.ImageBubbleMenuDefault = ImageBubbleMenuDefault;
3423
4218
  exports.ImageBubbleMenuEditLink = ImageBubbleMenuEditLink;
3424
4219
  exports.ImageBubbleMenuRoot = ImageBubbleMenuRoot;
3425
4220
  exports.ImageBubbleMenuToolbar = ImageBubbleMenuToolbar;
4221
+ exports.Italic = Italic;
3426
4222
  exports.LinkBubbleMenu = LinkBubbleMenu;
3427
4223
  exports.LinkBubbleMenuDefault = LinkBubbleMenuDefault;
3428
4224
  exports.LinkBubbleMenuEditLink = LinkBubbleMenuEditLink;
@@ -3431,12 +4227,15 @@ exports.LinkBubbleMenuOpenLink = LinkBubbleMenuOpenLink;
3431
4227
  exports.LinkBubbleMenuRoot = LinkBubbleMenuRoot;
3432
4228
  exports.LinkBubbleMenuToolbar = LinkBubbleMenuToolbar;
3433
4229
  exports.LinkBubbleMenuUnlink = LinkBubbleMenuUnlink;
4230
+ exports.ListItem = ListItem;
3434
4231
  exports.MAX_COLUMNS_DEPTH = MAX_COLUMNS_DEPTH;
3435
4232
  exports.MaxNesting = MaxNesting;
3436
4233
  exports.NUMBERED_LIST = NUMBERED_LIST;
3437
4234
  exports.NodeSelectorContent = NodeSelectorContent;
3438
4235
  exports.NodeSelectorRoot = NodeSelectorRoot;
3439
4236
  exports.NodeSelectorTrigger = NodeSelectorTrigger;
4237
+ exports.OrderedList = OrderedList;
4238
+ exports.Paragraph = Paragraph;
3440
4239
  exports.Placeholder = Placeholder;
3441
4240
  exports.PreservedStyle = PreservedStyle;
3442
4241
  exports.PreviewText = PreviewText;
@@ -3444,6 +4243,8 @@ exports.QUOTE = QUOTE;
3444
4243
  exports.SECTION = SECTION;
3445
4244
  exports.Section = Section;
3446
4245
  exports.SlashCommand = SlashCommand;
4246
+ exports.StarterKit = StarterKit;
4247
+ exports.Strike = Strike;
3447
4248
  exports.StyleAttribute = StyleAttribute;
3448
4249
  exports.Sup = Sup;
3449
4250
  exports.TEXT = TEXT;
@@ -3456,17 +4257,19 @@ exports.TableRow = TableRow;
3456
4257
  exports.ThreeColumns = ThreeColumns;
3457
4258
  exports.TwoColumns = TwoColumns;
3458
4259
  exports.Uppercase = Uppercase;
3459
- exports.coreExtensions = coreExtensions;
4260
+ exports.composeReactEmail = composeReactEmail;
3460
4261
  exports.createSlashCommand = createSlashCommand;
3461
4262
  exports.defaultSlashCommands = defaultSlashCommands;
3462
4263
  exports.editorEventBus = editorEventBus;
3463
4264
  exports.filterAndRankItems = filterAndRankItems;
3464
4265
  exports.getColumnsDepth = getColumnsDepth;
4266
+ exports.getGlobalContent = getGlobalContent;
3465
4267
  exports.isAtMaxColumnsDepth = isAtMaxColumnsDepth;
3466
4268
  exports.isInsideNode = isInsideNode;
3467
4269
  exports.processStylesForUnlink = processStylesForUnlink;
3468
4270
  exports.scoreItem = scoreItem;
3469
4271
  exports.setTextAlignment = setTextAlignment;
3470
4272
  exports.useButtonBubbleMenuContext = useButtonBubbleMenuContext;
4273
+ exports.useEditor = useEditor;
3471
4274
  exports.useImageBubbleMenuContext = useImageBubbleMenuContext;
3472
4275
  exports.useLinkBubbleMenuContext = useLinkBubbleMenuContext;