@mjml-agent-editor/editor 0.1.17 → 0.3.0
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/README.md +40 -5
- package/dist/canvas.cjs +3409 -624
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.d.cts +30 -2
- package/dist/canvas.d.ts +30 -2
- package/dist/canvas.js +2946 -386
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-VJRKDFM4.js → chunk-K7KQC7LX.js} +201 -2
- package/dist/chunk-K7KQC7LX.js.map +1 -0
- package/dist/index.cjs +200 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -4
- package/dist/index.d.ts +69 -4
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/src/components/editor/editor-theme.css +55 -80
- package/dist/chunk-VJRKDFM4.js.map +0 -1
package/dist/canvas.cjs
CHANGED
|
@@ -31,16 +31,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/canvas.ts
|
|
32
32
|
var canvas_exports = {};
|
|
33
33
|
__export(canvas_exports, {
|
|
34
|
-
EmailEditor: () => EmailEditor
|
|
34
|
+
EmailEditor: () => EmailEditor,
|
|
35
|
+
blockProblems: () => blockProblems
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(canvas_exports);
|
|
37
38
|
|
|
38
39
|
// src/components/editor/EmailEditor.tsx
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
43
|
-
var
|
|
40
|
+
var import_grapesjs2 = __toESM(require("grapesjs"), 1);
|
|
41
|
+
var import_react9 = __toESM(require("@grapesjs/react"), 1);
|
|
42
|
+
var import_grapesjs_mjml2 = __toESM(require("grapesjs-mjml"), 1);
|
|
43
|
+
var import_react10 = require("react");
|
|
44
|
+
var import_core4 = require("@mjml-agent-editor/core");
|
|
44
45
|
|
|
45
46
|
// src/stores.tsx
|
|
46
47
|
var import_react = require("react");
|
|
@@ -63,9 +64,204 @@ var DEFAULT_LABELS = {
|
|
|
63
64
|
exportHtml: "Export HTML",
|
|
64
65
|
exportMjml: "Export MJML",
|
|
65
66
|
copied: (kind) => `${kind} copied.`,
|
|
67
|
+
tabBlocks: "Blocks",
|
|
68
|
+
tabSettings: "Settings",
|
|
69
|
+
tabLayers: "Layers",
|
|
66
70
|
searchBlocks: "Search blocks\u2026",
|
|
71
|
+
noBlocksMatch: "No blocks match.",
|
|
72
|
+
blockAdded: (block) => `Added: ${block}`,
|
|
73
|
+
blockCategoryLayout: "Layout",
|
|
74
|
+
blockCategoryContent: "Content",
|
|
75
|
+
blockCategorySections: "Sections",
|
|
76
|
+
blockLabels: {
|
|
77
|
+
"mj-1-column": "1 column",
|
|
78
|
+
"mj-2-columns": "2 columns",
|
|
79
|
+
"mj-3-columns": "3 columns",
|
|
80
|
+
"mj-text": "Text",
|
|
81
|
+
"mj-image": "Image",
|
|
82
|
+
"mj-button": "Button",
|
|
83
|
+
"mj-divider": "Divider",
|
|
84
|
+
"mj-spacer": "Spacer",
|
|
85
|
+
"mj-social-group": "Social links",
|
|
86
|
+
"mj-navbar": "Navigation"
|
|
87
|
+
},
|
|
88
|
+
elementNames: {
|
|
89
|
+
"mj-wrapper": "Section group",
|
|
90
|
+
"mj-section": "Section",
|
|
91
|
+
"mj-column": "Column",
|
|
92
|
+
"mj-group": "Column group",
|
|
93
|
+
"mj-text": "Text",
|
|
94
|
+
"mj-button": "Button",
|
|
95
|
+
"mj-image": "Image",
|
|
96
|
+
"mj-divider": "Divider",
|
|
97
|
+
"mj-spacer": "Spacer",
|
|
98
|
+
"mj-hero": "Hero",
|
|
99
|
+
"mj-navbar": "Navigation",
|
|
100
|
+
"mj-navbar-link": "Navigation link",
|
|
101
|
+
"mj-social": "Social links",
|
|
102
|
+
"mj-social-element": "Social link",
|
|
103
|
+
"mj-raw": "Raw HTML",
|
|
104
|
+
"mj-table": "Table"
|
|
105
|
+
},
|
|
67
106
|
expandAll: "Expand all",
|
|
68
107
|
selectAnElement: "Select an element\u2026",
|
|
108
|
+
settingsEmpty: "Select something in the email to edit it.",
|
|
109
|
+
emailSettings: "Email settings",
|
|
110
|
+
loadingEditor: "Loading editor\u2026",
|
|
111
|
+
commentOnElement: "Comment on element / section",
|
|
112
|
+
groupContent: "Content",
|
|
113
|
+
groupLayout: "Layout",
|
|
114
|
+
groupSpacing: "Spacing",
|
|
115
|
+
groupStyle: "Style",
|
|
116
|
+
groupAdvanced: "Advanced",
|
|
117
|
+
otherAttributes: "Other attributes",
|
|
118
|
+
removeAttribute: "Remove",
|
|
119
|
+
resetToDefault: "Reset to default",
|
|
120
|
+
selectParent: "Select parent",
|
|
121
|
+
duplicate: "Duplicate",
|
|
122
|
+
deleteElement: "Delete",
|
|
123
|
+
multipleSelected: (count) => `${count} elements selected \u2014 select one to edit its settings.`,
|
|
124
|
+
editEachSide: "Edit each side",
|
|
125
|
+
increase: "Increase",
|
|
126
|
+
decrease: "Decrease",
|
|
127
|
+
changeUnit: (unit) => `Unit: ${unit}. Click to change`,
|
|
128
|
+
noUnit: "\xD7",
|
|
129
|
+
sidesVertical: "Top and bottom",
|
|
130
|
+
sidesHorizontal: "Left and right",
|
|
131
|
+
sideTop: "Top",
|
|
132
|
+
sideRight: "Right",
|
|
133
|
+
sideBottom: "Bottom",
|
|
134
|
+
sideLeft: "Left",
|
|
135
|
+
linkTypeProfile: "Your profile",
|
|
136
|
+
linkTypeShare: "Share this page",
|
|
137
|
+
allScreenSizes: "Changes apply to every screen size.",
|
|
138
|
+
columnWidths: "Column widths",
|
|
139
|
+
columnRatio: (ratio) => `Columns ${ratio}`,
|
|
140
|
+
socialNetworks: "Networks",
|
|
141
|
+
addNetwork: "Add network",
|
|
142
|
+
navbarLinks: "Links",
|
|
143
|
+
addLink: "Add link",
|
|
144
|
+
newLinkText: "Link",
|
|
145
|
+
inspectorLabels: {
|
|
146
|
+
// Fields
|
|
147
|
+
contentWidth: "Width",
|
|
148
|
+
pageBackground: "Page background",
|
|
149
|
+
"pageBackground.help": "Fills the area around and behind the email.",
|
|
150
|
+
fullWidth: "Full-width background",
|
|
151
|
+
"fullWidth.help": "The background reaches the window edges; the content keeps the email width.",
|
|
152
|
+
padding: "Padding",
|
|
153
|
+
backgroundColor: "Background",
|
|
154
|
+
backgroundImage: "Background image",
|
|
155
|
+
backgroundSize: "Image size",
|
|
156
|
+
backgroundRepeat: "Repeat image",
|
|
157
|
+
backgroundPosition: "Position",
|
|
158
|
+
cornerRadius: "Corners",
|
|
159
|
+
gap: "Space between sections",
|
|
160
|
+
border: "Border",
|
|
161
|
+
alignColumns: "Align columns",
|
|
162
|
+
"alignColumns.help": "Where the columns sit when they don't fill the row.",
|
|
163
|
+
reverseColumns: "Reverse column order",
|
|
164
|
+
"reverseColumns.help": "Swaps the columns on desktop; on phones they still stack in their original order.",
|
|
165
|
+
verticalAlign: "Vertical",
|
|
166
|
+
width: "Width",
|
|
167
|
+
innerBackground: "Inner background",
|
|
168
|
+
"innerBackground.help": "Fills only the area inside the padding, so it needs padding to show.",
|
|
169
|
+
innerCornerRadius: "Inner corners",
|
|
170
|
+
innerBorder: "Inner border",
|
|
171
|
+
align: "Alignment",
|
|
172
|
+
font: "Font",
|
|
173
|
+
fontSize: "Text size",
|
|
174
|
+
fontWeight: "Weight",
|
|
175
|
+
textColor: "Text color",
|
|
176
|
+
lineHeight: "Line height",
|
|
177
|
+
letterSpacing: "Letter spacing",
|
|
178
|
+
textTransform: "Capitalization",
|
|
179
|
+
blockBackground: "Block background",
|
|
180
|
+
"blockBackground.help": "Fills the whole block, including its padding.",
|
|
181
|
+
link: "Link",
|
|
182
|
+
buttonColor: "Button color",
|
|
183
|
+
buttonPadding: "Button padding",
|
|
184
|
+
"buttonPadding.help": "Space between the text and the button's edge; this sets the button's size.",
|
|
185
|
+
spaceAround: "Space around",
|
|
186
|
+
"spaceAround.help": "Empty space outside the button, between it and what is next to it.",
|
|
187
|
+
opensIn: "Opens in",
|
|
188
|
+
tooltip: "Tooltip",
|
|
189
|
+
rel: "Link relationship",
|
|
190
|
+
image: "Image",
|
|
191
|
+
altText: "Alt text",
|
|
192
|
+
"altText.help": "Read by screen readers and shown when images are blocked.",
|
|
193
|
+
fluidOnMobile: "Full width on mobile",
|
|
194
|
+
"fluidOnMobile.help": "On phones the image stretches across the screen, whatever its width here.",
|
|
195
|
+
lineColor: "Line color",
|
|
196
|
+
thickness: "Thickness",
|
|
197
|
+
lineStyle: "Line style",
|
|
198
|
+
height: "Height",
|
|
199
|
+
socialMode: "Direction",
|
|
200
|
+
iconSize: "Icon size",
|
|
201
|
+
iconSpacing: "Space around each icon",
|
|
202
|
+
network: "Network",
|
|
203
|
+
// Brand names are the same in every language and are not keys; these two are words.
|
|
204
|
+
networkWebsite: "Website",
|
|
205
|
+
networkCustom: "Custom",
|
|
206
|
+
customIcon: "Custom icon",
|
|
207
|
+
iconColor: "Icon color",
|
|
208
|
+
iconPosition: "Icon side",
|
|
209
|
+
heroMode: "Sizing",
|
|
210
|
+
"heroMode.help": "Fixed keeps the height you set; Fit image follows the background image's proportions.",
|
|
211
|
+
backgroundWidth: "Image width",
|
|
212
|
+
"backgroundWidth.help": "The background image's real size in pixels, so the hero keeps its proportions.",
|
|
213
|
+
backgroundHeight: "Image height",
|
|
214
|
+
fallbackColor: "Fallback color",
|
|
215
|
+
"fallbackColor.help": "Shown while the image loads, or when images are blocked.",
|
|
216
|
+
contentPadding: "Content padding",
|
|
217
|
+
contentBackground: "Content background",
|
|
218
|
+
hamburger: "Collapse into a menu on mobile",
|
|
219
|
+
"hamburger.help": "On phones the links fold into a menu button.",
|
|
220
|
+
menuIconColor: "Menu icon color",
|
|
221
|
+
menuIconSize: "Menu icon size",
|
|
222
|
+
menuIconAlign: "Menu icon align",
|
|
223
|
+
baseUrl: "Link prefix",
|
|
224
|
+
"baseUrl.help": "Added in front of every link in the bar, full addresses included, so links then give only the path.",
|
|
225
|
+
// Options
|
|
226
|
+
alignLeft: "Left",
|
|
227
|
+
alignCenter: "Center",
|
|
228
|
+
alignRight: "Right",
|
|
229
|
+
alignJustify: "Justify",
|
|
230
|
+
alignTop: "Top",
|
|
231
|
+
alignMiddle: "Middle",
|
|
232
|
+
alignBottom: "Bottom",
|
|
233
|
+
weightRegular: "Regular",
|
|
234
|
+
weightMedium: "Medium",
|
|
235
|
+
weightSemibold: "Semibold",
|
|
236
|
+
weightBold: "Bold",
|
|
237
|
+
caseNone: "As typed",
|
|
238
|
+
caseUpper: "UPPERCASE",
|
|
239
|
+
caseLower: "lowercase",
|
|
240
|
+
caseCapitalize: "Capitalize Each Word",
|
|
241
|
+
targetNewTab: "New tab",
|
|
242
|
+
targetSameTab: "Same tab",
|
|
243
|
+
sizeCover: "Fill",
|
|
244
|
+
sizeContain: "Fit",
|
|
245
|
+
sizeAuto: "Original",
|
|
246
|
+
repeatTile: "Tile",
|
|
247
|
+
repeatOnce: "Once",
|
|
248
|
+
positionTopLeft: "Top left",
|
|
249
|
+
positionTopCenter: "Top",
|
|
250
|
+
positionTopRight: "Top right",
|
|
251
|
+
positionCenterLeft: "Left",
|
|
252
|
+
positionCenter: "Center",
|
|
253
|
+
positionCenterRight: "Right",
|
|
254
|
+
positionBottomLeft: "Bottom left",
|
|
255
|
+
positionBottomCenter: "Bottom",
|
|
256
|
+
positionBottomRight: "Bottom right",
|
|
257
|
+
lineSolid: "Solid",
|
|
258
|
+
lineDashed: "Dashed",
|
|
259
|
+
lineDotted: "Dotted",
|
|
260
|
+
modeHorizontal: "In a row",
|
|
261
|
+
modeVertical: "Stacked",
|
|
262
|
+
heroFixed: "Fixed",
|
|
263
|
+
heroFluid: "Fit image"
|
|
264
|
+
},
|
|
69
265
|
attributes: "Attributes",
|
|
70
266
|
style: "Style",
|
|
71
267
|
rteFont: "Font",
|
|
@@ -132,7 +328,10 @@ function mergeLabels(overrides) {
|
|
|
132
328
|
return {
|
|
133
329
|
...DEFAULT_LABELS,
|
|
134
330
|
...overrides,
|
|
135
|
-
toolLabels: { ...DEFAULT_LABELS.toolLabels, ...overrides.toolLabels }
|
|
331
|
+
toolLabels: { ...DEFAULT_LABELS.toolLabels, ...overrides.toolLabels },
|
|
332
|
+
blockLabels: { ...DEFAULT_LABELS.blockLabels, ...overrides.blockLabels },
|
|
333
|
+
elementNames: { ...DEFAULT_LABELS.elementNames, ...overrides.elementNames },
|
|
334
|
+
inspectorLabels: { ...DEFAULT_LABELS.inspectorLabels, ...overrides.inspectorLabels }
|
|
136
335
|
};
|
|
137
336
|
}
|
|
138
337
|
|
|
@@ -255,11 +454,58 @@ function Input({ className, type, ...props }) {
|
|
|
255
454
|
);
|
|
256
455
|
}
|
|
257
456
|
|
|
457
|
+
// src/components/ui/tooltip.tsx
|
|
458
|
+
var React3 = require("react");
|
|
459
|
+
var import_radix_ui2 = require("radix-ui");
|
|
460
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
461
|
+
function TooltipProvider({
|
|
462
|
+
delayDuration = 0,
|
|
463
|
+
...props
|
|
464
|
+
}) {
|
|
465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
466
|
+
import_radix_ui2.Tooltip.Provider,
|
|
467
|
+
{
|
|
468
|
+
"data-slot": "tooltip-provider",
|
|
469
|
+
delayDuration,
|
|
470
|
+
...props
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
function Tooltip({ ...props }) {
|
|
475
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Root, { "data-slot": "tooltip", ...props });
|
|
476
|
+
}
|
|
477
|
+
function TooltipTrigger({ ...props }) {
|
|
478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
479
|
+
}
|
|
480
|
+
function TooltipContent({
|
|
481
|
+
className,
|
|
482
|
+
sideOffset = 0,
|
|
483
|
+
children,
|
|
484
|
+
...props
|
|
485
|
+
}) {
|
|
486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
487
|
+
import_radix_ui2.Tooltip.Content,
|
|
488
|
+
{
|
|
489
|
+
"data-slot": "tooltip-content",
|
|
490
|
+
sideOffset,
|
|
491
|
+
className: cn(
|
|
492
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
493
|
+
className
|
|
494
|
+
),
|
|
495
|
+
...props,
|
|
496
|
+
children: [
|
|
497
|
+
children,
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
) });
|
|
502
|
+
}
|
|
503
|
+
|
|
258
504
|
// src/components/ui/spinner.tsx
|
|
259
505
|
var import_lucide_react = require("lucide-react");
|
|
260
|
-
var
|
|
506
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
261
507
|
function Spinner({ className, ...props }) {
|
|
262
|
-
return /* @__PURE__ */ (0,
|
|
508
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
263
509
|
import_lucide_react.Loader2Icon,
|
|
264
510
|
{
|
|
265
511
|
"data-slot": "spinner",
|
|
@@ -272,17 +518,17 @@ function Spinner({ className, ...props }) {
|
|
|
272
518
|
}
|
|
273
519
|
|
|
274
520
|
// src/components/ui/popover.tsx
|
|
275
|
-
var
|
|
276
|
-
var
|
|
277
|
-
var
|
|
521
|
+
var React4 = require("react");
|
|
522
|
+
var import_radix_ui3 = require("radix-ui");
|
|
523
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
278
524
|
function Popover({ ...props }) {
|
|
279
|
-
return /* @__PURE__ */ (0,
|
|
525
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Root, { "data-slot": "popover", ...props });
|
|
280
526
|
}
|
|
281
527
|
function PopoverTrigger({ ...props }) {
|
|
282
|
-
return /* @__PURE__ */ (0,
|
|
528
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
283
529
|
}
|
|
284
530
|
function PopoverAnchor({ ...props }) {
|
|
285
|
-
return /* @__PURE__ */ (0,
|
|
531
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
286
532
|
}
|
|
287
533
|
function PopoverContent({
|
|
288
534
|
className,
|
|
@@ -290,8 +536,8 @@ function PopoverContent({
|
|
|
290
536
|
sideOffset = 4,
|
|
291
537
|
...props
|
|
292
538
|
}) {
|
|
293
|
-
return /* @__PURE__ */ (0,
|
|
294
|
-
|
|
539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
540
|
+
import_radix_ui3.Popover.Content,
|
|
295
541
|
{
|
|
296
542
|
"data-slot": "popover-content",
|
|
297
543
|
align,
|
|
@@ -306,22 +552,22 @@ function PopoverContent({
|
|
|
306
552
|
}
|
|
307
553
|
|
|
308
554
|
// src/components/ui/dialog.tsx
|
|
309
|
-
var
|
|
310
|
-
var
|
|
555
|
+
var React5 = require("react");
|
|
556
|
+
var import_radix_ui4 = require("radix-ui");
|
|
311
557
|
var import_lucide_react2 = require("lucide-react");
|
|
312
|
-
var
|
|
558
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
313
559
|
function Dialog({ ...props }) {
|
|
314
|
-
return /* @__PURE__ */ (0,
|
|
560
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui4.Dialog.Root, { "data-slot": "dialog", ...props });
|
|
315
561
|
}
|
|
316
562
|
function DialogPortal({ ...props }) {
|
|
317
|
-
return /* @__PURE__ */ (0,
|
|
563
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui4.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
|
|
318
564
|
}
|
|
319
565
|
function DialogOverlay({
|
|
320
566
|
className,
|
|
321
567
|
...props
|
|
322
568
|
}) {
|
|
323
|
-
return /* @__PURE__ */ (0,
|
|
324
|
-
|
|
569
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
570
|
+
import_radix_ui4.Dialog.Overlay,
|
|
325
571
|
{
|
|
326
572
|
"data-slot": "dialog-overlay",
|
|
327
573
|
className: cn(
|
|
@@ -338,10 +584,10 @@ function DialogContent({
|
|
|
338
584
|
showCloseButton = true,
|
|
339
585
|
...props
|
|
340
586
|
}) {
|
|
341
|
-
return /* @__PURE__ */ (0,
|
|
342
|
-
/* @__PURE__ */ (0,
|
|
343
|
-
/* @__PURE__ */ (0,
|
|
344
|
-
|
|
587
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(DialogPortal, { children: [
|
|
588
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DialogOverlay, {}),
|
|
589
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
590
|
+
import_radix_ui4.Dialog.Content,
|
|
345
591
|
{
|
|
346
592
|
"data-slot": "dialog-content",
|
|
347
593
|
className: cn(
|
|
@@ -351,9 +597,9 @@ function DialogContent({
|
|
|
351
597
|
...props,
|
|
352
598
|
children: [
|
|
353
599
|
children,
|
|
354
|
-
showCloseButton && /* @__PURE__ */ (0,
|
|
355
|
-
/* @__PURE__ */ (0,
|
|
356
|
-
/* @__PURE__ */ (0,
|
|
600
|
+
showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui4.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Button, { variant: "ghost", className: "absolute top-2 right-2", size: "icon-sm", children: [
|
|
601
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.XIcon, {}),
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "sr-only", children: "Close" })
|
|
357
603
|
] }) })
|
|
358
604
|
]
|
|
359
605
|
}
|
|
@@ -361,11 +607,11 @@ function DialogContent({
|
|
|
361
607
|
] });
|
|
362
608
|
}
|
|
363
609
|
function DialogHeader({ className, ...props }) {
|
|
364
|
-
return /* @__PURE__ */ (0,
|
|
610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2", className), ...props });
|
|
365
611
|
}
|
|
366
612
|
function DialogTitle({ className, ...props }) {
|
|
367
|
-
return /* @__PURE__ */ (0,
|
|
368
|
-
|
|
613
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
614
|
+
import_radix_ui4.Dialog.Title,
|
|
369
615
|
{
|
|
370
616
|
"data-slot": "dialog-title",
|
|
371
617
|
className: cn("font-heading text-base leading-none font-medium", className),
|
|
@@ -377,8 +623,8 @@ function DialogDescription({
|
|
|
377
623
|
className,
|
|
378
624
|
...props
|
|
379
625
|
}) {
|
|
380
|
-
return /* @__PURE__ */ (0,
|
|
381
|
-
|
|
626
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
627
|
+
import_radix_ui4.Dialog.Description,
|
|
382
628
|
{
|
|
383
629
|
"data-slot": "dialog-description",
|
|
384
630
|
className: cn(
|
|
@@ -412,10 +658,10 @@ function saveToastId(docId) {
|
|
|
412
658
|
}
|
|
413
659
|
|
|
414
660
|
// src/components/ui/textarea.tsx
|
|
415
|
-
var
|
|
416
|
-
var
|
|
661
|
+
var React6 = require("react");
|
|
662
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
417
663
|
function Textarea({ className, ...props }) {
|
|
418
|
-
return /* @__PURE__ */ (0,
|
|
664
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
419
665
|
"textarea",
|
|
420
666
|
{
|
|
421
667
|
"data-slot": "textarea",
|
|
@@ -432,7 +678,7 @@ function Textarea({ className, ...props }) {
|
|
|
432
678
|
var import_react2 = require("@grapesjs/react");
|
|
433
679
|
var import_react3 = require("react");
|
|
434
680
|
var import_lucide_react3 = require("lucide-react");
|
|
435
|
-
var
|
|
681
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
436
682
|
var keyOf = (c) => c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;
|
|
437
683
|
var keyOfTarget = (t) => t.objectId ? `obj-${t.objectId}` : `sec-${t.sectionId}`;
|
|
438
684
|
function CanvasComments({
|
|
@@ -586,11 +832,11 @@ function CanvasComments({
|
|
|
586
832
|
console.error(e);
|
|
587
833
|
}
|
|
588
834
|
};
|
|
589
|
-
return /* @__PURE__ */ (0,
|
|
835
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { ref: containerRef, className: "pointer-events-none absolute inset-0 z-20 overflow-hidden", children: [
|
|
590
836
|
pins.map(({ key, list }) => {
|
|
591
837
|
const pos = positions[key];
|
|
592
838
|
if (!pos) return null;
|
|
593
|
-
return /* @__PURE__ */ (0,
|
|
839
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
594
840
|
"button",
|
|
595
841
|
{
|
|
596
842
|
type: "button",
|
|
@@ -606,8 +852,8 @@ function CanvasComments({
|
|
|
606
852
|
key
|
|
607
853
|
);
|
|
608
854
|
}),
|
|
609
|
-
/* @__PURE__ */ (0,
|
|
610
|
-
/* @__PURE__ */ (0,
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Popover, { open: !!activeKey && !!activePos, onOpenChange: (o) => !o && closePopover(), children: [
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
611
857
|
"div",
|
|
612
858
|
{
|
|
613
859
|
style: {
|
|
@@ -619,45 +865,45 @@ function CanvasComments({
|
|
|
619
865
|
}
|
|
620
866
|
}
|
|
621
867
|
) }),
|
|
622
|
-
/* @__PURE__ */ (0,
|
|
868
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
623
869
|
PopoverContent,
|
|
624
870
|
{
|
|
625
871
|
side: "right",
|
|
626
872
|
align: "start",
|
|
627
873
|
className: "pointer-events-auto w-80 p-0 data-[state=closed]:!animate-none data-[state=closed]:!duration-0",
|
|
628
874
|
children: [
|
|
629
|
-
/* @__PURE__ */ (0,
|
|
630
|
-
/* @__PURE__ */ (0,
|
|
875
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground", children: [
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "min-w-0", children: activeTarget?.objectId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
631
877
|
"Comment on",
|
|
632
878
|
" ",
|
|
633
|
-
/* @__PURE__ */ (0,
|
|
634
|
-
] }) : /* @__PURE__ */ (0,
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-medium text-foreground", children: activeTarget.objectLabel })
|
|
880
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
635
881
|
labels.commentOnSection,
|
|
636
882
|
" ",
|
|
637
|
-
/* @__PURE__ */ (0,
|
|
883
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { className: "font-mono", children: activeTarget?.sectionId })
|
|
638
884
|
] }) }),
|
|
639
|
-
/* @__PURE__ */ (0,
|
|
885
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
640
886
|
"button",
|
|
641
887
|
{
|
|
642
888
|
type: "button",
|
|
643
889
|
onClick: closePopover,
|
|
644
890
|
"aria-label": labels.closeComments,
|
|
645
891
|
className: "-mr-1 -mt-0.5 shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
646
|
-
children: /* @__PURE__ */ (0,
|
|
892
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.X, { className: "size-4" })
|
|
647
893
|
}
|
|
648
894
|
)
|
|
649
895
|
] }),
|
|
650
|
-
/* @__PURE__ */ (0,
|
|
651
|
-
activeComments.length === 0 && /* @__PURE__ */ (0,
|
|
652
|
-
activeComments.map((c) => /* @__PURE__ */ (0,
|
|
653
|
-
/* @__PURE__ */ (0,
|
|
654
|
-
/* @__PURE__ */ (0,
|
|
896
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "max-h-64 space-y-2 overflow-y-auto p-3", children: [
|
|
897
|
+
activeComments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs text-muted-foreground", children: labels.noCommentsYet }),
|
|
898
|
+
activeComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2 text-sm", children: [
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "whitespace-pre-wrap", children: c.body }),
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
655
901
|
"button",
|
|
656
902
|
{
|
|
657
903
|
onClick: () => void resolve(c.id),
|
|
658
904
|
className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
|
|
659
905
|
children: [
|
|
660
|
-
/* @__PURE__ */ (0,
|
|
906
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.Check, { className: "size-3" }),
|
|
661
907
|
" ",
|
|
662
908
|
labels.resolveComment
|
|
663
909
|
]
|
|
@@ -665,8 +911,8 @@ function CanvasComments({
|
|
|
665
911
|
)
|
|
666
912
|
] }, c.id))
|
|
667
913
|
] }),
|
|
668
|
-
/* @__PURE__ */ (0,
|
|
669
|
-
/* @__PURE__ */ (0,
|
|
914
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "border-t p-3", children: [
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
670
916
|
Textarea,
|
|
671
917
|
{
|
|
672
918
|
autoFocus: true,
|
|
@@ -682,8 +928,8 @@ function CanvasComments({
|
|
|
682
928
|
}
|
|
683
929
|
}
|
|
684
930
|
),
|
|
685
|
-
/* @__PURE__ */ (0,
|
|
686
|
-
/* @__PURE__ */ (0,
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Button, { size: "sm", disabled: !body.trim(), onClick: () => void submit(), children: [
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.MessageSquarePlus, {}),
|
|
687
933
|
" ",
|
|
688
934
|
labels.addComment
|
|
689
935
|
] }) })
|
|
@@ -695,6 +941,30 @@ function CanvasComments({
|
|
|
695
941
|
] });
|
|
696
942
|
}
|
|
697
943
|
|
|
944
|
+
// src/lib/save-baseline.ts
|
|
945
|
+
function createSaveBaseline() {
|
|
946
|
+
let stored = "";
|
|
947
|
+
let loaded = null;
|
|
948
|
+
return {
|
|
949
|
+
/** The MJML the store holds, as far as this editor knows. */
|
|
950
|
+
get stored() {
|
|
951
|
+
return stored;
|
|
952
|
+
},
|
|
953
|
+
/** `canvas` is what the load serialises to, or null when it repaired something to write. */
|
|
954
|
+
load(mjml, canvas) {
|
|
955
|
+
stored = mjml;
|
|
956
|
+
loaded = canvas;
|
|
957
|
+
},
|
|
958
|
+
saved(mjml) {
|
|
959
|
+
stored = mjml;
|
|
960
|
+
loaded = null;
|
|
961
|
+
},
|
|
962
|
+
changed(mjml) {
|
|
963
|
+
return mjml !== stored && mjml !== loaded;
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
698
968
|
// src/lib/save-queue.ts
|
|
699
969
|
function createSaveQueue(save, delayMs) {
|
|
700
970
|
let timer = null;
|
|
@@ -724,21 +994,62 @@ function createSaveQueue(save, delayMs) {
|
|
|
724
994
|
};
|
|
725
995
|
}
|
|
726
996
|
|
|
997
|
+
// src/lib/turn-follower.ts
|
|
998
|
+
function createTurnFollower() {
|
|
999
|
+
let running = false;
|
|
1000
|
+
let mounted = true;
|
|
1001
|
+
let apply = null;
|
|
1002
|
+
return {
|
|
1003
|
+
turn(on) {
|
|
1004
|
+
if (on === running) return;
|
|
1005
|
+
running = on;
|
|
1006
|
+
if (mounted) apply?.(on);
|
|
1007
|
+
},
|
|
1008
|
+
/**
|
|
1009
|
+
* Whether the canvas is still mounted. An unmounted one's late reload must not unlock
|
|
1010
|
+
* the shared state a canvas mounted since has locked.
|
|
1011
|
+
*/
|
|
1012
|
+
get mounted() {
|
|
1013
|
+
return mounted;
|
|
1014
|
+
},
|
|
1015
|
+
// Both, as React mounts twice in development.
|
|
1016
|
+
mount() {
|
|
1017
|
+
mounted = true;
|
|
1018
|
+
},
|
|
1019
|
+
unmount() {
|
|
1020
|
+
mounted = false;
|
|
1021
|
+
},
|
|
1022
|
+
/** A turn already under way is applied at once; none means nothing to apply. */
|
|
1023
|
+
ready(fn) {
|
|
1024
|
+
apply = fn;
|
|
1025
|
+
if (running) fn(true);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/lib/shortcuts.ts
|
|
1031
|
+
function opensBlockSearch(e) {
|
|
1032
|
+
if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey || e.isComposing) return false;
|
|
1033
|
+
const target = e.target;
|
|
1034
|
+
if (target?.isContentEditable) return false;
|
|
1035
|
+
return !["INPUT", "TEXTAREA", "SELECT", "IFRAME"].includes(target?.tagName ?? "");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
727
1038
|
// src/components/editor/EmailEditor.tsx
|
|
728
1039
|
var import_sonner2 = require("sonner");
|
|
729
|
-
var
|
|
1040
|
+
var import_lucide_react10 = require("lucide-react");
|
|
730
1041
|
|
|
731
1042
|
// src/components/ui/switch.tsx
|
|
732
|
-
var
|
|
733
|
-
var
|
|
734
|
-
var
|
|
1043
|
+
var React7 = require("react");
|
|
1044
|
+
var import_radix_ui5 = require("radix-ui");
|
|
1045
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
735
1046
|
function Switch({
|
|
736
1047
|
className,
|
|
737
1048
|
size = "default",
|
|
738
1049
|
...props
|
|
739
1050
|
}) {
|
|
740
|
-
return /* @__PURE__ */ (0,
|
|
741
|
-
|
|
1051
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1052
|
+
import_radix_ui5.Switch.Root,
|
|
742
1053
|
{
|
|
743
1054
|
"data-slot": "switch",
|
|
744
1055
|
"data-size": size,
|
|
@@ -747,8 +1058,8 @@ function Switch({
|
|
|
747
1058
|
className
|
|
748
1059
|
),
|
|
749
1060
|
...props,
|
|
750
|
-
children: /* @__PURE__ */ (0,
|
|
751
|
-
|
|
1061
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1062
|
+
import_radix_ui5.Switch.Thumb,
|
|
752
1063
|
{
|
|
753
1064
|
"data-slot": "switch-thumb",
|
|
754
1065
|
className: "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
|
@@ -759,13 +1070,13 @@ function Switch({
|
|
|
759
1070
|
}
|
|
760
1071
|
|
|
761
1072
|
// src/components/editor/EmailEditor.tsx
|
|
762
|
-
var
|
|
1073
|
+
var import_lucide_react11 = require("lucide-react");
|
|
763
1074
|
|
|
764
1075
|
// src/components/editor/ImagePicker.tsx
|
|
765
1076
|
var import_react4 = require("react");
|
|
766
1077
|
var import_lucide_react4 = require("lucide-react");
|
|
767
1078
|
var import_sonner = require("sonner");
|
|
768
|
-
var
|
|
1079
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
769
1080
|
function ImagePicker({
|
|
770
1081
|
open,
|
|
771
1082
|
currentUrl,
|
|
@@ -857,13 +1168,13 @@ function ImagePicker({
|
|
|
857
1168
|
setDragging(false);
|
|
858
1169
|
if (!full) void upload(e.dataTransfer.files);
|
|
859
1170
|
};
|
|
860
|
-
return /* @__PURE__ */ (0,
|
|
861
|
-
/* @__PURE__ */ (0,
|
|
862
|
-
/* @__PURE__ */ (0,
|
|
863
|
-
quota && /* @__PURE__ */ (0,
|
|
1171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogContent, { className: "editor-dark gap-0 overflow-hidden border border-panel-border bg-panel p-0 text-panel-fg ring-0 sm:max-w-3xl", children: [
|
|
1172
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
|
|
1173
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogTitle, { children: labels.imagePickerTitle }),
|
|
1174
|
+
quota && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
|
|
864
1175
|
] }),
|
|
865
|
-
/* @__PURE__ */ (0,
|
|
866
|
-
library && (full ? /* @__PURE__ */ (0,
|
|
1176
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
|
|
1177
|
+
library && (full ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "rounded-lg border border-panel-border bg-panel-elevated px-4 py-3 text-sm text-panel-muted-fg", children: labels.imagePickerQuotaFull }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
867
1178
|
"div",
|
|
868
1179
|
{
|
|
869
1180
|
onDragOver: (e) => {
|
|
@@ -877,10 +1188,10 @@ function ImagePicker({
|
|
|
877
1188
|
dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
|
|
878
1189
|
),
|
|
879
1190
|
children: [
|
|
880
|
-
/* @__PURE__ */ (0,
|
|
881
|
-
/* @__PURE__ */ (0,
|
|
882
|
-
/* @__PURE__ */ (0,
|
|
883
|
-
/* @__PURE__ */ (0,
|
|
1191
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.ImageUp, { className: "size-8 text-panel-muted-fg" }),
|
|
1192
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
|
|
1193
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: labels.imagePickerDrop }),
|
|
1194
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
884
1195
|
Button,
|
|
885
1196
|
{
|
|
886
1197
|
type: "button",
|
|
@@ -891,7 +1202,7 @@ function ImagePicker({
|
|
|
891
1202
|
}
|
|
892
1203
|
)
|
|
893
1204
|
] }),
|
|
894
|
-
/* @__PURE__ */ (0,
|
|
1205
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
895
1206
|
"input",
|
|
896
1207
|
{
|
|
897
1208
|
ref: fileInput,
|
|
@@ -908,7 +1219,7 @@ function ImagePicker({
|
|
|
908
1219
|
]
|
|
909
1220
|
}
|
|
910
1221
|
)),
|
|
911
|
-
/* @__PURE__ */ (0,
|
|
1222
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
912
1223
|
"form",
|
|
913
1224
|
{
|
|
914
1225
|
className: "flex gap-2",
|
|
@@ -918,9 +1229,9 @@ function ImagePicker({
|
|
|
918
1229
|
if (value) onSelect(value);
|
|
919
1230
|
},
|
|
920
1231
|
children: [
|
|
921
|
-
/* @__PURE__ */ (0,
|
|
922
|
-
/* @__PURE__ */ (0,
|
|
923
|
-
/* @__PURE__ */ (0,
|
|
1232
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-1", children: [
|
|
1233
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
|
|
1234
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
924
1235
|
Input,
|
|
925
1236
|
{
|
|
926
1237
|
value: url,
|
|
@@ -930,7 +1241,7 @@ function ImagePicker({
|
|
|
930
1241
|
}
|
|
931
1242
|
)
|
|
932
1243
|
] }),
|
|
933
|
-
/* @__PURE__ */ (0,
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
934
1245
|
Button,
|
|
935
1246
|
{
|
|
936
1247
|
type: "submit",
|
|
@@ -944,22 +1255,22 @@ function ImagePicker({
|
|
|
944
1255
|
]
|
|
945
1256
|
}
|
|
946
1257
|
),
|
|
947
|
-
library && /* @__PURE__ */ (0,
|
|
948
|
-
/* @__PURE__ */ (0,
|
|
949
|
-
loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0,
|
|
950
|
-
Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0,
|
|
1258
|
+
library && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { children: [
|
|
1259
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
|
|
1260
|
+
loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
|
|
1261
|
+
Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
951
1262
|
"li",
|
|
952
1263
|
{
|
|
953
1264
|
className: "flex aspect-[4/3] items-center justify-center rounded-lg border border-panel-border bg-panel-elevated",
|
|
954
|
-
children: /* @__PURE__ */ (0,
|
|
1265
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {})
|
|
955
1266
|
},
|
|
956
1267
|
`pending-${i}`
|
|
957
1268
|
)),
|
|
958
1269
|
images.map((image) => {
|
|
959
1270
|
const selected = image.url === currentUrl;
|
|
960
1271
|
const confirming = confirmId === image.id;
|
|
961
|
-
return /* @__PURE__ */ (0,
|
|
962
|
-
/* @__PURE__ */ (0,
|
|
1272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "group relative", children: [
|
|
1273
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
963
1274
|
"button",
|
|
964
1275
|
{
|
|
965
1276
|
type: "button",
|
|
@@ -969,7 +1280,7 @@ function ImagePicker({
|
|
|
969
1280
|
"block aspect-[4/3] w-full overflow-hidden rounded-lg border bg-black/25 transition outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
970
1281
|
selected ? "border-brand ring-2 ring-brand" : "border-panel-border hover:border-panel-muted-fg"
|
|
971
1282
|
),
|
|
972
|
-
children: /* @__PURE__ */ (0,
|
|
1283
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
973
1284
|
"img",
|
|
974
1285
|
{
|
|
975
1286
|
src: image.url,
|
|
@@ -980,9 +1291,9 @@ function ImagePicker({
|
|
|
980
1291
|
)
|
|
981
1292
|
}
|
|
982
1293
|
),
|
|
983
|
-
selected && /* @__PURE__ */ (0,
|
|
984
|
-
image.name && /* @__PURE__ */ (0,
|
|
985
|
-
library.remove && /* @__PURE__ */ (0,
|
|
1294
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pointer-events-none absolute top-1.5 left-1.5 flex size-5 items-center justify-center rounded-full bg-brand text-brand-fg", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Check, { className: "size-3" }) }),
|
|
1295
|
+
image.name && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pointer-events-none absolute inset-x-0 bottom-0 truncate rounded-b-lg bg-black/60 px-2 py-1 text-[11px] text-white opacity-0 transition group-hover:opacity-100", children: image.name }),
|
|
1296
|
+
library.remove && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
986
1297
|
"button",
|
|
987
1298
|
{
|
|
988
1299
|
type: "button",
|
|
@@ -995,7 +1306,7 @@ function ImagePicker({
|
|
|
995
1306
|
confirming ? "bg-red-600 opacity-100" : "bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80"
|
|
996
1307
|
),
|
|
997
1308
|
children: [
|
|
998
|
-
/* @__PURE__ */ (0,
|
|
1309
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Trash2, { className: "size-3.5" }),
|
|
999
1310
|
confirming && labels.imagePickerConfirmDelete
|
|
1000
1311
|
]
|
|
1001
1312
|
}
|
|
@@ -1017,171 +1328,1201 @@ function matchesAccept(file, accept) {
|
|
|
1017
1328
|
});
|
|
1018
1329
|
}
|
|
1019
1330
|
|
|
1331
|
+
// src/components/editor/inspector/Inspector.tsx
|
|
1332
|
+
var import_react7 = require("react");
|
|
1333
|
+
var import_react8 = require("@grapesjs/react");
|
|
1334
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1335
|
+
|
|
1020
1336
|
// src/components/editor/ColorField.tsx
|
|
1021
|
-
var
|
|
1337
|
+
var import_react6 = require("react");
|
|
1022
1338
|
var import_react_colorful = require("react-colorful");
|
|
1339
|
+
var import_lucide_react6 = require("lucide-react");
|
|
1340
|
+
|
|
1341
|
+
// src/components/editor/inspector/controls.tsx
|
|
1342
|
+
var import_react5 = require("react");
|
|
1023
1343
|
var import_lucide_react5 = require("lucide-react");
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
"
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
"
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1344
|
+
|
|
1345
|
+
// src/components/editor/inspector/values.ts
|
|
1346
|
+
var import_core2 = require("@mjml-agent-editor/core");
|
|
1347
|
+
var SIDES = ["top", "right", "bottom", "left"];
|
|
1348
|
+
function expandBox(value) {
|
|
1349
|
+
const parts = value.trim().split(/\s+/).filter(Boolean);
|
|
1350
|
+
if (parts.length === 0 || parts.length > 4) return null;
|
|
1351
|
+
const [t, r = t, b = t, l = r] = parts;
|
|
1352
|
+
return [t, r, b, l];
|
|
1353
|
+
}
|
|
1354
|
+
function readBox(attributes, attr) {
|
|
1355
|
+
const short = expandBox(attributes[attr] ?? "") ?? ["", "", "", ""];
|
|
1356
|
+
return SIDES.map((side, i) => attributes[`${attr}-${side}`] ?? short[i]);
|
|
1357
|
+
}
|
|
1358
|
+
function boxShorthand(sides) {
|
|
1359
|
+
const [t, r, b, l] = sides.map((s) => s.trim() || "0");
|
|
1360
|
+
if (t === b && r === l) return t === r ? t : `${t} ${r}`;
|
|
1361
|
+
if (r === l) return `${t} ${r} ${b}`;
|
|
1362
|
+
return `${t} ${r} ${b} ${l}`;
|
|
1363
|
+
}
|
|
1364
|
+
function parseLength(value, units) {
|
|
1365
|
+
const match = /^(-?\d*\.?\d+)\s*(px|%|em)?$/.exec(value.trim());
|
|
1366
|
+
if (!match) return null;
|
|
1367
|
+
const unit = match[2] ?? "";
|
|
1368
|
+
return units.includes(unit) ? { amount: match[1], unit } : null;
|
|
1369
|
+
}
|
|
1370
|
+
var NO_SHARE = "-noshare";
|
|
1371
|
+
function parseNetwork(name) {
|
|
1372
|
+
return name.endsWith(NO_SHARE) ? { network: name.slice(0, -NO_SHARE.length), share: false } : { network: name, share: name !== "custom" && name !== "" };
|
|
1373
|
+
}
|
|
1374
|
+
function networkName(network, share) {
|
|
1375
|
+
return share || network === "custom" ? network : `${network}${NO_SHARE}`;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/components/editor/inspector/controls.tsx
|
|
1379
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1380
|
+
var FIELD = "h-7 w-full min-w-0 rounded-md border border-panel-border bg-panel-elevated px-2 text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg/70 focus:border-brand";
|
|
1381
|
+
var FieldLabel = (0, import_react5.createContext)({});
|
|
1382
|
+
var useFieldLabel = () => {
|
|
1383
|
+
const { label, help } = (0, import_react5.useContext)(FieldLabel);
|
|
1384
|
+
return { "aria-labelledby": label, "aria-describedby": help };
|
|
1385
|
+
};
|
|
1386
|
+
function Row({
|
|
1387
|
+
label,
|
|
1388
|
+
help,
|
|
1389
|
+
own,
|
|
1390
|
+
wide,
|
|
1391
|
+
inline,
|
|
1046
1392
|
labels,
|
|
1047
|
-
|
|
1393
|
+
onReset,
|
|
1394
|
+
children
|
|
1048
1395
|
}) {
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
const [
|
|
1052
|
-
const
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1396
|
+
const id = (0, import_react5.useId)();
|
|
1397
|
+
const helpId = (0, import_react5.useId)();
|
|
1398
|
+
const [helpOpen, setHelpOpen] = (0, import_react5.useState)(false);
|
|
1399
|
+
const control = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldLabel.Provider, { value: { label: id, help: help ? helpId : void 0 }, children });
|
|
1400
|
+
const caption = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex min-w-0 items-center gap-1.5 text-[11px] text-panel-muted-fg", children: [
|
|
1401
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1402
|
+
"span",
|
|
1403
|
+
{
|
|
1404
|
+
"aria-hidden": true,
|
|
1405
|
+
className: cn("size-1.5 shrink-0 rounded-full", own ? "bg-brand" : "bg-transparent")
|
|
1406
|
+
}
|
|
1407
|
+
),
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { id, className: "truncate", children: label }),
|
|
1409
|
+
help && // Its own provider: a host need not wrap the editor in one, as only EditorHeader asked.
|
|
1410
|
+
// Opened by a tap as well: Radix opens a tooltip on hover and focus, not on touch.
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Tooltip, { open: helpOpen, onOpenChange: setHelpOpen, children: [
|
|
1412
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1413
|
+
"button",
|
|
1414
|
+
{
|
|
1415
|
+
type: "button",
|
|
1416
|
+
"aria-label": help,
|
|
1417
|
+
onClick: () => setHelpOpen((open) => !open),
|
|
1418
|
+
className: "-m-0.5 shrink-0 rounded p-0.5 text-panel-muted-fg/70 hover:text-panel-fg focus-visible:text-panel-fg",
|
|
1419
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.CircleHelp, { className: "size-3" })
|
|
1420
|
+
}
|
|
1421
|
+
) }),
|
|
1422
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipContent, { side: "top", className: "max-w-56 leading-snug", children: help })
|
|
1423
|
+
] }) }),
|
|
1424
|
+
own && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1425
|
+
"button",
|
|
1426
|
+
{
|
|
1427
|
+
type: "button",
|
|
1428
|
+
onClick: onReset,
|
|
1429
|
+
title: labels.resetToDefault,
|
|
1430
|
+
"aria-label": `${labels.resetToDefault}: ${label}`,
|
|
1431
|
+
className: "ml-auto rounded p-0.5 text-panel-muted-fg opacity-0 group-hover/row:opacity-100 hover:text-panel-fg focus-visible:opacity-100",
|
|
1432
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.RotateCcw, { className: "size-3" })
|
|
1433
|
+
}
|
|
1434
|
+
)
|
|
1435
|
+
] });
|
|
1436
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "group/row px-3 py-1", children: [
|
|
1437
|
+
wide ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
1438
|
+
caption,
|
|
1439
|
+
control
|
|
1440
|
+
] }) : inline ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1441
|
+
caption,
|
|
1442
|
+
control
|
|
1443
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid grid-cols-[92px_minmax(0,1fr)] items-center gap-2", children: [
|
|
1444
|
+
caption,
|
|
1445
|
+
control
|
|
1446
|
+
] }),
|
|
1447
|
+
help && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { id: helpId, hidden: true, children: help })
|
|
1448
|
+
] });
|
|
1449
|
+
}
|
|
1450
|
+
function useDraft(value, commit, source = value) {
|
|
1451
|
+
const [draft, setDraft] = (0, import_react5.useState)(value);
|
|
1452
|
+
(0, import_react5.useEffect)(() => setDraft(value), [value, source]);
|
|
1453
|
+
const done = () => {
|
|
1454
|
+
if (draft.trim() !== value) commit(draft.trim());
|
|
1455
|
+
};
|
|
1456
|
+
const onKeyDown = (e) => {
|
|
1457
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
1458
|
+
if (e.key === "Escape") {
|
|
1459
|
+
setDraft(value);
|
|
1460
|
+
requestAnimationFrame(() => e.target.blur());
|
|
1060
1461
|
}
|
|
1462
|
+
};
|
|
1463
|
+
return { draft, setDraft, done, onKeyDown };
|
|
1464
|
+
}
|
|
1465
|
+
function TextInput({
|
|
1466
|
+
value,
|
|
1467
|
+
placeholder,
|
|
1468
|
+
accept,
|
|
1469
|
+
onCommit
|
|
1470
|
+
}) {
|
|
1471
|
+
const [invalid, setInvalid] = (0, import_react5.useState)(false);
|
|
1472
|
+
const field = useFieldLabel();
|
|
1473
|
+
const { draft, setDraft, done, onKeyDown } = useDraft(value, (next) => {
|
|
1474
|
+
const ok = !accept || accept(next);
|
|
1475
|
+
setInvalid(!ok);
|
|
1476
|
+
if (ok) onCommit(next);
|
|
1061
1477
|
});
|
|
1062
|
-
(0,
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1479
|
+
"input",
|
|
1480
|
+
{
|
|
1481
|
+
className: cn(FIELD, invalid && "border-red-400"),
|
|
1482
|
+
...field,
|
|
1483
|
+
"aria-invalid": invalid,
|
|
1484
|
+
value: draft,
|
|
1485
|
+
placeholder,
|
|
1486
|
+
onChange: (e) => setDraft(e.target.value),
|
|
1487
|
+
onBlur: done,
|
|
1488
|
+
onKeyDown
|
|
1489
|
+
}
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
var BARE_NUMBER = /^-?\d*\.?\d+$/;
|
|
1493
|
+
function normaliseLength(text, units, negative = false, unit = units[0]) {
|
|
1494
|
+
if (text === "") return "";
|
|
1495
|
+
const parsed = BARE_NUMBER.test(text) ? { amount: text, unit } : parseLength(text, units);
|
|
1496
|
+
if (!parsed || !negative && parsed.amount.startsWith("-")) return null;
|
|
1497
|
+
return `${parsed.amount}${parsed.unit}`;
|
|
1498
|
+
}
|
|
1499
|
+
function stepAmount(amount, delta, negative = false) {
|
|
1500
|
+
const moved = Math.round((Number(amount) + delta) * 100) / 100;
|
|
1501
|
+
const next = String(negative ? moved : Math.max(0, moved));
|
|
1502
|
+
return BARE_NUMBER.test(next) ? next : amount;
|
|
1503
|
+
}
|
|
1504
|
+
var REPEAT_AFTER_MS = 400;
|
|
1505
|
+
var REPEAT_EVERY_MS = 60;
|
|
1506
|
+
function LengthInput({
|
|
1507
|
+
value,
|
|
1508
|
+
placeholder,
|
|
1509
|
+
units,
|
|
1510
|
+
negative = false,
|
|
1511
|
+
compact = false,
|
|
1512
|
+
labelledBy,
|
|
1513
|
+
labels,
|
|
1514
|
+
onCommit
|
|
1515
|
+
}) {
|
|
1516
|
+
const fieldLabel = useFieldLabel();
|
|
1517
|
+
const own = parseLength(value, units);
|
|
1518
|
+
const fallback = parseLength(placeholder ?? "", units);
|
|
1519
|
+
const [picked, setPicked] = (0, import_react5.useState)(null);
|
|
1520
|
+
const unit = own?.unit ?? picked ?? fallback?.unit ?? units[0];
|
|
1521
|
+
const [invalid, setInvalid] = (0, import_react5.useState)(false);
|
|
1522
|
+
const commit = (0, import_react5.useRef)(onCommit);
|
|
1523
|
+
commit.current = onCommit;
|
|
1524
|
+
const endHold = (0, import_react5.useRef)(null);
|
|
1525
|
+
(0, import_react5.useEffect)(() => () => endHold.current?.(), []);
|
|
1526
|
+
const { draft, setDraft, done, onKeyDown } = useDraft(
|
|
1527
|
+
own ? own.amount : value,
|
|
1528
|
+
(next) => {
|
|
1529
|
+
endHold.current?.();
|
|
1530
|
+
const normal = normaliseLength(next, units, negative, unit);
|
|
1531
|
+
setInvalid(normal === null);
|
|
1532
|
+
if (normal !== null) commit.current(normal);
|
|
1533
|
+
},
|
|
1534
|
+
value
|
|
1535
|
+
);
|
|
1536
|
+
const from = () => {
|
|
1537
|
+
const text = draft.trim();
|
|
1538
|
+
if (BARE_NUMBER.test(text)) return { amount: text, unit };
|
|
1539
|
+
if (text === "") return { amount: fallback?.amount ?? "0", unit };
|
|
1540
|
+
return parseLength(text, units);
|
|
1083
1541
|
};
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1542
|
+
const stepOnce = (delta) => {
|
|
1543
|
+
endHold.current?.();
|
|
1544
|
+
const start = from();
|
|
1545
|
+
if (!start) return setInvalid(true);
|
|
1546
|
+
const amount = stepAmount(start.amount, delta, negative);
|
|
1547
|
+
setInvalid(false);
|
|
1548
|
+
setDraft(amount);
|
|
1549
|
+
commit.current(`${amount}${start.unit}`);
|
|
1089
1550
|
};
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1551
|
+
const onKey = (e) => {
|
|
1552
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return onKeyDown(e);
|
|
1553
|
+
e.preventDefault();
|
|
1554
|
+
stepOnce((e.key === "ArrowUp" ? 1 : -1) * (e.shiftKey ? 10 : 1));
|
|
1555
|
+
};
|
|
1556
|
+
const press = (direction) => (e) => {
|
|
1557
|
+
if (e.button !== 0 || endHold.current) return;
|
|
1558
|
+
e.preventDefault();
|
|
1559
|
+
const start = from();
|
|
1560
|
+
if (!start) return setInvalid(true);
|
|
1561
|
+
const delta = direction * (e.shiftKey ? 10 : 1);
|
|
1562
|
+
let amount = stepAmount(start.amount, delta, negative);
|
|
1563
|
+
const show = () => {
|
|
1564
|
+
setInvalid(false);
|
|
1565
|
+
setDraft(amount);
|
|
1566
|
+
commit.current(`${amount}${start.unit}`, true);
|
|
1567
|
+
};
|
|
1568
|
+
show();
|
|
1569
|
+
let timer = window.setTimeout(function repeat() {
|
|
1570
|
+
amount = stepAmount(amount, delta, negative);
|
|
1571
|
+
show();
|
|
1572
|
+
timer = window.setTimeout(repeat, REPEAT_EVERY_MS);
|
|
1573
|
+
}, REPEAT_AFTER_MS);
|
|
1574
|
+
const button = e.currentTarget;
|
|
1093
1575
|
try {
|
|
1094
|
-
|
|
1095
|
-
commit(sRGBHex);
|
|
1576
|
+
button.setPointerCapture(e.pointerId);
|
|
1096
1577
|
} catch {
|
|
1097
1578
|
}
|
|
1579
|
+
const events = ["pointerup", "pointercancel", "lostpointercapture"];
|
|
1580
|
+
const pointerId = e.pointerId;
|
|
1581
|
+
const end = () => {
|
|
1582
|
+
window.clearTimeout(timer);
|
|
1583
|
+
for (const type of events) button.removeEventListener(type, release);
|
|
1584
|
+
window.removeEventListener("pointerup", release);
|
|
1585
|
+
window.removeEventListener("blur", end);
|
|
1586
|
+
endHold.current = null;
|
|
1587
|
+
commit.current(`${amount}${start.unit}`);
|
|
1588
|
+
};
|
|
1589
|
+
const release = (ev) => {
|
|
1590
|
+
if (ev.pointerId === void 0 || ev.pointerId === pointerId)
|
|
1591
|
+
end();
|
|
1592
|
+
};
|
|
1593
|
+
endHold.current = end;
|
|
1594
|
+
for (const type of events) button.addEventListener(type, release);
|
|
1595
|
+
window.addEventListener("pointerup", release);
|
|
1596
|
+
window.addEventListener("blur", end);
|
|
1098
1597
|
};
|
|
1099
|
-
|
|
1100
|
-
|
|
1598
|
+
const click = (direction) => (e) => {
|
|
1599
|
+
if (e.detail === 0) stepOnce(direction);
|
|
1600
|
+
};
|
|
1601
|
+
const pickUnit = () => {
|
|
1602
|
+
endHold.current?.();
|
|
1603
|
+
const next = units[(units.indexOf(unit) + 1) % units.length];
|
|
1604
|
+
const text = draft.trim();
|
|
1605
|
+
if (!BARE_NUMBER.test(text)) return setPicked(next);
|
|
1606
|
+
const normal = normaliseLength(text, units, negative, next);
|
|
1607
|
+
if (normal === null) return setInvalid(true);
|
|
1608
|
+
setPicked(next);
|
|
1609
|
+
commit.current(normal);
|
|
1610
|
+
};
|
|
1611
|
+
const unitName = (u) => u || labels.noUnit;
|
|
1612
|
+
const readable = draft.trim() === "" || BARE_NUMBER.test(draft.trim());
|
|
1613
|
+
const arrow = "flex h-1/2 w-3.5 items-center justify-center text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
|
|
1614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1615
|
+
"div",
|
|
1101
1616
|
{
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
setOpen(next);
|
|
1107
|
-
},
|
|
1617
|
+
className: cn(
|
|
1618
|
+
"group/length flex h-7 w-full min-w-0 items-center overflow-hidden rounded-md border border-panel-border bg-panel-elevated focus-within:border-brand",
|
|
1619
|
+
invalid && "border-red-400"
|
|
1620
|
+
),
|
|
1108
1621
|
children: [
|
|
1109
|
-
/* @__PURE__ */ (0,
|
|
1110
|
-
|
|
1111
|
-
"div",
|
|
1622
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1623
|
+
"input",
|
|
1112
1624
|
{
|
|
1113
|
-
|
|
1114
|
-
|
|
1625
|
+
className: "h-full w-full min-w-0 flex-1 bg-transparent pl-2 font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg/70",
|
|
1626
|
+
value: draft,
|
|
1627
|
+
placeholder: fallback ? fallback.amount : placeholder,
|
|
1628
|
+
inputMode: "decimal",
|
|
1629
|
+
...fieldLabel,
|
|
1630
|
+
"aria-labelledby": labelledBy ?? fieldLabel["aria-labelledby"],
|
|
1631
|
+
"aria-invalid": invalid,
|
|
1632
|
+
onChange: (e) => {
|
|
1633
|
+
endHold.current?.();
|
|
1634
|
+
setDraft(e.target.value);
|
|
1635
|
+
},
|
|
1636
|
+
onBlur: done,
|
|
1637
|
+
onKeyDown: onKey
|
|
1638
|
+
}
|
|
1639
|
+
),
|
|
1640
|
+
readable && (units.length > 1 && !compact ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1641
|
+
"button",
|
|
1642
|
+
{
|
|
1643
|
+
type: "button",
|
|
1644
|
+
onClick: pickUnit,
|
|
1645
|
+
title: labels.changeUnit(unitName(unit)),
|
|
1646
|
+
"aria-label": labels.changeUnit(unitName(unit)),
|
|
1647
|
+
className: "shrink-0 rounded px-0.5 font-mono text-[10px] text-panel-muted-fg underline decoration-dotted underline-offset-2 hover:text-panel-fg",
|
|
1648
|
+
children: unitName(unit)
|
|
1649
|
+
}
|
|
1650
|
+
) : (
|
|
1651
|
+
// Narrow, the unit gives its place to the arrows while they show; a unit is typed.
|
|
1652
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1653
|
+
"span",
|
|
1654
|
+
{
|
|
1655
|
+
"aria-hidden": true,
|
|
1656
|
+
className: cn(
|
|
1657
|
+
"shrink-0 px-0.5 font-mono text-[10px] text-panel-muted-fg",
|
|
1658
|
+
compact && "group-focus-within/length:hidden group-hover/length:hidden"
|
|
1659
|
+
),
|
|
1660
|
+
children: unitName(unit)
|
|
1661
|
+
}
|
|
1662
|
+
)
|
|
1663
|
+
)),
|
|
1664
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1665
|
+
"span",
|
|
1666
|
+
{
|
|
1667
|
+
className: cn(
|
|
1668
|
+
"h-full shrink-0 flex-col border-l border-panel-border",
|
|
1669
|
+
compact ? "hidden group-focus-within/length:flex group-hover/length:flex" : "flex"
|
|
1670
|
+
),
|
|
1115
1671
|
children: [
|
|
1116
|
-
/* @__PURE__ */ (0,
|
|
1117
|
-
"
|
|
1672
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1673
|
+
"button",
|
|
1118
1674
|
{
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
if (e.key === "Escape") setText(value);
|
|
1127
|
-
},
|
|
1128
|
-
className: "min-w-0 flex-1 bg-transparent font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg"
|
|
1675
|
+
type: "button",
|
|
1676
|
+
tabIndex: -1,
|
|
1677
|
+
"aria-label": labels.increase,
|
|
1678
|
+
onPointerDown: press(1),
|
|
1679
|
+
onClick: click(1),
|
|
1680
|
+
className: arrow,
|
|
1681
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.ChevronUp, { className: "size-2.5" })
|
|
1129
1682
|
}
|
|
1130
1683
|
),
|
|
1131
|
-
/* @__PURE__ */ (0,
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1132
1685
|
"button",
|
|
1133
1686
|
{
|
|
1134
1687
|
type: "button",
|
|
1135
|
-
|
|
1136
|
-
"aria-label": labels.
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
]
|
|
1688
|
+
tabIndex: -1,
|
|
1689
|
+
"aria-label": labels.decrease,
|
|
1690
|
+
onPointerDown: press(-1),
|
|
1691
|
+
onClick: click(-1),
|
|
1692
|
+
className: arrow,
|
|
1693
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.ChevronDown, { className: "size-2.5" })
|
|
1142
1694
|
}
|
|
1143
|
-
)
|
|
1695
|
+
)
|
|
1144
1696
|
]
|
|
1145
1697
|
}
|
|
1146
|
-
)
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1698
|
+
)
|
|
1699
|
+
]
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
function BoxInput({
|
|
1704
|
+
attributes,
|
|
1705
|
+
attr,
|
|
1706
|
+
placeholder,
|
|
1707
|
+
units,
|
|
1708
|
+
labels,
|
|
1709
|
+
onCommit
|
|
1710
|
+
}) {
|
|
1711
|
+
const sides = readBox(attributes, attr);
|
|
1712
|
+
const defaults = readBox(placeholder ? { [attr]: placeholder } : {}, attr);
|
|
1713
|
+
const symmetric = sides[0] === sides[2] && sides[1] === sides[3];
|
|
1714
|
+
const [each, setEach] = (0, import_react5.useState)(!symmetric);
|
|
1715
|
+
(0, import_react5.useEffect)(() => {
|
|
1716
|
+
if (!symmetric) setEach(true);
|
|
1717
|
+
}, [symmetric]);
|
|
1718
|
+
const set = (indices, v, partial) => {
|
|
1719
|
+
const next = sides.map((s, i) => s || defaults[i]);
|
|
1720
|
+
for (const i of indices) next[i] = v || defaults[i];
|
|
1721
|
+
onCommit(next.every((s, i) => s === defaults[i]) ? "" : boxShorthand(next), partial);
|
|
1722
|
+
};
|
|
1723
|
+
const fieldLabel = useFieldLabel();
|
|
1724
|
+
const sideId = (0, import_react5.useId)();
|
|
1725
|
+
const sideNames = [labels.sideTop, labels.sideRight, labels.sideBottom, labels.sideLeft];
|
|
1726
|
+
const pairs = each ? SIDES.map((_, i) => [sideNames[i], [i]]) : [
|
|
1727
|
+
[labels.sidesVertical, [0, 2]],
|
|
1728
|
+
[labels.sidesHorizontal, [1, 3]]
|
|
1729
|
+
];
|
|
1730
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-start gap-1.5", children: [
|
|
1731
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("grid flex-1 gap-1.5", each ? "grid-cols-4" : "grid-cols-2"), children: pairs.map(([name, indices]) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex min-w-0 flex-col gap-0.5", title: name, children: [
|
|
1732
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1733
|
+
LengthInput,
|
|
1734
|
+
{
|
|
1735
|
+
value: sides[indices[0]],
|
|
1736
|
+
placeholder: defaults[indices[0]] || "0",
|
|
1737
|
+
units,
|
|
1738
|
+
compact: each,
|
|
1739
|
+
labels,
|
|
1740
|
+
labelledBy: [fieldLabel["aria-labelledby"], `${sideId}-${indices[0]}`].filter(Boolean).join(" "),
|
|
1741
|
+
onCommit: (v, partial) => set(indices, v, partial)
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1745
|
+
"span",
|
|
1746
|
+
{
|
|
1747
|
+
id: `${sideId}-${indices[0]}`,
|
|
1748
|
+
className: "truncate text-center text-[10px] text-panel-muted-fg/80",
|
|
1749
|
+
children: name
|
|
1750
|
+
}
|
|
1751
|
+
)
|
|
1752
|
+
] }, name)) }),
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1754
|
+
"button",
|
|
1755
|
+
{
|
|
1756
|
+
type: "button",
|
|
1757
|
+
onClick: () => setEach((v) => !v),
|
|
1758
|
+
disabled: each && !symmetric,
|
|
1759
|
+
"aria-pressed": each,
|
|
1760
|
+
title: labels.editEachSide,
|
|
1761
|
+
"aria-label": labels.editEachSide,
|
|
1762
|
+
className: cn(
|
|
1763
|
+
"flex size-7 shrink-0 items-center justify-center rounded-md border border-panel-border text-panel-muted-fg hover:text-panel-fg disabled:opacity-40",
|
|
1764
|
+
each && "border-brand text-panel-fg"
|
|
1765
|
+
),
|
|
1766
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.Grid2x2, { className: "size-3.5" })
|
|
1767
|
+
}
|
|
1768
|
+
)
|
|
1769
|
+
] });
|
|
1770
|
+
}
|
|
1771
|
+
var ALIGN_ICONS = {
|
|
1772
|
+
left: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignLeft, { className: "size-3.5" }),
|
|
1773
|
+
center: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignCenter, { className: "size-3.5" }),
|
|
1774
|
+
right: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignRight, { className: "size-3.5" }),
|
|
1775
|
+
justify: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignJustify, { className: "size-3.5" })
|
|
1776
|
+
};
|
|
1777
|
+
function Segmented({
|
|
1778
|
+
value,
|
|
1779
|
+
fallback,
|
|
1780
|
+
options,
|
|
1781
|
+
name,
|
|
1782
|
+
onCommit
|
|
1783
|
+
}) {
|
|
1784
|
+
const current = value || fallback;
|
|
1785
|
+
const iconsOnly = options.every((o) => o.value in ALIGN_ICONS);
|
|
1786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex h-7 overflow-hidden rounded-md border border-panel-border bg-panel-elevated", children: options.map((o) => {
|
|
1787
|
+
const active = current === o.value;
|
|
1788
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1789
|
+
"button",
|
|
1790
|
+
{
|
|
1791
|
+
type: "button",
|
|
1792
|
+
"aria-pressed": active,
|
|
1793
|
+
title: name(o),
|
|
1794
|
+
"aria-label": name(o),
|
|
1795
|
+
onClick: () => o.value !== value && onCommit(o.value),
|
|
1796
|
+
className: cn(
|
|
1797
|
+
"flex min-w-0 flex-1 items-center justify-center px-1 text-[11px] text-panel-muted-fg transition-colors hover:text-panel-fg",
|
|
1798
|
+
active && (value ? "bg-brand text-brand-fg hover:text-brand-fg" : "bg-panel-hover text-panel-fg")
|
|
1799
|
+
),
|
|
1800
|
+
children: iconsOnly ? ALIGN_ICONS[o.value] : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: name(o) })
|
|
1801
|
+
},
|
|
1802
|
+
o.value
|
|
1803
|
+
);
|
|
1804
|
+
}) });
|
|
1805
|
+
}
|
|
1806
|
+
function Select({
|
|
1807
|
+
value,
|
|
1808
|
+
fallback,
|
|
1809
|
+
options,
|
|
1810
|
+
name,
|
|
1811
|
+
onCommit
|
|
1812
|
+
}) {
|
|
1813
|
+
const known = !value || options.some((o) => o.value === value);
|
|
1814
|
+
const fallbackOption = options.find((o) => o.value === fallback);
|
|
1815
|
+
const field = useFieldLabel();
|
|
1816
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1817
|
+
"select",
|
|
1818
|
+
{
|
|
1819
|
+
className: cn(FIELD, "px-1.5"),
|
|
1820
|
+
...field,
|
|
1821
|
+
value: value || "",
|
|
1822
|
+
onChange: (e) => onCommit(e.target.value),
|
|
1823
|
+
children: [
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "", children: fallbackOption ? name(fallbackOption) : "\u2014" }),
|
|
1825
|
+
!known && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value, children: value }),
|
|
1826
|
+
options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: o.value, children: name(o) }, o.value))
|
|
1827
|
+
]
|
|
1828
|
+
}
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
function Toggle({
|
|
1832
|
+
checked,
|
|
1833
|
+
label,
|
|
1834
|
+
onCommit
|
|
1835
|
+
}) {
|
|
1836
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1837
|
+
Switch,
|
|
1838
|
+
{
|
|
1839
|
+
checked,
|
|
1840
|
+
onCheckedChange: onCommit,
|
|
1841
|
+
"aria-label": label,
|
|
1842
|
+
className: "justify-self-end data-checked:bg-brand data-unchecked:bg-panel-border"
|
|
1843
|
+
}
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// src/components/editor/mjml-attributes.ts
|
|
1848
|
+
var ALLOWED_ATTRIBUTES = {
|
|
1849
|
+
"mj-body": [
|
|
1850
|
+
"background-color",
|
|
1851
|
+
"width"
|
|
1852
|
+
],
|
|
1853
|
+
"mj-wrapper": [
|
|
1854
|
+
"background-color",
|
|
1855
|
+
"background-position",
|
|
1856
|
+
"background-position-x",
|
|
1857
|
+
"background-position-y",
|
|
1858
|
+
"background-repeat",
|
|
1859
|
+
"background-size",
|
|
1860
|
+
"background-url",
|
|
1861
|
+
"border",
|
|
1862
|
+
"border-bottom",
|
|
1863
|
+
"border-left",
|
|
1864
|
+
"border-radius",
|
|
1865
|
+
"border-right",
|
|
1866
|
+
"border-top",
|
|
1867
|
+
"direction",
|
|
1868
|
+
"full-width",
|
|
1869
|
+
"gap",
|
|
1870
|
+
"padding",
|
|
1871
|
+
"padding-bottom",
|
|
1872
|
+
"padding-left",
|
|
1873
|
+
"padding-right",
|
|
1874
|
+
"padding-top",
|
|
1875
|
+
"text-align",
|
|
1876
|
+
"text-padding"
|
|
1877
|
+
],
|
|
1878
|
+
"mj-section": [
|
|
1879
|
+
"background-color",
|
|
1880
|
+
"background-position",
|
|
1881
|
+
"background-position-x",
|
|
1882
|
+
"background-position-y",
|
|
1883
|
+
"background-repeat",
|
|
1884
|
+
"background-size",
|
|
1885
|
+
"background-url",
|
|
1886
|
+
"border",
|
|
1887
|
+
"border-bottom",
|
|
1888
|
+
"border-left",
|
|
1889
|
+
"border-radius",
|
|
1890
|
+
"border-right",
|
|
1891
|
+
"border-top",
|
|
1892
|
+
"direction",
|
|
1893
|
+
"full-width",
|
|
1894
|
+
"padding",
|
|
1895
|
+
"padding-bottom",
|
|
1896
|
+
"padding-left",
|
|
1897
|
+
"padding-right",
|
|
1898
|
+
"padding-top",
|
|
1899
|
+
"text-align",
|
|
1900
|
+
"text-padding"
|
|
1901
|
+
],
|
|
1902
|
+
"mj-column": [
|
|
1903
|
+
"background-color",
|
|
1904
|
+
"border",
|
|
1905
|
+
"border-bottom",
|
|
1906
|
+
"border-left",
|
|
1907
|
+
"border-radius",
|
|
1908
|
+
"border-right",
|
|
1909
|
+
"border-top",
|
|
1910
|
+
"direction",
|
|
1911
|
+
"inner-background-color",
|
|
1912
|
+
"inner-border",
|
|
1913
|
+
"inner-border-bottom",
|
|
1914
|
+
"inner-border-left",
|
|
1915
|
+
"inner-border-radius",
|
|
1916
|
+
"inner-border-right",
|
|
1917
|
+
"inner-border-top",
|
|
1918
|
+
"padding",
|
|
1919
|
+
"padding-bottom",
|
|
1920
|
+
"padding-left",
|
|
1921
|
+
"padding-right",
|
|
1922
|
+
"padding-top",
|
|
1923
|
+
"vertical-align",
|
|
1924
|
+
"width"
|
|
1925
|
+
],
|
|
1926
|
+
"mj-group": [
|
|
1927
|
+
"background-color",
|
|
1928
|
+
"direction",
|
|
1929
|
+
"vertical-align",
|
|
1930
|
+
"width"
|
|
1931
|
+
],
|
|
1932
|
+
"mj-text": [
|
|
1933
|
+
"align",
|
|
1934
|
+
"background-color",
|
|
1935
|
+
"color",
|
|
1936
|
+
"container-background-color",
|
|
1937
|
+
"font-family",
|
|
1938
|
+
"font-size",
|
|
1939
|
+
"font-style",
|
|
1940
|
+
"font-weight",
|
|
1941
|
+
"height",
|
|
1942
|
+
"letter-spacing",
|
|
1943
|
+
"line-height",
|
|
1944
|
+
"padding",
|
|
1945
|
+
"padding-bottom",
|
|
1946
|
+
"padding-left",
|
|
1947
|
+
"padding-right",
|
|
1948
|
+
"padding-top",
|
|
1949
|
+
"text-decoration",
|
|
1950
|
+
"text-transform",
|
|
1951
|
+
"vertical-align"
|
|
1952
|
+
],
|
|
1953
|
+
"mj-button": [
|
|
1954
|
+
"align",
|
|
1955
|
+
"background-color",
|
|
1956
|
+
"border",
|
|
1957
|
+
"border-bottom",
|
|
1958
|
+
"border-left",
|
|
1959
|
+
"border-radius",
|
|
1960
|
+
"border-right",
|
|
1961
|
+
"border-top",
|
|
1962
|
+
"color",
|
|
1963
|
+
"container-background-color",
|
|
1964
|
+
"font-family",
|
|
1965
|
+
"font-size",
|
|
1966
|
+
"font-style",
|
|
1967
|
+
"font-weight",
|
|
1968
|
+
"height",
|
|
1969
|
+
"href",
|
|
1970
|
+
"inner-padding",
|
|
1971
|
+
"letter-spacing",
|
|
1972
|
+
"line-height",
|
|
1973
|
+
"name",
|
|
1974
|
+
"padding",
|
|
1975
|
+
"padding-bottom",
|
|
1976
|
+
"padding-left",
|
|
1977
|
+
"padding-right",
|
|
1978
|
+
"padding-top",
|
|
1979
|
+
"rel",
|
|
1980
|
+
"target",
|
|
1981
|
+
"text-align",
|
|
1982
|
+
"text-decoration",
|
|
1983
|
+
"text-transform",
|
|
1984
|
+
"title",
|
|
1985
|
+
"vertical-align",
|
|
1986
|
+
"width"
|
|
1987
|
+
],
|
|
1988
|
+
"mj-image": [
|
|
1989
|
+
"align",
|
|
1990
|
+
"alt",
|
|
1991
|
+
"border",
|
|
1992
|
+
"border-bottom",
|
|
1993
|
+
"border-left",
|
|
1994
|
+
"border-radius",
|
|
1995
|
+
"border-right",
|
|
1996
|
+
"border-top",
|
|
1997
|
+
"container-background-color",
|
|
1998
|
+
"fluid-on-mobile",
|
|
1999
|
+
"font-size",
|
|
2000
|
+
"height",
|
|
2001
|
+
"href",
|
|
2002
|
+
"max-height",
|
|
2003
|
+
"name",
|
|
2004
|
+
"padding",
|
|
2005
|
+
"padding-bottom",
|
|
2006
|
+
"padding-left",
|
|
2007
|
+
"padding-right",
|
|
2008
|
+
"padding-top",
|
|
2009
|
+
"rel",
|
|
2010
|
+
"sizes",
|
|
2011
|
+
"src",
|
|
2012
|
+
"srcset",
|
|
2013
|
+
"target",
|
|
2014
|
+
"title",
|
|
2015
|
+
"usemap",
|
|
2016
|
+
"width"
|
|
2017
|
+
],
|
|
2018
|
+
"mj-divider": [
|
|
2019
|
+
"align",
|
|
2020
|
+
"border-color",
|
|
2021
|
+
"border-style",
|
|
2022
|
+
"border-width",
|
|
2023
|
+
"container-background-color",
|
|
2024
|
+
"padding",
|
|
2025
|
+
"padding-bottom",
|
|
2026
|
+
"padding-left",
|
|
2027
|
+
"padding-right",
|
|
2028
|
+
"padding-top",
|
|
2029
|
+
"width"
|
|
2030
|
+
],
|
|
2031
|
+
"mj-spacer": [
|
|
2032
|
+
"border",
|
|
2033
|
+
"border-bottom",
|
|
2034
|
+
"border-left",
|
|
2035
|
+
"border-right",
|
|
2036
|
+
"border-top",
|
|
2037
|
+
"container-background-color",
|
|
2038
|
+
"height",
|
|
2039
|
+
"padding",
|
|
2040
|
+
"padding-bottom",
|
|
2041
|
+
"padding-left",
|
|
2042
|
+
"padding-right",
|
|
2043
|
+
"padding-top"
|
|
2044
|
+
],
|
|
2045
|
+
"mj-social": [
|
|
2046
|
+
"align",
|
|
2047
|
+
"border-radius",
|
|
2048
|
+
"color",
|
|
2049
|
+
"container-background-color",
|
|
2050
|
+
"font-family",
|
|
2051
|
+
"font-size",
|
|
2052
|
+
"font-style",
|
|
2053
|
+
"font-weight",
|
|
2054
|
+
"icon-height",
|
|
2055
|
+
"icon-padding",
|
|
2056
|
+
"icon-size",
|
|
2057
|
+
"inner-padding",
|
|
2058
|
+
"line-height",
|
|
2059
|
+
"mode",
|
|
2060
|
+
"padding",
|
|
2061
|
+
"padding-bottom",
|
|
2062
|
+
"padding-left",
|
|
2063
|
+
"padding-right",
|
|
2064
|
+
"padding-top",
|
|
2065
|
+
"table-layout",
|
|
2066
|
+
"text-decoration",
|
|
2067
|
+
"text-padding",
|
|
2068
|
+
"vertical-align"
|
|
2069
|
+
],
|
|
2070
|
+
"mj-social-element": [
|
|
2071
|
+
"align",
|
|
2072
|
+
"alt",
|
|
2073
|
+
"background-color",
|
|
2074
|
+
"border-radius",
|
|
2075
|
+
"color",
|
|
2076
|
+
"font-family",
|
|
2077
|
+
"font-size",
|
|
2078
|
+
"font-style",
|
|
2079
|
+
"font-weight",
|
|
2080
|
+
"href",
|
|
2081
|
+
"icon-height",
|
|
2082
|
+
"icon-padding",
|
|
2083
|
+
"icon-position",
|
|
2084
|
+
"icon-size",
|
|
2085
|
+
"line-height",
|
|
2086
|
+
"name",
|
|
2087
|
+
"padding",
|
|
2088
|
+
"padding-bottom",
|
|
2089
|
+
"padding-left",
|
|
2090
|
+
"padding-right",
|
|
2091
|
+
"padding-top",
|
|
2092
|
+
"rel",
|
|
2093
|
+
"sizes",
|
|
2094
|
+
"src",
|
|
2095
|
+
"srcset",
|
|
2096
|
+
"target",
|
|
2097
|
+
"text-decoration",
|
|
2098
|
+
"text-padding",
|
|
2099
|
+
"title",
|
|
2100
|
+
"vertical-align"
|
|
2101
|
+
],
|
|
2102
|
+
"mj-hero": [
|
|
2103
|
+
"background-color",
|
|
2104
|
+
"background-height",
|
|
2105
|
+
"background-position",
|
|
2106
|
+
"background-url",
|
|
2107
|
+
"background-width",
|
|
2108
|
+
"border-radius",
|
|
2109
|
+
"container-background-color",
|
|
2110
|
+
"height",
|
|
2111
|
+
"inner-background-color",
|
|
2112
|
+
"inner-padding",
|
|
2113
|
+
"inner-padding-bottom",
|
|
2114
|
+
"inner-padding-left",
|
|
2115
|
+
"inner-padding-right",
|
|
2116
|
+
"inner-padding-top",
|
|
2117
|
+
"mode",
|
|
2118
|
+
"padding",
|
|
2119
|
+
"padding-bottom",
|
|
2120
|
+
"padding-left",
|
|
2121
|
+
"padding-right",
|
|
2122
|
+
"padding-top",
|
|
2123
|
+
"vertical-align"
|
|
2124
|
+
],
|
|
2125
|
+
"mj-navbar": [
|
|
2126
|
+
"align",
|
|
2127
|
+
"base-url",
|
|
2128
|
+
"hamburger",
|
|
2129
|
+
"ico-align",
|
|
2130
|
+
"ico-close",
|
|
2131
|
+
"ico-color",
|
|
2132
|
+
"ico-font-family",
|
|
2133
|
+
"ico-font-size",
|
|
2134
|
+
"ico-line-height",
|
|
2135
|
+
"ico-open",
|
|
2136
|
+
"ico-padding",
|
|
2137
|
+
"ico-padding-bottom",
|
|
2138
|
+
"ico-padding-left",
|
|
2139
|
+
"ico-padding-right",
|
|
2140
|
+
"ico-padding-top",
|
|
2141
|
+
"ico-text-decoration",
|
|
2142
|
+
"ico-text-transform",
|
|
2143
|
+
"padding",
|
|
2144
|
+
"padding-bottom",
|
|
2145
|
+
"padding-left",
|
|
2146
|
+
"padding-right",
|
|
2147
|
+
"padding-top"
|
|
2148
|
+
],
|
|
2149
|
+
"mj-navbar-link": [
|
|
2150
|
+
"color",
|
|
2151
|
+
"font-family",
|
|
2152
|
+
"font-size",
|
|
2153
|
+
"font-style",
|
|
2154
|
+
"font-weight",
|
|
2155
|
+
"href",
|
|
2156
|
+
"letter-spacing",
|
|
2157
|
+
"line-height",
|
|
2158
|
+
"name",
|
|
2159
|
+
"padding",
|
|
2160
|
+
"padding-bottom",
|
|
2161
|
+
"padding-left",
|
|
2162
|
+
"padding-right",
|
|
2163
|
+
"padding-top",
|
|
2164
|
+
"rel",
|
|
2165
|
+
"target",
|
|
2166
|
+
"text-decoration",
|
|
2167
|
+
"text-transform"
|
|
2168
|
+
]
|
|
2169
|
+
};
|
|
2170
|
+
var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["mj-class", "css-class", "id", "style"]);
|
|
2171
|
+
var START_TAG = /<[a-z][^\s/>]*(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
|
|
2172
|
+
var ATTRIBUTE = /\s+([^\s"'>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?/g;
|
|
2173
|
+
function withoutEditorProps(mjml) {
|
|
2174
|
+
return mjml.replace(
|
|
2175
|
+
START_TAG,
|
|
2176
|
+
(tag) => tag.replace(
|
|
2177
|
+
ATTRIBUTE,
|
|
2178
|
+
(attribute, name) => name.toLowerCase().startsWith("data-gjs-") ? "" : attribute
|
|
2179
|
+
)
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
function withClosedTags(mjml) {
|
|
2183
|
+
return mjml.replace(START_TAG, (tag) => {
|
|
2184
|
+
const selfClosed = /^<(mj-[\w-]+)([\s\S]*?)\s*\/>$/i.exec(tag);
|
|
2185
|
+
return selfClosed ? `<${selfClosed[1]}${selfClosed[2]}></${selfClosed[1]}>` : tag;
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
var TABLE = /<!--[\s\S]*?(?:-->|$)|(<mj-table\b(?:[^>"']|"[^"]*"|'[^']*')*>)((?:<!--[\s\S]*?-->|(?!<!--)[\s\S])*?)(<\/mj-table\s*>)/gi;
|
|
2189
|
+
function withTableRowsKept(mjml) {
|
|
2190
|
+
return mjml.replace(
|
|
2191
|
+
TABLE,
|
|
2192
|
+
(match, open, rows, close) => open ? `${open}<template>${rows}</template>${close}` : match
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
function sourceForEditor(mjml) {
|
|
2196
|
+
const cleaned = withoutEditorProps(mjml);
|
|
2197
|
+
return { mjml: withTableRowsKept(withClosedTags(cleaned)), repaired: cleaned !== mjml };
|
|
2198
|
+
}
|
|
2199
|
+
function illegalAttributes(tag, attributes) {
|
|
2200
|
+
const allowed = ALLOWED_ATTRIBUTES[tag];
|
|
2201
|
+
if (!allowed) return [];
|
|
2202
|
+
return Object.keys(attributes).filter((a) => !ALWAYS_ALLOWED.has(a) && !allowed.includes(a));
|
|
2203
|
+
}
|
|
2204
|
+
var NAMED_COLORS = /* @__PURE__ */ new Set([
|
|
2205
|
+
"aliceblue",
|
|
2206
|
+
"antiquewhite",
|
|
2207
|
+
"aqua",
|
|
2208
|
+
"aquamarine",
|
|
2209
|
+
"azure",
|
|
2210
|
+
"beige",
|
|
2211
|
+
"bisque",
|
|
2212
|
+
"black",
|
|
2213
|
+
"blanchedalmond",
|
|
2214
|
+
"blue",
|
|
2215
|
+
"blueviolet",
|
|
2216
|
+
"brown",
|
|
2217
|
+
"burlywood",
|
|
2218
|
+
"cadetblue",
|
|
2219
|
+
"chartreuse",
|
|
2220
|
+
"chocolate",
|
|
2221
|
+
"coral",
|
|
2222
|
+
"cornflowerblue",
|
|
2223
|
+
"cornsilk",
|
|
2224
|
+
"crimson",
|
|
2225
|
+
"cyan",
|
|
2226
|
+
"darkblue",
|
|
2227
|
+
"darkcyan",
|
|
2228
|
+
"darkgoldenrod",
|
|
2229
|
+
"darkgray",
|
|
2230
|
+
"darkgreen",
|
|
2231
|
+
"darkgrey",
|
|
2232
|
+
"darkkhaki",
|
|
2233
|
+
"darkmagenta",
|
|
2234
|
+
"darkolivegreen",
|
|
2235
|
+
"darkorange",
|
|
2236
|
+
"darkorchid",
|
|
2237
|
+
"darkred",
|
|
2238
|
+
"darksalmon",
|
|
2239
|
+
"darkseagreen",
|
|
2240
|
+
"darkslateblue",
|
|
2241
|
+
"darkslategray",
|
|
2242
|
+
"darkslategrey",
|
|
2243
|
+
"darkturquoise",
|
|
2244
|
+
"darkviolet",
|
|
2245
|
+
"deeppink",
|
|
2246
|
+
"deepskyblue",
|
|
2247
|
+
"dimgray",
|
|
2248
|
+
"dimgrey",
|
|
2249
|
+
"dodgerblue",
|
|
2250
|
+
"firebrick",
|
|
2251
|
+
"floralwhite",
|
|
2252
|
+
"forestgreen",
|
|
2253
|
+
"fuchsia",
|
|
2254
|
+
"gainsboro",
|
|
2255
|
+
"ghostwhite",
|
|
2256
|
+
"gold",
|
|
2257
|
+
"goldenrod",
|
|
2258
|
+
"gray",
|
|
2259
|
+
"green",
|
|
2260
|
+
"greenyellow",
|
|
2261
|
+
"grey",
|
|
2262
|
+
"honeydew",
|
|
2263
|
+
"hotpink",
|
|
2264
|
+
"indianred",
|
|
2265
|
+
"indigo",
|
|
2266
|
+
"inherit",
|
|
2267
|
+
"ivory",
|
|
2268
|
+
"khaki",
|
|
2269
|
+
"lavender",
|
|
2270
|
+
"lavenderblush",
|
|
2271
|
+
"lawngreen",
|
|
2272
|
+
"lemonchiffon",
|
|
2273
|
+
"lightblue",
|
|
2274
|
+
"lightcoral",
|
|
2275
|
+
"lightcyan",
|
|
2276
|
+
"lightgoldenrodyellow",
|
|
2277
|
+
"lightgray",
|
|
2278
|
+
"lightgreen",
|
|
2279
|
+
"lightgrey",
|
|
2280
|
+
"lightpink",
|
|
2281
|
+
"lightsalmon",
|
|
2282
|
+
"lightseagreen",
|
|
2283
|
+
"lightskyblue",
|
|
2284
|
+
"lightslategray",
|
|
2285
|
+
"lightslategrey",
|
|
2286
|
+
"lightsteelblue",
|
|
2287
|
+
"lightyellow",
|
|
2288
|
+
"lime",
|
|
2289
|
+
"limegreen",
|
|
2290
|
+
"linen",
|
|
2291
|
+
"magenta",
|
|
2292
|
+
"maroon",
|
|
2293
|
+
"mediumaquamarine",
|
|
2294
|
+
"mediumblue",
|
|
2295
|
+
"mediumorchid",
|
|
2296
|
+
"mediumpurple",
|
|
2297
|
+
"mediumseagreen",
|
|
2298
|
+
"mediumslateblue",
|
|
2299
|
+
"mediumspringgreen",
|
|
2300
|
+
"mediumturquoise",
|
|
2301
|
+
"mediumvioletred",
|
|
2302
|
+
"midnightblue",
|
|
2303
|
+
"mintcream",
|
|
2304
|
+
"mistyrose",
|
|
2305
|
+
"moccasin",
|
|
2306
|
+
"navajowhite",
|
|
2307
|
+
"navy",
|
|
2308
|
+
"oldlace",
|
|
2309
|
+
"olive",
|
|
2310
|
+
"olivedrab",
|
|
2311
|
+
"orange",
|
|
2312
|
+
"orangered",
|
|
2313
|
+
"orchid",
|
|
2314
|
+
"palegoldenrod",
|
|
2315
|
+
"palegreen",
|
|
2316
|
+
"paleturquoise",
|
|
2317
|
+
"palevioletred",
|
|
2318
|
+
"papayawhip",
|
|
2319
|
+
"peachpuff",
|
|
2320
|
+
"peru",
|
|
2321
|
+
"pink",
|
|
2322
|
+
"plum",
|
|
2323
|
+
"powderblue",
|
|
2324
|
+
"purple",
|
|
2325
|
+
"rebeccapurple",
|
|
2326
|
+
"red",
|
|
2327
|
+
"rosybrown",
|
|
2328
|
+
"royalblue",
|
|
2329
|
+
"saddlebrown",
|
|
2330
|
+
"salmon",
|
|
2331
|
+
"sandybrown",
|
|
2332
|
+
"seagreen",
|
|
2333
|
+
"seashell",
|
|
2334
|
+
"sienna",
|
|
2335
|
+
"silver",
|
|
2336
|
+
"skyblue",
|
|
2337
|
+
"slateblue",
|
|
2338
|
+
"slategray",
|
|
2339
|
+
"slategrey",
|
|
2340
|
+
"snow",
|
|
2341
|
+
"springgreen",
|
|
2342
|
+
"steelblue",
|
|
2343
|
+
"tan",
|
|
2344
|
+
"teal",
|
|
2345
|
+
"thistle",
|
|
2346
|
+
"tomato",
|
|
2347
|
+
"transparent",
|
|
2348
|
+
"turquoise",
|
|
2349
|
+
"violet",
|
|
2350
|
+
"wheat",
|
|
2351
|
+
"white",
|
|
2352
|
+
"whitesmoke",
|
|
2353
|
+
"yellow",
|
|
2354
|
+
"yellowgreen"
|
|
2355
|
+
]);
|
|
2356
|
+
|
|
2357
|
+
// src/components/editor/ColorField.tsx
|
|
2358
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
2359
|
+
var PRESETS = [
|
|
2360
|
+
"#000000",
|
|
2361
|
+
"#374151",
|
|
2362
|
+
"#6b7280",
|
|
2363
|
+
"#d1d5db",
|
|
2364
|
+
"#f3f4f6",
|
|
2365
|
+
"#ffffff",
|
|
2366
|
+
"#dc2626",
|
|
2367
|
+
"#ea580c",
|
|
2368
|
+
"#ca8a04",
|
|
2369
|
+
"#16a34a",
|
|
2370
|
+
"#0284c7",
|
|
2371
|
+
"#4f46e5",
|
|
2372
|
+
"#9333ea",
|
|
2373
|
+
"#db2777"
|
|
2374
|
+
];
|
|
2375
|
+
var eyeDropper = () => globalThis.EyeDropper;
|
|
2376
|
+
function ColorField({
|
|
2377
|
+
value,
|
|
2378
|
+
placeholder,
|
|
2379
|
+
documentColors: documentColors2,
|
|
2380
|
+
labels,
|
|
2381
|
+
onChange
|
|
2382
|
+
}) {
|
|
2383
|
+
const field = useFieldLabel();
|
|
2384
|
+
const [text, setText] = (0, import_react6.useState)(value);
|
|
2385
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
2386
|
+
const [used, setUsed] = (0, import_react6.useState)([]);
|
|
2387
|
+
const pressed = (0, import_react6.useRef)(false);
|
|
2388
|
+
const dragged = (0, import_react6.useRef)(null);
|
|
2389
|
+
const fieldRef = (0, import_react6.useRef)(null);
|
|
2390
|
+
const anchor = (0, import_react6.useRef)({
|
|
2391
|
+
getBoundingClientRect: () => {
|
|
2392
|
+
const field2 = fieldRef.current?.getBoundingClientRect() ?? new DOMRect();
|
|
2393
|
+
const panel = fieldRef.current?.closest(".editor-dark")?.getBoundingClientRect();
|
|
2394
|
+
return new DOMRect(panel?.right ?? field2.right, field2.top, 0, field2.height);
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
(0, import_react6.useEffect)(() => setText(value), [value]);
|
|
2398
|
+
const latestOnChange = (0, import_react6.useRef)(onChange);
|
|
2399
|
+
latestOnChange.current = onChange;
|
|
2400
|
+
const release = (0, import_react6.useCallback)(() => {
|
|
2401
|
+
pressed.current = false;
|
|
2402
|
+
if (dragged.current !== null) latestOnChange.current(dragged.current, false);
|
|
2403
|
+
dragged.current = null;
|
|
2404
|
+
}, []);
|
|
2405
|
+
(0, import_react6.useEffect)(() => {
|
|
2406
|
+
document.addEventListener("lostpointercapture", release);
|
|
2407
|
+
return () => {
|
|
2408
|
+
document.removeEventListener("lostpointercapture", release);
|
|
2409
|
+
release();
|
|
2410
|
+
};
|
|
2411
|
+
}, [release]);
|
|
2412
|
+
const hex = toHex(value) ?? toHex(placeholder ?? "") ?? "#000000";
|
|
2413
|
+
const translucent = hex.length === 9;
|
|
2414
|
+
const shown = value || placeholder || "";
|
|
2415
|
+
const commit = (next) => {
|
|
2416
|
+
dragged.current = null;
|
|
2417
|
+
const color = mjmlColor(next);
|
|
2418
|
+
if (color === null) return setText(value);
|
|
2419
|
+
setText(color);
|
|
2420
|
+
onChange(color, false);
|
|
2421
|
+
};
|
|
2422
|
+
const drag = (picked) => {
|
|
2423
|
+
const next = mjmlColor(picked) ?? picked;
|
|
2424
|
+
setText(next);
|
|
2425
|
+
if (!pressed.current) return commit(next);
|
|
2426
|
+
dragged.current = next;
|
|
2427
|
+
onChange(next, true);
|
|
2428
|
+
};
|
|
2429
|
+
const pickFromScreen = async () => {
|
|
2430
|
+
const Ctor = eyeDropper();
|
|
2431
|
+
if (!Ctor) return;
|
|
2432
|
+
try {
|
|
2433
|
+
const { sRGBHex } = await new Ctor().open();
|
|
2434
|
+
commit(sRGBHex);
|
|
2435
|
+
} catch {
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2439
|
+
Popover,
|
|
2440
|
+
{
|
|
2441
|
+
open,
|
|
2442
|
+
onOpenChange: (next) => {
|
|
2443
|
+
if (next) setUsed(documentColors2());
|
|
2444
|
+
else if (text.trim() !== value) commit(text.trim());
|
|
2445
|
+
setOpen(next);
|
|
2446
|
+
},
|
|
2447
|
+
children: [
|
|
2448
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverAnchor, { virtualRef: anchor }),
|
|
2449
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2450
|
+
"div",
|
|
2451
|
+
{
|
|
2452
|
+
ref: fieldRef,
|
|
2453
|
+
className: "flex h-8 w-full items-center gap-1.5 rounded-md border border-panel-border bg-panel-elevated pr-1.5 pl-2 focus-within:border-brand",
|
|
2454
|
+
children: [
|
|
2455
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2456
|
+
"input",
|
|
2457
|
+
{
|
|
2458
|
+
value: text,
|
|
2459
|
+
placeholder,
|
|
2460
|
+
spellCheck: false,
|
|
2461
|
+
...field,
|
|
2462
|
+
onChange: (e) => setText(e.target.value),
|
|
2463
|
+
onBlur: () => text !== value && commit(text.trim()),
|
|
2464
|
+
onKeyDown: (e) => {
|
|
2465
|
+
if (e.key === "Enter") commit(text.trim());
|
|
2466
|
+
if (e.key === "Escape") setText(value);
|
|
2467
|
+
},
|
|
2468
|
+
className: "min-w-0 flex-1 bg-transparent font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg"
|
|
2469
|
+
}
|
|
2470
|
+
),
|
|
2471
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2472
|
+
"button",
|
|
2473
|
+
{
|
|
2474
|
+
type: "button",
|
|
2475
|
+
title: labels.colorPickerOpen,
|
|
2476
|
+
"aria-label": labels.colorPickerOpen,
|
|
2477
|
+
className: "relative size-5 shrink-0 overflow-hidden rounded border border-white/20 outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
2478
|
+
children: [
|
|
2479
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Checkerboard, {}),
|
|
2480
|
+
mjmlColor(shown) && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "absolute inset-0", style: { background: shown } })
|
|
2481
|
+
]
|
|
2482
|
+
}
|
|
2483
|
+
) })
|
|
2484
|
+
]
|
|
2485
|
+
}
|
|
2486
|
+
),
|
|
2487
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2488
|
+
PopoverContent,
|
|
2489
|
+
{
|
|
2490
|
+
side: "right",
|
|
2491
|
+
align: "start",
|
|
2492
|
+
sideOffset: 8,
|
|
2493
|
+
className: "editor-dark flex w-52 flex-col gap-2.5 border border-panel-border bg-panel p-2.5 text-panel-fg",
|
|
2494
|
+
children: [
|
|
2495
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2496
|
+
"div",
|
|
1157
2497
|
{
|
|
1158
2498
|
onPointerDownCapture: (e) => {
|
|
1159
2499
|
pressed.current = true;
|
|
1160
2500
|
e.target.setPointerCapture(e.pointerId);
|
|
1161
2501
|
},
|
|
1162
|
-
children: translucent ? /* @__PURE__ */ (0,
|
|
2502
|
+
children: translucent ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_colorful.HexAlphaColorPicker, { color: hex, onChange: drag, className: "color-field-picker" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_colorful.HexColorPicker, { color: hex, onChange: drag, className: "color-field-picker" })
|
|
1163
2503
|
}
|
|
1164
2504
|
),
|
|
1165
|
-
/* @__PURE__ */ (0,
|
|
1166
|
-
/* @__PURE__ */ (0,
|
|
2505
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
2506
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1167
2507
|
"span",
|
|
1168
2508
|
{
|
|
1169
2509
|
className: "size-6 shrink-0 rounded border border-white/20",
|
|
1170
2510
|
style: { background: hex }
|
|
1171
2511
|
}
|
|
1172
2512
|
),
|
|
1173
|
-
/* @__PURE__ */ (0,
|
|
2513
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1174
2514
|
"input",
|
|
1175
2515
|
{
|
|
1176
2516
|
value: text,
|
|
1177
2517
|
spellCheck: false,
|
|
2518
|
+
...field,
|
|
1178
2519
|
onChange: (e) => setText(e.target.value),
|
|
1179
2520
|
onKeyDown: (e) => e.key === "Enter" && commit(text.trim()),
|
|
1180
2521
|
onBlur: () => text !== value && commit(text.trim()),
|
|
1181
2522
|
className: "h-6 min-w-0 flex-1 rounded border border-panel-border bg-panel-elevated px-1.5 font-mono text-[11px] outline-none focus:border-brand"
|
|
1182
2523
|
}
|
|
1183
2524
|
),
|
|
1184
|
-
eyeDropper() && /* @__PURE__ */ (0,
|
|
2525
|
+
eyeDropper() && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1185
2526
|
"button",
|
|
1186
2527
|
{
|
|
1187
2528
|
type: "button",
|
|
@@ -1189,11 +2530,11 @@ function ColorField({
|
|
|
1189
2530
|
title: labels.colorPickerEyedropper,
|
|
1190
2531
|
"aria-label": labels.colorPickerEyedropper,
|
|
1191
2532
|
className: "flex size-6 shrink-0 items-center justify-center rounded border border-panel-border bg-panel-elevated text-panel-muted-fg hover:text-panel-fg",
|
|
1192
|
-
children: /* @__PURE__ */ (0,
|
|
2533
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react6.Pipette, { className: "size-3" })
|
|
1193
2534
|
}
|
|
1194
2535
|
)
|
|
1195
2536
|
] }),
|
|
1196
|
-
used.length > 0 && /* @__PURE__ */ (0,
|
|
2537
|
+
used.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1197
2538
|
Swatches,
|
|
1198
2539
|
{
|
|
1199
2540
|
title: labels.colorPickerDocument,
|
|
@@ -1202,7 +2543,7 @@ function ColorField({
|
|
|
1202
2543
|
onPick: commit
|
|
1203
2544
|
}
|
|
1204
2545
|
),
|
|
1205
|
-
/* @__PURE__ */ (0,
|
|
2546
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1206
2547
|
Swatches,
|
|
1207
2548
|
{
|
|
1208
2549
|
title: labels.colorPickerPresets,
|
|
@@ -1226,10 +2567,10 @@ function Swatches({
|
|
|
1226
2567
|
onPick,
|
|
1227
2568
|
transparentLabel
|
|
1228
2569
|
}) {
|
|
1229
|
-
return /* @__PURE__ */ (0,
|
|
1230
|
-
/* @__PURE__ */ (0,
|
|
1231
|
-
/* @__PURE__ */ (0,
|
|
1232
|
-
colors.map((color) => /* @__PURE__ */ (0,
|
|
2570
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
2571
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "mb-1 text-[10px] font-medium tracking-wide text-panel-muted-fg uppercase", children: title }),
|
|
2572
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "grid grid-cols-8 gap-1", children: [
|
|
2573
|
+
colors.map((color) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1233
2574
|
"button",
|
|
1234
2575
|
{
|
|
1235
2576
|
type: "button",
|
|
@@ -1243,134 +2584,1630 @@ function Swatches({
|
|
|
1243
2584
|
},
|
|
1244
2585
|
color
|
|
1245
2586
|
)),
|
|
1246
|
-
transparentLabel && /* @__PURE__ */ (0,
|
|
2587
|
+
transparentLabel && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2588
|
+
"button",
|
|
2589
|
+
{
|
|
2590
|
+
type: "button",
|
|
2591
|
+
title: transparentLabel,
|
|
2592
|
+
onClick: () => onPick("transparent"),
|
|
2593
|
+
className: "relative aspect-square overflow-hidden rounded border border-white/15 outline-none hover:scale-110 focus-visible:ring-2 focus-visible:ring-brand",
|
|
2594
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Checkerboard, {})
|
|
2595
|
+
}
|
|
2596
|
+
)
|
|
2597
|
+
] })
|
|
2598
|
+
] });
|
|
2599
|
+
}
|
|
2600
|
+
function Checkerboard() {
|
|
2601
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2602
|
+
"span",
|
|
2603
|
+
{
|
|
2604
|
+
className: "absolute inset-0",
|
|
2605
|
+
style: {
|
|
2606
|
+
backgroundImage: "repeating-conic-gradient(#9ca3af 0% 25%, #e5e7eb 0% 50%)",
|
|
2607
|
+
backgroundSize: "8px 8px"
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
function mjmlColor(value) {
|
|
2613
|
+
const v = value.trim();
|
|
2614
|
+
if (v === "") return "";
|
|
2615
|
+
if (NAMED_COLORS.has(v.toLowerCase())) return v.toLowerCase();
|
|
2616
|
+
const hex = toHex(v);
|
|
2617
|
+
if (!hex) return null;
|
|
2618
|
+
if (hex.length === 7) return hex;
|
|
2619
|
+
const [r, g, b, a] = [1, 3, 5, 7].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
2620
|
+
return `rgba(${r},${g},${b},${Number((a / 255).toFixed(2))})`;
|
|
2621
|
+
}
|
|
2622
|
+
function toHex(value, { opaque = false } = {}) {
|
|
2623
|
+
const v = value.trim().toLowerCase();
|
|
2624
|
+
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(v);
|
|
2625
|
+
if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;
|
|
2626
|
+
if (/^#[0-9a-f]{6}$/.test(v)) return v;
|
|
2627
|
+
if (/^#[0-9a-f]{8}$/.test(v)) return opaque || v.endsWith("ff") ? v.slice(0, 7) : v;
|
|
2628
|
+
const rgb = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)(?:[\s,/]+([\d.]+%?))?/.exec(v);
|
|
2629
|
+
if (rgb) {
|
|
2630
|
+
const channels = [rgb[1], rgb[2], rgb[3]].map((n) => Math.min(255, Number(n)));
|
|
2631
|
+
const alphaText = rgb[4];
|
|
2632
|
+
const alpha = alphaText?.endsWith("%") ? parseFloat(alphaText) / 100 : Number(alphaText ?? 1);
|
|
2633
|
+
if (!opaque && alpha < 1) channels.push(Math.round(Math.max(0, alpha) * 255));
|
|
2634
|
+
return `#${channels.map((n) => n.toString(16).padStart(2, "0")).join("")}`;
|
|
2635
|
+
}
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// src/components/editor/ImageField.tsx
|
|
2640
|
+
var import_lucide_react7 = require("lucide-react");
|
|
2641
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2642
|
+
function ImageField({
|
|
2643
|
+
value,
|
|
2644
|
+
labels,
|
|
2645
|
+
onChoose,
|
|
2646
|
+
onClear
|
|
2647
|
+
}) {
|
|
2648
|
+
const url = imageUrl(value);
|
|
2649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "relative", children: [
|
|
2650
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
2651
|
+
"button",
|
|
2652
|
+
{
|
|
2653
|
+
type: "button",
|
|
2654
|
+
onClick: onChoose,
|
|
2655
|
+
title: labels.chooseImage,
|
|
2656
|
+
className: "group relative block w-full overflow-hidden rounded-md border border-panel-border bg-panel-elevated outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
2657
|
+
children: [
|
|
2658
|
+
url ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("img", { src: url, alt: "", className: "h-24 w-full bg-black/25 object-contain" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "flex h-16 flex-col items-center justify-center gap-1 text-xs text-panel-muted-fg", children: [
|
|
2659
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react7.ImageIcon, { className: "size-5" }),
|
|
2660
|
+
labels.chooseImage
|
|
2661
|
+
] }),
|
|
2662
|
+
url && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 text-xs font-medium text-white opacity-0 transition group-hover:opacity-100", children: labels.chooseImage })
|
|
2663
|
+
]
|
|
2664
|
+
}
|
|
2665
|
+
),
|
|
2666
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2667
|
+
"button",
|
|
2668
|
+
{
|
|
2669
|
+
type: "button",
|
|
2670
|
+
onClick: onClear,
|
|
2671
|
+
title: labels.imageFieldRemove,
|
|
2672
|
+
"aria-label": labels.imageFieldRemove,
|
|
2673
|
+
className: "absolute top-1 right-1 flex size-5 items-center justify-center rounded bg-black/60 text-white hover:bg-black/80",
|
|
2674
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react7.X, { className: "size-3" })
|
|
2675
|
+
}
|
|
2676
|
+
)
|
|
2677
|
+
] });
|
|
2678
|
+
}
|
|
2679
|
+
function imageUrl(value) {
|
|
2680
|
+
const v = value.trim();
|
|
2681
|
+
if (!v || v === "none") return null;
|
|
2682
|
+
const css = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(v);
|
|
2683
|
+
if (css) return css[2] || null;
|
|
2684
|
+
return /^[a-z-]+\(/i.test(v) ? null : v;
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// src/components/editor/inspector/schema.ts
|
|
2688
|
+
var PX = ["px"];
|
|
2689
|
+
var PX_PCT = ["px", "%"];
|
|
2690
|
+
var LINE_HEIGHT = ["", "px", "%"];
|
|
2691
|
+
var LETTER_SPACING = ["px", "em"];
|
|
2692
|
+
var ALIGN = [
|
|
2693
|
+
{ value: "left", label: "alignLeft" },
|
|
2694
|
+
{ value: "center", label: "alignCenter" },
|
|
2695
|
+
{ value: "right", label: "alignRight" }
|
|
2696
|
+
];
|
|
2697
|
+
var VERTICAL_ALIGN = [
|
|
2698
|
+
{ value: "top", label: "alignTop" },
|
|
2699
|
+
{ value: "middle", label: "alignMiddle" },
|
|
2700
|
+
{ value: "bottom", label: "alignBottom" }
|
|
2701
|
+
];
|
|
2702
|
+
var FONTS = [
|
|
2703
|
+
{ value: "Ubuntu, Helvetica, Arial, sans-serif", label: "Ubuntu" },
|
|
2704
|
+
{ value: "Arial, Helvetica, sans-serif", label: "Arial" },
|
|
2705
|
+
{ value: "Helvetica, Arial, sans-serif", label: "Helvetica" },
|
|
2706
|
+
{ value: "Verdana, Geneva, sans-serif", label: "Verdana" },
|
|
2707
|
+
{ value: "Tahoma, Geneva, sans-serif", label: "Tahoma" },
|
|
2708
|
+
{ value: "'Trebuchet MS', Helvetica, sans-serif", label: "Trebuchet MS" },
|
|
2709
|
+
{ value: "Georgia, 'Times New Roman', serif", label: "Georgia" },
|
|
2710
|
+
{ value: "'Times New Roman', Times, serif", label: "Times New Roman" },
|
|
2711
|
+
{ value: "'Courier New', Courier, monospace", label: "Courier New" }
|
|
2712
|
+
];
|
|
2713
|
+
var WEIGHTS = [
|
|
2714
|
+
{ value: "normal", label: "weightRegular" },
|
|
2715
|
+
{ value: "500", label: "weightMedium" },
|
|
2716
|
+
{ value: "600", label: "weightSemibold" },
|
|
2717
|
+
{ value: "bold", label: "weightBold" }
|
|
2718
|
+
];
|
|
2719
|
+
var TEXT_TRANSFORM = [
|
|
2720
|
+
{ value: "none", label: "caseNone" },
|
|
2721
|
+
{ value: "uppercase", label: "caseUpper" },
|
|
2722
|
+
{ value: "lowercase", label: "caseLower" },
|
|
2723
|
+
{ value: "capitalize", label: "caseCapitalize" }
|
|
2724
|
+
];
|
|
2725
|
+
var TARGET = [
|
|
2726
|
+
{ value: "_blank", label: "targetNewTab" },
|
|
2727
|
+
{ value: "_self", label: "targetSameTab" }
|
|
2728
|
+
];
|
|
2729
|
+
var BACKGROUND_POSITION = [
|
|
2730
|
+
{ value: "top left", label: "positionTopLeft" },
|
|
2731
|
+
{ value: "top center", label: "positionTopCenter" },
|
|
2732
|
+
{ value: "top right", label: "positionTopRight" },
|
|
2733
|
+
{ value: "center left", label: "positionCenterLeft" },
|
|
2734
|
+
{ value: "center center", label: "positionCenter" },
|
|
2735
|
+
{ value: "center right", label: "positionCenterRight" },
|
|
2736
|
+
{ value: "bottom left", label: "positionBottomLeft" },
|
|
2737
|
+
{ value: "bottom center", label: "positionBottomCenter" },
|
|
2738
|
+
{ value: "bottom right", label: "positionBottomRight" }
|
|
2739
|
+
];
|
|
2740
|
+
var NETWORKS = [
|
|
2741
|
+
{ value: "facebook", label: "Facebook" },
|
|
2742
|
+
{ value: "instagram", label: "Instagram" },
|
|
2743
|
+
{ value: "x", label: "X" },
|
|
2744
|
+
{ value: "linkedin", label: "LinkedIn" },
|
|
2745
|
+
{ value: "youtube", label: "YouTube" },
|
|
2746
|
+
{ value: "pinterest", label: "Pinterest" },
|
|
2747
|
+
{ value: "github", label: "GitHub" },
|
|
2748
|
+
{ value: "web", label: "networkWebsite" },
|
|
2749
|
+
{ value: "snapchat", label: "Snapchat" },
|
|
2750
|
+
{ value: "vimeo", label: "Vimeo" },
|
|
2751
|
+
{ value: "medium", label: "Medium" },
|
|
2752
|
+
{ value: "dribbble", label: "Dribbble" },
|
|
2753
|
+
{ value: "soundcloud", label: "SoundCloud" },
|
|
2754
|
+
{ value: "tumblr", label: "Tumblr" },
|
|
2755
|
+
{ value: "xing", label: "Xing" },
|
|
2756
|
+
{ value: "custom", label: "networkCustom" }
|
|
2757
|
+
];
|
|
2758
|
+
var hasBackgroundImage = (a) => !!a["background-url"];
|
|
2759
|
+
var SECTION_FIELDS = [
|
|
2760
|
+
{
|
|
2761
|
+
attr: "full-width",
|
|
2762
|
+
label: "fullWidth",
|
|
2763
|
+
group: "layout",
|
|
2764
|
+
control: { type: "toggle", on: "full-width" }
|
|
2765
|
+
},
|
|
2766
|
+
{
|
|
2767
|
+
attr: "padding",
|
|
2768
|
+
label: "padding",
|
|
2769
|
+
group: "spacing",
|
|
2770
|
+
control: { type: "box", units: PX_PCT },
|
|
2771
|
+
default: "20px 0"
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
attr: "background-color",
|
|
2775
|
+
label: "backgroundColor",
|
|
2776
|
+
group: "style",
|
|
2777
|
+
control: { type: "color" }
|
|
2778
|
+
},
|
|
2779
|
+
{ attr: "background-url", label: "backgroundImage", group: "style", control: { type: "image" } },
|
|
2780
|
+
{
|
|
2781
|
+
attr: "background-size",
|
|
2782
|
+
label: "backgroundSize",
|
|
2783
|
+
group: "style",
|
|
2784
|
+
control: {
|
|
2785
|
+
type: "segmented",
|
|
2786
|
+
options: [
|
|
2787
|
+
{ value: "cover", label: "sizeCover" },
|
|
2788
|
+
{ value: "contain", label: "sizeContain" },
|
|
2789
|
+
{ value: "auto", label: "sizeAuto" }
|
|
2790
|
+
]
|
|
2791
|
+
},
|
|
2792
|
+
default: "auto",
|
|
2793
|
+
when: hasBackgroundImage
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
attr: "background-repeat",
|
|
2797
|
+
label: "backgroundRepeat",
|
|
2798
|
+
group: "style",
|
|
2799
|
+
control: {
|
|
2800
|
+
type: "segmented",
|
|
2801
|
+
options: [
|
|
2802
|
+
{ value: "repeat", label: "repeatTile" },
|
|
2803
|
+
{ value: "no-repeat", label: "repeatOnce" }
|
|
2804
|
+
]
|
|
2805
|
+
},
|
|
2806
|
+
default: "repeat",
|
|
2807
|
+
when: hasBackgroundImage
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
attr: "background-position",
|
|
2811
|
+
label: "backgroundPosition",
|
|
2812
|
+
group: "style",
|
|
2813
|
+
control: { type: "select", options: BACKGROUND_POSITION },
|
|
2814
|
+
default: "top center",
|
|
2815
|
+
when: hasBackgroundImage
|
|
2816
|
+
},
|
|
2817
|
+
{
|
|
2818
|
+
attr: "border-radius",
|
|
2819
|
+
label: "cornerRadius",
|
|
2820
|
+
group: "style",
|
|
2821
|
+
control: { type: "length", units: PX_PCT }
|
|
2822
|
+
}
|
|
2823
|
+
];
|
|
2824
|
+
var SCHEMA = {
|
|
2825
|
+
"mj-body": [
|
|
2826
|
+
{ attr: "width", label: "contentWidth", group: "layout", control: { type: "length", units: PX }, default: "600px" },
|
|
2827
|
+
{ attr: "background-color", label: "pageBackground", group: "style", control: { type: "color" } }
|
|
2828
|
+
],
|
|
2829
|
+
"mj-wrapper": [
|
|
2830
|
+
...SECTION_FIELDS,
|
|
2831
|
+
{ attr: "gap", label: "gap", group: "advanced", control: { type: "length", units: PX } },
|
|
2832
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
2833
|
+
],
|
|
2834
|
+
"mj-section": [
|
|
2835
|
+
...SECTION_FIELDS,
|
|
2836
|
+
{ attr: "text-align", label: "alignColumns", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2837
|
+
{ attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } },
|
|
2838
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
2839
|
+
],
|
|
2840
|
+
"mj-column": [
|
|
2841
|
+
{ attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
|
|
2842
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
2843
|
+
{ attr: "background-color", label: "backgroundColor", group: "style", control: { type: "color" } },
|
|
2844
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
2845
|
+
{ attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
2846
|
+
{ attr: "inner-background-color", label: "innerBackground", group: "advanced", control: { type: "color" } },
|
|
2847
|
+
{ attr: "inner-border-radius", label: "innerCornerRadius", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
2848
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } },
|
|
2849
|
+
{ attr: "inner-border", label: "innerBorder", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
2850
|
+
],
|
|
2851
|
+
"mj-text": [
|
|
2852
|
+
{
|
|
2853
|
+
attr: "align",
|
|
2854
|
+
label: "align",
|
|
2855
|
+
group: "layout",
|
|
2856
|
+
control: { type: "segmented", options: [...ALIGN, { value: "justify", label: "alignJustify" }] },
|
|
2857
|
+
default: "left"
|
|
2858
|
+
},
|
|
2859
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2860
|
+
{ attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
2861
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
2862
|
+
{ attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS } },
|
|
2863
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
2864
|
+
{ attr: "line-height", label: "lineHeight", group: "style", control: { type: "length", units: LINE_HEIGHT }, default: "1" },
|
|
2865
|
+
{ attr: "container-background-color", label: "blockBackground", group: "style", control: { type: "color" } },
|
|
2866
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
2867
|
+
{ attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM } }
|
|
2868
|
+
],
|
|
2869
|
+
"mj-button": [
|
|
2870
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2871
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2872
|
+
{ attr: "inner-padding", label: "buttonPadding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2873
|
+
{ attr: "padding", label: "spaceAround", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2874
|
+
{ attr: "background-color", label: "buttonColor", group: "style", control: { type: "color" }, default: "#414141" },
|
|
2875
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#ffffff" },
|
|
2876
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
2877
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
|
|
2878
|
+
{ attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
2879
|
+
{ attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
2880
|
+
{ attr: "font-weight", label: "fontWeight", group: "advanced", control: { type: "select", options: WEIGHTS }, default: "normal" },
|
|
2881
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2882
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
2883
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
2884
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } },
|
|
2885
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "none" },
|
|
2886
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
2887
|
+
{ attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM }, default: "none" },
|
|
2888
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "120%" }
|
|
2889
|
+
],
|
|
2890
|
+
"mj-image": [
|
|
2891
|
+
{ attr: "src", label: "image", group: "content", control: { type: "image" } },
|
|
2892
|
+
{ attr: "alt", label: "altText", group: "content", control: { type: "text" } },
|
|
2893
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2894
|
+
{ attr: "width", label: "width", group: "layout", control: { type: "length", units: PX } },
|
|
2895
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2896
|
+
{ attr: "fluid-on-mobile", label: "fluidOnMobile", group: "layout", control: { type: "toggle", on: "true" } },
|
|
2897
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2898
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
2899
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2900
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
2901
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
2902
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "0" },
|
|
2903
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2904
|
+
],
|
|
2905
|
+
"mj-divider": [
|
|
2906
|
+
{ attr: "width", label: "width", group: "layout", control: { type: "length", units: PX_PCT }, default: "100%" },
|
|
2907
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2908
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2909
|
+
{ attr: "border-color", label: "lineColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
2910
|
+
{ attr: "border-width", label: "thickness", group: "style", control: { type: "length", units: PX }, default: "4px" },
|
|
2911
|
+
{
|
|
2912
|
+
attr: "border-style",
|
|
2913
|
+
label: "lineStyle",
|
|
2914
|
+
group: "style",
|
|
2915
|
+
control: {
|
|
2916
|
+
type: "segmented",
|
|
2917
|
+
options: [
|
|
2918
|
+
{ value: "solid", label: "lineSolid" },
|
|
2919
|
+
{ value: "dashed", label: "lineDashed" },
|
|
2920
|
+
{ value: "dotted", label: "lineDotted" }
|
|
2921
|
+
]
|
|
2922
|
+
},
|
|
2923
|
+
default: "solid"
|
|
2924
|
+
},
|
|
2925
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2926
|
+
],
|
|
2927
|
+
"mj-spacer": [
|
|
2928
|
+
{ attr: "height", label: "height", group: "layout", control: { type: "length", units: PX }, default: "20px" },
|
|
2929
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2930
|
+
],
|
|
2931
|
+
"mj-social": [
|
|
2932
|
+
{
|
|
2933
|
+
attr: "mode",
|
|
2934
|
+
label: "socialMode",
|
|
2935
|
+
group: "layout",
|
|
2936
|
+
control: {
|
|
2937
|
+
type: "segmented",
|
|
2938
|
+
options: [
|
|
2939
|
+
{ value: "horizontal", label: "modeHorizontal" },
|
|
2940
|
+
{ value: "vertical", label: "modeVertical" }
|
|
2941
|
+
]
|
|
2942
|
+
},
|
|
2943
|
+
default: "horizontal"
|
|
2944
|
+
},
|
|
2945
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2946
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2947
|
+
{ attr: "inner-padding", label: "iconSpacing", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
2948
|
+
{ attr: "icon-size", label: "iconSize", group: "style", control: { type: "length", units: PX_PCT }, default: "20px" },
|
|
2949
|
+
// A single value: mj-social's border-radius is not a 4-side shorthand.
|
|
2950
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
|
|
2951
|
+
{ attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
2952
|
+
{ attr: "font-size", label: "fontSize", group: "advanced", control: { type: "length", units: PX }, default: "13px" },
|
|
2953
|
+
{ attr: "color", label: "textColor", group: "advanced", control: { type: "color" }, default: "#333333" },
|
|
2954
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" },
|
|
2955
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2956
|
+
],
|
|
2957
|
+
"mj-social-element": [
|
|
2958
|
+
{ attr: "name", label: "network", group: "content", control: { type: "network", networks: NETWORKS } },
|
|
2959
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2960
|
+
{
|
|
2961
|
+
attr: "src",
|
|
2962
|
+
label: "customIcon",
|
|
2963
|
+
group: "content",
|
|
2964
|
+
control: { type: "image" },
|
|
2965
|
+
when: (a) => (a.name ?? "").startsWith("custom") || !!a.src
|
|
2966
|
+
},
|
|
2967
|
+
{ attr: "alt", label: "altText", group: "content", control: { type: "text" } },
|
|
2968
|
+
{ attr: "background-color", label: "iconColor", group: "style", control: { type: "color" } },
|
|
2969
|
+
{ attr: "icon-size", label: "iconSize", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
2970
|
+
{ attr: "border-radius", label: "cornerRadius", group: "advanced", control: { type: "length", units: PX }, default: "3px" },
|
|
2971
|
+
{ attr: "padding", label: "padding", group: "advanced", control: { type: "box", units: PX_PCT }, default: "4px" },
|
|
2972
|
+
{ attr: "icon-position", label: "iconPosition", group: "advanced", control: { type: "segmented", options: [{ value: "left", label: "alignLeft" }, { value: "right", label: "alignRight" }] }, default: "left" },
|
|
2973
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
2974
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2975
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } }
|
|
2976
|
+
],
|
|
2977
|
+
"mj-hero": [
|
|
2978
|
+
{ attr: "background-url", label: "backgroundImage", group: "content", control: { type: "image" } },
|
|
2979
|
+
{
|
|
2980
|
+
attr: "mode",
|
|
2981
|
+
label: "heroMode",
|
|
2982
|
+
group: "content",
|
|
2983
|
+
control: {
|
|
2984
|
+
type: "segmented",
|
|
2985
|
+
options: [
|
|
2986
|
+
{ value: "fixed-height", label: "heroFixed" },
|
|
2987
|
+
{ value: "fluid-height", label: "heroFluid" }
|
|
2988
|
+
]
|
|
2989
|
+
},
|
|
2990
|
+
default: "fixed-height"
|
|
2991
|
+
},
|
|
2992
|
+
{
|
|
2993
|
+
attr: "height",
|
|
2994
|
+
label: "height",
|
|
2995
|
+
group: "layout",
|
|
2996
|
+
control: { type: "length", units: PX_PCT },
|
|
2997
|
+
default: "0px",
|
|
2998
|
+
when: (a) => (a.mode ?? "fixed-height") === "fixed-height"
|
|
2999
|
+
},
|
|
3000
|
+
{
|
|
3001
|
+
attr: "background-width",
|
|
3002
|
+
label: "backgroundWidth",
|
|
3003
|
+
group: "layout",
|
|
3004
|
+
control: { type: "length", units: PX_PCT },
|
|
3005
|
+
when: (a) => a.mode === "fluid-height"
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
attr: "background-height",
|
|
3009
|
+
label: "backgroundHeight",
|
|
3010
|
+
group: "layout",
|
|
3011
|
+
control: { type: "length", units: PX_PCT },
|
|
3012
|
+
when: (a) => a.mode === "fluid-height"
|
|
3013
|
+
},
|
|
3014
|
+
{ attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
|
|
3015
|
+
{ attr: "background-position", label: "backgroundPosition", group: "layout", control: { type: "select", options: BACKGROUND_POSITION }, default: "center center" },
|
|
3016
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "0px" },
|
|
3017
|
+
{ attr: "background-color", label: "fallbackColor", group: "style", control: { type: "color" }, default: "#ffffff" },
|
|
3018
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
3019
|
+
{ attr: "inner-padding", label: "contentPadding", group: "advanced", control: { type: "box", units: PX_PCT } },
|
|
3020
|
+
{ attr: "inner-background-color", label: "contentBackground", group: "advanced", control: { type: "color" } }
|
|
3021
|
+
],
|
|
3022
|
+
"mj-navbar": [
|
|
3023
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
3024
|
+
{ attr: "hamburger", label: "hamburger", group: "layout", control: { type: "toggle", on: "hamburger" } },
|
|
3025
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
3026
|
+
{ attr: "ico-color", label: "menuIconColor", group: "advanced", control: { type: "color" }, default: "#000000", when: (a) => !!a.hamburger },
|
|
3027
|
+
{ attr: "ico-font-size", label: "menuIconSize", group: "advanced", control: { type: "length", units: PX_PCT }, default: "30px", when: (a) => !!a.hamburger },
|
|
3028
|
+
{ attr: "ico-align", label: "menuIconAlign", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center", when: (a) => !!a.hamburger },
|
|
3029
|
+
{ attr: "base-url", label: "baseUrl", group: "advanced", control: { type: "link" } }
|
|
3030
|
+
],
|
|
3031
|
+
"mj-navbar-link": [
|
|
3032
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
3033
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "15px 10px" },
|
|
3034
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
3035
|
+
{ attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
3036
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
3037
|
+
{ attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS }, default: "normal" },
|
|
3038
|
+
{ attr: "text-transform", label: "textTransform", group: "style", control: { type: "select", options: TEXT_TRANSFORM }, default: "uppercase" },
|
|
3039
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
3040
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
3041
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
3042
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" }
|
|
3043
|
+
]
|
|
3044
|
+
};
|
|
3045
|
+
|
|
3046
|
+
// src/components/editor/layers.ts
|
|
3047
|
+
var svg = (paths) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
|
|
3048
|
+
var ICONS = {
|
|
3049
|
+
"mj-wrapper": svg(
|
|
3050
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect width="10" height="10" x="7" y="7" rx="1"/>'
|
|
3051
|
+
),
|
|
3052
|
+
"mj-section": svg('<rect width="20" height="12" x="2" y="6" rx="2"/>'),
|
|
3053
|
+
"mj-hero": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/>'),
|
|
3054
|
+
"mj-group": svg(
|
|
3055
|
+
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>'
|
|
3056
|
+
),
|
|
3057
|
+
"mj-column": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M12 3v18"/>'),
|
|
3058
|
+
"mj-text": svg('<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>'),
|
|
3059
|
+
"mj-image": svg(
|
|
3060
|
+
'<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>'
|
|
3061
|
+
),
|
|
3062
|
+
"mj-button": svg('<rect width="20" height="10" x="2" y="7" rx="3"/><path d="M8 12h8"/>'),
|
|
3063
|
+
"mj-divider": svg('<path d="M5 12h14"/>'),
|
|
3064
|
+
"mj-spacer": svg('<path d="M12 3v18"/><path d="m8 17 4 4 4-4"/><path d="m8 7 4-4 4 4"/>'),
|
|
3065
|
+
"mj-social": svg(
|
|
3066
|
+
'<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.59 13.51 6.83 3.98"/><path d="m15.41 6.51-6.82 3.98"/>'
|
|
3067
|
+
),
|
|
3068
|
+
"mj-social-element": svg(
|
|
3069
|
+
'<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/>'
|
|
3070
|
+
),
|
|
3071
|
+
"mj-navbar": svg('<path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/>'),
|
|
3072
|
+
"mj-navbar-link": svg(
|
|
3073
|
+
'<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>'
|
|
3074
|
+
),
|
|
3075
|
+
"mj-raw": svg('<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
|
|
3076
|
+
"mj-table": svg(
|
|
3077
|
+
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M12 3v18"/>'
|
|
3078
|
+
)
|
|
3079
|
+
};
|
|
3080
|
+
function tagOf(c) {
|
|
3081
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
3082
|
+
}
|
|
3083
|
+
function textOf(c) {
|
|
3084
|
+
const template = document.createElement("template");
|
|
3085
|
+
template.innerHTML = c.getInnerHTML();
|
|
3086
|
+
template.content.querySelectorAll("br, p, div, li, tr, h1, h2, h3, h4, h5, h6").forEach((el) => el.after("\n"));
|
|
3087
|
+
const lines = (template.content.textContent ?? "").split("\n");
|
|
3088
|
+
const first = lines.map((l) => l.replace(/\s+/g, " ").trim()).find(Boolean) ?? "";
|
|
3089
|
+
return first.slice(0, 60);
|
|
3090
|
+
}
|
|
3091
|
+
function escapeHtml(s) {
|
|
3092
|
+
return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
3093
|
+
}
|
|
3094
|
+
function layerName(c, labels) {
|
|
3095
|
+
const tag = tagOf(c);
|
|
3096
|
+
const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
3097
|
+
const attributes = c.getAttributes();
|
|
3098
|
+
switch (tag) {
|
|
3099
|
+
case "mj-text":
|
|
3100
|
+
case "mj-button":
|
|
3101
|
+
case "mj-navbar-link":
|
|
3102
|
+
return textOf(c) || kind;
|
|
3103
|
+
case "mj-image":
|
|
3104
|
+
return attributes.alt?.trim() || kind;
|
|
3105
|
+
case "mj-social-element": {
|
|
3106
|
+
const { network } = parseNetwork(String(attributes.name ?? ""));
|
|
3107
|
+
const option = NETWORKS.find((o) => o.value === network && network !== "custom");
|
|
3108
|
+
return textOf(c) || option && (labels.inspectorLabels[option.label] ?? option.label) || kind;
|
|
3109
|
+
}
|
|
3110
|
+
case "mj-section":
|
|
3111
|
+
case "mj-wrapper":
|
|
3112
|
+
case "mj-hero": {
|
|
3113
|
+
let words = "";
|
|
3114
|
+
c.onAll((d) => {
|
|
3115
|
+
if (!words && d !== c && ["mj-text", "mj-button"].includes(tagOf(d))) words = textOf(d);
|
|
3116
|
+
});
|
|
3117
|
+
return words || kind;
|
|
3118
|
+
}
|
|
3119
|
+
case "mj-column": {
|
|
3120
|
+
const columns = c.parent()?.components().filter((s) => tagOf(s) === "mj-column") ?? [];
|
|
3121
|
+
return columns.length > 1 ? `${kind} ${columns.indexOf(c) + 1}` : kind;
|
|
3122
|
+
}
|
|
3123
|
+
default:
|
|
3124
|
+
return kind;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
var CONTENT = /* @__PURE__ */ new Set(["mj-text", "mj-button", "mj-navbar-link", "mj-social-element", "mj-raw"]);
|
|
3128
|
+
function badgeLabel(labels) {
|
|
3129
|
+
return (c) => {
|
|
3130
|
+
const tag = tagOf(c);
|
|
3131
|
+
const icon = ICONS[tag] ? `<div class="gjs-badge__icon">${ICONS[tag]}</div>` : "";
|
|
3132
|
+
const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
3133
|
+
return `${icon}<div class="gjs-badge__name">${escapeHtml(kind)}</div>`;
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
function setupLayerNames(editor, labels) {
|
|
3137
|
+
const hideContent = (c) => c.onAll((d) => d !== c && d.set("layerable", false, { avoidStore: true }));
|
|
3138
|
+
for (const type of editor.Components.getTypes()) {
|
|
3139
|
+
if (!type.id.startsWith("mj-")) continue;
|
|
3140
|
+
editor.Components.addType(type.id, {
|
|
3141
|
+
model: {
|
|
3142
|
+
// Ours whatever the element says: GrapesJS writes it into the row's markup as it is.
|
|
3143
|
+
getIcon() {
|
|
3144
|
+
return ICONS[type.id] ?? "";
|
|
3145
|
+
},
|
|
3146
|
+
// GrapesJS writes the name into the row's markup unescaped, and it is document text.
|
|
3147
|
+
getName() {
|
|
3148
|
+
return escapeHtml(layerName(this, labels));
|
|
3149
|
+
},
|
|
3150
|
+
...CONTENT.has(type.id) && {
|
|
3151
|
+
// Editing a text replaces its children, so they are hidden again as they arrive.
|
|
3152
|
+
init() {
|
|
3153
|
+
hideContent(this);
|
|
3154
|
+
this.listenTo(this.components(), "add reset", () => hideContent(this));
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
},
|
|
3158
|
+
// Runs the plugin's own `init` as well.
|
|
3159
|
+
extendFn: ["init"]
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
let pending = false;
|
|
3163
|
+
editor.on("update component:add component:remove", () => {
|
|
3164
|
+
if (pending) return;
|
|
3165
|
+
pending = true;
|
|
3166
|
+
requestAnimationFrame(() => {
|
|
3167
|
+
pending = false;
|
|
3168
|
+
editor.Layers.getRoot()?.onAll((c) => {
|
|
3169
|
+
const view = c.viewLayer;
|
|
3170
|
+
const el = view?.getInputName?.();
|
|
3171
|
+
const name = layerName(c, labels);
|
|
3172
|
+
if (!el || el.textContent === name) return;
|
|
3173
|
+
el.textContent = name;
|
|
3174
|
+
el.parentElement?.setAttribute("title", name);
|
|
3175
|
+
});
|
|
3176
|
+
});
|
|
3177
|
+
});
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
// src/components/editor/inspector/StructureSection.tsx
|
|
3181
|
+
var import_lucide_react8 = require("lucide-react");
|
|
3182
|
+
|
|
3183
|
+
// src/components/editor/inspector/structure.ts
|
|
3184
|
+
function tagOf2(c) {
|
|
3185
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
3186
|
+
}
|
|
3187
|
+
var COLUMN_RATIOS = {
|
|
3188
|
+
2: [
|
|
3189
|
+
[1, 1],
|
|
3190
|
+
[1, 2],
|
|
3191
|
+
[2, 1]
|
|
3192
|
+
],
|
|
3193
|
+
3: [
|
|
3194
|
+
[1, 1, 1],
|
|
3195
|
+
[1, 2, 1]
|
|
3196
|
+
]
|
|
3197
|
+
};
|
|
3198
|
+
function columnsOf(section) {
|
|
3199
|
+
const children = section.components().models;
|
|
3200
|
+
return children.length > 0 && children.every((c) => tagOf2(c) === "mj-column") ? children : null;
|
|
3201
|
+
}
|
|
3202
|
+
function ratioWidths(ratio) {
|
|
3203
|
+
if (ratio.every((r) => r === ratio[0])) return null;
|
|
3204
|
+
const total = ratio.reduce((a, b) => a + b, 0);
|
|
3205
|
+
return ratio.map((r) => `${Math.round(r / total * 1e4) / 100}%`);
|
|
3206
|
+
}
|
|
3207
|
+
function currentRatio(columns) {
|
|
3208
|
+
const widths = columns.map((c) => String(c.getAttributes().width ?? ""));
|
|
3209
|
+
return COLUMN_RATIOS[columns.length]?.find((ratio) => {
|
|
3210
|
+
const want = ratioWidths(ratio);
|
|
3211
|
+
if (!want) return widths.every((w) => w === "");
|
|
3212
|
+
return widths.every(
|
|
3213
|
+
(w, i) => /%$/.test(w) && Math.abs(parseFloat(w) - parseFloat(want[i])) < 0.5
|
|
3214
|
+
);
|
|
3215
|
+
}) ?? null;
|
|
3216
|
+
}
|
|
3217
|
+
function setRatio(columns, ratio) {
|
|
3218
|
+
const widths = ratioWidths(ratio);
|
|
3219
|
+
columns.forEach((c, i) => {
|
|
3220
|
+
if (widths) c.addAttributes({ width: widths[i] });
|
|
3221
|
+
else c.removeAttributes(["width"]);
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
var PROFILE_URLS = {
|
|
3225
|
+
facebook: "https://facebook.com/",
|
|
3226
|
+
instagram: "https://instagram.com/",
|
|
3227
|
+
x: "https://x.com/",
|
|
3228
|
+
linkedin: "https://linkedin.com/",
|
|
3229
|
+
youtube: "https://youtube.com/",
|
|
3230
|
+
pinterest: "https://pinterest.com/",
|
|
3231
|
+
github: "https://github.com/"
|
|
3232
|
+
};
|
|
3233
|
+
function addNetwork(social) {
|
|
3234
|
+
const present = new Set(
|
|
3235
|
+
social.components().map((c) => String(c.getAttributes().name ?? "").replace(/-noshare$/, ""))
|
|
3236
|
+
);
|
|
3237
|
+
const network = Object.keys(PROFILE_URLS).find((n) => !present.has(n)) ?? "facebook";
|
|
3238
|
+
return social.append(
|
|
3239
|
+
`<mj-social-element name="${network}-noshare" href="${PROFILE_URLS[network]}"></mj-social-element>`
|
|
3240
|
+
)[0];
|
|
3241
|
+
}
|
|
3242
|
+
function addNavbarLink(navbar, text) {
|
|
3243
|
+
return navbar.append(`<mj-navbar-link>${escapeHtml2(text)}</mj-navbar-link>`)[0];
|
|
3244
|
+
}
|
|
3245
|
+
function escapeHtml2(s) {
|
|
3246
|
+
return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
// src/components/editor/inspector/StructureSection.tsx
|
|
3250
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3251
|
+
function tagOf3(c) {
|
|
3252
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
3253
|
+
}
|
|
3254
|
+
function StructureSection({
|
|
3255
|
+
editor,
|
|
3256
|
+
component,
|
|
3257
|
+
labels,
|
|
3258
|
+
locked
|
|
3259
|
+
}) {
|
|
3260
|
+
switch (tagOf3(component)) {
|
|
3261
|
+
case "mj-section":
|
|
3262
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ColumnWidths, { component, labels, locked });
|
|
3263
|
+
case "mj-social":
|
|
3264
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3265
|
+
Children,
|
|
3266
|
+
{
|
|
3267
|
+
editor,
|
|
3268
|
+
component,
|
|
3269
|
+
labels,
|
|
3270
|
+
locked,
|
|
3271
|
+
title: labels.socialNetworks,
|
|
3272
|
+
add: labels.addNetwork,
|
|
3273
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.AtSign, { className: "size-3.5 shrink-0" }),
|
|
3274
|
+
onAdd: () => editor.select(addNetwork(component))
|
|
3275
|
+
}
|
|
3276
|
+
);
|
|
3277
|
+
case "mj-navbar":
|
|
3278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3279
|
+
Children,
|
|
3280
|
+
{
|
|
3281
|
+
editor,
|
|
3282
|
+
component,
|
|
3283
|
+
labels,
|
|
3284
|
+
locked,
|
|
3285
|
+
title: labels.navbarLinks,
|
|
3286
|
+
add: labels.addLink,
|
|
3287
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.Link2, { className: "size-3.5 shrink-0" }),
|
|
3288
|
+
onAdd: () => editor.select(addNavbarLink(component, labels.newLinkText))
|
|
3289
|
+
}
|
|
3290
|
+
);
|
|
3291
|
+
default:
|
|
3292
|
+
return null;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
function Heading({ children }) {
|
|
3296
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children });
|
|
3297
|
+
}
|
|
3298
|
+
function ColumnWidths({
|
|
3299
|
+
component,
|
|
3300
|
+
labels,
|
|
3301
|
+
locked
|
|
3302
|
+
}) {
|
|
3303
|
+
const columns = columnsOf(component);
|
|
3304
|
+
const ratios = columns && COLUMN_RATIOS[columns.length];
|
|
3305
|
+
if (!columns || !ratios) return null;
|
|
3306
|
+
const current = currentRatio(columns);
|
|
3307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
3308
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Heading, { children: labels.columnWidths }),
|
|
3309
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex gap-1.5 px-3 py-1", children: ratios.map((ratio) => {
|
|
3310
|
+
const name = labels.columnRatio(ratio.join(":"));
|
|
3311
|
+
const active = current?.join() === ratio.join();
|
|
3312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3313
|
+
"button",
|
|
3314
|
+
{
|
|
3315
|
+
type: "button",
|
|
3316
|
+
disabled: locked,
|
|
3317
|
+
"aria-pressed": active,
|
|
3318
|
+
title: name,
|
|
3319
|
+
"aria-label": name,
|
|
3320
|
+
onClick: () => !active && setRatio(columns, ratio),
|
|
3321
|
+
className: cn(
|
|
3322
|
+
"flex h-8 flex-1 items-center gap-0.5 rounded-md border px-1.5 transition-colors",
|
|
3323
|
+
active ? "border-brand bg-brand/15" : "border-panel-border bg-panel-elevated hover:border-panel-muted-fg"
|
|
3324
|
+
),
|
|
3325
|
+
children: ratio.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3326
|
+
"span",
|
|
3327
|
+
{
|
|
3328
|
+
style: { flexGrow: part },
|
|
3329
|
+
className: cn(
|
|
3330
|
+
"h-4 basis-0 rounded-sm",
|
|
3331
|
+
active ? "bg-brand" : "bg-panel-muted-fg/50"
|
|
3332
|
+
)
|
|
3333
|
+
},
|
|
3334
|
+
i
|
|
3335
|
+
))
|
|
3336
|
+
},
|
|
3337
|
+
ratio.join()
|
|
3338
|
+
);
|
|
3339
|
+
}) })
|
|
3340
|
+
] });
|
|
3341
|
+
}
|
|
3342
|
+
function Children({
|
|
3343
|
+
editor,
|
|
3344
|
+
component,
|
|
3345
|
+
labels,
|
|
3346
|
+
locked,
|
|
3347
|
+
title,
|
|
3348
|
+
add,
|
|
3349
|
+
icon,
|
|
3350
|
+
onAdd
|
|
3351
|
+
}) {
|
|
3352
|
+
const children = component.components().models;
|
|
3353
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
3354
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Heading, { children: title }),
|
|
3355
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "flex flex-col px-2", children: children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3356
|
+
"button",
|
|
3357
|
+
{
|
|
3358
|
+
type: "button",
|
|
3359
|
+
onClick: () => editor.select(child),
|
|
3360
|
+
className: "group flex w-full items-center gap-2 rounded-md px-1.5 py-1 text-left text-xs text-panel-fg hover:bg-panel-hover",
|
|
3361
|
+
children: [
|
|
3362
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-panel-muted-fg", children: icon }),
|
|
3363
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 flex-1 truncate", children: layerName(child, labels) }),
|
|
3364
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.ChevronRight, { className: "size-3 shrink-0 text-panel-muted-fg opacity-0 group-hover:opacity-100" })
|
|
3365
|
+
]
|
|
3366
|
+
}
|
|
3367
|
+
) }, child.cid)) }),
|
|
3368
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3369
|
+
"button",
|
|
3370
|
+
{
|
|
3371
|
+
type: "button",
|
|
3372
|
+
disabled: locked,
|
|
3373
|
+
onClick: onAdd,
|
|
3374
|
+
className: "mx-3 mt-1 flex items-center gap-1.5 rounded-md px-1 py-1 text-xs text-panel-muted-fg hover:text-panel-fg disabled:opacity-50",
|
|
3375
|
+
children: [
|
|
3376
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.Plus, { className: "size-3.5" }),
|
|
3377
|
+
add
|
|
3378
|
+
]
|
|
3379
|
+
}
|
|
3380
|
+
)
|
|
3381
|
+
] });
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
// src/components/editor/inspector/write.ts
|
|
3385
|
+
function same(a, b) {
|
|
3386
|
+
const keys = Object.keys(a);
|
|
3387
|
+
return keys.length === Object.keys(b).length && keys.every((k) => k in b && a[k] === b[k]);
|
|
3388
|
+
}
|
|
3389
|
+
function createWriter(editor, component, blocked = () => false) {
|
|
3390
|
+
const isImage = String(component.get("tagName") || component.get("type")) === "mj-image";
|
|
3391
|
+
const current = () => {
|
|
3392
|
+
const { __p: _marker, style: _style, ...rest } = component.get("attributes");
|
|
3393
|
+
return rest;
|
|
3394
|
+
};
|
|
3395
|
+
let drag = null;
|
|
3396
|
+
const record = (start, end) => {
|
|
3397
|
+
component.setAttributes(start, { avoidStore: true });
|
|
3398
|
+
if (!same(end, start)) component.setAttributes(end);
|
|
3399
|
+
delete component.get("attributes").__p;
|
|
3400
|
+
};
|
|
3401
|
+
return async (attr, value, { partial, remove = [] } = {}) => {
|
|
3402
|
+
if (editor.getEditing() === component) {
|
|
3403
|
+
const view = component.getView();
|
|
3404
|
+
await view?.syncContent?.();
|
|
3405
|
+
}
|
|
3406
|
+
if (blocked()) return;
|
|
3407
|
+
if (drag && drag.attr !== attr) {
|
|
3408
|
+
const { start: start2 } = drag;
|
|
3409
|
+
drag = null;
|
|
3410
|
+
record(start2, current());
|
|
3411
|
+
}
|
|
3412
|
+
if (attr === "src" && isImage) return void component.set({ src: value });
|
|
3413
|
+
const next = current();
|
|
3414
|
+
for (const name of remove) delete next[name];
|
|
3415
|
+
if (value === "") delete next[attr];
|
|
3416
|
+
else next[attr] = value;
|
|
3417
|
+
if (partial) {
|
|
3418
|
+
drag ??= { attr, start: current() };
|
|
3419
|
+
component.setAttributes(next, { avoidStore: true });
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
const start = drag?.start ?? current();
|
|
3423
|
+
drag = null;
|
|
3424
|
+
record(start, next);
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
// src/components/editor/inspector/Inspector.tsx
|
|
3429
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3430
|
+
var GROUPS = ["content", "layout", "spacing", "style"];
|
|
3431
|
+
var HIDDEN = /* @__PURE__ */ new Set(["css-class", "mj-class", "id", "style"]);
|
|
3432
|
+
function tagOf4(c) {
|
|
3433
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
3434
|
+
}
|
|
3435
|
+
function attributesOf(c) {
|
|
3436
|
+
const out = {};
|
|
3437
|
+
for (const [k, v] of Object.entries(c.getAttributes())) {
|
|
3438
|
+
if (v !== void 0 && v !== null && v !== false) out[k] = String(v);
|
|
3439
|
+
}
|
|
3440
|
+
if (tagOf4(c) === "mj-image") out.src = String(c.get("src") ?? "");
|
|
3441
|
+
return out;
|
|
3442
|
+
}
|
|
3443
|
+
function documentColors(editor) {
|
|
3444
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3445
|
+
const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
|
|
3446
|
+
for (const match of editor.getHtml().matchAll(colorValue)) {
|
|
3447
|
+
const hex = toHex(match[1], { opaque: true });
|
|
3448
|
+
if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
|
|
3449
|
+
}
|
|
3450
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
|
|
3451
|
+
}
|
|
3452
|
+
function Inspector({
|
|
3453
|
+
onEmailSettings,
|
|
3454
|
+
empty
|
|
3455
|
+
}) {
|
|
3456
|
+
const editor = (0, import_react8.useEditorMaybe)();
|
|
3457
|
+
const labels = useLabels();
|
|
3458
|
+
const { locked } = useAgentTurn();
|
|
3459
|
+
const lockedNow = (0, import_react7.useRef)(locked);
|
|
3460
|
+
(0, import_react7.useLayoutEffect)(() => {
|
|
3461
|
+
lockedNow.current = locked;
|
|
3462
|
+
});
|
|
3463
|
+
const [, refresh] = (0, import_react7.useReducer)((n) => n + 1, 0);
|
|
3464
|
+
const root = (0, import_react7.useRef)(null);
|
|
3465
|
+
const refocus = (0, import_react7.useRef)(false);
|
|
3466
|
+
(0, import_react7.useEffect)(() => {
|
|
3467
|
+
if (!editor) return;
|
|
3468
|
+
const events = "component:update change:device";
|
|
3469
|
+
const toggled = () => {
|
|
3470
|
+
if (root.current?.contains(document.activeElement)) refocus.current = true;
|
|
3471
|
+
refresh();
|
|
3472
|
+
};
|
|
3473
|
+
editor.on(events, refresh);
|
|
3474
|
+
editor.on("component:toggled", toggled);
|
|
3475
|
+
return () => {
|
|
3476
|
+
editor.off(events, refresh);
|
|
3477
|
+
editor.off("component:toggled", toggled);
|
|
3478
|
+
};
|
|
3479
|
+
}, [editor]);
|
|
3480
|
+
(0, import_react7.useEffect)(() => {
|
|
3481
|
+
if (!refocus.current) return;
|
|
3482
|
+
refocus.current = false;
|
|
3483
|
+
root.current?.querySelector("[data-focus-target]")?.focus();
|
|
3484
|
+
});
|
|
3485
|
+
if (!editor) return null;
|
|
3486
|
+
const selected = editor.getSelectedAll();
|
|
3487
|
+
const component = selected[0];
|
|
3488
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: root, children: selected.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { "data-focus-target": true, tabIndex: -1, className: "outline-none", children: empty }) : selected.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3489
|
+
"p",
|
|
3490
|
+
{
|
|
3491
|
+
"data-focus-target": true,
|
|
3492
|
+
tabIndex: -1,
|
|
3493
|
+
className: "px-4 py-8 text-center text-xs text-panel-muted-fg outline-none",
|
|
3494
|
+
children: labels.multipleSelected(selected.length)
|
|
3495
|
+
}
|
|
3496
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3497
|
+
ElementSettings,
|
|
3498
|
+
{
|
|
3499
|
+
editor,
|
|
3500
|
+
component,
|
|
3501
|
+
labels,
|
|
3502
|
+
locked,
|
|
3503
|
+
lockedNow,
|
|
3504
|
+
onEmailSettings
|
|
3505
|
+
},
|
|
3506
|
+
component.cid
|
|
3507
|
+
) });
|
|
3508
|
+
}
|
|
3509
|
+
function ElementSettings({
|
|
3510
|
+
editor,
|
|
3511
|
+
component,
|
|
3512
|
+
labels,
|
|
3513
|
+
locked,
|
|
3514
|
+
lockedNow,
|
|
3515
|
+
onEmailSettings
|
|
3516
|
+
}) {
|
|
3517
|
+
const [advancedOpen, setAdvancedOpen] = (0, import_react7.useState)(false);
|
|
3518
|
+
const tag = tagOf4(component);
|
|
3519
|
+
const fields = SCHEMA[tag] ?? [];
|
|
3520
|
+
const attributes = attributesOf(component);
|
|
3521
|
+
const visible = fields.filter((f) => !f.when || f.when(attributes));
|
|
3522
|
+
const t = (key) => labels.inspectorLabels[key] ?? key;
|
|
3523
|
+
const optionName = (o) => labels.inspectorLabels[o.label] ?? o.label;
|
|
3524
|
+
const [writer] = (0, import_react7.useState)(() => createWriter(editor, component, () => lockedNow.current));
|
|
3525
|
+
const write = (attr, value, options) => lockedNow.current ? Promise.resolve() : writer(attr, value, options);
|
|
3526
|
+
const writeBox = (attr, shorthand, partial) => write(attr, shorthand, { remove: SIDES.map((s) => `${attr}-${s}`), partial });
|
|
3527
|
+
const chooseImage = (attr) => {
|
|
3528
|
+
if (attr === "src" && tag === "mj-image") return void editor.runCommand("open-image-picker");
|
|
3529
|
+
const am = editor.AssetManager;
|
|
3530
|
+
am.open({
|
|
3531
|
+
types: ["image"],
|
|
3532
|
+
current: imageUrl(attributes[attr] ?? "") ?? void 0,
|
|
3533
|
+
select(asset, complete) {
|
|
3534
|
+
void write(attr, typeof asset === "string" ? asset : asset.getSrc());
|
|
3535
|
+
if (complete) am.close();
|
|
3536
|
+
}
|
|
3537
|
+
});
|
|
3538
|
+
};
|
|
3539
|
+
const safeLink = (attr) => (link) => {
|
|
3540
|
+
if (tag === "mj-navbar-link" && attr === "href") {
|
|
3541
|
+
return (0, import_core2.isSafeNavbarLink)(String(component.parent()?.getAttributes()["base-url"] ?? ""), link);
|
|
3542
|
+
}
|
|
3543
|
+
if (tag === "mj-navbar" && attr === "base-url") {
|
|
3544
|
+
return component.components().every((c) => (0, import_core2.isSafeNavbarLink)(link, String(c.getAttributes().href ?? "")));
|
|
3545
|
+
}
|
|
3546
|
+
return (0, import_core2.isSafeLink)(link);
|
|
3547
|
+
};
|
|
3548
|
+
const control = (f) => {
|
|
3549
|
+
const value = attributes[f.attr] ?? "";
|
|
3550
|
+
const c = f.control;
|
|
3551
|
+
switch (c.type) {
|
|
3552
|
+
case "text":
|
|
3553
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3554
|
+
TextInput,
|
|
3555
|
+
{
|
|
3556
|
+
value,
|
|
3557
|
+
placeholder: c.placeholder ?? f.default,
|
|
3558
|
+
onCommit: (v) => write(f.attr, v)
|
|
3559
|
+
}
|
|
3560
|
+
);
|
|
3561
|
+
case "link":
|
|
3562
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3563
|
+
TextInput,
|
|
3564
|
+
{
|
|
3565
|
+
value,
|
|
3566
|
+
placeholder: "https://",
|
|
3567
|
+
accept: safeLink(f.attr),
|
|
3568
|
+
onCommit: (v) => write(f.attr, v)
|
|
3569
|
+
}
|
|
3570
|
+
);
|
|
3571
|
+
case "color":
|
|
3572
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3573
|
+
ColorField,
|
|
3574
|
+
{
|
|
3575
|
+
value,
|
|
3576
|
+
placeholder: f.default,
|
|
3577
|
+
documentColors: () => documentColors(editor),
|
|
3578
|
+
labels,
|
|
3579
|
+
onChange: (v, partial) => write(f.attr, v, { partial })
|
|
3580
|
+
}
|
|
3581
|
+
);
|
|
3582
|
+
case "image":
|
|
3583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3584
|
+
ImageField,
|
|
3585
|
+
{
|
|
3586
|
+
value,
|
|
3587
|
+
labels,
|
|
3588
|
+
onChoose: () => chooseImage(f.attr),
|
|
3589
|
+
onClear: () => write(f.attr, "")
|
|
3590
|
+
}
|
|
3591
|
+
);
|
|
3592
|
+
case "length":
|
|
3593
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3594
|
+
LengthInput,
|
|
3595
|
+
{
|
|
3596
|
+
value,
|
|
3597
|
+
placeholder: f.default,
|
|
3598
|
+
units: c.units,
|
|
3599
|
+
negative: c.negative,
|
|
3600
|
+
labels,
|
|
3601
|
+
onCommit: (v, partial) => write(f.attr, v, { partial })
|
|
3602
|
+
}
|
|
3603
|
+
);
|
|
3604
|
+
case "box":
|
|
3605
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3606
|
+
BoxInput,
|
|
3607
|
+
{
|
|
3608
|
+
attributes,
|
|
3609
|
+
attr: f.attr,
|
|
3610
|
+
placeholder: f.default,
|
|
3611
|
+
units: c.units,
|
|
3612
|
+
labels,
|
|
3613
|
+
onCommit: (v, partial) => writeBox(f.attr, v, partial)
|
|
3614
|
+
}
|
|
3615
|
+
);
|
|
3616
|
+
case "segmented":
|
|
3617
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3618
|
+
Segmented,
|
|
3619
|
+
{
|
|
3620
|
+
value,
|
|
3621
|
+
fallback: f.default,
|
|
3622
|
+
options: c.options,
|
|
3623
|
+
name: optionName,
|
|
3624
|
+
onCommit: (v) => write(f.attr, v)
|
|
3625
|
+
}
|
|
3626
|
+
);
|
|
3627
|
+
case "select":
|
|
3628
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3629
|
+
Select,
|
|
3630
|
+
{
|
|
3631
|
+
value,
|
|
3632
|
+
fallback: f.default,
|
|
3633
|
+
options: c.options,
|
|
3634
|
+
name: optionName,
|
|
3635
|
+
onCommit: (v) => write(f.attr, v)
|
|
3636
|
+
}
|
|
3637
|
+
);
|
|
3638
|
+
case "toggle":
|
|
3639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3640
|
+
Toggle,
|
|
3641
|
+
{
|
|
3642
|
+
checked: value === c.on,
|
|
3643
|
+
label: t(f.label),
|
|
3644
|
+
onCommit: (on) => write(f.attr, on ? c.on : "")
|
|
3645
|
+
}
|
|
3646
|
+
);
|
|
3647
|
+
case "network": {
|
|
3648
|
+
const { network, share } = parseNetwork(value);
|
|
3649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
3650
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3651
|
+
Select,
|
|
3652
|
+
{
|
|
3653
|
+
value: network,
|
|
3654
|
+
options: c.networks,
|
|
3655
|
+
name: optionName,
|
|
3656
|
+
onCommit: (n) => write(f.attr, n ? networkName(n, share) : "")
|
|
3657
|
+
}
|
|
3658
|
+
),
|
|
3659
|
+
network && network !== "custom" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3660
|
+
Segmented,
|
|
3661
|
+
{
|
|
3662
|
+
value: share ? "share" : "profile",
|
|
3663
|
+
options: [
|
|
3664
|
+
{ value: "profile", label: labels.linkTypeProfile },
|
|
3665
|
+
{ value: "share", label: labels.linkTypeShare }
|
|
3666
|
+
],
|
|
3667
|
+
name: (o) => o.label,
|
|
3668
|
+
onCommit: (v) => write(f.attr, networkName(network, v === "share"))
|
|
3669
|
+
}
|
|
3670
|
+
)
|
|
3671
|
+
] });
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
};
|
|
3675
|
+
const own = (f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)].some((a) => attributes[a] !== void 0) : !!attributes[f.attr];
|
|
3676
|
+
const reset = (f) => f.control.type === "box" ? writeBox(f.attr, "") : write(f.attr, "");
|
|
3677
|
+
const wide = (f) => ["color", "image", "link", "text", "box", "network"].includes(f.control.type);
|
|
3678
|
+
const row = (f) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3679
|
+
Row,
|
|
3680
|
+
{
|
|
3681
|
+
label: t(f.label),
|
|
3682
|
+
help: labels.inspectorLabels[`${f.label}.help`],
|
|
3683
|
+
own: own(f),
|
|
3684
|
+
wide: wide(f),
|
|
3685
|
+
inline: f.control.type === "toggle",
|
|
3686
|
+
labels,
|
|
3687
|
+
onReset: () => void reset(f),
|
|
3688
|
+
children: control(f)
|
|
3689
|
+
},
|
|
3690
|
+
f.attr
|
|
3691
|
+
);
|
|
3692
|
+
const named = new Set(
|
|
3693
|
+
fields.flatMap(
|
|
3694
|
+
(f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)] : [f.attr]
|
|
3695
|
+
)
|
|
3696
|
+
);
|
|
3697
|
+
const others = Object.entries(attributes).filter(([k]) => !named.has(k) && !HIDDEN.has(k));
|
|
3698
|
+
const advanced = visible.filter((f) => f.group === "advanced");
|
|
3699
|
+
const advancedCount = advanced.length + others.length;
|
|
3700
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pb-6", children: [
|
|
3701
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3702
|
+
Header,
|
|
3703
|
+
{
|
|
3704
|
+
editor,
|
|
3705
|
+
component,
|
|
3706
|
+
labels,
|
|
3707
|
+
onEmailSettings
|
|
3708
|
+
}
|
|
3709
|
+
),
|
|
3710
|
+
editor.getDevice() !== "Desktop" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "mx-3 mt-2 flex items-center gap-1.5 rounded-md bg-panel-elevated px-2 py-1.5 text-[11px] text-panel-muted-fg", children: [
|
|
3711
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Monitor, { className: "size-3.5 shrink-0" }),
|
|
3712
|
+
labels.allScreenSizes
|
|
3713
|
+
] }),
|
|
3714
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StructureSection, { editor, component, labels, locked }),
|
|
3715
|
+
GROUPS.map((group) => {
|
|
3716
|
+
const inGroup = visible.filter((f) => f.group === group);
|
|
3717
|
+
if (inGroup.length === 0) return null;
|
|
3718
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
3719
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children: groupName(group, labels) }),
|
|
3720
|
+
inGroup.map(row)
|
|
3721
|
+
] }, group);
|
|
3722
|
+
}),
|
|
3723
|
+
advancedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "py-1", children: [
|
|
3724
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3725
|
+
"button",
|
|
3726
|
+
{
|
|
3727
|
+
type: "button",
|
|
3728
|
+
onClick: () => setAdvancedOpen((o) => !o),
|
|
3729
|
+
"aria-expanded": advancedOpen,
|
|
3730
|
+
className: "flex w-full items-center gap-1 px-3 py-2 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase hover:text-panel-fg",
|
|
3731
|
+
children: [
|
|
3732
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3733
|
+
import_lucide_react9.ChevronRight,
|
|
3734
|
+
{
|
|
3735
|
+
className: cn("size-3 transition-transform", advancedOpen && "rotate-90")
|
|
3736
|
+
}
|
|
3737
|
+
),
|
|
3738
|
+
labels.groupAdvanced,
|
|
3739
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "font-normal normal-case", children: [
|
|
3740
|
+
"(",
|
|
3741
|
+
advancedCount,
|
|
3742
|
+
")"
|
|
3743
|
+
] })
|
|
3744
|
+
]
|
|
3745
|
+
}
|
|
3746
|
+
),
|
|
3747
|
+
advancedOpen && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
3748
|
+
advanced.map(row),
|
|
3749
|
+
others.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "px-3 pt-2", children: [
|
|
3750
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "pb-1 text-[11px] text-panel-muted-fg", children: labels.otherAttributes }),
|
|
3751
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-1", children: others.map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3752
|
+
"li",
|
|
3753
|
+
{
|
|
3754
|
+
className: "flex items-center gap-2 rounded-md bg-panel-elevated px-2 py-1 font-mono text-[11px]",
|
|
3755
|
+
children: [
|
|
3756
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-panel-muted-fg", children: k }),
|
|
3757
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "min-w-0 flex-1 truncate text-panel-fg", title: v, children: v }),
|
|
3758
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3759
|
+
"button",
|
|
3760
|
+
{
|
|
3761
|
+
type: "button",
|
|
3762
|
+
onClick: () => void write(k, ""),
|
|
3763
|
+
title: labels.removeAttribute,
|
|
3764
|
+
"aria-label": `${labels.removeAttribute}: ${k}`,
|
|
3765
|
+
className: "text-panel-muted-fg hover:text-panel-fg",
|
|
3766
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.X, { className: "size-3" })
|
|
3767
|
+
}
|
|
3768
|
+
)
|
|
3769
|
+
]
|
|
3770
|
+
},
|
|
3771
|
+
k
|
|
3772
|
+
)) })
|
|
3773
|
+
] })
|
|
3774
|
+
] })
|
|
3775
|
+
] })
|
|
3776
|
+
] });
|
|
3777
|
+
}
|
|
3778
|
+
function groupName(group, labels) {
|
|
3779
|
+
return {
|
|
3780
|
+
content: labels.groupContent,
|
|
3781
|
+
layout: labels.groupLayout,
|
|
3782
|
+
spacing: labels.groupSpacing,
|
|
3783
|
+
style: labels.groupStyle
|
|
3784
|
+
}[group];
|
|
3785
|
+
}
|
|
3786
|
+
function Header({
|
|
3787
|
+
editor,
|
|
3788
|
+
component,
|
|
3789
|
+
labels,
|
|
3790
|
+
onEmailSettings
|
|
3791
|
+
}) {
|
|
3792
|
+
const tag = tagOf4(component);
|
|
3793
|
+
const isBody = tag === "mj-body";
|
|
3794
|
+
const name = isBody ? labels.emailSettings : labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
3795
|
+
const words = tag === "mj-column" ? "" : layerName(component, labels);
|
|
3796
|
+
const excerpt = words === name ? "" : words;
|
|
3797
|
+
const parent = component.parent();
|
|
3798
|
+
const action = "flex size-7 items-center justify-center rounded-md text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
|
|
3799
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "sticky top-0 z-10 flex items-center gap-2 border-b border-panel-border bg-panel px-3 py-2", children: [
|
|
3800
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
3801
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3802
|
+
"p",
|
|
3803
|
+
{
|
|
3804
|
+
"data-focus-target": true,
|
|
3805
|
+
tabIndex: -1,
|
|
3806
|
+
className: "truncate text-sm font-medium text-panel-fg outline-none",
|
|
3807
|
+
children: name
|
|
3808
|
+
}
|
|
3809
|
+
),
|
|
3810
|
+
excerpt && !isBody && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "truncate text-[11px] text-panel-muted-fg", children: [
|
|
3811
|
+
"\u201C",
|
|
3812
|
+
excerpt,
|
|
3813
|
+
"\u201D"
|
|
3814
|
+
] })
|
|
3815
|
+
] }),
|
|
3816
|
+
!isBody && parent && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3817
|
+
"button",
|
|
3818
|
+
{
|
|
3819
|
+
type: "button",
|
|
3820
|
+
className: action,
|
|
3821
|
+
title: labels.selectParent,
|
|
3822
|
+
"aria-label": labels.selectParent,
|
|
3823
|
+
onClick: () => tagOf4(parent) === "mj-body" ? onEmailSettings() : editor.select(parent),
|
|
3824
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.ArrowUp, { className: "size-3.5" })
|
|
3825
|
+
}
|
|
3826
|
+
),
|
|
3827
|
+
!isBody && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
3828
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3829
|
+
"button",
|
|
3830
|
+
{
|
|
3831
|
+
type: "button",
|
|
3832
|
+
className: action,
|
|
3833
|
+
title: labels.duplicate,
|
|
3834
|
+
"aria-label": labels.duplicate,
|
|
3835
|
+
onClick: () => editor.runCommand("tlb-clone"),
|
|
3836
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Copy, { className: "size-3.5" })
|
|
3837
|
+
}
|
|
3838
|
+
),
|
|
3839
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1247
3840
|
"button",
|
|
1248
3841
|
{
|
|
1249
3842
|
type: "button",
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
3843
|
+
className: action,
|
|
3844
|
+
title: labels.deleteElement,
|
|
3845
|
+
"aria-label": labels.deleteElement,
|
|
3846
|
+
onClick: () => editor.runCommand("tlb-delete"),
|
|
3847
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Trash2, { className: "size-3.5" })
|
|
1254
3848
|
}
|
|
1255
3849
|
)
|
|
1256
3850
|
] })
|
|
1257
3851
|
] });
|
|
1258
3852
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
3853
|
+
|
|
3854
|
+
// src/components/editor/blocks.ts
|
|
3855
|
+
var import_grapesjs = __toESM(require("grapesjs"), 1);
|
|
3856
|
+
var import_grapesjs_mjml = __toESM(require("grapesjs-mjml"), 1);
|
|
3857
|
+
|
|
3858
|
+
// src/components/editor/ids.ts
|
|
3859
|
+
var ID_RE = {
|
|
3860
|
+
sec: /(?:^|\s)sec-([A-Za-z0-9_-]+)(?=\s|$)/,
|
|
3861
|
+
obj: /(?:^|\s)obj-([A-Za-z0-9_-]+)(?=\s|$)/
|
|
3862
|
+
};
|
|
3863
|
+
function idOf(c, prefix) {
|
|
3864
|
+
return String(c.getAttributes()["css-class"] ?? "").match(ID_RE[prefix])?.[1] ?? null;
|
|
3865
|
+
}
|
|
3866
|
+
var newId = () => Math.random().toString(36).slice(2, 10);
|
|
3867
|
+
function sectionIdOf(c) {
|
|
3868
|
+
return idOf(c, "sec");
|
|
3869
|
+
}
|
|
3870
|
+
function addIdClass(c, prefix) {
|
|
3871
|
+
const existing = idOf(c, prefix);
|
|
3872
|
+
if (existing) return existing;
|
|
3873
|
+
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
3874
|
+
const id = newId();
|
|
3875
|
+
c.addAttributes({ "css-class": `${cssClass} ${prefix}-${id}`.trim() });
|
|
3876
|
+
return id;
|
|
3877
|
+
}
|
|
3878
|
+
function freshenCopiedIds(root, added) {
|
|
3879
|
+
const used = (prefix, id, except) => {
|
|
3880
|
+
let found = false;
|
|
3881
|
+
root?.onAll((o) => {
|
|
3882
|
+
if (o !== except && idOf(o, prefix) === id) found = true;
|
|
3883
|
+
});
|
|
3884
|
+
return found;
|
|
3885
|
+
};
|
|
3886
|
+
added.onAll((c) => {
|
|
3887
|
+
for (const prefix of ["sec", "obj"]) {
|
|
3888
|
+
const id = idOf(c, prefix);
|
|
3889
|
+
if (!id || !used(prefix, id, c)) continue;
|
|
3890
|
+
const cssClass = String(c.getAttributes()["css-class"]);
|
|
3891
|
+
const fresh = cssClass.replace(
|
|
3892
|
+
ID_RE[prefix],
|
|
3893
|
+
(token) => token.replace(`${prefix}-${id}`, `${prefix}-${newId()}`)
|
|
3894
|
+
);
|
|
3895
|
+
c.addAttributes({ "css-class": fresh });
|
|
1268
3896
|
}
|
|
1269
|
-
);
|
|
3897
|
+
});
|
|
1270
3898
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
3899
|
+
|
|
3900
|
+
// src/components/editor/insert-block.ts
|
|
3901
|
+
var addsBlock = (e) => (e.key === "Enter" || e.key === " ") && !e.repeat && !e.ctrlKey && !e.metaKey && !e.altKey;
|
|
3902
|
+
var tileAttributes = (id) => ({ tabindex: "0", role: "button", "data-block": id });
|
|
3903
|
+
var tagOf5 = (c) => String(c.get("tagName") ?? "");
|
|
3904
|
+
function closest(c, tag) {
|
|
3905
|
+
for (let cur = c; cur; cur = cur.parent() ?? void 0) if (tagOf5(cur) === tag) return cur;
|
|
3906
|
+
return void 0;
|
|
3907
|
+
}
|
|
3908
|
+
function childOf(parent, c) {
|
|
3909
|
+
let cur = c;
|
|
3910
|
+
while (cur.parent() && cur.parent() !== parent) cur = cur.parent();
|
|
3911
|
+
return cur;
|
|
3912
|
+
}
|
|
3913
|
+
function insertBlock(editor, block) {
|
|
3914
|
+
const content = block.getContent();
|
|
3915
|
+
let body;
|
|
3916
|
+
editor.getWrapper()?.onAll((c) => {
|
|
3917
|
+
if (!body && tagOf5(c) === "mj-body") body = c;
|
|
3918
|
+
});
|
|
3919
|
+
if (typeof content !== "string" || !body) return void 0;
|
|
3920
|
+
const selected = editor.getSelected();
|
|
3921
|
+
const section = closest(selected, "mj-section");
|
|
3922
|
+
const sections = section?.parent() ?? body;
|
|
3923
|
+
const afterSection = section ? { at: section.index() + 1 } : {};
|
|
3924
|
+
const column = closest(selected, "mj-column");
|
|
3925
|
+
let added;
|
|
3926
|
+
if (/^\s*<mj-section\b/i.test(content)) {
|
|
3927
|
+
added = sections.append(content, afterSection)[0];
|
|
3928
|
+
} else if (column && selected) {
|
|
3929
|
+
const at = selected === column ? {} : { at: childOf(column, selected).index() + 1 };
|
|
3930
|
+
added = column.append(content, at)[0];
|
|
3931
|
+
} else {
|
|
3932
|
+
const [wrapper] = sections.append(
|
|
3933
|
+
`<mj-section><mj-column>${content}</mj-column></mj-section>`,
|
|
3934
|
+
afterSection
|
|
3935
|
+
);
|
|
3936
|
+
added = wrapper?.components().at(0)?.components().at(0);
|
|
1284
3937
|
}
|
|
1285
|
-
return
|
|
3938
|
+
if (!added) return void 0;
|
|
3939
|
+
editor.select(added);
|
|
3940
|
+
if (block.get("activate")) added.getView()?.onActive(void 0);
|
|
3941
|
+
return added;
|
|
1286
3942
|
}
|
|
1287
3943
|
|
|
1288
|
-
// src/components/editor/
|
|
1289
|
-
var
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
3944
|
+
// src/components/editor/blocks.ts
|
|
3945
|
+
var EDITABLE_TAGS = /* @__PURE__ */ new Set([
|
|
3946
|
+
"mj-section",
|
|
3947
|
+
"mj-group",
|
|
3948
|
+
"mj-column",
|
|
3949
|
+
"mj-text",
|
|
3950
|
+
"mj-image",
|
|
3951
|
+
"mj-button",
|
|
3952
|
+
"mj-divider",
|
|
3953
|
+
"mj-spacer",
|
|
3954
|
+
"mj-social",
|
|
3955
|
+
"mj-social-element",
|
|
3956
|
+
"mj-navbar",
|
|
3957
|
+
"mj-navbar-link"
|
|
3958
|
+
]);
|
|
3959
|
+
var CONTENT_TAGS = /* @__PURE__ */ new Set(["mj-text", "mj-button", "mj-social-element", "mj-navbar-link"]);
|
|
3960
|
+
var EDITOR_INTERNALS = /data-gjs-|__p\b|custom-name/i;
|
|
3961
|
+
var DROPPED_MARKUP = /<(?:script|style|link|meta|iframe|object|embed|form)\b/i;
|
|
3962
|
+
var INLINE_IMAGE = /\b(?:src|srcset|background-url|icon)\s*=\s*(?:"[^"]*data:|'[^']*data:|data:)/i;
|
|
3963
|
+
var LINK_IN_MARKUP = /[\s/"'](?:xlink:)?(?:href|action|formaction)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
3964
|
+
var validator;
|
|
3965
|
+
function blockProblems(mjml, editor = validator ??= import_grapesjs.default.init({
|
|
3966
|
+
headless: true,
|
|
3967
|
+
storageManager: false,
|
|
3968
|
+
telemetry: false,
|
|
3969
|
+
plugins: [import_grapesjs_mjml.default]
|
|
3970
|
+
})) {
|
|
3971
|
+
if (EDITOR_INTERNALS.test(mjml)) return ["it contains GrapesJS's own attributes"];
|
|
3972
|
+
if (INLINE_IMAGE.test(mjml)) return ["it inlines an image as a data: URL"];
|
|
3973
|
+
let tree;
|
|
3974
|
+
try {
|
|
3975
|
+
tree = editor.runCommand("mjml-code-to-html", {
|
|
3976
|
+
mjml: `<mjml><mj-body>${withClosedTags(mjml)}</mj-body></mjml>`,
|
|
3977
|
+
validationLevel: "strict"
|
|
3978
|
+
}).json;
|
|
3979
|
+
} catch (e) {
|
|
3980
|
+
const errors = e.errors;
|
|
3981
|
+
if (!errors) throw e;
|
|
3982
|
+
return errors.map((error) => error.formattedMessage ?? error.message ?? String(error));
|
|
3983
|
+
}
|
|
3984
|
+
const problems = [];
|
|
3985
|
+
const top = tree.children?.find((c) => c.tagName === "mj-body")?.children ?? [];
|
|
3986
|
+
if (top.length !== 1 || top[0].tagName !== "mj-section") {
|
|
3987
|
+
problems.push("it is not exactly one mj-section");
|
|
3988
|
+
}
|
|
3989
|
+
const visit = (node, parent) => {
|
|
3990
|
+
const tag = node.tagName;
|
|
3991
|
+
const attributes = node.attributes ?? {};
|
|
3992
|
+
const content = node.content ?? "";
|
|
3993
|
+
if (!EDITABLE_TAGS.has(tag)) problems.push(`${tag} is not editable here`);
|
|
3994
|
+
const cssClass = attributes["css-class"] ?? "";
|
|
3995
|
+
if (ID_RE.sec.test(cssClass) || ID_RE.obj.test(cssClass)) {
|
|
3996
|
+
problems.push(`${tag} carries a sec-* or obj-* id`);
|
|
3997
|
+
}
|
|
3998
|
+
if ("mj-class" in attributes) problems.push(`${tag} uses mj-class`);
|
|
3999
|
+
const links = [attributes.href ?? ""];
|
|
4000
|
+
for (const match of content.matchAll(LINK_IN_MARKUP)) {
|
|
4001
|
+
links.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
4002
|
+
}
|
|
4003
|
+
const linked = tag === "mj-navbar-link" ? (0, import_core2.isSafeNavbarLink)(parent?.attributes?.["base-url"] ?? "", attributes.href ?? "") : true;
|
|
4004
|
+
if (!linked || !links.every(import_core2.isSafeLink)) {
|
|
4005
|
+
problems.push(`${tag} has a link scheme mail must not carry`);
|
|
4006
|
+
}
|
|
4007
|
+
if (CONTENT_TAGS.has(tag) ? /<mj-/i.test(content) || DROPPED_MARKUP.test(content) : content.trim()) {
|
|
4008
|
+
problems.push(`${tag} holds content the editor does not keep`);
|
|
4009
|
+
}
|
|
4010
|
+
node.children?.forEach((child) => visit(child, node));
|
|
4011
|
+
};
|
|
4012
|
+
top.forEach((node) => visit(node));
|
|
4013
|
+
return problems;
|
|
4014
|
+
}
|
|
4015
|
+
var escapeHtml3 = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4016
|
+
function registerBlocks(editor, blocks, category, media) {
|
|
4017
|
+
for (const block of blocks) {
|
|
4018
|
+
const problems = editor.Blocks.get(block.id) ? [`the id is already taken`] : blockProblems(block.mjml, editor);
|
|
4019
|
+
if (problems.length > 0) {
|
|
4020
|
+
console.error(`Block "${block.id}" is left out of the palette: ${problems.join("; ")}`);
|
|
4021
|
+
continue;
|
|
4022
|
+
}
|
|
4023
|
+
editor.Blocks.add(block.id, {
|
|
4024
|
+
// GrapesJS writes the label into the tile as markup.
|
|
4025
|
+
label: escapeHtml3(block.label),
|
|
4026
|
+
category,
|
|
4027
|
+
media: block.media ?? media,
|
|
4028
|
+
content: withClosedTags(block.mjml),
|
|
4029
|
+
attributes: tileAttributes(block.id)
|
|
4030
|
+
});
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
// src/components/editor/builtin-blocks.ts
|
|
4035
|
+
var BUILTIN_BLOCKS = [
|
|
4036
|
+
"mj-1-column",
|
|
4037
|
+
"mj-2-columns",
|
|
4038
|
+
"mj-3-columns",
|
|
4039
|
+
"mj-text",
|
|
4040
|
+
"mj-image",
|
|
4041
|
+
"mj-button",
|
|
4042
|
+
"mj-divider",
|
|
4043
|
+
"mj-spacer",
|
|
4044
|
+
"mj-social-group",
|
|
4045
|
+
"mj-navbar"
|
|
4046
|
+
];
|
|
4047
|
+
var LAYOUT_BLOCKS = /* @__PURE__ */ new Set(["mj-1-column", "mj-2-columns", "mj-3-columns"]);
|
|
4048
|
+
var CONTENT2 = {
|
|
4049
|
+
// The plugin's version links Google+ and shares the page: a bare network name makes the
|
|
4050
|
+
// icon open a "share this link" dialog, while `-noshare` links to the profile a footer
|
|
4051
|
+
// means. Without `alt` each link is an unnamed image to a screen reader.
|
|
4052
|
+
"mj-social-group": `<mj-social icon-size="24px" mode="horizontal">
|
|
4053
|
+
<mj-social-element name="facebook-noshare" href="https://facebook.com/" alt="Facebook"></mj-social-element>
|
|
4054
|
+
<mj-social-element name="instagram-noshare" href="https://instagram.com/" alt="Instagram"></mj-social-element>
|
|
4055
|
+
<mj-social-element name="x-noshare" href="https://x.com/" alt="X"></mj-social-element>
|
|
4056
|
+
</mj-social>`,
|
|
4057
|
+
// The plugin's sample is a via.placeholder.com picture, stored in the email and loaded by
|
|
4058
|
+
// every reader's mail client. Dropping one opens the image picker instead.
|
|
4059
|
+
"mj-image": "<mj-image></mj-image>"
|
|
4060
|
+
};
|
|
4061
|
+
function builtinBlock(labels) {
|
|
4062
|
+
return (id) => ({
|
|
4063
|
+
label: labels.blockLabels[id] ?? id,
|
|
4064
|
+
// By id: a regex on the label broke as soon as a host translated it.
|
|
4065
|
+
category: LAYOUT_BLOCKS.has(id) ? labels.blockCategoryLayout : labels.blockCategoryContent,
|
|
4066
|
+
...id in CONTENT2 && { content: CONTENT2[id] },
|
|
4067
|
+
attributes: tileAttributes(id)
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
var CANVAS_CSS = 'img:not([src]), img[src=""] { min-height: 120px; background: #f4f4f5; outline: 1px dashed #a1a1aa; outline-offset: -1px; }';
|
|
4071
|
+
|
|
4072
|
+
// src/components/editor/table-type.ts
|
|
4073
|
+
var import_core3 = require("@mjml-agent-editor/core");
|
|
4074
|
+
var DROPPED = "script, style, link, iframe, frame, object, embed, applet, base, meta, animate, set, svg, math, template";
|
|
4075
|
+
var LINK_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "xlink:href", "action", "formaction"]);
|
|
4076
|
+
function cleanRows(rows) {
|
|
4077
|
+
rows.querySelectorAll(DROPPED).forEach((el) => el.remove());
|
|
4078
|
+
rows.querySelectorAll("*").forEach((el) => {
|
|
4079
|
+
for (const { name, value } of [...el.attributes]) {
|
|
4080
|
+
const lower = name.toLowerCase();
|
|
4081
|
+
const script = /^(java|vb)script:/i.test(value.replace(/[\u0000-\u0020]/g, ""));
|
|
4082
|
+
if (lower.startsWith("on") || script || LINK_ATTRIBUTES.has(lower) && !(0, import_core3.isSafeLink)(value)) {
|
|
4083
|
+
el.removeAttribute(name);
|
|
1313
4084
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
4085
|
+
}
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
var ROW_STYLE = "pointer-events: all; display: table; width: 100%;";
|
|
4089
|
+
function tablePreview(doc, attributes, rows) {
|
|
4090
|
+
const attr = (name, fallback) => String(attributes[name] ?? fallback);
|
|
4091
|
+
const template = doc.createElement("template");
|
|
4092
|
+
template.innerHTML = `<table>${rows}</table>`;
|
|
4093
|
+
const table = doc.createElement("table");
|
|
4094
|
+
table.setAttribute("cellpadding", String(parseInt(attr("cellpadding", "0"), 10) || 0));
|
|
4095
|
+
table.setAttribute("cellspacing", String(parseInt(attr("cellspacing", "0"), 10) || 0));
|
|
4096
|
+
Object.assign(table.style, {
|
|
4097
|
+
width: attr("width", "100%"),
|
|
4098
|
+
border: attr("border", "none"),
|
|
4099
|
+
color: attr("color", "#000000"),
|
|
4100
|
+
fontFamily: attr("font-family", "Ubuntu, Helvetica, Arial, sans-serif"),
|
|
4101
|
+
fontSize: attr("font-size", "13px"),
|
|
4102
|
+
lineHeight: attr("line-height", "22px"),
|
|
4103
|
+
tableLayout: attr("table-layout", "auto")
|
|
4104
|
+
});
|
|
4105
|
+
for (const row of template.content.querySelectorAll("tr")) {
|
|
4106
|
+
const tr = table.insertRow();
|
|
4107
|
+
for (const cell2 of row.querySelectorAll(":scope > th, :scope > td")) {
|
|
4108
|
+
const copy = doc.createElement(cell2.tagName.toLowerCase());
|
|
4109
|
+
copy.textContent = cell2.textContent;
|
|
4110
|
+
const align = cell2.getAttribute("align");
|
|
4111
|
+
if (align && /^(left|right|center)$/i.test(align)) copy.setAttribute("align", align);
|
|
4112
|
+
tr.append(copy);
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
const cell = doc.createElement("td");
|
|
4116
|
+
cell.style.padding = attr("padding", "10px 25px");
|
|
4117
|
+
cell.append(table);
|
|
4118
|
+
return cell;
|
|
4119
|
+
}
|
|
4120
|
+
function addTableType(editor) {
|
|
4121
|
+
const base = editor.Components.getType("default").view.prototype;
|
|
4122
|
+
editor.Components.addType("mj-table", {
|
|
4123
|
+
isComponent: (el) => {
|
|
4124
|
+
if (el.tagName?.toLowerCase() !== "mj-table") return false;
|
|
4125
|
+
const rows = el.querySelector(":scope > template");
|
|
4126
|
+
if (rows) cleanRows(rows.content);
|
|
4127
|
+
return { type: "mj-table", content: rows?.innerHTML ?? "", components: [] };
|
|
4128
|
+
},
|
|
4129
|
+
model: { defaults: { tagName: "mj-table", droppable: false, editable: false } },
|
|
4130
|
+
view: {
|
|
4131
|
+
tagName: "tr",
|
|
4132
|
+
// GrapesJS clears the element's attributes, style among them, when the model's change.
|
|
4133
|
+
updateAttributes(...args) {
|
|
4134
|
+
base.updateAttributes.apply(this, args);
|
|
4135
|
+
this.el.style.cssText = ROW_STYLE;
|
|
4136
|
+
},
|
|
4137
|
+
updateContent() {
|
|
4138
|
+
const { el, model } = this;
|
|
4139
|
+
el.style.cssText = ROW_STYLE;
|
|
4140
|
+
el.replaceChildren(
|
|
4141
|
+
tablePreview(el.ownerDocument, model.getAttributes(), String(model.get("content") ?? ""))
|
|
4142
|
+
);
|
|
1324
4143
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
4144
|
+
}
|
|
4145
|
+
});
|
|
1327
4146
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
4147
|
+
|
|
4148
|
+
// src/components/editor/plugin-types.ts
|
|
4149
|
+
var COLUMN_CHILDREN = /* @__PURE__ */ new Set([
|
|
4150
|
+
"mj-accordion",
|
|
4151
|
+
"mj-button",
|
|
4152
|
+
"mj-carousel",
|
|
4153
|
+
"mj-divider",
|
|
4154
|
+
"mj-image",
|
|
4155
|
+
"mj-navbar",
|
|
4156
|
+
"mj-raw",
|
|
4157
|
+
"mj-social",
|
|
4158
|
+
"mj-spacer",
|
|
4159
|
+
"mj-table",
|
|
4160
|
+
"mj-text"
|
|
4161
|
+
]);
|
|
4162
|
+
function fixPluginTypes(editor) {
|
|
4163
|
+
addTableType(editor);
|
|
4164
|
+
for (const type of editor.Components.getTypes()) {
|
|
4165
|
+
if (!type.id.startsWith("mj-") || type.id === "mj-table") continue;
|
|
4166
|
+
const base = type.model.prototype.getAttrToHTML;
|
|
4167
|
+
editor.Components.addType(type.id, {
|
|
4168
|
+
model: {
|
|
4169
|
+
defaults: {
|
|
4170
|
+
"style-default": {},
|
|
4171
|
+
...type.id === "mj-column" && {
|
|
4172
|
+
droppable: (source) => COLUMN_CHILDREN.has(String(source.get("tagName")))
|
|
4173
|
+
},
|
|
4174
|
+
// GrapesJS's own is an SVG's markup, which the canvas wrote into `src` and saved.
|
|
4175
|
+
...type.id === "mj-image" && { src: "" }
|
|
4176
|
+
},
|
|
4177
|
+
getAttrToHTML() {
|
|
4178
|
+
const attributes = base.call(this);
|
|
4179
|
+
delete attributes.__p;
|
|
4180
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
4181
|
+
if (typeof value === "string") attributes[name] = value.replace(/"/g, """);
|
|
4182
|
+
}
|
|
4183
|
+
return attributes;
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
});
|
|
4187
|
+
}
|
|
1334
4188
|
}
|
|
1335
4189
|
|
|
1336
4190
|
// src/components/editor/EmailEditor.tsx
|
|
1337
|
-
var
|
|
1338
|
-
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1339
|
-
var SEC_ID_RE = /\bsec-([A-Za-z0-9_-]+)\b/;
|
|
1340
|
-
var OBJ_ID_RE = /\bobj-([A-Za-z0-9_-]+)\b/;
|
|
4191
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1341
4192
|
var TYPE_LABEL = {
|
|
1342
4193
|
"mj-text": "Text",
|
|
1343
4194
|
"mj-button": "Button",
|
|
1344
4195
|
"mj-image": "Image",
|
|
1345
4196
|
"mj-column": "Column"
|
|
1346
4197
|
};
|
|
1347
|
-
function
|
|
4198
|
+
function tagOf6(c) {
|
|
1348
4199
|
return String(c.get("tagName") || c.get("type") || "");
|
|
1349
4200
|
}
|
|
1350
4201
|
var SPARKLES_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>';
|
|
1351
4202
|
var IMAGE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"/><path d="m14 19.5 3-3 3 3"/><path d="M17 22v-5.5"/><circle cx="9" cy="9" r="2"/></svg>';
|
|
4203
|
+
var SECTION_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="7" x="3" y="3" rx="1"/><rect width="9" height="7" x="3" y="14" rx="1"/><rect width="5" height="7" x="16" y="14" rx="1"/></svg>';
|
|
1352
4204
|
function isSection(c) {
|
|
1353
|
-
return
|
|
4205
|
+
return tagOf6(c) === "mj-section";
|
|
1354
4206
|
}
|
|
1355
4207
|
function isCommentable(c) {
|
|
1356
|
-
const t =
|
|
4208
|
+
const t = tagOf6(c);
|
|
1357
4209
|
return t === "mj-section" || t in TYPE_LABEL;
|
|
1358
4210
|
}
|
|
1359
|
-
function classMatch(c, re) {
|
|
1360
|
-
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
1361
|
-
return cssClass.match(re)?.[1] ?? null;
|
|
1362
|
-
}
|
|
1363
|
-
function sectionIdOf(c) {
|
|
1364
|
-
return classMatch(c, SEC_ID_RE);
|
|
1365
|
-
}
|
|
1366
|
-
function addIdClass(c, prefix) {
|
|
1367
|
-
const existing = classMatch(c, prefix === "sec" ? SEC_ID_RE : OBJ_ID_RE);
|
|
1368
|
-
if (existing) return existing;
|
|
1369
|
-
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
1370
|
-
const id = Math.random().toString(36).slice(2, 10);
|
|
1371
|
-
c.addAttributes({ "css-class": `${cssClass} ${prefix}-${id}`.trim() });
|
|
1372
|
-
return id;
|
|
1373
|
-
}
|
|
1374
4211
|
function closestSection(c) {
|
|
1375
4212
|
let cur = c;
|
|
1376
4213
|
while (cur) {
|
|
@@ -1379,27 +4216,12 @@ function closestSection(c) {
|
|
|
1379
4216
|
}
|
|
1380
4217
|
return null;
|
|
1381
4218
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
"mj-group": "Group",
|
|
1386
|
-
"mj-text": "Text",
|
|
1387
|
-
"mj-button": "Button",
|
|
1388
|
-
"mj-image": "Image",
|
|
1389
|
-
"mj-divider": "Divider",
|
|
1390
|
-
"mj-spacer": "Spacer",
|
|
1391
|
-
"mj-hero": "Hero",
|
|
1392
|
-
"mj-navbar": "Navbar",
|
|
1393
|
-
"mj-social": "Social",
|
|
1394
|
-
"mj-social-element": "Social",
|
|
1395
|
-
"mj-wrapper": "Wrapper"
|
|
1396
|
-
};
|
|
1397
|
-
function crumbName(c) {
|
|
1398
|
-
const t = tagOf(c);
|
|
1399
|
-
return CRUMB_NAME[t] ?? t.replace(/^mj-/, "") ?? "Element";
|
|
4219
|
+
function crumbName(c, labels) {
|
|
4220
|
+
const t = tagOf6(c);
|
|
4221
|
+
return labels.elementNames[t] ?? t.replace(/^mj-/, "");
|
|
1400
4222
|
}
|
|
1401
4223
|
function objectLabel(c) {
|
|
1402
|
-
const base = TYPE_LABEL[
|
|
4224
|
+
const base = TYPE_LABEL[tagOf6(c)] ?? tagOf6(c);
|
|
1403
4225
|
const text = String(c.getEl?.()?.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 30);
|
|
1404
4226
|
return text ? `${base}: \u201C${text}\u201D` : base;
|
|
1405
4227
|
}
|
|
@@ -1431,75 +4253,6 @@ function wrapSelectionStyle(el, style) {
|
|
|
1431
4253
|
} catch {
|
|
1432
4254
|
}
|
|
1433
4255
|
}
|
|
1434
|
-
function setupStyleFields(editor, labels) {
|
|
1435
|
-
const sm = editor.StyleManager;
|
|
1436
|
-
const base = sm.getType("base").view;
|
|
1437
|
-
const documentColors = () => {
|
|
1438
|
-
const source = `${editor.getHtml()} ${editor.getCss() ?? ""}`;
|
|
1439
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1440
|
-
const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
|
|
1441
|
-
for (const match of source.matchAll(colorValue)) {
|
|
1442
|
-
const hex = toHex(match[1], { opaque: true });
|
|
1443
|
-
if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
|
|
1444
|
-
}
|
|
1445
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
|
|
1446
|
-
};
|
|
1447
|
-
const ownValue = (property) => property.hasValue?.({ noParent: true }) ? String(property.getValue?.() ?? "") : "";
|
|
1448
|
-
const reactView = (field) => base.extend({
|
|
1449
|
-
create({ property }) {
|
|
1450
|
-
const el = document.createElement("div");
|
|
1451
|
-
el.addEventListener("change", (event) => event.stopPropagation());
|
|
1452
|
-
const root = (0, import_client.createRoot)(el);
|
|
1453
|
-
this.__root = root;
|
|
1454
|
-
this.__render = () => root.render(field(property, ownValue(property)));
|
|
1455
|
-
this.__render();
|
|
1456
|
-
return el;
|
|
1457
|
-
},
|
|
1458
|
-
update() {
|
|
1459
|
-
this.__render?.();
|
|
1460
|
-
},
|
|
1461
|
-
destroy() {
|
|
1462
|
-
const root = this.__root;
|
|
1463
|
-
this.__root = void 0;
|
|
1464
|
-
this.__render = void 0;
|
|
1465
|
-
setTimeout(() => root?.unmount());
|
|
1466
|
-
}
|
|
1467
|
-
});
|
|
1468
|
-
sm.addType("color", {
|
|
1469
|
-
view: reactView((property, value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1470
|
-
ColorField,
|
|
1471
|
-
{
|
|
1472
|
-
value,
|
|
1473
|
-
placeholder: String(property.getDefaultValue?.() ?? ""),
|
|
1474
|
-
documentColors,
|
|
1475
|
-
labels,
|
|
1476
|
-
onChange: (next, partial) => property.upValue(next, { partial })
|
|
1477
|
-
}
|
|
1478
|
-
))
|
|
1479
|
-
});
|
|
1480
|
-
sm.addType("file", {
|
|
1481
|
-
view: reactView((property, value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1482
|
-
ImageField,
|
|
1483
|
-
{
|
|
1484
|
-
value,
|
|
1485
|
-
labels,
|
|
1486
|
-
onClear: () => property.upValue(""),
|
|
1487
|
-
onChoose: () => {
|
|
1488
|
-
const am = editor.AssetManager;
|
|
1489
|
-
am.open({
|
|
1490
|
-
types: ["image"],
|
|
1491
|
-
current: imageUrl(value) ?? void 0,
|
|
1492
|
-
select(asset, complete) {
|
|
1493
|
-
const url = typeof asset === "string" ? asset : asset.getSrc();
|
|
1494
|
-
property.upValue(url, { partial: !complete });
|
|
1495
|
-
if (complete) am.close();
|
|
1496
|
-
}
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
))
|
|
1501
|
-
});
|
|
1502
|
-
}
|
|
1503
4256
|
function setupRichText(editor, labels) {
|
|
1504
4257
|
const rte = editor.RichTextEditor;
|
|
1505
4258
|
if (!rte || rte.__customActions) return;
|
|
@@ -1536,26 +4289,45 @@ function setupRichText(editor, labels) {
|
|
|
1536
4289
|
result: (r, action) => r.exec("hiliteColor", val(action, "input"))
|
|
1537
4290
|
});
|
|
1538
4291
|
}
|
|
1539
|
-
function EmailEditor({
|
|
4292
|
+
function EmailEditor({
|
|
4293
|
+
docId,
|
|
4294
|
+
onReady,
|
|
4295
|
+
commentsRefresh,
|
|
4296
|
+
onOpenCountChange,
|
|
4297
|
+
blocks = []
|
|
4298
|
+
}) {
|
|
1540
4299
|
const documents = useDocumentStore();
|
|
1541
4300
|
const labels = useLabels();
|
|
1542
|
-
const loadingRef = (0,
|
|
1543
|
-
const saveStatusRef = (0,
|
|
1544
|
-
const stateListeners = (0,
|
|
1545
|
-
const [loading, setLoading] = (0,
|
|
1546
|
-
const [sidebarView, setSidebarView] = (0,
|
|
1547
|
-
const
|
|
1548
|
-
const
|
|
1549
|
-
|
|
4301
|
+
const loadingRef = (0, import_react10.useRef)(false);
|
|
4302
|
+
const saveStatusRef = (0, import_react10.useRef)("idle");
|
|
4303
|
+
const stateListeners = (0, import_react10.useRef)(/* @__PURE__ */ new Set());
|
|
4304
|
+
const [loading, setLoading] = (0, import_react10.useState)(true);
|
|
4305
|
+
const [sidebarView, setSidebarView] = (0, import_react10.useState)("blocks");
|
|
4306
|
+
const blockSearchRef = (0, import_react10.useRef)(null);
|
|
4307
|
+
const onBlockSearchKey = (e) => {
|
|
4308
|
+
if (readOnlyRef.current || !opensBlockSearch(e)) return;
|
|
4309
|
+
e.preventDefault();
|
|
4310
|
+
setSidebarView("blocks");
|
|
4311
|
+
requestAnimationFrame(() => blockSearchRef.current?.focus());
|
|
4312
|
+
};
|
|
4313
|
+
const [composeTarget, setComposeTarget] = (0, import_react10.useState)(null);
|
|
4314
|
+
const selectionSource = (0, import_react10.useRef)("other");
|
|
4315
|
+
const editorRef = (0, import_react10.useRef)(null);
|
|
1550
4316
|
const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
|
|
1551
|
-
const readOnlyRef = (0,
|
|
1552
|
-
const applyReadOnly = (0,
|
|
4317
|
+
const readOnlyRef = (0, import_react10.useRef)(false);
|
|
4318
|
+
const applyReadOnly = (0, import_react10.useRef)(async () => {
|
|
1553
4319
|
});
|
|
1554
|
-
(0,
|
|
1555
|
-
(0,
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
4320
|
+
const [turn] = (0, import_react10.useState)(createTurnFollower);
|
|
4321
|
+
(0, import_react10.useEffect)(() => turn.turn(turnRunning), [turn, turnRunning]);
|
|
4322
|
+
(0, import_react10.useEffect)(() => {
|
|
4323
|
+
turn.mount();
|
|
4324
|
+
return () => {
|
|
4325
|
+
turn.unmount();
|
|
4326
|
+
setLocked(false);
|
|
4327
|
+
};
|
|
4328
|
+
}, [turn, setLocked]);
|
|
4329
|
+
const [crumbs, setCrumbs] = (0, import_react10.useState)([]);
|
|
4330
|
+
const [picker, setPicker] = (0, import_react10.useState)(
|
|
1559
4331
|
null
|
|
1560
4332
|
);
|
|
1561
4333
|
const onEditor = async (editor) => {
|
|
@@ -1564,7 +4336,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1564
4336
|
const chain = [];
|
|
1565
4337
|
let cur = c;
|
|
1566
4338
|
while (cur) {
|
|
1567
|
-
const t =
|
|
4339
|
+
const t = tagOf6(cur);
|
|
1568
4340
|
if (!t || t === "mj-body" || t === "wrapper" || t === "Wrapper") break;
|
|
1569
4341
|
chain.unshift(cur);
|
|
1570
4342
|
cur = cur.parent() ?? void 0;
|
|
@@ -1613,7 +4385,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1613
4385
|
if (isSection(c)) addIdClass(c, "sec");
|
|
1614
4386
|
if (!isCommentable(c)) return;
|
|
1615
4387
|
const toolbar = [...c.get("toolbar") ?? []];
|
|
1616
|
-
if (
|
|
4388
|
+
if (tagOf6(c) === "mj-image" && !toolbar.some((t) => t.command === "open-image-picker")) {
|
|
1617
4389
|
toolbar.push({
|
|
1618
4390
|
command: "open-image-picker",
|
|
1619
4391
|
label: IMAGE_SVG,
|
|
@@ -1621,11 +4393,11 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1621
4393
|
});
|
|
1622
4394
|
c.set("toolbar", toolbar);
|
|
1623
4395
|
}
|
|
1624
|
-
if (!toolbar.some((t) => t.command === "open-comments")) {
|
|
4396
|
+
if (closestSection(c) && !toolbar.some((t) => t.command === "open-comments")) {
|
|
1625
4397
|
toolbar.push({
|
|
1626
4398
|
command: "open-comments",
|
|
1627
4399
|
label: SPARKLES_SVG,
|
|
1628
|
-
attributes: { title:
|
|
4400
|
+
attributes: { title: labels.commentOnElement, "data-role": "sparkles" }
|
|
1629
4401
|
});
|
|
1630
4402
|
c.set("toolbar", toolbar);
|
|
1631
4403
|
}
|
|
@@ -1633,7 +4405,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1633
4405
|
editor.Commands.add("open-image-picker", {
|
|
1634
4406
|
run(ed) {
|
|
1635
4407
|
const target = ed.getSelected();
|
|
1636
|
-
if (!target ||
|
|
4408
|
+
if (!target || tagOf6(target) !== "mj-image") return;
|
|
1637
4409
|
const am = ed.AssetManager;
|
|
1638
4410
|
am.open({
|
|
1639
4411
|
target,
|
|
@@ -1652,21 +4424,29 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1652
4424
|
}
|
|
1653
4425
|
});
|
|
1654
4426
|
setupRichText(editor, labels);
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
4427
|
+
fixPluginTypes(editor);
|
|
4428
|
+
registerBlocks(editor, blocks, labels.blockCategorySections, SECTION_SVG);
|
|
4429
|
+
setupLayerNames(editor, labels);
|
|
4430
|
+
const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-column"]);
|
|
4431
|
+
const showBodyInLayers = () => {
|
|
4432
|
+
const body = findByTag("mj-body");
|
|
4433
|
+
if (!body) return;
|
|
4434
|
+
editor.Layers.setRoot(body);
|
|
4435
|
+
body.onAll((c) => {
|
|
4436
|
+
if (STRUCTURE.has(tagOf6(c))) editor.Layers.setOpen(c, true);
|
|
4437
|
+
});
|
|
1661
4438
|
};
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
(
|
|
1665
|
-
|
|
4439
|
+
const keepStructureOpen = () => {
|
|
4440
|
+
const layers = editor.Layers;
|
|
4441
|
+
const opened = layers.model?.get("opened") ?? {};
|
|
4442
|
+
for (const [cid, c] of Object.entries(opened)) {
|
|
4443
|
+
if (STRUCTURE.has(tagOf6(c))) delete opened[cid];
|
|
1666
4444
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
4445
|
+
};
|
|
4446
|
+
editor.on("component:add", (c) => {
|
|
4447
|
+
if (!loadingRef.current) freshenCopiedIds(editor.getWrapper(), c);
|
|
4448
|
+
decorate(c);
|
|
4449
|
+
});
|
|
1670
4450
|
editor.on("load", () => {
|
|
1671
4451
|
const body = editor.Canvas.getBody();
|
|
1672
4452
|
body?.addEventListener(
|
|
@@ -1676,45 +4456,28 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1676
4456
|
},
|
|
1677
4457
|
true
|
|
1678
4458
|
);
|
|
4459
|
+
editor.Canvas.getDocument()?.addEventListener("keydown", onBlockSearchKey);
|
|
4460
|
+
showBodyInLayers();
|
|
1679
4461
|
});
|
|
1680
4462
|
editor.on("component:selected", (c) => {
|
|
4463
|
+
keepStructureOpen();
|
|
1681
4464
|
if (selectionSource.current === "canvas") setSidebarView("settings");
|
|
1682
4465
|
selectionSource.current = "other";
|
|
1683
4466
|
buildCrumbs(c);
|
|
1684
|
-
setSelectedImageSrc(c && tagOf(c) === "mj-image" ? String(c.get("src") ?? "") : null);
|
|
1685
|
-
if (c?.get("type") === "mj-image" && !c.getTrait("src")) {
|
|
1686
|
-
c.addTrait(
|
|
1687
|
-
{
|
|
1688
|
-
type: "text",
|
|
1689
|
-
name: "src",
|
|
1690
|
-
label: "Image (URL)",
|
|
1691
|
-
placeholder: "https://\u2026",
|
|
1692
|
-
changeProp: true
|
|
1693
|
-
},
|
|
1694
|
-
{ at: 0 }
|
|
1695
|
-
);
|
|
1696
|
-
}
|
|
1697
4467
|
});
|
|
1698
4468
|
editor.on("component:deselected", () => {
|
|
1699
|
-
|
|
1700
|
-
buildCrumbs(rest);
|
|
1701
|
-
setSelectedImageSrc(
|
|
1702
|
-
rest && tagOf(rest) === "mj-image" ? String(rest.get("src") ?? "") : null
|
|
1703
|
-
);
|
|
1704
|
-
});
|
|
1705
|
-
editor.on("component:update:src", (c) => {
|
|
1706
|
-
if (c === editor.getSelected()) setSelectedImageSrc(String(c.get("src") ?? ""));
|
|
4469
|
+
buildCrumbs(editor.getSelected());
|
|
1707
4470
|
});
|
|
1708
|
-
|
|
4471
|
+
const baseline = createSaveBaseline();
|
|
1709
4472
|
const save = async () => {
|
|
1710
4473
|
const view = editor.getEditing()?.getView();
|
|
1711
4474
|
await view?.syncContent?.();
|
|
1712
4475
|
const mjml = editor.getHtml();
|
|
1713
|
-
if (mjml
|
|
4476
|
+
if (!baseline.changed(mjml)) return;
|
|
1714
4477
|
setSave("saving");
|
|
1715
4478
|
try {
|
|
1716
4479
|
await documents.save(docId, { mjml, projectData: editor.getProjectData() });
|
|
1717
|
-
|
|
4480
|
+
baseline.saved(mjml);
|
|
1718
4481
|
setSave("saved");
|
|
1719
4482
|
} catch (e) {
|
|
1720
4483
|
setSave("error");
|
|
@@ -1745,6 +4508,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1745
4508
|
});
|
|
1746
4509
|
}
|
|
1747
4510
|
const lock = (on) => {
|
|
4511
|
+
if (!turn.mounted) return;
|
|
1748
4512
|
readOnlyRef.current = on;
|
|
1749
4513
|
setLocked(on);
|
|
1750
4514
|
notifyState();
|
|
@@ -1770,24 +4534,36 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1770
4534
|
return;
|
|
1771
4535
|
}
|
|
1772
4536
|
lock(false);
|
|
4537
|
+
queue.schedule();
|
|
1773
4538
|
};
|
|
1774
4539
|
const reload = () => queue.run(async () => {
|
|
1775
4540
|
const fresh = await documents.get(docId);
|
|
1776
|
-
if (fresh.mjml !== stored) loadMjml(fresh.mjml);
|
|
4541
|
+
if (fresh.mjml !== baseline.stored) loadMjml(fresh.mjml);
|
|
1777
4542
|
});
|
|
1778
4543
|
const loadMjml = (mjml) => {
|
|
1779
4544
|
loadingRef.current = true;
|
|
4545
|
+
let repaired = false;
|
|
4546
|
+
const adds = !mjml || (0, import_core4.listSections)(mjml).some((s) => !s.sectionId);
|
|
1780
4547
|
try {
|
|
1781
4548
|
queue.cancel();
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
4549
|
+
baseline.load(mjml, null);
|
|
4550
|
+
const source = sourceForEditor(mjml || import_core4.STARTER_MJML);
|
|
4551
|
+
repaired = source.repaired;
|
|
4552
|
+
editor.setComponents(source.mjml);
|
|
4553
|
+
editor.getWrapper()?.onAll((c) => {
|
|
4554
|
+
const illegal = illegalAttributes(tagOf6(c), c.getAttributes());
|
|
4555
|
+
if (illegal.length === 0) return;
|
|
4556
|
+
c.removeAttributes(illegal);
|
|
4557
|
+
repaired = true;
|
|
4558
|
+
});
|
|
4559
|
+
if (!repaired && !adds) baseline.load(mjml, editor.getHtml());
|
|
4560
|
+
showBodyInLayers();
|
|
4561
|
+
editor.UndoManager.clear();
|
|
1788
4562
|
} finally {
|
|
1789
4563
|
loadingRef.current = false;
|
|
1790
4564
|
}
|
|
4565
|
+
notifyState();
|
|
4566
|
+
if ((repaired || adds) && !readOnlyRef.current) queue.schedule();
|
|
1791
4567
|
};
|
|
1792
4568
|
const ensureHighlightStyles = () => {
|
|
1793
4569
|
const cdoc = editor.Canvas.getDocument();
|
|
@@ -1809,7 +4585,10 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1809
4585
|
cdoc.head.appendChild(style);
|
|
1810
4586
|
};
|
|
1811
4587
|
const sectionEl = (sectionId) => {
|
|
1812
|
-
|
|
4588
|
+
let comp;
|
|
4589
|
+
editor.getWrapper()?.onAll((c) => {
|
|
4590
|
+
if (!comp && isSection(c) && sectionIdOf(c) === sectionId) comp = c;
|
|
4591
|
+
});
|
|
1813
4592
|
return comp?.getEl?.() ?? void 0;
|
|
1814
4593
|
};
|
|
1815
4594
|
const compiledHtml = () => {
|
|
@@ -1820,9 +4599,9 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1820
4599
|
return editor.getHtml();
|
|
1821
4600
|
}
|
|
1822
4601
|
};
|
|
4602
|
+
turn.ready((on) => void applyReadOnly.current(on));
|
|
1823
4603
|
try {
|
|
1824
|
-
|
|
1825
|
-
loadMjml(doc.mjml);
|
|
4604
|
+
await queue.run(async () => loadMjml((await documents.get(docId)).mjml));
|
|
1826
4605
|
} catch (e) {
|
|
1827
4606
|
import_sonner2.toast.error(labels.documentLoadFailed);
|
|
1828
4607
|
console.error(e);
|
|
@@ -1874,10 +4653,10 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1874
4653
|
}
|
|
1875
4654
|
});
|
|
1876
4655
|
};
|
|
1877
|
-
return /* @__PURE__ */ (0,
|
|
1878
|
-
|
|
4656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4657
|
+
import_react9.default,
|
|
1879
4658
|
{
|
|
1880
|
-
grapesjs:
|
|
4659
|
+
grapesjs: import_grapesjs2.default,
|
|
1881
4660
|
className: "h-full",
|
|
1882
4661
|
options: {
|
|
1883
4662
|
height: "100%",
|
|
@@ -1886,12 +4665,13 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1886
4665
|
// app.grapesjs.com and leave a session-storage entry, which a host would then have
|
|
1887
4666
|
// to disclose and, for the entry, ask consent for.
|
|
1888
4667
|
telemetry: false,
|
|
4668
|
+
canvasCss: CANVAS_CSS,
|
|
1889
4669
|
fromElement: false,
|
|
1890
4670
|
panels: { defaults: [] },
|
|
1891
4671
|
blockManager: { appendTo: "#gjs-blocks" },
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
layerManager: { appendTo: "#gjs-layers" },
|
|
4672
|
+
// The eye hides a layer by writing `display="none"` into the MJML, which is illegal.
|
|
4673
|
+
// The root row is mj-body, which the sidebar reaches as "Email settings" instead.
|
|
4674
|
+
layerManager: { appendTo: "#gjs-layers", hidable: false, showWrapper: false },
|
|
1895
4675
|
assetManager: {
|
|
1896
4676
|
// Never inline files: mail clients block `data:` images (see ImageLibrary).
|
|
1897
4677
|
embedAsBase64: false,
|
|
@@ -1905,115 +4685,118 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1905
4685
|
close: () => setPicker(null)
|
|
1906
4686
|
}
|
|
1907
4687
|
},
|
|
4688
|
+
canvas: {
|
|
4689
|
+
customBadgeLabel: badgeLabel(labels),
|
|
4690
|
+
// A file or text dragged in from outside lands as `<img>` or `<div>`, which no MJML
|
|
4691
|
+
// validator accepts; drags from the palette are unaffected.
|
|
4692
|
+
allowExternalDrop: false
|
|
4693
|
+
},
|
|
1908
4694
|
deviceManager: {
|
|
1909
4695
|
devices: [
|
|
1910
4696
|
{ id: "desktop", name: "Desktop", width: "" },
|
|
1911
4697
|
{ id: "mobile", name: "Mobile", width: "375px", widthMedia: "480px" }
|
|
1912
4698
|
]
|
|
1913
4699
|
},
|
|
1914
|
-
plugins: [
|
|
4700
|
+
plugins: [
|
|
4701
|
+
(editor) => (0, import_grapesjs_mjml2.default)(editor, {
|
|
4702
|
+
blocks: BUILTIN_BLOCKS,
|
|
4703
|
+
block: builtinBlock(labels)
|
|
4704
|
+
})
|
|
4705
|
+
]
|
|
1915
4706
|
},
|
|
1916
4707
|
onEditor,
|
|
1917
|
-
children: /* @__PURE__ */ (0,
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
" ",
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
}
|
|
1932
|
-
),
|
|
1933
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
|
|
1934
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
|
|
1935
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react6.Canvas, { className: "h-full" }),
|
|
1936
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1937
|
-
CanvasComments,
|
|
1938
|
-
{
|
|
1939
|
-
docId,
|
|
1940
|
-
composeTarget,
|
|
1941
|
-
onComposeConsumed: () => setComposeTarget(null),
|
|
1942
|
-
refreshSignal: commentsRefresh,
|
|
1943
|
-
onOpenCountChange
|
|
1944
|
-
}
|
|
1945
|
-
),
|
|
1946
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1947
|
-
ImagePicker,
|
|
4708
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
4709
|
+
"div",
|
|
4710
|
+
{
|
|
4711
|
+
className: "relative flex h-full min-h-0 flex-1",
|
|
4712
|
+
inert: readOnly,
|
|
4713
|
+
onKeyDown: (e) => onBlockSearchKey(e.nativeEvent),
|
|
4714
|
+
children: [
|
|
4715
|
+
readOnly && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "absolute inset-0 z-30 flex cursor-not-allowed items-start justify-center bg-white/20 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex items-center gap-2 rounded-full bg-panel px-3 py-1 text-xs text-panel-fg shadow", children: [
|
|
4716
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
|
|
4717
|
+
" ",
|
|
4718
|
+
labels.agentWorking
|
|
4719
|
+
] }) }),
|
|
4720
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4721
|
+
LeftSidebar,
|
|
1948
4722
|
{
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
setPicker(null);
|
|
1954
|
-
},
|
|
1955
|
-
onClose: () => {
|
|
1956
|
-
setPicker(null);
|
|
1957
|
-
editorRef.current?.AssetManager.close();
|
|
1958
|
-
}
|
|
4723
|
+
view: sidebarView,
|
|
4724
|
+
onViewChange: setSidebarView,
|
|
4725
|
+
markLayersSource: () => selectionSource.current = "layers",
|
|
4726
|
+
blockSearchRef
|
|
1959
4727
|
}
|
|
1960
4728
|
),
|
|
1961
|
-
|
|
1962
|
-
/* @__PURE__ */ (0,
|
|
1963
|
-
|
|
4729
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
|
|
4730
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
|
|
4731
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react9.Canvas, { className: "h-full" }),
|
|
4732
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4733
|
+
CanvasComments,
|
|
4734
|
+
{
|
|
4735
|
+
docId,
|
|
4736
|
+
composeTarget,
|
|
4737
|
+
onComposeConsumed: () => setComposeTarget(null),
|
|
4738
|
+
refreshSignal: commentsRefresh,
|
|
4739
|
+
onOpenCountChange
|
|
4740
|
+
}
|
|
4741
|
+
),
|
|
4742
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4743
|
+
ImagePicker,
|
|
4744
|
+
{
|
|
4745
|
+
open: picker !== null,
|
|
4746
|
+
currentUrl: picker?.current,
|
|
4747
|
+
onSelect: (url) => {
|
|
4748
|
+
if (!readOnlyRef.current) picker?.select(url);
|
|
4749
|
+
setPicker(null);
|
|
4750
|
+
},
|
|
4751
|
+
onClose: () => {
|
|
4752
|
+
setPicker(null);
|
|
4753
|
+
editorRef.current?.AssetManager.close();
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
),
|
|
4757
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
|
|
4758
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
|
|
4759
|
+
" ",
|
|
4760
|
+
labels.loadingEditor
|
|
4761
|
+
] })
|
|
4762
|
+
] }),
|
|
4763
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
|
|
1964
4764
|
] })
|
|
1965
|
-
]
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
] })
|
|
4765
|
+
]
|
|
4766
|
+
}
|
|
4767
|
+
)
|
|
1969
4768
|
}
|
|
1970
4769
|
);
|
|
1971
4770
|
}
|
|
1972
|
-
function CollapseSection({
|
|
1973
|
-
title,
|
|
1974
|
-
defaultOpen = true,
|
|
1975
|
-
children
|
|
1976
|
-
}) {
|
|
1977
|
-
const [open, setOpen] = (0, import_react7.useState)(defaultOpen);
|
|
1978
|
-
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-panel-border", children: [
|
|
1979
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1980
|
-
"button",
|
|
1981
|
-
{
|
|
1982
|
-
type: "button",
|
|
1983
|
-
onClick: () => setOpen((o) => !o),
|
|
1984
|
-
className: "flex w-full items-center gap-1.5 px-3 py-2 text-xs font-semibold tracking-wide text-panel-muted-fg uppercase hover:text-panel-fg",
|
|
1985
|
-
children: [
|
|
1986
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronRight, { className: cn("size-3.5 transition-transform", open && "rotate-90") }),
|
|
1987
|
-
title
|
|
1988
|
-
]
|
|
1989
|
-
}
|
|
1990
|
-
),
|
|
1991
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn(!open && "hidden"), children })
|
|
1992
|
-
] });
|
|
1993
|
-
}
|
|
1994
4771
|
function LeftSidebar({
|
|
1995
4772
|
view,
|
|
1996
4773
|
onViewChange,
|
|
1997
4774
|
markLayersSource,
|
|
1998
|
-
|
|
1999
|
-
onChooseImage
|
|
4775
|
+
blockSearchRef
|
|
2000
4776
|
}) {
|
|
2001
|
-
const editor = (0,
|
|
4777
|
+
const editor = (0, import_react9.useEditorMaybe)();
|
|
2002
4778
|
const labels = useLabels();
|
|
2003
|
-
const [layersExpanded, setLayersExpanded] = (0,
|
|
2004
|
-
const [blockSearch, setBlockSearch] = (0,
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
if (body)
|
|
2010
|
-
}
|
|
2011
|
-
|
|
4779
|
+
const [layersExpanded, setLayersExpanded] = (0, import_react10.useState)(false);
|
|
4780
|
+
const [blockSearch, setBlockSearch] = (0, import_react10.useState)("");
|
|
4781
|
+
const [noBlockMatches, setNoBlockMatches] = (0, import_react10.useState)(false);
|
|
4782
|
+
const selectBody = () => {
|
|
4783
|
+
let body;
|
|
4784
|
+
editor?.getWrapper()?.onAll((c) => {
|
|
4785
|
+
if (!body && tagOf6(c) === "mj-body") body = c;
|
|
4786
|
+
});
|
|
4787
|
+
if (body) editor?.select(body);
|
|
2012
4788
|
};
|
|
2013
4789
|
const toggleLayers = (expand) => {
|
|
2014
4790
|
setLayersExpanded(expand);
|
|
2015
4791
|
editor?.getWrapper()?.onAll((c) => c.set("open", expand));
|
|
2016
4792
|
};
|
|
4793
|
+
const onTileKey = (e) => {
|
|
4794
|
+
const tile = e.target.closest("[data-block]");
|
|
4795
|
+
const block = tile && editor?.Blocks.get(tile.dataset.block ?? "");
|
|
4796
|
+
if (!editor || !block || !addsBlock(e)) return;
|
|
4797
|
+
e.preventDefault();
|
|
4798
|
+
if (insertBlock(editor, block)) (0, import_sonner2.toast)(labels.blockAdded(tile.textContent?.trim() ?? ""));
|
|
4799
|
+
};
|
|
2017
4800
|
const filterBlocks = (q) => {
|
|
2018
4801
|
setBlockSearch(q);
|
|
2019
4802
|
const root = document.getElementById("gjs-blocks");
|
|
@@ -2029,17 +4812,22 @@ function LeftSidebar({
|
|
|
2029
4812
|
);
|
|
2030
4813
|
cat.style.display = anyVisible ? "" : "none";
|
|
2031
4814
|
});
|
|
4815
|
+
setNoBlockMatches(
|
|
4816
|
+
[...root.querySelectorAll(".gjs-block")].every(
|
|
4817
|
+
(b) => b.style.display === "none"
|
|
4818
|
+
)
|
|
4819
|
+
);
|
|
2032
4820
|
};
|
|
2033
|
-
return /* @__PURE__ */ (0,
|
|
2034
|
-
/* @__PURE__ */ (0,
|
|
2035
|
-
["blocks",
|
|
2036
|
-
["settings",
|
|
2037
|
-
["layers",
|
|
2038
|
-
].map(([value, label]) => /* @__PURE__ */ (0,
|
|
4821
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg", children: [
|
|
4822
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex border-b border-panel-border", children: [
|
|
4823
|
+
["blocks", labels.tabBlocks],
|
|
4824
|
+
["settings", labels.tabSettings],
|
|
4825
|
+
["layers", labels.tabLayers]
|
|
4826
|
+
].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
2039
4827
|
"button",
|
|
2040
4828
|
{
|
|
2041
4829
|
type: "button",
|
|
2042
|
-
onClick: () =>
|
|
4830
|
+
onClick: () => onViewChange(value),
|
|
2043
4831
|
className: cn(
|
|
2044
4832
|
"relative flex-1 px-3 py-2.5 text-sm transition-colors",
|
|
2045
4833
|
"after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:transition-colors",
|
|
@@ -2049,70 +4837,57 @@ function LeftSidebar({
|
|
|
2049
4837
|
},
|
|
2050
4838
|
value
|
|
2051
4839
|
)) }),
|
|
2052
|
-
/* @__PURE__ */ (0,
|
|
2053
|
-
/* @__PURE__ */ (0,
|
|
2054
|
-
/* @__PURE__ */ (0,
|
|
2055
|
-
/* @__PURE__ */ (0,
|
|
4840
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "blocks" && "hidden"), children: [
|
|
4841
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative border-b border-panel-border p-2", children: [
|
|
4842
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react11.Search, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg" }),
|
|
4843
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
2056
4844
|
Input,
|
|
2057
4845
|
{
|
|
4846
|
+
ref: blockSearchRef,
|
|
2058
4847
|
value: blockSearch,
|
|
2059
4848
|
onChange: (e) => filterBlocks(e.target.value),
|
|
2060
4849
|
placeholder: labels.searchBlocks,
|
|
2061
4850
|
className: "h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg"
|
|
2062
4851
|
}
|
|
2063
4852
|
),
|
|
2064
|
-
/* @__PURE__ */ (0,
|
|
2065
|
-
] }),
|
|
2066
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
|
|
2067
|
-
] }),
|
|
2068
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: [
|
|
2069
|
-
selectedImageSrc !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-panel-border p-3", children: [
|
|
2070
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2071
|
-
"button",
|
|
2072
|
-
{
|
|
2073
|
-
type: "button",
|
|
2074
|
-
onClick: onChooseImage,
|
|
2075
|
-
title: labels.chooseImage,
|
|
2076
|
-
className: "group relative block w-full overflow-hidden rounded-lg border border-panel-border bg-panel-elevated",
|
|
2077
|
-
children: [
|
|
2078
|
-
selectedImageSrc ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2079
|
-
"img",
|
|
2080
|
-
{
|
|
2081
|
-
src: selectedImageSrc,
|
|
2082
|
-
alt: "",
|
|
2083
|
-
className: "h-32 w-full bg-black/25 object-contain"
|
|
2084
|
-
}
|
|
2085
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "flex h-32 items-center justify-center text-panel-muted-fg", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ImageIcon, { className: "size-8" }) }),
|
|
2086
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 text-sm font-medium text-white opacity-0 transition group-hover:opacity-100", children: labels.chooseImage })
|
|
2087
|
-
]
|
|
2088
|
-
}
|
|
2089
|
-
),
|
|
2090
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2091
|
-
Button,
|
|
2092
|
-
{
|
|
2093
|
-
type: "button",
|
|
2094
|
-
size: "sm",
|
|
2095
|
-
className: "mt-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
|
|
2096
|
-
onClick: onChooseImage,
|
|
2097
|
-
children: [
|
|
2098
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ImageUp, {}),
|
|
2099
|
-
labels.chooseImage
|
|
2100
|
-
]
|
|
2101
|
-
}
|
|
2102
|
-
)
|
|
4853
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("kbd", { className: "pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 rounded border border-panel-border bg-panel px-1 py-0.5 font-mono text-[10px] text-panel-muted-fg", children: "/" })
|
|
2103
4854
|
] }),
|
|
2104
|
-
/* @__PURE__ */ (0,
|
|
2105
|
-
/* @__PURE__ */ (0,
|
|
4855
|
+
noBlockMatches && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "px-3 py-6 text-center text-xs text-panel-muted-fg", children: labels.noBlocksMatch }),
|
|
4856
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto", onKeyDown: onTileKey })
|
|
2106
4857
|
] }),
|
|
2107
|
-
/* @__PURE__ */ (0,
|
|
4858
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4859
|
+
Inspector,
|
|
4860
|
+
{
|
|
4861
|
+
onEmailSettings: selectBody,
|
|
4862
|
+
empty: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col items-center gap-3 px-6 py-10 text-center", children: [
|
|
4863
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react10.MousePointerClick, { className: "size-6 text-panel-muted-fg" }),
|
|
4864
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm text-panel-muted-fg", children: labels.settingsEmpty }),
|
|
4865
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
4866
|
+
Button,
|
|
4867
|
+
{
|
|
4868
|
+
type: "button",
|
|
4869
|
+
size: "sm",
|
|
4870
|
+
variant: "outline",
|
|
4871
|
+
className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
|
|
4872
|
+
onClick: selectBody,
|
|
4873
|
+
children: [
|
|
4874
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react10.Mail, {}),
|
|
4875
|
+
labels.emailSettings
|
|
4876
|
+
]
|
|
4877
|
+
}
|
|
4878
|
+
)
|
|
4879
|
+
] })
|
|
4880
|
+
}
|
|
4881
|
+
) }),
|
|
4882
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
2108
4883
|
"div",
|
|
2109
4884
|
{
|
|
2110
4885
|
className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
|
|
2111
4886
|
onMouseDownCapture: markLayersSource,
|
|
2112
4887
|
children: [
|
|
2113
|
-
/* @__PURE__ */ (0,
|
|
2114
|
-
/* @__PURE__ */ (0,
|
|
2115
|
-
/* @__PURE__ */ (0,
|
|
4888
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center justify-between gap-2 border-b border-panel-border px-3 py-2 text-xs text-panel-muted-fg", children: [
|
|
4889
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: labels.expandAll }),
|
|
4890
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
2116
4891
|
Switch,
|
|
2117
4892
|
{
|
|
2118
4893
|
checked: layersExpanded,
|
|
@@ -2121,7 +4896,16 @@ function LeftSidebar({
|
|
|
2121
4896
|
}
|
|
2122
4897
|
)
|
|
2123
4898
|
] }),
|
|
2124
|
-
/* @__PURE__ */ (0,
|
|
4899
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4900
|
+
"div",
|
|
4901
|
+
{
|
|
4902
|
+
id: "gjs-layers",
|
|
4903
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
4904
|
+
onDoubleClickCapture: (e) => {
|
|
4905
|
+
if (e.target.closest("[data-name]")) e.stopPropagation();
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
)
|
|
2125
4909
|
]
|
|
2126
4910
|
}
|
|
2127
4911
|
)
|
|
@@ -2132,9 +4916,9 @@ function Breadcrumb({
|
|
|
2132
4916
|
onSelect
|
|
2133
4917
|
}) {
|
|
2134
4918
|
const labels = useLabels();
|
|
2135
|
-
return /* @__PURE__ */ (0,
|
|
2136
|
-
i > 0 && /* @__PURE__ */ (0,
|
|
2137
|
-
/* @__PURE__ */ (0,
|
|
4919
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex h-9 shrink-0 items-center gap-0.5 border-t border-border bg-surface px-3 text-xs text-muted-foreground", children: crumbs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-muted-foreground/60", children: labels.selectAnElement }) : crumbs.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex items-center gap-0.5", children: [
|
|
4920
|
+
i > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react10.ChevronRight, { className: "size-3 opacity-40" }),
|
|
4921
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
2138
4922
|
"button",
|
|
2139
4923
|
{
|
|
2140
4924
|
type: "button",
|
|
@@ -2143,13 +4927,14 @@ function Breadcrumb({
|
|
|
2143
4927
|
"rounded px-1.5 py-0.5 hover:bg-muted hover:text-foreground",
|
|
2144
4928
|
i === crumbs.length - 1 && "font-medium text-brand"
|
|
2145
4929
|
),
|
|
2146
|
-
children: crumbName(c)
|
|
4930
|
+
children: crumbName(c, labels)
|
|
2147
4931
|
}
|
|
2148
4932
|
)
|
|
2149
4933
|
] }, i)) });
|
|
2150
4934
|
}
|
|
2151
4935
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2152
4936
|
0 && (module.exports = {
|
|
2153
|
-
EmailEditor
|
|
4937
|
+
EmailEditor,
|
|
4938
|
+
blockProblems
|
|
2154
4939
|
});
|
|
2155
4940
|
//# sourceMappingURL=canvas.cjs.map
|