@react-email/editor 0.0.0-experimental.0 → 0.0.0-experimental.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +188 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +188 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +793 -1
- package/dist/index.mjs +779 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -10
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Node, findChildren, mergeAttributes } from "@tiptap/core";
|
|
1
|
+
import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as ReactEmailComponents from "@react-email/components";
|
|
4
4
|
import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
|
|
@@ -7,6 +7,7 @@ import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
|
7
7
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
8
8
|
import { fromHtml } from "hast-util-from-html";
|
|
9
9
|
import Prism from "prismjs";
|
|
10
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
10
11
|
|
|
11
12
|
//#region src/core/email-node.ts
|
|
12
13
|
var EmailNode = class EmailNode extends Node {
|
|
@@ -38,6 +39,61 @@ var EmailNode = class EmailNode extends Node {
|
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/extensions/alignment-attribute.tsx
|
|
44
|
+
const AlignmentAttribute = Extension.create({
|
|
45
|
+
name: "alignmentAttribute",
|
|
46
|
+
addOptions() {
|
|
47
|
+
return {
|
|
48
|
+
types: [],
|
|
49
|
+
alignments: [
|
|
50
|
+
"left",
|
|
51
|
+
"center",
|
|
52
|
+
"right",
|
|
53
|
+
"justify"
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
addGlobalAttributes() {
|
|
58
|
+
return [{
|
|
59
|
+
types: this.options.types,
|
|
60
|
+
attributes: { alignment: {
|
|
61
|
+
parseHTML: (element) => {
|
|
62
|
+
const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
|
|
63
|
+
if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
|
|
64
|
+
return null;
|
|
65
|
+
},
|
|
66
|
+
renderHTML: (attributes) => {
|
|
67
|
+
if (attributes.alignment === "left") return {};
|
|
68
|
+
return { alignment: attributes.alignment };
|
|
69
|
+
}
|
|
70
|
+
} }
|
|
71
|
+
}];
|
|
72
|
+
},
|
|
73
|
+
addCommands() {
|
|
74
|
+
return { setAlignment: (alignment) => ({ commands }) => {
|
|
75
|
+
if (!this.options.alignments.includes(alignment)) return false;
|
|
76
|
+
return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
|
|
77
|
+
} };
|
|
78
|
+
},
|
|
79
|
+
addKeyboardShortcuts() {
|
|
80
|
+
return {
|
|
81
|
+
Enter: () => {
|
|
82
|
+
const { from } = this.editor.state.selection;
|
|
83
|
+
const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
|
|
84
|
+
if (currentAlignment) requestAnimationFrame(() => {
|
|
85
|
+
this.editor.commands.setAlignment(currentAlignment);
|
|
86
|
+
});
|
|
87
|
+
return false;
|
|
88
|
+
},
|
|
89
|
+
"Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
|
|
90
|
+
"Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
|
|
91
|
+
"Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
|
|
92
|
+
"Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
41
97
|
//#endregion
|
|
42
98
|
//#region src/utils/attribute-helpers.ts
|
|
43
99
|
/**
|
|
@@ -94,6 +150,14 @@ const LAYOUT_ATTRIBUTES = [
|
|
|
94
150
|
"height"
|
|
95
151
|
];
|
|
96
152
|
/**
|
|
153
|
+
* Table-specific HTML attributes used for table layout and styling.
|
|
154
|
+
*/
|
|
155
|
+
const TABLE_ATTRIBUTES = [
|
|
156
|
+
"border",
|
|
157
|
+
"cellpadding",
|
|
158
|
+
"cellspacing"
|
|
159
|
+
];
|
|
160
|
+
/**
|
|
97
161
|
* Table cell-specific HTML attributes.
|
|
98
162
|
*/
|
|
99
163
|
const TABLE_CELL_ATTRIBUTES = [
|
|
@@ -110,6 +174,7 @@ const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
|
|
|
110
174
|
|
|
111
175
|
//#endregion
|
|
112
176
|
//#region src/utils/styles.ts
|
|
177
|
+
const WHITE_SPACE_REGEX = /\s+/;
|
|
113
178
|
const inlineCssToJs = (inlineStyle, options = {}) => {
|
|
114
179
|
const styleObject = {};
|
|
115
180
|
if (!inlineStyle || inlineStyle === "" || typeof inlineStyle === "object") return styleObject;
|
|
@@ -124,6 +189,157 @@ const inlineCssToJs = (inlineStyle, options = {}) => {
|
|
|
124
189
|
});
|
|
125
190
|
return styleObject;
|
|
126
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* Expands CSS shorthand properties (margin, padding) into their longhand equivalents.
|
|
194
|
+
* This prevents shorthand properties from overriding specific longhand properties in email clients.
|
|
195
|
+
*
|
|
196
|
+
* @param styles - Style object that may contain shorthand properties
|
|
197
|
+
* @returns New style object with shorthand properties expanded to longhand
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* expandShorthandProperties({ margin: '0', paddingTop: '10px' })
|
|
201
|
+
* // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }
|
|
202
|
+
*/
|
|
203
|
+
function expandShorthandProperties(styles) {
|
|
204
|
+
if (!styles || typeof styles !== "object") return {};
|
|
205
|
+
const expanded = {};
|
|
206
|
+
for (const key in styles) {
|
|
207
|
+
const value = styles[key];
|
|
208
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
209
|
+
switch (key) {
|
|
210
|
+
case "margin": {
|
|
211
|
+
const values = parseShorthandValue(value);
|
|
212
|
+
expanded.marginTop = values.top;
|
|
213
|
+
expanded.marginRight = values.right;
|
|
214
|
+
expanded.marginBottom = values.bottom;
|
|
215
|
+
expanded.marginLeft = values.left;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "padding": {
|
|
219
|
+
const values = parseShorthandValue(value);
|
|
220
|
+
expanded.paddingTop = values.top;
|
|
221
|
+
expanded.paddingRight = values.right;
|
|
222
|
+
expanded.paddingBottom = values.bottom;
|
|
223
|
+
expanded.paddingLeft = values.left;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "border": {
|
|
227
|
+
const values = convertBorderValue(value);
|
|
228
|
+
expanded.borderStyle = values.style;
|
|
229
|
+
expanded.borderWidth = values.width;
|
|
230
|
+
expanded.borderColor = values.color;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "borderTopLeftRadius":
|
|
234
|
+
case "borderTopRightRadius":
|
|
235
|
+
case "borderBottomLeftRadius":
|
|
236
|
+
case "borderBottomRightRadius":
|
|
237
|
+
expanded[key] = value;
|
|
238
|
+
if (styles.borderTopLeftRadius && styles.borderTopRightRadius && styles.borderBottomLeftRadius && styles.borderBottomRightRadius) {
|
|
239
|
+
const values = [
|
|
240
|
+
styles.borderTopLeftRadius,
|
|
241
|
+
styles.borderTopRightRadius,
|
|
242
|
+
styles.borderBottomLeftRadius,
|
|
243
|
+
styles.borderBottomRightRadius
|
|
244
|
+
];
|
|
245
|
+
if (new Set(values).size === 1) expanded.borderRadius = values[0];
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
default: expanded[key] = value;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return expanded;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Parses CSS shorthand value (1-4 values) into individual side values.
|
|
255
|
+
* Follows CSS specification for shorthand property value parsing.
|
|
256
|
+
*
|
|
257
|
+
* @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')
|
|
258
|
+
* @returns Object with top, right, bottom, left values
|
|
259
|
+
*/
|
|
260
|
+
function parseShorthandValue(value) {
|
|
261
|
+
const stringValue = String(value).trim();
|
|
262
|
+
const parts = stringValue.split(WHITE_SPACE_REGEX);
|
|
263
|
+
const len = parts.length;
|
|
264
|
+
if (len === 1) return {
|
|
265
|
+
top: parts[0],
|
|
266
|
+
right: parts[0],
|
|
267
|
+
bottom: parts[0],
|
|
268
|
+
left: parts[0]
|
|
269
|
+
};
|
|
270
|
+
if (len === 2) return {
|
|
271
|
+
top: parts[0],
|
|
272
|
+
right: parts[1],
|
|
273
|
+
bottom: parts[0],
|
|
274
|
+
left: parts[1]
|
|
275
|
+
};
|
|
276
|
+
if (len === 3) return {
|
|
277
|
+
top: parts[0],
|
|
278
|
+
right: parts[1],
|
|
279
|
+
bottom: parts[2],
|
|
280
|
+
left: parts[1]
|
|
281
|
+
};
|
|
282
|
+
if (len === 4) return {
|
|
283
|
+
top: parts[0],
|
|
284
|
+
right: parts[1],
|
|
285
|
+
bottom: parts[2],
|
|
286
|
+
left: parts[3]
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
top: stringValue,
|
|
290
|
+
right: stringValue,
|
|
291
|
+
bottom: stringValue,
|
|
292
|
+
left: stringValue
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function convertBorderValue(value) {
|
|
296
|
+
const stringValue = String(value).trim();
|
|
297
|
+
const parts = stringValue.split(WHITE_SPACE_REGEX);
|
|
298
|
+
switch (parts.length) {
|
|
299
|
+
case 1: return {
|
|
300
|
+
style: "solid",
|
|
301
|
+
width: parts[0],
|
|
302
|
+
color: "black"
|
|
303
|
+
};
|
|
304
|
+
case 2: return {
|
|
305
|
+
style: parts[1],
|
|
306
|
+
width: parts[0],
|
|
307
|
+
color: "black"
|
|
308
|
+
};
|
|
309
|
+
case 3: return {
|
|
310
|
+
style: parts[1],
|
|
311
|
+
width: parts[0],
|
|
312
|
+
color: parts[2]
|
|
313
|
+
};
|
|
314
|
+
case 4: return {
|
|
315
|
+
style: parts[1],
|
|
316
|
+
width: parts[0],
|
|
317
|
+
color: parts[2]
|
|
318
|
+
};
|
|
319
|
+
default: return {
|
|
320
|
+
style: "solid",
|
|
321
|
+
width: stringValue,
|
|
322
|
+
color: "black"
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Resolves conflicts between reset styles and inline styles by expanding
|
|
328
|
+
* shorthand properties (margin, padding) to longhand before merging.
|
|
329
|
+
* This prevents shorthand properties from overriding specific longhand properties.
|
|
330
|
+
*
|
|
331
|
+
* @param resetStyles - Base reset styles that may contain shorthand properties
|
|
332
|
+
* @param inlineStyles - Inline styles that should override reset styles
|
|
333
|
+
* @returns Merged styles with inline styles taking precedence
|
|
334
|
+
*/
|
|
335
|
+
function resolveConflictingStyles(resetStyles, inlineStyles) {
|
|
336
|
+
const expandedResetStyles = expandShorthandProperties(resetStyles);
|
|
337
|
+
const expandedInlineStyles = expandShorthandProperties(inlineStyles);
|
|
338
|
+
return {
|
|
339
|
+
...expandedResetStyles,
|
|
340
|
+
...expandedInlineStyles
|
|
341
|
+
};
|
|
342
|
+
}
|
|
127
343
|
|
|
128
344
|
//#endregion
|
|
129
345
|
//#region src/extensions/body.tsx
|
|
@@ -170,6 +386,92 @@ const Body = EmailNode.create({
|
|
|
170
386
|
}
|
|
171
387
|
});
|
|
172
388
|
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/extensions/bold.ts
|
|
391
|
+
/**
|
|
392
|
+
* Matches bold text via `**` as input.
|
|
393
|
+
*/
|
|
394
|
+
const starInputRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
|
|
395
|
+
/**
|
|
396
|
+
* Matches bold text via `**` while pasting.
|
|
397
|
+
*/
|
|
398
|
+
const starPasteRegex = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
|
|
399
|
+
/**
|
|
400
|
+
* Matches bold text via `__` as input.
|
|
401
|
+
*/
|
|
402
|
+
const underscoreInputRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
|
|
403
|
+
/**
|
|
404
|
+
* Matches bold text via `__` while pasting.
|
|
405
|
+
*/
|
|
406
|
+
const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
|
|
407
|
+
/**
|
|
408
|
+
* This extension allows you to mark text as bold.
|
|
409
|
+
* @see https://tiptap.dev/api/marks/bold
|
|
410
|
+
*/
|
|
411
|
+
const Bold = Mark.create({
|
|
412
|
+
name: "bold",
|
|
413
|
+
addOptions() {
|
|
414
|
+
return { HTMLAttributes: {} };
|
|
415
|
+
},
|
|
416
|
+
parseHTML() {
|
|
417
|
+
return [
|
|
418
|
+
{ tag: "strong" },
|
|
419
|
+
{
|
|
420
|
+
tag: "b",
|
|
421
|
+
getAttrs: (node) => node.style.fontWeight !== "normal" && null
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
style: "font-weight=400",
|
|
425
|
+
clearMark: (mark) => mark.type.name === this.name
|
|
426
|
+
}
|
|
427
|
+
];
|
|
428
|
+
},
|
|
429
|
+
renderHTML({ HTMLAttributes }) {
|
|
430
|
+
return [
|
|
431
|
+
"strong",
|
|
432
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
433
|
+
0
|
|
434
|
+
];
|
|
435
|
+
},
|
|
436
|
+
addCommands() {
|
|
437
|
+
return {
|
|
438
|
+
setBold: () => ({ commands }) => {
|
|
439
|
+
return commands.setMark(this.name);
|
|
440
|
+
},
|
|
441
|
+
toggleBold: () => ({ commands }) => {
|
|
442
|
+
return commands.toggleMark(this.name);
|
|
443
|
+
},
|
|
444
|
+
unsetBold: () => ({ commands }) => {
|
|
445
|
+
return commands.unsetMark(this.name);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
addKeyboardShortcuts() {
|
|
450
|
+
return {
|
|
451
|
+
"Mod-b": () => this.editor.commands.toggleBold(),
|
|
452
|
+
"Mod-B": () => this.editor.commands.toggleBold()
|
|
453
|
+
};
|
|
454
|
+
},
|
|
455
|
+
addInputRules() {
|
|
456
|
+
return [markInputRule({
|
|
457
|
+
find: starInputRegex,
|
|
458
|
+
type: this.type
|
|
459
|
+
}), markInputRule({
|
|
460
|
+
find: underscoreInputRegex,
|
|
461
|
+
type: this.type
|
|
462
|
+
})];
|
|
463
|
+
},
|
|
464
|
+
addPasteRules() {
|
|
465
|
+
return [markPasteRule({
|
|
466
|
+
find: starPasteRegex,
|
|
467
|
+
type: this.type
|
|
468
|
+
}), markPasteRule({
|
|
469
|
+
find: underscorePasteRegex,
|
|
470
|
+
type: this.type
|
|
471
|
+
})];
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
173
475
|
//#endregion
|
|
174
476
|
//#region src/extensions/button.tsx
|
|
175
477
|
const Button = EmailNode.create({
|
|
@@ -250,6 +552,48 @@ const Button = EmailNode.create({
|
|
|
250
552
|
}
|
|
251
553
|
});
|
|
252
554
|
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/extensions/class-attribute.tsx
|
|
557
|
+
const ClassAttribute = Extension.create({
|
|
558
|
+
name: "classAttribute",
|
|
559
|
+
addOptions() {
|
|
560
|
+
return {
|
|
561
|
+
types: [],
|
|
562
|
+
class: []
|
|
563
|
+
};
|
|
564
|
+
},
|
|
565
|
+
addGlobalAttributes() {
|
|
566
|
+
return [{
|
|
567
|
+
types: this.options.types,
|
|
568
|
+
attributes: { class: {
|
|
569
|
+
default: "",
|
|
570
|
+
parseHTML: (element) => element.className || "",
|
|
571
|
+
renderHTML: (attributes) => {
|
|
572
|
+
return attributes.class ? { class: attributes.class } : {};
|
|
573
|
+
}
|
|
574
|
+
} }
|
|
575
|
+
}];
|
|
576
|
+
},
|
|
577
|
+
addCommands() {
|
|
578
|
+
return {
|
|
579
|
+
unsetClass: () => ({ commands }) => {
|
|
580
|
+
return this.options.types.every((type) => commands.resetAttributes(type, "class"));
|
|
581
|
+
},
|
|
582
|
+
setClass: (classList) => ({ commands }) => {
|
|
583
|
+
return this.options.types.every((type) => commands.updateAttributes(type, { class: classList }));
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
},
|
|
587
|
+
addKeyboardShortcuts() {
|
|
588
|
+
return { Enter: ({ editor }) => {
|
|
589
|
+
requestAnimationFrame(() => {
|
|
590
|
+
editor.commands.resetAttributes("paragraph", "class");
|
|
591
|
+
});
|
|
592
|
+
return false;
|
|
593
|
+
} };
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
253
597
|
//#endregion
|
|
254
598
|
//#region src/utils/prism-utils.ts
|
|
255
599
|
const publicURL = "/styles/prism";
|
|
@@ -509,6 +853,169 @@ const Div = EmailNode.create({
|
|
|
509
853
|
}
|
|
510
854
|
});
|
|
511
855
|
|
|
856
|
+
//#endregion
|
|
857
|
+
//#region src/extensions/max-nesting.ts
|
|
858
|
+
const MaxNesting = Extension.create({
|
|
859
|
+
name: "maxNesting",
|
|
860
|
+
addOptions() {
|
|
861
|
+
return {
|
|
862
|
+
maxDepth: 3,
|
|
863
|
+
nodeTypes: void 0
|
|
864
|
+
};
|
|
865
|
+
},
|
|
866
|
+
addProseMirrorPlugins() {
|
|
867
|
+
const { maxDepth, nodeTypes } = this.options;
|
|
868
|
+
if (typeof maxDepth !== "number" || maxDepth < 1) throw new Error("maxDepth must be a positive number");
|
|
869
|
+
return [new Plugin({
|
|
870
|
+
key: new PluginKey("maxNesting"),
|
|
871
|
+
appendTransaction(transactions, _oldState, newState) {
|
|
872
|
+
if (!transactions.some((tr$1) => tr$1.docChanged)) return null;
|
|
873
|
+
const rangesToLift = [];
|
|
874
|
+
newState.doc.descendants((node, pos) => {
|
|
875
|
+
let depth = 0;
|
|
876
|
+
let currentPos = pos;
|
|
877
|
+
let currentNode = node;
|
|
878
|
+
while (currentNode && depth <= maxDepth) {
|
|
879
|
+
if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
|
|
880
|
+
const $pos = newState.doc.resolve(currentPos);
|
|
881
|
+
if ($pos.depth === 0) break;
|
|
882
|
+
currentPos = $pos.before($pos.depth);
|
|
883
|
+
currentNode = newState.doc.nodeAt(currentPos);
|
|
884
|
+
}
|
|
885
|
+
if (depth > maxDepth) {
|
|
886
|
+
const $pos = newState.doc.resolve(pos);
|
|
887
|
+
if ($pos.depth > 0) {
|
|
888
|
+
const range = $pos.blockRange();
|
|
889
|
+
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({
|
|
890
|
+
range,
|
|
891
|
+
target: range.start - 1
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
if (rangesToLift.length === 0) return null;
|
|
897
|
+
const tr = newState.tr;
|
|
898
|
+
for (let i = rangesToLift.length - 1; i >= 0; i--) {
|
|
899
|
+
const { range, target } = rangesToLift[i];
|
|
900
|
+
tr.lift(range, target);
|
|
901
|
+
}
|
|
902
|
+
return tr;
|
|
903
|
+
},
|
|
904
|
+
filterTransaction(tr) {
|
|
905
|
+
if (!tr.docChanged) return true;
|
|
906
|
+
let wouldCreateDeepNesting = false;
|
|
907
|
+
const newDoc = tr.doc;
|
|
908
|
+
newDoc.descendants((node, pos) => {
|
|
909
|
+
if (wouldCreateDeepNesting) return false;
|
|
910
|
+
let depth = 0;
|
|
911
|
+
let currentPos = pos;
|
|
912
|
+
let currentNode = node;
|
|
913
|
+
while (currentNode && depth <= maxDepth) {
|
|
914
|
+
if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
|
|
915
|
+
const $pos = newDoc.resolve(currentPos);
|
|
916
|
+
if ($pos.depth === 0) break;
|
|
917
|
+
currentPos = $pos.before($pos.depth);
|
|
918
|
+
currentNode = newDoc.nodeAt(currentPos);
|
|
919
|
+
}
|
|
920
|
+
if (depth > maxDepth) {
|
|
921
|
+
wouldCreateDeepNesting = true;
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
return !wouldCreateDeepNesting;
|
|
926
|
+
}
|
|
927
|
+
})];
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/extensions/placeholder.ts
|
|
933
|
+
const createPlaceholderExtension = (options) => {
|
|
934
|
+
return Placeholder.configure({
|
|
935
|
+
placeholder: ({ node }) => {
|
|
936
|
+
if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
|
|
937
|
+
return "Press '/' for commands";
|
|
938
|
+
},
|
|
939
|
+
includeChildren: true,
|
|
940
|
+
...options
|
|
941
|
+
});
|
|
942
|
+
};
|
|
943
|
+
const placeholder = createPlaceholderExtension();
|
|
944
|
+
|
|
945
|
+
//#endregion
|
|
946
|
+
//#region src/extensions/preserved-style.ts
|
|
947
|
+
const PreservedStyle = Mark.create({
|
|
948
|
+
name: "preservedStyle",
|
|
949
|
+
addAttributes() {
|
|
950
|
+
return { style: {
|
|
951
|
+
default: null,
|
|
952
|
+
parseHTML: (element) => element.getAttribute("style"),
|
|
953
|
+
renderHTML: (attributes) => {
|
|
954
|
+
if (!attributes.style) return {};
|
|
955
|
+
return { style: attributes.style };
|
|
956
|
+
}
|
|
957
|
+
} };
|
|
958
|
+
},
|
|
959
|
+
parseHTML() {
|
|
960
|
+
return [{
|
|
961
|
+
tag: "span[style]",
|
|
962
|
+
getAttrs: (element) => {
|
|
963
|
+
if (typeof element === "string") return false;
|
|
964
|
+
const style = element.getAttribute("style");
|
|
965
|
+
if (style && hasPreservableStyles(style)) return { style };
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
}];
|
|
969
|
+
},
|
|
970
|
+
renderHTML({ HTMLAttributes }) {
|
|
971
|
+
return [
|
|
972
|
+
"span",
|
|
973
|
+
mergeAttributes(HTMLAttributes),
|
|
974
|
+
0
|
|
975
|
+
];
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
const LINK_INDICATOR_STYLES = [
|
|
979
|
+
"color",
|
|
980
|
+
"text-decoration",
|
|
981
|
+
"text-decoration-line",
|
|
982
|
+
"text-decoration-color",
|
|
983
|
+
"text-decoration-style"
|
|
984
|
+
];
|
|
985
|
+
function parseStyleString(styleString) {
|
|
986
|
+
const temp = document.createElement("div");
|
|
987
|
+
temp.style.cssText = styleString;
|
|
988
|
+
return temp.style;
|
|
989
|
+
}
|
|
990
|
+
function hasBackground(style) {
|
|
991
|
+
const bgColor = style.backgroundColor;
|
|
992
|
+
const bg = style.background;
|
|
993
|
+
if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
|
|
994
|
+
if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
function hasPreservableStyles(styleString) {
|
|
998
|
+
return processStylesForUnlink(styleString) !== null;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Processes styles when unlinking:
|
|
1002
|
+
* - Has background (button-like): preserve all styles
|
|
1003
|
+
* - No background: strip link-indicator styles (color, text-decoration), keep the rest
|
|
1004
|
+
*/
|
|
1005
|
+
function processStylesForUnlink(styleString) {
|
|
1006
|
+
if (!styleString) return null;
|
|
1007
|
+
const style = parseStyleString(styleString);
|
|
1008
|
+
if (hasBackground(style)) return styleString;
|
|
1009
|
+
const filtered = [];
|
|
1010
|
+
for (let i = 0; i < style.length; i++) {
|
|
1011
|
+
const prop = style[i];
|
|
1012
|
+
if (LINK_INDICATOR_STYLES.includes(prop)) continue;
|
|
1013
|
+
const value = style.getPropertyValue(prop);
|
|
1014
|
+
if (value) filtered.push(`${prop}: ${value}`);
|
|
1015
|
+
}
|
|
1016
|
+
return filtered.length > 0 ? filtered.join("; ") : null;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
512
1019
|
//#endregion
|
|
513
1020
|
//#region src/utils/get-text-alignment.ts
|
|
514
1021
|
function getTextAlignment(alignment) {
|
|
@@ -569,5 +1076,275 @@ const Section = EmailNode.create({
|
|
|
569
1076
|
});
|
|
570
1077
|
|
|
571
1078
|
//#endregion
|
|
572
|
-
|
|
1079
|
+
//#region src/extensions/style-attribute.tsx
|
|
1080
|
+
const StyleAttribute = Extension.create({
|
|
1081
|
+
name: "styleAttribute",
|
|
1082
|
+
priority: 101,
|
|
1083
|
+
addOptions() {
|
|
1084
|
+
return {
|
|
1085
|
+
types: [],
|
|
1086
|
+
style: []
|
|
1087
|
+
};
|
|
1088
|
+
},
|
|
1089
|
+
addGlobalAttributes() {
|
|
1090
|
+
return [{
|
|
1091
|
+
types: this.options.types,
|
|
1092
|
+
attributes: { style: {
|
|
1093
|
+
default: "",
|
|
1094
|
+
parseHTML: (element) => element.getAttribute("style") || "",
|
|
1095
|
+
renderHTML: (attributes) => {
|
|
1096
|
+
return { style: attributes.style ?? "" };
|
|
1097
|
+
}
|
|
1098
|
+
} }
|
|
1099
|
+
}];
|
|
1100
|
+
},
|
|
1101
|
+
addCommands() {
|
|
1102
|
+
return {
|
|
1103
|
+
unsetStyle: () => ({ commands }) => {
|
|
1104
|
+
return this.options.types.every((type) => commands.resetAttributes(type, "style"));
|
|
1105
|
+
},
|
|
1106
|
+
setStyle: (style) => ({ commands }) => {
|
|
1107
|
+
return this.options.types.every((type) => commands.updateAttributes(type, { style }));
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
},
|
|
1111
|
+
addKeyboardShortcuts() {
|
|
1112
|
+
return { Enter: ({ editor }) => {
|
|
1113
|
+
const { state } = editor.view;
|
|
1114
|
+
const { selection } = state;
|
|
1115
|
+
const { $from } = selection;
|
|
1116
|
+
const textBefore = $from.nodeBefore?.text || "";
|
|
1117
|
+
if (textBefore.includes("{{") || textBefore.includes("{{{")) return false;
|
|
1118
|
+
requestAnimationFrame(() => {
|
|
1119
|
+
editor.commands.resetAttributes("paragraph", "style");
|
|
1120
|
+
});
|
|
1121
|
+
return false;
|
|
1122
|
+
} };
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
//#endregion
|
|
1127
|
+
//#region src/extensions/sup.ts
|
|
1128
|
+
/**
|
|
1129
|
+
* This extension allows you to mark text as superscript.
|
|
1130
|
+
* @see https://tiptap.dev/api/marks/superscript
|
|
1131
|
+
*/
|
|
1132
|
+
const Sup = Mark.create({
|
|
1133
|
+
name: "sup",
|
|
1134
|
+
addOptions() {
|
|
1135
|
+
return { HTMLAttributes: {} };
|
|
1136
|
+
},
|
|
1137
|
+
parseHTML() {
|
|
1138
|
+
return [{ tag: "sup" }];
|
|
1139
|
+
},
|
|
1140
|
+
renderHTML({ HTMLAttributes }) {
|
|
1141
|
+
return [
|
|
1142
|
+
"sup",
|
|
1143
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
1144
|
+
0
|
|
1145
|
+
];
|
|
1146
|
+
},
|
|
1147
|
+
addCommands() {
|
|
1148
|
+
return {
|
|
1149
|
+
setSup: () => ({ commands }) => {
|
|
1150
|
+
return commands.setMark(this.name);
|
|
1151
|
+
},
|
|
1152
|
+
toggleSup: () => ({ commands }) => {
|
|
1153
|
+
return commands.toggleMark(this.name);
|
|
1154
|
+
},
|
|
1155
|
+
unsetSup: () => ({ commands }) => {
|
|
1156
|
+
return commands.unsetMark(this.name);
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
//#endregion
|
|
1163
|
+
//#region src/extensions/table.tsx
|
|
1164
|
+
const Table = EmailNode.create({
|
|
1165
|
+
name: "table",
|
|
1166
|
+
group: "block",
|
|
1167
|
+
content: "tableRow+",
|
|
1168
|
+
isolating: true,
|
|
1169
|
+
tableRole: "table",
|
|
1170
|
+
addAttributes() {
|
|
1171
|
+
return { ...createStandardAttributes([
|
|
1172
|
+
...TABLE_ATTRIBUTES,
|
|
1173
|
+
...LAYOUT_ATTRIBUTES,
|
|
1174
|
+
...COMMON_HTML_ATTRIBUTES
|
|
1175
|
+
]) };
|
|
1176
|
+
},
|
|
1177
|
+
parseHTML() {
|
|
1178
|
+
return [{
|
|
1179
|
+
tag: "table",
|
|
1180
|
+
getAttrs: (node) => {
|
|
1181
|
+
if (typeof node === "string") return false;
|
|
1182
|
+
const element = node;
|
|
1183
|
+
const attrs = {};
|
|
1184
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
1185
|
+
attrs[attr.name] = attr.value;
|
|
1186
|
+
});
|
|
1187
|
+
return attrs;
|
|
1188
|
+
}
|
|
1189
|
+
}];
|
|
1190
|
+
},
|
|
1191
|
+
renderHTML({ HTMLAttributes }) {
|
|
1192
|
+
return [
|
|
1193
|
+
"table",
|
|
1194
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
1195
|
+
[
|
|
1196
|
+
"tbody",
|
|
1197
|
+
{},
|
|
1198
|
+
0
|
|
1199
|
+
]
|
|
1200
|
+
];
|
|
1201
|
+
},
|
|
1202
|
+
renderToReactEmail({ children, node, styles }) {
|
|
1203
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1204
|
+
const alignment = node.attrs?.align || node.attrs?.alignment;
|
|
1205
|
+
const width = node.attrs?.width;
|
|
1206
|
+
const centeringStyles = alignment === "center" ? {
|
|
1207
|
+
marginLeft: "auto",
|
|
1208
|
+
marginRight: "auto"
|
|
1209
|
+
} : {};
|
|
1210
|
+
return /* @__PURE__ */ jsx(Section$1, {
|
|
1211
|
+
className: node.attrs?.class || void 0,
|
|
1212
|
+
align: alignment,
|
|
1213
|
+
style: resolveConflictingStyles(styles.reset, {
|
|
1214
|
+
...inlineStyles,
|
|
1215
|
+
...centeringStyles
|
|
1216
|
+
}),
|
|
1217
|
+
...width !== void 0 ? { width } : {},
|
|
1218
|
+
children
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
const TableRow = EmailNode.create({
|
|
1223
|
+
name: "tableRow",
|
|
1224
|
+
group: "tableRow",
|
|
1225
|
+
content: "(tableCell | tableHeader)+",
|
|
1226
|
+
addAttributes() {
|
|
1227
|
+
return { ...createStandardAttributes([
|
|
1228
|
+
...TABLE_CELL_ATTRIBUTES,
|
|
1229
|
+
...LAYOUT_ATTRIBUTES,
|
|
1230
|
+
...COMMON_HTML_ATTRIBUTES
|
|
1231
|
+
]) };
|
|
1232
|
+
},
|
|
1233
|
+
parseHTML() {
|
|
1234
|
+
return [{
|
|
1235
|
+
tag: "tr",
|
|
1236
|
+
getAttrs: (node) => {
|
|
1237
|
+
if (typeof node === "string") return false;
|
|
1238
|
+
const element = node;
|
|
1239
|
+
const attrs = {};
|
|
1240
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
1241
|
+
attrs[attr.name] = attr.value;
|
|
1242
|
+
});
|
|
1243
|
+
return attrs;
|
|
1244
|
+
}
|
|
1245
|
+
}];
|
|
1246
|
+
},
|
|
1247
|
+
renderHTML({ HTMLAttributes }) {
|
|
1248
|
+
return [
|
|
1249
|
+
"tr",
|
|
1250
|
+
HTMLAttributes,
|
|
1251
|
+
0
|
|
1252
|
+
];
|
|
1253
|
+
},
|
|
1254
|
+
renderToReactEmail({ children, node, styles }) {
|
|
1255
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1256
|
+
return /* @__PURE__ */ jsx("tr", {
|
|
1257
|
+
className: node.attrs?.class || void 0,
|
|
1258
|
+
style: {
|
|
1259
|
+
...styles.reset,
|
|
1260
|
+
...inlineStyles
|
|
1261
|
+
},
|
|
1262
|
+
children
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
const TableCell = EmailNode.create({
|
|
1267
|
+
name: "tableCell",
|
|
1268
|
+
group: "tableCell",
|
|
1269
|
+
content: "block+",
|
|
1270
|
+
isolating: true,
|
|
1271
|
+
addAttributes() {
|
|
1272
|
+
return { ...createStandardAttributes([
|
|
1273
|
+
...TABLE_CELL_ATTRIBUTES,
|
|
1274
|
+
...LAYOUT_ATTRIBUTES,
|
|
1275
|
+
...COMMON_HTML_ATTRIBUTES
|
|
1276
|
+
]) };
|
|
1277
|
+
},
|
|
1278
|
+
parseHTML() {
|
|
1279
|
+
return [{
|
|
1280
|
+
tag: "td",
|
|
1281
|
+
getAttrs: (node) => {
|
|
1282
|
+
if (typeof node === "string") return false;
|
|
1283
|
+
const element = node;
|
|
1284
|
+
const attrs = {};
|
|
1285
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
1286
|
+
attrs[attr.name] = attr.value;
|
|
1287
|
+
});
|
|
1288
|
+
return attrs;
|
|
1289
|
+
}
|
|
1290
|
+
}];
|
|
1291
|
+
},
|
|
1292
|
+
renderHTML({ HTMLAttributes }) {
|
|
1293
|
+
return [
|
|
1294
|
+
"td",
|
|
1295
|
+
HTMLAttributes,
|
|
1296
|
+
0
|
|
1297
|
+
];
|
|
1298
|
+
},
|
|
1299
|
+
renderToReactEmail({ children, node, styles }) {
|
|
1300
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1301
|
+
return /* @__PURE__ */ jsx(Column, {
|
|
1302
|
+
className: node.attrs?.class || void 0,
|
|
1303
|
+
align: node.attrs?.align || node.attrs?.alignment,
|
|
1304
|
+
style: {
|
|
1305
|
+
...styles.reset,
|
|
1306
|
+
...inlineStyles
|
|
1307
|
+
},
|
|
1308
|
+
children
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
const TableHeader = Node.create({
|
|
1313
|
+
name: "tableHeader",
|
|
1314
|
+
group: "tableCell",
|
|
1315
|
+
content: "block+",
|
|
1316
|
+
isolating: true,
|
|
1317
|
+
addAttributes() {
|
|
1318
|
+
return { ...createStandardAttributes([
|
|
1319
|
+
...TABLE_HEADER_ATTRIBUTES,
|
|
1320
|
+
...TABLE_CELL_ATTRIBUTES,
|
|
1321
|
+
...LAYOUT_ATTRIBUTES,
|
|
1322
|
+
...COMMON_HTML_ATTRIBUTES
|
|
1323
|
+
]) };
|
|
1324
|
+
},
|
|
1325
|
+
parseHTML() {
|
|
1326
|
+
return [{
|
|
1327
|
+
tag: "th",
|
|
1328
|
+
getAttrs: (node) => {
|
|
1329
|
+
if (typeof node === "string") return false;
|
|
1330
|
+
const element = node;
|
|
1331
|
+
const attrs = {};
|
|
1332
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
1333
|
+
attrs[attr.name] = attr.value;
|
|
1334
|
+
});
|
|
1335
|
+
return attrs;
|
|
1336
|
+
}
|
|
1337
|
+
}];
|
|
1338
|
+
},
|
|
1339
|
+
renderHTML({ HTMLAttributes }) {
|
|
1340
|
+
return [
|
|
1341
|
+
"th",
|
|
1342
|
+
HTMLAttributes,
|
|
1343
|
+
0
|
|
1344
|
+
];
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
//#endregion
|
|
1349
|
+
export { AlignmentAttribute, Body, Bold, Button, ClassAttribute, CodeBlockPrism, Div, EmailNode, MaxNesting, PreservedStyle, Section, StyleAttribute, Sup, Table, TableCell, TableHeader, TableRow, createPlaceholderExtension, placeholder, processStylesForUnlink };
|
|
573
1350
|
//# sourceMappingURL=index.mjs.map
|