@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.mjs CHANGED
@@ -1,54 +1,37 @@
1
- import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
2
- import { StarterKit } from "@tiptap/starter-kit";
3
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
1
  import * as ReactEmailComponents from "@react-email/components";
5
- import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
2
+ import { Body as Body$1, Button as Button$1, CodeBlock, Column, Head, Heading, Hr, Html, Link, Preview, Row, Section as Section$1, pretty, render, toPlainText } from "@react-email/components";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { Extension, InputRule, Mark, Node as Node$1, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
5
+ import { UndoRedo } from "@tiptap/extensions";
6
+ import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, ReactRenderer, useCurrentEditor, useEditor as useEditor$1, useEditorState } from "@tiptap/react";
7
+ import * as React from "react";
8
+ import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
9
+ import TipTapStarterKit from "@tiptap/starter-kit";
10
+ import BlockquoteBase from "@tiptap/extension-blockquote";
11
+ import BulletListBase from "@tiptap/extension-bullet-list";
12
+ import CodeBase from "@tiptap/extension-code";
6
13
  import CodeBlock$1 from "@tiptap/extension-code-block";
7
14
  import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
8
15
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
9
16
  import { fromHtml } from "hast-util-from-html";
10
17
  import Prism from "prismjs";
18
+ import HorizontalRule from "@tiptap/extension-horizontal-rule";
19
+ import HardBreakBase from "@tiptap/extension-hard-break";
20
+ import { Heading as Heading$1 } from "@tiptap/extension-heading";
21
+ import ItalicBase from "@tiptap/extension-italic";
22
+ import TiptapLink from "@tiptap/extension-link";
23
+ import ListItemBase from "@tiptap/extension-list-item";
24
+ import OrderedListBase from "@tiptap/extension-ordered-list";
25
+ import ParagraphBase from "@tiptap/extension-paragraph";
11
26
  import TipTapPlaceholder from "@tiptap/extension-placeholder";
12
- import { ReactRenderer, useCurrentEditor, useEditorState } from "@tiptap/react";
13
- import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code, CodeIcon, Columns2, Columns3, Columns4, ExternalLinkIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, MousePointer, PencilIcon, Rows2, SplitSquareVertical, SquareCode, StrikethroughIcon, Text, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
14
- import * as React from "react";
15
- import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
27
+ import StrikeBase from "@tiptap/extension-strike";
28
+ import { generateJSON } from "@tiptap/html";
29
+ import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code as Code$1, CodeIcon, Columns2, Columns3, Columns4, ExternalLinkIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, MousePointer, PencilIcon, Rows2, SplitSquareVertical, SquareCode, StrikethroughIcon, Text, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
16
30
  import * as Popover from "@radix-ui/react-popover";
17
31
  import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
18
32
  import Suggestion from "@tiptap/suggestion";
19
33
  import tippy from "tippy.js";
20
34
 
21
- //#region src/core/email-node.ts
22
- var EmailNode = class EmailNode extends Node {
23
- constructor(config) {
24
- super(config);
25
- }
26
- /**
27
- * Create a new Node instance
28
- * @param config - Node configuration object or a function that returns a configuration object
29
- */
30
- static create(config) {
31
- return new EmailNode(typeof config === "function" ? config() : config);
32
- }
33
- static from(node, renderToReactEmail) {
34
- const customNode = EmailNode.create({});
35
- Object.assign(customNode, { ...node });
36
- customNode.config = {
37
- ...node.config,
38
- renderToReactEmail
39
- };
40
- return customNode;
41
- }
42
- configure(options) {
43
- return super.configure(options);
44
- }
45
- extend(extendedConfig) {
46
- const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
47
- return super.extend(resolvedConfig);
48
- }
49
- };
50
-
51
- //#endregion
52
35
  //#region src/core/event-bus.ts
53
36
  const EVENT_PREFIX = "@react-email/editor:";
54
37
  var EditorEventBus = class {
@@ -90,139 +73,6 @@ var EditorEventBus = class {
90
73
  };
91
74
  const editorEventBus = new EditorEventBus();
92
75
 
93
- //#endregion
94
- //#region src/extensions/alignment-attribute.tsx
95
- const AlignmentAttribute = Extension.create({
96
- name: "alignmentAttribute",
97
- addOptions() {
98
- return {
99
- types: [],
100
- alignments: [
101
- "left",
102
- "center",
103
- "right",
104
- "justify"
105
- ]
106
- };
107
- },
108
- addGlobalAttributes() {
109
- return [{
110
- types: this.options.types,
111
- attributes: { alignment: {
112
- parseHTML: (element) => {
113
- const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
114
- if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
115
- return null;
116
- },
117
- renderHTML: (attributes) => {
118
- if (attributes.alignment === "left") return {};
119
- return { alignment: attributes.alignment };
120
- }
121
- } }
122
- }];
123
- },
124
- addCommands() {
125
- return { setAlignment: (alignment) => ({ commands }) => {
126
- if (!this.options.alignments.includes(alignment)) return false;
127
- return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
128
- } };
129
- },
130
- addKeyboardShortcuts() {
131
- return {
132
- Enter: () => {
133
- const { from } = this.editor.state.selection;
134
- const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
135
- if (currentAlignment) requestAnimationFrame(() => {
136
- this.editor.commands.setAlignment(currentAlignment);
137
- });
138
- return false;
139
- },
140
- "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
141
- "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
142
- "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
143
- "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
144
- };
145
- }
146
- });
147
-
148
- //#endregion
149
- //#region src/utils/attribute-helpers.ts
150
- /**
151
- * Creates TipTap attribute definitions for a list of HTML attributes.
152
- * Each attribute will have the same pattern:
153
- * - default: null
154
- * - parseHTML: extracts the attribute from the element
155
- * - renderHTML: conditionally renders the attribute if it has a value
156
- *
157
- * @param attributeNames - Array of HTML attribute names to create definitions for
158
- * @returns Object with TipTap attribute definitions
159
- *
160
- * @example
161
- * const attrs = createStandardAttributes(['class', 'id', 'title']);
162
- * // Returns:
163
- * // {
164
- * // class: {
165
- * // default: null,
166
- * // parseHTML: (element) => element.getAttribute('class'),
167
- * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
168
- * // },
169
- * // ...
170
- * // }
171
- */
172
- function createStandardAttributes(attributeNames) {
173
- return Object.fromEntries(attributeNames.map((attr) => [attr, {
174
- default: null,
175
- parseHTML: (element) => element.getAttribute(attr),
176
- renderHTML: (attributes) => {
177
- if (!attributes[attr]) return {};
178
- return { [attr]: attributes[attr] };
179
- }
180
- }]));
181
- }
182
- /**
183
- * Common HTML attributes used across multiple extensions.
184
- * These preserve attributes during HTML import and editing for better
185
- * fidelity when importing existing email templates.
186
- */
187
- const COMMON_HTML_ATTRIBUTES = [
188
- "id",
189
- "class",
190
- "title",
191
- "lang",
192
- "dir",
193
- "data-id"
194
- ];
195
- /**
196
- * Layout-specific HTML attributes used for positioning and sizing.
197
- */
198
- const LAYOUT_ATTRIBUTES = [
199
- "align",
200
- "width",
201
- "height"
202
- ];
203
- /**
204
- * Table-specific HTML attributes used for table layout and styling.
205
- */
206
- const TABLE_ATTRIBUTES = [
207
- "border",
208
- "cellpadding",
209
- "cellspacing"
210
- ];
211
- /**
212
- * Table cell-specific HTML attributes.
213
- */
214
- const TABLE_CELL_ATTRIBUTES = [
215
- "valign",
216
- "bgcolor",
217
- "colspan",
218
- "rowspan"
219
- ];
220
- /**
221
- * Table header cell-specific HTML attributes.
222
- * These are additional attributes that only apply to <th> elements.
223
- */
224
- const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
225
-
226
76
  //#endregion
227
77
  //#region src/utils/styles.ts
228
78
  const WHITE_SPACE_REGEX = /\s+/;
@@ -379,18 +229,354 @@ function convertBorderValue(value) {
379
229
  * shorthand properties (margin, padding) to longhand before merging.
380
230
  * This prevents shorthand properties from overriding specific longhand properties.
381
231
  *
382
- * @param resetStyles - Base reset styles that may contain shorthand properties
383
- * @param inlineStyles - Inline styles that should override reset styles
384
- * @returns Merged styles with inline styles taking precedence
232
+ * @param resetStyles - Base reset styles that may contain shorthand properties
233
+ * @param inlineStyles - Inline styles that should override reset styles
234
+ * @returns Merged styles with inline styles taking precedence
235
+ */
236
+ function resolveConflictingStyles(resetStyles, inlineStyles) {
237
+ const expandedResetStyles = expandShorthandProperties(resetStyles);
238
+ const expandedInlineStyles = expandShorthandProperties(inlineStyles);
239
+ return {
240
+ ...expandedResetStyles,
241
+ ...expandedInlineStyles
242
+ };
243
+ }
244
+
245
+ //#endregion
246
+ //#region src/core/serializer/default-base-template.tsx
247
+ function DefaultBaseTemplate({ children, previewText }) {
248
+ return /* @__PURE__ */ jsxs(Html, { children: [
249
+ /* @__PURE__ */ jsxs(Head, { children: [
250
+ /* @__PURE__ */ jsx("meta", {
251
+ content: "width=device-width",
252
+ name: "viewport"
253
+ }),
254
+ /* @__PURE__ */ jsx("meta", {
255
+ content: "IE=edge",
256
+ httpEquiv: "X-UA-Compatible"
257
+ }),
258
+ /* @__PURE__ */ jsx("meta", { name: "x-apple-disable-message-reformatting" }),
259
+ /* @__PURE__ */ jsx("meta", {
260
+ content: "telephone=no,address=no,email=no,date=no,url=no",
261
+ name: "format-detection"
262
+ })
263
+ ] }),
264
+ previewText && previewText !== "" && /* @__PURE__ */ jsx(Preview, { children: previewText }),
265
+ /* @__PURE__ */ jsx(Body$1, { children: /* @__PURE__ */ jsx(Section$1, {
266
+ width: "100%",
267
+ align: "center",
268
+ children: /* @__PURE__ */ jsx(Section$1, {
269
+ style: { width: "100%" },
270
+ children
271
+ })
272
+ }) })
273
+ ] });
274
+ }
275
+
276
+ //#endregion
277
+ //#region src/core/serializer/email-mark.ts
278
+ var EmailMark = class EmailMark extends Mark {
279
+ constructor(config) {
280
+ super(config);
281
+ }
282
+ /**
283
+ * Create a new Mark instance
284
+ * @param config - Mark configuration object or a function that returns a configuration object
285
+ */
286
+ static create(config) {
287
+ return new EmailMark(typeof config === "function" ? config() : config);
288
+ }
289
+ static from(mark, renderToReactEmail) {
290
+ const customMark = EmailMark.create({});
291
+ Object.assign(customMark, { ...mark });
292
+ customMark.config = {
293
+ ...mark.config,
294
+ renderToReactEmail
295
+ };
296
+ return customMark;
297
+ }
298
+ configure(options) {
299
+ return super.configure(options);
300
+ }
301
+ extend(extendedConfig) {
302
+ const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
303
+ return super.extend(resolvedConfig);
304
+ }
305
+ };
306
+
307
+ //#endregion
308
+ //#region src/core/serializer/email-node.ts
309
+ var EmailNode = class EmailNode extends Node$1 {
310
+ constructor(config) {
311
+ super(config);
312
+ }
313
+ /**
314
+ * Create a new Node instance
315
+ * @param config - Node configuration object or a function that returns a configuration object
316
+ */
317
+ static create(config) {
318
+ return new EmailNode(typeof config === "function" ? config() : config);
319
+ }
320
+ static from(node, renderToReactEmail) {
321
+ const customNode = EmailNode.create({});
322
+ Object.assign(customNode, { ...node });
323
+ customNode.config = {
324
+ ...node.config,
325
+ renderToReactEmail
326
+ };
327
+ return customNode;
328
+ }
329
+ configure(options) {
330
+ return super.configure(options);
331
+ }
332
+ extend(extendedConfig) {
333
+ const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
334
+ return super.extend(resolvedConfig);
335
+ }
336
+ };
337
+
338
+ //#endregion
339
+ //#region src/core/serializer/compose-react-email.tsx
340
+ const MARK_ORDER = {
341
+ preservedStyle: 0,
342
+ italic: 1,
343
+ strike: 2,
344
+ underline: 3,
345
+ link: 4,
346
+ bold: 5,
347
+ code: 6
348
+ };
349
+ const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
350
+ function getOrderedMarks(marks) {
351
+ if (!marks) return [];
352
+ return [...marks].sort((a, b) => (MARK_ORDER[a.type] ?? Number.MAX_SAFE_INTEGER) - (MARK_ORDER[b.type] ?? Number.MAX_SAFE_INTEGER));
353
+ }
354
+ const composeReactEmail = async ({ editor, preview }) => {
355
+ const data = editor.getJSON();
356
+ const extensions = editor.extensionManager.extensions;
357
+ const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
358
+ const emailNodeComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailNode).map((extension) => [extension.name, extension.config.renderToReactEmail]));
359
+ const emailMarkComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailMark).map((extension) => [extension.name, extension.config.renderToReactEmail]));
360
+ function renderMark(mark, node, children, depth) {
361
+ const markStyle = serializerPlugin?.getNodeStyles({
362
+ type: mark.type,
363
+ attrs: mark.attrs ?? {}
364
+ }, depth, editor) ?? {};
365
+ const markRenderer = emailMarkComponentRegistry[mark.type];
366
+ if (markRenderer) return markRenderer({
367
+ mark,
368
+ node,
369
+ style: markStyle,
370
+ children
371
+ });
372
+ return children;
373
+ }
374
+ function parseContent(content, depth = 0) {
375
+ if (!content) return;
376
+ return content.map((node, index) => {
377
+ const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
378
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
379
+ if (node.type && emailNodeComponentRegistry[node.type]) {
380
+ const Component = emailNodeComponentRegistry[node.type];
381
+ const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
382
+ return /* @__PURE__ */ jsx(Component, {
383
+ node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
384
+ ...node,
385
+ attrs: {
386
+ ...node.attrs,
387
+ width: inlineStyles.width
388
+ }
389
+ } : node,
390
+ style,
391
+ children: parseContent(node.content, childDepth)
392
+ }, index);
393
+ }
394
+ switch (node.type) {
395
+ case "text": {
396
+ let wrappedText = node.text;
397
+ getOrderedMarks(node.marks).forEach((mark) => {
398
+ wrappedText = renderMark(mark, node, wrappedText, depth);
399
+ });
400
+ const textAttributes = node.marks?.find((mark) => mark.type === "textStyle")?.attrs;
401
+ return /* @__PURE__ */ jsx("span", {
402
+ style: {
403
+ ...textAttributes,
404
+ ...style
405
+ },
406
+ children: wrappedText
407
+ }, index);
408
+ }
409
+ default: return null;
410
+ }
411
+ });
412
+ }
413
+ const unformattedHtml = await render(/* @__PURE__ */ jsx(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
414
+ previewText: preview,
415
+ editor,
416
+ children: parseContent(data.content)
417
+ }));
418
+ const [prettyHtml, text] = await Promise.all([pretty(unformattedHtml), toPlainText(unformattedHtml)]);
419
+ return {
420
+ html: prettyHtml,
421
+ text
422
+ };
423
+ };
424
+
425
+ //#endregion
426
+ //#region src/extensions/alignment-attribute.tsx
427
+ const AlignmentAttribute = Extension.create({
428
+ name: "alignmentAttribute",
429
+ addOptions() {
430
+ return {
431
+ types: [],
432
+ alignments: [
433
+ "left",
434
+ "center",
435
+ "right",
436
+ "justify"
437
+ ]
438
+ };
439
+ },
440
+ addGlobalAttributes() {
441
+ return [{
442
+ types: this.options.types,
443
+ attributes: { alignment: {
444
+ parseHTML: (element) => {
445
+ const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
446
+ if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
447
+ return null;
448
+ },
449
+ renderHTML: (attributes) => {
450
+ if (attributes.alignment === "left") return {};
451
+ return { alignment: attributes.alignment };
452
+ }
453
+ } }
454
+ }];
455
+ },
456
+ addCommands() {
457
+ return { setAlignment: (alignment) => ({ commands }) => {
458
+ if (!this.options.alignments.includes(alignment)) return false;
459
+ return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
460
+ } };
461
+ },
462
+ addKeyboardShortcuts() {
463
+ return {
464
+ Enter: () => {
465
+ const { from } = this.editor.state.selection;
466
+ const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
467
+ if (currentAlignment) requestAnimationFrame(() => {
468
+ this.editor.commands.setAlignment(currentAlignment);
469
+ });
470
+ return false;
471
+ },
472
+ "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
473
+ "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
474
+ "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
475
+ "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
476
+ };
477
+ }
478
+ });
479
+
480
+ //#endregion
481
+ //#region src/utils/get-text-alignment.ts
482
+ function getTextAlignment(alignment) {
483
+ switch (alignment) {
484
+ case "left": return { textAlign: "left" };
485
+ case "center": return { textAlign: "center" };
486
+ case "right": return { textAlign: "right" };
487
+ default: return {};
488
+ }
489
+ }
490
+
491
+ //#endregion
492
+ //#region src/extensions/blockquote.tsx
493
+ const Blockquote = EmailNode.from(BlockquoteBase, ({ children, node, style }) => /* @__PURE__ */ jsx("blockquote", {
494
+ className: node.attrs?.class || void 0,
495
+ style: {
496
+ ...style,
497
+ ...inlineCssToJs(node.attrs?.style),
498
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
499
+ },
500
+ children
501
+ }));
502
+
503
+ //#endregion
504
+ //#region src/utils/attribute-helpers.ts
505
+ /**
506
+ * Creates TipTap attribute definitions for a list of HTML attributes.
507
+ * Each attribute will have the same pattern:
508
+ * - default: null
509
+ * - parseHTML: extracts the attribute from the element
510
+ * - renderHTML: conditionally renders the attribute if it has a value
511
+ *
512
+ * @param attributeNames - Array of HTML attribute names to create definitions for
513
+ * @returns Object with TipTap attribute definitions
514
+ *
515
+ * @example
516
+ * const attrs = createStandardAttributes(['class', 'id', 'title']);
517
+ * // Returns:
518
+ * // {
519
+ * // class: {
520
+ * // default: null,
521
+ * // parseHTML: (element) => element.getAttribute('class'),
522
+ * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
523
+ * // },
524
+ * // ...
525
+ * // }
385
526
  */
386
- function resolveConflictingStyles(resetStyles, inlineStyles) {
387
- const expandedResetStyles = expandShorthandProperties(resetStyles);
388
- const expandedInlineStyles = expandShorthandProperties(inlineStyles);
389
- return {
390
- ...expandedResetStyles,
391
- ...expandedInlineStyles
392
- };
527
+ function createStandardAttributes(attributeNames) {
528
+ return Object.fromEntries(attributeNames.map((attr) => [attr, {
529
+ default: null,
530
+ parseHTML: (element) => element.getAttribute(attr),
531
+ renderHTML: (attributes) => {
532
+ if (!attributes[attr]) return {};
533
+ return { [attr]: attributes[attr] };
534
+ }
535
+ }]));
393
536
  }
537
+ /**
538
+ * Common HTML attributes used across multiple extensions.
539
+ * These preserve attributes during HTML import and editing for better
540
+ * fidelity when importing existing email templates.
541
+ */
542
+ const COMMON_HTML_ATTRIBUTES = [
543
+ "id",
544
+ "class",
545
+ "title",
546
+ "lang",
547
+ "dir",
548
+ "data-id"
549
+ ];
550
+ /**
551
+ * Layout-specific HTML attributes used for positioning and sizing.
552
+ */
553
+ const LAYOUT_ATTRIBUTES = [
554
+ "align",
555
+ "width",
556
+ "height"
557
+ ];
558
+ /**
559
+ * Table-specific HTML attributes used for table layout and styling.
560
+ */
561
+ const TABLE_ATTRIBUTES = [
562
+ "border",
563
+ "cellpadding",
564
+ "cellspacing"
565
+ ];
566
+ /**
567
+ * Table cell-specific HTML attributes.
568
+ */
569
+ const TABLE_CELL_ATTRIBUTES = [
570
+ "valign",
571
+ "bgcolor",
572
+ "colspan",
573
+ "rowspan"
574
+ ];
575
+ /**
576
+ * Table header cell-specific HTML attributes.
577
+ * These are additional attributes that only apply to <th> elements.
578
+ */
579
+ const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
394
580
 
395
581
  //#endregion
396
582
  //#region src/extensions/body.tsx
@@ -438,7 +624,7 @@ const Body = EmailNode.create({
438
624
  });
439
625
 
440
626
  //#endregion
441
- //#region src/extensions/bold.ts
627
+ //#region src/extensions/bold.tsx
442
628
  /**
443
629
  * Matches bold text via `**` as input.
444
630
  */
@@ -459,7 +645,7 @@ const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
459
645
  * This extension allows you to mark text as bold.
460
646
  * @see https://tiptap.dev/api/marks/bold
461
647
  */
462
- const Bold = Mark.create({
648
+ const Bold = EmailMark.create({
463
649
  name: "bold",
464
650
  addOptions() {
465
651
  return { HTMLAttributes: {} };
@@ -484,6 +670,12 @@ const Bold = Mark.create({
484
670
  0
485
671
  ];
486
672
  },
673
+ renderToReactEmail({ children, style }) {
674
+ return /* @__PURE__ */ jsx("strong", {
675
+ style,
676
+ children
677
+ });
678
+ },
487
679
  addCommands() {
488
680
  return {
489
681
  setBold: () => ({ commands }) => {
@@ -523,6 +715,17 @@ const Bold = Mark.create({
523
715
  }
524
716
  });
525
717
 
718
+ //#endregion
719
+ //#region src/extensions/bullet-list.tsx
720
+ const BulletList = EmailNode.from(BulletListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ul", {
721
+ className: node.attrs?.class || void 0,
722
+ style: {
723
+ ...style,
724
+ ...inlineCssToJs(node.attrs?.style)
725
+ },
726
+ children
727
+ }));
728
+
526
729
  //#endregion
527
730
  //#region src/extensions/button.tsx
528
731
  const Button = EmailNode.create({
@@ -644,6 +847,16 @@ const ClassAttribute = Extension.create({
644
847
  }
645
848
  });
646
849
 
850
+ //#endregion
851
+ //#region src/extensions/code.tsx
852
+ const Code = EmailMark.from(CodeBase, ({ children, node, style }) => /* @__PURE__ */ jsx("code", {
853
+ style: {
854
+ ...style,
855
+ ...inlineCssToJs(node.attrs?.style)
856
+ },
857
+ children
858
+ }));
859
+
647
860
  //#endregion
648
861
  //#region src/utils/prism-utils.ts
649
862
  const publicURL = "/styles/prism";
@@ -878,16 +1091,314 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
878
1091
  });
879
1092
 
880
1093
  //#endregion
881
- //#region src/extensions/div.tsx
882
- const Div = EmailNode.create({
883
- name: "div",
884
- group: "block",
885
- content: "block+",
886
- defining: true,
887
- isolating: true,
1094
+ //#region src/extensions/div.tsx
1095
+ const Div = EmailNode.create({
1096
+ name: "div",
1097
+ group: "block",
1098
+ content: "block+",
1099
+ defining: true,
1100
+ isolating: true,
1101
+ parseHTML() {
1102
+ return [{
1103
+ tag: "div:not([data-type])",
1104
+ getAttrs: (node) => {
1105
+ if (typeof node === "string") return false;
1106
+ const element = node;
1107
+ const attrs = {};
1108
+ Array.from(element.attributes).forEach((attr) => {
1109
+ attrs[attr.name] = attr.value;
1110
+ });
1111
+ return attrs;
1112
+ }
1113
+ }];
1114
+ },
1115
+ renderHTML({ HTMLAttributes }) {
1116
+ return [
1117
+ "div",
1118
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1119
+ 0
1120
+ ];
1121
+ },
1122
+ addAttributes() {
1123
+ return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
1124
+ },
1125
+ renderToReactEmail({ children, node, style }) {
1126
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1127
+ return /* @__PURE__ */ jsx("div", {
1128
+ className: node.attrs?.class || void 0,
1129
+ style: {
1130
+ ...style,
1131
+ ...inlineStyles
1132
+ },
1133
+ children
1134
+ });
1135
+ }
1136
+ });
1137
+
1138
+ //#endregion
1139
+ //#region src/extensions/divider.tsx
1140
+ const Divider = EmailNode.from(HorizontalRule.extend({
1141
+ addAttributes() {
1142
+ return { class: { default: "divider" } };
1143
+ },
1144
+ addInputRules() {
1145
+ return [new InputRule({
1146
+ find: /^(?:---|—-|___\s|\*\*\*\s)$/,
1147
+ handler: ({ state, range }) => {
1148
+ const attributes = {};
1149
+ const { tr } = state;
1150
+ const start = range.from;
1151
+ const end = range.to;
1152
+ tr.insert(start - 1, this.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
1153
+ }
1154
+ })];
1155
+ },
1156
+ addNodeView() {
1157
+ return ReactNodeViewRenderer((props) => {
1158
+ const node = props.node;
1159
+ const { class: className, ...rest } = node.attrs;
1160
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(Hr, {
1161
+ ...rest,
1162
+ className: "node-hr",
1163
+ style: inlineCssToJs(node.attrs.style)
1164
+ }) });
1165
+ });
1166
+ }
1167
+ }), ({ node, style }) => {
1168
+ return /* @__PURE__ */ jsx(Hr, {
1169
+ className: node.attrs?.class || void 0,
1170
+ style: {
1171
+ ...style,
1172
+ ...inlineCssToJs(node.attrs?.style)
1173
+ }
1174
+ });
1175
+ });
1176
+
1177
+ //#endregion
1178
+ //#region src/extensions/global-content.ts
1179
+ const GLOBAL_CONTENT_NODE_TYPE = "globalContent";
1180
+ let cachedGlobalPosition = null;
1181
+ function findGlobalContentPositions(doc) {
1182
+ const positions = [];
1183
+ doc.descendants((node, position) => {
1184
+ if (node.type.name === GLOBAL_CONTENT_NODE_TYPE) positions.push(position);
1185
+ });
1186
+ return positions;
1187
+ }
1188
+ function getCachedGlobalContentPosition(doc) {
1189
+ if (cachedGlobalPosition != null) try {
1190
+ if (doc.nodeAt(cachedGlobalPosition)?.type.name === GLOBAL_CONTENT_NODE_TYPE) return cachedGlobalPosition;
1191
+ } catch {
1192
+ cachedGlobalPosition = null;
1193
+ }
1194
+ cachedGlobalPosition = findGlobalContentPositions(doc)[0] ?? null;
1195
+ return cachedGlobalPosition;
1196
+ }
1197
+ function getGlobalContent(key, editor) {
1198
+ const position = getCachedGlobalContentPosition(editor.state.doc);
1199
+ if (cachedGlobalPosition == null) return null;
1200
+ return editor.state.doc.nodeAt(position)?.attrs.data[key] ?? null;
1201
+ }
1202
+ const GlobalContent = Node$1.create({
1203
+ name: GLOBAL_CONTENT_NODE_TYPE,
1204
+ addOptions() {
1205
+ return {
1206
+ key: GLOBAL_CONTENT_NODE_TYPE,
1207
+ data: {}
1208
+ };
1209
+ },
1210
+ group: "block",
1211
+ selectable: false,
1212
+ draggable: false,
1213
+ atom: true,
1214
+ addAttributes() {
1215
+ return { data: { default: this.options.data } };
1216
+ },
1217
+ parseHTML() {
1218
+ return [{ tag: `div[data-type="${this.name}"]` }];
1219
+ },
1220
+ renderHTML({ HTMLAttributes }) {
1221
+ return ["div", mergeAttributes(HTMLAttributes, {
1222
+ "data-type": this.name,
1223
+ style: "width: 100%; height: 1px; visibility: hidden;"
1224
+ })];
1225
+ },
1226
+ addCommands() {
1227
+ return { setGlobalContent: (key, value) => ({ tr, dispatch }) => {
1228
+ const ensureGlobalPosition = () => {
1229
+ const positions = findGlobalContentPositions(tr.doc);
1230
+ for (let i = positions.length - 1; i > 0; i--) tr.delete(positions[i], positions[i] + 1);
1231
+ const pos = positions[0] ?? -1;
1232
+ if (pos >= 0) cachedGlobalPosition = pos;
1233
+ else {
1234
+ cachedGlobalPosition = 0;
1235
+ tr.insert(0, this.type.create());
1236
+ }
1237
+ };
1238
+ if (dispatch) {
1239
+ ensureGlobalPosition();
1240
+ if (cachedGlobalPosition == null) return false;
1241
+ tr.setNodeAttribute(cachedGlobalPosition, "data", {
1242
+ ...tr.doc.nodeAt(cachedGlobalPosition)?.attrs.data,
1243
+ [key]: value
1244
+ });
1245
+ }
1246
+ return true;
1247
+ } };
1248
+ }
1249
+ });
1250
+
1251
+ //#endregion
1252
+ //#region src/extensions/hard-break.tsx
1253
+ const HardBreak = EmailNode.from(HardBreakBase, () => /* @__PURE__ */ jsx("br", {}));
1254
+
1255
+ //#endregion
1256
+ //#region src/extensions/heading.tsx
1257
+ const Heading$2 = EmailNode.from(Heading$1.extend({ addNodeView() {
1258
+ return ReactNodeViewRenderer(({ node }) => {
1259
+ const level = node.attrs.level ?? 1;
1260
+ const { class: className, ...rest } = node.attrs;
1261
+ const attrs = {
1262
+ ...rest,
1263
+ className: `node-h${level} ${className}`,
1264
+ style: inlineCssToJs(node.attrs.style)
1265
+ };
1266
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(Heading, {
1267
+ as: `h${level}`,
1268
+ ...attrs,
1269
+ children: /* @__PURE__ */ jsx(NodeViewContent, {})
1270
+ }) });
1271
+ });
1272
+ } }), ({ children, node, style }) => {
1273
+ return /* @__PURE__ */ jsx(Heading, {
1274
+ as: `h${node.attrs?.level ?? 1}`,
1275
+ className: node.attrs?.class || void 0,
1276
+ style: {
1277
+ ...style,
1278
+ ...inlineCssToJs(node.attrs?.style),
1279
+ ...getTextAlignment(node.attrs?.align ?? node.attrs?.alignment)
1280
+ },
1281
+ children
1282
+ });
1283
+ });
1284
+
1285
+ //#endregion
1286
+ //#region src/extensions/italic.tsx
1287
+ const Italic = EmailMark.from(ItalicBase, ({ children, style }) => /* @__PURE__ */ jsx("em", {
1288
+ style,
1289
+ children
1290
+ }));
1291
+
1292
+ //#endregion
1293
+ //#region src/extensions/preserved-style.tsx
1294
+ const PreservedStyle = EmailMark.create({
1295
+ name: "preservedStyle",
1296
+ addAttributes() {
1297
+ return { style: {
1298
+ default: null,
1299
+ parseHTML: (element) => element.getAttribute("style"),
1300
+ renderHTML: (attributes) => {
1301
+ if (!attributes.style) return {};
1302
+ return { style: attributes.style };
1303
+ }
1304
+ } };
1305
+ },
1306
+ parseHTML() {
1307
+ return [{
1308
+ tag: "span[style]",
1309
+ getAttrs: (element) => {
1310
+ if (typeof element === "string") return false;
1311
+ const style = element.getAttribute("style");
1312
+ if (style && hasPreservableStyles(style)) return { style };
1313
+ return false;
1314
+ }
1315
+ }];
1316
+ },
1317
+ renderHTML({ HTMLAttributes }) {
1318
+ return [
1319
+ "span",
1320
+ mergeAttributes(HTMLAttributes),
1321
+ 0
1322
+ ];
1323
+ },
1324
+ renderToReactEmail({ children, mark }) {
1325
+ return /* @__PURE__ */ jsx("span", {
1326
+ style: mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : void 0,
1327
+ children
1328
+ });
1329
+ }
1330
+ });
1331
+ const LINK_INDICATOR_STYLES = [
1332
+ "color",
1333
+ "text-decoration",
1334
+ "text-decoration-line",
1335
+ "text-decoration-color",
1336
+ "text-decoration-style"
1337
+ ];
1338
+ function parseStyleString(styleString) {
1339
+ const temp = document.createElement("div");
1340
+ temp.style.cssText = styleString;
1341
+ return temp.style;
1342
+ }
1343
+ function hasBackground(style) {
1344
+ const bgColor = style.backgroundColor;
1345
+ const bg = style.background;
1346
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1347
+ if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1348
+ return false;
1349
+ }
1350
+ function hasPreservableStyles(styleString) {
1351
+ return processStylesForUnlink(styleString) !== null;
1352
+ }
1353
+ /**
1354
+ * Processes styles when unlinking:
1355
+ * - Has background (button-like): preserve all styles
1356
+ * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1357
+ */
1358
+ function processStylesForUnlink(styleString) {
1359
+ if (!styleString) return null;
1360
+ const style = parseStyleString(styleString);
1361
+ if (hasBackground(style)) return styleString;
1362
+ const filtered = [];
1363
+ for (let i = 0; i < style.length; i++) {
1364
+ const prop = style[i];
1365
+ if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1366
+ const value = style.getPropertyValue(prop);
1367
+ if (value) filtered.push(`${prop}: ${value}`);
1368
+ }
1369
+ return filtered.length > 0 ? filtered.join("; ") : null;
1370
+ }
1371
+
1372
+ //#endregion
1373
+ //#region src/extensions/link.tsx
1374
+ const Link$1 = EmailMark.from(TiptapLink, ({ children, mark, style }) => {
1375
+ const linkMarkStyle = mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : {};
1376
+ return /* @__PURE__ */ jsx(Link, {
1377
+ href: mark.attrs?.href ?? void 0,
1378
+ rel: mark.attrs?.rel ?? void 0,
1379
+ style: {
1380
+ ...style,
1381
+ ...linkMarkStyle
1382
+ },
1383
+ target: mark.attrs?.target ?? void 0,
1384
+ ...mark.attrs?.["ses:no-track"] ? { "ses:no-track": mark.attrs["ses:no-track"] } : {},
1385
+ children
1386
+ });
1387
+ }).extend({
888
1388
  parseHTML() {
889
1389
  return [{
890
- tag: "div:not([data-type])",
1390
+ tag: "a[target]:not([data-id=\"react-email-button\"])",
1391
+ getAttrs: (node) => {
1392
+ if (typeof node === "string") return false;
1393
+ const element = node;
1394
+ const attrs = {};
1395
+ Array.from(element.attributes).forEach((attr) => {
1396
+ attrs[attr.name] = attr.value;
1397
+ });
1398
+ return attrs;
1399
+ }
1400
+ }, {
1401
+ tag: "a[href]:not([data-id=\"react-email-button\"])",
891
1402
  getAttrs: (node) => {
892
1403
  if (typeof node === "string") return false;
893
1404
  const element = node;
@@ -899,29 +1410,51 @@ const Div = EmailNode.create({
899
1410
  }
900
1411
  }];
901
1412
  },
902
- renderHTML({ HTMLAttributes }) {
903
- return [
904
- "div",
905
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
906
- 0
907
- ];
908
- },
909
1413
  addAttributes() {
910
- return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
1414
+ return {
1415
+ ...this.parent?.(),
1416
+ "ses:no-track": {
1417
+ default: null,
1418
+ parseHTML: (element) => element.getAttribute("ses:no-track")
1419
+ }
1420
+ };
911
1421
  },
912
- renderToReactEmail({ children, node, style }) {
913
- const inlineStyles = inlineCssToJs(node.attrs?.style);
914
- return /* @__PURE__ */ jsx("div", {
915
- className: node.attrs?.class || void 0,
916
- style: {
917
- ...style,
918
- ...inlineStyles
919
- },
920
- children
921
- });
1422
+ addCommands() {
1423
+ return {
1424
+ ...this.parent?.(),
1425
+ unsetLink: () => ({ state, chain }) => {
1426
+ const { from } = state.selection;
1427
+ const linkStyle = state.doc.resolve(from).marks().find((m) => m.type.name === "link")?.attrs?.style ?? null;
1428
+ const preservedStyle = processStylesForUnlink(linkStyle);
1429
+ const shouldRemoveUnderline = preservedStyle !== linkStyle;
1430
+ if (preservedStyle) {
1431
+ const cmd = chain().extendMarkRange("link").unsetMark("link").setMark("preservedStyle", { style: preservedStyle });
1432
+ return shouldRemoveUnderline ? cmd.unsetMark("underline").run() : cmd.run();
1433
+ }
1434
+ return chain().extendMarkRange("link").unsetMark("link").unsetMark("underline").run();
1435
+ }
1436
+ };
1437
+ },
1438
+ addKeyboardShortcuts() {
1439
+ return { "Mod-k": () => {
1440
+ editorEventBus.dispatch("bubble-menu:add-link", void 0);
1441
+ return this.editor.chain().focus().toggleLink({ href: "" }).run();
1442
+ } };
922
1443
  }
923
1444
  });
924
1445
 
1446
+ //#endregion
1447
+ //#region src/extensions/list-item.tsx
1448
+ const ListItem = EmailNode.from(ListItemBase, ({ children, node, style }) => /* @__PURE__ */ jsx("li", {
1449
+ className: node.attrs?.class || void 0,
1450
+ style: {
1451
+ ...style,
1452
+ ...inlineCssToJs(node.attrs?.style),
1453
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1454
+ },
1455
+ children
1456
+ }));
1457
+
925
1458
  //#endregion
926
1459
  //#region src/extensions/max-nesting.ts
927
1460
  const MaxNesting = Extension.create({
@@ -997,6 +1530,33 @@ const MaxNesting = Extension.create({
997
1530
  }
998
1531
  });
999
1532
 
1533
+ //#endregion
1534
+ //#region src/extensions/ordered-list.tsx
1535
+ const OrderedList = EmailNode.from(OrderedListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ol", {
1536
+ className: node.attrs?.class || void 0,
1537
+ start: node.attrs?.start,
1538
+ style: {
1539
+ ...style,
1540
+ ...inlineCssToJs(node.attrs?.style)
1541
+ },
1542
+ children
1543
+ }));
1544
+
1545
+ //#endregion
1546
+ //#region src/extensions/paragraph.tsx
1547
+ const Paragraph = EmailNode.from(ParagraphBase, ({ children, node, style }) => {
1548
+ const isEmpty = !node.content || node.content.length === 0;
1549
+ return /* @__PURE__ */ jsx("p", {
1550
+ className: node.attrs?.class || void 0,
1551
+ style: {
1552
+ ...style,
1553
+ ...inlineCssToJs(node.attrs?.style),
1554
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1555
+ },
1556
+ children: isEmpty ? /* @__PURE__ */ jsx("br", {}) : children
1557
+ });
1558
+ });
1559
+
1000
1560
  //#endregion
1001
1561
  //#region src/extensions/placeholder.ts
1002
1562
  const Placeholder = TipTapPlaceholder.configure({
@@ -1007,83 +1567,9 @@ const Placeholder = TipTapPlaceholder.configure({
1007
1567
  includeChildren: true
1008
1568
  });
1009
1569
 
1010
- //#endregion
1011
- //#region src/extensions/preserved-style.ts
1012
- const PreservedStyle = Mark.create({
1013
- name: "preservedStyle",
1014
- addAttributes() {
1015
- return { style: {
1016
- default: null,
1017
- parseHTML: (element) => element.getAttribute("style"),
1018
- renderHTML: (attributes) => {
1019
- if (!attributes.style) return {};
1020
- return { style: attributes.style };
1021
- }
1022
- } };
1023
- },
1024
- parseHTML() {
1025
- return [{
1026
- tag: "span[style]",
1027
- getAttrs: (element) => {
1028
- if (typeof element === "string") return false;
1029
- const style = element.getAttribute("style");
1030
- if (style && hasPreservableStyles(style)) return { style };
1031
- return false;
1032
- }
1033
- }];
1034
- },
1035
- renderHTML({ HTMLAttributes }) {
1036
- return [
1037
- "span",
1038
- mergeAttributes(HTMLAttributes),
1039
- 0
1040
- ];
1041
- }
1042
- });
1043
- const LINK_INDICATOR_STYLES = [
1044
- "color",
1045
- "text-decoration",
1046
- "text-decoration-line",
1047
- "text-decoration-color",
1048
- "text-decoration-style"
1049
- ];
1050
- function parseStyleString(styleString) {
1051
- const temp = document.createElement("div");
1052
- temp.style.cssText = styleString;
1053
- return temp.style;
1054
- }
1055
- function hasBackground(style) {
1056
- const bgColor = style.backgroundColor;
1057
- const bg = style.background;
1058
- if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1059
- if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1060
- return false;
1061
- }
1062
- function hasPreservableStyles(styleString) {
1063
- return processStylesForUnlink(styleString) !== null;
1064
- }
1065
- /**
1066
- * Processes styles when unlinking:
1067
- * - Has background (button-like): preserve all styles
1068
- * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1069
- */
1070
- function processStylesForUnlink(styleString) {
1071
- if (!styleString) return null;
1072
- const style = parseStyleString(styleString);
1073
- if (hasBackground(style)) return styleString;
1074
- const filtered = [];
1075
- for (let i = 0; i < style.length; i++) {
1076
- const prop = style[i];
1077
- if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1078
- const value = style.getPropertyValue(prop);
1079
- if (value) filtered.push(`${prop}: ${value}`);
1080
- }
1081
- return filtered.length > 0 ? filtered.join("; ") : null;
1082
- }
1083
-
1084
1570
  //#endregion
1085
1571
  //#region src/extensions/preview-text.ts
1086
- const PreviewText = Node.create({
1572
+ const PreviewText = Node$1.create({
1087
1573
  name: "previewText",
1088
1574
  group: "block",
1089
1575
  selectable: false,
@@ -1122,17 +1608,6 @@ const PreviewText = Node.create({
1122
1608
  }
1123
1609
  });
1124
1610
 
1125
- //#endregion
1126
- //#region src/utils/get-text-alignment.ts
1127
- function getTextAlignment(alignment) {
1128
- switch (alignment) {
1129
- case "left": return { textAlign: "left" };
1130
- case "center": return { textAlign: "center" };
1131
- case "right": return { textAlign: "right" };
1132
- default: return {};
1133
- }
1134
- }
1135
-
1136
1611
  //#endregion
1137
1612
  //#region src/extensions/section.tsx
1138
1613
  const Section = EmailNode.create({
@@ -1181,6 +1656,13 @@ const Section = EmailNode.create({
1181
1656
  }
1182
1657
  });
1183
1658
 
1659
+ //#endregion
1660
+ //#region src/extensions/strike.tsx
1661
+ const Strike = EmailMark.from(StrikeBase, ({ children, style }) => /* @__PURE__ */ jsx("s", {
1662
+ style,
1663
+ children
1664
+ }));
1665
+
1184
1666
  //#endregion
1185
1667
  //#region src/extensions/style-attribute.tsx
1186
1668
  const StyleAttribute = Extension.create({
@@ -1230,12 +1712,12 @@ const StyleAttribute = Extension.create({
1230
1712
  });
1231
1713
 
1232
1714
  //#endregion
1233
- //#region src/extensions/sup.ts
1715
+ //#region src/extensions/sup.tsx
1234
1716
  /**
1235
1717
  * This extension allows you to mark text as superscript.
1236
1718
  * @see https://tiptap.dev/api/marks/superscript
1237
1719
  */
1238
- const Sup = Mark.create({
1720
+ const Sup = EmailMark.create({
1239
1721
  name: "sup",
1240
1722
  addOptions() {
1241
1723
  return { HTMLAttributes: {} };
@@ -1250,6 +1732,12 @@ const Sup = Mark.create({
1250
1732
  0
1251
1733
  ];
1252
1734
  },
1735
+ renderToReactEmail({ children, style }) {
1736
+ return /* @__PURE__ */ jsx("sup", {
1737
+ style,
1738
+ children
1739
+ });
1740
+ },
1253
1741
  addCommands() {
1254
1742
  return {
1255
1743
  setSup: () => ({ commands }) => {
@@ -1415,7 +1903,7 @@ const TableCell = EmailNode.create({
1415
1903
  });
1416
1904
  }
1417
1905
  });
1418
- const TableHeader = Node.create({
1906
+ const TableHeader = Node$1.create({
1419
1907
  name: "tableHeader",
1420
1908
  group: "tableCell",
1421
1909
  content: "block+",
@@ -1452,8 +1940,8 @@ const TableHeader = Node.create({
1452
1940
  });
1453
1941
 
1454
1942
  //#endregion
1455
- //#region src/extensions/uppercase.ts
1456
- const Uppercase = Mark.create({
1943
+ //#region src/extensions/uppercase.tsx
1944
+ const Uppercase = EmailMark.create({
1457
1945
  name: "uppercase",
1458
1946
  addOptions() {
1459
1947
  return { HTMLAttributes: {} };
@@ -1474,6 +1962,15 @@ const Uppercase = Mark.create({
1474
1962
  0
1475
1963
  ];
1476
1964
  },
1965
+ renderToReactEmail({ children, style }) {
1966
+ return /* @__PURE__ */ jsx("span", {
1967
+ style: {
1968
+ ...style,
1969
+ textTransform: "uppercase"
1970
+ },
1971
+ children
1972
+ });
1973
+ },
1477
1974
  addCommands() {
1478
1975
  return {
1479
1976
  setUppercase: () => ({ commands }) => {
@@ -1661,39 +2158,23 @@ const ColumnsColumn = EmailNode.create({
1661
2158
 
1662
2159
  //#endregion
1663
2160
  //#region src/extensions/index.ts
1664
- const coreExtensions = [
1665
- StarterKit.configure({
1666
- undoRedo: false,
1667
- heading: false,
1668
- link: false,
1669
- underline: false,
1670
- trailingNode: false,
1671
- bold: false,
1672
- gapcursor: false,
1673
- listItem: {},
1674
- bulletList: { HTMLAttributes: { class: "node-bulletList" } },
1675
- paragraph: { HTMLAttributes: { class: "node-paragraph" } },
1676
- orderedList: { HTMLAttributes: { class: "node-orderedList" } },
1677
- blockquote: { HTMLAttributes: { class: "node-blockquote" } },
1678
- codeBlock: false,
1679
- code: { HTMLAttributes: {
1680
- class: "node-inlineCode",
1681
- spellcheck: "false"
1682
- } },
1683
- horizontalRule: false,
1684
- dropcursor: {
1685
- color: "#61a8f8",
1686
- class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
1687
- width: 4
1688
- }
1689
- }),
1690
- CodeBlockPrism.configure({
1691
- defaultLanguage: "javascript",
1692
- HTMLAttributes: { class: "prism node-codeBlock" }
1693
- }),
2161
+ const starterKitExtensions = {
2162
+ CodeBlockPrism,
2163
+ Code,
2164
+ Paragraph,
2165
+ BulletList,
2166
+ OrderedList,
2167
+ Blockquote,
2168
+ ListItem,
2169
+ HardBreak,
2170
+ Italic,
1694
2171
  Placeholder,
1695
2172
  PreviewText,
1696
2173
  Bold,
2174
+ Strike,
2175
+ Heading: Heading$2,
2176
+ Divider,
2177
+ Link: Link$1,
1697
2178
  Sup,
1698
2179
  Uppercase,
1699
2180
  PreservedStyle,
@@ -1705,80 +2186,378 @@ const coreExtensions = [
1705
2186
  Div,
1706
2187
  Button,
1707
2188
  Section,
1708
- AlignmentAttribute.configure({ types: [
1709
- "heading",
1710
- "paragraph",
1711
- "image",
1712
- "blockquote",
1713
- "codeBlock",
1714
- "bulletList",
1715
- "orderedList",
1716
- "listItem",
1717
- "button",
1718
- "youtube",
1719
- "twitter",
1720
- "table",
1721
- "tableRow",
1722
- "tableCell",
1723
- "tableHeader",
1724
- "columnsColumn"
1725
- ] }),
1726
- StyleAttribute.configure({ types: [
1727
- "heading",
1728
- "paragraph",
1729
- "image",
1730
- "blockquote",
1731
- "codeBlock",
1732
- "bulletList",
1733
- "orderedList",
1734
- "listItem",
1735
- "button",
1736
- "youtube",
1737
- "twitter",
1738
- "horizontalRule",
1739
- "footer",
1740
- "section",
1741
- "div",
1742
- "body",
1743
- "table",
1744
- "tableRow",
1745
- "tableCell",
1746
- "tableHeader",
1747
- "columnsColumn",
1748
- "link"
1749
- ] }),
1750
- ClassAttribute.configure({ types: [
1751
- "heading",
1752
- "paragraph",
1753
- "image",
1754
- "blockquote",
1755
- "bulletList",
1756
- "orderedList",
1757
- "listItem",
1758
- "button",
1759
- "youtube",
1760
- "twitter",
1761
- "horizontalRule",
1762
- "footer",
1763
- "section",
1764
- "div",
1765
- "body",
1766
- "table",
1767
- "tableRow",
1768
- "tableCell",
1769
- "tableHeader",
1770
- "columnsColumn",
1771
- "link"
1772
- ] }),
1773
- MaxNesting.configure({
1774
- maxDepth: 50,
1775
- nodeTypes: [
1776
- "section",
1777
- "bulletList",
1778
- "orderedList"
1779
- ]
1780
- })
1781
- ];
2189
+ GlobalContent,
2190
+ AlignmentAttribute,
2191
+ StyleAttribute,
2192
+ ClassAttribute,
2193
+ MaxNesting
2194
+ };
2195
+ const StarterKit = Extension.create({
2196
+ name: "reactEmailStarterKit",
2197
+ addOptions() {
2198
+ return {
2199
+ TiptapStarterKit: {},
2200
+ CodeBlockPrism: {
2201
+ defaultLanguage: "javascript",
2202
+ HTMLAttributes: { class: "prism node-codeBlock" }
2203
+ },
2204
+ Code: { HTMLAttributes: {
2205
+ class: "node-inlineCode",
2206
+ spellcheck: "false"
2207
+ } },
2208
+ Paragraph: { HTMLAttributes: { class: "node-paragraph" } },
2209
+ BulletList: { HTMLAttributes: { class: "node-bulletList" } },
2210
+ OrderedList: { HTMLAttributes: { class: "node-orderedList" } },
2211
+ Blockquote: { HTMLAttributes: { class: "node-blockquote" } },
2212
+ ListItem: {},
2213
+ HardBreak: {},
2214
+ Italic: {},
2215
+ Placeholder: {},
2216
+ PreviewText: {},
2217
+ Bold: {},
2218
+ Strike: {},
2219
+ Heading: {},
2220
+ Divider: {},
2221
+ Link: {},
2222
+ Sup: {},
2223
+ Uppercase: {},
2224
+ PreservedStyle: {},
2225
+ Table: {},
2226
+ TableRow: {},
2227
+ TableCell: {},
2228
+ TableHeader: {},
2229
+ Body: {},
2230
+ Div: {},
2231
+ Button: {},
2232
+ Section: {},
2233
+ GlobalContent: {},
2234
+ AlignmentAttribute: { types: [
2235
+ "heading",
2236
+ "paragraph",
2237
+ "image",
2238
+ "blockquote",
2239
+ "codeBlock",
2240
+ "bulletList",
2241
+ "orderedList",
2242
+ "listItem",
2243
+ "button",
2244
+ "youtube",
2245
+ "twitter",
2246
+ "table",
2247
+ "tableRow",
2248
+ "tableCell",
2249
+ "tableHeader",
2250
+ "columnsColumn"
2251
+ ] },
2252
+ StyleAttribute: { types: [
2253
+ "heading",
2254
+ "paragraph",
2255
+ "image",
2256
+ "blockquote",
2257
+ "codeBlock",
2258
+ "bulletList",
2259
+ "orderedList",
2260
+ "listItem",
2261
+ "button",
2262
+ "youtube",
2263
+ "twitter",
2264
+ "horizontalRule",
2265
+ "footer",
2266
+ "section",
2267
+ "div",
2268
+ "body",
2269
+ "table",
2270
+ "tableRow",
2271
+ "tableCell",
2272
+ "tableHeader",
2273
+ "columnsColumn",
2274
+ "link"
2275
+ ] },
2276
+ ClassAttribute: { types: [
2277
+ "heading",
2278
+ "paragraph",
2279
+ "image",
2280
+ "blockquote",
2281
+ "bulletList",
2282
+ "orderedList",
2283
+ "listItem",
2284
+ "button",
2285
+ "youtube",
2286
+ "twitter",
2287
+ "horizontalRule",
2288
+ "footer",
2289
+ "section",
2290
+ "div",
2291
+ "body",
2292
+ "table",
2293
+ "tableRow",
2294
+ "tableCell",
2295
+ "tableHeader",
2296
+ "columnsColumn",
2297
+ "link"
2298
+ ] },
2299
+ MaxNesting: {
2300
+ maxDepth: 50,
2301
+ nodeTypes: [
2302
+ "section",
2303
+ "bulletList",
2304
+ "orderedList"
2305
+ ]
2306
+ }
2307
+ };
2308
+ },
2309
+ addExtensions() {
2310
+ const extensions = [];
2311
+ if (this.options.TiptapStarterKit !== false) extensions.push(TipTapStarterKit.configure({
2312
+ undoRedo: false,
2313
+ heading: false,
2314
+ link: false,
2315
+ underline: false,
2316
+ trailingNode: false,
2317
+ bold: false,
2318
+ italic: false,
2319
+ strike: false,
2320
+ code: false,
2321
+ paragraph: false,
2322
+ bulletList: false,
2323
+ orderedList: false,
2324
+ listItem: false,
2325
+ blockquote: false,
2326
+ hardBreak: false,
2327
+ gapcursor: false,
2328
+ codeBlock: false,
2329
+ horizontalRule: false,
2330
+ dropcursor: {
2331
+ color: "#61a8f8",
2332
+ class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
2333
+ width: 4
2334
+ },
2335
+ ...this.options.TiptapStarterKit
2336
+ }));
2337
+ for (const [name, extension] of Object.entries(starterKitExtensions)) {
2338
+ const key = name;
2339
+ const extensionOptions = this.options[key];
2340
+ if (extensionOptions !== false) extensions.push(extension.configure(extensionOptions));
2341
+ }
2342
+ return extensions;
2343
+ }
2344
+ });
2345
+
2346
+ //#endregion
2347
+ //#region src/core/create-drop-handler.ts
2348
+ function createDropHandler({ onPaste, onUploadImage }) {
2349
+ return (view, event, _slice, moved) => {
2350
+ if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
2351
+ event.preventDefault();
2352
+ const file = event.dataTransfer.files[0];
2353
+ if (onPaste?.(file, view)) return true;
2354
+ if (file.type.includes("image/") && onUploadImage) {
2355
+ onUploadImage(file, view, (view.posAtCoords({
2356
+ left: event.clientX,
2357
+ top: event.clientY
2358
+ })?.pos || 0) - 1);
2359
+ return true;
2360
+ }
2361
+ }
2362
+ return false;
2363
+ };
2364
+ }
2365
+
2366
+ //#endregion
2367
+ //#region src/utils/paste-sanitizer.ts
2368
+ /**
2369
+ * Sanitizes pasted HTML.
2370
+ * - From editor (has node-* classes): pass through as-is
2371
+ * - From external: strip all styles/classes, keep only semantic HTML
2372
+ */
2373
+ /**
2374
+ * Detects content from the Resend editor by checking for node-* class names.
2375
+ */
2376
+ const EDITOR_CLASS_PATTERN = /class="[^"]*node-/;
2377
+ /**
2378
+ * Attributes to preserve on specific elements for EXTERNAL content.
2379
+ * Only functional attributes - NO style or class.
2380
+ */
2381
+ const PRESERVED_ATTRIBUTES = {
2382
+ a: [
2383
+ "href",
2384
+ "target",
2385
+ "rel"
2386
+ ],
2387
+ img: [
2388
+ "src",
2389
+ "alt",
2390
+ "width",
2391
+ "height"
2392
+ ],
2393
+ td: ["colspan", "rowspan"],
2394
+ th: [
2395
+ "colspan",
2396
+ "rowspan",
2397
+ "scope"
2398
+ ],
2399
+ table: [
2400
+ "border",
2401
+ "cellpadding",
2402
+ "cellspacing"
2403
+ ],
2404
+ "*": ["id"]
2405
+ };
2406
+ function isFromEditor(html) {
2407
+ return EDITOR_CLASS_PATTERN.test(html);
2408
+ }
2409
+ function sanitizePastedHtml(html) {
2410
+ if (isFromEditor(html)) return html;
2411
+ const doc = new DOMParser().parseFromString(html, "text/html");
2412
+ sanitizeNode(doc.body);
2413
+ return doc.body.innerHTML;
2414
+ }
2415
+ function sanitizeNode(node) {
2416
+ if (node.nodeType === Node.ELEMENT_NODE) sanitizeElement(node);
2417
+ for (const child of Array.from(node.childNodes)) sanitizeNode(child);
2418
+ }
2419
+ function sanitizeElement(el) {
2420
+ const allowedForTag = PRESERVED_ATTRIBUTES[el.tagName.toLowerCase()] || [];
2421
+ const allowedGlobal = PRESERVED_ATTRIBUTES["*"] || [];
2422
+ const allowed = new Set([...allowedForTag, ...allowedGlobal]);
2423
+ const attributesToRemove = [];
2424
+ for (const attr of Array.from(el.attributes)) {
2425
+ if (attr.name.startsWith("data-")) {
2426
+ attributesToRemove.push(attr.name);
2427
+ continue;
2428
+ }
2429
+ if (!allowed.has(attr.name)) attributesToRemove.push(attr.name);
2430
+ }
2431
+ for (const attr of attributesToRemove) el.removeAttribute(attr);
2432
+ }
2433
+
2434
+ //#endregion
2435
+ //#region src/core/create-paste-handler.ts
2436
+ function createPasteHandler({ onPaste, onUploadImage, extensions }) {
2437
+ return (view, event, slice) => {
2438
+ const text = event.clipboardData?.getData("text/plain");
2439
+ if (text && onPaste?.(text, view)) {
2440
+ event.preventDefault();
2441
+ return true;
2442
+ }
2443
+ if (event.clipboardData?.files?.[0]) {
2444
+ const file = event.clipboardData.files[0];
2445
+ if (onPaste?.(file, view)) {
2446
+ event.preventDefault();
2447
+ return true;
2448
+ }
2449
+ if (file.type.includes("image/") && onUploadImage) {
2450
+ const pos = view.state.selection.from;
2451
+ onUploadImage(file, view, pos);
2452
+ return true;
2453
+ }
2454
+ }
2455
+ /**
2456
+ * If the coming content has a single child, we can assume
2457
+ * it's a plain text and doesn't need to be parsed and
2458
+ * be introduced in a new line
2459
+ */
2460
+ if (slice.content.childCount === 1) return false;
2461
+ if (event.clipboardData?.getData?.("text/html")) {
2462
+ event.preventDefault();
2463
+ const jsonContent = generateJSON(sanitizePastedHtml(event.clipboardData.getData("text/html")), extensions);
2464
+ const node = view.state.schema.nodeFromJSON(jsonContent);
2465
+ const transaction = view.state.tr.replaceSelectionWith(node, false);
2466
+ view.dispatch(transaction);
2467
+ return true;
2468
+ }
2469
+ return false;
2470
+ };
2471
+ }
2472
+
2473
+ //#endregion
2474
+ //#region src/core/is-document-visually-empty.ts
2475
+ function isDocumentVisuallyEmpty(doc) {
2476
+ let nonGlobalNodeCount = 0;
2477
+ let firstNonGlobalNode = null;
2478
+ for (let index = 0; index < doc.childCount; index += 1) {
2479
+ const node = doc.child(index);
2480
+ if (node.type.name === "globalContent") continue;
2481
+ nonGlobalNodeCount += 1;
2482
+ if (firstNonGlobalNode === null) firstNonGlobalNode = {
2483
+ type: node.type,
2484
+ textContent: node.textContent,
2485
+ childCount: node.content.childCount
2486
+ };
2487
+ }
2488
+ if (nonGlobalNodeCount === 0) return true;
2489
+ if (nonGlobalNodeCount !== 1) return false;
2490
+ return firstNonGlobalNode?.type.name === "paragraph" && firstNonGlobalNode.textContent.trim().length === 0 && firstNonGlobalNode.childCount === 0;
2491
+ }
2492
+
2493
+ //#endregion
2494
+ //#region src/core/use-editor.ts
2495
+ const COLLABORATION_EXTENSION_NAMES = new Set(["liveblocksExtension", "collaboration"]);
2496
+ function hasCollaborationExtension(exts) {
2497
+ return exts.some((ext) => COLLABORATION_EXTENSION_NAMES.has(ext.name));
2498
+ }
2499
+ function useEditor({ content, extensions = [], onUpdate, onPaste, onUploadImage, onReady, editable = true, ...rest }) {
2500
+ const [contentError, setContentError] = React.useState(null);
2501
+ const isCollaborative = hasCollaborationExtension(extensions);
2502
+ const effectiveExtensions = React.useMemo(() => [
2503
+ StarterKit,
2504
+ ...isCollaborative ? [] : [UndoRedo],
2505
+ ...extensions
2506
+ ], [extensions, isCollaborative]);
2507
+ const editor = useEditor$1({
2508
+ content: isCollaborative ? void 0 : content,
2509
+ extensions: effectiveExtensions,
2510
+ editable,
2511
+ immediatelyRender: false,
2512
+ enableContentCheck: true,
2513
+ onContentError({ editor: editor$1, error, disableCollaboration }) {
2514
+ disableCollaboration();
2515
+ setContentError(error);
2516
+ console.error(error);
2517
+ editor$1.setEditable(false);
2518
+ },
2519
+ onCreate({ editor: editor$1 }) {
2520
+ onReady?.(editor$1);
2521
+ },
2522
+ onUpdate({ editor: editor$1, transaction }) {
2523
+ onUpdate?.(editor$1, transaction);
2524
+ },
2525
+ editorProps: {
2526
+ handleDOMEvents: { click: (view, event) => {
2527
+ if (!view.editable) {
2528
+ if (event.target.closest("a")) {
2529
+ event.preventDefault();
2530
+ return true;
2531
+ }
2532
+ }
2533
+ return false;
2534
+ } },
2535
+ handlePaste: createPasteHandler({
2536
+ onPaste,
2537
+ onUploadImage,
2538
+ extensions: effectiveExtensions
2539
+ }),
2540
+ handleDrop: createDropHandler({
2541
+ onPaste,
2542
+ onUploadImage
2543
+ })
2544
+ },
2545
+ ...rest
2546
+ });
2547
+ return {
2548
+ editor,
2549
+ isEditorEmpty: useEditorState({
2550
+ editor,
2551
+ selector: (context) => {
2552
+ if (!context.editor) return true;
2553
+ return isDocumentVisuallyEmpty(context.editor.state.doc);
2554
+ }
2555
+ }) ?? true,
2556
+ extensions: effectiveExtensions,
2557
+ contentError,
2558
+ isCollaborative
2559
+ };
2560
+ }
1782
2561
 
1783
2562
  //#endregion
1784
2563
  //#region src/utils/set-text-alignment.ts
@@ -2219,7 +2998,7 @@ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, class
2219
2998
  },
2220
2999
  {
2221
3000
  name: "Code",
2222
- icon: Code,
3001
+ icon: Code$1,
2223
3002
  command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
2224
3003
  isActive: editorState?.isCodeBlockActive ?? false
2225
3004
  }
@@ -2934,13 +3713,12 @@ function groupByCategory(items) {
2934
3713
  return ordered;
2935
3714
  }
2936
3715
  function CommandItem({ item, selected, onSelect }) {
2937
- const Icon = item.icon;
2938
3716
  return /* @__PURE__ */ jsxs("button", {
2939
3717
  "data-re-slash-command-item": "",
2940
3718
  "data-selected": selected || void 0,
2941
3719
  onClick: onSelect,
2942
3720
  type: "button",
2943
- children: [/* @__PURE__ */ jsx(Icon, { size: 20 }), /* @__PURE__ */ jsx("span", { children: item.title })]
3721
+ children: [item.icon, /* @__PURE__ */ jsx("span", { children: item.title })]
2944
3722
  });
2945
3723
  }
2946
3724
  function CommandList({ items, command, query, ref }) {
@@ -3015,11 +3793,11 @@ function CommandList({ items, command, query, ref }) {
3015
3793
  }
3016
3794
 
3017
3795
  //#endregion
3018
- //#region src/ui/slash-command/commands.ts
3796
+ //#region src/ui/slash-command/commands.tsx
3019
3797
  const TEXT = {
3020
3798
  title: "Text",
3021
3799
  description: "Plain text block",
3022
- icon: Text,
3800
+ icon: /* @__PURE__ */ jsx(Text, { size: 20 }),
3023
3801
  category: "Text",
3024
3802
  searchTerms: ["p", "paragraph"],
3025
3803
  command: ({ editor, range }) => {
@@ -3029,7 +3807,7 @@ const TEXT = {
3029
3807
  const H1 = {
3030
3808
  title: "Title",
3031
3809
  description: "Large heading",
3032
- icon: Heading1,
3810
+ icon: /* @__PURE__ */ jsx(Heading1, { size: 20 }),
3033
3811
  category: "Text",
3034
3812
  searchTerms: [
3035
3813
  "title",
@@ -3044,7 +3822,7 @@ const H1 = {
3044
3822
  const H2 = {
3045
3823
  title: "Subtitle",
3046
3824
  description: "Medium heading",
3047
- icon: Heading2,
3825
+ icon: /* @__PURE__ */ jsx(Heading2, { size: 20 }),
3048
3826
  category: "Text",
3049
3827
  searchTerms: [
3050
3828
  "subtitle",
@@ -3058,7 +3836,7 @@ const H2 = {
3058
3836
  const H3 = {
3059
3837
  title: "Heading",
3060
3838
  description: "Small heading",
3061
- icon: Heading3,
3839
+ icon: /* @__PURE__ */ jsx(Heading3, { size: 20 }),
3062
3840
  category: "Text",
3063
3841
  searchTerms: [
3064
3842
  "subtitle",
@@ -3072,7 +3850,7 @@ const H3 = {
3072
3850
  const BULLET_LIST = {
3073
3851
  title: "Bullet list",
3074
3852
  description: "Unordered list",
3075
- icon: List,
3853
+ icon: /* @__PURE__ */ jsx(List, { size: 20 }),
3076
3854
  category: "Text",
3077
3855
  searchTerms: ["unordered", "point"],
3078
3856
  command: ({ editor, range }) => {
@@ -3082,7 +3860,7 @@ const BULLET_LIST = {
3082
3860
  const NUMBERED_LIST = {
3083
3861
  title: "Numbered list",
3084
3862
  description: "Ordered list",
3085
- icon: ListOrdered,
3863
+ icon: /* @__PURE__ */ jsx(ListOrdered, { size: 20 }),
3086
3864
  category: "Text",
3087
3865
  searchTerms: ["ordered"],
3088
3866
  command: ({ editor, range }) => {
@@ -3092,7 +3870,7 @@ const NUMBERED_LIST = {
3092
3870
  const QUOTE = {
3093
3871
  title: "Quote",
3094
3872
  description: "Block quote",
3095
- icon: TextQuote,
3873
+ icon: /* @__PURE__ */ jsx(TextQuote, { size: 20 }),
3096
3874
  category: "Text",
3097
3875
  searchTerms: ["blockquote"],
3098
3876
  command: ({ editor, range }) => {
@@ -3102,7 +3880,7 @@ const QUOTE = {
3102
3880
  const CODE = {
3103
3881
  title: "Code block",
3104
3882
  description: "Code snippet",
3105
- icon: SquareCode,
3883
+ icon: /* @__PURE__ */ jsx(SquareCode, { size: 20 }),
3106
3884
  category: "Text",
3107
3885
  searchTerms: ["codeblock"],
3108
3886
  command: ({ editor, range }) => {
@@ -3112,7 +3890,7 @@ const CODE = {
3112
3890
  const BUTTON = {
3113
3891
  title: "Button",
3114
3892
  description: "Clickable button",
3115
- icon: MousePointer,
3893
+ icon: /* @__PURE__ */ jsx(MousePointer, { size: 20 }),
3116
3894
  category: "Layout",
3117
3895
  searchTerms: ["button"],
3118
3896
  command: ({ editor, range }) => {
@@ -3122,7 +3900,7 @@ const BUTTON = {
3122
3900
  const DIVIDER = {
3123
3901
  title: "Divider",
3124
3902
  description: "Horizontal separator",
3125
- icon: SplitSquareVertical,
3903
+ icon: /* @__PURE__ */ jsx(SplitSquareVertical, { size: 20 }),
3126
3904
  category: "Layout",
3127
3905
  searchTerms: [
3128
3906
  "hr",
@@ -3136,7 +3914,7 @@ const DIVIDER = {
3136
3914
  const SECTION = {
3137
3915
  title: "Section",
3138
3916
  description: "Content section",
3139
- icon: Rows2,
3917
+ icon: /* @__PURE__ */ jsx(Rows2, { size: 20 }),
3140
3918
  category: "Layout",
3141
3919
  searchTerms: [
3142
3920
  "section",
@@ -3150,7 +3928,7 @@ const SECTION = {
3150
3928
  const TWO_COLUMNS = {
3151
3929
  title: "2 columns",
3152
3930
  description: "Two column layout",
3153
- icon: Columns2,
3931
+ icon: /* @__PURE__ */ jsx(Columns2, { size: 20 }),
3154
3932
  category: "Layout",
3155
3933
  searchTerms: [
3156
3934
  "columns",
@@ -3171,7 +3949,7 @@ const TWO_COLUMNS = {
3171
3949
  const THREE_COLUMNS = {
3172
3950
  title: "3 columns",
3173
3951
  description: "Three column layout",
3174
- icon: Columns3,
3952
+ icon: /* @__PURE__ */ jsx(Columns3, { size: 20 }),
3175
3953
  category: "Layout",
3176
3954
  searchTerms: [
3177
3955
  "columns",
@@ -3191,7 +3969,7 @@ const THREE_COLUMNS = {
3191
3969
  const FOUR_COLUMNS = {
3192
3970
  title: "4 columns",
3193
3971
  description: "Four column layout",
3194
- icon: Columns4,
3972
+ icon: /* @__PURE__ */ jsx(Columns4, { size: 20 }),
3195
3973
  category: "Layout",
3196
3974
  searchTerms: [
3197
3975
  "columns",
@@ -3343,5 +4121,5 @@ function createSlashCommand(options) {
3343
4121
  const SlashCommand = createSlashCommand();
3344
4122
 
3345
4123
  //#endregion
3346
- export { AlignmentAttribute, BULLET_LIST, BUTTON, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, EmailNode, FOUR_COLUMNS, FourColumns, H1, H2, H3, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, coreExtensions, createSlashCommand, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, isAtMaxColumnsDepth, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useImageBubbleMenuContext, useLinkBubbleMenuContext };
4124
+ export { AlignmentAttribute, BULLET_LIST, BUTTON, Blockquote, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, BulletList, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, Code, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, EmailNode, FOUR_COLUMNS, FourColumns, GlobalContent, H1, H2, H3, HardBreak, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, Italic, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, ListItem, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, OrderedList, Paragraph, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StarterKit, Strike, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, composeReactEmail, createSlashCommand, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, getGlobalContent, isAtMaxColumnsDepth, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useEditor, useImageBubbleMenuContext, useLinkBubbleMenuContext };
3347
4125
  //# sourceMappingURL=index.mjs.map