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

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