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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/columns-CUxUEHje.mjs +497 -0
  2. package/dist/columns-CUxUEHje.mjs.map +1 -0
  3. package/dist/columns-ZSaLdkg9.cjs +630 -0
  4. package/dist/core/index.cjs +8 -0
  5. package/dist/core/index.d.cts +2 -0
  6. package/dist/core/index.d.mts +2 -0
  7. package/dist/core/index.mjs +4 -0
  8. package/dist/core-C5_RDJBI.mjs +1982 -0
  9. package/dist/core-C5_RDJBI.mjs.map +1 -0
  10. package/dist/core-Rxnpk8B_.cjs +2239 -0
  11. package/dist/extensions/index.cjs +47 -0
  12. package/dist/extensions/index.d.cts +394 -0
  13. package/dist/extensions/index.d.cts.map +1 -0
  14. package/dist/extensions/index.d.mts +394 -0
  15. package/dist/extensions/index.d.mts.map +1 -0
  16. package/dist/extensions/index.mjs +4 -0
  17. package/dist/index-CfslA7KT.d.cts +130 -0
  18. package/dist/index-CfslA7KT.d.cts.map +1 -0
  19. package/dist/index-hbHRR7oB.d.mts +130 -0
  20. package/dist/index-hbHRR7oB.d.mts.map +1 -0
  21. package/dist/set-text-alignment-Bx3bPteH.cjs +24 -0
  22. package/dist/set-text-alignment-DZvgnbvz.mjs +19 -0
  23. package/dist/set-text-alignment-DZvgnbvz.mjs.map +1 -0
  24. package/dist/ui/index.cjs +1646 -0
  25. package/dist/ui/index.d.cts +668 -0
  26. package/dist/ui/index.d.cts.map +1 -0
  27. package/dist/ui/index.d.mts +668 -0
  28. package/dist/ui/index.d.mts.map +1 -0
  29. package/dist/ui/index.mjs +1584 -0
  30. package/dist/ui/index.mjs.map +1 -0
  31. package/dist/utils/index.cjs +3 -0
  32. package/dist/utils/index.d.cts +7 -0
  33. package/dist/utils/index.d.cts.map +1 -0
  34. package/dist/utils/index.d.mts +7 -0
  35. package/dist/utils/index.d.mts.map +1 -0
  36. package/dist/utils/index.mjs +3 -0
  37. package/package.json +39 -11
  38. package/dist/index.cjs +0 -4228
  39. package/dist/index.d.cts +0 -1175
  40. package/dist/index.d.cts.map +0 -1
  41. package/dist/index.d.mts +0 -1175
  42. package/dist/index.d.mts.map +0 -1
  43. package/dist/index.mjs +0 -4072
  44. package/dist/index.mjs.map +0 -1
package/dist/index.cjs DELETED
@@ -1,4228 +0,0 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) {
13
- __defProp(to, key, {
14
- get: ((k) => from[k]).bind(null, key),
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- });
17
- }
18
- }
19
- }
20
- return to;
21
- };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
- value: mod,
24
- enumerable: true
25
- }) : target, mod));
26
-
27
- //#endregion
28
- let _react_email_components = require("@react-email/components");
29
- _react_email_components = __toESM(_react_email_components);
30
- let react_jsx_runtime = require("react/jsx-runtime");
31
- let _tiptap_core = require("@tiptap/core");
32
- let _tiptap_extensions = require("@tiptap/extensions");
33
- let _tiptap_react = require("@tiptap/react");
34
- let react = require("react");
35
- react = __toESM(react);
36
- let _tiptap_starter_kit = require("@tiptap/starter-kit");
37
- _tiptap_starter_kit = __toESM(_tiptap_starter_kit);
38
- let _tiptap_extension_blockquote = require("@tiptap/extension-blockquote");
39
- _tiptap_extension_blockquote = __toESM(_tiptap_extension_blockquote);
40
- let _tiptap_extension_bold = require("@tiptap/extension-bold");
41
- _tiptap_extension_bold = __toESM(_tiptap_extension_bold);
42
- let _tiptap_extension_bullet_list = require("@tiptap/extension-bullet-list");
43
- _tiptap_extension_bullet_list = __toESM(_tiptap_extension_bullet_list);
44
- let _tiptap_extension_code = require("@tiptap/extension-code");
45
- _tiptap_extension_code = __toESM(_tiptap_extension_code);
46
- let _tiptap_extension_code_block = require("@tiptap/extension-code-block");
47
- _tiptap_extension_code_block = __toESM(_tiptap_extension_code_block);
48
- let _tiptap_pm_state = require("@tiptap/pm/state");
49
- let _tiptap_pm_view = require("@tiptap/pm/view");
50
- let hast_util_from_html = require("hast-util-from-html");
51
- let prismjs = require("prismjs");
52
- prismjs = __toESM(prismjs);
53
- let _tiptap_extension_horizontal_rule = require("@tiptap/extension-horizontal-rule");
54
- _tiptap_extension_horizontal_rule = __toESM(_tiptap_extension_horizontal_rule);
55
- let _tiptap_extension_hard_break = require("@tiptap/extension-hard-break");
56
- _tiptap_extension_hard_break = __toESM(_tiptap_extension_hard_break);
57
- let _tiptap_extension_heading = require("@tiptap/extension-heading");
58
- let _tiptap_extension_italic = require("@tiptap/extension-italic");
59
- _tiptap_extension_italic = __toESM(_tiptap_extension_italic);
60
- let _tiptap_extension_link = require("@tiptap/extension-link");
61
- _tiptap_extension_link = __toESM(_tiptap_extension_link);
62
- let _tiptap_extension_list_item = require("@tiptap/extension-list-item");
63
- _tiptap_extension_list_item = __toESM(_tiptap_extension_list_item);
64
- let _tiptap_extension_ordered_list = require("@tiptap/extension-ordered-list");
65
- _tiptap_extension_ordered_list = __toESM(_tiptap_extension_ordered_list);
66
- let _tiptap_extension_paragraph = require("@tiptap/extension-paragraph");
67
- _tiptap_extension_paragraph = __toESM(_tiptap_extension_paragraph);
68
- let _tiptap_extension_placeholder = require("@tiptap/extension-placeholder");
69
- _tiptap_extension_placeholder = __toESM(_tiptap_extension_placeholder);
70
- let _tiptap_extension_strike = require("@tiptap/extension-strike");
71
- _tiptap_extension_strike = __toESM(_tiptap_extension_strike);
72
- let _tiptap_extension_superscript = require("@tiptap/extension-superscript");
73
- _tiptap_extension_superscript = __toESM(_tiptap_extension_superscript);
74
- let _tiptap_extension_underline = require("@tiptap/extension-underline");
75
- _tiptap_extension_underline = __toESM(_tiptap_extension_underline);
76
- let _tiptap_html = require("@tiptap/html");
77
- let lucide_react = require("lucide-react");
78
- let _radix_ui_react_popover = require("@radix-ui/react-popover");
79
- _radix_ui_react_popover = __toESM(_radix_ui_react_popover);
80
- let _tiptap_react_menus = require("@tiptap/react/menus");
81
- let _floating_ui_react_dom = require("@floating-ui/react-dom");
82
- let _tiptap_suggestion = require("@tiptap/suggestion");
83
- _tiptap_suggestion = __toESM(_tiptap_suggestion);
84
- let react_dom = require("react-dom");
85
-
86
- //#region src/core/event-bus.ts
87
- const EVENT_PREFIX = "@react-email/editor:";
88
- var EditorEventBus = class {
89
- prefixEventName(eventName) {
90
- return `${EVENT_PREFIX}${String(eventName)}`;
91
- }
92
- dispatch(eventName, payload, options) {
93
- const target = options?.target ?? window;
94
- const prefixedEventName = this.prefixEventName(eventName);
95
- const event = new CustomEvent(prefixedEventName, {
96
- detail: payload,
97
- bubbles: false,
98
- cancelable: false
99
- });
100
- target.dispatchEvent(event);
101
- }
102
- on(eventName, handler, options) {
103
- const target = options?.target ?? window;
104
- const prefixedEventName = this.prefixEventName(eventName);
105
- const abortController = new AbortController();
106
- const wrappedHandler = (event) => {
107
- const customEvent = event;
108
- const result = handler(customEvent.detail);
109
- if (result instanceof Promise) result.catch((error) => {
110
- console.error(`Error in async event handler for ${prefixedEventName}:`, {
111
- event: customEvent.detail,
112
- error
113
- });
114
- });
115
- };
116
- target.addEventListener(prefixedEventName, wrappedHandler, {
117
- ...options,
118
- signal: abortController.signal
119
- });
120
- return { unsubscribe: () => {
121
- abortController.abort();
122
- } };
123
- }
124
- };
125
- const editorEventBus = new EditorEventBus();
126
-
127
- //#endregion
128
- //#region src/core/is-document-visually-empty.ts
129
- function isDocumentVisuallyEmpty(doc) {
130
- let nonGlobalNodeCount = 0;
131
- let firstNonGlobalNode = null;
132
- for (let index = 0; index < doc.childCount; index += 1) {
133
- const node = doc.child(index);
134
- if (node.type.name === "globalContent") continue;
135
- nonGlobalNodeCount += 1;
136
- if (firstNonGlobalNode === null) firstNonGlobalNode = {
137
- type: node.type,
138
- textContent: node.textContent,
139
- childCount: node.content.childCount
140
- };
141
- }
142
- if (nonGlobalNodeCount === 0) return true;
143
- if (nonGlobalNodeCount !== 1) return false;
144
- return firstNonGlobalNode?.type.name === "paragraph" && firstNonGlobalNode.textContent.trim().length === 0 && firstNonGlobalNode.childCount === 0;
145
- }
146
-
147
- //#endregion
148
- //#region src/utils/styles.ts
149
- const WHITE_SPACE_REGEX = /\s+/;
150
- const inlineCssToJs = (inlineStyle, options = {}) => {
151
- const styleObject = {};
152
- if (!inlineStyle || inlineStyle === "" || typeof inlineStyle === "object") return styleObject;
153
- inlineStyle.split(";").forEach((style) => {
154
- if (style.trim()) {
155
- const [key, value] = style.split(":");
156
- const valueTrimmed = value?.trim();
157
- if (!valueTrimmed) return;
158
- const formattedKey = key.trim().replace(/-\w/g, (match) => match[1].toUpperCase());
159
- styleObject[formattedKey] = options?.removeUnit ? valueTrimmed.replace(/px|%/g, "") : valueTrimmed;
160
- }
161
- });
162
- return styleObject;
163
- };
164
- /**
165
- * Expands CSS shorthand properties (margin, padding) into their longhand equivalents.
166
- * This prevents shorthand properties from overriding specific longhand properties in email clients.
167
- *
168
- * @param styles - Style object that may contain shorthand properties
169
- * @returns New style object with shorthand properties expanded to longhand
170
- *
171
- * @example
172
- * expandShorthandProperties({ margin: '0', paddingTop: '10px' })
173
- * // Returns: { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '0', paddingTop: '10px' }
174
- */
175
- function expandShorthandProperties(styles) {
176
- if (!styles || typeof styles !== "object") return {};
177
- const expanded = {};
178
- for (const key in styles) {
179
- const value = styles[key];
180
- if (value === void 0 || value === null || value === "") continue;
181
- switch (key) {
182
- case "margin": {
183
- const values = parseShorthandValue(value);
184
- expanded.marginTop = values.top;
185
- expanded.marginRight = values.right;
186
- expanded.marginBottom = values.bottom;
187
- expanded.marginLeft = values.left;
188
- break;
189
- }
190
- case "padding": {
191
- const values = parseShorthandValue(value);
192
- expanded.paddingTop = values.top;
193
- expanded.paddingRight = values.right;
194
- expanded.paddingBottom = values.bottom;
195
- expanded.paddingLeft = values.left;
196
- break;
197
- }
198
- case "border": {
199
- const values = convertBorderValue(value);
200
- expanded.borderStyle = values.style;
201
- expanded.borderWidth = values.width;
202
- expanded.borderColor = values.color;
203
- break;
204
- }
205
- case "borderTopLeftRadius":
206
- case "borderTopRightRadius":
207
- case "borderBottomLeftRadius":
208
- case "borderBottomRightRadius":
209
- expanded[key] = value;
210
- if (styles.borderTopLeftRadius && styles.borderTopRightRadius && styles.borderBottomLeftRadius && styles.borderBottomRightRadius) {
211
- const values = [
212
- styles.borderTopLeftRadius,
213
- styles.borderTopRightRadius,
214
- styles.borderBottomLeftRadius,
215
- styles.borderBottomRightRadius
216
- ];
217
- if (new Set(values).size === 1) expanded.borderRadius = values[0];
218
- }
219
- break;
220
- default: expanded[key] = value;
221
- }
222
- }
223
- return expanded;
224
- }
225
- /**
226
- * Parses CSS shorthand value (1-4 values) into individual side values.
227
- * Follows CSS specification for shorthand property value parsing.
228
- *
229
- * @param value - Shorthand value string (e.g., '0', '10px 20px', '5px 10px 15px 20px')
230
- * @returns Object with top, right, bottom, left values
231
- */
232
- function parseShorthandValue(value) {
233
- const stringValue = String(value).trim();
234
- const parts = stringValue.split(WHITE_SPACE_REGEX);
235
- const len = parts.length;
236
- if (len === 1) return {
237
- top: parts[0],
238
- right: parts[0],
239
- bottom: parts[0],
240
- left: parts[0]
241
- };
242
- if (len === 2) return {
243
- top: parts[0],
244
- right: parts[1],
245
- bottom: parts[0],
246
- left: parts[1]
247
- };
248
- if (len === 3) return {
249
- top: parts[0],
250
- right: parts[1],
251
- bottom: parts[2],
252
- left: parts[1]
253
- };
254
- if (len === 4) return {
255
- top: parts[0],
256
- right: parts[1],
257
- bottom: parts[2],
258
- left: parts[3]
259
- };
260
- return {
261
- top: stringValue,
262
- right: stringValue,
263
- bottom: stringValue,
264
- left: stringValue
265
- };
266
- }
267
- function convertBorderValue(value) {
268
- const stringValue = String(value).trim();
269
- const parts = stringValue.split(WHITE_SPACE_REGEX);
270
- switch (parts.length) {
271
- case 1: return {
272
- style: "solid",
273
- width: parts[0],
274
- color: "black"
275
- };
276
- case 2: return {
277
- style: parts[1],
278
- width: parts[0],
279
- color: "black"
280
- };
281
- case 3: return {
282
- style: parts[1],
283
- width: parts[0],
284
- color: parts[2]
285
- };
286
- case 4: return {
287
- style: parts[1],
288
- width: parts[0],
289
- color: parts[2]
290
- };
291
- default: return {
292
- style: "solid",
293
- width: stringValue,
294
- color: "black"
295
- };
296
- }
297
- }
298
- /**
299
- * Resolves conflicts between reset styles and inline styles by expanding
300
- * shorthand properties (margin, padding) to longhand before merging.
301
- * This prevents shorthand properties from overriding specific longhand properties.
302
- *
303
- * @param resetStyles - Base reset styles that may contain shorthand properties
304
- * @param inlineStyles - Inline styles that should override reset styles
305
- * @returns Merged styles with inline styles taking precedence
306
- */
307
- function resolveConflictingStyles(resetStyles, inlineStyles) {
308
- const expandedResetStyles = expandShorthandProperties(resetStyles);
309
- const expandedInlineStyles = expandShorthandProperties(inlineStyles);
310
- return {
311
- ...expandedResetStyles,
312
- ...expandedInlineStyles
313
- };
314
- }
315
-
316
- //#endregion
317
- //#region src/core/serializer/default-base-template.tsx
318
- function DefaultBaseTemplate({ children, previewText }) {
319
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_react_email_components.Html, { children: [
320
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_react_email_components.Head, { children: [
321
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
322
- content: "width=device-width",
323
- name: "viewport"
324
- }),
325
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
326
- content: "IE=edge",
327
- httpEquiv: "X-UA-Compatible"
328
- }),
329
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", { name: "x-apple-disable-message-reformatting" }),
330
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
331
- content: "telephone=no,address=no,email=no,date=no,url=no",
332
- name: "format-detection"
333
- })
334
- ] }),
335
- previewText && previewText !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Preview, { children: previewText }),
336
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Body, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Section, {
337
- width: "100%",
338
- align: "center",
339
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Section, {
340
- style: { width: "100%" },
341
- children
342
- })
343
- }) })
344
- ] });
345
- }
346
-
347
- //#endregion
348
- //#region src/core/serializer/email-mark.ts
349
- var EmailMark = class EmailMark extends _tiptap_core.Mark {
350
- constructor(config) {
351
- super(config);
352
- }
353
- /**
354
- * Create a new Mark instance
355
- * @param config - Mark configuration object or a function that returns a configuration object
356
- */
357
- static create(config) {
358
- return new EmailMark(typeof config === "function" ? config() : config);
359
- }
360
- static from(mark, renderToReactEmail) {
361
- const customMark = EmailMark.create({});
362
- Object.assign(customMark, { ...mark });
363
- customMark.config = {
364
- ...mark.config,
365
- renderToReactEmail
366
- };
367
- return customMark;
368
- }
369
- configure(options) {
370
- return super.configure(options);
371
- }
372
- extend(extendedConfig) {
373
- const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
374
- return super.extend(resolvedConfig);
375
- }
376
- };
377
-
378
- //#endregion
379
- //#region src/core/serializer/email-node.ts
380
- var EmailNode = class EmailNode extends _tiptap_core.Node {
381
- constructor(config) {
382
- super(config);
383
- }
384
- /**
385
- * Create a new Node instance
386
- * @param config - Node configuration object or a function that returns a configuration object
387
- */
388
- static create(config) {
389
- return new EmailNode(typeof config === "function" ? config() : config);
390
- }
391
- static from(node, renderToReactEmail) {
392
- const customNode = EmailNode.create({});
393
- Object.assign(customNode, { ...node });
394
- customNode.config = {
395
- ...node.config,
396
- renderToReactEmail
397
- };
398
- return customNode;
399
- }
400
- configure(options) {
401
- return super.configure(options);
402
- }
403
- extend(extendedConfig) {
404
- const resolvedConfig = typeof extendedConfig === "function" ? extendedConfig() : extendedConfig;
405
- return super.extend(resolvedConfig);
406
- }
407
- };
408
-
409
- //#endregion
410
- //#region src/core/serializer/compose-react-email.tsx
411
- const MARK_ORDER = {
412
- preservedStyle: 0,
413
- italic: 1,
414
- strike: 2,
415
- underline: 3,
416
- link: 4,
417
- bold: 5,
418
- code: 6
419
- };
420
- const NODES_WITH_INCREMENTED_CHILD_DEPTH = new Set(["bulletList", "orderedList"]);
421
- function getOrderedMarks(marks) {
422
- if (!marks) return [];
423
- return [...marks].sort((a, b) => (MARK_ORDER[a.type] ?? Number.MAX_SAFE_INTEGER) - (MARK_ORDER[b.type] ?? Number.MAX_SAFE_INTEGER));
424
- }
425
- const composeReactEmail = async ({ editor, preview }) => {
426
- const data = editor.getJSON();
427
- const extensions = editor.extensionManager.extensions;
428
- const serializerPlugin = extensions.map((ext) => ext.options?.serializerPlugin).filter((p) => Boolean(p)).at(-1);
429
- const emailNodeComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailNode).map((extension) => [extension.name, extension.config.renderToReactEmail]));
430
- const emailMarkComponentRegistry = Object.fromEntries(extensions.filter((ext) => ext instanceof EmailMark).map((extension) => [extension.name, extension.config.renderToReactEmail]));
431
- function renderMark(mark, node, children, depth) {
432
- const markStyle = serializerPlugin?.getNodeStyles({
433
- type: mark.type,
434
- attrs: mark.attrs ?? {}
435
- }, depth, editor) ?? {};
436
- const markRenderer = emailMarkComponentRegistry[mark.type];
437
- if (markRenderer) return markRenderer({
438
- mark,
439
- node,
440
- style: markStyle,
441
- children
442
- });
443
- return children;
444
- }
445
- function parseContent(content, depth = 0) {
446
- if (!content) return;
447
- return content.map((node, index) => {
448
- const style = serializerPlugin?.getNodeStyles(node, depth, editor) ?? {};
449
- const inlineStyles = inlineCssToJs(node.attrs?.style);
450
- if (node.type && emailNodeComponentRegistry[node.type]) {
451
- const Component = emailNodeComponentRegistry[node.type];
452
- const childDepth = NODES_WITH_INCREMENTED_CHILD_DEPTH.has(node.type) ? depth + 1 : depth;
453
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
454
- node: node.type === "table" && inlineStyles.width && !node.attrs?.width ? {
455
- ...node,
456
- attrs: {
457
- ...node.attrs,
458
- width: inlineStyles.width
459
- }
460
- } : node,
461
- style,
462
- children: parseContent(node.content, childDepth)
463
- }, index);
464
- }
465
- switch (node.type) {
466
- case "text": {
467
- let wrappedText = node.text;
468
- getOrderedMarks(node.marks).forEach((mark) => {
469
- wrappedText = renderMark(mark, node, wrappedText, depth);
470
- });
471
- const textAttributes = node.marks?.find((mark) => mark.type === "textStyle")?.attrs;
472
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
473
- style: {
474
- ...textAttributes,
475
- ...style
476
- },
477
- children: wrappedText
478
- }, index);
479
- }
480
- default: return null;
481
- }
482
- });
483
- }
484
- const unformattedHtml = await (0, _react_email_components.render)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(serializerPlugin?.BaseTemplate ?? DefaultBaseTemplate, {
485
- previewText: preview,
486
- editor,
487
- children: parseContent(data.content)
488
- }));
489
- const [prettyHtml, text] = await Promise.all([(0, _react_email_components.pretty)(unformattedHtml), (0, _react_email_components.toPlainText)(unformattedHtml)]);
490
- return {
491
- html: prettyHtml,
492
- text
493
- };
494
- };
495
-
496
- //#endregion
497
- //#region src/extensions/alignment-attribute.tsx
498
- const AlignmentAttribute = _tiptap_core.Extension.create({
499
- name: "alignmentAttribute",
500
- addOptions() {
501
- return {
502
- types: [],
503
- alignments: [
504
- "left",
505
- "center",
506
- "right",
507
- "justify"
508
- ]
509
- };
510
- },
511
- addGlobalAttributes() {
512
- return [{
513
- types: this.options.types,
514
- attributes: { alignment: {
515
- parseHTML: (element) => {
516
- const explicitAlign = element.getAttribute("align") || element.getAttribute("alignment") || element.style.textAlign;
517
- if (explicitAlign && this.options.alignments.includes(explicitAlign)) return explicitAlign;
518
- return null;
519
- },
520
- renderHTML: (attributes) => {
521
- if (attributes.alignment === "left") return {};
522
- return { alignment: attributes.alignment };
523
- }
524
- } }
525
- }];
526
- },
527
- addCommands() {
528
- return { setAlignment: (alignment) => ({ commands }) => {
529
- if (!this.options.alignments.includes(alignment)) return false;
530
- return this.options.types.every((type) => commands.updateAttributes(type, { alignment }));
531
- } };
532
- },
533
- addKeyboardShortcuts() {
534
- return {
535
- Enter: () => {
536
- const { from } = this.editor.state.selection;
537
- const currentAlignment = this.editor.state.doc.nodeAt(from)?.attrs?.alignment;
538
- if (currentAlignment) requestAnimationFrame(() => {
539
- this.editor.commands.setAlignment(currentAlignment);
540
- });
541
- return false;
542
- },
543
- "Mod-Shift-l": () => this.editor.commands.setAlignment("left"),
544
- "Mod-Shift-e": () => this.editor.commands.setAlignment("center"),
545
- "Mod-Shift-r": () => this.editor.commands.setAlignment("right"),
546
- "Mod-Shift-j": () => this.editor.commands.setAlignment("justify")
547
- };
548
- }
549
- });
550
-
551
- //#endregion
552
- //#region src/utils/get-text-alignment.ts
553
- function getTextAlignment(alignment) {
554
- switch (alignment) {
555
- case "left": return { textAlign: "left" };
556
- case "center": return { textAlign: "center" };
557
- case "right": return { textAlign: "right" };
558
- default: return {};
559
- }
560
- }
561
-
562
- //#endregion
563
- //#region src/extensions/blockquote.tsx
564
- const Blockquote = EmailNode.from(_tiptap_extension_blockquote.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("blockquote", {
565
- className: node.attrs?.class || void 0,
566
- style: {
567
- ...style,
568
- ...inlineCssToJs(node.attrs?.style),
569
- ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
570
- },
571
- children
572
- }));
573
-
574
- //#endregion
575
- //#region src/utils/attribute-helpers.ts
576
- /**
577
- * Creates TipTap attribute definitions for a list of HTML attributes.
578
- * Each attribute will have the same pattern:
579
- * - default: null
580
- * - parseHTML: extracts the attribute from the element
581
- * - renderHTML: conditionally renders the attribute if it has a value
582
- *
583
- * @param attributeNames - Array of HTML attribute names to create definitions for
584
- * @returns Object with TipTap attribute definitions
585
- *
586
- * @example
587
- * const attrs = createStandardAttributes(['class', 'id', 'title']);
588
- * // Returns:
589
- * // {
590
- * // class: {
591
- * // default: null,
592
- * // parseHTML: (element) => element.getAttribute('class'),
593
- * // renderHTML: (attributes) => attributes.class ? { class: attributes.class } : {}
594
- * // },
595
- * // ...
596
- * // }
597
- */
598
- function createStandardAttributes(attributeNames) {
599
- return Object.fromEntries(attributeNames.map((attr) => [attr, {
600
- default: null,
601
- parseHTML: (element) => element.getAttribute(attr),
602
- renderHTML: (attributes) => {
603
- if (!attributes[attr]) return {};
604
- return { [attr]: attributes[attr] };
605
- }
606
- }]));
607
- }
608
- /**
609
- * Common HTML attributes used across multiple extensions.
610
- * These preserve attributes during HTML import and editing for better
611
- * fidelity when importing existing email templates.
612
- */
613
- const COMMON_HTML_ATTRIBUTES = [
614
- "id",
615
- "class",
616
- "title",
617
- "lang",
618
- "dir",
619
- "data-id"
620
- ];
621
- /**
622
- * Layout-specific HTML attributes used for positioning and sizing.
623
- */
624
- const LAYOUT_ATTRIBUTES = [
625
- "align",
626
- "width",
627
- "height"
628
- ];
629
- /**
630
- * Table-specific HTML attributes used for table layout and styling.
631
- */
632
- const TABLE_ATTRIBUTES = [
633
- "border",
634
- "cellpadding",
635
- "cellspacing"
636
- ];
637
- /**
638
- * Table cell-specific HTML attributes.
639
- */
640
- const TABLE_CELL_ATTRIBUTES = [
641
- "valign",
642
- "bgcolor",
643
- "colspan",
644
- "rowspan"
645
- ];
646
- /**
647
- * Table header cell-specific HTML attributes.
648
- * These are additional attributes that only apply to <th> elements.
649
- */
650
- const TABLE_HEADER_ATTRIBUTES = [...TABLE_CELL_ATTRIBUTES, "scope"];
651
-
652
- //#endregion
653
- //#region src/extensions/body.tsx
654
- const Body = EmailNode.create({
655
- name: "body",
656
- group: "block",
657
- content: "block+",
658
- defining: true,
659
- isolating: true,
660
- addAttributes() {
661
- return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
662
- },
663
- parseHTML() {
664
- return [{
665
- tag: "body",
666
- getAttrs: (node) => {
667
- if (typeof node === "string") return false;
668
- const element = node;
669
- const attrs = {};
670
- Array.from(element.attributes).forEach((attr) => {
671
- attrs[attr.name] = attr.value;
672
- });
673
- return attrs;
674
- }
675
- }];
676
- },
677
- renderHTML({ HTMLAttributes }) {
678
- return [
679
- "div",
680
- (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
681
- 0
682
- ];
683
- },
684
- renderToReactEmail({ children, node, style }) {
685
- const inlineStyles = inlineCssToJs(node.attrs?.style);
686
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
687
- className: node.attrs?.class || void 0,
688
- style: {
689
- ...style,
690
- ...inlineStyles
691
- },
692
- children
693
- });
694
- }
695
- });
696
-
697
- //#endregion
698
- //#region src/extensions/bold.tsx
699
- const BoldWithoutFontWeightInference = _tiptap_extension_bold.default.extend({ parseHTML() {
700
- return [
701
- { tag: "strong" },
702
- {
703
- tag: "b",
704
- getAttrs: (node) => node.style.fontWeight !== "normal" && null
705
- },
706
- {
707
- style: "font-weight=400",
708
- clearMark: (mark) => mark.type.name === this.name
709
- }
710
- ];
711
- } });
712
- const Bold = EmailMark.from(BoldWithoutFontWeightInference, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
713
- style,
714
- children
715
- }));
716
-
717
- //#endregion
718
- //#region src/extensions/bullet-list.tsx
719
- const BulletList = EmailNode.from(_tiptap_extension_bullet_list.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
720
- className: node.attrs?.class || void 0,
721
- style: {
722
- ...style,
723
- ...inlineCssToJs(node.attrs?.style)
724
- },
725
- children
726
- }));
727
-
728
- //#endregion
729
- //#region src/extensions/button.tsx
730
- const Button = EmailNode.create({
731
- name: "button",
732
- group: "block",
733
- content: "inline*",
734
- defining: true,
735
- draggable: true,
736
- marks: "bold",
737
- addAttributes() {
738
- return {
739
- class: { default: "button" },
740
- href: { default: "#" },
741
- alignment: { default: "left" }
742
- };
743
- },
744
- parseHTML() {
745
- return [{
746
- tag: "a[data-id=\"react-email-button\"]",
747
- getAttrs: (node) => {
748
- if (typeof node === "string") return false;
749
- const element = node;
750
- const attrs = {};
751
- Array.from(element.attributes).forEach((attr) => {
752
- attrs[attr.name] = attr.value;
753
- });
754
- return attrs;
755
- }
756
- }];
757
- },
758
- renderHTML({ HTMLAttributes }) {
759
- return [
760
- "div",
761
- (0, _tiptap_core.mergeAttributes)({ class: `align-${HTMLAttributes?.alignment}` }),
762
- [
763
- "a",
764
- (0, _tiptap_core.mergeAttributes)({
765
- class: `node-button ${HTMLAttributes?.class}`,
766
- style: HTMLAttributes?.style,
767
- "data-id": "react-email-button",
768
- "data-href": HTMLAttributes?.href
769
- }),
770
- 0
771
- ]
772
- ];
773
- },
774
- addCommands() {
775
- return {
776
- updateButton: (attributes) => ({ commands }) => {
777
- return commands.updateAttributes("button", attributes);
778
- },
779
- setButton: () => ({ commands }) => {
780
- return commands.insertContent({
781
- type: "button",
782
- content: [{
783
- type: "text",
784
- text: "Button"
785
- }]
786
- });
787
- }
788
- };
789
- },
790
- renderToReactEmail({ children, node, style }) {
791
- const inlineStyles = inlineCssToJs(node.attrs?.style);
792
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Row, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Column, {
793
- align: node.attrs?.align || node.attrs?.alignment,
794
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Button, {
795
- className: node.attrs?.class || void 0,
796
- href: node.attrs?.href,
797
- style: {
798
- ...style,
799
- ...inlineStyles
800
- },
801
- children
802
- })
803
- }) });
804
- }
805
- });
806
-
807
- //#endregion
808
- //#region src/extensions/class-attribute.tsx
809
- const ClassAttribute = _tiptap_core.Extension.create({
810
- name: "classAttribute",
811
- addOptions() {
812
- return {
813
- types: [],
814
- class: []
815
- };
816
- },
817
- addGlobalAttributes() {
818
- return [{
819
- types: this.options.types,
820
- attributes: { class: {
821
- default: "",
822
- parseHTML: (element) => element.className || "",
823
- renderHTML: (attributes) => {
824
- return attributes.class ? { class: attributes.class } : {};
825
- }
826
- } }
827
- }];
828
- },
829
- addCommands() {
830
- return {
831
- unsetClass: () => ({ commands }) => {
832
- return this.options.types.every((type) => commands.resetAttributes(type, "class"));
833
- },
834
- setClass: (classList) => ({ commands }) => {
835
- return this.options.types.every((type) => commands.updateAttributes(type, { class: classList }));
836
- }
837
- };
838
- },
839
- addKeyboardShortcuts() {
840
- return { Enter: ({ editor }) => {
841
- requestAnimationFrame(() => {
842
- editor.commands.resetAttributes("paragraph", "class");
843
- });
844
- return false;
845
- } };
846
- }
847
- });
848
-
849
- //#endregion
850
- //#region src/extensions/code.tsx
851
- const Code = EmailMark.from(_tiptap_extension_code.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
852
- style: {
853
- ...style,
854
- ...inlineCssToJs(node.attrs?.style)
855
- },
856
- children
857
- }));
858
-
859
- //#endregion
860
- //#region src/utils/prism-utils.ts
861
- const publicURL = "/styles/prism";
862
- function loadPrismTheme(theme) {
863
- const link = document.createElement("link");
864
- link.rel = "stylesheet";
865
- link.href = `${publicURL}/prism-${theme}.css`;
866
- link.setAttribute("data-prism-theme", "");
867
- document.head.appendChild(link);
868
- }
869
- function removePrismTheme() {
870
- const existingTheme = document.querySelectorAll("link[rel=\"stylesheet\"][data-prism-theme]");
871
- if (existingTheme.length > 0) existingTheme.forEach((cssLinkTag) => {
872
- cssLinkTag.remove();
873
- });
874
- }
875
- function hasPrismThemeLoaded(theme) {
876
- return !!document.querySelector(`link[rel="stylesheet"][data-prism-theme][href="${publicURL}/prism-${theme}.css"]`);
877
- }
878
-
879
- //#endregion
880
- //#region src/extensions/prism-plugin.ts
881
- const PRISM_LANGUAGE_LOADED_META = "prismLanguageLoaded";
882
- function parseNodes(nodes, className = []) {
883
- return nodes.flatMap((node) => {
884
- const classes = [...className, ...node.properties ? node.properties.className : []];
885
- if (node.children) return parseNodes(node.children, classes);
886
- return {
887
- text: node.value ?? "",
888
- classes
889
- };
890
- });
891
- }
892
- function getHighlightNodes(html) {
893
- return (0, hast_util_from_html.fromHtml)(html, { fragment: true }).children;
894
- }
895
- function registeredLang(aliasOrLanguage) {
896
- const allSupportLang = Object.keys(prismjs.default.languages).filter((id) => typeof prismjs.default.languages[id] === "object");
897
- return Boolean(allSupportLang.find((x) => x === aliasOrLanguage));
898
- }
899
- function getDecorations({ doc, name, defaultLanguage, defaultTheme, loadingLanguages, onLanguageLoaded }) {
900
- const decorations = [];
901
- (0, _tiptap_core.findChildren)(doc, (node) => node.type.name === name).forEach((block) => {
902
- let from = block.pos + 1;
903
- const language = block.node.attrs.language || defaultLanguage;
904
- const theme = block.node.attrs.theme || defaultTheme;
905
- let html = "";
906
- try {
907
- if (!registeredLang(language) && !loadingLanguages.has(language)) {
908
- loadingLanguages.add(language);
909
- import(`prismjs/components/prism-${language}`).then(() => {
910
- loadingLanguages.delete(language);
911
- onLanguageLoaded(language);
912
- }).catch(() => {
913
- loadingLanguages.delete(language);
914
- });
915
- }
916
- if (!hasPrismThemeLoaded(theme)) loadPrismTheme(theme);
917
- html = prismjs.default.highlight(block.node.textContent, prismjs.default.languages[language], language);
918
- } catch {
919
- html = prismjs.default.highlight(block.node.textContent, prismjs.default.languages.javascript, "js");
920
- }
921
- parseNodes(getHighlightNodes(html)).forEach((node) => {
922
- const to = from + node.text.length;
923
- if (node.classes.length) {
924
- const decoration = _tiptap_pm_view.Decoration.inline(from, to, { class: node.classes.join(" ") });
925
- decorations.push(decoration);
926
- }
927
- from = to;
928
- });
929
- });
930
- return _tiptap_pm_view.DecorationSet.create(doc, decorations);
931
- }
932
- function PrismPlugin({ name, defaultLanguage, defaultTheme }) {
933
- if (!defaultLanguage) throw Error("You must specify the defaultLanguage parameter");
934
- const loadingLanguages = /* @__PURE__ */ new Set();
935
- let pluginView = null;
936
- const onLanguageLoaded = (language) => {
937
- if (pluginView) pluginView.dispatch(pluginView.state.tr.setMeta(PRISM_LANGUAGE_LOADED_META, language));
938
- };
939
- const prismjsPlugin = new _tiptap_pm_state.Plugin({
940
- key: new _tiptap_pm_state.PluginKey("prism"),
941
- view(view) {
942
- pluginView = view;
943
- return { destroy() {
944
- pluginView = null;
945
- } };
946
- },
947
- state: {
948
- init: (_, { doc }) => {
949
- return getDecorations({
950
- doc,
951
- name,
952
- defaultLanguage,
953
- defaultTheme,
954
- loadingLanguages,
955
- onLanguageLoaded
956
- });
957
- },
958
- apply: (transaction, decorationSet, oldState, newState) => {
959
- const oldNodeName = oldState.selection.$head.parent.type.name;
960
- const newNodeName = newState.selection.$head.parent.type.name;
961
- const oldNodes = (0, _tiptap_core.findChildren)(oldState.doc, (node) => node.type.name === name);
962
- const newNodes = (0, _tiptap_core.findChildren)(newState.doc, (node) => node.type.name === name);
963
- if (transaction.getMeta(PRISM_LANGUAGE_LOADED_META) || transaction.docChanged && ([oldNodeName, newNodeName].includes(name) || newNodes.length !== oldNodes.length || transaction.steps.some((step) => {
964
- const rangeStep = step;
965
- return rangeStep.from !== void 0 && rangeStep.to !== void 0 && oldNodes.some((node) => {
966
- return node.pos >= rangeStep.from && node.pos + node.node.nodeSize <= rangeStep.to;
967
- });
968
- }))) return getDecorations({
969
- doc: transaction.doc,
970
- name,
971
- defaultLanguage,
972
- defaultTheme,
973
- loadingLanguages,
974
- onLanguageLoaded
975
- });
976
- return decorationSet.map(transaction.mapping, transaction.doc);
977
- }
978
- },
979
- props: { decorations(state) {
980
- return prismjsPlugin.getState(state);
981
- } },
982
- destroy() {
983
- pluginView = null;
984
- removePrismTheme();
985
- }
986
- });
987
- return prismjsPlugin;
988
- }
989
-
990
- //#endregion
991
- //#region src/extensions/code-block.tsx
992
- const CodeBlockPrism = EmailNode.from(_tiptap_extension_code_block.default.extend({
993
- addOptions() {
994
- return {
995
- languageClassPrefix: "language-",
996
- exitOnTripleEnter: false,
997
- exitOnArrowDown: false,
998
- enableTabIndentation: true,
999
- tabSize: 2,
1000
- defaultLanguage: "javascript",
1001
- defaultTheme: "default",
1002
- HTMLAttributes: {}
1003
- };
1004
- },
1005
- addAttributes() {
1006
- return {
1007
- ...this.parent?.(),
1008
- language: {
1009
- default: this.options.defaultLanguage,
1010
- parseHTML: (element) => {
1011
- if (!element) return null;
1012
- const { languageClassPrefix } = this.options;
1013
- if (!languageClassPrefix) return null;
1014
- const language = [...element.firstElementChild?.classList || []].filter((className) => className.startsWith(languageClassPrefix || "")).map((className) => className.replace(languageClassPrefix, ""))[0];
1015
- if (!language) return null;
1016
- return language;
1017
- },
1018
- rendered: false
1019
- },
1020
- theme: {
1021
- default: this.options.defaultTheme,
1022
- rendered: false
1023
- }
1024
- };
1025
- },
1026
- renderHTML({ node, HTMLAttributes }) {
1027
- return [
1028
- "pre",
1029
- (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, { class: node.attrs.language ? `${this.options.languageClassPrefix}${node.attrs.language}` : null }, { "data-theme": node.attrs.theme }),
1030
- [
1031
- "code",
1032
- { class: node.attrs.language ? `${this.options.languageClassPrefix}${node.attrs.language} node-codeTag` : "node-codeTag" },
1033
- 0
1034
- ]
1035
- ];
1036
- },
1037
- addKeyboardShortcuts() {
1038
- return {
1039
- ...this.parent?.(),
1040
- "Mod-a": ({ editor }) => {
1041
- const { state } = editor;
1042
- const { selection } = state;
1043
- const { $from } = selection;
1044
- for (let depth = $from.depth; depth >= 1; depth--) if ($from.node(depth).type.name === this.name) {
1045
- const blockStart = $from.start(depth);
1046
- const blockEnd = $from.end(depth);
1047
- if (selection.from === blockStart && selection.to === blockEnd) return false;
1048
- const tr = state.tr.setSelection(_tiptap_pm_state.TextSelection.create(state.doc, blockStart, blockEnd));
1049
- editor.view.dispatch(tr);
1050
- return true;
1051
- }
1052
- return false;
1053
- }
1054
- };
1055
- },
1056
- addProseMirrorPlugins() {
1057
- return [...this.parent?.() || [], PrismPlugin({
1058
- name: this.name,
1059
- defaultLanguage: this.options.defaultLanguage,
1060
- defaultTheme: this.options.defaultTheme
1061
- })];
1062
- }
1063
- }), ({ node, style }) => {
1064
- const language = node.attrs?.language ? `${node.attrs.language}` : "javascript";
1065
- const userTheme = _react_email_components[node.attrs?.theme];
1066
- const theme = userTheme ? {
1067
- ...userTheme,
1068
- base: {
1069
- ...userTheme.base,
1070
- borderRadius: "0.125rem",
1071
- padding: "0.75rem 1rem"
1072
- }
1073
- } : { base: {
1074
- color: "#1e293b",
1075
- background: "#f1f5f9",
1076
- lineHeight: "1.5",
1077
- fontFamily: "\"Fira Code\", \"Fira Mono\", Menlo, Consolas, \"DejaVu Sans Mono\", monospace",
1078
- padding: "0.75rem 1rem",
1079
- borderRadius: "0.125rem"
1080
- } };
1081
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.CodeBlock, {
1082
- code: node.content?.[0]?.text ?? "",
1083
- language,
1084
- theme,
1085
- style: {
1086
- width: "auto",
1087
- ...style
1088
- }
1089
- });
1090
- });
1091
-
1092
- //#endregion
1093
- //#region src/extensions/div.tsx
1094
- const Div = EmailNode.create({
1095
- name: "div",
1096
- group: "block",
1097
- content: "block+",
1098
- defining: true,
1099
- isolating: true,
1100
- parseHTML() {
1101
- return [{
1102
- tag: "div:not([data-type])",
1103
- getAttrs: (node) => {
1104
- if (typeof node === "string") return false;
1105
- const element = node;
1106
- const attrs = {};
1107
- Array.from(element.attributes).forEach((attr) => {
1108
- attrs[attr.name] = attr.value;
1109
- });
1110
- return attrs;
1111
- }
1112
- }];
1113
- },
1114
- renderHTML({ HTMLAttributes }) {
1115
- return [
1116
- "div",
1117
- (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
1118
- 0
1119
- ];
1120
- },
1121
- addAttributes() {
1122
- return { ...createStandardAttributes([...COMMON_HTML_ATTRIBUTES, ...LAYOUT_ATTRIBUTES]) };
1123
- },
1124
- renderToReactEmail({ children, node, style }) {
1125
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1126
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1127
- className: node.attrs?.class || void 0,
1128
- style: {
1129
- ...style,
1130
- ...inlineStyles
1131
- },
1132
- children
1133
- });
1134
- }
1135
- });
1136
-
1137
- //#endregion
1138
- //#region src/extensions/divider.tsx
1139
- const Divider = EmailNode.from(_tiptap_extension_horizontal_rule.default.extend({
1140
- addAttributes() {
1141
- return { class: { default: "divider" } };
1142
- },
1143
- addInputRules() {
1144
- return [new _tiptap_core.InputRule({
1145
- find: /^(?:---|—-|___\s|\*\*\*\s)$/,
1146
- handler: ({ state, range }) => {
1147
- const attributes = {};
1148
- const { tr } = state;
1149
- const start = range.from;
1150
- const end = range.to;
1151
- tr.insert(start - 1, this.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end));
1152
- }
1153
- })];
1154
- },
1155
- addNodeView() {
1156
- return (0, _tiptap_react.ReactNodeViewRenderer)((props) => {
1157
- const node = props.node;
1158
- const { class: className, ...rest } = node.attrs;
1159
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react.NodeViewWrapper, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Hr, {
1160
- ...rest,
1161
- className: "node-hr",
1162
- style: inlineCssToJs(node.attrs.style)
1163
- }) });
1164
- });
1165
- }
1166
- }), ({ node, style }) => {
1167
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Hr, {
1168
- className: node.attrs?.class || void 0,
1169
- style: {
1170
- ...style,
1171
- ...inlineCssToJs(node.attrs?.style)
1172
- }
1173
- });
1174
- });
1175
-
1176
- //#endregion
1177
- //#region src/extensions/global-content.ts
1178
- const GLOBAL_CONTENT_NODE_TYPE = "globalContent";
1179
- let cachedGlobalPosition = null;
1180
- function findGlobalContentPositions(doc) {
1181
- const positions = [];
1182
- doc.descendants((node, position) => {
1183
- if (node.type.name === GLOBAL_CONTENT_NODE_TYPE) positions.push(position);
1184
- });
1185
- return positions;
1186
- }
1187
- function getCachedGlobalContentPosition(doc) {
1188
- if (cachedGlobalPosition != null) try {
1189
- if (doc.nodeAt(cachedGlobalPosition)?.type.name === GLOBAL_CONTENT_NODE_TYPE) return cachedGlobalPosition;
1190
- } catch {
1191
- cachedGlobalPosition = null;
1192
- }
1193
- cachedGlobalPosition = findGlobalContentPositions(doc)[0] ?? null;
1194
- return cachedGlobalPosition;
1195
- }
1196
- function getGlobalContent(key, editor) {
1197
- const position = getCachedGlobalContentPosition(editor.state.doc);
1198
- if (cachedGlobalPosition == null) return null;
1199
- return editor.state.doc.nodeAt(position)?.attrs.data[key] ?? null;
1200
- }
1201
- const GlobalContent = _tiptap_core.Node.create({
1202
- name: GLOBAL_CONTENT_NODE_TYPE,
1203
- addOptions() {
1204
- return {
1205
- key: GLOBAL_CONTENT_NODE_TYPE,
1206
- data: {}
1207
- };
1208
- },
1209
- group: "block",
1210
- selectable: false,
1211
- draggable: false,
1212
- atom: true,
1213
- addAttributes() {
1214
- return { data: { default: this.options.data } };
1215
- },
1216
- parseHTML() {
1217
- return [{ tag: `div[data-type="${this.name}"]` }];
1218
- },
1219
- renderHTML({ HTMLAttributes }) {
1220
- return ["div", (0, _tiptap_core.mergeAttributes)(HTMLAttributes, {
1221
- "data-type": this.name,
1222
- style: "width: 100%; height: 1px; visibility: hidden;"
1223
- })];
1224
- },
1225
- addCommands() {
1226
- return { setGlobalContent: (key, value) => ({ tr, dispatch }) => {
1227
- const ensureGlobalPosition = () => {
1228
- const positions = findGlobalContentPositions(tr.doc);
1229
- for (let i = positions.length - 1; i > 0; i--) tr.delete(positions[i], positions[i] + 1);
1230
- const pos = positions[0] ?? -1;
1231
- if (pos >= 0) cachedGlobalPosition = pos;
1232
- else {
1233
- cachedGlobalPosition = 0;
1234
- tr.insert(0, this.type.create());
1235
- }
1236
- };
1237
- if (dispatch) {
1238
- ensureGlobalPosition();
1239
- if (cachedGlobalPosition == null) return false;
1240
- tr.setNodeAttribute(cachedGlobalPosition, "data", {
1241
- ...tr.doc.nodeAt(cachedGlobalPosition)?.attrs.data,
1242
- [key]: value
1243
- });
1244
- }
1245
- return true;
1246
- } };
1247
- }
1248
- });
1249
-
1250
- //#endregion
1251
- //#region src/extensions/hard-break.tsx
1252
- const HardBreak = EmailNode.from(_tiptap_extension_hard_break.default, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}));
1253
-
1254
- //#endregion
1255
- //#region src/extensions/heading.tsx
1256
- const Heading = EmailNode.from(_tiptap_extension_heading.Heading.extend({ addNodeView() {
1257
- return (0, _tiptap_react.ReactNodeViewRenderer)(({ node }) => {
1258
- const level = node.attrs.level ?? 1;
1259
- const { class: className, ...rest } = node.attrs;
1260
- const attrs = {
1261
- ...rest,
1262
- className: `node-h${level} ${className}`,
1263
- style: inlineCssToJs(node.attrs.style)
1264
- };
1265
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react.NodeViewWrapper, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Heading, {
1266
- as: `h${level}`,
1267
- ...attrs,
1268
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react.NodeViewContent, {})
1269
- }) });
1270
- });
1271
- } }), ({ children, node, style }) => {
1272
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Heading, {
1273
- as: `h${node.attrs?.level ?? 1}`,
1274
- className: node.attrs?.class || void 0,
1275
- style: {
1276
- ...style,
1277
- ...inlineCssToJs(node.attrs?.style),
1278
- ...getTextAlignment(node.attrs?.align ?? node.attrs?.alignment)
1279
- },
1280
- children
1281
- });
1282
- });
1283
-
1284
- //#endregion
1285
- //#region src/extensions/italic.tsx
1286
- const Italic = EmailMark.from(_tiptap_extension_italic.default, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", {
1287
- style,
1288
- children
1289
- }));
1290
-
1291
- //#endregion
1292
- //#region src/extensions/preserved-style.tsx
1293
- const PreservedStyle = EmailMark.create({
1294
- name: "preservedStyle",
1295
- addAttributes() {
1296
- return { style: {
1297
- default: null,
1298
- parseHTML: (element) => element.getAttribute("style"),
1299
- renderHTML: (attributes) => {
1300
- if (!attributes.style) return {};
1301
- return { style: attributes.style };
1302
- }
1303
- } };
1304
- },
1305
- parseHTML() {
1306
- return [{
1307
- tag: "span[style]",
1308
- getAttrs: (element) => {
1309
- if (typeof element === "string") return false;
1310
- const style = element.getAttribute("style");
1311
- if (style && hasPreservableStyles(style)) return { style };
1312
- return false;
1313
- }
1314
- }];
1315
- },
1316
- renderHTML({ HTMLAttributes }) {
1317
- return [
1318
- "span",
1319
- (0, _tiptap_core.mergeAttributes)(HTMLAttributes),
1320
- 0
1321
- ];
1322
- },
1323
- renderToReactEmail({ children, mark }) {
1324
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1325
- style: mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : void 0,
1326
- children
1327
- });
1328
- }
1329
- });
1330
- const LINK_INDICATOR_STYLES = [
1331
- "color",
1332
- "text-decoration",
1333
- "text-decoration-line",
1334
- "text-decoration-color",
1335
- "text-decoration-style"
1336
- ];
1337
- function parseStyleString(styleString) {
1338
- const temp = document.createElement("div");
1339
- temp.style.cssText = styleString;
1340
- return temp.style;
1341
- }
1342
- function hasBackground(style) {
1343
- const bgColor = style.backgroundColor;
1344
- const bg = style.background;
1345
- if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") return true;
1346
- if (bg && bg !== "transparent" && bg !== "none" && bg !== "rgba(0, 0, 0, 0)") return true;
1347
- return false;
1348
- }
1349
- function hasPreservableStyles(styleString) {
1350
- return processStylesForUnlink(styleString) !== null;
1351
- }
1352
- /**
1353
- * Processes styles when unlinking:
1354
- * - Has background (button-like): preserve all styles
1355
- * - No background: strip link-indicator styles (color, text-decoration), keep the rest
1356
- */
1357
- function processStylesForUnlink(styleString) {
1358
- if (!styleString) return null;
1359
- const style = parseStyleString(styleString);
1360
- if (hasBackground(style)) return styleString;
1361
- const filtered = [];
1362
- for (let i = 0; i < style.length; i++) {
1363
- const prop = style[i];
1364
- if (LINK_INDICATOR_STYLES.includes(prop)) continue;
1365
- const value = style.getPropertyValue(prop);
1366
- if (value) filtered.push(`${prop}: ${value}`);
1367
- }
1368
- return filtered.length > 0 ? filtered.join("; ") : null;
1369
- }
1370
-
1371
- //#endregion
1372
- //#region src/extensions/link.tsx
1373
- const Link = EmailMark.from(_tiptap_extension_link.default, ({ children, mark, style }) => {
1374
- const linkMarkStyle = mark.attrs?.style ? inlineCssToJs(mark.attrs.style) : {};
1375
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Link, {
1376
- href: mark.attrs?.href ?? void 0,
1377
- rel: mark.attrs?.rel ?? void 0,
1378
- style: {
1379
- ...style,
1380
- ...linkMarkStyle
1381
- },
1382
- target: mark.attrs?.target ?? void 0,
1383
- ...mark.attrs?.["ses:no-track"] ? { "ses:no-track": mark.attrs["ses:no-track"] } : {},
1384
- children
1385
- });
1386
- }).extend({
1387
- parseHTML() {
1388
- return [{
1389
- tag: "a[target]:not([data-id=\"react-email-button\"])",
1390
- getAttrs: (node) => {
1391
- if (typeof node === "string") return false;
1392
- const element = node;
1393
- const attrs = {};
1394
- Array.from(element.attributes).forEach((attr) => {
1395
- attrs[attr.name] = attr.value;
1396
- });
1397
- return attrs;
1398
- }
1399
- }, {
1400
- tag: "a[href]:not([data-id=\"react-email-button\"])",
1401
- getAttrs: (node) => {
1402
- if (typeof node === "string") return false;
1403
- const element = node;
1404
- const attrs = {};
1405
- Array.from(element.attributes).forEach((attr) => {
1406
- attrs[attr.name] = attr.value;
1407
- });
1408
- return attrs;
1409
- }
1410
- }];
1411
- },
1412
- addAttributes() {
1413
- return {
1414
- ...this.parent?.(),
1415
- "ses:no-track": {
1416
- default: null,
1417
- parseHTML: (element) => element.getAttribute("ses:no-track")
1418
- }
1419
- };
1420
- },
1421
- addCommands() {
1422
- return {
1423
- ...this.parent?.(),
1424
- unsetLink: () => ({ state, chain }) => {
1425
- const { from } = state.selection;
1426
- const linkStyle = state.doc.resolve(from).marks().find((m) => m.type.name === "link")?.attrs?.style ?? null;
1427
- const preservedStyle = processStylesForUnlink(linkStyle);
1428
- const shouldRemoveUnderline = preservedStyle !== linkStyle;
1429
- if (preservedStyle) {
1430
- const cmd = chain().extendMarkRange("link").unsetMark("link").setMark("preservedStyle", { style: preservedStyle });
1431
- return shouldRemoveUnderline ? cmd.unsetMark("underline").run() : cmd.run();
1432
- }
1433
- return chain().extendMarkRange("link").unsetMark("link").unsetMark("underline").run();
1434
- }
1435
- };
1436
- },
1437
- addKeyboardShortcuts() {
1438
- return { "Mod-k": () => {
1439
- editorEventBus.dispatch("bubble-menu:add-link", void 0);
1440
- return this.editor.chain().focus().toggleLink({ href: "" }).run();
1441
- } };
1442
- }
1443
- });
1444
-
1445
- //#endregion
1446
- //#region src/extensions/list-item.tsx
1447
- const ListItem = EmailNode.from(_tiptap_extension_list_item.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1448
- className: node.attrs?.class || void 0,
1449
- style: {
1450
- ...style,
1451
- ...inlineCssToJs(node.attrs?.style),
1452
- ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1453
- },
1454
- children
1455
- }));
1456
-
1457
- //#endregion
1458
- //#region src/extensions/max-nesting.ts
1459
- const MaxNesting = _tiptap_core.Extension.create({
1460
- name: "maxNesting",
1461
- addOptions() {
1462
- return {
1463
- maxDepth: 3,
1464
- nodeTypes: void 0
1465
- };
1466
- },
1467
- addProseMirrorPlugins() {
1468
- const { maxDepth, nodeTypes } = this.options;
1469
- if (typeof maxDepth !== "number" || maxDepth < 1) throw new Error("maxDepth must be a positive number");
1470
- return [new _tiptap_pm_state.Plugin({
1471
- key: new _tiptap_pm_state.PluginKey("maxNesting"),
1472
- appendTransaction(transactions, _oldState, newState) {
1473
- if (!transactions.some((tr$1) => tr$1.docChanged)) return null;
1474
- const rangesToLift = [];
1475
- newState.doc.descendants((node, pos) => {
1476
- let depth = 0;
1477
- let currentPos = pos;
1478
- let currentNode = node;
1479
- while (currentNode && depth <= maxDepth) {
1480
- if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
1481
- const $pos = newState.doc.resolve(currentPos);
1482
- if ($pos.depth === 0) break;
1483
- currentPos = $pos.before($pos.depth);
1484
- currentNode = newState.doc.nodeAt(currentPos);
1485
- }
1486
- if (depth > maxDepth) {
1487
- const $pos = newState.doc.resolve(pos);
1488
- if ($pos.depth > 0) {
1489
- const range = $pos.blockRange();
1490
- 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({
1491
- range,
1492
- target: range.start - 1
1493
- });
1494
- }
1495
- }
1496
- });
1497
- if (rangesToLift.length === 0) return null;
1498
- const tr = newState.tr;
1499
- for (let i = rangesToLift.length - 1; i >= 0; i--) {
1500
- const { range, target } = rangesToLift[i];
1501
- tr.lift(range, target);
1502
- }
1503
- return tr;
1504
- },
1505
- filterTransaction(tr) {
1506
- if (!tr.docChanged) return true;
1507
- let wouldCreateDeepNesting = false;
1508
- const newDoc = tr.doc;
1509
- newDoc.descendants((node, pos) => {
1510
- if (wouldCreateDeepNesting) return false;
1511
- let depth = 0;
1512
- let currentPos = pos;
1513
- let currentNode = node;
1514
- while (currentNode && depth <= maxDepth) {
1515
- if (!nodeTypes || nodeTypes.includes(currentNode.type.name)) depth++;
1516
- const $pos = newDoc.resolve(currentPos);
1517
- if ($pos.depth === 0) break;
1518
- currentPos = $pos.before($pos.depth);
1519
- currentNode = newDoc.nodeAt(currentPos);
1520
- }
1521
- if (depth > maxDepth) {
1522
- wouldCreateDeepNesting = true;
1523
- return false;
1524
- }
1525
- });
1526
- return !wouldCreateDeepNesting;
1527
- }
1528
- })];
1529
- }
1530
- });
1531
-
1532
- //#endregion
1533
- //#region src/extensions/ordered-list.tsx
1534
- const OrderedList = EmailNode.from(_tiptap_extension_ordered_list.default, ({ children, node, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1535
- className: node.attrs?.class || void 0,
1536
- start: node.attrs?.start,
1537
- style: {
1538
- ...style,
1539
- ...inlineCssToJs(node.attrs?.style)
1540
- },
1541
- children
1542
- }));
1543
-
1544
- //#endregion
1545
- //#region src/extensions/paragraph.tsx
1546
- const Paragraph = EmailNode.from(_tiptap_extension_paragraph.default, ({ children, node, style }) => {
1547
- const isEmpty = !node.content || node.content.length === 0;
1548
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1549
- className: node.attrs?.class || void 0,
1550
- style: {
1551
- ...style,
1552
- ...inlineCssToJs(node.attrs?.style),
1553
- ...getTextAlignment(node.attrs?.align || node.attrs?.alignment)
1554
- },
1555
- children: isEmpty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}) : children
1556
- });
1557
- });
1558
-
1559
- //#endregion
1560
- //#region src/extensions/placeholder.ts
1561
- const Placeholder = _tiptap_extension_placeholder.default.configure({
1562
- placeholder: ({ node }) => {
1563
- if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
1564
- return "Press '/' for commands";
1565
- },
1566
- includeChildren: true
1567
- });
1568
-
1569
- //#endregion
1570
- //#region src/extensions/preview-text.ts
1571
- const PreviewText = _tiptap_core.Node.create({
1572
- name: "previewText",
1573
- group: "block",
1574
- selectable: false,
1575
- draggable: false,
1576
- atom: true,
1577
- addOptions() {
1578
- return { HTMLAttributes: {} };
1579
- },
1580
- addStorage() {
1581
- return { previewText: null };
1582
- },
1583
- renderHTML() {
1584
- return ["div", { style: "display: none" }];
1585
- },
1586
- parseHTML() {
1587
- return [{
1588
- tag: "div[data-skip-in-text=\"true\"]",
1589
- getAttrs: (node) => {
1590
- if (typeof node === "string") return false;
1591
- const element = node;
1592
- let directText = "";
1593
- for (const child of element.childNodes) if (child.nodeType === 3) directText += child.textContent || "";
1594
- const cleanText = directText.trim();
1595
- if (cleanText) this.storage.previewText = cleanText;
1596
- return false;
1597
- }
1598
- }, {
1599
- tag: "span.preheader",
1600
- getAttrs: (node) => {
1601
- if (typeof node === "string") return false;
1602
- const preheaderText = node.textContent?.trim();
1603
- if (preheaderText) this.storage.previewText = preheaderText;
1604
- return false;
1605
- }
1606
- }];
1607
- }
1608
- });
1609
-
1610
- //#endregion
1611
- //#region src/extensions/section.tsx
1612
- const Section = EmailNode.create({
1613
- name: "section",
1614
- group: "block",
1615
- content: "block+",
1616
- isolating: true,
1617
- defining: true,
1618
- parseHTML() {
1619
- return [{ tag: "section[data-type=\"section\"]" }];
1620
- },
1621
- renderHTML({ HTMLAttributes }) {
1622
- return [
1623
- "section",
1624
- (0, _tiptap_core.mergeAttributes)({
1625
- "data-type": "section",
1626
- class: "node-section"
1627
- }, HTMLAttributes),
1628
- 0
1629
- ];
1630
- },
1631
- addCommands() {
1632
- return { insertSection: () => ({ commands }) => {
1633
- return commands.insertContent({
1634
- type: this.name,
1635
- content: [{
1636
- type: "paragraph",
1637
- content: []
1638
- }]
1639
- });
1640
- } };
1641
- },
1642
- renderToReactEmail({ children, node, style }) {
1643
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1644
- const textAlign = node.attrs?.align || node.attrs?.alignment;
1645
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Section, {
1646
- className: node.attrs?.class || void 0,
1647
- align: textAlign,
1648
- style: {
1649
- ...style,
1650
- ...inlineStyles,
1651
- ...getTextAlignment(textAlign)
1652
- },
1653
- children
1654
- });
1655
- }
1656
- });
1657
-
1658
- //#endregion
1659
- //#region src/extensions/strike.tsx
1660
- const Strike = EmailMark.from(_tiptap_extension_strike.default, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("s", {
1661
- style,
1662
- children
1663
- }));
1664
-
1665
- //#endregion
1666
- //#region src/extensions/style-attribute.tsx
1667
- const StyleAttribute = _tiptap_core.Extension.create({
1668
- name: "styleAttribute",
1669
- priority: 101,
1670
- addOptions() {
1671
- return {
1672
- types: [],
1673
- style: []
1674
- };
1675
- },
1676
- addGlobalAttributes() {
1677
- return [{
1678
- types: this.options.types,
1679
- attributes: { style: {
1680
- default: "",
1681
- parseHTML: (element) => element.getAttribute("style") || "",
1682
- renderHTML: (attributes) => {
1683
- return { style: attributes.style ?? "" };
1684
- }
1685
- } }
1686
- }];
1687
- },
1688
- addCommands() {
1689
- return {
1690
- unsetStyle: () => ({ commands }) => {
1691
- return this.options.types.every((type) => commands.resetAttributes(type, "style"));
1692
- },
1693
- setStyle: (style) => ({ commands }) => {
1694
- return this.options.types.every((type) => commands.updateAttributes(type, { style }));
1695
- }
1696
- };
1697
- },
1698
- addKeyboardShortcuts() {
1699
- return { Enter: ({ editor }) => {
1700
- const { state } = editor.view;
1701
- const { selection } = state;
1702
- const { $from } = selection;
1703
- const textBefore = $from.nodeBefore?.text || "";
1704
- if (textBefore.includes("{{") || textBefore.includes("{{{")) return false;
1705
- requestAnimationFrame(() => {
1706
- editor.commands.resetAttributes("paragraph", "style");
1707
- });
1708
- return false;
1709
- } };
1710
- }
1711
- });
1712
-
1713
- //#endregion
1714
- //#region src/extensions/sup.tsx
1715
- const SupBase = _tiptap_extension_superscript.default.extend({
1716
- name: "sup",
1717
- addCommands() {
1718
- return {
1719
- ...this.parent?.(),
1720
- setSup: () => ({ commands }) => {
1721
- return commands.setMark(this.name);
1722
- },
1723
- toggleSup: () => ({ commands }) => {
1724
- return commands.toggleMark(this.name);
1725
- },
1726
- unsetSup: () => ({ commands }) => {
1727
- return commands.unsetMark(this.name);
1728
- }
1729
- };
1730
- }
1731
- });
1732
- const Sup = EmailMark.from(SupBase, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("sup", {
1733
- style,
1734
- children
1735
- }));
1736
-
1737
- //#endregion
1738
- //#region src/extensions/table.tsx
1739
- const Table = EmailNode.create({
1740
- name: "table",
1741
- group: "block",
1742
- content: "tableRow+",
1743
- isolating: true,
1744
- tableRole: "table",
1745
- addAttributes() {
1746
- return { ...createStandardAttributes([
1747
- ...TABLE_ATTRIBUTES,
1748
- ...LAYOUT_ATTRIBUTES,
1749
- ...COMMON_HTML_ATTRIBUTES
1750
- ]) };
1751
- },
1752
- parseHTML() {
1753
- return [{
1754
- tag: "table",
1755
- getAttrs: (node) => {
1756
- if (typeof node === "string") return false;
1757
- const element = node;
1758
- const attrs = {};
1759
- Array.from(element.attributes).forEach((attr) => {
1760
- attrs[attr.name] = attr.value;
1761
- });
1762
- return attrs;
1763
- }
1764
- }];
1765
- },
1766
- renderHTML({ HTMLAttributes }) {
1767
- return [
1768
- "table",
1769
- (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes),
1770
- [
1771
- "tbody",
1772
- {},
1773
- 0
1774
- ]
1775
- ];
1776
- },
1777
- renderToReactEmail({ children, node, style }) {
1778
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1779
- const alignment = node.attrs?.align || node.attrs?.alignment;
1780
- const width = node.attrs?.width;
1781
- const centeringStyles = alignment === "center" ? {
1782
- marginLeft: "auto",
1783
- marginRight: "auto"
1784
- } : {};
1785
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Section, {
1786
- className: node.attrs?.class || void 0,
1787
- align: alignment,
1788
- style: resolveConflictingStyles(style, {
1789
- ...inlineStyles,
1790
- ...centeringStyles
1791
- }),
1792
- ...width !== void 0 ? { width } : {},
1793
- children
1794
- });
1795
- }
1796
- });
1797
- const TableRow = EmailNode.create({
1798
- name: "tableRow",
1799
- group: "tableRow",
1800
- content: "(tableCell | tableHeader)+",
1801
- addAttributes() {
1802
- return { ...createStandardAttributes([
1803
- ...TABLE_CELL_ATTRIBUTES,
1804
- ...LAYOUT_ATTRIBUTES,
1805
- ...COMMON_HTML_ATTRIBUTES
1806
- ]) };
1807
- },
1808
- parseHTML() {
1809
- return [{
1810
- tag: "tr",
1811
- getAttrs: (node) => {
1812
- if (typeof node === "string") return false;
1813
- const element = node;
1814
- const attrs = {};
1815
- Array.from(element.attributes).forEach((attr) => {
1816
- attrs[attr.name] = attr.value;
1817
- });
1818
- return attrs;
1819
- }
1820
- }];
1821
- },
1822
- renderHTML({ HTMLAttributes }) {
1823
- return [
1824
- "tr",
1825
- HTMLAttributes,
1826
- 0
1827
- ];
1828
- },
1829
- renderToReactEmail({ children, node, style }) {
1830
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1831
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
1832
- className: node.attrs?.class || void 0,
1833
- style: {
1834
- ...style,
1835
- ...inlineStyles
1836
- },
1837
- children
1838
- });
1839
- }
1840
- });
1841
- const TableCell = EmailNode.create({
1842
- name: "tableCell",
1843
- group: "tableCell",
1844
- content: "block+",
1845
- isolating: true,
1846
- addAttributes() {
1847
- return { ...createStandardAttributes([
1848
- ...TABLE_CELL_ATTRIBUTES,
1849
- ...LAYOUT_ATTRIBUTES,
1850
- ...COMMON_HTML_ATTRIBUTES
1851
- ]) };
1852
- },
1853
- parseHTML() {
1854
- return [{
1855
- tag: "td",
1856
- getAttrs: (node) => {
1857
- if (typeof node === "string") return false;
1858
- const element = node;
1859
- const attrs = {};
1860
- Array.from(element.attributes).forEach((attr) => {
1861
- attrs[attr.name] = attr.value;
1862
- });
1863
- return attrs;
1864
- }
1865
- }];
1866
- },
1867
- renderHTML({ HTMLAttributes }) {
1868
- return [
1869
- "td",
1870
- HTMLAttributes,
1871
- 0
1872
- ];
1873
- },
1874
- renderToReactEmail({ children, node, style }) {
1875
- const inlineStyles = inlineCssToJs(node.attrs?.style);
1876
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Column, {
1877
- className: node.attrs?.class || void 0,
1878
- align: node.attrs?.align || node.attrs?.alignment,
1879
- style: {
1880
- ...style,
1881
- ...inlineStyles
1882
- },
1883
- children
1884
- });
1885
- }
1886
- });
1887
- const TableHeader = _tiptap_core.Node.create({
1888
- name: "tableHeader",
1889
- group: "tableCell",
1890
- content: "block+",
1891
- isolating: true,
1892
- addAttributes() {
1893
- return { ...createStandardAttributes([
1894
- ...TABLE_HEADER_ATTRIBUTES,
1895
- ...TABLE_CELL_ATTRIBUTES,
1896
- ...LAYOUT_ATTRIBUTES,
1897
- ...COMMON_HTML_ATTRIBUTES
1898
- ]) };
1899
- },
1900
- parseHTML() {
1901
- return [{
1902
- tag: "th",
1903
- getAttrs: (node) => {
1904
- if (typeof node === "string") return false;
1905
- const element = node;
1906
- const attrs = {};
1907
- Array.from(element.attributes).forEach((attr) => {
1908
- attrs[attr.name] = attr.value;
1909
- });
1910
- return attrs;
1911
- }
1912
- }];
1913
- },
1914
- renderHTML({ HTMLAttributes }) {
1915
- return [
1916
- "th",
1917
- HTMLAttributes,
1918
- 0
1919
- ];
1920
- }
1921
- });
1922
-
1923
- //#endregion
1924
- //#region src/extensions/underline.tsx
1925
- const Underline = EmailMark.from(_tiptap_extension_underline.default, ({ children, style }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("u", {
1926
- style,
1927
- children
1928
- }));
1929
-
1930
- //#endregion
1931
- //#region src/extensions/uppercase.tsx
1932
- const Uppercase = EmailMark.create({
1933
- name: "uppercase",
1934
- addOptions() {
1935
- return { HTMLAttributes: {} };
1936
- },
1937
- parseHTML() {
1938
- return [{
1939
- tag: "span",
1940
- getAttrs: (node) => {
1941
- if (node.style.textTransform === "uppercase") return {};
1942
- return false;
1943
- }
1944
- }];
1945
- },
1946
- renderHTML({ HTMLAttributes }) {
1947
- return [
1948
- "span",
1949
- (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, { style: "text-transform: uppercase" }),
1950
- 0
1951
- ];
1952
- },
1953
- renderToReactEmail({ children, style }) {
1954
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1955
- style: {
1956
- ...style,
1957
- textTransform: "uppercase"
1958
- },
1959
- children
1960
- });
1961
- },
1962
- addCommands() {
1963
- return {
1964
- setUppercase: () => ({ commands }) => {
1965
- return commands.setMark(this.name);
1966
- },
1967
- toggleUppercase: () => ({ commands }) => {
1968
- return commands.toggleMark(this.name);
1969
- },
1970
- unsetUppercase: () => ({ commands }) => {
1971
- return commands.unsetMark(this.name);
1972
- }
1973
- };
1974
- }
1975
- });
1976
-
1977
- //#endregion
1978
- //#region src/extensions/columns.tsx
1979
- const COLUMN_PARENT_TYPES = [
1980
- "twoColumns",
1981
- "threeColumns",
1982
- "fourColumns"
1983
- ];
1984
- const COLUMN_PARENT_SET = new Set(COLUMN_PARENT_TYPES);
1985
- const MAX_COLUMNS_DEPTH = 3;
1986
- function getColumnsDepth(doc, from) {
1987
- const $from = doc.resolve(from);
1988
- let depth = 0;
1989
- for (let d = $from.depth; d > 0; d--) if (COLUMN_PARENT_SET.has($from.node(d).type.name)) depth++;
1990
- return depth;
1991
- }
1992
- const VARIANTS = [
1993
- {
1994
- name: "twoColumns",
1995
- columnCount: 2,
1996
- content: "columnsColumn columnsColumn",
1997
- dataType: "two-columns"
1998
- },
1999
- {
2000
- name: "threeColumns",
2001
- columnCount: 3,
2002
- content: "columnsColumn columnsColumn columnsColumn",
2003
- dataType: "three-columns"
2004
- },
2005
- {
2006
- name: "fourColumns",
2007
- columnCount: 4,
2008
- content: "columnsColumn{4}",
2009
- dataType: "four-columns"
2010
- }
2011
- ];
2012
- const NODE_TYPE_MAP = {
2013
- 2: "twoColumns",
2014
- 3: "threeColumns",
2015
- 4: "fourColumns"
2016
- };
2017
- function createColumnsNode(config, includeCommands) {
2018
- return EmailNode.create({
2019
- name: config.name,
2020
- group: "block",
2021
- content: config.content,
2022
- isolating: true,
2023
- defining: true,
2024
- addAttributes() {
2025
- return createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]);
2026
- },
2027
- parseHTML() {
2028
- return [{ tag: `div[data-type="${config.dataType}"]` }];
2029
- },
2030
- renderHTML({ HTMLAttributes }) {
2031
- return [
2032
- "div",
2033
- (0, _tiptap_core.mergeAttributes)({
2034
- "data-type": config.dataType,
2035
- class: "node-columns"
2036
- }, HTMLAttributes),
2037
- 0
2038
- ];
2039
- },
2040
- ...includeCommands && { addCommands() {
2041
- return { insertColumns: (count) => ({ commands, state }) => {
2042
- if (getColumnsDepth(state.doc, state.selection.from) >= MAX_COLUMNS_DEPTH) return false;
2043
- const nodeType = NODE_TYPE_MAP[count];
2044
- const children = Array.from({ length: count }, () => ({
2045
- type: "columnsColumn",
2046
- content: [{
2047
- type: "paragraph",
2048
- content: []
2049
- }]
2050
- }));
2051
- return commands.insertContent({
2052
- type: nodeType,
2053
- content: children
2054
- });
2055
- } };
2056
- } },
2057
- renderToReactEmail({ children, node, style }) {
2058
- const inlineStyles = inlineCssToJs(node.attrs?.style);
2059
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Row, {
2060
- className: node.attrs?.class || void 0,
2061
- style: {
2062
- ...style,
2063
- ...inlineStyles
2064
- },
2065
- children
2066
- });
2067
- }
2068
- });
2069
- }
2070
- const TwoColumns = createColumnsNode(VARIANTS[0], true);
2071
- const ThreeColumns = createColumnsNode(VARIANTS[1], false);
2072
- const FourColumns = createColumnsNode(VARIANTS[2], false);
2073
- const ColumnsColumn = EmailNode.create({
2074
- name: "columnsColumn",
2075
- group: "columnsColumn",
2076
- content: "block+",
2077
- isolating: true,
2078
- addAttributes() {
2079
- return { ...createStandardAttributes([...LAYOUT_ATTRIBUTES, ...COMMON_HTML_ATTRIBUTES]) };
2080
- },
2081
- parseHTML() {
2082
- return [{ tag: "div[data-type=\"column\"]" }];
2083
- },
2084
- renderHTML({ HTMLAttributes }) {
2085
- return [
2086
- "div",
2087
- (0, _tiptap_core.mergeAttributes)({
2088
- "data-type": "column",
2089
- class: "node-column"
2090
- }, HTMLAttributes),
2091
- 0
2092
- ];
2093
- },
2094
- addKeyboardShortcuts() {
2095
- return {
2096
- Backspace: ({ editor }) => {
2097
- const { state } = editor;
2098
- const { selection } = state;
2099
- const { empty, $from } = selection;
2100
- if (!empty) return false;
2101
- for (let depth = $from.depth; depth >= 1; depth--) {
2102
- if ($from.pos !== $from.start(depth)) break;
2103
- const indexInParent = $from.index(depth - 1);
2104
- if (indexInParent === 0) continue;
2105
- const prevNode = $from.node(depth - 1).child(indexInParent - 1);
2106
- if (COLUMN_PARENT_SET.has(prevNode.type.name)) {
2107
- const deleteFrom = $from.before(depth) - prevNode.nodeSize;
2108
- const deleteTo = $from.before(depth);
2109
- editor.view.dispatch(state.tr.delete(deleteFrom, deleteTo));
2110
- return true;
2111
- }
2112
- break;
2113
- }
2114
- return false;
2115
- },
2116
- "Mod-a": ({ editor }) => {
2117
- const { state } = editor;
2118
- const { $from } = state.selection;
2119
- for (let d = $from.depth; d > 0; d--) {
2120
- if ($from.node(d).type.name !== "columnsColumn") continue;
2121
- const columnStart = $from.start(d);
2122
- const columnEnd = $from.end(d);
2123
- const { from, to } = state.selection;
2124
- if (from === columnStart && to === columnEnd) return false;
2125
- editor.view.dispatch(state.tr.setSelection(_tiptap_pm_state.TextSelection.create(state.doc, columnStart, columnEnd)));
2126
- return true;
2127
- }
2128
- return false;
2129
- }
2130
- };
2131
- },
2132
- renderToReactEmail({ children, node, style }) {
2133
- const inlineStyles = inlineCssToJs(node.attrs?.style);
2134
- const width = node.attrs?.width;
2135
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_email_components.Column, {
2136
- className: node.attrs?.class || void 0,
2137
- style: {
2138
- ...style,
2139
- ...inlineStyles,
2140
- ...width ? { width } : {}
2141
- },
2142
- children
2143
- });
2144
- }
2145
- });
2146
-
2147
- //#endregion
2148
- //#region src/extensions/index.ts
2149
- const starterKitExtensions = {
2150
- CodeBlockPrism,
2151
- Code,
2152
- Paragraph,
2153
- BulletList,
2154
- OrderedList,
2155
- Blockquote,
2156
- ListItem,
2157
- HardBreak,
2158
- Italic,
2159
- Placeholder,
2160
- PreviewText,
2161
- Bold,
2162
- Strike,
2163
- Heading,
2164
- Divider,
2165
- Link,
2166
- Sup,
2167
- Underline,
2168
- Uppercase,
2169
- PreservedStyle,
2170
- Table,
2171
- TableRow,
2172
- TableCell,
2173
- TableHeader,
2174
- Body,
2175
- Div,
2176
- Button,
2177
- Section,
2178
- GlobalContent,
2179
- AlignmentAttribute,
2180
- StyleAttribute,
2181
- ClassAttribute,
2182
- MaxNesting
2183
- };
2184
- const StarterKit = _tiptap_core.Extension.create({
2185
- name: "reactEmailStarterKit",
2186
- addOptions() {
2187
- return {
2188
- TiptapStarterKit: {},
2189
- CodeBlockPrism: {
2190
- defaultLanguage: "javascript",
2191
- HTMLAttributes: { class: "prism node-codeBlock" }
2192
- },
2193
- Code: { HTMLAttributes: {
2194
- class: "node-inlineCode",
2195
- spellcheck: "false"
2196
- } },
2197
- Paragraph: { HTMLAttributes: { class: "node-paragraph" } },
2198
- BulletList: { HTMLAttributes: { class: "node-bulletList" } },
2199
- OrderedList: { HTMLAttributes: { class: "node-orderedList" } },
2200
- Blockquote: { HTMLAttributes: { class: "node-blockquote" } },
2201
- ListItem: {},
2202
- HardBreak: {},
2203
- Italic: {},
2204
- Placeholder: {},
2205
- PreviewText: {},
2206
- Bold: {},
2207
- Strike: {},
2208
- Heading: {},
2209
- Divider: {},
2210
- Link: {},
2211
- Sup: {},
2212
- Underline: {},
2213
- Uppercase: {},
2214
- PreservedStyle: {},
2215
- Table: {},
2216
- TableRow: {},
2217
- TableCell: {},
2218
- TableHeader: {},
2219
- Body: {},
2220
- Div: {},
2221
- Button: {},
2222
- Section: {},
2223
- GlobalContent: {},
2224
- AlignmentAttribute: { types: [
2225
- "heading",
2226
- "paragraph",
2227
- "image",
2228
- "blockquote",
2229
- "codeBlock",
2230
- "bulletList",
2231
- "orderedList",
2232
- "listItem",
2233
- "button",
2234
- "youtube",
2235
- "twitter",
2236
- "table",
2237
- "tableRow",
2238
- "tableCell",
2239
- "tableHeader",
2240
- "columnsColumn"
2241
- ] },
2242
- StyleAttribute: { types: [
2243
- "heading",
2244
- "paragraph",
2245
- "image",
2246
- "blockquote",
2247
- "codeBlock",
2248
- "bulletList",
2249
- "orderedList",
2250
- "listItem",
2251
- "button",
2252
- "youtube",
2253
- "twitter",
2254
- "horizontalRule",
2255
- "footer",
2256
- "section",
2257
- "div",
2258
- "body",
2259
- "table",
2260
- "tableRow",
2261
- "tableCell",
2262
- "tableHeader",
2263
- "columnsColumn",
2264
- "link"
2265
- ] },
2266
- ClassAttribute: { types: [
2267
- "heading",
2268
- "paragraph",
2269
- "image",
2270
- "blockquote",
2271
- "bulletList",
2272
- "orderedList",
2273
- "listItem",
2274
- "button",
2275
- "youtube",
2276
- "twitter",
2277
- "horizontalRule",
2278
- "footer",
2279
- "section",
2280
- "div",
2281
- "body",
2282
- "table",
2283
- "tableRow",
2284
- "tableCell",
2285
- "tableHeader",
2286
- "columnsColumn",
2287
- "link"
2288
- ] },
2289
- MaxNesting: {
2290
- maxDepth: 50,
2291
- nodeTypes: [
2292
- "section",
2293
- "bulletList",
2294
- "orderedList"
2295
- ]
2296
- }
2297
- };
2298
- },
2299
- addExtensions() {
2300
- const extensions = [];
2301
- if (this.options.TiptapStarterKit !== false) extensions.push(_tiptap_starter_kit.default.configure({
2302
- undoRedo: false,
2303
- heading: false,
2304
- link: false,
2305
- underline: false,
2306
- trailingNode: false,
2307
- bold: false,
2308
- italic: false,
2309
- strike: false,
2310
- code: false,
2311
- paragraph: false,
2312
- bulletList: false,
2313
- orderedList: false,
2314
- listItem: false,
2315
- blockquote: false,
2316
- hardBreak: false,
2317
- gapcursor: false,
2318
- codeBlock: false,
2319
- horizontalRule: false,
2320
- dropcursor: {
2321
- color: "#61a8f8",
2322
- class: "rounded-full animate-[fade-in_300ms_ease-in-out] !z-40",
2323
- width: 4
2324
- },
2325
- ...this.options.TiptapStarterKit
2326
- }));
2327
- for (const [name, extension] of Object.entries(starterKitExtensions)) {
2328
- const key = name;
2329
- const extensionOptions = this.options[key];
2330
- if (extensionOptions !== false) extensions.push(extension.configure(extensionOptions));
2331
- }
2332
- return extensions;
2333
- }
2334
- });
2335
-
2336
- //#endregion
2337
- //#region src/core/create-drop-handler.ts
2338
- function createDropHandler({ onPaste, onUploadImage }) {
2339
- return (view, event, _slice, moved) => {
2340
- if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
2341
- event.preventDefault();
2342
- const file = event.dataTransfer.files[0];
2343
- if (onPaste?.(file, view)) return true;
2344
- if (file.type.includes("image/") && onUploadImage) {
2345
- onUploadImage(file, view, (view.posAtCoords({
2346
- left: event.clientX,
2347
- top: event.clientY
2348
- })?.pos || 0) - 1);
2349
- return true;
2350
- }
2351
- }
2352
- return false;
2353
- };
2354
- }
2355
-
2356
- //#endregion
2357
- //#region src/utils/paste-sanitizer.ts
2358
- /**
2359
- * Sanitizes pasted HTML.
2360
- * - From editor (has node-* classes): pass through as-is
2361
- * - From external: strip all styles/classes, keep only semantic HTML
2362
- */
2363
- /**
2364
- * Detects content from the Resend editor by checking for node-* class names.
2365
- */
2366
- const EDITOR_CLASS_PATTERN = /class="[^"]*node-/;
2367
- /**
2368
- * Attributes to preserve on specific elements for EXTERNAL content.
2369
- * Only functional attributes - NO style or class.
2370
- */
2371
- const PRESERVED_ATTRIBUTES = {
2372
- a: [
2373
- "href",
2374
- "target",
2375
- "rel"
2376
- ],
2377
- img: [
2378
- "src",
2379
- "alt",
2380
- "width",
2381
- "height"
2382
- ],
2383
- td: ["colspan", "rowspan"],
2384
- th: [
2385
- "colspan",
2386
- "rowspan",
2387
- "scope"
2388
- ],
2389
- table: [
2390
- "border",
2391
- "cellpadding",
2392
- "cellspacing"
2393
- ],
2394
- "*": ["id"]
2395
- };
2396
- function isFromEditor(html) {
2397
- return EDITOR_CLASS_PATTERN.test(html);
2398
- }
2399
- function sanitizePastedHtml(html) {
2400
- if (isFromEditor(html)) return html;
2401
- const doc = new DOMParser().parseFromString(html, "text/html");
2402
- sanitizeNode(doc.body);
2403
- return doc.body.innerHTML;
2404
- }
2405
- function sanitizeNode(node) {
2406
- if (node.nodeType === Node.ELEMENT_NODE) sanitizeElement(node);
2407
- for (const child of Array.from(node.childNodes)) sanitizeNode(child);
2408
- }
2409
- function sanitizeElement(el) {
2410
- const allowedForTag = PRESERVED_ATTRIBUTES[el.tagName.toLowerCase()] || [];
2411
- const allowedGlobal = PRESERVED_ATTRIBUTES["*"] || [];
2412
- const allowed = new Set([...allowedForTag, ...allowedGlobal]);
2413
- const attributesToRemove = [];
2414
- for (const attr of Array.from(el.attributes)) {
2415
- if (attr.name.startsWith("data-")) {
2416
- attributesToRemove.push(attr.name);
2417
- continue;
2418
- }
2419
- if (!allowed.has(attr.name)) attributesToRemove.push(attr.name);
2420
- }
2421
- for (const attr of attributesToRemove) el.removeAttribute(attr);
2422
- }
2423
-
2424
- //#endregion
2425
- //#region src/core/create-paste-handler.ts
2426
- function createPasteHandler({ onPaste, onUploadImage, extensions }) {
2427
- return (view, event, slice) => {
2428
- const text = event.clipboardData?.getData("text/plain");
2429
- if (text && onPaste?.(text, view)) {
2430
- event.preventDefault();
2431
- return true;
2432
- }
2433
- if (event.clipboardData?.files?.[0]) {
2434
- const file = event.clipboardData.files[0];
2435
- if (onPaste?.(file, view)) {
2436
- event.preventDefault();
2437
- return true;
2438
- }
2439
- if (file.type.includes("image/") && onUploadImage) {
2440
- const pos = view.state.selection.from;
2441
- onUploadImage(file, view, pos);
2442
- return true;
2443
- }
2444
- }
2445
- /**
2446
- * If the coming content has a single child, we can assume
2447
- * it's a plain text and doesn't need to be parsed and
2448
- * be introduced in a new line
2449
- */
2450
- if (slice.content.childCount === 1) return false;
2451
- if (event.clipboardData?.getData?.("text/html")) {
2452
- event.preventDefault();
2453
- const jsonContent = (0, _tiptap_html.generateJSON)(sanitizePastedHtml(event.clipboardData.getData("text/html")), extensions);
2454
- const node = view.state.schema.nodeFromJSON(jsonContent);
2455
- const transaction = view.state.tr.replaceSelectionWith(node, false);
2456
- view.dispatch(transaction);
2457
- return true;
2458
- }
2459
- return false;
2460
- };
2461
- }
2462
-
2463
- //#endregion
2464
- //#region src/core/use-editor.ts
2465
- const COLLABORATION_EXTENSION_NAMES = new Set(["liveblocksExtension", "collaboration"]);
2466
- function hasCollaborationExtension(exts) {
2467
- return exts.some((ext) => COLLABORATION_EXTENSION_NAMES.has(ext.name));
2468
- }
2469
- function useEditor({ content, extensions = [], onUpdate, onPaste, onUploadImage, onReady, editable = true, ...rest }) {
2470
- const [contentError, setContentError] = react.useState(null);
2471
- const isCollaborative = hasCollaborationExtension(extensions);
2472
- const effectiveExtensions = react.useMemo(() => [
2473
- StarterKit,
2474
- ...isCollaborative ? [] : [_tiptap_extensions.UndoRedo],
2475
- ...extensions
2476
- ], [extensions, isCollaborative]);
2477
- const editor = (0, _tiptap_react.useEditor)({
2478
- content: isCollaborative ? void 0 : content,
2479
- extensions: effectiveExtensions,
2480
- editable,
2481
- immediatelyRender: false,
2482
- enableContentCheck: true,
2483
- onContentError({ editor: editor$1, error, disableCollaboration }) {
2484
- disableCollaboration();
2485
- setContentError(error);
2486
- console.error(error);
2487
- editor$1.setEditable(false);
2488
- },
2489
- onCreate({ editor: editor$1 }) {
2490
- onReady?.(editor$1);
2491
- },
2492
- onUpdate({ editor: editor$1, transaction }) {
2493
- onUpdate?.(editor$1, transaction);
2494
- },
2495
- editorProps: {
2496
- handleDOMEvents: { click: (view, event) => {
2497
- if (!view.editable) {
2498
- if (event.target.closest("a")) {
2499
- event.preventDefault();
2500
- return true;
2501
- }
2502
- }
2503
- return false;
2504
- } },
2505
- handlePaste: createPasteHandler({
2506
- onPaste,
2507
- onUploadImage,
2508
- extensions: effectiveExtensions
2509
- }),
2510
- handleDrop: createDropHandler({
2511
- onPaste,
2512
- onUploadImage
2513
- })
2514
- },
2515
- ...rest
2516
- });
2517
- return {
2518
- editor,
2519
- isEditorEmpty: (0, _tiptap_react.useEditorState)({
2520
- editor,
2521
- selector: (context) => {
2522
- if (!context.editor) return true;
2523
- return isDocumentVisuallyEmpty(context.editor.state.doc);
2524
- }
2525
- }) ?? true,
2526
- extensions: effectiveExtensions,
2527
- contentError,
2528
- isCollaborative
2529
- };
2530
- }
2531
-
2532
- //#endregion
2533
- //#region src/utils/set-text-alignment.ts
2534
- function setTextAlignment(editor, alignment) {
2535
- const { from, to } = editor.state.selection;
2536
- const tr = editor.state.tr;
2537
- editor.state.doc.nodesBetween(from, to, (node, pos) => {
2538
- if (node.isTextblock) {
2539
- const prop = "align" in node.attrs ? "align" : "alignment";
2540
- tr.setNodeMarkup(pos, null, {
2541
- ...node.attrs,
2542
- [prop]: alignment
2543
- });
2544
- }
2545
- });
2546
- editor.view.dispatch(tr);
2547
- }
2548
-
2549
- //#endregion
2550
- //#region src/ui/bubble-menu/context.tsx
2551
- const BubbleMenuContext = react.createContext(null);
2552
- function useBubbleMenuContext() {
2553
- const context = react.useContext(BubbleMenuContext);
2554
- if (!context) throw new Error("BubbleMenu compound components must be used within <BubbleMenu.Root>");
2555
- return context;
2556
- }
2557
-
2558
- //#endregion
2559
- //#region src/ui/bubble-menu/item.tsx
2560
- function BubbleMenuItem({ name, isActive, onCommand, className, children, ...rest }) {
2561
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2562
- type: "button",
2563
- "aria-label": name,
2564
- "aria-pressed": isActive,
2565
- className,
2566
- "data-re-bubble-menu-item": "",
2567
- "data-item": name,
2568
- ...isActive ? { "data-active": "" } : {},
2569
- onMouseDown: (e) => e.preventDefault(),
2570
- onClick: onCommand,
2571
- ...rest,
2572
- children
2573
- });
2574
- }
2575
-
2576
- //#endregion
2577
- //#region src/ui/bubble-menu/align-center.tsx
2578
- function BubbleMenuAlignCenter({ className, children }) {
2579
- const { editor } = useBubbleMenuContext();
2580
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
2581
- name: "align-center",
2582
- isActive: (0, _tiptap_react.useEditorState)({
2583
- editor,
2584
- selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "center" }) ?? false
2585
- }),
2586
- onCommand: () => setTextAlignment(editor, "center"),
2587
- className,
2588
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignCenterIcon, {})
2589
- });
2590
- }
2591
-
2592
- //#endregion
2593
- //#region src/ui/bubble-menu/align-left.tsx
2594
- function BubbleMenuAlignLeft({ className, children }) {
2595
- const { editor } = useBubbleMenuContext();
2596
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
2597
- name: "align-left",
2598
- isActive: (0, _tiptap_react.useEditorState)({
2599
- editor,
2600
- selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "left" }) ?? false
2601
- }),
2602
- onCommand: () => setTextAlignment(editor, "left"),
2603
- className,
2604
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignLeftIcon, {})
2605
- });
2606
- }
2607
-
2608
- //#endregion
2609
- //#region src/ui/bubble-menu/align-right.tsx
2610
- function BubbleMenuAlignRight({ className, children }) {
2611
- const { editor } = useBubbleMenuContext();
2612
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
2613
- name: "align-right",
2614
- isActive: (0, _tiptap_react.useEditorState)({
2615
- editor,
2616
- selector: ({ editor: editor$1 }) => editor$1?.isActive({ alignment: "right" }) ?? false
2617
- }),
2618
- onCommand: () => setTextAlignment(editor, "right"),
2619
- className,
2620
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlignRightIcon, {})
2621
- });
2622
- }
2623
-
2624
- //#endregion
2625
- //#region src/ui/bubble-menu/create-mark-bubble-item.tsx
2626
- function createMarkBubbleItem(config) {
2627
- function MarkBubbleItem({ className, children }) {
2628
- const { editor } = useBubbleMenuContext();
2629
- const isActive = (0, _tiptap_react.useEditorState)({
2630
- editor,
2631
- selector: ({ editor: editor$1 }) => {
2632
- if (config.activeParams) return editor$1?.isActive(config.activeName, config.activeParams) ?? false;
2633
- return editor$1?.isActive(config.activeName) ?? false;
2634
- }
2635
- });
2636
- const handleCommand = () => {
2637
- const chain = editor.chain().focus();
2638
- const method = chain[config.command];
2639
- if (method) method.call(chain).run();
2640
- };
2641
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItem, {
2642
- name: config.name,
2643
- isActive,
2644
- onCommand: handleCommand,
2645
- className,
2646
- children: children ?? config.icon
2647
- });
2648
- }
2649
- MarkBubbleItem.displayName = `BubbleMenu${config.name.charAt(0).toUpperCase() + config.name.slice(1)}`;
2650
- return MarkBubbleItem;
2651
- }
2652
-
2653
- //#endregion
2654
- //#region src/ui/bubble-menu/bold.tsx
2655
- const BubbleMenuBold = createMarkBubbleItem({
2656
- name: "bold",
2657
- activeName: "bold",
2658
- command: "toggleBold",
2659
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BoldIcon, {})
2660
- });
2661
-
2662
- //#endregion
2663
- //#region src/ui/bubble-menu/code.tsx
2664
- const BubbleMenuCode = createMarkBubbleItem({
2665
- name: "code",
2666
- activeName: "code",
2667
- command: "toggleCode",
2668
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CodeIcon, {})
2669
- });
2670
-
2671
- //#endregion
2672
- //#region src/ui/bubble-menu/group.tsx
2673
- function BubbleMenuItemGroup({ className, children }) {
2674
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
2675
- className,
2676
- "data-re-bubble-menu-group": "",
2677
- children
2678
- });
2679
- }
2680
-
2681
- //#endregion
2682
- //#region src/ui/bubble-menu/italic.tsx
2683
- const BubbleMenuItalic = createMarkBubbleItem({
2684
- name: "italic",
2685
- activeName: "italic",
2686
- command: "toggleItalic",
2687
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ItalicIcon, {})
2688
- });
2689
-
2690
- //#endregion
2691
- //#region src/ui/bubble-menu/utils.ts
2692
- const SAFE_PROTOCOLS = new Set([
2693
- "http:",
2694
- "https:",
2695
- "mailto:",
2696
- "tel:"
2697
- ]);
2698
- /**
2699
- * Basic URL validation and auto-prefixing.
2700
- * Rejects dangerous schemes (javascript:, data:, vbscript:, etc.).
2701
- * Returns the valid URL string or null.
2702
- */
2703
- function getUrlFromString(str) {
2704
- if (str === "#") return str;
2705
- try {
2706
- const url = new URL(str);
2707
- if (SAFE_PROTOCOLS.has(url.protocol)) return str;
2708
- return null;
2709
- } catch {}
2710
- try {
2711
- if (str.includes(".") && !str.includes(" ")) return new URL(`https://${str}`).toString();
2712
- } catch {}
2713
- return null;
2714
- }
2715
- function setLinkHref(editor, href) {
2716
- if (href.length === 0) {
2717
- editor.chain().unsetLink().run();
2718
- return;
2719
- }
2720
- const { from, to } = editor.state.selection;
2721
- if (from === to) {
2722
- editor.chain().extendMarkRange("link").setLink({ href }).setTextSelection({
2723
- from,
2724
- to
2725
- }).run();
2726
- return;
2727
- }
2728
- editor.chain().setLink({ href }).run();
2729
- }
2730
- function focusEditor(editor) {
2731
- setTimeout(() => {
2732
- editor.commands.focus();
2733
- }, 0);
2734
- }
2735
-
2736
- //#endregion
2737
- //#region src/ui/bubble-menu/link-selector.tsx
2738
- function BubbleMenuLinkSelector({ className, showToggle = true, validateUrl, onLinkApply, onLinkRemove, children, open: controlledOpen, onOpenChange }) {
2739
- const { editor } = useBubbleMenuContext();
2740
- const [uncontrolledOpen, setUncontrolledOpen] = react.useState(false);
2741
- const isControlled = controlledOpen !== void 0;
2742
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
2743
- const setIsOpen = react.useCallback((value) => {
2744
- if (!isControlled) setUncontrolledOpen(value);
2745
- onOpenChange?.(value);
2746
- }, [isControlled, onOpenChange]);
2747
- const editorState = (0, _tiptap_react.useEditorState)({
2748
- editor,
2749
- selector: ({ editor: editor$1 }) => ({
2750
- isLinkActive: editor$1?.isActive("link") ?? false,
2751
- hasLink: Boolean(editor$1?.getAttributes("link").href),
2752
- currentHref: editor$1?.getAttributes("link").href || ""
2753
- })
2754
- });
2755
- const setIsOpenRef = react.useRef(setIsOpen);
2756
- setIsOpenRef.current = setIsOpen;
2757
- react.useEffect(() => {
2758
- const subscription = editorEventBus.on("bubble-menu:add-link", () => {
2759
- setIsOpenRef.current(true);
2760
- });
2761
- return () => {
2762
- setIsOpenRef.current(false);
2763
- subscription.unsubscribe();
2764
- };
2765
- }, []);
2766
- if (!editorState) return null;
2767
- const handleOpenLink = () => {
2768
- setIsOpen(!isOpen);
2769
- };
2770
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2771
- "data-re-link-selector": "",
2772
- ...isOpen ? { "data-open": "" } : {},
2773
- ...editorState.hasLink ? { "data-has-link": "" } : {},
2774
- className,
2775
- children: [showToggle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2776
- type: "button",
2777
- "aria-expanded": isOpen,
2778
- "aria-haspopup": "true",
2779
- "aria-label": "Add link",
2780
- "aria-pressed": editorState.isLinkActive && editorState.hasLink,
2781
- "data-re-link-selector-trigger": "",
2782
- onClick: handleOpenLink,
2783
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LinkIcon, {})
2784
- }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkForm, {
2785
- editor,
2786
- currentHref: editorState.currentHref,
2787
- validateUrl,
2788
- onLinkApply,
2789
- onLinkRemove,
2790
- setIsOpen,
2791
- children
2792
- })]
2793
- });
2794
- }
2795
- function LinkForm({ editor, currentHref, validateUrl, onLinkApply, onLinkRemove, setIsOpen, children }) {
2796
- const inputRef = react.useRef(null);
2797
- const formRef = react.useRef(null);
2798
- const displayHref = currentHref === "#" ? "" : currentHref;
2799
- const [inputValue, setInputValue] = react.useState(displayHref);
2800
- react.useEffect(() => {
2801
- const timeoutId = setTimeout(() => {
2802
- inputRef.current?.focus();
2803
- }, 0);
2804
- return () => clearTimeout(timeoutId);
2805
- }, []);
2806
- react.useEffect(() => {
2807
- const handleKeyDown = (event) => {
2808
- if (event.key === "Escape") {
2809
- if (editor.getAttributes("link").href === "#") editor.chain().unsetLink().run();
2810
- setIsOpen(false);
2811
- }
2812
- };
2813
- const handleClickOutside = (event) => {
2814
- if (formRef.current && !formRef.current.contains(event.target)) {
2815
- const form = formRef.current;
2816
- const submitEvent = new Event("submit", {
2817
- bubbles: true,
2818
- cancelable: true
2819
- });
2820
- form.dispatchEvent(submitEvent);
2821
- setIsOpen(false);
2822
- }
2823
- };
2824
- document.addEventListener("mousedown", handleClickOutside);
2825
- window.addEventListener("keydown", handleKeyDown);
2826
- return () => {
2827
- window.removeEventListener("keydown", handleKeyDown);
2828
- document.removeEventListener("mousedown", handleClickOutside);
2829
- };
2830
- }, [editor, setIsOpen]);
2831
- function handleSubmit(e) {
2832
- e.preventDefault();
2833
- const value = inputValue.trim();
2834
- if (value === "") {
2835
- setLinkHref(editor, "");
2836
- setIsOpen(false);
2837
- focusEditor(editor);
2838
- onLinkRemove?.();
2839
- return;
2840
- }
2841
- const finalValue = (validateUrl ?? getUrlFromString)(value);
2842
- if (!finalValue) {
2843
- setLinkHref(editor, "");
2844
- setIsOpen(false);
2845
- focusEditor(editor);
2846
- onLinkRemove?.();
2847
- return;
2848
- }
2849
- setLinkHref(editor, finalValue);
2850
- setIsOpen(false);
2851
- focusEditor(editor);
2852
- onLinkApply?.(finalValue);
2853
- }
2854
- function handleUnlink(e) {
2855
- e.stopPropagation();
2856
- setLinkHref(editor, "");
2857
- setIsOpen(false);
2858
- focusEditor(editor);
2859
- onLinkRemove?.();
2860
- }
2861
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2862
- ref: formRef,
2863
- "data-re-link-selector-form": "",
2864
- onMouseDown: (e) => e.stopPropagation(),
2865
- onClick: (e) => e.stopPropagation(),
2866
- onKeyDown: (e) => e.stopPropagation(),
2867
- onSubmit: handleSubmit,
2868
- children: [
2869
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2870
- ref: inputRef,
2871
- "data-re-link-selector-input": "",
2872
- value: inputValue,
2873
- onFocus: (e) => e.stopPropagation(),
2874
- onChange: (e) => setInputValue(e.target.value),
2875
- placeholder: "Paste a link",
2876
- type: "text"
2877
- }),
2878
- children,
2879
- displayHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2880
- type: "button",
2881
- "aria-label": "Remove link",
2882
- "data-re-link-selector-unlink": "",
2883
- onClick: handleUnlink,
2884
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
2885
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2886
- type: "submit",
2887
- "aria-label": "Apply link",
2888
- "data-re-link-selector-apply": "",
2889
- onMouseDown: (e) => e.stopPropagation(),
2890
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
2891
- })
2892
- ]
2893
- });
2894
- }
2895
-
2896
- //#endregion
2897
- //#region src/ui/bubble-menu/node-selector.tsx
2898
- const NodeSelectorContext = react.createContext(null);
2899
- function useNodeSelectorContext() {
2900
- const context = react.useContext(NodeSelectorContext);
2901
- if (!context) throw new Error("NodeSelector compound components must be used within <NodeSelector.Root>");
2902
- return context;
2903
- }
2904
- function NodeSelectorRoot({ omit = [], open: controlledOpen, onOpenChange, className, children }) {
2905
- const { editor } = useBubbleMenuContext();
2906
- const [uncontrolledOpen, setUncontrolledOpen] = react.useState(false);
2907
- const isControlled = controlledOpen !== void 0;
2908
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
2909
- const setIsOpen = react.useCallback((value) => {
2910
- if (!isControlled) setUncontrolledOpen(value);
2911
- onOpenChange?.(value);
2912
- }, [isControlled, onOpenChange]);
2913
- const editorState = (0, _tiptap_react.useEditorState)({
2914
- editor,
2915
- selector: ({ editor: editor$1 }) => ({
2916
- isParagraphActive: (editor$1?.isActive("paragraph") ?? false) && !editor$1?.isActive("bulletList") && !editor$1?.isActive("orderedList"),
2917
- isHeading1Active: editor$1?.isActive("heading", { level: 1 }) ?? false,
2918
- isHeading2Active: editor$1?.isActive("heading", { level: 2 }) ?? false,
2919
- isHeading3Active: editor$1?.isActive("heading", { level: 3 }) ?? false,
2920
- isBulletListActive: editor$1?.isActive("bulletList") ?? false,
2921
- isOrderedListActive: editor$1?.isActive("orderedList") ?? false,
2922
- isBlockquoteActive: editor$1?.isActive("blockquote") ?? false,
2923
- isCodeBlockActive: editor$1?.isActive("codeBlock") ?? false
2924
- })
2925
- });
2926
- const allItems = react.useMemo(() => [
2927
- {
2928
- name: "Text",
2929
- icon: lucide_react.TextIcon,
2930
- command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").run(),
2931
- isActive: editorState?.isParagraphActive ?? false
2932
- },
2933
- {
2934
- name: "Title",
2935
- icon: lucide_react.Heading1,
2936
- command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
2937
- isActive: editorState?.isHeading1Active ?? false
2938
- },
2939
- {
2940
- name: "Subtitle",
2941
- icon: lucide_react.Heading2,
2942
- command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
2943
- isActive: editorState?.isHeading2Active ?? false
2944
- },
2945
- {
2946
- name: "Heading",
2947
- icon: lucide_react.Heading3,
2948
- command: () => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
2949
- isActive: editorState?.isHeading3Active ?? false
2950
- },
2951
- {
2952
- name: "Bullet List",
2953
- icon: lucide_react.List,
2954
- command: () => editor.chain().focus().clearNodes().toggleBulletList().run(),
2955
- isActive: editorState?.isBulletListActive ?? false
2956
- },
2957
- {
2958
- name: "Numbered List",
2959
- icon: lucide_react.ListOrdered,
2960
- command: () => editor.chain().focus().clearNodes().toggleOrderedList().run(),
2961
- isActive: editorState?.isOrderedListActive ?? false
2962
- },
2963
- {
2964
- name: "Quote",
2965
- icon: lucide_react.TextQuote,
2966
- command: () => editor.chain().focus().clearNodes().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
2967
- isActive: editorState?.isBlockquoteActive ?? false
2968
- },
2969
- {
2970
- name: "Code",
2971
- icon: lucide_react.Code,
2972
- command: () => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
2973
- isActive: editorState?.isCodeBlockActive ?? false
2974
- }
2975
- ], [editor, editorState]);
2976
- const items = react.useMemo(() => allItems.filter((item) => !omit.includes(item.name)), [allItems, omit]);
2977
- const activeItem = react.useMemo(() => items.find((item) => item.isActive) ?? { name: "Multiple" }, [items]);
2978
- const contextValue = react.useMemo(() => ({
2979
- items,
2980
- activeItem,
2981
- isOpen,
2982
- setIsOpen
2983
- }), [
2984
- items,
2985
- activeItem,
2986
- isOpen,
2987
- setIsOpen
2988
- ]);
2989
- if (!editorState || items.length === 0) return null;
2990
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContext.Provider, {
2991
- value: contextValue,
2992
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Root, {
2993
- open: isOpen,
2994
- onOpenChange: setIsOpen,
2995
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2996
- "data-re-node-selector": "",
2997
- ...isOpen ? { "data-open": "" } : {},
2998
- className,
2999
- children
3000
- })
3001
- })
3002
- });
3003
- }
3004
- function NodeSelectorTrigger({ className, children }) {
3005
- const { activeItem, isOpen, setIsOpen } = useNodeSelectorContext();
3006
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Trigger, {
3007
- "data-re-node-selector-trigger": "",
3008
- className,
3009
- onClick: () => setIsOpen(!isOpen),
3010
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: activeItem.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {})] })
3011
- });
3012
- }
3013
- function NodeSelectorContent({ className, align = "start", children }) {
3014
- const { items, setIsOpen } = useNodeSelectorContext();
3015
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Content, {
3016
- align,
3017
- "data-re-node-selector-content": "",
3018
- className,
3019
- children: children ? children(items, () => setIsOpen(false)) : items.map((item) => {
3020
- const Icon = item.icon;
3021
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3022
- type: "button",
3023
- "data-re-node-selector-item": "",
3024
- ...item.isActive ? { "data-active": "" } : {},
3025
- onClick: () => {
3026
- item.command();
3027
- setIsOpen(false);
3028
- },
3029
- children: [
3030
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}),
3031
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.name }),
3032
- item.isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
3033
- ]
3034
- }, item.name);
3035
- })
3036
- });
3037
- }
3038
- function BubbleMenuNodeSelector({ omit = [], className, triggerContent, open, onOpenChange }) {
3039
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(NodeSelectorRoot, {
3040
- omit,
3041
- open,
3042
- onOpenChange,
3043
- className,
3044
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorTrigger, { children: triggerContent }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorContent, {})]
3045
- });
3046
- }
3047
-
3048
- //#endregion
3049
- //#region src/ui/bubble-menu/root.tsx
3050
- function BubbleMenuRoot({ excludeNodes = [], placement = "bottom", offset: offset$1 = 8, onHide, className, children }) {
3051
- const { editor } = (0, _tiptap_react.useCurrentEditor)();
3052
- if (!editor) return null;
3053
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react_menus.BubbleMenu, {
3054
- editor,
3055
- "data-re-bubble-menu": "",
3056
- shouldShow: ({ editor: editor$1, view }) => {
3057
- for (const node of excludeNodes) if (editor$1.isActive(node)) return false;
3058
- if (view.dom.classList.contains("dragging")) return false;
3059
- return editor$1.view.state.selection.content().size > 0;
3060
- },
3061
- options: {
3062
- placement,
3063
- offset: offset$1,
3064
- onHide
3065
- },
3066
- className,
3067
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuContext.Provider, {
3068
- value: { editor },
3069
- children
3070
- })
3071
- });
3072
- }
3073
-
3074
- //#endregion
3075
- //#region src/ui/bubble-menu/strike.tsx
3076
- const BubbleMenuStrike = createMarkBubbleItem({
3077
- name: "strike",
3078
- activeName: "strike",
3079
- command: "toggleStrike",
3080
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.StrikethroughIcon, {})
3081
- });
3082
-
3083
- //#endregion
3084
- //#region src/ui/bubble-menu/underline.tsx
3085
- const BubbleMenuUnderline = createMarkBubbleItem({
3086
- name: "underline",
3087
- activeName: "underline",
3088
- command: "toggleUnderline",
3089
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnderlineIcon, {})
3090
- });
3091
-
3092
- //#endregion
3093
- //#region src/ui/bubble-menu/uppercase.tsx
3094
- const BubbleMenuUppercase = createMarkBubbleItem({
3095
- name: "uppercase",
3096
- activeName: "uppercase",
3097
- command: "toggleUppercase",
3098
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CaseUpperIcon, {})
3099
- });
3100
-
3101
- //#endregion
3102
- //#region src/ui/bubble-menu/default.tsx
3103
- function BubbleMenuDefault({ excludeItems = [], excludeNodes, placement, offset: offset$1, onHide, className }) {
3104
- const [isNodeSelectorOpen, setIsNodeSelectorOpen] = react.useState(false);
3105
- const [isLinkSelectorOpen, setIsLinkSelectorOpen] = react.useState(false);
3106
- const has = (item) => !excludeItems.includes(item);
3107
- const handleNodeSelectorOpenChange = react.useCallback((open) => {
3108
- setIsNodeSelectorOpen(open);
3109
- if (open) setIsLinkSelectorOpen(false);
3110
- }, []);
3111
- const handleLinkSelectorOpenChange = react.useCallback((open) => {
3112
- setIsLinkSelectorOpen(open);
3113
- if (open) setIsNodeSelectorOpen(false);
3114
- }, []);
3115
- const handleHide = react.useCallback(() => {
3116
- setIsNodeSelectorOpen(false);
3117
- setIsLinkSelectorOpen(false);
3118
- onHide?.();
3119
- }, [onHide]);
3120
- const hasFormattingItems = has("bold") || has("italic") || has("underline") || has("strike") || has("code") || has("uppercase");
3121
- const hasAlignmentItems = has("align-left") || has("align-center") || has("align-right");
3122
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuRoot, {
3123
- excludeNodes,
3124
- placement,
3125
- offset: offset$1,
3126
- onHide: handleHide,
3127
- className,
3128
- children: [
3129
- has("node-selector") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuNodeSelector, {
3130
- open: isNodeSelectorOpen,
3131
- onOpenChange: handleNodeSelectorOpenChange
3132
- }),
3133
- has("link-selector") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuLinkSelector, {
3134
- open: isLinkSelectorOpen,
3135
- onOpenChange: handleLinkSelectorOpenChange
3136
- }),
3137
- hasFormattingItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuItemGroup, { children: [
3138
- has("bold") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuBold, {}),
3139
- has("italic") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuItalic, {}),
3140
- has("underline") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuUnderline, {}),
3141
- has("strike") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuStrike, {}),
3142
- has("code") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuCode, {}),
3143
- has("uppercase") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuUppercase, {})
3144
- ] }),
3145
- hasAlignmentItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BubbleMenuItemGroup, { children: [
3146
- has("align-left") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuAlignLeft, {}),
3147
- has("align-center") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuAlignCenter, {}),
3148
- has("align-right") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleMenuAlignRight, {})
3149
- ] })
3150
- ]
3151
- });
3152
- }
3153
-
3154
- //#endregion
3155
- //#region src/ui/bubble-menu/separator.tsx
3156
- function BubbleMenuSeparator({ className }) {
3157
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
3158
- className,
3159
- "data-re-bubble-menu-separator": ""
3160
- });
3161
- }
3162
-
3163
- //#endregion
3164
- //#region src/ui/bubble-menu/index.ts
3165
- const BubbleMenu = {
3166
- Root: BubbleMenuRoot,
3167
- ItemGroup: BubbleMenuItemGroup,
3168
- Separator: BubbleMenuSeparator,
3169
- Item: BubbleMenuItem,
3170
- Bold: BubbleMenuBold,
3171
- Italic: BubbleMenuItalic,
3172
- Underline: BubbleMenuUnderline,
3173
- Strike: BubbleMenuStrike,
3174
- Code: BubbleMenuCode,
3175
- Uppercase: BubbleMenuUppercase,
3176
- AlignLeft: BubbleMenuAlignLeft,
3177
- AlignCenter: BubbleMenuAlignCenter,
3178
- AlignRight: BubbleMenuAlignRight,
3179
- NodeSelector: Object.assign(BubbleMenuNodeSelector, {
3180
- Root: NodeSelectorRoot,
3181
- Trigger: NodeSelectorTrigger,
3182
- Content: NodeSelectorContent
3183
- }),
3184
- LinkSelector: BubbleMenuLinkSelector,
3185
- Default: BubbleMenuDefault
3186
- };
3187
-
3188
- //#endregion
3189
- //#region src/ui/button-bubble-menu/context.tsx
3190
- const ButtonBubbleMenuContext = react.createContext(null);
3191
- function useButtonBubbleMenuContext() {
3192
- const context = react.useContext(ButtonBubbleMenuContext);
3193
- if (!context) throw new Error("ButtonBubbleMenu compound components must be used within <ButtonBubbleMenu.Root>");
3194
- return context;
3195
- }
3196
-
3197
- //#endregion
3198
- //#region src/ui/button-bubble-menu/edit-link.tsx
3199
- function ButtonBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
3200
- const { setIsEditing } = useButtonBubbleMenuContext();
3201
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3202
- ...rest,
3203
- type: "button",
3204
- "aria-label": "Edit link",
3205
- "data-re-btn-bm-item": "",
3206
- "data-item": "edit-link",
3207
- className,
3208
- onMouseDown: (e) => {
3209
- e.preventDefault();
3210
- onMouseDown?.(e);
3211
- },
3212
- onClick: (e) => {
3213
- onClick?.(e);
3214
- setIsEditing(true);
3215
- },
3216
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LinkIcon, {})
3217
- });
3218
- }
3219
-
3220
- //#endregion
3221
- //#region src/ui/button-bubble-menu/root.tsx
3222
- function ButtonBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3223
- const { editor } = (0, _tiptap_react.useCurrentEditor)();
3224
- const [isEditing, setIsEditing] = react.useState(false);
3225
- if (!editor) return null;
3226
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react_menus.BubbleMenu, {
3227
- editor,
3228
- "data-re-btn-bm": "",
3229
- shouldShow: ({ editor: e, view }) => e.isActive("button") && !view.dom.classList.contains("dragging"),
3230
- options: {
3231
- placement,
3232
- offset: offset$1,
3233
- onHide: () => {
3234
- setIsEditing(false);
3235
- onHide?.();
3236
- }
3237
- },
3238
- className,
3239
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuContext.Provider, {
3240
- value: {
3241
- editor,
3242
- isEditing,
3243
- setIsEditing
3244
- },
3245
- children
3246
- })
3247
- });
3248
- }
3249
-
3250
- //#endregion
3251
- //#region src/ui/button-bubble-menu/toolbar.tsx
3252
- function ButtonBubbleMenuToolbar({ children, ...rest }) {
3253
- const { isEditing } = useButtonBubbleMenuContext();
3254
- if (isEditing) return null;
3255
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3256
- "data-re-btn-bm-toolbar": "",
3257
- ...rest,
3258
- children
3259
- });
3260
- }
3261
-
3262
- //#endregion
3263
- //#region src/ui/button-bubble-menu/default.tsx
3264
- function ButtonBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
3265
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuRoot, {
3266
- placement,
3267
- offset: offset$1,
3268
- onHide,
3269
- className,
3270
- children: !excludeItems.includes("edit-link") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuToolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonBubbleMenuEditLink, {}) })
3271
- });
3272
- }
3273
-
3274
- //#endregion
3275
- //#region src/ui/button-bubble-menu/index.ts
3276
- const ButtonBubbleMenu = {
3277
- Root: ButtonBubbleMenuRoot,
3278
- Toolbar: ButtonBubbleMenuToolbar,
3279
- EditLink: ButtonBubbleMenuEditLink,
3280
- Default: ButtonBubbleMenuDefault
3281
- };
3282
-
3283
- //#endregion
3284
- //#region src/ui/image-bubble-menu/context.tsx
3285
- const ImageBubbleMenuContext = react.createContext(null);
3286
- function useImageBubbleMenuContext() {
3287
- const context = react.useContext(ImageBubbleMenuContext);
3288
- if (!context) throw new Error("ImageBubbleMenu compound components must be used within <ImageBubbleMenu.Root>");
3289
- return context;
3290
- }
3291
-
3292
- //#endregion
3293
- //#region src/ui/image-bubble-menu/edit-link.tsx
3294
- function ImageBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
3295
- const { setIsEditing } = useImageBubbleMenuContext();
3296
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3297
- ...rest,
3298
- type: "button",
3299
- "aria-label": "Edit link",
3300
- "data-re-img-bm-item": "",
3301
- "data-item": "edit-link",
3302
- className,
3303
- onMouseDown: (e) => {
3304
- e.preventDefault();
3305
- onMouseDown?.(e);
3306
- },
3307
- onClick: (e) => {
3308
- onClick?.(e);
3309
- setIsEditing(true);
3310
- },
3311
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LinkIcon, {})
3312
- });
3313
- }
3314
-
3315
- //#endregion
3316
- //#region src/ui/image-bubble-menu/root.tsx
3317
- function ImageBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3318
- const { editor } = (0, _tiptap_react.useCurrentEditor)();
3319
- const [isEditing, setIsEditing] = react.useState(false);
3320
- if (!editor) return null;
3321
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react_menus.BubbleMenu, {
3322
- editor,
3323
- "data-re-img-bm": "",
3324
- shouldShow: ({ editor: e, view }) => e.isActive("image") && !view.dom.classList.contains("dragging"),
3325
- options: {
3326
- placement,
3327
- offset: offset$1,
3328
- onHide: () => {
3329
- setIsEditing(false);
3330
- onHide?.();
3331
- }
3332
- },
3333
- className,
3334
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuContext.Provider, {
3335
- value: {
3336
- editor,
3337
- isEditing,
3338
- setIsEditing
3339
- },
3340
- children
3341
- })
3342
- });
3343
- }
3344
-
3345
- //#endregion
3346
- //#region src/ui/image-bubble-menu/toolbar.tsx
3347
- function ImageBubbleMenuToolbar({ children, ...rest }) {
3348
- const { isEditing } = useImageBubbleMenuContext();
3349
- if (isEditing) return null;
3350
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3351
- "data-re-img-bm-toolbar": "",
3352
- ...rest,
3353
- children
3354
- });
3355
- }
3356
-
3357
- //#endregion
3358
- //#region src/ui/image-bubble-menu/default.tsx
3359
- function ImageBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className }) {
3360
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuRoot, {
3361
- placement,
3362
- offset: offset$1,
3363
- onHide,
3364
- className,
3365
- children: !excludeItems.includes("edit-link") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuToolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageBubbleMenuEditLink, {}) })
3366
- });
3367
- }
3368
-
3369
- //#endregion
3370
- //#region src/ui/image-bubble-menu/index.ts
3371
- const ImageBubbleMenu = {
3372
- Root: ImageBubbleMenuRoot,
3373
- Toolbar: ImageBubbleMenuToolbar,
3374
- EditLink: ImageBubbleMenuEditLink,
3375
- Default: ImageBubbleMenuDefault
3376
- };
3377
-
3378
- //#endregion
3379
- //#region src/ui/link-bubble-menu/context.tsx
3380
- const LinkBubbleMenuContext = react.createContext(null);
3381
- function useLinkBubbleMenuContext() {
3382
- const context = react.useContext(LinkBubbleMenuContext);
3383
- if (!context) throw new Error("LinkBubbleMenu compound components must be used within <LinkBubbleMenu.Root>");
3384
- return context;
3385
- }
3386
-
3387
- //#endregion
3388
- //#region src/ui/link-bubble-menu/edit-link.tsx
3389
- function LinkBubbleMenuEditLink({ className, children, onClick, onMouseDown, ...rest }) {
3390
- const { setIsEditing } = useLinkBubbleMenuContext();
3391
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3392
- type: "button",
3393
- "aria-label": "Edit link",
3394
- "data-re-link-bm-item": "",
3395
- "data-item": "edit-link",
3396
- className,
3397
- onMouseDown: (e) => {
3398
- e.preventDefault();
3399
- onMouseDown?.(e);
3400
- },
3401
- onClick: (e) => {
3402
- onClick?.(e);
3403
- setIsEditing(true);
3404
- },
3405
- ...rest,
3406
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PencilIcon, {})
3407
- });
3408
- }
3409
-
3410
- //#endregion
3411
- //#region src/ui/link-bubble-menu/form.tsx
3412
- function LinkBubbleMenuForm({ className, validateUrl, onLinkApply, onLinkRemove, children }) {
3413
- const { editor, linkHref, isEditing, setIsEditing } = useLinkBubbleMenuContext();
3414
- const inputRef = react.useRef(null);
3415
- const formRef = react.useRef(null);
3416
- const displayHref = linkHref === "#" ? "" : linkHref;
3417
- const [inputValue, setInputValue] = react.useState(displayHref);
3418
- react.useEffect(() => {
3419
- if (!isEditing) return;
3420
- const timeoutId = setTimeout(() => {
3421
- inputRef.current?.focus();
3422
- }, 0);
3423
- return () => clearTimeout(timeoutId);
3424
- }, [isEditing]);
3425
- react.useEffect(() => {
3426
- if (!isEditing) return;
3427
- const handleKeyDown = (event) => {
3428
- if (event.key === "Escape") setIsEditing(false);
3429
- };
3430
- const handleClickOutside = (event) => {
3431
- if (formRef.current && !formRef.current.contains(event.target)) {
3432
- const form = formRef.current;
3433
- const submitEvent = new Event("submit", {
3434
- bubbles: true,
3435
- cancelable: true
3436
- });
3437
- form.dispatchEvent(submitEvent);
3438
- setIsEditing(false);
3439
- }
3440
- };
3441
- document.addEventListener("mousedown", handleClickOutside);
3442
- window.addEventListener("keydown", handleKeyDown);
3443
- return () => {
3444
- window.removeEventListener("keydown", handleKeyDown);
3445
- document.removeEventListener("mousedown", handleClickOutside);
3446
- };
3447
- }, [isEditing, setIsEditing]);
3448
- if (!isEditing) return null;
3449
- function handleSubmit(e) {
3450
- e.preventDefault();
3451
- const value = inputValue.trim();
3452
- if (value === "") {
3453
- setLinkHref(editor, "");
3454
- setIsEditing(false);
3455
- focusEditor(editor);
3456
- onLinkRemove?.();
3457
- return;
3458
- }
3459
- const finalValue = (validateUrl ?? getUrlFromString)(value);
3460
- if (!finalValue) {
3461
- setLinkHref(editor, "");
3462
- setIsEditing(false);
3463
- focusEditor(editor);
3464
- onLinkRemove?.();
3465
- return;
3466
- }
3467
- setLinkHref(editor, finalValue);
3468
- setIsEditing(false);
3469
- focusEditor(editor);
3470
- onLinkApply?.(finalValue);
3471
- }
3472
- function handleUnlink(e) {
3473
- e.stopPropagation();
3474
- setLinkHref(editor, "");
3475
- setIsEditing(false);
3476
- focusEditor(editor);
3477
- onLinkRemove?.();
3478
- }
3479
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
3480
- ref: formRef,
3481
- "data-re-link-bm-form": "",
3482
- className,
3483
- onMouseDown: (e) => e.stopPropagation(),
3484
- onClick: (e) => e.stopPropagation(),
3485
- onKeyDown: (e) => e.stopPropagation(),
3486
- onSubmit: handleSubmit,
3487
- children: [
3488
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3489
- ref: inputRef,
3490
- "data-re-link-bm-input": "",
3491
- value: inputValue,
3492
- onFocus: (e) => e.stopPropagation(),
3493
- onChange: (e) => setInputValue(e.target.value),
3494
- placeholder: "Paste a link",
3495
- type: "text"
3496
- }),
3497
- children,
3498
- displayHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3499
- type: "button",
3500
- "aria-label": "Remove link",
3501
- "data-re-link-bm-unlink": "",
3502
- onClick: handleUnlink,
3503
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
3504
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3505
- type: "submit",
3506
- "aria-label": "Apply link",
3507
- "data-re-link-bm-apply": "",
3508
- onMouseDown: (e) => e.stopPropagation(),
3509
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {})
3510
- })
3511
- ]
3512
- });
3513
- }
3514
-
3515
- //#endregion
3516
- //#region src/ui/link-bubble-menu/open-link.tsx
3517
- function LinkBubbleMenuOpenLink({ className, children, ...rest }) {
3518
- const { linkHref } = useLinkBubbleMenuContext();
3519
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
3520
- ...rest,
3521
- href: linkHref,
3522
- target: "_blank",
3523
- rel: "noopener noreferrer",
3524
- "aria-label": "Open link",
3525
- "data-re-link-bm-item": "",
3526
- "data-item": "open-link",
3527
- className,
3528
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ExternalLinkIcon, {})
3529
- });
3530
- }
3531
-
3532
- //#endregion
3533
- //#region src/ui/link-bubble-menu/root.tsx
3534
- function LinkBubbleMenuRoot({ onHide, placement = "top", offset: offset$1 = 8, className, children }) {
3535
- const { editor } = (0, _tiptap_react.useCurrentEditor)();
3536
- const [isEditing, setIsEditing] = react.useState(false);
3537
- const linkHref = (0, _tiptap_react.useEditorState)({
3538
- editor,
3539
- selector: ({ editor: e }) => e?.getAttributes("link").href ?? ""
3540
- });
3541
- if (!editor) return null;
3542
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tiptap_react_menus.BubbleMenu, {
3543
- editor,
3544
- "data-re-link-bm": "",
3545
- shouldShow: ({ editor: e }) => e.isActive("link") && e.view.state.selection.content().size === 0,
3546
- options: {
3547
- placement,
3548
- offset: offset$1,
3549
- onHide: () => {
3550
- setIsEditing(false);
3551
- onHide?.();
3552
- }
3553
- },
3554
- className,
3555
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkBubbleMenuContext.Provider, {
3556
- value: {
3557
- editor,
3558
- linkHref: linkHref ?? "",
3559
- isEditing,
3560
- setIsEditing
3561
- },
3562
- children
3563
- })
3564
- });
3565
- }
3566
-
3567
- //#endregion
3568
- //#region src/ui/link-bubble-menu/toolbar.tsx
3569
- function LinkBubbleMenuToolbar({ children, ...rest }) {
3570
- const { isEditing } = useLinkBubbleMenuContext();
3571
- if (isEditing) return null;
3572
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3573
- "data-re-link-bm-toolbar": "",
3574
- ...rest,
3575
- children
3576
- });
3577
- }
3578
-
3579
- //#endregion
3580
- //#region src/ui/link-bubble-menu/unlink.tsx
3581
- function LinkBubbleMenuUnlink({ className, children, onClick, onMouseDown, ...rest }) {
3582
- const { editor } = useLinkBubbleMenuContext();
3583
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3584
- type: "button",
3585
- "aria-label": "Remove link",
3586
- "data-re-link-bm-item": "",
3587
- "data-item": "unlink",
3588
- className,
3589
- onMouseDown: (e) => {
3590
- e.preventDefault();
3591
- onMouseDown?.(e);
3592
- },
3593
- onClick: (e) => {
3594
- onClick?.(e);
3595
- editor.chain().focus().unsetLink().run();
3596
- },
3597
- ...rest,
3598
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UnlinkIcon, {})
3599
- });
3600
- }
3601
-
3602
- //#endregion
3603
- //#region src/ui/link-bubble-menu/default.tsx
3604
- function LinkBubbleMenuDefault({ excludeItems = [], placement, offset: offset$1, onHide, className, validateUrl, onLinkApply, onLinkRemove }) {
3605
- const has = (item) => !excludeItems.includes(item);
3606
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LinkBubbleMenuRoot, {
3607
- placement,
3608
- offset: offset$1,
3609
- onHide,
3610
- className,
3611
- children: [(has("edit-link") || has("open-link") || has("unlink")) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LinkBubbleMenuToolbar, { children: [
3612
- has("edit-link") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkBubbleMenuEditLink, {}),
3613
- has("open-link") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkBubbleMenuOpenLink, {}),
3614
- has("unlink") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkBubbleMenuUnlink, {})
3615
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkBubbleMenuForm, {
3616
- validateUrl,
3617
- onLinkApply,
3618
- onLinkRemove
3619
- })]
3620
- });
3621
- }
3622
-
3623
- //#endregion
3624
- //#region src/ui/link-bubble-menu/index.ts
3625
- const LinkBubbleMenu = {
3626
- Root: LinkBubbleMenuRoot,
3627
- Toolbar: LinkBubbleMenuToolbar,
3628
- Form: LinkBubbleMenuForm,
3629
- EditLink: LinkBubbleMenuEditLink,
3630
- Unlink: LinkBubbleMenuUnlink,
3631
- OpenLink: LinkBubbleMenuOpenLink,
3632
- Default: LinkBubbleMenuDefault
3633
- };
3634
-
3635
- //#endregion
3636
- //#region src/ui/slash-command/utils.ts
3637
- function isInsideNode(editor, type) {
3638
- const { $from } = editor.state.selection;
3639
- for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === type) return true;
3640
- return false;
3641
- }
3642
- function isAtMaxColumnsDepth(editor) {
3643
- const { from } = editor.state.selection;
3644
- return getColumnsDepth(editor.state.doc, from) >= MAX_COLUMNS_DEPTH;
3645
- }
3646
- function updateScrollView(container, item) {
3647
- const containerRect = container.getBoundingClientRect();
3648
- const itemRect = item.getBoundingClientRect();
3649
- if (itemRect.top < containerRect.top) container.scrollTop -= containerRect.top - itemRect.top;
3650
- else if (itemRect.bottom > containerRect.bottom) container.scrollTop += itemRect.bottom - containerRect.bottom;
3651
- }
3652
-
3653
- //#endregion
3654
- //#region src/ui/slash-command/command-list.tsx
3655
- const CATEGORY_ORDER = [
3656
- "Text",
3657
- "Media",
3658
- "Layout",
3659
- "Utility"
3660
- ];
3661
- function groupByCategory(items) {
3662
- const seen = /* @__PURE__ */ new Map();
3663
- for (const item of items) {
3664
- const existing = seen.get(item.category);
3665
- if (existing) existing.push(item);
3666
- else seen.set(item.category, [item]);
3667
- }
3668
- const ordered = [];
3669
- for (const cat of CATEGORY_ORDER) {
3670
- const group = seen.get(cat);
3671
- if (group) {
3672
- ordered.push({
3673
- category: cat,
3674
- items: group
3675
- });
3676
- seen.delete(cat);
3677
- }
3678
- }
3679
- for (const [category, group] of seen) ordered.push({
3680
- category,
3681
- items: group
3682
- });
3683
- return ordered;
3684
- }
3685
- function CommandItem({ item, selected, onSelect }) {
3686
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3687
- "data-re-slash-command-item": "",
3688
- "data-selected": selected || void 0,
3689
- onClick: onSelect,
3690
- type: "button",
3691
- children: [item.icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.title })]
3692
- });
3693
- }
3694
- function CommandList({ items, query, selectedIndex, onSelect }) {
3695
- const containerRef = (0, react.useRef)(null);
3696
- (0, react.useLayoutEffect)(() => {
3697
- const container = containerRef.current;
3698
- if (!container) return;
3699
- const selected = container.querySelector("[data-selected]");
3700
- if (selected) updateScrollView(container, selected);
3701
- }, [selectedIndex]);
3702
- if (items.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3703
- "data-re-slash-command": "",
3704
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3705
- "data-re-slash-command-empty": "",
3706
- children: "No results"
3707
- })
3708
- });
3709
- if (query.trim().length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3710
- "data-re-slash-command": "",
3711
- ref: containerRef,
3712
- children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandItem, {
3713
- item,
3714
- onSelect: () => onSelect(index),
3715
- selected: index === selectedIndex
3716
- }, item.title))
3717
- });
3718
- const groups = groupByCategory(items);
3719
- let flatIndex = 0;
3720
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3721
- "data-re-slash-command": "",
3722
- ref: containerRef,
3723
- children: groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3724
- "data-re-slash-command-category": "",
3725
- children: group.category
3726
- }), group.items.map((item) => {
3727
- const currentIndex = flatIndex++;
3728
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandItem, {
3729
- item,
3730
- onSelect: () => onSelect(currentIndex),
3731
- selected: currentIndex === selectedIndex
3732
- }, item.title);
3733
- })] }, group.category))
3734
- });
3735
- }
3736
-
3737
- //#endregion
3738
- //#region src/ui/slash-command/commands.tsx
3739
- const TEXT = {
3740
- title: "Text",
3741
- description: "Plain text block",
3742
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Text, { size: 20 }),
3743
- category: "Text",
3744
- searchTerms: ["p", "paragraph"],
3745
- command: ({ editor, range }) => {
3746
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
3747
- }
3748
- };
3749
- const H1 = {
3750
- title: "Title",
3751
- description: "Large heading",
3752
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heading1, { size: 20 }),
3753
- category: "Text",
3754
- searchTerms: [
3755
- "title",
3756
- "big",
3757
- "large",
3758
- "h1"
3759
- ],
3760
- command: ({ editor, range }) => {
3761
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
3762
- }
3763
- };
3764
- const H2 = {
3765
- title: "Subtitle",
3766
- description: "Medium heading",
3767
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heading2, { size: 20 }),
3768
- category: "Text",
3769
- searchTerms: [
3770
- "subtitle",
3771
- "medium",
3772
- "h2"
3773
- ],
3774
- command: ({ editor, range }) => {
3775
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
3776
- }
3777
- };
3778
- const H3 = {
3779
- title: "Heading",
3780
- description: "Small heading",
3781
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heading3, { size: 20 }),
3782
- category: "Text",
3783
- searchTerms: [
3784
- "subtitle",
3785
- "small",
3786
- "h3"
3787
- ],
3788
- command: ({ editor, range }) => {
3789
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
3790
- }
3791
- };
3792
- const BULLET_LIST = {
3793
- title: "Bullet list",
3794
- description: "Unordered list",
3795
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.List, { size: 20 }),
3796
- category: "Text",
3797
- searchTerms: ["unordered", "point"],
3798
- command: ({ editor, range }) => {
3799
- editor.chain().focus().deleteRange(range).toggleBulletList().run();
3800
- }
3801
- };
3802
- const NUMBERED_LIST = {
3803
- title: "Numbered list",
3804
- description: "Ordered list",
3805
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ListOrdered, { size: 20 }),
3806
- category: "Text",
3807
- searchTerms: ["ordered"],
3808
- command: ({ editor, range }) => {
3809
- editor.chain().focus().deleteRange(range).toggleOrderedList().run();
3810
- }
3811
- };
3812
- const QUOTE = {
3813
- title: "Quote",
3814
- description: "Block quote",
3815
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.TextQuote, { size: 20 }),
3816
- category: "Text",
3817
- searchTerms: ["blockquote"],
3818
- command: ({ editor, range }) => {
3819
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run();
3820
- }
3821
- };
3822
- const CODE = {
3823
- title: "Code block",
3824
- description: "Code snippet",
3825
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.SquareCode, { size: 20 }),
3826
- category: "Text",
3827
- searchTerms: ["codeblock"],
3828
- command: ({ editor, range }) => {
3829
- editor.chain().focus().deleteRange(range).toggleCodeBlock().run();
3830
- }
3831
- };
3832
- const BUTTON = {
3833
- title: "Button",
3834
- description: "Clickable button",
3835
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.MousePointer, { size: 20 }),
3836
- category: "Layout",
3837
- searchTerms: ["button"],
3838
- command: ({ editor, range }) => {
3839
- editor.chain().focus().deleteRange(range).setButton().run();
3840
- }
3841
- };
3842
- const DIVIDER = {
3843
- title: "Divider",
3844
- description: "Horizontal separator",
3845
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.SplitSquareVertical, { size: 20 }),
3846
- category: "Layout",
3847
- searchTerms: [
3848
- "hr",
3849
- "divider",
3850
- "separator"
3851
- ],
3852
- command: ({ editor, range }) => {
3853
- editor.chain().focus().deleteRange(range).setHorizontalRule().run();
3854
- }
3855
- };
3856
- const SECTION = {
3857
- title: "Section",
3858
- description: "Content section",
3859
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Rows2, { size: 20 }),
3860
- category: "Layout",
3861
- searchTerms: [
3862
- "section",
3863
- "row",
3864
- "container"
3865
- ],
3866
- command: ({ editor, range }) => {
3867
- editor.chain().focus().deleteRange(range).insertSection().run();
3868
- }
3869
- };
3870
- const TWO_COLUMNS = {
3871
- title: "2 columns",
3872
- description: "Two column layout",
3873
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns2, { size: 20 }),
3874
- category: "Layout",
3875
- searchTerms: [
3876
- "columns",
3877
- "column",
3878
- "layout",
3879
- "grid",
3880
- "split",
3881
- "side-by-side",
3882
- "multi-column",
3883
- "row",
3884
- "two",
3885
- "2"
3886
- ],
3887
- command: ({ editor, range }) => {
3888
- editor.chain().focus().deleteRange(range).insertColumns(2).run();
3889
- }
3890
- };
3891
- const THREE_COLUMNS = {
3892
- title: "3 columns",
3893
- description: "Three column layout",
3894
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns3, { size: 20 }),
3895
- category: "Layout",
3896
- searchTerms: [
3897
- "columns",
3898
- "column",
3899
- "layout",
3900
- "grid",
3901
- "split",
3902
- "multi-column",
3903
- "row",
3904
- "three",
3905
- "3"
3906
- ],
3907
- command: ({ editor, range }) => {
3908
- editor.chain().focus().deleteRange(range).insertColumns(3).run();
3909
- }
3910
- };
3911
- const FOUR_COLUMNS = {
3912
- title: "4 columns",
3913
- description: "Four column layout",
3914
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns4, { size: 20 }),
3915
- category: "Layout",
3916
- searchTerms: [
3917
- "columns",
3918
- "column",
3919
- "layout",
3920
- "grid",
3921
- "split",
3922
- "multi-column",
3923
- "row",
3924
- "four",
3925
- "4"
3926
- ],
3927
- command: ({ editor, range }) => {
3928
- editor.chain().focus().deleteRange(range).insertColumns(4).run();
3929
- }
3930
- };
3931
- const defaultSlashCommands = [
3932
- TEXT,
3933
- H1,
3934
- H2,
3935
- H3,
3936
- BULLET_LIST,
3937
- NUMBERED_LIST,
3938
- QUOTE,
3939
- CODE,
3940
- BUTTON,
3941
- DIVIDER,
3942
- SECTION,
3943
- TWO_COLUMNS,
3944
- THREE_COLUMNS,
3945
- FOUR_COLUMNS
3946
- ];
3947
-
3948
- //#endregion
3949
- //#region src/ui/slash-command/search.ts
3950
- function scoreItem(item, query) {
3951
- if (!query) return 100;
3952
- const q = query.toLowerCase();
3953
- const title = item.title.toLowerCase();
3954
- const description = item.description.toLowerCase();
3955
- const terms = item.searchTerms?.map((t) => t.toLowerCase()) ?? [];
3956
- if (title === q) return 100;
3957
- if (title.startsWith(q)) return 90;
3958
- if (title.split(/\s+/).some((w) => w.startsWith(q))) return 80;
3959
- if (terms.some((t) => t === q)) return 70;
3960
- if (terms.some((t) => t.startsWith(q))) return 60;
3961
- if (title.includes(q)) return 40;
3962
- if (terms.some((t) => t.includes(q))) return 30;
3963
- if (description.includes(q)) return 20;
3964
- return 0;
3965
- }
3966
- function filterAndRankItems(items, query) {
3967
- const trimmed = query.trim();
3968
- if (!trimmed) return items;
3969
- const scored = items.map((item) => ({
3970
- item,
3971
- score: scoreItem(item, trimmed)
3972
- })).filter(({ score }) => score > 0);
3973
- scored.sort((a, b) => b.score - a.score);
3974
- return scored.map(({ item }) => item);
3975
- }
3976
-
3977
- //#endregion
3978
- //#region src/ui/slash-command/root.tsx
3979
- const pluginKey = new _tiptap_pm_state.PluginKey("slash-command");
3980
- const INITIAL_STATE = {
3981
- active: false,
3982
- query: "",
3983
- items: [],
3984
- clientRect: null
3985
- };
3986
- function defaultFilterItems(items, query, editor) {
3987
- return filterAndRankItems(isAtMaxColumnsDepth(editor) ? items.filter((item) => item.category !== "Layout" || !item.title.includes("column")) : items, query);
3988
- }
3989
- function SlashCommandRoot({ items: itemsProp, filterItems: filterItemsProp, char = "/", allow: allowProp, children }) {
3990
- const { editor } = (0, _tiptap_react.useCurrentEditor)();
3991
- const [state, setState] = (0, react.useState)(INITIAL_STATE);
3992
- const [selectedIndex, setSelectedIndex] = (0, react.useState)(0);
3993
- const itemsRef = (0, react.useRef)(itemsProp ?? defaultSlashCommands);
3994
- const filterRef = (0, react.useRef)(filterItemsProp ?? defaultFilterItems);
3995
- const allowRef = (0, react.useRef)(allowProp ?? (({ editor: e }) => !e.isActive("codeBlock")));
3996
- itemsRef.current = itemsProp ?? defaultSlashCommands;
3997
- filterRef.current = filterItemsProp ?? defaultFilterItems;
3998
- allowRef.current = allowProp ?? (({ editor: e }) => !e.isActive("codeBlock"));
3999
- const commandRef = (0, react.useRef)(null);
4000
- const suggestionItemsRef = (0, react.useRef)([]);
4001
- const selectedIndexRef = (0, react.useRef)(0);
4002
- suggestionItemsRef.current = state.items;
4003
- selectedIndexRef.current = selectedIndex;
4004
- const { refs, floatingStyles } = (0, _floating_ui_react_dom.useFloating)({
4005
- open: state.active,
4006
- placement: "bottom-start",
4007
- middleware: [
4008
- (0, _floating_ui_react_dom.offset)(8),
4009
- (0, _floating_ui_react_dom.flip)(),
4010
- (0, _floating_ui_react_dom.shift)({ padding: 8 })
4011
- ],
4012
- whileElementsMounted: _floating_ui_react_dom.autoUpdate
4013
- });
4014
- (0, react.useEffect)(() => {
4015
- if (!state.clientRect) return;
4016
- refs.setReference({ getBoundingClientRect: state.clientRect });
4017
- }, [state.clientRect, refs]);
4018
- (0, react.useEffect)(() => {
4019
- setSelectedIndex(0);
4020
- }, [state.items]);
4021
- const onSelect = (0, react.useCallback)((index) => {
4022
- const item = suggestionItemsRef.current[index];
4023
- if (item && commandRef.current) commandRef.current(item);
4024
- }, []);
4025
- (0, react.useEffect)(() => {
4026
- if (!editor) return;
4027
- const plugin = (0, _tiptap_suggestion.default)({
4028
- pluginKey,
4029
- editor,
4030
- char,
4031
- allow: ({ editor: e }) => allowRef.current({ editor: e }),
4032
- command: ({ editor: e, range, props }) => {
4033
- props.command({
4034
- editor: e,
4035
- range
4036
- });
4037
- },
4038
- items: ({ query, editor: e }) => filterRef.current(itemsRef.current, query, e),
4039
- render: () => ({
4040
- onStart: (props) => {
4041
- commandRef.current = props.command;
4042
- setState({
4043
- active: true,
4044
- query: props.query,
4045
- items: props.items,
4046
- clientRect: props.clientRect ?? null
4047
- });
4048
- },
4049
- onUpdate: (props) => {
4050
- commandRef.current = props.command;
4051
- setState({
4052
- active: true,
4053
- query: props.query,
4054
- items: props.items,
4055
- clientRect: props.clientRect ?? null
4056
- });
4057
- },
4058
- onKeyDown: ({ event }) => {
4059
- if (event.key === "Escape") {
4060
- setState(INITIAL_STATE);
4061
- return true;
4062
- }
4063
- const items = suggestionItemsRef.current;
4064
- if (items.length === 0) return false;
4065
- if (event.key === "ArrowUp") {
4066
- setSelectedIndex((i) => (i + items.length - 1) % items.length);
4067
- return true;
4068
- }
4069
- if (event.key === "ArrowDown") {
4070
- setSelectedIndex((i) => (i + 1) % items.length);
4071
- return true;
4072
- }
4073
- if (event.key === "Enter") {
4074
- const item = items[selectedIndexRef.current];
4075
- if (item && commandRef.current) commandRef.current(item);
4076
- return true;
4077
- }
4078
- return false;
4079
- },
4080
- onExit: () => {
4081
- setState(INITIAL_STATE);
4082
- requestAnimationFrame(() => {
4083
- commandRef.current = null;
4084
- });
4085
- }
4086
- })
4087
- });
4088
- editor.registerPlugin(plugin, (newPlugin, plugins) => [newPlugin, ...plugins]);
4089
- return () => {
4090
- editor.unregisterPlugin(pluginKey);
4091
- };
4092
- }, [editor, char]);
4093
- if (!editor || !state.active) return null;
4094
- const renderProps = {
4095
- items: state.items,
4096
- query: state.query,
4097
- selectedIndex,
4098
- onSelect
4099
- };
4100
- let content;
4101
- if (children) content = children(renderProps);
4102
- else content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandList, { ...renderProps });
4103
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4104
- ref: refs.setFloating,
4105
- style: floatingStyles,
4106
- children: content
4107
- }), document.body);
4108
- }
4109
-
4110
- //#endregion
4111
- //#region src/ui/slash-command/index.ts
4112
- const SlashCommand = {
4113
- Root: SlashCommandRoot,
4114
- CommandList
4115
- };
4116
-
4117
- //#endregion
4118
- exports.AlignmentAttribute = AlignmentAttribute;
4119
- exports.BULLET_LIST = BULLET_LIST;
4120
- exports.BUTTON = BUTTON;
4121
- exports.Blockquote = Blockquote;
4122
- exports.Body = Body;
4123
- exports.Bold = Bold;
4124
- exports.BubbleMenu = BubbleMenu;
4125
- exports.BubbleMenuAlignCenter = BubbleMenuAlignCenter;
4126
- exports.BubbleMenuAlignLeft = BubbleMenuAlignLeft;
4127
- exports.BubbleMenuAlignRight = BubbleMenuAlignRight;
4128
- exports.BubbleMenuBold = BubbleMenuBold;
4129
- exports.BubbleMenuCode = BubbleMenuCode;
4130
- exports.BubbleMenuDefault = BubbleMenuDefault;
4131
- exports.BubbleMenuItalic = BubbleMenuItalic;
4132
- exports.BubbleMenuItem = BubbleMenuItem;
4133
- exports.BubbleMenuItemGroup = BubbleMenuItemGroup;
4134
- exports.BubbleMenuLinkSelector = BubbleMenuLinkSelector;
4135
- exports.BubbleMenuNodeSelector = BubbleMenuNodeSelector;
4136
- exports.BubbleMenuRoot = BubbleMenuRoot;
4137
- exports.BubbleMenuSeparator = BubbleMenuSeparator;
4138
- exports.BubbleMenuStrike = BubbleMenuStrike;
4139
- exports.BubbleMenuUnderline = BubbleMenuUnderline;
4140
- exports.BubbleMenuUppercase = BubbleMenuUppercase;
4141
- exports.BulletList = BulletList;
4142
- exports.Button = Button;
4143
- exports.ButtonBubbleMenu = ButtonBubbleMenu;
4144
- exports.ButtonBubbleMenuDefault = ButtonBubbleMenuDefault;
4145
- exports.ButtonBubbleMenuEditLink = ButtonBubbleMenuEditLink;
4146
- exports.ButtonBubbleMenuRoot = ButtonBubbleMenuRoot;
4147
- exports.ButtonBubbleMenuToolbar = ButtonBubbleMenuToolbar;
4148
- exports.CODE = CODE;
4149
- exports.COLUMN_PARENT_TYPES = COLUMN_PARENT_TYPES;
4150
- exports.ClassAttribute = ClassAttribute;
4151
- exports.Code = Code;
4152
- exports.CodeBlockPrism = CodeBlockPrism;
4153
- exports.ColumnsColumn = ColumnsColumn;
4154
- exports.CommandList = CommandList;
4155
- exports.DIVIDER = DIVIDER;
4156
- exports.Div = Div;
4157
- exports.Divider = Divider;
4158
- exports.EmailNode = EmailNode;
4159
- exports.FOUR_COLUMNS = FOUR_COLUMNS;
4160
- exports.FourColumns = FourColumns;
4161
- exports.GlobalContent = GlobalContent;
4162
- exports.H1 = H1;
4163
- exports.H2 = H2;
4164
- exports.H3 = H3;
4165
- exports.HardBreak = HardBreak;
4166
- exports.Heading = Heading;
4167
- exports.ImageBubbleMenu = ImageBubbleMenu;
4168
- exports.ImageBubbleMenuDefault = ImageBubbleMenuDefault;
4169
- exports.ImageBubbleMenuEditLink = ImageBubbleMenuEditLink;
4170
- exports.ImageBubbleMenuRoot = ImageBubbleMenuRoot;
4171
- exports.ImageBubbleMenuToolbar = ImageBubbleMenuToolbar;
4172
- exports.Italic = Italic;
4173
- exports.Link = Link;
4174
- exports.LinkBubbleMenu = LinkBubbleMenu;
4175
- exports.LinkBubbleMenuDefault = LinkBubbleMenuDefault;
4176
- exports.LinkBubbleMenuEditLink = LinkBubbleMenuEditLink;
4177
- exports.LinkBubbleMenuForm = LinkBubbleMenuForm;
4178
- exports.LinkBubbleMenuOpenLink = LinkBubbleMenuOpenLink;
4179
- exports.LinkBubbleMenuRoot = LinkBubbleMenuRoot;
4180
- exports.LinkBubbleMenuToolbar = LinkBubbleMenuToolbar;
4181
- exports.LinkBubbleMenuUnlink = LinkBubbleMenuUnlink;
4182
- exports.ListItem = ListItem;
4183
- exports.MAX_COLUMNS_DEPTH = MAX_COLUMNS_DEPTH;
4184
- exports.MaxNesting = MaxNesting;
4185
- exports.NUMBERED_LIST = NUMBERED_LIST;
4186
- exports.NodeSelectorContent = NodeSelectorContent;
4187
- exports.NodeSelectorRoot = NodeSelectorRoot;
4188
- exports.NodeSelectorTrigger = NodeSelectorTrigger;
4189
- exports.OrderedList = OrderedList;
4190
- exports.Paragraph = Paragraph;
4191
- exports.Placeholder = Placeholder;
4192
- exports.PreservedStyle = PreservedStyle;
4193
- exports.PreviewText = PreviewText;
4194
- exports.QUOTE = QUOTE;
4195
- exports.SECTION = SECTION;
4196
- exports.Section = Section;
4197
- exports.SlashCommand = SlashCommand;
4198
- exports.StarterKit = StarterKit;
4199
- exports.Strike = Strike;
4200
- exports.StyleAttribute = StyleAttribute;
4201
- exports.Sup = Sup;
4202
- exports.TEXT = TEXT;
4203
- exports.THREE_COLUMNS = THREE_COLUMNS;
4204
- exports.TWO_COLUMNS = TWO_COLUMNS;
4205
- exports.Table = Table;
4206
- exports.TableCell = TableCell;
4207
- exports.TableHeader = TableHeader;
4208
- exports.TableRow = TableRow;
4209
- exports.ThreeColumns = ThreeColumns;
4210
- exports.TwoColumns = TwoColumns;
4211
- exports.Underline = Underline;
4212
- exports.Uppercase = Uppercase;
4213
- exports.composeReactEmail = composeReactEmail;
4214
- exports.defaultSlashCommands = defaultSlashCommands;
4215
- exports.editorEventBus = editorEventBus;
4216
- exports.filterAndRankItems = filterAndRankItems;
4217
- exports.getColumnsDepth = getColumnsDepth;
4218
- exports.getGlobalContent = getGlobalContent;
4219
- exports.isAtMaxColumnsDepth = isAtMaxColumnsDepth;
4220
- exports.isDocumentVisuallyEmpty = isDocumentVisuallyEmpty;
4221
- exports.isInsideNode = isInsideNode;
4222
- exports.processStylesForUnlink = processStylesForUnlink;
4223
- exports.scoreItem = scoreItem;
4224
- exports.setTextAlignment = setTextAlignment;
4225
- exports.useButtonBubbleMenuContext = useButtonBubbleMenuContext;
4226
- exports.useEditor = useEditor;
4227
- exports.useImageBubbleMenuContext = useImageBubbleMenuContext;
4228
- exports.useLinkBubbleMenuContext = useLinkBubbleMenuContext;