@react-email/editor 0.0.0-experimental.15 → 0.0.0-experimental.17
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.cjs +1222 -419
- package/dist/index.d.cts +334 -160
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +335 -161
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1202 -424
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -5
package/dist/index.mjs
CHANGED
|
@@ -1,54 +1,37 @@
|
|
|
1
|
-
import { Extension, Mark, Node, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
import { StarterKit } from "@tiptap/starter-kit";
|
|
3
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
1
|
import * as ReactEmailComponents from "@react-email/components";
|
|
5
|
-
import { Button as Button$1, CodeBlock, Column, Row, Section as Section$1 } from "@react-email/components";
|
|
2
|
+
import { Body as Body$1, Button as Button$1, CodeBlock, Column, Head, Heading, Hr, Html, Link, Preview, Row, Section as Section$1, pretty, render, toPlainText } from "@react-email/components";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Extension, InputRule, Mark, Node as Node$1, findChildren, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
5
|
+
import { UndoRedo } from "@tiptap/extensions";
|
|
6
|
+
import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, ReactRenderer, useCurrentEditor, useEditor as useEditor$1, useEditorState } from "@tiptap/react";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
9
|
+
import TipTapStarterKit from "@tiptap/starter-kit";
|
|
10
|
+
import BlockquoteBase from "@tiptap/extension-blockquote";
|
|
11
|
+
import BulletListBase from "@tiptap/extension-bullet-list";
|
|
12
|
+
import CodeBase from "@tiptap/extension-code";
|
|
6
13
|
import CodeBlock$1 from "@tiptap/extension-code-block";
|
|
7
14
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
15
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
9
16
|
import { fromHtml } from "hast-util-from-html";
|
|
10
17
|
import Prism from "prismjs";
|
|
18
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
|
19
|
+
import HardBreakBase from "@tiptap/extension-hard-break";
|
|
20
|
+
import { Heading as Heading$1 } from "@tiptap/extension-heading";
|
|
21
|
+
import ItalicBase from "@tiptap/extension-italic";
|
|
22
|
+
import TiptapLink from "@tiptap/extension-link";
|
|
23
|
+
import ListItemBase from "@tiptap/extension-list-item";
|
|
24
|
+
import OrderedListBase from "@tiptap/extension-ordered-list";
|
|
25
|
+
import ParagraphBase from "@tiptap/extension-paragraph";
|
|
11
26
|
import TipTapPlaceholder from "@tiptap/extension-placeholder";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
27
|
+
import StrikeBase from "@tiptap/extension-strike";
|
|
28
|
+
import { generateJSON } from "@tiptap/html";
|
|
29
|
+
import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, CaseUpperIcon, Check, ChevronDown, Code as Code$1, CodeIcon, Columns2, Columns3, Columns4, ExternalLinkIcon, Heading1, Heading2, Heading3, ItalicIcon, LinkIcon, List, ListOrdered, MousePointer, PencilIcon, Rows2, SplitSquareVertical, SquareCode, StrikethroughIcon, Text, TextIcon, TextQuote, UnderlineIcon, UnlinkIcon } from "lucide-react";
|
|
16
30
|
import * as Popover from "@radix-ui/react-popover";
|
|
17
31
|
import { BubbleMenu as BubbleMenu$1 } from "@tiptap/react/menus";
|
|
18
32
|
import Suggestion from "@tiptap/suggestion";
|
|
19
33
|
import tippy from "tippy.js";
|
|
20
34
|
|
|
21
|
-
//#region src/core/email-node.ts
|
|
22
|
-
var EmailNode = class EmailNode extends Node {
|
|
23
|
-
constructor(config) {
|
|
24
|
-
super(config);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Create a new Node instance
|
|
28
|
-
* @param config - Node configuration object or a function that returns a configuration object
|
|
29
|
-
*/
|
|
30
|
-
static create(config) {
|
|
31
|
-
return new EmailNode(typeof config === "function" ? config() : config);
|
|
32
|
-
}
|
|
33
|
-
static from(node, renderToReactEmail) {
|
|
34
|
-
const customNode = EmailNode.create({});
|
|
35
|
-
Object.assign(customNode, { ...node });
|
|
36
|
-
customNode.config = {
|
|
37
|
-
...node.config,
|
|
38
|
-
renderToReactEmail
|
|
39
|
-
};
|
|
40
|
-
return customNode;
|
|
41
|
-
}
|
|
42
|
-
configure(options) {
|
|
43
|
-
return super.configure(options);
|
|
44
|
-
}
|
|
45
|
-
extend(extendedConfig) {
|
|
46
|
-
const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
|
|
47
|
-
return super.extend(resolvedConfig);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
35
|
//#region src/core/event-bus.ts
|
|
53
36
|
const EVENT_PREFIX = "@react-email/editor:";
|
|
54
37
|
var EditorEventBus = class {
|
|
@@ -90,139 +73,6 @@ var EditorEventBus = class {
|
|
|
90
73
|
};
|
|
91
74
|
const editorEventBus = new EditorEventBus();
|
|
92
75
|
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/extensions/alignment-attribute.tsx
|
|
95
|
-
const AlignmentAttribute = Extension.create({
|
|
96
|
-
name: "alignmentAttribute",
|
|
97
|
-
addOptions() {
|
|
98
|
-
return {
|
|
99
|
-
types: [],
|
|
100
|
-
alignments: [
|
|
101
|
-
"left",
|
|
102
|
-
"center",
|
|
103
|
-
"right",
|
|
104
|
-
"justify"
|
|
105
|
-
]
|
|
106
|
-
};
|
|
107
|
-
},
|
|
108
|
-
addGlobalAttributes() {
|
|
109
|
-
return [{
|
|
110
|
-
types: this.options.types,
|
|
111
|
-
attributes: { alignment: {
|
|
112
|
-
parseHTML: (element) => {
|
|
113
|
-
const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
|
|
114
|
-
if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
|
|
115
|
-
return null;
|
|
116
|
-
},
|
|
117
|
-
renderHTML: (attributes) => {
|
|
118
|
-
if (attributes.alignment === "left") return {};
|
|
119
|
-
return { alignment: attributes.alignment };
|
|
120
|
-
}
|
|
121
|
-
} }
|
|
122
|
-
}];
|
|
123
|
-
},
|
|
124
|
-
addCommands() {
|
|
125
|
-
return { setAlignment: (alignment) => ({ commands }) => {
|
|
126
|
-
if (!this.options.alignments.includes(alignment)) return false;
|
|
127
|
-
return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
|
|
128
|
-
} };
|
|
129
|
-
},
|
|
130
|
-
addKeyboardShortcuts() {
|
|
131
|
-
return {
|
|
132
|
-
Enter: () => {
|
|
133
|
-
const { from } = this.editor.state.selection;
|
|
134
|
-
const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
|
|
135
|
-
if (currentAlignment) requestAnimationFrame(() => {
|
|
136
|
-
this.editor.commands.setAlignment(currentAlignment);
|
|
137
|
-
});
|
|
138
|
-
return false;
|
|
139
|
-
},
|
|
140
|
-
"Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
|
|
141
|
-
"Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
|
|
142
|
-
"Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
|
|
143
|
-
"Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/utils/attribute-helpers.ts
|
|
150
|
-
/**
|
|
151
|
-
* Creates TipTap attribute definitions for a list of HTML attributes.
|
|
152
|
-
* Each attribute will have the same pattern:
|
|
153
|
-
* - default: null
|
|
154
|
-
* - parseHTML: extracts the attribute from the element
|
|
155
|
-
* - renderHTML: conditionally renders the attribute if it has a value
|
|
156
|
-
*
|
|
157
|
-
* @param attributeNames - Array of HTML attribute names to create definitions for
|
|
158
|
-
* @returns Object with TipTap attribute definitions
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* const attrs = createStandardAttributes(['class', 'id', 'title']);
|
|
162
|
-
* // Returns:
|
|
163
|
-
* // {
|
|
164
|
-
* // class: {
|
|
165
|
-
* // default: null,
|
|
166
|
-
* // parseHTML: (element) => element.getAttribute('class'),
|
|
167
|
-
* // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
|
|
168
|
-
* // },
|
|
169
|
-
* // ...
|
|
170
|
-
* // }
|
|
171
|
-
*/
|
|
172
|
-
function createStandardAttributes(attributeNames) {
|
|
173
|
-
return Object.fromEntries(attributeNames.map((attr) => [attr, {
|
|
174
|
-
default: null,
|
|
175
|
-
parseHTML: (element) => element.getAttribute(attr),
|
|
176
|
-
renderHTML: (attributes) => {
|
|
177
|
-
if (!attributes[attr]) return {};
|
|
178
|
-
return { [attr]: attributes[attr] };
|
|
179
|
-
}
|
|
180
|
-
}]));
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Common HTML attributes used across multiple extensions.
|
|
184
|
-
* These preserve attributes during HTML import and editing for better
|
|
185
|
-
* fidelity when importing existing email templates.
|
|
186
|
-
*/
|
|
187
|
-
const COMMON_HTML_ATTRIBUTES = [
|
|
188
|
-
"id",
|
|
189
|
-
"class",
|
|
190
|
-
"title",
|
|
191
|
-
"lang",
|
|
192
|
-
"dir",
|
|
193
|
-
"data-id"
|
|
194
|
-
];
|
|
195
|
-
/**
|
|
196
|
-
* Layout-specific HTML attributes used for positioning and sizing.
|
|
197
|
-
*/
|
|
198
|
-
const LAYOUT_ATTRIBUTES = [
|
|
199
|
-
"align",
|
|
200
|
-
"width",
|
|
201
|
-
"height"
|
|
202
|
-
];
|
|
203
|
-
/**
|
|
204
|
-
* Table-specific HTML attributes used for table layout and styling.
|
|
205
|
-
*/
|
|
206
|
-
const TABLE_ATTRIBUTES = [
|
|
207
|
-
"border",
|
|
208
|
-
"cellpadding",
|
|
209
|
-
"cellspacing"
|
|
210
|
-
];
|
|
211
|
-
/**
|
|
212
|
-
* Table cell-specific HTML attributes.
|
|
213
|
-
*/
|
|
214
|
-
const TABLE_CELL_ATTRIBUTES = [
|
|
215
|
-
"valign",
|
|
216
|
-
"bgcolor",
|
|
217
|
-
"colspan",
|
|
218
|
-
"rowspan"
|
|
219
|
-
];
|
|
220
|
-
/**
|
|
221
|
-
* Table header cell-specific HTML attributes.
|
|
222
|
-
* These are additional attributes that only apply to <th> elements.
|
|
223
|
-
*/
|
|
224
|
-
const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
|
|
225
|
-
|
|
226
76
|
//#endregion
|
|
227
77
|
//#region src/utils/styles.ts
|
|
228
78
|
const WHITE_SPACE_REGEX = /\s+/;
|
|
@@ -379,18 +229,354 @@ function convertBorderValue(value) {
|
|
|
379
229
|
* shorthand properties (margin, padding) to longhand before merging.
|
|
380
230
|
* This prevents shorthand properties from overriding specific longhand properties.
|
|
381
231
|
*
|
|
382
|
-
* @param resetStyles - Base reset styles that may contain shorthand properties
|
|
383
|
-
* @param inlineStyles - Inline styles that should override reset styles
|
|
384
|
-
* @returns Merged styles with inline styles taking precedence
|
|
232
|
+
* @param resetStyles - Base reset styles that may contain shorthand properties
|
|
233
|
+
* @param inlineStyles - Inline styles that should override reset styles
|
|
234
|
+
* @returns Merged styles with inline styles taking precedence
|
|
235
|
+
*/
|
|
236
|
+
function resolveConflictingStyles(resetStyles, inlineStyles) {
|
|
237
|
+
const expandedResetStyles = expandShorthandProperties(resetStyles);
|
|
238
|
+
const expandedInlineStyles = expandShorthandProperties(inlineStyles);
|
|
239
|
+
return {
|
|
240
|
+
...expandedResetStyles,
|
|
241
|
+
...expandedInlineStyles
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/core/serializer/default-base-template.tsx
|
|
247
|
+
function DefaultBaseTemplate({ children, previewText }) {
|
|
248
|
+
return /* @__PURE__ */ jsxs(Html, { children: [
|
|
249
|
+
/* @__PURE__ */ jsxs(Head, { children: [
|
|
250
|
+
/* @__PURE__ */ jsx("meta", {
|
|
251
|
+
content: "width=device-width",
|
|
252
|
+
name: "viewport"
|
|
253
|
+
}),
|
|
254
|
+
/* @__PURE__ */ jsx("meta", {
|
|
255
|
+
content: "IE=edge",
|
|
256
|
+
httpEquiv: "X-UA-Compatible"
|
|
257
|
+
}),
|
|
258
|
+
/* @__PURE__ */ jsx("meta", { name: "x-apple-disable-message-reformatting" }),
|
|
259
|
+
/* @__PURE__ */ jsx("meta", {
|
|
260
|
+
content: "telephone=no,address=no,email=no,date=no,url=no",
|
|
261
|
+
name: "format-detection"
|
|
262
|
+
})
|
|
263
|
+
] }),
|
|
264
|
+
previewText && previewText !== "" && /* @__PURE__ */ jsx(Preview, { children: previewText }),
|
|
265
|
+
/* @__PURE__ */ jsx(Body$1, { children: /* @__PURE__ */ jsx(Section$1, {
|
|
266
|
+
width: "100%",
|
|
267
|
+
align: "center",
|
|
268
|
+
children: /* @__PURE__ */ jsx(Section$1, {
|
|
269
|
+
style: { width: "100%" },
|
|
270
|
+
children
|
|
271
|
+
})
|
|
272
|
+
}) })
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/core/serializer/email-mark.ts
|
|
278
|
+
var EmailMark = class EmailMark extends Mark {
|
|
279
|
+
constructor(config) {
|
|
280
|
+
super(config);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Create a new Mark instance
|
|
284
|
+
* @param config - Mark configuration object or a function that returns a configuration object
|
|
285
|
+
*/
|
|
286
|
+
static create(config) {
|
|
287
|
+
return new EmailMark(typeof config === "function" ? config() : config);
|
|
288
|
+
}
|
|
289
|
+
static from(mark, renderToReactEmail) {
|
|
290
|
+
const customMark = EmailMark.create({});
|
|
291
|
+
Object.assign(customMark, { ...mark });
|
|
292
|
+
customMark.config = {
|
|
293
|
+
...mark.config,
|
|
294
|
+
renderToReactEmail
|
|
295
|
+
};
|
|
296
|
+
return customMark;
|
|
297
|
+
}
|
|
298
|
+
configure(options) {
|
|
299
|
+
return super.configure(options);
|
|
300
|
+
}
|
|
301
|
+
extend(extendedConfig) {
|
|
302
|
+
const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
|
|
303
|
+
return super.extend(resolvedConfig);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/core/serializer/email-node.ts
|
|
309
|
+
var EmailNode = class EmailNode extends Node$1 {
|
|
310
|
+
constructor(config) {
|
|
311
|
+
super(config);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Create a new Node instance
|
|
315
|
+
* @param config - Node configuration object or a function that returns a configuration object
|
|
316
|
+
*/
|
|
317
|
+
static create(config) {
|
|
318
|
+
return new EmailNode(typeof config === "function" ? config() : config);
|
|
319
|
+
}
|
|
320
|
+
static from(node, renderToReactEmail) {
|
|
321
|
+
const customNode = EmailNode.create({});
|
|
322
|
+
Object.assign(customNode, { ...node });
|
|
323
|
+
customNode.config = {
|
|
324
|
+
...node.config,
|
|
325
|
+
renderToReactEmail
|
|
326
|
+
};
|
|
327
|
+
return customNode;
|
|
328
|
+
}
|
|
329
|
+
configure(options) {
|
|
330
|
+
return super.configure(options);
|
|
331
|
+
}
|
|
332
|
+
extend(extendedConfig) {
|
|
333
|
+
const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
|
|
334
|
+
return super.extend(resolvedConfig);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/core/serializer/compose-react-email.tsx
|
|
340
|
+
const MARK_ORDER = {
|
|
341
|
+
preservedStyle: 0,
|
|
342
|
+
italic: 1,
|
|
343
|
+
strike: 2,
|
|
344
|
+
underline: 3,
|
|
345
|
+
link: 4,
|
|
346
|
+
bold: 5,
|
|
347
|
+
code: 6
|
|
348
|
+
};
|
|
349
|
+
const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
|
|
350
|
+
function getOrderedMarks(marks) {
|
|
351
|
+
if (!marks) return [];
|
|
352
|
+
return [...marks].sort((a, b) => (MARK_ORDER[a.type] ?? Number.MAX_SAFE_INTEGER) - (MARK_ORDER[b.type] ?? Number.MAX_SAFE_INTEGER));
|
|
353
|
+
}
|
|
354
|
+
const composeReactEmail = async ({ editor, preview }) => {
|
|
355
|
+
const data = editor.getJSON();
|
|
356
|
+
const extensions = editor.extensionManager.extensions;
|
|
357
|
+
const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
|
|
358
|
+
const emailNodeComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailNode).map((extension) => [extension.name, extension.config.renderToReactEmail]));
|
|
359
|
+
const emailMarkComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailMark).map((extension) => [extension.name, extension.config.renderToReactEmail]));
|
|
360
|
+
function renderMark(mark, node, children, depth) {
|
|
361
|
+
const markStyle = serializerPlugin?.getNodeStyles({
|
|
362
|
+
type: mark.type,
|
|
363
|
+
attrs: mark.attrs ?? {}
|
|
364
|
+
}, depth, editor) ?? {};
|
|
365
|
+
const markRenderer = emailMarkComponentRegistry[mark.type];
|
|
366
|
+
if (markRenderer) return markRenderer({
|
|
367
|
+
mark,
|
|
368
|
+
node,
|
|
369
|
+
style: markStyle,
|
|
370
|
+
children
|
|
371
|
+
});
|
|
372
|
+
return children;
|
|
373
|
+
}
|
|
374
|
+
function parseContent(content, depth = 0) {
|
|
375
|
+
if (!content) return;
|
|
376
|
+
return content.map((node, index) => {
|
|
377
|
+
const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
|
|
378
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
379
|
+
if (node.type && emailNodeComponentRegistry[node.type]) {
|
|
380
|
+
const Component = emailNodeComponentRegistry[node.type];
|
|
381
|
+
const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
|
|
382
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
383
|
+
node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
|
|
384
|
+
...node,
|
|
385
|
+
attrs: {
|
|
386
|
+
...node.attrs,
|
|
387
|
+
width: inlineStyles.width
|
|
388
|
+
}
|
|
389
|
+
} : node,
|
|
390
|
+
style,
|
|
391
|
+
children: parseContent(node.content, childDepth)
|
|
392
|
+
}, index);
|
|
393
|
+
}
|
|
394
|
+
switch (node.type) {
|
|
395
|
+
case "text": {
|
|
396
|
+
let wrappedText = node.text;
|
|
397
|
+
getOrderedMarks(node.marks).forEach((mark) => {
|
|
398
|
+
wrappedText = renderMark(mark, node, wrappedText, depth);
|
|
399
|
+
});
|
|
400
|
+
const textAttributes = node.marks?.find((mark) => mark.type === "textStyle")?.attrs;
|
|
401
|
+
return /* @__PURE__ */ jsx("span", {
|
|
402
|
+
style: {
|
|
403
|
+
...textAttributes,
|
|
404
|
+
...style
|
|
405
|
+
},
|
|
406
|
+
children: wrappedText
|
|
407
|
+
}, index);
|
|
408
|
+
}
|
|
409
|
+
default: return null;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
const unformattedHtml = await render(/* @__PURE__ */ jsx(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
|
|
414
|
+
previewText: preview,
|
|
415
|
+
editor,
|
|
416
|
+
children: parseContent(data.content)
|
|
417
|
+
}));
|
|
418
|
+
const [prettyHtml, text] = await Promise.all([pretty(unformattedHtml), toPlainText(unformattedHtml)]);
|
|
419
|
+
return {
|
|
420
|
+
html: prettyHtml,
|
|
421
|
+
text
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/extensions/alignment-attribute.tsx
|
|
427
|
+
const AlignmentAttribute = Extension.create({
|
|
428
|
+
name: "alignmentAttribute",
|
|
429
|
+
addOptions() {
|
|
430
|
+
return {
|
|
431
|
+
types: [],
|
|
432
|
+
alignments: [
|
|
433
|
+
"left",
|
|
434
|
+
"center",
|
|
435
|
+
"right",
|
|
436
|
+
"justify"
|
|
437
|
+
]
|
|
438
|
+
};
|
|
439
|
+
},
|
|
440
|
+
addGlobalAttributes() {
|
|
441
|
+
return [{
|
|
442
|
+
types: this.options.types,
|
|
443
|
+
attributes: { alignment: {
|
|
444
|
+
parseHTML: (element) => {
|
|
445
|
+
const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
|
|
446
|
+
if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
|
|
447
|
+
return null;
|
|
448
|
+
},
|
|
449
|
+
renderHTML: (attributes) => {
|
|
450
|
+
if (attributes.alignment === "left") return {};
|
|
451
|
+
return { alignment: attributes.alignment };
|
|
452
|
+
}
|
|
453
|
+
} }
|
|
454
|
+
}];
|
|
455
|
+
},
|
|
456
|
+
addCommands() {
|
|
457
|
+
return { setAlignment: (alignment) => ({ commands }) => {
|
|
458
|
+
if (!this.options.alignments.includes(alignment)) return false;
|
|
459
|
+
return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
|
|
460
|
+
} };
|
|
461
|
+
},
|
|
462
|
+
addKeyboardShortcuts() {
|
|
463
|
+
return {
|
|
464
|
+
Enter: () => {
|
|
465
|
+
const { from } = this.editor.state.selection;
|
|
466
|
+
const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
|
|
467
|
+
if (currentAlignment) requestAnimationFrame(() => {
|
|
468
|
+
this.editor.commands.setAlignment(currentAlignment);
|
|
469
|
+
});
|
|
470
|
+
return false;
|
|
471
|
+
},
|
|
472
|
+
"Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
|
|
473
|
+
"Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
|
|
474
|
+
"Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
|
|
475
|
+
"Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/utils/get-text-alignment.ts
|
|
482
|
+
function getTextAlignment(alignment) {
|
|
483
|
+
switch (alignment) {
|
|
484
|
+
case "left": return { textAlign: "left" };
|
|
485
|
+
case "center": return { textAlign: "center" };
|
|
486
|
+
case "right": return { textAlign: "right" };
|
|
487
|
+
default: return {};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/extensions/blockquote.tsx
|
|
493
|
+
const Blockquote = EmailNode.from(BlockquoteBase, ({ children, node, style }) => /* @__PURE__ */ jsx("blockquote", {
|
|
494
|
+
className: node.attrs?.class || void 0,
|
|
495
|
+
style: {
|
|
496
|
+
...style,
|
|
497
|
+
...inlineCssToJs(node.attrs?.style),
|
|
498
|
+
...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
|
|
499
|
+
},
|
|
500
|
+
children
|
|
501
|
+
}));
|
|
502
|
+
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/utils/attribute-helpers.ts
|
|
505
|
+
/**
|
|
506
|
+
* Creates TipTap attribute definitions for a list of HTML attributes.
|
|
507
|
+
* Each attribute will have the same pattern:
|
|
508
|
+
* - default: null
|
|
509
|
+
* - parseHTML: extracts the attribute from the element
|
|
510
|
+
* - renderHTML: conditionally renders the attribute if it has a value
|
|
511
|
+
*
|
|
512
|
+
* @param attributeNames - Array of HTML attribute names to create definitions for
|
|
513
|
+
* @returns Object with TipTap attribute definitions
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* const attrs = createStandardAttributes(['class', 'id', 'title']);
|
|
517
|
+
* // Returns:
|
|
518
|
+
* // {
|
|
519
|
+
* // class: {
|
|
520
|
+
* // default: null,
|
|
521
|
+
* // parseHTML: (element) => element.getAttribute('class'),
|
|
522
|
+
* // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
|
|
523
|
+
* // },
|
|
524
|
+
* // ...
|
|
525
|
+
* // }
|
|
385
526
|
*/
|
|
386
|
-
function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
527
|
+
function createStandardAttributes(attributeNames) {
|
|
528
|
+
return Object.fromEntries(attributeNames.map((attr) => [attr, {
|
|
529
|
+
default: null,
|
|
530
|
+
parseHTML: (element) => element.getAttribute(attr),
|
|
531
|
+
renderHTML: (attributes) => {
|
|
532
|
+
if (!attributes[attr]) return {};
|
|
533
|
+
return { [attr]: attributes[attr] };
|
|
534
|
+
}
|
|
535
|
+
}]));
|
|
393
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Common HTML attributes used across multiple extensions.
|
|
539
|
+
* These preserve attributes during HTML import and editing for better
|
|
540
|
+
* fidelity when importing existing email templates.
|
|
541
|
+
*/
|
|
542
|
+
const COMMON_HTML_ATTRIBUTES = [
|
|
543
|
+
"id",
|
|
544
|
+
"class",
|
|
545
|
+
"title",
|
|
546
|
+
"lang",
|
|
547
|
+
"dir",
|
|
548
|
+
"data-id"
|
|
549
|
+
];
|
|
550
|
+
/**
|
|
551
|
+
* Layout-specific HTML attributes used for positioning and sizing.
|
|
552
|
+
*/
|
|
553
|
+
const LAYOUT_ATTRIBUTES = [
|
|
554
|
+
"align",
|
|
555
|
+
"width",
|
|
556
|
+
"height"
|
|
557
|
+
];
|
|
558
|
+
/**
|
|
559
|
+
* Table-specific HTML attributes used for table layout and styling.
|
|
560
|
+
*/
|
|
561
|
+
const TABLE_ATTRIBUTES = [
|
|
562
|
+
"border",
|
|
563
|
+
"cellpadding",
|
|
564
|
+
"cellspacing"
|
|
565
|
+
];
|
|
566
|
+
/**
|
|
567
|
+
* Table cell-specific HTML attributes.
|
|
568
|
+
*/
|
|
569
|
+
const TABLE_CELL_ATTRIBUTES = [
|
|
570
|
+
"valign",
|
|
571
|
+
"bgcolor",
|
|
572
|
+
"colspan",
|
|
573
|
+
"rowspan"
|
|
574
|
+
];
|
|
575
|
+
/**
|
|
576
|
+
* Table header cell-specific HTML attributes.
|
|
577
|
+
* These are additional attributes that only apply to <th> elements.
|
|
578
|
+
*/
|
|
579
|
+
const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
|
|
394
580
|
|
|
395
581
|
//#endregion
|
|
396
582
|
//#region src/extensions/body.tsx
|
|
@@ -438,7 +624,7 @@ const Body = EmailNode.create({
|
|
|
438
624
|
});
|
|
439
625
|
|
|
440
626
|
//#endregion
|
|
441
|
-
//#region src/extensions/bold.
|
|
627
|
+
//#region src/extensions/bold.tsx
|
|
442
628
|
/**
|
|
443
629
|
* Matches bold text via `**` as input.
|
|
444
630
|
*/
|
|
@@ -459,7 +645,7 @@ const underscorePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
|
|
|
459
645
|
* This extension allows you to mark text as bold.
|
|
460
646
|
* @see https://tiptap.dev/api/marks/bold
|
|
461
647
|
*/
|
|
462
|
-
const Bold =
|
|
648
|
+
const Bold = EmailMark.create({
|
|
463
649
|
name: "bold",
|
|
464
650
|
addOptions() {
|
|
465
651
|
return { HTMLAttributes: {} };
|
|
@@ -484,6 +670,12 @@ const Bold = Mark.create({
|
|
|
484
670
|
0
|
|
485
671
|
];
|
|
486
672
|
},
|
|
673
|
+
renderToReactEmail({ children, style }) {
|
|
674
|
+
return /* @__PURE__ */ jsx("strong", {
|
|
675
|
+
style,
|
|
676
|
+
children
|
|
677
|
+
});
|
|
678
|
+
},
|
|
487
679
|
addCommands() {
|
|
488
680
|
return {
|
|
489
681
|
setBold: () => ({ commands }) => {
|
|
@@ -523,6 +715,17 @@ const Bold = Mark.create({
|
|
|
523
715
|
}
|
|
524
716
|
});
|
|
525
717
|
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/extensions/bullet-list.tsx
|
|
720
|
+
const BulletList = EmailNode.from(BulletListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ul", {
|
|
721
|
+
className: node.attrs?.class || void 0,
|
|
722
|
+
style: {
|
|
723
|
+
...style,
|
|
724
|
+
...inlineCssToJs(node.attrs?.style)
|
|
725
|
+
},
|
|
726
|
+
children
|
|
727
|
+
}));
|
|
728
|
+
|
|
526
729
|
//#endregion
|
|
527
730
|
//#region src/extensions/button.tsx
|
|
528
731
|
const Button = EmailNode.create({
|
|
@@ -644,6 +847,16 @@ const ClassAttribute = Extension.create({
|
|
|
644
847
|
}
|
|
645
848
|
});
|
|
646
849
|
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/extensions/code.tsx
|
|
852
|
+
const Code = EmailMark.from(CodeBase, ({ children, node, style }) => /* @__PURE__ */ jsx("code", {
|
|
853
|
+
style: {
|
|
854
|
+
...style,
|
|
855
|
+
...inlineCssToJs(node.attrs?.style)
|
|
856
|
+
},
|
|
857
|
+
children
|
|
858
|
+
}));
|
|
859
|
+
|
|
647
860
|
//#endregion
|
|
648
861
|
//#region src/utils/prism-utils.ts
|
|
649
862
|
const publicURL = "/styles/prism";
|
|
@@ -878,16 +1091,314 @@ const CodeBlockPrism = EmailNode.from(CodeBlock$1.extend({
|
|
|
878
1091
|
});
|
|
879
1092
|
|
|
880
1093
|
//#endregion
|
|
881
|
-
//#region src/extensions/div.tsx
|
|
882
|
-
const Div = EmailNode.create({
|
|
883
|
-
name: "div",
|
|
884
|
-
group: "block",
|
|
885
|
-
content: "block+",
|
|
886
|
-
defining: true,
|
|
887
|
-
isolating: true,
|
|
1094
|
+
//#region src/extensions/div.tsx
|
|
1095
|
+
const Div = EmailNode.create({
|
|
1096
|
+
name: "div",
|
|
1097
|
+
group: "block",
|
|
1098
|
+
content: "block+",
|
|
1099
|
+
defining: true,
|
|
1100
|
+
isolating: true,
|
|
1101
|
+
parseHTML() {
|
|
1102
|
+
return [{
|
|
1103
|
+
tag: "div:not([data-type])",
|
|
1104
|
+
getAttrs: (node) => {
|
|
1105
|
+
if (typeof node === "string") return false;
|
|
1106
|
+
const element = node;
|
|
1107
|
+
const attrs = {};
|
|
1108
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
1109
|
+
attrs[attr.name] = attr.value;
|
|
1110
|
+
});
|
|
1111
|
+
return attrs;
|
|
1112
|
+
}
|
|
1113
|
+
}];
|
|
1114
|
+
},
|
|
1115
|
+
renderHTML({ HTMLAttributes }) {
|
|
1116
|
+
return [
|
|
1117
|
+
"div",
|
|
1118
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
1119
|
+
0
|
|
1120
|
+
];
|
|
1121
|
+
},
|
|
1122
|
+
addAttributes() {
|
|
1123
|
+
return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
|
|
1124
|
+
},
|
|
1125
|
+
renderToReactEmail({ children, node, style }) {
|
|
1126
|
+
const inlineStyles = inlineCssToJs(node.attrs?.style);
|
|
1127
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1128
|
+
className: node.attrs?.class || void 0,
|
|
1129
|
+
style: {
|
|
1130
|
+
...style,
|
|
1131
|
+
...inlineStyles
|
|
1132
|
+
},
|
|
1133
|
+
children
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
//#endregion
|
|
1139
|
+
//#region src/extensions/divider.tsx
|
|
1140
|
+
const Divider = EmailNode.from(HorizontalRule.extend({
|
|
1141
|
+
addAttributes() {
|
|
1142
|
+
return { class: { default: "divider" } };
|
|
1143
|
+
},
|
|
1144
|
+
addInputRules() {
|
|
1145
|
+
return [new InputRule({
|
|
1146
|
+
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
|
|
1147
|
+
handler: ({ state, range }) => {
|
|
1148
|
+
const attributes = {};
|
|
1149
|
+
const { tr } = state;
|
|
1150
|
+
const start = range.from;
|
|
1151
|
+
const end = range.to;
|
|
1152
|
+
tr.insert(start - 1, this.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
|
|
1153
|
+
}
|
|
1154
|
+
})];
|
|
1155
|
+
},
|
|
1156
|
+
addNodeView() {
|
|
1157
|
+
return ReactNodeViewRenderer((props) => {
|
|
1158
|
+
const node = props.node;
|
|
1159
|
+
const { class: className, ...rest } = node.attrs;
|
|
1160
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(Hr, {
|
|
1161
|
+
...rest,
|
|
1162
|
+
className: "node-hr",
|
|
1163
|
+
style: inlineCssToJs(node.attrs.style)
|
|
1164
|
+
}) });
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}), ({ node, style }) => {
|
|
1168
|
+
return /* @__PURE__ */ jsx(Hr, {
|
|
1169
|
+
className: node.attrs?.class || void 0,
|
|
1170
|
+
style: {
|
|
1171
|
+
...style,
|
|
1172
|
+
...inlineCssToJs(node.attrs?.style)
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/extensions/global-content.ts
|
|
1179
|
+
const GLOBAL_CONTENT_NODE_TYPE = "globalContent";
|
|
1180
|
+
let cachedGlobalPosition = null;
|
|
1181
|
+
function findGlobalContentPositions(doc) {
|
|
1182
|
+
const positions = [];
|
|
1183
|
+
doc.descendants((node, position) => {
|
|
1184
|
+
if (node.type.name === GLOBAL_CONTENT_NODE_TYPE) positions.push(position);
|
|
1185
|
+
});
|
|
1186
|
+
return positions;
|
|
1187
|
+
}
|
|
1188
|
+
function getCachedGlobalContentPosition(doc) {
|
|
1189
|
+
if (cachedGlobalPosition != null) try {
|
|
1190
|
+
if (doc.nodeAt(cachedGlobalPosition)?.type.name === GLOBAL_CONTENT_NODE_TYPE) return cachedGlobalPosition;
|
|
1191
|
+
} catch {
|
|
1192
|
+
cachedGlobalPosition = null;
|
|
1193
|
+
}
|
|
1194
|
+
cachedGlobalPosition = findGlobalContentPositions(doc)[0] ?? null;
|
|
1195
|
+
return cachedGlobalPosition;
|
|
1196
|
+
}
|
|
1197
|
+
function getGlobalContent(key, editor) {
|
|
1198
|
+
const position = getCachedGlobalContentPosition(editor.state.doc);
|
|
1199
|
+
if (cachedGlobalPosition == null) return null;
|
|
1200
|
+
return editor.state.doc.nodeAt(position)?.attrs.data[key] ?? null;
|
|
1201
|
+
}
|
|
1202
|
+
const GlobalContent = Node$1.create({
|
|
1203
|
+
name: GLOBAL_CONTENT_NODE_TYPE,
|
|
1204
|
+
addOptions() {
|
|
1205
|
+
return {
|
|
1206
|
+
key: GLOBAL_CONTENT_NODE_TYPE,
|
|
1207
|
+
data: {}
|
|
1208
|
+
};
|
|
1209
|
+
},
|
|
1210
|
+
group: "block",
|
|
1211
|
+
selectable: false,
|
|
1212
|
+
draggable: false,
|
|
1213
|
+
atom: true,
|
|
1214
|
+
addAttributes() {
|
|
1215
|
+
return { data: { default: this.options.data } };
|
|
1216
|
+
},
|
|
1217
|
+
parseHTML() {
|
|
1218
|
+
return [{ tag: `div[data-type="${this.name}"]` }];
|
|
1219
|
+
},
|
|
1220
|
+
renderHTML({ HTMLAttributes }) {
|
|
1221
|
+
return ["div", mergeAttributes(HTMLAttributes, {
|
|
1222
|
+
"data-type": this.name,
|
|
1223
|
+
style: "width: 100%; height: 1px; visibility: hidden;"
|
|
1224
|
+
})];
|
|
1225
|
+
},
|
|
1226
|
+
addCommands() {
|
|
1227
|
+
return { setGlobalContent: (key, value) => ({ tr, dispatch }) => {
|
|
1228
|
+
const ensureGlobalPosition = () => {
|
|
1229
|
+
const positions = findGlobalContentPositions(tr.doc);
|
|
1230
|
+
for (let i = positions.length - 1; i > 0; i--) tr.delete(positions[i], positions[i] + 1);
|
|
1231
|
+
const pos = positions[0] ?? -1;
|
|
1232
|
+
if (pos >= 0) cachedGlobalPosition = pos;
|
|
1233
|
+
else {
|
|
1234
|
+
cachedGlobalPosition = 0;
|
|
1235
|
+
tr.insert(0, this.type.create());
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
if (dispatch) {
|
|
1239
|
+
ensureGlobalPosition();
|
|
1240
|
+
if (cachedGlobalPosition == null) return false;
|
|
1241
|
+
tr.setNodeAttribute(cachedGlobalPosition, "data", {
|
|
1242
|
+
...tr.doc.nodeAt(cachedGlobalPosition)?.attrs.data,
|
|
1243
|
+
[key]: value
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
return true;
|
|
1247
|
+
} };
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
//#endregion
|
|
1252
|
+
//#region src/extensions/hard-break.tsx
|
|
1253
|
+
const HardBreak = EmailNode.from(HardBreakBase, () => /* @__PURE__ */ jsx("br", {}));
|
|
1254
|
+
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/extensions/heading.tsx
|
|
1257
|
+
const Heading$2 = EmailNode.from(Heading$1.extend({ addNodeView() {
|
|
1258
|
+
return ReactNodeViewRenderer(({ node }) => {
|
|
1259
|
+
const level = node.attrs.level ?? 1;
|
|
1260
|
+
const { class: className, ...rest } = node.attrs;
|
|
1261
|
+
const attrs = {
|
|
1262
|
+
...rest,
|
|
1263
|
+
className: `node-h${level} ${className}`,
|
|
1264
|
+
style: inlineCssToJs(node.attrs.style)
|
|
1265
|
+
};
|
|
1266
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(Heading, {
|
|
1267
|
+
as: `h${level}`,
|
|
1268
|
+
...attrs,
|
|
1269
|
+
children: /* @__PURE__ */ jsx(NodeViewContent, {})
|
|
1270
|
+
}) });
|
|
1271
|
+
});
|
|
1272
|
+
} }), ({ children, node, style }) => {
|
|
1273
|
+
return /* @__PURE__ */ jsx(Heading, {
|
|
1274
|
+
as: `h${node.attrs?.level ?? 1}`,
|
|
1275
|
+
className: node.attrs?.class || void 0,
|
|
1276
|
+
style: {
|
|
1277
|
+
...style,
|
|
1278
|
+
...inlineCssToJs(node.attrs?.style),
|
|
1279
|
+
...getTextAlignment(node.attrs?.align ?? node.attrs?.alignment)
|
|
1280
|
+
},
|
|
1281
|
+
children
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
//#endregion
|
|
1286
|
+
//#region src/extensions/italic.tsx
|
|
1287
|
+
const Italic = EmailMark.from(ItalicBase, ({ children, style }) => /* @__PURE__ */ jsx("em", {
|
|
1288
|
+
style,
|
|
1289
|
+
children
|
|
1290
|
+
}));
|
|
1291
|
+
|
|
1292
|
+
//#endregion
|
|
1293
|
+
//#region src/extensions/preserved-style.tsx
|
|
1294
|
+
const PreservedStyle = EmailMark.create({
|
|
1295
|
+
name: "preservedStyle",
|
|
1296
|
+
addAttributes() {
|
|
1297
|
+
return { style: {
|
|
1298
|
+
default: null,
|
|
1299
|
+
parseHTML: (element) => element.getAttribute("style"),
|
|
1300
|
+
renderHTML: (attributes) => {
|
|
1301
|
+
if (!attributes.style) return {};
|
|
1302
|
+
return { style: attributes.style };
|
|
1303
|
+
}
|
|
1304
|
+
} };
|
|
1305
|
+
},
|
|
1306
|
+
parseHTML() {
|
|
1307
|
+
return [{
|
|
1308
|
+
tag: "span[style]",
|
|
1309
|
+
getAttrs: (element) => {
|
|
1310
|
+
if (typeof element === "string") return false;
|
|
1311
|
+
const style = element.getAttribute("style");
|
|
1312
|
+
if (style && hasPreservableStyles(style)) return { style };
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
}];
|
|
1316
|
+
},
|
|
1317
|
+
renderHTML({ HTMLAttributes }) {
|
|
1318
|
+
return [
|
|
1319
|
+
"span",
|
|
1320
|
+
mergeAttributes(HTMLAttributes),
|
|
1321
|
+
0
|
|
1322
|
+
];
|
|
1323
|
+
},
|
|
1324
|
+
renderToReactEmail({ children, mark }) {
|
|
1325
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1326
|
+
style: mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : void 0,
|
|
1327
|
+
children
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
const LINK_INDICATOR_STYLES = [
|
|
1332
|
+
"color",
|
|
1333
|
+
"text-decoration",
|
|
1334
|
+
"text-decoration-line",
|
|
1335
|
+
"text-decoration-color",
|
|
1336
|
+
"text-decoration-style"
|
|
1337
|
+
];
|
|
1338
|
+
function parseStyleString(styleString) {
|
|
1339
|
+
const temp = document.createElement("div");
|
|
1340
|
+
temp.style.cssText = styleString;
|
|
1341
|
+
return temp.style;
|
|
1342
|
+
}
|
|
1343
|
+
function hasBackground(style) {
|
|
1344
|
+
const bgColor = style.backgroundColor;
|
|
1345
|
+
const bg = style.background;
|
|
1346
|
+
if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
|
|
1347
|
+
if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
function hasPreservableStyles(styleString) {
|
|
1351
|
+
return processStylesForUnlink(styleString) !== null;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Processes styles when unlinking:
|
|
1355
|
+
* - Has background (button-like): preserve all styles
|
|
1356
|
+
* - No background: strip link-indicator styles (color, text-decoration), keep the rest
|
|
1357
|
+
*/
|
|
1358
|
+
function processStylesForUnlink(styleString) {
|
|
1359
|
+
if (!styleString) return null;
|
|
1360
|
+
const style = parseStyleString(styleString);
|
|
1361
|
+
if (hasBackground(style)) return styleString;
|
|
1362
|
+
const filtered = [];
|
|
1363
|
+
for (let i = 0; i < style.length; i++) {
|
|
1364
|
+
const prop = style[i];
|
|
1365
|
+
if (LINK_INDICATOR_STYLES.includes(prop)) continue;
|
|
1366
|
+
const value = style.getPropertyValue(prop);
|
|
1367
|
+
if (value) filtered.push(`${prop}: ${value}`);
|
|
1368
|
+
}
|
|
1369
|
+
return filtered.length > 0 ? filtered.join("; ") : null;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
//#endregion
|
|
1373
|
+
//#region src/extensions/link.tsx
|
|
1374
|
+
const Link$1 = EmailMark.from(TiptapLink, ({ children, mark, style }) => {
|
|
1375
|
+
const linkMarkStyle = mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : {};
|
|
1376
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
1377
|
+
href: mark.attrs?.href ?? void 0,
|
|
1378
|
+
rel: mark.attrs?.rel ?? void 0,
|
|
1379
|
+
style: {
|
|
1380
|
+
...style,
|
|
1381
|
+
...linkMarkStyle
|
|
1382
|
+
},
|
|
1383
|
+
target: mark.attrs?.target ?? void 0,
|
|
1384
|
+
...mark.attrs?.["ses:no-track"] ? { "ses:no-track": mark.attrs["ses:no-track"] } : {},
|
|
1385
|
+
children
|
|
1386
|
+
});
|
|
1387
|
+
}).extend({
|
|
888
1388
|
parseHTML() {
|
|
889
1389
|
return [{
|
|
890
|
-
tag: "
|
|
1390
|
+
tag: "a[target]:not([data-id=\"react-email-button\"])",
|
|
1391
|
+
getAttrs: (node) => {
|
|
1392
|
+
if (typeof node === "string") return false;
|
|
1393
|
+
const element = node;
|
|
1394
|
+
const attrs = {};
|
|
1395
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
1396
|
+
attrs[attr.name] = attr.value;
|
|
1397
|
+
});
|
|
1398
|
+
return attrs;
|
|
1399
|
+
}
|
|
1400
|
+
}, {
|
|
1401
|
+
tag: "a[href]:not([data-id=\"react-email-button\"])",
|
|
891
1402
|
getAttrs: (node) => {
|
|
892
1403
|
if (typeof node === "string") return false;
|
|
893
1404
|
const element = node;
|
|
@@ -899,29 +1410,51 @@ const Div = EmailNode.create({
|
|
|
899
1410
|
}
|
|
900
1411
|
}];
|
|
901
1412
|
},
|
|
902
|
-
renderHTML({ HTMLAttributes }) {
|
|
903
|
-
return [
|
|
904
|
-
"div",
|
|
905
|
-
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
906
|
-
0
|
|
907
|
-
];
|
|
908
|
-
},
|
|
909
1413
|
addAttributes() {
|
|
910
|
-
return {
|
|
1414
|
+
return {
|
|
1415
|
+
...this.parent?.(),
|
|
1416
|
+
"ses:no-track": {
|
|
1417
|
+
default: null,
|
|
1418
|
+
parseHTML: (element) => element.getAttribute("ses:no-track")
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
911
1421
|
},
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1422
|
+
addCommands() {
|
|
1423
|
+
return {
|
|
1424
|
+
...this.parent?.(),
|
|
1425
|
+
unsetLink: () => ({ state, chain }) => {
|
|
1426
|
+
const { from } = state.selection;
|
|
1427
|
+
const linkStyle = state.doc.resolve(from).marks().find((m) => m.type.name === "link")?.attrs?.style ?? null;
|
|
1428
|
+
const preservedStyle = processStylesForUnlink(linkStyle);
|
|
1429
|
+
const shouldRemoveUnderline = preservedStyle !== linkStyle;
|
|
1430
|
+
if (preservedStyle) {
|
|
1431
|
+
const cmd = chain().extendMarkRange("link").unsetMark("link").setMark("preservedStyle", { style: preservedStyle });
|
|
1432
|
+
return shouldRemoveUnderline ? cmd.unsetMark("underline").run() : cmd.run();
|
|
1433
|
+
}
|
|
1434
|
+
return chain().extendMarkRange("link").unsetMark("link").unsetMark("underline").run();
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
},
|
|
1438
|
+
addKeyboardShortcuts() {
|
|
1439
|
+
return { "Mod-k": () => {
|
|
1440
|
+
editorEventBus.dispatch("bubble-menu:add-link", void 0);
|
|
1441
|
+
return this.editor.chain().focus().toggleLink({ href: "" }).run();
|
|
1442
|
+
} };
|
|
922
1443
|
}
|
|
923
1444
|
});
|
|
924
1445
|
|
|
1446
|
+
//#endregion
|
|
1447
|
+
//#region src/extensions/list-item.tsx
|
|
1448
|
+
const ListItem = EmailNode.from(ListItemBase, ({ children, node, style }) => /* @__PURE__ */ jsx("li", {
|
|
1449
|
+
className: node.attrs?.class || void 0,
|
|
1450
|
+
style: {
|
|
1451
|
+
...style,
|
|
1452
|
+
...inlineCssToJs(node.attrs?.style),
|
|
1453
|
+
...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
|
|
1454
|
+
},
|
|
1455
|
+
children
|
|
1456
|
+
}));
|
|
1457
|
+
|
|
925
1458
|
//#endregion
|
|
926
1459
|
//#region src/extensions/max-nesting.ts
|
|
927
1460
|
const MaxNesting = Extension.create({
|
|
@@ -997,6 +1530,33 @@ const MaxNesting = Extension.create({
|
|
|
997
1530
|
}
|
|
998
1531
|
});
|
|
999
1532
|
|
|
1533
|
+
//#endregion
|
|
1534
|
+
//#region src/extensions/ordered-list.tsx
|
|
1535
|
+
const OrderedList = EmailNode.from(OrderedListBase, ({ children, node, style }) => /* @__PURE__ */ jsx("ol", {
|
|
1536
|
+
className: node.attrs?.class || void 0,
|
|
1537
|
+
start: node.attrs?.start,
|
|
1538
|
+
style: {
|
|
1539
|
+
...style,
|
|
1540
|
+
...inlineCssToJs(node.attrs?.style)
|
|
1541
|
+
},
|
|
1542
|
+
children
|
|
1543
|
+
}));
|
|
1544
|
+
|
|
1545
|
+
//#endregion
|
|
1546
|
+
//#region src/extensions/paragraph.tsx
|
|
1547
|
+
const Paragraph = EmailNode.from(ParagraphBase, ({ children, node, style }) => {
|
|
1548
|
+
const isEmpty = !node.content || node.content.length === 0;
|
|
1549
|
+
return /* @__PURE__ */ jsx("p", {
|
|
1550
|
+
className: node.attrs?.class || void 0,
|
|
1551
|
+
style: {
|
|
1552
|
+
...style,
|
|
1553
|
+
...inlineCssToJs(node.attrs?.style),
|
|
1554
|
+
...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
|
|
1555
|
+
},
|
|
1556
|
+
children: isEmpty ? /* @__PURE__ */ jsx("br", {}) : children
|
|
1557
|
+
});
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1000
1560
|
//#endregion
|
|
1001
1561
|
//#region src/extensions/placeholder.ts
|
|
1002
1562
|
const Placeholder = TipTapPlaceholder.configure({
|
|
@@ -1007,83 +1567,9 @@ const Placeholder = TipTapPlaceholder.configure({
|
|
|
1007
1567
|
includeChildren: true
|
|
1008
1568
|
});
|
|
1009
1569
|
|
|
1010
|
-
//#endregion
|
|
1011
|
-
//#region src/extensions/preserved-style.ts
|
|
1012
|
-
const PreservedStyle = Mark.create({
|
|
1013
|
-
name: "preservedStyle",
|
|
1014
|
-
addAttributes() {
|
|
1015
|
-
return { style: {
|
|
1016
|
-
default: null,
|
|
1017
|
-
parseHTML: (element) => element.getAttribute("style"),
|
|
1018
|
-
renderHTML: (attributes) => {
|
|
1019
|
-
if (!attributes.style) return {};
|
|
1020
|
-
return { style: attributes.style };
|
|
1021
|
-
}
|
|
1022
|
-
} };
|
|
1023
|
-
},
|
|
1024
|
-
parseHTML() {
|
|
1025
|
-
return [{
|
|
1026
|
-
tag: "span[style]",
|
|
1027
|
-
getAttrs: (element) => {
|
|
1028
|
-
if (typeof element === "string") return false;
|
|
1029
|
-
const style = element.getAttribute("style");
|
|
1030
|
-
if (style && hasPreservableStyles(style)) return { style };
|
|
1031
|
-
return false;
|
|
1032
|
-
}
|
|
1033
|
-
}];
|
|
1034
|
-
},
|
|
1035
|
-
renderHTML({ HTMLAttributes }) {
|
|
1036
|
-
return [
|
|
1037
|
-
"span",
|
|
1038
|
-
mergeAttributes(HTMLAttributes),
|
|
1039
|
-
0
|
|
1040
|
-
];
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
const LINK_INDICATOR_STYLES = [
|
|
1044
|
-
"color",
|
|
1045
|
-
"text-decoration",
|
|
1046
|
-
"text-decoration-line",
|
|
1047
|
-
"text-decoration-color",
|
|
1048
|
-
"text-decoration-style"
|
|
1049
|
-
];
|
|
1050
|
-
function parseStyleString(styleString) {
|
|
1051
|
-
const temp = document.createElement("div");
|
|
1052
|
-
temp.style.cssText = styleString;
|
|
1053
|
-
return temp.style;
|
|
1054
|
-
}
|
|
1055
|
-
function hasBackground(style) {
|
|
1056
|
-
const bgColor = style.backgroundColor;
|
|
1057
|
-
const bg = style.background;
|
|
1058
|
-
if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
|
|
1059
|
-
if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
|
|
1060
|
-
return false;
|
|
1061
|
-
}
|
|
1062
|
-
function hasPreservableStyles(styleString) {
|
|
1063
|
-
return processStylesForUnlink(styleString) !== null;
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Processes styles when unlinking:
|
|
1067
|
-
* - Has background (button-like): preserve all styles
|
|
1068
|
-
* - No background: strip link-indicator styles (color, text-decoration), keep the rest
|
|
1069
|
-
*/
|
|
1070
|
-
function processStylesForUnlink(styleString) {
|
|
1071
|
-
if (!styleString) return null;
|
|
1072
|
-
const style = parseStyleString(styleString);
|
|
1073
|
-
if (hasBackground(style)) return styleString;
|
|
1074
|
-
const filtered = [];
|
|
1075
|
-
for (let i = 0; i < style.length; i++) {
|
|
1076
|
-
const prop = style[i];
|
|
1077
|
-
if (LINK_INDICATOR_STYLES.includes(prop)) continue;
|
|
1078
|
-
const value = style.getPropertyValue(prop);
|
|
1079
|
-
if (value) filtered.push(`${prop}: ${value}`);
|
|
1080
|
-
}
|
|
1081
|
-
return filtered.length > 0 ? filtered.join("; ") : null;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
1570
|
//#endregion
|
|
1085
1571
|
//#region src/extensions/preview-text.ts
|
|
1086
|
-
const PreviewText = Node.create({
|
|
1572
|
+
const PreviewText = Node$1.create({
|
|
1087
1573
|
name: "previewText",
|
|
1088
1574
|
group: "block",
|
|
1089
1575
|
selectable: false,
|
|
@@ -1122,17 +1608,6 @@ const PreviewText = Node.create({
|
|
|
1122
1608
|
}
|
|
1123
1609
|
});
|
|
1124
1610
|
|
|
1125
|
-
//#endregion
|
|
1126
|
-
//#region src/utils/get-text-alignment.ts
|
|
1127
|
-
function getTextAlignment(alignment) {
|
|
1128
|
-
switch (alignment) {
|
|
1129
|
-
case "left": return { textAlign: "left" };
|
|
1130
|
-
case "center": return { textAlign: "center" };
|
|
1131
|
-
case "right": return { textAlign: "right" };
|
|
1132
|
-
default: return {};
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
1611
|
//#endregion
|
|
1137
1612
|
//#region src/extensions/section.tsx
|
|
1138
1613
|
const Section = EmailNode.create({
|
|
@@ -1181,6 +1656,13 @@ const Section = EmailNode.create({
|
|
|
1181
1656
|
}
|
|
1182
1657
|
});
|
|
1183
1658
|
|
|
1659
|
+
//#endregion
|
|
1660
|
+
//#region src/extensions/strike.tsx
|
|
1661
|
+
const Strike = EmailMark.from(StrikeBase, ({ children, style }) => /* @__PURE__ */ jsx("s", {
|
|
1662
|
+
style,
|
|
1663
|
+
children
|
|
1664
|
+
}));
|
|
1665
|
+
|
|
1184
1666
|
//#endregion
|
|
1185
1667
|
//#region src/extensions/style-attribute.tsx
|
|
1186
1668
|
const StyleAttribute = Extension.create({
|
|
@@ -1230,12 +1712,12 @@ const StyleAttribute = Extension.create({
|
|
|
1230
1712
|
});
|
|
1231
1713
|
|
|
1232
1714
|
//#endregion
|
|
1233
|
-
//#region src/extensions/sup.
|
|
1715
|
+
//#region src/extensions/sup.tsx
|
|
1234
1716
|
/**
|
|
1235
1717
|
* This extension allows you to mark text as superscript.
|
|
1236
1718
|
* @see https://tiptap.dev/api/marks/superscript
|
|
1237
1719
|
*/
|
|
1238
|
-
const Sup =
|
|
1720
|
+
const Sup = EmailMark.create({
|
|
1239
1721
|
name: "sup",
|
|
1240
1722
|
addOptions() {
|
|
1241
1723
|
return { HTMLAttributes: {} };
|
|
@@ -1250,6 +1732,12 @@ const Sup = Mark.create({
|
|
|
1250
1732
|
0
|
|
1251
1733
|
];
|
|
1252
1734
|
},
|
|
1735
|
+
renderToReactEmail({ children, style }) {
|
|
1736
|
+
return /* @__PURE__ */ jsx("sup", {
|
|
1737
|
+
style,
|
|
1738
|
+
children
|
|
1739
|
+
});
|
|
1740
|
+
},
|
|
1253
1741
|
addCommands() {
|
|
1254
1742
|
return {
|
|
1255
1743
|
setSup: () => ({ commands }) => {
|
|
@@ -1415,7 +1903,7 @@ const TableCell = EmailNode.create({
|
|
|
1415
1903
|
});
|
|
1416
1904
|
}
|
|
1417
1905
|
});
|
|
1418
|
-
const TableHeader = Node.create({
|
|
1906
|
+
const TableHeader = Node$1.create({
|
|
1419
1907
|
name: "tableHeader",
|
|
1420
1908
|
group: "tableCell",
|
|
1421
1909
|
content: "block+",
|
|
@@ -1452,8 +1940,8 @@ const TableHeader = Node.create({
|
|
|
1452
1940
|
});
|
|
1453
1941
|
|
|
1454
1942
|
//#endregion
|
|
1455
|
-
//#region src/extensions/uppercase.
|
|
1456
|
-
const Uppercase =
|
|
1943
|
+
//#region src/extensions/uppercase.tsx
|
|
1944
|
+
const Uppercase = EmailMark.create({
|
|
1457
1945
|
name: "uppercase",
|
|
1458
1946
|
addOptions() {
|
|
1459
1947
|
return { HTMLAttributes: {} };
|
|
@@ -1474,6 +1962,15 @@ const Uppercase = Mark.create({
|
|
|
1474
1962
|
0
|
|
1475
1963
|
];
|
|
1476
1964
|
},
|
|
1965
|
+
renderToReactEmail({ children, style }) {
|
|
1966
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1967
|
+
style: {
|
|
1968
|
+
...style,
|
|
1969
|
+
textTransform: "uppercase"
|
|
1970
|
+
},
|
|
1971
|
+
children
|
|
1972
|
+
});
|
|
1973
|
+
},
|
|
1477
1974
|
addCommands() {
|
|
1478
1975
|
return {
|
|
1479
1976
|
setUppercase: () => ({ commands }) => {
|
|
@@ -1661,39 +2158,23 @@ const ColumnsColumn = EmailNode.create({
|
|
|
1661
2158
|
|
|
1662
2159
|
//#endregion
|
|
1663
2160
|
//#region src/extensions/index.ts
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
bulletList: { HTMLAttributes: { class: "node-bulletList" } },
|
|
1675
|
-
paragraph: { HTMLAttributes: { class: "node-paragraph" } },
|
|
1676
|
-
orderedList: { HTMLAttributes: { class: "node-orderedList" } },
|
|
1677
|
-
blockquote: { HTMLAttributes: { class: "node-blockquote" } },
|
|
1678
|
-
codeBlock: false,
|
|
1679
|
-
code: { HTMLAttributes: {
|
|
1680
|
-
class: "node-inlineCode",
|
|
1681
|
-
spellcheck: "false"
|
|
1682
|
-
} },
|
|
1683
|
-
horizontalRule: false,
|
|
1684
|
-
dropcursor: {
|
|
1685
|
-
color: "#61a8f8",
|
|
1686
|
-
class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
|
|
1687
|
-
width: 4
|
|
1688
|
-
}
|
|
1689
|
-
}),
|
|
1690
|
-
CodeBlockPrism.configure({
|
|
1691
|
-
defaultLanguage: "javascript",
|
|
1692
|
-
HTMLAttributes: { class: "prism node-codeBlock" }
|
|
1693
|
-
}),
|
|
2161
|
+
const starterKitExtensions = {
|
|
2162
|
+
CodeBlockPrism,
|
|
2163
|
+
Code,
|
|
2164
|
+
Paragraph,
|
|
2165
|
+
BulletList,
|
|
2166
|
+
OrderedList,
|
|
2167
|
+
Blockquote,
|
|
2168
|
+
ListItem,
|
|
2169
|
+
HardBreak,
|
|
2170
|
+
Italic,
|
|
1694
2171
|
Placeholder,
|
|
1695
2172
|
PreviewText,
|
|
1696
2173
|
Bold,
|
|
2174
|
+
Strike,
|
|
2175
|
+
Heading: Heading$2,
|
|
2176
|
+
Divider,
|
|
2177
|
+
Link: Link$1,
|
|
1697
2178
|
Sup,
|
|
1698
2179
|
Uppercase,
|
|
1699
2180
|
PreservedStyle,
|
|
@@ -1705,80 +2186,378 @@ const coreExtensions = [
|
|
|
1705
2186
|
Div,
|
|
1706
2187
|
Button,
|
|
1707
2188
|
Section,
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2189
|
+
GlobalContent,
|
|
2190
|
+
AlignmentAttribute,
|
|
2191
|
+
StyleAttribute,
|
|
2192
|
+
ClassAttribute,
|
|
2193
|
+
MaxNesting
|
|
2194
|
+
};
|
|
2195
|
+
const StarterKit = Extension.create({
|
|
2196
|
+
name: "reactEmailStarterKit",
|
|
2197
|
+
addOptions() {
|
|
2198
|
+
return {
|
|
2199
|
+
TiptapStarterKit: {},
|
|
2200
|
+
CodeBlockPrism: {
|
|
2201
|
+
defaultLanguage: "javascript",
|
|
2202
|
+
HTMLAttributes: { class: "prism node-codeBlock" }
|
|
2203
|
+
},
|
|
2204
|
+
Code: { HTMLAttributes: {
|
|
2205
|
+
class: "node-inlineCode",
|
|
2206
|
+
spellcheck: "false"
|
|
2207
|
+
} },
|
|
2208
|
+
Paragraph: { HTMLAttributes: { class: "node-paragraph" } },
|
|
2209
|
+
BulletList: { HTMLAttributes: { class: "node-bulletList" } },
|
|
2210
|
+
OrderedList: { HTMLAttributes: { class: "node-orderedList" } },
|
|
2211
|
+
Blockquote: { HTMLAttributes: { class: "node-blockquote" } },
|
|
2212
|
+
ListItem: {},
|
|
2213
|
+
HardBreak: {},
|
|
2214
|
+
Italic: {},
|
|
2215
|
+
Placeholder: {},
|
|
2216
|
+
PreviewText: {},
|
|
2217
|
+
Bold: {},
|
|
2218
|
+
Strike: {},
|
|
2219
|
+
Heading: {},
|
|
2220
|
+
Divider: {},
|
|
2221
|
+
Link: {},
|
|
2222
|
+
Sup: {},
|
|
2223
|
+
Uppercase: {},
|
|
2224
|
+
PreservedStyle: {},
|
|
2225
|
+
Table: {},
|
|
2226
|
+
TableRow: {},
|
|
2227
|
+
TableCell: {},
|
|
2228
|
+
TableHeader: {},
|
|
2229
|
+
Body: {},
|
|
2230
|
+
Div: {},
|
|
2231
|
+
Button: {},
|
|
2232
|
+
Section: {},
|
|
2233
|
+
GlobalContent: {},
|
|
2234
|
+
AlignmentAttribute: { types: [
|
|
2235
|
+
"heading",
|
|
2236
|
+
"paragraph",
|
|
2237
|
+
"image",
|
|
2238
|
+
"blockquote",
|
|
2239
|
+
"codeBlock",
|
|
2240
|
+
"bulletList",
|
|
2241
|
+
"orderedList",
|
|
2242
|
+
"listItem",
|
|
2243
|
+
"button",
|
|
2244
|
+
"youtube",
|
|
2245
|
+
"twitter",
|
|
2246
|
+
"table",
|
|
2247
|
+
"tableRow",
|
|
2248
|
+
"tableCell",
|
|
2249
|
+
"tableHeader",
|
|
2250
|
+
"columnsColumn"
|
|
2251
|
+
] },
|
|
2252
|
+
StyleAttribute: { types: [
|
|
2253
|
+
"heading",
|
|
2254
|
+
"paragraph",
|
|
2255
|
+
"image",
|
|
2256
|
+
"blockquote",
|
|
2257
|
+
"codeBlock",
|
|
2258
|
+
"bulletList",
|
|
2259
|
+
"orderedList",
|
|
2260
|
+
"listItem",
|
|
2261
|
+
"button",
|
|
2262
|
+
"youtube",
|
|
2263
|
+
"twitter",
|
|
2264
|
+
"horizontalRule",
|
|
2265
|
+
"footer",
|
|
2266
|
+
"section",
|
|
2267
|
+
"div",
|
|
2268
|
+
"body",
|
|
2269
|
+
"table",
|
|
2270
|
+
"tableRow",
|
|
2271
|
+
"tableCell",
|
|
2272
|
+
"tableHeader",
|
|
2273
|
+
"columnsColumn",
|
|
2274
|
+
"link"
|
|
2275
|
+
] },
|
|
2276
|
+
ClassAttribute: { types: [
|
|
2277
|
+
"heading",
|
|
2278
|
+
"paragraph",
|
|
2279
|
+
"image",
|
|
2280
|
+
"blockquote",
|
|
2281
|
+
"bulletList",
|
|
2282
|
+
"orderedList",
|
|
2283
|
+
"listItem",
|
|
2284
|
+
"button",
|
|
2285
|
+
"youtube",
|
|
2286
|
+
"twitter",
|
|
2287
|
+
"horizontalRule",
|
|
2288
|
+
"footer",
|
|
2289
|
+
"section",
|
|
2290
|
+
"div",
|
|
2291
|
+
"body",
|
|
2292
|
+
"table",
|
|
2293
|
+
"tableRow",
|
|
2294
|
+
"tableCell",
|
|
2295
|
+
"tableHeader",
|
|
2296
|
+
"columnsColumn",
|
|
2297
|
+
"link"
|
|
2298
|
+
] },
|
|
2299
|
+
MaxNesting: {
|
|
2300
|
+
maxDepth: 50,
|
|
2301
|
+
nodeTypes: [
|
|
2302
|
+
"section",
|
|
2303
|
+
"bulletList",
|
|
2304
|
+
"orderedList"
|
|
2305
|
+
]
|
|
2306
|
+
}
|
|
2307
|
+
};
|
|
2308
|
+
},
|
|
2309
|
+
addExtensions() {
|
|
2310
|
+
const extensions = [];
|
|
2311
|
+
if (this.options.TiptapStarterKit !== false) extensions.push(TipTapStarterKit.configure({
|
|
2312
|
+
undoRedo: false,
|
|
2313
|
+
heading: false,
|
|
2314
|
+
link: false,
|
|
2315
|
+
underline: false,
|
|
2316
|
+
trailingNode: false,
|
|
2317
|
+
bold: false,
|
|
2318
|
+
italic: false,
|
|
2319
|
+
strike: false,
|
|
2320
|
+
code: false,
|
|
2321
|
+
paragraph: false,
|
|
2322
|
+
bulletList: false,
|
|
2323
|
+
orderedList: false,
|
|
2324
|
+
listItem: false,
|
|
2325
|
+
blockquote: false,
|
|
2326
|
+
hardBreak: false,
|
|
2327
|
+
gapcursor: false,
|
|
2328
|
+
codeBlock: false,
|
|
2329
|
+
horizontalRule: false,
|
|
2330
|
+
dropcursor: {
|
|
2331
|
+
color: "#61a8f8",
|
|
2332
|
+
class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
|
|
2333
|
+
width: 4
|
|
2334
|
+
},
|
|
2335
|
+
...this.options.TiptapStarterKit
|
|
2336
|
+
}));
|
|
2337
|
+
for (const [name, extension] of Object.entries(starterKitExtensions)) {
|
|
2338
|
+
const key = name;
|
|
2339
|
+
const extensionOptions = this.options[key];
|
|
2340
|
+
if (extensionOptions !== false) extensions.push(extension.configure(extensionOptions));
|
|
2341
|
+
}
|
|
2342
|
+
return extensions;
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
//#endregion
|
|
2347
|
+
//#region src/core/create-drop-handler.ts
|
|
2348
|
+
function createDropHandler({ onPaste, onUploadImage }) {
|
|
2349
|
+
return (view, event, _slice, moved) => {
|
|
2350
|
+
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
|
|
2351
|
+
event.preventDefault();
|
|
2352
|
+
const file = event.dataTransfer.files[0];
|
|
2353
|
+
if (onPaste?.(file, view)) return true;
|
|
2354
|
+
if (file.type.includes("image/") && onUploadImage) {
|
|
2355
|
+
onUploadImage(file, view, (view.posAtCoords({
|
|
2356
|
+
left: event.clientX,
|
|
2357
|
+
top: event.clientY
|
|
2358
|
+
})?.pos || 0) - 1);
|
|
2359
|
+
return true;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return false;
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
//#endregion
|
|
2367
|
+
//#region src/utils/paste-sanitizer.ts
|
|
2368
|
+
/**
|
|
2369
|
+
* Sanitizes pasted HTML.
|
|
2370
|
+
* - From editor (has node-* classes): pass through as-is
|
|
2371
|
+
* - From external: strip all styles/classes, keep only semantic HTML
|
|
2372
|
+
*/
|
|
2373
|
+
/**
|
|
2374
|
+
* Detects content from the Resend editor by checking for node-* class names.
|
|
2375
|
+
*/
|
|
2376
|
+
const EDITOR_CLASS_PATTERN = /class="[^"]*node-/;
|
|
2377
|
+
/**
|
|
2378
|
+
* Attributes to preserve on specific elements for EXTERNAL content.
|
|
2379
|
+
* Only functional attributes - NO style or class.
|
|
2380
|
+
*/
|
|
2381
|
+
const PRESERVED_ATTRIBUTES = {
|
|
2382
|
+
a: [
|
|
2383
|
+
"href",
|
|
2384
|
+
"target",
|
|
2385
|
+
"rel"
|
|
2386
|
+
],
|
|
2387
|
+
img: [
|
|
2388
|
+
"src",
|
|
2389
|
+
"alt",
|
|
2390
|
+
"width",
|
|
2391
|
+
"height"
|
|
2392
|
+
],
|
|
2393
|
+
td: ["colspan", "rowspan"],
|
|
2394
|
+
th: [
|
|
2395
|
+
"colspan",
|
|
2396
|
+
"rowspan",
|
|
2397
|
+
"scope"
|
|
2398
|
+
],
|
|
2399
|
+
table: [
|
|
2400
|
+
"border",
|
|
2401
|
+
"cellpadding",
|
|
2402
|
+
"cellspacing"
|
|
2403
|
+
],
|
|
2404
|
+
"*": ["id"]
|
|
2405
|
+
};
|
|
2406
|
+
function isFromEditor(html) {
|
|
2407
|
+
return EDITOR_CLASS_PATTERN.test(html);
|
|
2408
|
+
}
|
|
2409
|
+
function sanitizePastedHtml(html) {
|
|
2410
|
+
if (isFromEditor(html)) return html;
|
|
2411
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
2412
|
+
sanitizeNode(doc.body);
|
|
2413
|
+
return doc.body.innerHTML;
|
|
2414
|
+
}
|
|
2415
|
+
function sanitizeNode(node) {
|
|
2416
|
+
if (node.nodeType === Node.ELEMENT_NODE) sanitizeElement(node);
|
|
2417
|
+
for (const child of Array.from(node.childNodes)) sanitizeNode(child);
|
|
2418
|
+
}
|
|
2419
|
+
function sanitizeElement(el) {
|
|
2420
|
+
const allowedForTag = PRESERVED_ATTRIBUTES[el.tagName.toLowerCase()] || [];
|
|
2421
|
+
const allowedGlobal = PRESERVED_ATTRIBUTES["*"] || [];
|
|
2422
|
+
const allowed = new Set([...allowedForTag, ...allowedGlobal]);
|
|
2423
|
+
const attributesToRemove = [];
|
|
2424
|
+
for (const attr of Array.from(el.attributes)) {
|
|
2425
|
+
if (attr.name.startsWith("data-")) {
|
|
2426
|
+
attributesToRemove.push(attr.name);
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
if (!allowed.has(attr.name)) attributesToRemove.push(attr.name);
|
|
2430
|
+
}
|
|
2431
|
+
for (const attr of attributesToRemove) el.removeAttribute(attr);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
//#endregion
|
|
2435
|
+
//#region src/core/create-paste-handler.ts
|
|
2436
|
+
function createPasteHandler({ onPaste, onUploadImage, extensions }) {
|
|
2437
|
+
return (view, event, slice) => {
|
|
2438
|
+
const text = event.clipboardData?.getData("text/plain");
|
|
2439
|
+
if (text && onPaste?.(text, view)) {
|
|
2440
|
+
event.preventDefault();
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
if (event.clipboardData?.files?.[0]) {
|
|
2444
|
+
const file = event.clipboardData.files[0];
|
|
2445
|
+
if (onPaste?.(file, view)) {
|
|
2446
|
+
event.preventDefault();
|
|
2447
|
+
return true;
|
|
2448
|
+
}
|
|
2449
|
+
if (file.type.includes("image/") && onUploadImage) {
|
|
2450
|
+
const pos = view.state.selection.from;
|
|
2451
|
+
onUploadImage(file, view, pos);
|
|
2452
|
+
return true;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* If the coming content has a single child, we can assume
|
|
2457
|
+
* it's a plain text and doesn't need to be parsed and
|
|
2458
|
+
* be introduced in a new line
|
|
2459
|
+
*/
|
|
2460
|
+
if (slice.content.childCount === 1) return false;
|
|
2461
|
+
if (event.clipboardData?.getData?.("text/html")) {
|
|
2462
|
+
event.preventDefault();
|
|
2463
|
+
const jsonContent = generateJSON(sanitizePastedHtml(event.clipboardData.getData("text/html")), extensions);
|
|
2464
|
+
const node = view.state.schema.nodeFromJSON(jsonContent);
|
|
2465
|
+
const transaction = view.state.tr.replaceSelectionWith(node, false);
|
|
2466
|
+
view.dispatch(transaction);
|
|
2467
|
+
return true;
|
|
2468
|
+
}
|
|
2469
|
+
return false;
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
//#endregion
|
|
2474
|
+
//#region src/core/is-document-visually-empty.ts
|
|
2475
|
+
function isDocumentVisuallyEmpty(doc) {
|
|
2476
|
+
let nonGlobalNodeCount = 0;
|
|
2477
|
+
let firstNonGlobalNode = null;
|
|
2478
|
+
for (let index = 0; index < doc.childCount; index += 1) {
|
|
2479
|
+
const node = doc.child(index);
|
|
2480
|
+
if (node.type.name === "globalContent") continue;
|
|
2481
|
+
nonGlobalNodeCount += 1;
|
|
2482
|
+
if (firstNonGlobalNode === null) firstNonGlobalNode = {
|
|
2483
|
+
type: node.type,
|
|
2484
|
+
textContent: node.textContent,
|
|
2485
|
+
childCount: node.content.childCount
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
if (nonGlobalNodeCount === 0) return true;
|
|
2489
|
+
if (nonGlobalNodeCount !== 1) return false;
|
|
2490
|
+
return firstNonGlobalNode?.type.name === "paragraph" && firstNonGlobalNode.textContent.trim().length === 0 && firstNonGlobalNode.childCount === 0;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
//#endregion
|
|
2494
|
+
//#region src/core/use-editor.ts
|
|
2495
|
+
const COLLABORATION_EXTENSION_NAMES = new Set(["liveblocksExtension", "collaboration"]);
|
|
2496
|
+
function hasCollaborationExtension(exts) {
|
|
2497
|
+
return exts.some((ext) => COLLABORATION_EXTENSION_NAMES.has(ext.name));
|
|
2498
|
+
}
|
|
2499
|
+
function useEditor({ content, extensions = [], onUpdate, onPaste, onUploadImage, onReady, editable = true, ...rest }) {
|
|
2500
|
+
const [contentError, setContentError] = React.useState(null);
|
|
2501
|
+
const isCollaborative = hasCollaborationExtension(extensions);
|
|
2502
|
+
const effectiveExtensions = React.useMemo(() => [
|
|
2503
|
+
StarterKit,
|
|
2504
|
+
...isCollaborative ? [] : [UndoRedo],
|
|
2505
|
+
...extensions
|
|
2506
|
+
], [extensions, isCollaborative]);
|
|
2507
|
+
const editor = useEditor$1({
|
|
2508
|
+
content: isCollaborative ? void 0 : content,
|
|
2509
|
+
extensions: effectiveExtensions,
|
|
2510
|
+
editable,
|
|
2511
|
+
immediatelyRender: false,
|
|
2512
|
+
enableContentCheck: true,
|
|
2513
|
+
onContentError({ editor: editor$1, error, disableCollaboration }) {
|
|
2514
|
+
disableCollaboration();
|
|
2515
|
+
setContentError(error);
|
|
2516
|
+
console.error(error);
|
|
2517
|
+
editor$1.setEditable(false);
|
|
2518
|
+
},
|
|
2519
|
+
onCreate({ editor: editor$1 }) {
|
|
2520
|
+
onReady?.(editor$1);
|
|
2521
|
+
},
|
|
2522
|
+
onUpdate({ editor: editor$1, transaction }) {
|
|
2523
|
+
onUpdate?.(editor$1, transaction);
|
|
2524
|
+
},
|
|
2525
|
+
editorProps: {
|
|
2526
|
+
handleDOMEvents: { click: (view, event) => {
|
|
2527
|
+
if (!view.editable) {
|
|
2528
|
+
if (event.target.closest("a")) {
|
|
2529
|
+
event.preventDefault();
|
|
2530
|
+
return true;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return false;
|
|
2534
|
+
} },
|
|
2535
|
+
handlePaste: createPasteHandler({
|
|
2536
|
+
onPaste,
|
|
2537
|
+
onUploadImage,
|
|
2538
|
+
extensions: effectiveExtensions
|
|
2539
|
+
}),
|
|
2540
|
+
handleDrop: createDropHandler({
|
|
2541
|
+
onPaste,
|
|
2542
|
+
onUploadImage
|
|
2543
|
+
})
|
|
2544
|
+
},
|
|
2545
|
+
...rest
|
|
2546
|
+
});
|
|
2547
|
+
return {
|
|
2548
|
+
editor,
|
|
2549
|
+
isEditorEmpty: useEditorState({
|
|
2550
|
+
editor,
|
|
2551
|
+
selector: (context) => {
|
|
2552
|
+
if (!context.editor) return true;
|
|
2553
|
+
return isDocumentVisuallyEmpty(context.editor.state.doc);
|
|
2554
|
+
}
|
|
2555
|
+
}) ?? true,
|
|
2556
|
+
extensions: effectiveExtensions,
|
|
2557
|
+
contentError,
|
|
2558
|
+
isCollaborative
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
1782
2561
|
|
|
1783
2562
|
//#endregion
|
|
1784
2563
|
//#region src/utils/set-text-alignment.ts
|
|
@@ -2219,7 +2998,7 @@ function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, class
|
|
|
2219
2998
|
},
|
|
2220
2999
|
{
|
|
2221
3000
|
name: "Code",
|
|
2222
|
-
icon: Code,
|
|
3001
|
+
icon: Code$1,
|
|
2223
3002
|
command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
|
|
2224
3003
|
isActive: editorState?.isCodeBlockActive ?? false
|
|
2225
3004
|
}
|
|
@@ -2934,13 +3713,12 @@ function groupByCategory(items) {
|
|
|
2934
3713
|
return ordered;
|
|
2935
3714
|
}
|
|
2936
3715
|
function CommandItem({ item, selected, onSelect }) {
|
|
2937
|
-
const Icon = item.icon;
|
|
2938
3716
|
return /* @__PURE__ */ jsxs("button", {
|
|
2939
3717
|
"data-re-slash-command-item": "",
|
|
2940
3718
|
"data-selected": selected || void 0,
|
|
2941
3719
|
onClick: onSelect,
|
|
2942
3720
|
type: "button",
|
|
2943
|
-
children: [
|
|
3721
|
+
children: [item.icon, /* @__PURE__ */ jsx("span", { children: item.title })]
|
|
2944
3722
|
});
|
|
2945
3723
|
}
|
|
2946
3724
|
function CommandList({ items, command, query, ref }) {
|
|
@@ -3015,11 +3793,11 @@ function CommandList({ items, command, query, ref }) {
|
|
|
3015
3793
|
}
|
|
3016
3794
|
|
|
3017
3795
|
//#endregion
|
|
3018
|
-
//#region src/ui/slash-command/commands.
|
|
3796
|
+
//#region src/ui/slash-command/commands.tsx
|
|
3019
3797
|
const TEXT = {
|
|
3020
3798
|
title: "Text",
|
|
3021
3799
|
description: "Plain text block",
|
|
3022
|
-
icon: Text,
|
|
3800
|
+
icon: /* @__PURE__ */ jsx(Text, { size: 20 }),
|
|
3023
3801
|
category: "Text",
|
|
3024
3802
|
searchTerms: ["p", "paragraph"],
|
|
3025
3803
|
command: ({ editor, range }) => {
|
|
@@ -3029,7 +3807,7 @@ const TEXT = {
|
|
|
3029
3807
|
const H1 = {
|
|
3030
3808
|
title: "Title",
|
|
3031
3809
|
description: "Large heading",
|
|
3032
|
-
icon: Heading1,
|
|
3810
|
+
icon: /* @__PURE__ */ jsx(Heading1, { size: 20 }),
|
|
3033
3811
|
category: "Text",
|
|
3034
3812
|
searchTerms: [
|
|
3035
3813
|
"title",
|
|
@@ -3044,7 +3822,7 @@ const H1 = {
|
|
|
3044
3822
|
const H2 = {
|
|
3045
3823
|
title: "Subtitle",
|
|
3046
3824
|
description: "Medium heading",
|
|
3047
|
-
icon: Heading2,
|
|
3825
|
+
icon: /* @__PURE__ */ jsx(Heading2, { size: 20 }),
|
|
3048
3826
|
category: "Text",
|
|
3049
3827
|
searchTerms: [
|
|
3050
3828
|
"subtitle",
|
|
@@ -3058,7 +3836,7 @@ const H2 = {
|
|
|
3058
3836
|
const H3 = {
|
|
3059
3837
|
title: "Heading",
|
|
3060
3838
|
description: "Small heading",
|
|
3061
|
-
icon: Heading3,
|
|
3839
|
+
icon: /* @__PURE__ */ jsx(Heading3, { size: 20 }),
|
|
3062
3840
|
category: "Text",
|
|
3063
3841
|
searchTerms: [
|
|
3064
3842
|
"subtitle",
|
|
@@ -3072,7 +3850,7 @@ const H3 = {
|
|
|
3072
3850
|
const BULLET_LIST = {
|
|
3073
3851
|
title: "Bullet list",
|
|
3074
3852
|
description: "Unordered list",
|
|
3075
|
-
icon: List,
|
|
3853
|
+
icon: /* @__PURE__ */ jsx(List, { size: 20 }),
|
|
3076
3854
|
category: "Text",
|
|
3077
3855
|
searchTerms: ["unordered", "point"],
|
|
3078
3856
|
command: ({ editor, range }) => {
|
|
@@ -3082,7 +3860,7 @@ const BULLET_LIST = {
|
|
|
3082
3860
|
const NUMBERED_LIST = {
|
|
3083
3861
|
title: "Numbered list",
|
|
3084
3862
|
description: "Ordered list",
|
|
3085
|
-
icon: ListOrdered,
|
|
3863
|
+
icon: /* @__PURE__ */ jsx(ListOrdered, { size: 20 }),
|
|
3086
3864
|
category: "Text",
|
|
3087
3865
|
searchTerms: ["ordered"],
|
|
3088
3866
|
command: ({ editor, range }) => {
|
|
@@ -3092,7 +3870,7 @@ const NUMBERED_LIST = {
|
|
|
3092
3870
|
const QUOTE = {
|
|
3093
3871
|
title: "Quote",
|
|
3094
3872
|
description: "Block quote",
|
|
3095
|
-
icon: TextQuote,
|
|
3873
|
+
icon: /* @__PURE__ */ jsx(TextQuote, { size: 20 }),
|
|
3096
3874
|
category: "Text",
|
|
3097
3875
|
searchTerms: ["blockquote"],
|
|
3098
3876
|
command: ({ editor, range }) => {
|
|
@@ -3102,7 +3880,7 @@ const QUOTE = {
|
|
|
3102
3880
|
const CODE = {
|
|
3103
3881
|
title: "Code block",
|
|
3104
3882
|
description: "Code snippet",
|
|
3105
|
-
icon: SquareCode,
|
|
3883
|
+
icon: /* @__PURE__ */ jsx(SquareCode, { size: 20 }),
|
|
3106
3884
|
category: "Text",
|
|
3107
3885
|
searchTerms: ["codeblock"],
|
|
3108
3886
|
command: ({ editor, range }) => {
|
|
@@ -3112,7 +3890,7 @@ const CODE = {
|
|
|
3112
3890
|
const BUTTON = {
|
|
3113
3891
|
title: "Button",
|
|
3114
3892
|
description: "Clickable button",
|
|
3115
|
-
icon: MousePointer,
|
|
3893
|
+
icon: /* @__PURE__ */ jsx(MousePointer, { size: 20 }),
|
|
3116
3894
|
category: "Layout",
|
|
3117
3895
|
searchTerms: ["button"],
|
|
3118
3896
|
command: ({ editor, range }) => {
|
|
@@ -3122,7 +3900,7 @@ const BUTTON = {
|
|
|
3122
3900
|
const DIVIDER = {
|
|
3123
3901
|
title: "Divider",
|
|
3124
3902
|
description: "Horizontal separator",
|
|
3125
|
-
icon: SplitSquareVertical,
|
|
3903
|
+
icon: /* @__PURE__ */ jsx(SplitSquareVertical, { size: 20 }),
|
|
3126
3904
|
category: "Layout",
|
|
3127
3905
|
searchTerms: [
|
|
3128
3906
|
"hr",
|
|
@@ -3136,7 +3914,7 @@ const DIVIDER = {
|
|
|
3136
3914
|
const SECTION = {
|
|
3137
3915
|
title: "Section",
|
|
3138
3916
|
description: "Content section",
|
|
3139
|
-
icon: Rows2,
|
|
3917
|
+
icon: /* @__PURE__ */ jsx(Rows2, { size: 20 }),
|
|
3140
3918
|
category: "Layout",
|
|
3141
3919
|
searchTerms: [
|
|
3142
3920
|
"section",
|
|
@@ -3150,7 +3928,7 @@ const SECTION = {
|
|
|
3150
3928
|
const TWO_COLUMNS = {
|
|
3151
3929
|
title: "2 columns",
|
|
3152
3930
|
description: "Two column layout",
|
|
3153
|
-
icon: Columns2,
|
|
3931
|
+
icon: /* @__PURE__ */ jsx(Columns2, { size: 20 }),
|
|
3154
3932
|
category: "Layout",
|
|
3155
3933
|
searchTerms: [
|
|
3156
3934
|
"columns",
|
|
@@ -3171,7 +3949,7 @@ const TWO_COLUMNS = {
|
|
|
3171
3949
|
const THREE_COLUMNS = {
|
|
3172
3950
|
title: "3 columns",
|
|
3173
3951
|
description: "Three column layout",
|
|
3174
|
-
icon: Columns3,
|
|
3952
|
+
icon: /* @__PURE__ */ jsx(Columns3, { size: 20 }),
|
|
3175
3953
|
category: "Layout",
|
|
3176
3954
|
searchTerms: [
|
|
3177
3955
|
"columns",
|
|
@@ -3191,7 +3969,7 @@ const THREE_COLUMNS = {
|
|
|
3191
3969
|
const FOUR_COLUMNS = {
|
|
3192
3970
|
title: "4 columns",
|
|
3193
3971
|
description: "Four column layout",
|
|
3194
|
-
icon: Columns4,
|
|
3972
|
+
icon: /* @__PURE__ */ jsx(Columns4, { size: 20 }),
|
|
3195
3973
|
category: "Layout",
|
|
3196
3974
|
searchTerms: [
|
|
3197
3975
|
"columns",
|
|
@@ -3343,5 +4121,5 @@ function createSlashCommand(options) {
|
|
|
3343
4121
|
const SlashCommand = createSlashCommand();
|
|
3344
4122
|
|
|
3345
4123
|
//#endregion
|
|
3346
|
-
export { AlignmentAttribute, BULLET_LIST, BUTTON, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, EmailNode, FOUR_COLUMNS, FourColumns, H1, H2, H3, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase,
|
|
4124
|
+
export { AlignmentAttribute, BULLET_LIST, BUTTON, Blockquote, Body, Bold, BubbleMenu, BubbleMenuAlignCenter, BubbleMenuAlignLeft, BubbleMenuAlignRight, BubbleMenuBold, BubbleMenuCode, BubbleMenuDefault, BubbleMenuItalic, BubbleMenuItem, BubbleMenuItemGroup, BubbleMenuLinkSelector, BubbleMenuNodeSelector, BubbleMenuRoot, BubbleMenuSeparator, BubbleMenuStrike, BubbleMenuUnderline, BubbleMenuUppercase, BulletList, Button, ButtonBubbleMenu, ButtonBubbleMenuDefault, ButtonBubbleMenuEditLink, ButtonBubbleMenuRoot, ButtonBubbleMenuToolbar, CODE, COLUMN_PARENT_TYPES, ClassAttribute, Code, CodeBlockPrism, ColumnsColumn, CommandList, DIVIDER, Div, EmailNode, FOUR_COLUMNS, FourColumns, GlobalContent, H1, H2, H3, HardBreak, ImageBubbleMenu, ImageBubbleMenuDefault, ImageBubbleMenuEditLink, ImageBubbleMenuRoot, ImageBubbleMenuToolbar, Italic, LinkBubbleMenu, LinkBubbleMenuDefault, LinkBubbleMenuEditLink, LinkBubbleMenuForm, LinkBubbleMenuOpenLink, LinkBubbleMenuRoot, LinkBubbleMenuToolbar, LinkBubbleMenuUnlink, ListItem, MAX_COLUMNS_DEPTH, MaxNesting, NUMBERED_LIST, NodeSelectorContent, NodeSelectorRoot, NodeSelectorTrigger, OrderedList, Paragraph, Placeholder, PreservedStyle, PreviewText, QUOTE, SECTION, Section, SlashCommand, StarterKit, Strike, StyleAttribute, Sup, TEXT, THREE_COLUMNS, TWO_COLUMNS, Table, TableCell, TableHeader, TableRow, ThreeColumns, TwoColumns, Uppercase, composeReactEmail, createSlashCommand, defaultSlashCommands, editorEventBus, filterAndRankItems, getColumnsDepth, getGlobalContent, isAtMaxColumnsDepth, isInsideNode, processStylesForUnlink, scoreItem, setTextAlignment, useButtonBubbleMenuContext, useEditor, useImageBubbleMenuContext, useLinkBubbleMenuContext };
|
|
3347
4125
|
//# sourceMappingURL=index.mjs.map
|