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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/core/index.cjs +9 -0
  2. package/dist/core/index.d.cts +2 -0
  3. package/dist/core/index.d.mts +3 -0
  4. package/dist/core/index.mjs +4 -0
  5. package/dist/create-paste-handler-B8BtjBk3.d.cts +14 -0
  6. package/dist/create-paste-handler-B8BtjBk3.d.cts.map +1 -0
  7. package/dist/create-paste-handler-CGR738bC.d.mts +14 -0
  8. package/dist/create-paste-handler-CGR738bC.d.mts.map +1 -0
  9. package/dist/event-bus-CHEzOS_O.mjs +329 -0
  10. package/dist/event-bus-CHEzOS_O.mjs.map +1 -0
  11. package/dist/event-bus-fb8U7hrl.cjs +450 -0
  12. package/dist/extension-DyY8_bh4.mjs +1110 -0
  13. package/dist/extension-DyY8_bh4.mjs.map +1 -0
  14. package/dist/extension-w5VaUeSw.cjs +1235 -0
  15. package/dist/extensions/index.cjs +51 -0
  16. package/dist/extensions/index.d.cts +399 -0
  17. package/dist/extensions/index.d.cts.map +1 -0
  18. package/dist/extensions/index.d.mts +400 -0
  19. package/dist/extensions/index.d.mts.map +1 -0
  20. package/dist/extensions/index.mjs +5 -0
  21. package/dist/extensions-BvfmaKCn.mjs +2088 -0
  22. package/dist/extensions-BvfmaKCn.mjs.map +1 -0
  23. package/dist/extensions-CkjPj2JO.cjs +2369 -0
  24. package/dist/global-content-D_WYaFgX.mjs +78 -0
  25. package/dist/global-content-D_WYaFgX.mjs.map +1 -0
  26. package/dist/global-content-bJgotqmA.cjs +89 -0
  27. package/dist/index-C4KcMQ0R.d.cts +161 -0
  28. package/dist/index-C4KcMQ0R.d.cts.map +1 -0
  29. package/dist/index-CxX7W63O.d.mts +161 -0
  30. package/dist/index-CxX7W63O.d.mts.map +1 -0
  31. package/dist/index.cjs +74 -0
  32. package/dist/index.css +832 -0
  33. package/dist/index.css.map +1 -0
  34. package/dist/index.d.cts +33 -0
  35. package/dist/index.d.cts.map +1 -0
  36. package/dist/index.d.mts +31 -277
  37. package/dist/index.d.mts.map +1 -1
  38. package/dist/index.mjs +64 -1377
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/plugins/index.cjs +23 -0
  41. package/dist/plugins/index.d.cts +191 -0
  42. package/dist/plugins/index.d.cts.map +1 -0
  43. package/dist/plugins/index.d.mts +191 -0
  44. package/dist/plugins/index.d.mts.map +1 -0
  45. package/dist/plugins/index.mjs +3 -0
  46. package/dist/root-CkYaJZpj.mjs +2316 -0
  47. package/dist/root-CkYaJZpj.mjs.map +1 -0
  48. package/dist/root-Gu08xybW.cjs +2832 -0
  49. package/dist/set-text-alignment-Cv72txmv.cjs +24 -0
  50. package/dist/set-text-alignment-OA8IMWmO.mjs +19 -0
  51. package/dist/set-text-alignment-OA8IMWmO.mjs.map +1 -0
  52. package/dist/styles-C-cCyJCn.cjs +211 -0
  53. package/dist/styles-_TMw3YxC.mjs +194 -0
  54. package/dist/styles-_TMw3YxC.mjs.map +1 -0
  55. package/dist/ui/bubble-menu/bubble-menu.css +285 -0
  56. package/dist/ui/index.cjs +147 -0
  57. package/dist/ui/index.d.cts +939 -0
  58. package/dist/ui/index.d.cts.map +1 -0
  59. package/dist/ui/index.d.mts +939 -0
  60. package/dist/ui/index.d.mts.map +1 -0
  61. package/dist/ui/index.mjs +60 -0
  62. package/dist/ui/index.mjs.map +1 -0
  63. package/dist/ui/slash-command/slash-command.css +44 -0
  64. package/dist/ui/themes/default.css +830 -0
  65. package/dist/utils/index.cjs +3 -0
  66. package/dist/utils/index.d.cts +7 -0
  67. package/dist/utils/index.d.cts.map +1 -0
  68. package/dist/utils/index.d.mts +7 -0
  69. package/dist/utils/index.d.mts.map +1 -0
  70. package/dist/utils/index.mjs +3 -0
  71. package/package.json +109 -21
  72. package/dist/index.d.ts +0 -279
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/index.js +0 -1436
@@ -0,0 +1,2088 @@
1
+ import { n as inlineCssToJs, r as resolveConflictingStyles } from "./styles-_TMw3YxC.mjs";
2
+ import { d as TABLE_ATTRIBUTES, f as TABLE_CELL_ATTRIBUTES, h as EmailNode, i as FourColumns, l as COMMON_HTML_ATTRIBUTES, m as createStandardAttributes, o as ThreeColumns, p as TABLE_HEADER_ATTRIBUTES, r as ColumnsColumn, s as TwoColumns, t as editorEventBus, u as LAYOUT_ATTRIBUTES } from "./event-bus-CHEzOS_O.mjs";
3
+ import { t as GlobalContent } from "./global-content-D_WYaFgX.mjs";
4
+ import { UndoRedo } from "@tiptap/extensions";
5
+ import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, useEditor, useEditorState } from "@tiptap/react";
6
+ import * as React from "react";
7
+ import { generateJSON } from "@tiptap/html";
8
+ import * as ReactEmailComponents from "@react-email/components";
9
+ import { Body, Button, CodeBlock, Column, Container, Head, Heading, Hr, Html, Link, Preview, Row, Section, pretty, render, toPlainText } from "@react-email/components";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ import { Extension, InputRule, Mark, Node as Node$1, findChildren, mergeAttributes } from "@tiptap/core";
12
+ import TipTapStarterKit from "@tiptap/starter-kit";
13
+ import BlockquoteBase from "@tiptap/extension-blockquote";
14
+ import BoldBase from "@tiptap/extension-bold";
15
+ import BulletListBase from "@tiptap/extension-bullet-list";
16
+ import CodeBase from "@tiptap/extension-code";
17
+ import CodeBlock$1 from "@tiptap/extension-code-block";
18
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
19
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
20
+ import { fromHtml } from "hast-util-from-html";
21
+ import Prism from "prismjs";
22
+ import HorizontalRule from "@tiptap/extension-horizontal-rule";
23
+ import HardBreakBase from "@tiptap/extension-hard-break";
24
+ import { Heading as Heading$1 } from "@tiptap/extension-heading";
25
+ import ItalicBase from "@tiptap/extension-italic";
26
+ import TiptapLink from "@tiptap/extension-link";
27
+ import ListItemBase from "@tiptap/extension-list-item";
28
+ import OrderedListBase from "@tiptap/extension-ordered-list";
29
+ import ParagraphBase from "@tiptap/extension-paragraph";
30
+ import TipTapPlaceholder from "@tiptap/extension-placeholder";
31
+ import StrikeBase from "@tiptap/extension-strike";
32
+ import SuperscriptBase from "@tiptap/extension-superscript";
33
+ import { Text } from "@tiptap/extension-text";
34
+ import UnderlineBase from "@tiptap/extension-underline";
35
+
36
+ //#region src/core/create-drop-handler.ts
37
+ function createDropHandler({ onPaste, onUploadImage }) {
38
+ return (view, event, _slice, moved) => {
39
+ if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
40
+ event.preventDefault();
41
+ const file = event.dataTransfer.files[0];
42
+ if (onPaste?.(file, view)) return true;
43
+ if (file.type.includes("image/") && onUploadImage) {
44
+ onUploadImage(file, view, (view.posAtCoords({
45
+ left: event.clientX,
46
+ top: event.clientY
47
+ })?.pos || 0) - 1);
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ };
53
+ }
54
+
55
+ //#endregion
56
+ //#region src/utils/paste-sanitizer.ts
57
+ /**
58
+ * Sanitizes pasted HTML.
59
+ * - From editor (has node-* classes): pass through as-is
60
+ * - From external: strip all styles/classes, keep only semantic HTML
61
+ */
62
+ /**
63
+ * Detects content from the Resend editor by checking for node-* class names.
64
+ */
65
+ const EDITOR_CLASS_PATTERN = /class="[^"]*node-/;
66
+ /**
67
+ * Attributes to preserve on specific elements for EXTERNAL content.
68
+ * Only functional attributes - NO style or class.
69
+ */
70
+ const PRESERVED_ATTRIBUTES = {
71
+ a: [
72
+ "href",
73
+ "target",
74
+ "rel"
75
+ ],
76
+ img: [
77
+ "src",
78
+ "alt",
79
+ "width",
80
+ "height"
81
+ ],
82
+ td: ["colspan", "rowspan"],
83
+ th: [
84
+ "colspan",
85
+ "rowspan",
86
+ "scope"
87
+ ],
88
+ table: [
89
+ "border",
90
+ "cellpadding",
91
+ "cellspacing"
92
+ ],
93
+ "*": ["id"]
94
+ };
95
+ function isFromEditor(html) {
96
+ return EDITOR_CLASS_PATTERN.test(html);
97
+ }
98
+ function sanitizePastedHtml(html) {
99
+ if (isFromEditor(html)) return html;
100
+ const doc = new DOMParser().parseFromString(html, "text/html");
101
+ sanitizeNode(doc.body);
102
+ return doc.body.innerHTML;
103
+ }
104
+ function sanitizeNode(node) {
105
+ if (node.nodeType === Node.ELEMENT_NODE) sanitizeElement(node);
106
+ for (const child of Array.from(node.childNodes)) sanitizeNode(child);
107
+ }
108
+ function sanitizeElement(el) {
109
+ const allowedForTag = PRESERVED_ATTRIBUTES[el.tagName.toLowerCase()] || [];
110
+ const allowedGlobal = PRESERVED_ATTRIBUTES["*"] || [];
111
+ const allowed = new Set([...allowedForTag, ...allowedGlobal]);
112
+ const attributesToRemove = [];
113
+ for (const attr of Array.from(el.attributes)) {
114
+ if (attr.name.startsWith("data-")) {
115
+ attributesToRemove.push(attr.name);
116
+ continue;
117
+ }
118
+ if (!allowed.has(attr.name)) attributesToRemove.push(attr.name);
119
+ }
120
+ for (const attr of attributesToRemove) el.removeAttribute(attr);
121
+ }
122
+
123
+ //#endregion
124
+ //#region src/core/create-paste-handler.ts
125
+ function createPasteHandler({ onPaste, onUploadImage, extensions }) {
126
+ return (view, event, slice) => {
127
+ const text = event.clipboardData?.getData("text/plain");
128
+ if (text && onPaste?.(text, view)) {
129
+ event.preventDefault();
130
+ return true;
131
+ }
132
+ if (event.clipboardData?.files?.[0]) {
133
+ const file = event.clipboardData.files[0];
134
+ if (onPaste?.(file, view)) {
135
+ event.preventDefault();
136
+ return true;
137
+ }
138
+ if (file.type.includes("image/") && onUploadImage) {
139
+ const pos = view.state.selection.from;
140
+ onUploadImage(file, view, pos);
141
+ return true;
142
+ }
143
+ }
144
+ /**
145
+ * If the coming content has a single child, we can assume
146
+ * it's a plain text and doesn't need to be parsed and
147
+ * be introduced in a new line
148
+ */
149
+ if (slice.content.childCount === 1) return false;
150
+ if (event.clipboardData?.getData?.("text/html")) {
151
+ event.preventDefault();
152
+ const jsonContent = generateJSON(sanitizePastedHtml(event.clipboardData.getData("text/html")), extensions);
153
+ const node = view.state.schema.nodeFromJSON(jsonContent);
154
+ const transaction = view.state.tr.replaceSelectionWith(node, false);
155
+ view.dispatch(transaction);
156
+ return true;
157
+ }
158
+ return false;
159
+ };
160
+ }
161
+
162
+ //#endregion
163
+ //#region src/core/serializer/default-base-template.tsx
164
+ function DefaultBaseTemplate({ children, previewText }) {
165
+ return /* @__PURE__ */ jsxs(Html, { children: [
166
+ /* @__PURE__ */ jsxs(Head, { children: [
167
+ /* @__PURE__ */ jsx("meta", {
168
+ content: "width=device-width",
169
+ name: "viewport"
170
+ }),
171
+ /* @__PURE__ */ jsx("meta", {
172
+ content: "IE=edge",
173
+ httpEquiv: "X-UA-Compatible"
174
+ }),
175
+ /* @__PURE__ */ jsx("meta", { name: "x-apple-disable-message-reformatting" }),
176
+ /* @__PURE__ */ jsx("meta", {
177
+ content: "telephone=no,address=no,email=no,date=no,url=no",
178
+ name: "format-detection"
179
+ })
180
+ ] }),
181
+ previewText && previewText !== "" && /* @__PURE__ */ jsx(Preview, { children: previewText }),
182
+ /* @__PURE__ */ jsx(Body, { children })
183
+ ] });
184
+ }
185
+
186
+ //#endregion
187
+ //#region src/core/serializer/email-mark.ts
188
+ var EmailMark = class EmailMark extends Mark {
189
+ constructor(config) {
190
+ super(config);
191
+ }
192
+ /**
193
+ * Create a new Mark instance
194
+ * @param config - Mark configuration object or a function that returns a configuration object
195
+ */
196
+ static create(config) {
197
+ return new EmailMark(typeof config === "function" ? config() : config);
198
+ }
199
+ static from(mark, renderToReactEmail) {
200
+ const customMark = EmailMark.create({});
201
+ Object.assign(customMark, { ...mark });
202
+ customMark.config = {
203
+ ...mark.config,
204
+ renderToReactEmail
205
+ };
206
+ return customMark;
207
+ }
208
+ configure(options) {
209
+ return super.configure(options);
210
+ }
211
+ extend(extendedConfig) {
212
+ const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
213
+ return super.extend(resolvedConfig);
214
+ }
215
+ };
216
+
217
+ //#endregion
218
+ //#region src/core/serializer/compose-react-email.tsx
219
+ const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
220
+ const composeReactEmail = async ({ editor, preview }) => {
221
+ const data = editor.getJSON();
222
+ const extensions = editor.extensionManager.extensions;
223
+ const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
224
+ const typeToExtensionMap = Object.fromEntries(extensions.map((extension) => [extension.name, extension]));
225
+ function parseContent(content, depth = 0) {
226
+ if (!content) return;
227
+ return content.map((node, index) => {
228
+ const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
229
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
230
+ if (!node.type) return null;
231
+ const emailNode = typeToExtensionMap[node.type];
232
+ if (!emailNode || !(emailNode instanceof EmailNode)) return null;
233
+ const NodeComponent = emailNode.config.renderToReactEmail;
234
+ const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
235
+ let renderedNode = node.text ? node.text : /* @__PURE__ */ jsx(NodeComponent, {
236
+ node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
237
+ ...node,
238
+ attrs: {
239
+ ...node.attrs,
240
+ width: inlineStyles.width
241
+ }
242
+ } : node,
243
+ style,
244
+ extension: emailNode,
245
+ children: parseContent(node.content, childDepth)
246
+ }, index);
247
+ if (node.marks) for (const mark of node.marks) {
248
+ const emailMark = typeToExtensionMap[mark.type];
249
+ if (emailMark instanceof EmailMark) {
250
+ const MarkComponent = emailMark.config.renderToReactEmail;
251
+ renderedNode = /* @__PURE__ */ jsx(MarkComponent, {
252
+ mark,
253
+ node,
254
+ style: serializerPlugin?.getNodeStyles({
255
+ type: mark.type,
256
+ attrs: mark.attrs ?? {}
257
+ }, depth, editor) ?? {},
258
+ extension: emailMark,
259
+ children: renderedNode
260
+ });
261
+ }
262
+ }
263
+ return renderedNode;
264
+ });
265
+ }
266
+ const unformattedHtml = await render(/* @__PURE__ */ jsx(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
267
+ previewText: preview,
268
+ editor,
269
+ children: parseContent(data.content)
270
+ }));
271
+ const [prettyHtml, text] = await Promise.all([pretty(unformattedHtml), toPlainText(unformattedHtml)]);
272
+ return {
273
+ html: prettyHtml,
274
+ text
275
+ };
276
+ };
277
+
278
+ //#endregion
279
+ //#region src/extensions/alignment-attribute.tsx
280
+ const AlignmentAttribute = Extension.create({
281
+ name: "alignmentAttribute",
282
+ addOptions() {
283
+ return {
284
+ types: [],
285
+ alignments: [
286
+ "left",
287
+ "center",
288
+ "right",
289
+ "justify"
290
+ ]
291
+ };
292
+ },
293
+ addGlobalAttributes() {
294
+ return [{
295
+ types: this.options.types,
296
+ attributes: { alignment: {
297
+ parseHTML: (element) => {
298
+ const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
299
+ if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
300
+ return null;
301
+ },
302
+ renderHTML: (attributes) => {
303
+ if (attributes.alignment === "left") return {};
304
+ return { alignment: attributes.alignment };
305
+ }
306
+ } }
307
+ }];
308
+ },
309
+ addCommands() {
310
+ return { setAlignment: (alignment) => ({ commands }) => {
311
+ if (!this.options.alignments.includes(alignment)) return false;
312
+ return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
313
+ } };
314
+ },
315
+ addKeyboardShortcuts() {
316
+ return {
317
+ Enter: () => {
318
+ const { from } = this.editor.state.selection;
319
+ const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
320
+ if (currentAlignment) requestAnimationFrame(() => {
321
+ this.editor.commands.setAlignment(currentAlignment);
322
+ });
323
+ return false;
324
+ },
325
+ "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
326
+ "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
327
+ "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
328
+ "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
329
+ };
330
+ }
331
+ });
332
+
333
+ //#endregion
334
+ //#region src/utils/get-text-alignment.ts
335
+ function getTextAlignment(alignment) {
336
+ switch (alignment) {
337
+ case "left": return { textAlign: "left" };
338
+ case "center": return { textAlign: "center" };
339
+ case "right": return { textAlign: "right" };
340
+ default: return {};
341
+ }
342
+ }
343
+
344
+ //#endregion
345
+ //#region src/extensions/blockquote.tsx
346
+ const Blockquote = EmailNode.from(BlockquoteBase, ({ children, node, style }) => /* @__PURE__ */ jsx("blockquote", {
347
+ className: node.attrs?.class || void 0,
348
+ style: {
349
+ ...style,
350
+ ...inlineCssToJs(node.attrs?.style),
351
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
352
+ },
353
+ children
354
+ }));
355
+
356
+ //#endregion
357
+ //#region src/extensions/body.tsx
358
+ const Body$1 = EmailNode.create({
359
+ name: "body",
360
+ group: "block",
361
+ content: "block+",
362
+ defining: true,
363
+ isolating: true,
364
+ addAttributes() {
365
+ return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
366
+ },
367
+ parseHTML() {
368
+ return [{
369
+ tag: "body",
370
+ getAttrs: (node) => {
371
+ if (typeof node === "string") return false;
372
+ const element = node;
373
+ const attrs = {};
374
+ Array.from(element.attributes).forEach((attr) => {
375
+ attrs[attr.name] = attr.value;
376
+ });
377
+ return attrs;
378
+ }
379
+ }];
380
+ },
381
+ renderHTML({ HTMLAttributes }) {
382
+ return [
383
+ "div",
384
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
385
+ 0
386
+ ];
387
+ },
388
+ renderToReactEmail({ children, node, style }) {
389
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
390
+ return /* @__PURE__ */ jsx("div", {
391
+ className: node.attrs?.class || void 0,
392
+ style: {
393
+ ...style,
394
+ ...inlineStyles
395
+ },
396
+ children
397
+ });
398
+ }
399
+ });
400
+
401
+ //#endregion
402
+ //#region src/extensions/bold.tsx
403
+ const BoldWithoutFontWeightInference = BoldBase.extend({ parseHTML() {
404
+ return [
405
+ { tag: "strong" },
406
+ {
407
+ tag: "b",
408
+ getAttrs: (node) => node.style.fontWeight !== "normal" && null
409
+ },
410
+ {
411
+ style: "font-weight=400",
412
+ clearMark: (mark) => mark.type.name === this.name
413
+ }
414
+ ];
415
+ } });
416
+ const Bold = EmailMark.from(BoldWithoutFontWeightInference, ({ children, style }) => /* @__PURE__ */ jsx("strong", {
417
+ style,
418
+ children
419
+ }));
420
+
421
+ //#endregion
422
+ //#region src/extensions/bullet-list.tsx
423
+ const BulletList = EmailNode.from(BulletListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ul", {
424
+ className: node.attrs?.class || void 0,
425
+ style: {
426
+ ...style,
427
+ ...inlineCssToJs(node.attrs?.style)
428
+ },
429
+ children
430
+ }));
431
+
432
+ //#endregion
433
+ //#region src/extensions/button.tsx
434
+ const Button$1 = EmailNode.create({
435
+ name: "button",
436
+ group: "block",
437
+ content: "inline*",
438
+ defining: true,
439
+ draggable: true,
440
+ marks: "bold",
441
+ addAttributes() {
442
+ return {
443
+ class: { default: "button" },
444
+ href: { default: "#" },
445
+ alignment: { default: "left" }
446
+ };
447
+ },
448
+ parseHTML() {
449
+ return [{
450
+ tag: "a[data-id=\"react-email-button\"]",
451
+ getAttrs: (node) => {
452
+ if (typeof node === "string") return false;
453
+ const element = node;
454
+ const attrs = {};
455
+ Array.from(element.attributes).forEach((attr) => {
456
+ attrs[attr.name] = attr.value;
457
+ });
458
+ return attrs;
459
+ }
460
+ }];
461
+ },
462
+ renderHTML({ HTMLAttributes }) {
463
+ return [
464
+ "div",
465
+ mergeAttributes({ class: `align-${HTMLAttributes?.alignment}` }),
466
+ [
467
+ "a",
468
+ mergeAttributes({
469
+ class: `node-button ${HTMLAttributes?.class}`,
470
+ style: HTMLAttributes?.style,
471
+ "data-id": "react-email-button",
472
+ "data-href": HTMLAttributes?.href
473
+ }),
474
+ 0
475
+ ]
476
+ ];
477
+ },
478
+ addCommands() {
479
+ return {
480
+ updateButton: (attributes) => ({ commands }) => {
481
+ return commands.updateAttributes("button", attributes);
482
+ },
483
+ setButton: () => ({ commands }) => {
484
+ return commands.insertContent({
485
+ type: "button",
486
+ content: [{
487
+ type: "text",
488
+ text: "Button"
489
+ }]
490
+ });
491
+ }
492
+ };
493
+ },
494
+ renderToReactEmail({ children, node, style }) {
495
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
496
+ return /* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Column, {
497
+ align: node.attrs?.align || node.attrs?.alignment,
498
+ children: /* @__PURE__ */ jsx(Button, {
499
+ className: node.attrs?.class || void 0,
500
+ href: node.attrs?.href,
501
+ style: {
502
+ ...style,
503
+ ...inlineStyles
504
+ },
505
+ children
506
+ })
507
+ }) });
508
+ }
509
+ });
510
+
511
+ //#endregion
512
+ //#region src/extensions/class-attribute.tsx
513
+ const ClassAttribute = Extension.create({
514
+ name: "classAttribute",
515
+ addOptions() {
516
+ return {
517
+ types: [],
518
+ class: []
519
+ };
520
+ },
521
+ addGlobalAttributes() {
522
+ return [{
523
+ types: this.options.types,
524
+ attributes: { class: {
525
+ default: "",
526
+ parseHTML: (element) => element.className || "",
527
+ renderHTML: (attributes) => {
528
+ return attributes.class ? { class: attributes.class } : {};
529
+ }
530
+ } }
531
+ }];
532
+ },
533
+ addCommands() {
534
+ return {
535
+ unsetClass: () => ({ commands }) => {
536
+ return this.options.types.every((type) => commands.resetAttributes(type, "class"));
537
+ },
538
+ setClass: (classList) => ({ commands }) => {
539
+ return this.options.types.every((type) => commands.updateAttributes(type, { class: classList }));
540
+ }
541
+ };
542
+ },
543
+ addKeyboardShortcuts() {
544
+ return { Enter: ({ editor }) => {
545
+ requestAnimationFrame(() => {
546
+ editor.commands.resetAttributes("paragraph", "class");
547
+ });
548
+ return false;
549
+ } };
550
+ }
551
+ });
552
+
553
+ //#endregion
554
+ //#region src/extensions/code.tsx
555
+ const Code = EmailMark.from(CodeBase, ({ children, node, style }) => /* @__PURE__ */ jsx("code", {
556
+ style: {
557
+ ...style,
558
+ ...inlineCssToJs(node.attrs?.style)
559
+ },
560
+ children
561
+ }));
562
+
563
+ //#endregion
564
+ //#region src/utils/prism-utils.ts
565
+ const publicURL = "/styles/prism";
566
+ function loadPrismTheme(theme) {
567
+ const link = document.createElement("link");
568
+ link.rel = "stylesheet";
569
+ link.href = `${publicURL}/prism-${theme}.css`;
570
+ link.setAttribute("data-prism-theme", "");
571
+ document.head.appendChild(link);
572
+ }
573
+ function removePrismTheme() {
574
+ const existingTheme = document.querySelectorAll("link[rel=\"stylesheet\"][data-prism-theme]");
575
+ if (existingTheme.length > 0) existingTheme.forEach((cssLinkTag) => {
576
+ cssLinkTag.remove();
577
+ });
578
+ }
579
+ function hasPrismThemeLoaded(theme) {
580
+ return !!document.querySelector(`link[rel="stylesheet"][data-prism-theme][href="${publicURL}/prism-${theme}.css"]`);
581
+ }
582
+
583
+ //#endregion
584
+ //#region src/extensions/prism-plugin.ts
585
+ const PRISM_LANGUAGE_LOADED_META = "prismLanguageLoaded";
586
+ function parseNodes(nodes, className = []) {
587
+ return nodes.flatMap((node) => {
588
+ const classes = [...className, ...node.properties ? node.properties.className : []];
589
+ if (node.children) return parseNodes(node.children, classes);
590
+ return {
591
+ text: node.value ?? "",
592
+ classes
593
+ };
594
+ });
595
+ }
596
+ function getHighlightNodes(html) {
597
+ return fromHtml(html, { fragment: true }).children;
598
+ }
599
+ function registeredLang(aliasOrLanguage) {
600
+ const allSupportLang = Object.keys(Prism.languages).filter((id) => typeof Prism.languages[id] === "object");
601
+ return Boolean(allSupportLang.find((x) => x === aliasOrLanguage));
602
+ }
603
+ function getDecorations({ doc, name, defaultLanguage, defaultTheme, loadingLanguages, onLanguageLoaded }) {
604
+ const decorations = [];
605
+ findChildren(doc, (node) => node.type.name === name).forEach((block) => {
606
+ let from = block.pos + 1;
607
+ const language = block.node.attrs.language || defaultLanguage;
608
+ const theme = block.node.attrs.theme || defaultTheme;
609
+ let html = "";
610
+ try {
611
+ if (!registeredLang(language) && !loadingLanguages.has(language)) {
612
+ loadingLanguages.add(language);
613
+ import(`prismjs/components/prism-${language}`).then(() => {
614
+ loadingLanguages.delete(language);
615
+ onLanguageLoaded(language);
616
+ }).catch(() => {
617
+ loadingLanguages.delete(language);
618
+ });
619
+ }
620
+ if (!hasPrismThemeLoaded(theme)) loadPrismTheme(theme);
621
+ html = Prism.highlight(block.node.textContent, Prism.languages[language], language);
622
+ } catch {
623
+ html = Prism.highlight(block.node.textContent, Prism.languages.javascript, "js");
624
+ }
625
+ parseNodes(getHighlightNodes(html)).forEach((node) => {
626
+ const to = from + node.text.length;
627
+ if (node.classes.length) {
628
+ const decoration = Decoration.inline(from, to, { class: node.classes.join(" ") });
629
+ decorations.push(decoration);
630
+ }
631
+ from = to;
632
+ });
633
+ });
634
+ return DecorationSet.create(doc, decorations);
635
+ }
636
+ function PrismPlugin({ name, defaultLanguage, defaultTheme }) {
637
+ if (!defaultLanguage) throw Error("You must specify the defaultLanguage parameter");
638
+ const loadingLanguages = /* @__PURE__ */ new Set();
639
+ let pluginView = null;
640
+ const onLanguageLoaded = (language) => {
641
+ if (pluginView) pluginView.dispatch(pluginView.state.tr.setMeta(PRISM_LANGUAGE_LOADED_META, language));
642
+ };
643
+ const prismjsPlugin = new Plugin({
644
+ key: new PluginKey("prism"),
645
+ view(view) {
646
+ pluginView = view;
647
+ return { destroy() {
648
+ pluginView = null;
649
+ } };
650
+ },
651
+ state: {
652
+ init: (_, { doc }) => {
653
+ return getDecorations({
654
+ doc,
655
+ name,
656
+ defaultLanguage,
657
+ defaultTheme,
658
+ loadingLanguages,
659
+ onLanguageLoaded
660
+ });
661
+ },
662
+ apply: (transaction, decorationSet, oldState, newState) => {
663
+ const oldNodeName = oldState.selection.$head.parent.type.name;
664
+ const newNodeName = newState.selection.$head.parent.type.name;
665
+ const oldNodes = findChildren(oldState.doc, (node) => node.type.name === name);
666
+ const newNodes = findChildren(newState.doc, (node) => node.type.name === name);
667
+ if (transaction.getMeta(PRISM_LANGUAGE_LOADED_META) || transaction.docChanged && ([oldNodeName, newNodeName].includes(name) || newNodes.length !== oldNodes.length || transaction.steps.some((step) => {
668
+ const rangeStep = step;
669
+ return rangeStep.from !== void 0 && rangeStep.to !== void 0 && oldNodes.some((node) => {
670
+ return node.pos >= rangeStep.from && node.pos + node.node.nodeSize <= rangeStep.to;
671
+ });
672
+ }))) return getDecorations({
673
+ doc: transaction.doc,
674
+ name,
675
+ defaultLanguage,
676
+ defaultTheme,
677
+ loadingLanguages,
678
+ onLanguageLoaded
679
+ });
680
+ return decorationSet.map(transaction.mapping, transaction.doc);
681
+ }
682
+ },
683
+ props: { decorations(state) {
684
+ return prismjsPlugin.getState(state);
685
+ } },
686
+ destroy() {
687
+ pluginView = null;
688
+ removePrismTheme();
689
+ }
690
+ });
691
+ return prismjsPlugin;
692
+ }
693
+
694
+ //#endregion
695
+ //#region src/extensions/code-block.tsx
696
+ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
697
+ addOptions() {
698
+ return {
699
+ languageClassPrefix: "language-",
700
+ exitOnTripleEnter: false,
701
+ exitOnArrowDown: false,
702
+ enableTabIndentation: true,
703
+ tabSize: 2,
704
+ defaultLanguage: "javascript",
705
+ defaultTheme: "default",
706
+ HTMLAttributes: {}
707
+ };
708
+ },
709
+ addAttributes() {
710
+ return {
711
+ ...this.parent?.(),
712
+ language: {
713
+ default: this.options.defaultLanguage,
714
+ parseHTML: (element) => {
715
+ if (!element) return null;
716
+ const { languageClassPrefix } = this.options;
717
+ if (!languageClassPrefix) return null;
718
+ const language = [...element.firstElementChild?.classList || []].filter((className) => className.startsWith(languageClassPrefix || "")).map((className) => className.replace(languageClassPrefix, ""))[0];
719
+ if (!language) return null;
720
+ return language;
721
+ },
722
+ rendered: false
723
+ },
724
+ theme: {
725
+ default: this.options.defaultTheme,
726
+ rendered: false
727
+ }
728
+ };
729
+ },
730
+ renderHTML({ node, HTMLAttributes }) {
731
+ return [
732
+ "pre",
733
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { class: node.attrs.language ? `${this.options.languageClassPrefix}${node.attrs.language}` : null }, { "data-theme": node.attrs.theme }),
734
+ [
735
+ "code",
736
+ { class: node.attrs.language ? `${this.options.languageClassPrefix}${node.attrs.language} node-codeTag` : "node-codeTag" },
737
+ 0
738
+ ]
739
+ ];
740
+ },
741
+ addKeyboardShortcuts() {
742
+ return {
743
+ ...this.parent?.(),
744
+ "Mod-a": ({ editor }) => {
745
+ const { state } = editor;
746
+ const { selection } = state;
747
+ const { $from } = selection;
748
+ for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
749
+ const blockStart = $from.start(depth);
750
+ const blockEnd = $from.end(depth);
751
+ if (selection.from === blockStart && selection.to === blockEnd) return false;
752
+ const tr = state.tr.setSelection(TextSelection.create(state.doc, blockStart, blockEnd));
753
+ editor.view.dispatch(tr);
754
+ return true;
755
+ }
756
+ return false;
757
+ }
758
+ };
759
+ },
760
+ addProseMirrorPlugins() {
761
+ return [...this.parent?.() || [], PrismPlugin({
762
+ name: this.name,
763
+ defaultLanguage: this.options.defaultLanguage,
764
+ defaultTheme: this.options.defaultTheme
765
+ })];
766
+ }
767
+ }), ({ node, style }) => {
768
+ const language = node.attrs?.language ? `${node.attrs.language}` : "javascript";
769
+ const userTheme = ReactEmailComponents[node.attrs?.theme];
770
+ const theme = userTheme ? {
771
+ ...userTheme,
772
+ base: {
773
+ ...userTheme.base,
774
+ borderRadius: "0.125rem",
775
+ padding: "0.75rem 1rem"
776
+ }
777
+ } : { base: {
778
+ color: "#1e293b",
779
+ background: "#f1f5f9",
780
+ lineHeight: "1.5",
781
+ fontFamily: "\"Fira Code\", \"Fira Mono\", Menlo, Consolas, \"DejaVu Sans Mono\", monospace",
782
+ padding: "0.75rem 1rem",
783
+ borderRadius: "0.125rem"
784
+ } };
785
+ return /* @__PURE__ */ jsx(CodeBlock, {
786
+ code: node.content?.[0]?.text ?? "",
787
+ language,
788
+ theme,
789
+ style: {
790
+ width: "auto",
791
+ ...style
792
+ }
793
+ });
794
+ });
795
+
796
+ //#endregion
797
+ //#region src/utils/is-collaboration.ts
798
+ const COLLABORATION_EXTENSION_NAMES = new Set(["liveblocksExtension", "collaboration"]);
799
+ function hasCollaborationExtension(exts) {
800
+ return exts.some((ext) => COLLABORATION_EXTENSION_NAMES.has(ext.name));
801
+ }
802
+
803
+ //#endregion
804
+ //#region src/extensions/container.tsx
805
+ function hasContainerNode(doc) {
806
+ let found = false;
807
+ doc.forEach((node) => {
808
+ if (node.type.name === "container") found = true;
809
+ });
810
+ return found;
811
+ }
812
+ function wrapInContainer(state) {
813
+ const { doc } = state;
814
+ const containerType = state.schema.nodes.container;
815
+ const contentNodes = [];
816
+ const globalContentNodes = [];
817
+ doc.forEach((node) => {
818
+ if (node.type.name === "globalContent") globalContentNodes.push(node);
819
+ else contentNodes.push(node);
820
+ });
821
+ const containerContent = contentNodes.length > 0 ? contentNodes : [state.schema.nodes.paragraph.create()];
822
+ const containerNode = containerType.create(null, containerContent);
823
+ const newDocContent = [...globalContentNodes, containerNode];
824
+ const tr = state.tr;
825
+ tr.replaceWith(0, doc.content.size, newDocContent);
826
+ tr.setMeta("addToHistory", false);
827
+ return tr;
828
+ }
829
+ const Container$1 = EmailNode.create({
830
+ name: "container",
831
+ group: "block",
832
+ content: "block+",
833
+ defining: true,
834
+ isolating: true,
835
+ selectable: false,
836
+ draggable: false,
837
+ addOptions() {
838
+ return { HTMLAttributes: {} };
839
+ },
840
+ parseHTML() {
841
+ return [{ tag: "div[data-type=\"container\"]" }, {
842
+ tag: "table[role=\"presentation\"]",
843
+ priority: 60,
844
+ getAttrs: (node) => {
845
+ if (typeof node === "string") return false;
846
+ const table = node;
847
+ if (!table.style.maxWidth) return false;
848
+ if (!table.querySelector(":scope > tbody > tr:only-child > td:only-child")) return false;
849
+ return null;
850
+ },
851
+ contentElement: (node) => node.querySelector(":scope > tbody > tr > td")
852
+ }];
853
+ },
854
+ renderHTML({ HTMLAttributes }) {
855
+ return [
856
+ "div",
857
+ mergeAttributes({
858
+ "data-type": "container",
859
+ class: "node-container"
860
+ }, this.options.HTMLAttributes, HTMLAttributes),
861
+ 0
862
+ ];
863
+ },
864
+ addProseMirrorPlugins() {
865
+ const isCollaborative = hasCollaborationExtension(this.editor.extensionManager.extensions);
866
+ return [new Plugin({
867
+ key: new PluginKey("containerEnforcer"),
868
+ view: isCollaborative ? void 0 : (editorView) => {
869
+ if (!hasContainerNode(editorView.state.doc)) {
870
+ const tr = wrapInContainer(editorView.state);
871
+ editorView.dispatch(tr);
872
+ }
873
+ return {};
874
+ },
875
+ appendTransaction(_transactions, oldState, newState) {
876
+ if (hasContainerNode(newState.doc)) return null;
877
+ if (newState.doc.eq(oldState.doc)) return null;
878
+ return wrapInContainer(newState);
879
+ }
880
+ })];
881
+ },
882
+ renderToReactEmail({ children, node, style }) {
883
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
884
+ return /* @__PURE__ */ jsx(Container, {
885
+ className: node.attrs?.class || void 0,
886
+ style: {
887
+ ...style,
888
+ ...inlineStyles,
889
+ width: "100%",
890
+ maxWidth: style?.width ?? style?.maxWidth
891
+ },
892
+ children
893
+ });
894
+ }
895
+ });
896
+
897
+ //#endregion
898
+ //#region src/extensions/div.tsx
899
+ const Div = EmailNode.create({
900
+ name: "div",
901
+ group: "block",
902
+ content: "block+",
903
+ defining: true,
904
+ isolating: true,
905
+ parseHTML() {
906
+ return [{
907
+ tag: "div:not([data-type])",
908
+ getAttrs: (node) => {
909
+ if (typeof node === "string") return false;
910
+ const element = node;
911
+ const attrs = {};
912
+ Array.from(element.attributes).forEach((attr) => {
913
+ attrs[attr.name] = attr.value;
914
+ });
915
+ return attrs;
916
+ }
917
+ }];
918
+ },
919
+ renderHTML({ HTMLAttributes }) {
920
+ return [
921
+ "div",
922
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
923
+ 0
924
+ ];
925
+ },
926
+ addAttributes() {
927
+ return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
928
+ },
929
+ renderToReactEmail({ children, node, style }) {
930
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
931
+ return /* @__PURE__ */ jsx("div", {
932
+ className: node.attrs?.class || void 0,
933
+ style: {
934
+ ...style,
935
+ ...inlineStyles
936
+ },
937
+ children
938
+ });
939
+ }
940
+ });
941
+
942
+ //#endregion
943
+ //#region src/core/is-document-visually-empty.ts
944
+ function isDocumentVisuallyEmpty(doc) {
945
+ let nonGlobalNodeCount = 0;
946
+ let firstNonGlobalNode = null;
947
+ for (let index = 0; index < doc.childCount; index += 1) {
948
+ const node = doc.child(index);
949
+ if (node.type.name === "globalContent") continue;
950
+ nonGlobalNodeCount += 1;
951
+ if (firstNonGlobalNode === null) firstNonGlobalNode = node;
952
+ }
953
+ if (nonGlobalNodeCount === 0) return true;
954
+ if (nonGlobalNodeCount !== 1) return false;
955
+ if (firstNonGlobalNode.type.name === "container") return hasOnlyEmptyParagraph(firstNonGlobalNode);
956
+ return isEmptyParagraph(firstNonGlobalNode);
957
+ }
958
+ function hasOnlyEmptyParagraph(node) {
959
+ if (node.childCount === 0) return true;
960
+ if (node.childCount !== 1) return false;
961
+ return isEmptyParagraph(node.child(0));
962
+ }
963
+ function isEmptyParagraph(node) {
964
+ return node.type.name === "paragraph" && node.textContent.length === 0;
965
+ }
966
+
967
+ //#endregion
968
+ //#region src/core/use-editor.ts
969
+ function useEditor$1({ content, extensions = [], onUpdate, onPaste, onUploadImage, onReady, editable = true, ...rest }) {
970
+ const [contentError, setContentError] = React.useState(null);
971
+ const isCollaborative = hasCollaborationExtension(extensions);
972
+ const effectiveExtensions = React.useMemo(() => [
973
+ StarterKit,
974
+ ...isCollaborative ? [] : [UndoRedo],
975
+ ...extensions
976
+ ], [extensions, isCollaborative]);
977
+ const editor = useEditor({
978
+ content: isCollaborative ? void 0 : content,
979
+ extensions: effectiveExtensions,
980
+ editable,
981
+ immediatelyRender: false,
982
+ enableContentCheck: true,
983
+ onContentError({ editor: editor$1, error, disableCollaboration }) {
984
+ disableCollaboration();
985
+ setContentError(error);
986
+ console.error(error);
987
+ editor$1.setEditable(false);
988
+ },
989
+ onCreate({ editor: editor$1 }) {
990
+ onReady?.(editor$1);
991
+ },
992
+ onUpdate({ editor: editor$1, transaction }) {
993
+ onUpdate?.(editor$1, transaction);
994
+ },
995
+ editorProps: {
996
+ handleDOMEvents: { click: (view, event) => {
997
+ if (!view.editable) {
998
+ if (event.target.closest("a")) {
999
+ event.preventDefault();
1000
+ return true;
1001
+ }
1002
+ }
1003
+ return false;
1004
+ } },
1005
+ handlePaste: createPasteHandler({
1006
+ onPaste,
1007
+ onUploadImage,
1008
+ extensions: effectiveExtensions
1009
+ }),
1010
+ handleDrop: createDropHandler({
1011
+ onPaste,
1012
+ onUploadImage
1013
+ })
1014
+ },
1015
+ ...rest
1016
+ });
1017
+ return {
1018
+ editor,
1019
+ isEditorEmpty: useEditorState({
1020
+ editor,
1021
+ selector: (context) => {
1022
+ if (!context.editor) return true;
1023
+ return isDocumentVisuallyEmpty(context.editor.state.doc);
1024
+ }
1025
+ }) ?? true,
1026
+ extensions: effectiveExtensions,
1027
+ contentError,
1028
+ isCollaborative
1029
+ };
1030
+ }
1031
+
1032
+ //#endregion
1033
+ //#region src/extensions/divider.tsx
1034
+ const Divider = EmailNode.from(HorizontalRule.extend({
1035
+ addAttributes() {
1036
+ return { class: { default: "divider" } };
1037
+ },
1038
+ addInputRules() {
1039
+ return [new InputRule({
1040
+ find: /^(?:---|—-|___\s|\*\*\*\s)$/,
1041
+ handler: ({ state, range }) => {
1042
+ const attributes = {};
1043
+ const { tr } = state;
1044
+ const start = range.from;
1045
+ const end = range.to;
1046
+ tr.insert(start - 1, this.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
1047
+ }
1048
+ })];
1049
+ },
1050
+ addNodeView() {
1051
+ return ReactNodeViewRenderer((props) => {
1052
+ const node = props.node;
1053
+ const { class: className, ...rest } = node.attrs;
1054
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(Hr, {
1055
+ ...rest,
1056
+ className: "node-hr",
1057
+ style: inlineCssToJs(node.attrs.style)
1058
+ }) });
1059
+ });
1060
+ }
1061
+ }), ({ node, style }) => {
1062
+ return /* @__PURE__ */ jsx(Hr, {
1063
+ className: node.attrs?.class || void 0,
1064
+ style: {
1065
+ ...style,
1066
+ ...inlineCssToJs(node.attrs?.style)
1067
+ }
1068
+ });
1069
+ });
1070
+
1071
+ //#endregion
1072
+ //#region src/extensions/hard-break.tsx
1073
+ const HardBreak = EmailNode.from(HardBreakBase, () => /* @__PURE__ */ jsx("br", {}));
1074
+
1075
+ //#endregion
1076
+ //#region src/extensions/heading.tsx
1077
+ const Heading$2 = EmailNode.from(Heading$1.extend({ addNodeView() {
1078
+ return ReactNodeViewRenderer(({ node }) => {
1079
+ const level = node.attrs.level ?? 1;
1080
+ const { class: className, ...rest } = node.attrs;
1081
+ const attrs = {
1082
+ ...rest,
1083
+ className: `node-h${level} ${className}`,
1084
+ style: inlineCssToJs(node.attrs.style)
1085
+ };
1086
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(Heading, {
1087
+ as: `h${level}`,
1088
+ ...attrs,
1089
+ children: /* @__PURE__ */ jsx(NodeViewContent, {})
1090
+ }) });
1091
+ });
1092
+ } }), ({ children, node, style }) => {
1093
+ return /* @__PURE__ */ jsx(Heading, {
1094
+ as: `h${node.attrs?.level ?? 1}`,
1095
+ className: node.attrs?.class || void 0,
1096
+ style: {
1097
+ ...style,
1098
+ ...inlineCssToJs(node.attrs?.style),
1099
+ ...getTextAlignment(node.attrs?.align ?? node.attrs?.alignment)
1100
+ },
1101
+ children
1102
+ });
1103
+ });
1104
+
1105
+ //#endregion
1106
+ //#region src/extensions/italic.tsx
1107
+ const Italic = EmailMark.from(ItalicBase, ({ children, style }) => /* @__PURE__ */ jsx("em", {
1108
+ style,
1109
+ children
1110
+ }));
1111
+
1112
+ //#endregion
1113
+ //#region src/extensions/preserved-style.tsx
1114
+ const PreservedStyle = EmailMark.create({
1115
+ name: "preservedStyle",
1116
+ addAttributes() {
1117
+ return { style: {
1118
+ default: null,
1119
+ parseHTML: (element) => element.getAttribute("style"),
1120
+ renderHTML: (attributes) => {
1121
+ if (!attributes.style) return {};
1122
+ return { style: attributes.style };
1123
+ }
1124
+ } };
1125
+ },
1126
+ parseHTML() {
1127
+ return [{
1128
+ tag: "span[style]",
1129
+ getAttrs: (element) => {
1130
+ if (typeof element === "string") return false;
1131
+ const style = element.getAttribute("style");
1132
+ if (style && hasPreservableStyles(style)) return { style };
1133
+ return false;
1134
+ }
1135
+ }];
1136
+ },
1137
+ renderHTML({ HTMLAttributes }) {
1138
+ return [
1139
+ "span",
1140
+ mergeAttributes(HTMLAttributes),
1141
+ 0
1142
+ ];
1143
+ },
1144
+ renderToReactEmail({ children, mark }) {
1145
+ return /* @__PURE__ */ jsx("span", {
1146
+ style: mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : void 0,
1147
+ children
1148
+ });
1149
+ }
1150
+ });
1151
+ const LINK_INDICATOR_STYLES = [
1152
+ "color",
1153
+ "text-decoration",
1154
+ "text-decoration-line",
1155
+ "text-decoration-color",
1156
+ "text-decoration-style"
1157
+ ];
1158
+ function parseStyleString(styleString) {
1159
+ const temp = document.createElement("div");
1160
+ temp.style.cssText = styleString;
1161
+ return temp.style;
1162
+ }
1163
+ function hasBackground(style) {
1164
+ const bgColor = style.backgroundColor;
1165
+ const bg = style.background;
1166
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1167
+ if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1168
+ return false;
1169
+ }
1170
+ function hasPreservableStyles(styleString) {
1171
+ return processStylesForUnlink(styleString) !== null;
1172
+ }
1173
+ /**
1174
+ * Processes styles when unlinking:
1175
+ * - Has background (button-like): preserve all styles
1176
+ * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1177
+ */
1178
+ function processStylesForUnlink(styleString) {
1179
+ if (!styleString) return null;
1180
+ const style = parseStyleString(styleString);
1181
+ if (hasBackground(style)) return styleString;
1182
+ const filtered = [];
1183
+ for (let i = 0; i < style.length; i++) {
1184
+ const prop = style[i];
1185
+ if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1186
+ const value = style.getPropertyValue(prop);
1187
+ if (value) filtered.push(`${prop}: ${value}`);
1188
+ }
1189
+ return filtered.length > 0 ? filtered.join("; ") : null;
1190
+ }
1191
+
1192
+ //#endregion
1193
+ //#region src/extensions/link.tsx
1194
+ const Link$1 = EmailMark.from(TiptapLink, ({ children, mark, style }) => {
1195
+ const linkMarkStyle = mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : {};
1196
+ return /* @__PURE__ */ jsx(Link, {
1197
+ href: mark.attrs?.href ?? void 0,
1198
+ rel: mark.attrs?.rel ?? void 0,
1199
+ style: {
1200
+ ...style,
1201
+ ...linkMarkStyle
1202
+ },
1203
+ target: mark.attrs?.target ?? void 0,
1204
+ ...mark.attrs?.["ses:no-track"] ? { "ses:no-track": mark.attrs["ses:no-track"] } : {},
1205
+ children
1206
+ });
1207
+ }).extend({
1208
+ parseHTML() {
1209
+ return [{
1210
+ tag: "a[target]:not([data-id=\"react-email-button\"])",
1211
+ getAttrs: (node) => {
1212
+ if (typeof node === "string") return false;
1213
+ const element = node;
1214
+ const attrs = {};
1215
+ Array.from(element.attributes).forEach((attr) => {
1216
+ attrs[attr.name] = attr.value;
1217
+ });
1218
+ return attrs;
1219
+ }
1220
+ }, {
1221
+ tag: "a[href]:not([data-id=\"react-email-button\"])",
1222
+ getAttrs: (node) => {
1223
+ if (typeof node === "string") return false;
1224
+ const element = node;
1225
+ const attrs = {};
1226
+ Array.from(element.attributes).forEach((attr) => {
1227
+ attrs[attr.name] = attr.value;
1228
+ });
1229
+ return attrs;
1230
+ }
1231
+ }];
1232
+ },
1233
+ addAttributes() {
1234
+ return {
1235
+ ...this.parent?.(),
1236
+ "ses:no-track": {
1237
+ default: null,
1238
+ parseHTML: (element) => element.getAttribute("ses:no-track")
1239
+ }
1240
+ };
1241
+ },
1242
+ addCommands() {
1243
+ return {
1244
+ ...this.parent?.(),
1245
+ unsetLink: () => ({ state, chain }) => {
1246
+ const { from } = state.selection;
1247
+ const linkStyle = state.doc.resolve(from).marks().find((m) => m.type.name === "link")?.attrs?.style ?? null;
1248
+ const preservedStyle = processStylesForUnlink(linkStyle);
1249
+ const shouldRemoveUnderline = preservedStyle !== linkStyle;
1250
+ if (preservedStyle) {
1251
+ const cmd = chain().extendMarkRange("link").unsetMark("link").setMark("preservedStyle", { style: preservedStyle });
1252
+ return shouldRemoveUnderline ? cmd.unsetMark("underline").run() : cmd.run();
1253
+ }
1254
+ return chain().extendMarkRange("link").unsetMark("link").unsetMark("underline").run();
1255
+ }
1256
+ };
1257
+ },
1258
+ addKeyboardShortcuts() {
1259
+ return { "Mod-k": () => {
1260
+ editorEventBus.dispatch("bubble-menu:add-link", void 0);
1261
+ return this.editor.chain().focus().toggleLink({ href: "" }).run();
1262
+ } };
1263
+ }
1264
+ });
1265
+
1266
+ //#endregion
1267
+ //#region src/extensions/list-item.tsx
1268
+ const ListItem = EmailNode.from(ListItemBase, ({ children, node, style }) => /* @__PURE__ */ jsx("li", {
1269
+ className: node.attrs?.class || void 0,
1270
+ style: {
1271
+ ...style,
1272
+ ...inlineCssToJs(node.attrs?.style),
1273
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1274
+ },
1275
+ children
1276
+ }));
1277
+
1278
+ //#endregion
1279
+ //#region src/extensions/max-nesting.ts
1280
+ const MaxNesting = Extension.create({
1281
+ name: "maxNesting",
1282
+ addOptions() {
1283
+ return {
1284
+ maxDepth: 3,
1285
+ nodeTypes: void 0
1286
+ };
1287
+ },
1288
+ addProseMirrorPlugins() {
1289
+ const { maxDepth, nodeTypes } = this.options;
1290
+ if (typeof maxDepth !== "number" || maxDepth < 1) throw new Error("maxDepth must be a positive number");
1291
+ return [new Plugin({
1292
+ key: new PluginKey("maxNesting"),
1293
+ appendTransaction(transactions, _oldState, newState) {
1294
+ if (!transactions.some((tr$1) => tr$1.docChanged)) return null;
1295
+ const rangesToLift = [];
1296
+ newState.doc.descendants((node, pos) => {
1297
+ let depth = 0;
1298
+ let currentPos = pos;
1299
+ let currentNode = node;
1300
+ while (currentNode && depth <= maxDepth) {
1301
+ if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
1302
+ const $pos = newState.doc.resolve(currentPos);
1303
+ if ($pos.depth === 0) break;
1304
+ currentPos = $pos.before($pos.depth);
1305
+ currentNode = newState.doc.nodeAt(currentPos);
1306
+ }
1307
+ if (depth > maxDepth) {
1308
+ const $pos = newState.doc.resolve(pos);
1309
+ if ($pos.depth > 0) {
1310
+ const range = $pos.blockRange();
1311
+ if (range && "canReplace" in newState.schema.nodes.doc && typeof newState.schema.nodes.doc.canReplace === "function" && newState.schema.nodes.doc.canReplace(range.start - 1, range.end + 1, newState.doc.slice(range.start, range.end).content)) rangesToLift.push({
1312
+ range,
1313
+ target: range.start - 1
1314
+ });
1315
+ }
1316
+ }
1317
+ });
1318
+ if (rangesToLift.length === 0) return null;
1319
+ const tr = newState.tr;
1320
+ for (let i = rangesToLift.length - 1; i >= 0; i--) {
1321
+ const { range, target } = rangesToLift[i];
1322
+ tr.lift(range, target);
1323
+ }
1324
+ return tr;
1325
+ },
1326
+ filterTransaction(tr) {
1327
+ if (!tr.docChanged) return true;
1328
+ let wouldCreateDeepNesting = false;
1329
+ const newDoc = tr.doc;
1330
+ newDoc.descendants((node, pos) => {
1331
+ if (wouldCreateDeepNesting) return false;
1332
+ let depth = 0;
1333
+ let currentPos = pos;
1334
+ let currentNode = node;
1335
+ while (currentNode && depth <= maxDepth) {
1336
+ if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
1337
+ const $pos = newDoc.resolve(currentPos);
1338
+ if ($pos.depth === 0) break;
1339
+ currentPos = $pos.before($pos.depth);
1340
+ currentNode = newDoc.nodeAt(currentPos);
1341
+ }
1342
+ if (depth > maxDepth) {
1343
+ wouldCreateDeepNesting = true;
1344
+ return false;
1345
+ }
1346
+ });
1347
+ return !wouldCreateDeepNesting;
1348
+ }
1349
+ })];
1350
+ }
1351
+ });
1352
+
1353
+ //#endregion
1354
+ //#region src/extensions/ordered-list.tsx
1355
+ const OrderedList = EmailNode.from(OrderedListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ol", {
1356
+ className: node.attrs?.class || void 0,
1357
+ start: node.attrs?.start,
1358
+ style: {
1359
+ ...style,
1360
+ ...inlineCssToJs(node.attrs?.style)
1361
+ },
1362
+ children
1363
+ }));
1364
+
1365
+ //#endregion
1366
+ //#region src/extensions/paragraph.tsx
1367
+ const Paragraph = EmailNode.from(ParagraphBase, ({ children, node, style }) => {
1368
+ const isEmpty = !node.content || node.content.length === 0;
1369
+ return /* @__PURE__ */ jsx("p", {
1370
+ className: node.attrs?.class || void 0,
1371
+ style: {
1372
+ ...style,
1373
+ ...inlineCssToJs(node.attrs?.style),
1374
+ ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1375
+ },
1376
+ children: isEmpty ? /* @__PURE__ */ jsx("br", {}) : children
1377
+ });
1378
+ });
1379
+
1380
+ //#endregion
1381
+ //#region src/extensions/placeholder.ts
1382
+ const Placeholder = TipTapPlaceholder.configure({
1383
+ placeholder: ({ node }) => {
1384
+ if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
1385
+ return "Press '/' for commands";
1386
+ },
1387
+ includeChildren: true
1388
+ });
1389
+
1390
+ //#endregion
1391
+ //#region src/extensions/preview-text.ts
1392
+ const PreviewText = Node$1.create({
1393
+ name: "previewText",
1394
+ group: "block",
1395
+ selectable: false,
1396
+ draggable: false,
1397
+ atom: true,
1398
+ addOptions() {
1399
+ return { HTMLAttributes: {} };
1400
+ },
1401
+ addStorage() {
1402
+ return { previewText: null };
1403
+ },
1404
+ renderHTML() {
1405
+ return ["div", { style: "display: none" }];
1406
+ },
1407
+ parseHTML() {
1408
+ return [{
1409
+ tag: "div[data-skip-in-text=\"true\"]",
1410
+ getAttrs: (node) => {
1411
+ if (typeof node === "string") return false;
1412
+ const element = node;
1413
+ let directText = "";
1414
+ for (const child of element.childNodes) if (child.nodeType === 3) directText += child.textContent || "";
1415
+ const cleanText = directText.trim();
1416
+ if (cleanText) this.storage.previewText = cleanText;
1417
+ return false;
1418
+ }
1419
+ }, {
1420
+ tag: "span.preheader",
1421
+ getAttrs: (node) => {
1422
+ if (typeof node === "string") return false;
1423
+ const preheaderText = node.textContent?.trim();
1424
+ if (preheaderText) this.storage.previewText = preheaderText;
1425
+ return false;
1426
+ }
1427
+ }];
1428
+ }
1429
+ });
1430
+
1431
+ //#endregion
1432
+ //#region src/extensions/section.tsx
1433
+ const Section$1 = EmailNode.create({
1434
+ name: "section",
1435
+ group: "block",
1436
+ content: "block+",
1437
+ isolating: true,
1438
+ defining: true,
1439
+ parseHTML() {
1440
+ return [{ tag: "section[data-type=\"section\"]" }];
1441
+ },
1442
+ renderHTML({ HTMLAttributes }) {
1443
+ return [
1444
+ "section",
1445
+ mergeAttributes({
1446
+ "data-type": "section",
1447
+ class: "node-section"
1448
+ }, HTMLAttributes),
1449
+ 0
1450
+ ];
1451
+ },
1452
+ addCommands() {
1453
+ return { insertSection: () => ({ commands }) => {
1454
+ return commands.insertContent({
1455
+ type: this.name,
1456
+ content: [{
1457
+ type: "paragraph",
1458
+ content: []
1459
+ }]
1460
+ });
1461
+ } };
1462
+ },
1463
+ renderToReactEmail({ children, node, style }) {
1464
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1465
+ const textAlign = node.attrs?.align || node.attrs?.alignment;
1466
+ return /* @__PURE__ */ jsx(Section, {
1467
+ className: node.attrs?.class || void 0,
1468
+ align: textAlign,
1469
+ style: {
1470
+ ...style,
1471
+ ...inlineStyles,
1472
+ ...getTextAlignment(textAlign)
1473
+ },
1474
+ children
1475
+ });
1476
+ }
1477
+ });
1478
+
1479
+ //#endregion
1480
+ //#region src/extensions/strike.tsx
1481
+ const Strike = EmailMark.from(StrikeBase, ({ children, style }) => /* @__PURE__ */ jsx("s", {
1482
+ style,
1483
+ children
1484
+ }));
1485
+
1486
+ //#endregion
1487
+ //#region src/extensions/style-attribute.tsx
1488
+ const StyleAttribute = Extension.create({
1489
+ name: "styleAttribute",
1490
+ priority: 101,
1491
+ addOptions() {
1492
+ return {
1493
+ types: [],
1494
+ style: []
1495
+ };
1496
+ },
1497
+ addGlobalAttributes() {
1498
+ return [{
1499
+ types: this.options.types,
1500
+ attributes: { style: {
1501
+ default: "",
1502
+ parseHTML: (element) => element.getAttribute("style") || "",
1503
+ renderHTML: (attributes) => {
1504
+ return { style: attributes.style ?? "" };
1505
+ }
1506
+ } }
1507
+ }];
1508
+ },
1509
+ addCommands() {
1510
+ return {
1511
+ unsetStyle: () => ({ commands }) => {
1512
+ return this.options.types.every((type) => commands.resetAttributes(type, "style"));
1513
+ },
1514
+ setStyle: (style) => ({ commands }) => {
1515
+ return this.options.types.every((type) => commands.updateAttributes(type, { style }));
1516
+ }
1517
+ };
1518
+ },
1519
+ addKeyboardShortcuts() {
1520
+ return { Enter: ({ editor }) => {
1521
+ const { state } = editor.view;
1522
+ const { selection } = state;
1523
+ const { $from } = selection;
1524
+ const textBefore = $from.nodeBefore?.text || "";
1525
+ if (textBefore.includes("{{") || textBefore.includes("{{{")) return false;
1526
+ requestAnimationFrame(() => {
1527
+ editor.commands.resetAttributes("paragraph", "style");
1528
+ });
1529
+ return false;
1530
+ } };
1531
+ }
1532
+ });
1533
+
1534
+ //#endregion
1535
+ //#region src/extensions/sup.tsx
1536
+ const SupBase = SuperscriptBase.extend({
1537
+ name: "sup",
1538
+ addCommands() {
1539
+ return {
1540
+ ...this.parent?.(),
1541
+ setSup: () => ({ commands }) => {
1542
+ return commands.setMark(this.name);
1543
+ },
1544
+ toggleSup: () => ({ commands }) => {
1545
+ return commands.toggleMark(this.name);
1546
+ },
1547
+ unsetSup: () => ({ commands }) => {
1548
+ return commands.unsetMark(this.name);
1549
+ }
1550
+ };
1551
+ }
1552
+ });
1553
+ const Sup = EmailMark.from(SupBase, ({ children, style }) => /* @__PURE__ */ jsx("sup", {
1554
+ style,
1555
+ children
1556
+ }));
1557
+
1558
+ //#endregion
1559
+ //#region src/extensions/table.tsx
1560
+ const Table = EmailNode.create({
1561
+ name: "table",
1562
+ group: "block",
1563
+ content: "tableRow+",
1564
+ isolating: true,
1565
+ tableRole: "table",
1566
+ addAttributes() {
1567
+ return { ...createStandardAttributes([
1568
+ ...TABLE_ATTRIBUTES,
1569
+ ...LAYOUT_ATTRIBUTES,
1570
+ ...COMMON_HTML_ATTRIBUTES
1571
+ ]) };
1572
+ },
1573
+ parseHTML() {
1574
+ return [{
1575
+ tag: "table",
1576
+ getAttrs: (node) => {
1577
+ if (typeof node === "string") return false;
1578
+ const element = node;
1579
+ const attrs = {};
1580
+ Array.from(element.attributes).forEach((attr) => {
1581
+ attrs[attr.name] = attr.value;
1582
+ });
1583
+ return attrs;
1584
+ }
1585
+ }];
1586
+ },
1587
+ renderHTML({ HTMLAttributes }) {
1588
+ return [
1589
+ "table",
1590
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
1591
+ [
1592
+ "tbody",
1593
+ {},
1594
+ 0
1595
+ ]
1596
+ ];
1597
+ },
1598
+ renderToReactEmail({ children, node, style }) {
1599
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1600
+ const alignment = node.attrs?.align || node.attrs?.alignment;
1601
+ const width = node.attrs?.width;
1602
+ const centeringStyles = alignment === "center" ? {
1603
+ marginLeft: "auto",
1604
+ marginRight: "auto"
1605
+ } : {};
1606
+ return /* @__PURE__ */ jsx(Section, {
1607
+ className: node.attrs?.class || void 0,
1608
+ align: alignment,
1609
+ style: resolveConflictingStyles(style, {
1610
+ ...inlineStyles,
1611
+ ...centeringStyles
1612
+ }),
1613
+ ...width !== void 0 ? { width } : {},
1614
+ children
1615
+ });
1616
+ }
1617
+ });
1618
+ const TableRow = EmailNode.create({
1619
+ name: "tableRow",
1620
+ group: "tableRow",
1621
+ content: "(tableCell | tableHeader)+",
1622
+ addAttributes() {
1623
+ return { ...createStandardAttributes([
1624
+ ...TABLE_CELL_ATTRIBUTES,
1625
+ ...LAYOUT_ATTRIBUTES,
1626
+ ...COMMON_HTML_ATTRIBUTES
1627
+ ]) };
1628
+ },
1629
+ parseHTML() {
1630
+ return [{
1631
+ tag: "tr",
1632
+ getAttrs: (node) => {
1633
+ if (typeof node === "string") return false;
1634
+ const element = node;
1635
+ const attrs = {};
1636
+ Array.from(element.attributes).forEach((attr) => {
1637
+ attrs[attr.name] = attr.value;
1638
+ });
1639
+ return attrs;
1640
+ }
1641
+ }];
1642
+ },
1643
+ renderHTML({ HTMLAttributes }) {
1644
+ return [
1645
+ "tr",
1646
+ HTMLAttributes,
1647
+ 0
1648
+ ];
1649
+ },
1650
+ renderToReactEmail({ children, node, style }) {
1651
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1652
+ return /* @__PURE__ */ jsx("tr", {
1653
+ className: node.attrs?.class || void 0,
1654
+ style: {
1655
+ ...style,
1656
+ ...inlineStyles
1657
+ },
1658
+ children
1659
+ });
1660
+ }
1661
+ });
1662
+ const TableCell = EmailNode.create({
1663
+ name: "tableCell",
1664
+ group: "tableCell",
1665
+ content: "block+",
1666
+ isolating: true,
1667
+ addAttributes() {
1668
+ return { ...createStandardAttributes([
1669
+ ...TABLE_CELL_ATTRIBUTES,
1670
+ ...LAYOUT_ATTRIBUTES,
1671
+ ...COMMON_HTML_ATTRIBUTES
1672
+ ]) };
1673
+ },
1674
+ parseHTML() {
1675
+ return [{
1676
+ tag: "td",
1677
+ getAttrs: (node) => {
1678
+ if (typeof node === "string") return false;
1679
+ const element = node;
1680
+ const attrs = {};
1681
+ Array.from(element.attributes).forEach((attr) => {
1682
+ attrs[attr.name] = attr.value;
1683
+ });
1684
+ return attrs;
1685
+ }
1686
+ }];
1687
+ },
1688
+ renderHTML({ HTMLAttributes }) {
1689
+ return [
1690
+ "td",
1691
+ HTMLAttributes,
1692
+ 0
1693
+ ];
1694
+ },
1695
+ renderToReactEmail({ children, node, style }) {
1696
+ const inlineStyles = inlineCssToJs(node.attrs?.style);
1697
+ return /* @__PURE__ */ jsx(Column, {
1698
+ className: node.attrs?.class || void 0,
1699
+ align: node.attrs?.align || node.attrs?.alignment,
1700
+ style: {
1701
+ ...style,
1702
+ ...inlineStyles
1703
+ },
1704
+ children
1705
+ });
1706
+ }
1707
+ });
1708
+ const TableHeader = Node$1.create({
1709
+ name: "tableHeader",
1710
+ group: "tableCell",
1711
+ content: "block+",
1712
+ isolating: true,
1713
+ addAttributes() {
1714
+ return { ...createStandardAttributes([
1715
+ ...TABLE_HEADER_ATTRIBUTES,
1716
+ ...TABLE_CELL_ATTRIBUTES,
1717
+ ...LAYOUT_ATTRIBUTES,
1718
+ ...COMMON_HTML_ATTRIBUTES
1719
+ ]) };
1720
+ },
1721
+ parseHTML() {
1722
+ return [{
1723
+ tag: "th",
1724
+ getAttrs: (node) => {
1725
+ if (typeof node === "string") return false;
1726
+ const element = node;
1727
+ const attrs = {};
1728
+ Array.from(element.attributes).forEach((attr) => {
1729
+ attrs[attr.name] = attr.value;
1730
+ });
1731
+ return attrs;
1732
+ }
1733
+ }];
1734
+ },
1735
+ renderHTML({ HTMLAttributes }) {
1736
+ return [
1737
+ "th",
1738
+ HTMLAttributes,
1739
+ 0
1740
+ ];
1741
+ }
1742
+ });
1743
+
1744
+ //#endregion
1745
+ //#region src/extensions/text.tsx
1746
+ const Text$1 = EmailNode.from(Text, ({ children }) => {
1747
+ return /* @__PURE__ */ jsx(Fragment, { children });
1748
+ });
1749
+
1750
+ //#endregion
1751
+ //#region src/extensions/trailing-node.tsx
1752
+ const skipTrailingNodeMeta = "skipTrailingNode";
1753
+ function nodeEqualsType({ types, node }) {
1754
+ return node && Array.isArray(types) && types.includes(node.type) || node?.type === types;
1755
+ }
1756
+ /**
1757
+ * This extension allows you to add an extra node at the end of a node.
1758
+ *
1759
+ * Differently from TipTap's native one, it allows you to pick which node to append the trailing node to.
1760
+ * @see https://www.tiptap.dev/api/extensions/trailing-node
1761
+ *
1762
+ * We could contribute this to TipTap's core extensions and I think we should at some once we get some time.
1763
+ */
1764
+ const TrailingNode = Extension.create({
1765
+ name: "trailingNode",
1766
+ addOptions() {
1767
+ return {
1768
+ node: void 0,
1769
+ appendTo: "doc",
1770
+ notAfter: []
1771
+ };
1772
+ },
1773
+ addProseMirrorPlugins() {
1774
+ const plugin = new PluginKey(this.name);
1775
+ const defaultNode = this.options.node || this.editor.schema.topNodeType.contentMatch.defaultType?.name || "paragraph";
1776
+ const notAfter = Array.isArray(this.options.notAfter) ? this.options.notAfter : [this.options.notAfter].filter(Boolean);
1777
+ const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => notAfter.concat(defaultNode).includes(node.name));
1778
+ const appendToType = this.editor.schema.nodes[this.options.appendTo || "doc"];
1779
+ const getInsertPositions = (doc) => {
1780
+ const positions = [];
1781
+ if (doc.type === appendToType) {
1782
+ if (!nodeEqualsType({
1783
+ node: doc.lastChild,
1784
+ types: disabledNodes
1785
+ })) positions.push(doc.content.size);
1786
+ }
1787
+ doc.descendants((node, pos) => {
1788
+ if (node.type !== appendToType) return;
1789
+ if (!nodeEqualsType({
1790
+ node: node.lastChild,
1791
+ types: disabledNodes
1792
+ })) positions.push(pos + node.nodeSize - 1);
1793
+ });
1794
+ return positions;
1795
+ };
1796
+ return [new Plugin({
1797
+ key: plugin,
1798
+ appendTransaction: (transactions, __, state) => {
1799
+ const { doc, tr, schema } = state;
1800
+ const shouldInsert = plugin.getState(state);
1801
+ const type = schema.nodes[defaultNode];
1802
+ if (transactions.some((transaction) => transaction.getMeta(skipTrailingNodeMeta))) return;
1803
+ if (!shouldInsert) return;
1804
+ const positions = getInsertPositions(doc);
1805
+ for (const pos of positions.sort((a, b) => b - a)) tr.insert(pos, type.create());
1806
+ return positions.length > 0 ? tr : void 0;
1807
+ },
1808
+ state: {
1809
+ init: (_, state) => {
1810
+ return getInsertPositions(state.doc).length > 0;
1811
+ },
1812
+ apply: (tr, value) => {
1813
+ if (!tr.docChanged) return value;
1814
+ if (tr.getMeta("__uniqueIDTransaction")) return value;
1815
+ return getInsertPositions(tr.doc).length > 0;
1816
+ }
1817
+ }
1818
+ })];
1819
+ }
1820
+ });
1821
+
1822
+ //#endregion
1823
+ //#region src/extensions/underline.tsx
1824
+ const Underline = EmailMark.from(UnderlineBase, ({ children, style }) => /* @__PURE__ */ jsx("u", {
1825
+ style,
1826
+ children
1827
+ }));
1828
+
1829
+ //#endregion
1830
+ //#region src/extensions/uppercase.tsx
1831
+ const Uppercase = EmailMark.create({
1832
+ name: "uppercase",
1833
+ addOptions() {
1834
+ return { HTMLAttributes: {} };
1835
+ },
1836
+ parseHTML() {
1837
+ return [{
1838
+ tag: "span",
1839
+ getAttrs: (node) => {
1840
+ if (node.style.textTransform === "uppercase") return {};
1841
+ return false;
1842
+ }
1843
+ }];
1844
+ },
1845
+ renderHTML({ HTMLAttributes }) {
1846
+ return [
1847
+ "span",
1848
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
1849
+ 0
1850
+ ];
1851
+ },
1852
+ renderToReactEmail({ children, style }) {
1853
+ return /* @__PURE__ */ jsx("span", {
1854
+ style: {
1855
+ ...style,
1856
+ textTransform: "uppercase"
1857
+ },
1858
+ children
1859
+ });
1860
+ },
1861
+ addCommands() {
1862
+ return {
1863
+ setUppercase: () => ({ commands }) => {
1864
+ return commands.setMark(this.name);
1865
+ },
1866
+ toggleUppercase: () => ({ commands }) => {
1867
+ return commands.toggleMark(this.name);
1868
+ },
1869
+ unsetUppercase: () => ({ commands }) => {
1870
+ return commands.unsetMark(this.name);
1871
+ }
1872
+ };
1873
+ }
1874
+ });
1875
+
1876
+ //#endregion
1877
+ //#region src/extensions/index.ts
1878
+ const starterKitExtensions = {
1879
+ CodeBlockPrism,
1880
+ Code,
1881
+ TwoColumns,
1882
+ ThreeColumns,
1883
+ FourColumns,
1884
+ Container: Container$1,
1885
+ ColumnsColumn,
1886
+ Paragraph,
1887
+ BulletList,
1888
+ OrderedList,
1889
+ Blockquote,
1890
+ ListItem,
1891
+ HardBreak,
1892
+ Italic,
1893
+ Placeholder,
1894
+ PreviewText,
1895
+ TrailingNode,
1896
+ Bold,
1897
+ Strike,
1898
+ Heading: Heading$2,
1899
+ Divider,
1900
+ Link: Link$1,
1901
+ Sup,
1902
+ Underline,
1903
+ Uppercase,
1904
+ PreservedStyle,
1905
+ Table,
1906
+ TableRow,
1907
+ TableCell,
1908
+ TableHeader,
1909
+ Body: Body$1,
1910
+ Div,
1911
+ Button: Button$1,
1912
+ Section: Section$1,
1913
+ GlobalContent,
1914
+ Text: Text$1,
1915
+ AlignmentAttribute,
1916
+ StyleAttribute,
1917
+ ClassAttribute,
1918
+ MaxNesting
1919
+ };
1920
+ const StarterKit = Extension.create({
1921
+ name: "reactEmailStarterKit",
1922
+ addOptions() {
1923
+ return {
1924
+ TiptapStarterKit: {},
1925
+ CodeBlockPrism: {
1926
+ defaultLanguage: "javascript",
1927
+ HTMLAttributes: { class: "prism node-codeBlock" }
1928
+ },
1929
+ TrailingNode: {
1930
+ node: "paragraph",
1931
+ appendTo: "container"
1932
+ },
1933
+ Code: { HTMLAttributes: {
1934
+ class: "node-inlineCode",
1935
+ spellcheck: "false"
1936
+ } },
1937
+ TwoColumns: {},
1938
+ ThreeColumns: {},
1939
+ FourColumns: {},
1940
+ ColumnsColumn: {},
1941
+ Paragraph: { HTMLAttributes: { class: "node-paragraph" } },
1942
+ BulletList: { HTMLAttributes: { class: "node-bulletList" } },
1943
+ OrderedList: { HTMLAttributes: { class: "node-orderedList" } },
1944
+ Blockquote: { HTMLAttributes: { class: "node-blockquote" } },
1945
+ ListItem: {},
1946
+ HardBreak: {},
1947
+ Italic: {},
1948
+ Placeholder: {},
1949
+ PreviewText: {},
1950
+ Bold: {},
1951
+ Strike: {},
1952
+ Heading: {},
1953
+ Divider: {},
1954
+ Link: {
1955
+ openOnClick: false,
1956
+ HTMLAttributes: { class: "node-link" }
1957
+ },
1958
+ Sup: {},
1959
+ Underline: {},
1960
+ Uppercase: {},
1961
+ PreservedStyle: {},
1962
+ Table: {},
1963
+ TableRow: {},
1964
+ TableCell: {},
1965
+ TableHeader: {},
1966
+ Body: {},
1967
+ Container: {},
1968
+ Div: {},
1969
+ Button: {},
1970
+ Section: {},
1971
+ GlobalContent: {},
1972
+ AlignmentAttribute: { types: [
1973
+ "heading",
1974
+ "paragraph",
1975
+ "image",
1976
+ "blockquote",
1977
+ "codeBlock",
1978
+ "bulletList",
1979
+ "orderedList",
1980
+ "listItem",
1981
+ "button",
1982
+ "youtube",
1983
+ "twitter",
1984
+ "table",
1985
+ "tableRow",
1986
+ "tableCell",
1987
+ "tableHeader",
1988
+ "columnsColumn"
1989
+ ] },
1990
+ StyleAttribute: { types: [
1991
+ "heading",
1992
+ "paragraph",
1993
+ "image",
1994
+ "blockquote",
1995
+ "codeBlock",
1996
+ "bulletList",
1997
+ "orderedList",
1998
+ "listItem",
1999
+ "button",
2000
+ "youtube",
2001
+ "twitter",
2002
+ "horizontalRule",
2003
+ "footer",
2004
+ "section",
2005
+ "div",
2006
+ "body",
2007
+ "table",
2008
+ "tableRow",
2009
+ "tableCell",
2010
+ "tableHeader",
2011
+ "columnsColumn",
2012
+ "link"
2013
+ ] },
2014
+ Text: {},
2015
+ ClassAttribute: { types: [
2016
+ "heading",
2017
+ "paragraph",
2018
+ "image",
2019
+ "blockquote",
2020
+ "bulletList",
2021
+ "orderedList",
2022
+ "listItem",
2023
+ "button",
2024
+ "youtube",
2025
+ "twitter",
2026
+ "horizontalRule",
2027
+ "footer",
2028
+ "section",
2029
+ "div",
2030
+ "body",
2031
+ "table",
2032
+ "tableRow",
2033
+ "tableCell",
2034
+ "tableHeader",
2035
+ "columnsColumn",
2036
+ "link"
2037
+ ] },
2038
+ MaxNesting: {
2039
+ maxDepth: 50,
2040
+ nodeTypes: [
2041
+ "section",
2042
+ "bulletList",
2043
+ "orderedList"
2044
+ ]
2045
+ }
2046
+ };
2047
+ },
2048
+ addExtensions() {
2049
+ const extensions = [];
2050
+ if (this.options.TiptapStarterKit !== false) extensions.push(TipTapStarterKit.configure({
2051
+ undoRedo: false,
2052
+ heading: false,
2053
+ link: false,
2054
+ underline: false,
2055
+ trailingNode: false,
2056
+ bold: false,
2057
+ italic: false,
2058
+ strike: false,
2059
+ code: false,
2060
+ paragraph: false,
2061
+ bulletList: false,
2062
+ orderedList: false,
2063
+ listItem: false,
2064
+ blockquote: false,
2065
+ hardBreak: false,
2066
+ gapcursor: false,
2067
+ codeBlock: false,
2068
+ text: false,
2069
+ horizontalRule: false,
2070
+ dropcursor: {
2071
+ color: "#61a8f8",
2072
+ class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
2073
+ width: 4
2074
+ },
2075
+ ...this.options.TiptapStarterKit
2076
+ }));
2077
+ for (const [name, extension] of Object.entries(starterKitExtensions)) {
2078
+ const key = name;
2079
+ const extensionOptions = this.options[key];
2080
+ if (extensionOptions !== false) extensions.push(extension.configure(extensionOptions));
2081
+ }
2082
+ return extensions;
2083
+ }
2084
+ });
2085
+
2086
+ //#endregion
2087
+ export { Div as A, AlignmentAttribute as B, processStylesForUnlink as C, Divider as D, HardBreak as E, Button$1 as F, EmailMark as H, BulletList as I, Bold as L, CodeBlockPrism as M, Code as N, useEditor$1 as O, ClassAttribute as P, Body$1 as R, PreservedStyle as S, Heading$2 as T, createPasteHandler as U, composeReactEmail as V, createDropHandler as W, Paragraph as _, skipTrailingNodeMeta as a, ListItem as b, TableCell as c, Sup as d, StyleAttribute as f, Placeholder as g, PreviewText as h, TrailingNode as i, Container$1 as j, isDocumentVisuallyEmpty as k, TableHeader as l, Section$1 as m, Uppercase as n, Text$1 as o, Strike as p, Underline as r, Table as s, StarterKit as t, TableRow as u, OrderedList as v, Italic as w, Link$1 as x, MaxNesting as y, Blockquote as z };
2088
+ //# sourceMappingURL=extensions-BvfmaKCn.mjs.map