@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.
- package/dist/columns-CUxUEHje.mjs +497 -0
- package/dist/columns-CUxUEHje.mjs.map +1 -0
- package/dist/columns-ZSaLdkg9.cjs +630 -0
- package/dist/core/index.cjs +8 -0
- package/dist/core/index.d.cts +2 -0
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.mjs +4 -0
- package/dist/core-BjmRceVw.mjs +1999 -0
- package/dist/core-BjmRceVw.mjs.map +1 -0
- package/dist/core-iuG1UrYN.cjs +2250 -0
- package/dist/extensions/index.cjs +46 -0
- package/dist/extensions/index.d.cts +389 -0
- package/dist/extensions/index.d.cts.map +1 -0
- package/dist/extensions/index.d.mts +389 -0
- package/dist/extensions/index.d.mts.map +1 -0
- package/dist/extensions/index.mjs +4 -0
- package/dist/index-CfslA7KT.d.cts +130 -0
- package/dist/index-CfslA7KT.d.cts.map +1 -0
- package/dist/index-hbHRR7oB.d.mts +130 -0
- package/dist/index-hbHRR7oB.d.mts.map +1 -0
- package/dist/set-text-alignment-Bx3bPteH.cjs +24 -0
- package/dist/set-text-alignment-DZvgnbvz.mjs +19 -0
- package/dist/set-text-alignment-DZvgnbvz.mjs.map +1 -0
- package/dist/ui/index.cjs +1646 -0
- package/dist/ui/index.d.cts +668 -0
- package/dist/ui/index.d.cts.map +1 -0
- package/dist/ui/index.d.mts +668 -0
- package/dist/ui/index.d.mts.map +1 -0
- package/dist/ui/index.mjs +1584 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/utils/index.cjs +3 -0
- package/dist/utils/index.d.cts +7 -0
- package/dist/utils/index.d.cts.map +1 -0
- package/dist/utils/index.d.mts +7 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +3 -0
- package/package.json +38 -11
- package/dist/index.cjs +0 -4228
- package/dist/index.d.cts +0 -1175
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts +0 -1175
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -4072
- 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
|