@react-email/editor 0.0.0-experimental.22 → 0.0.0-experimental.24

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