@react-email/editor 0.0.0-experimental.22 → 0.0.0-experimental.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/columns-CUxUEHje.mjs +497 -0
- package/dist/columns-CUxUEHje.mjs.map +1 -0
- package/dist/columns-ZSaLdkg9.cjs +630 -0
- package/dist/core/index.cjs +8 -0
- package/dist/core/index.d.cts +2 -0
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.mjs +4 -0
- package/dist/core-BjmRceVw.mjs +1999 -0
- package/dist/core-BjmRceVw.mjs.map +1 -0
- package/dist/core-iuG1UrYN.cjs +2250 -0
- package/dist/extensions/index.cjs +46 -0
- package/dist/extensions/index.d.cts +389 -0
- package/dist/extensions/index.d.cts.map +1 -0
- package/dist/extensions/index.d.mts +389 -0
- package/dist/extensions/index.d.mts.map +1 -0
- package/dist/extensions/index.mjs +4 -0
- package/dist/index-CfslA7KT.d.cts +130 -0
- package/dist/index-CfslA7KT.d.cts.map +1 -0
- package/dist/index-hbHRR7oB.d.mts +130 -0
- package/dist/index-hbHRR7oB.d.mts.map +1 -0
- package/dist/set-text-alignment-Bx3bPteH.cjs +24 -0
- package/dist/set-text-alignment-DZvgnbvz.mjs +19 -0
- package/dist/set-text-alignment-DZvgnbvz.mjs.map +1 -0
- package/dist/ui/index.cjs +1646 -0
- package/dist/ui/index.d.cts +668 -0
- package/dist/ui/index.d.cts.map +1 -0
- package/dist/ui/index.d.mts +668 -0
- package/dist/ui/index.d.mts.map +1 -0
- package/dist/ui/index.mjs +1584 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/utils/index.cjs +3 -0
- package/dist/utils/index.d.cts +7 -0
- package/dist/utils/index.d.cts.map +1 -0
- package/dist/utils/index.d.mts +7 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +3 -0
- package/package.json +38 -11
- package/dist/index.cjs +0 -4228
- package/dist/index.d.cts +0 -1175
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts +0 -1175
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -4072
- package/dist/index.mjs.map +0 -1
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;
|