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