@mjml-agent-editor/editor 0.1.12 → 0.1.15
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 +46 -0
- package/dist/canvas.cjs +1103 -119
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.d.cts +2 -0
- package/dist/canvas.d.ts +2 -0
- package/dist/canvas.js +936 -83
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-4N2OJT22.js → chunk-3RCKZYM4.js} +136 -32
- package/dist/chunk-3RCKZYM4.js.map +1 -0
- package/dist/index.cjs +94 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -2
- package/dist/index.d.ts +66 -2
- package/dist/index.js +3 -1
- package/package.json +8 -5
- package/src/components/editor/editor-theme.css +29 -0
- package/dist/chunk-4N2OJT22.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_react6 = __toESM(require("@grapesjs/react"), 1);
|
|
41
41
|
var import_grapesjs_mjml = __toESM(require("grapesjs-mjml"), 1);
|
|
42
|
-
var
|
|
42
|
+
var import_react7 = require("react");
|
|
43
43
|
var import_core2 = require("@mjml-agent-editor/core");
|
|
44
44
|
|
|
45
45
|
// src/stores.tsx
|
|
@@ -74,6 +74,29 @@ var DEFAULT_LABELS = {
|
|
|
74
74
|
rteBackgroundColor: "Background color",
|
|
75
75
|
documentLoadFailed: "Failed to load the document.",
|
|
76
76
|
documentSaveFailed: "Failed to save changes.",
|
|
77
|
+
retry: "Retry",
|
|
78
|
+
chooseImage: "Choose image",
|
|
79
|
+
imagePickerTitle: "Choose an image",
|
|
80
|
+
imagePickerDrop: "Drag images here, or",
|
|
81
|
+
imagePickerBrowse: "Browse files",
|
|
82
|
+
imagePickerUrlPlaceholder: "\u2026or paste an image URL",
|
|
83
|
+
imagePickerUseUrl: "Use URL",
|
|
84
|
+
imagePickerGallery: "Your images",
|
|
85
|
+
imagePickerEmpty: "No images yet \u2014 the ones you upload will be kept here.",
|
|
86
|
+
imagePickerQuota: (used, limit) => `${used} of ${limit} uploads used`,
|
|
87
|
+
imagePickerQuotaFull: "Upload limit reached \u2014 pick an image below or paste a URL.",
|
|
88
|
+
imagePickerDelete: "Delete image",
|
|
89
|
+
imagePickerConfirmDelete: "Click again to delete",
|
|
90
|
+
imagePickerLoadFailed: "Failed to load your images.",
|
|
91
|
+
imageUploadFailed: "Failed to upload the image.",
|
|
92
|
+
imageDeleteFailed: "Failed to delete the image.",
|
|
93
|
+
imagePickerUnsupported: (fileName) => `${fileName} is not a supported image type.`,
|
|
94
|
+
imageFieldRemove: "Remove image",
|
|
95
|
+
colorPickerOpen: "Pick a color",
|
|
96
|
+
colorPickerEyedropper: "Pick from the screen",
|
|
97
|
+
colorPickerDocument: "In this email",
|
|
98
|
+
colorPickerPresets: "Presets",
|
|
99
|
+
colorPickerTransparent: "Transparent",
|
|
77
100
|
commentOnSection: "Comment on the whole section",
|
|
78
101
|
closeComments: "Close",
|
|
79
102
|
noCommentsYet: "No comments yet \u2014 add the first one.",
|
|
@@ -85,6 +108,7 @@ var DEFAULT_LABELS = {
|
|
|
85
108
|
agentWorking: "Agent is working\u2026",
|
|
86
109
|
reasoning: "Reasoning",
|
|
87
110
|
completedSteps: (count, failed) => failed > 0 ? `Completed steps (${count}, ${failed} failed)` : `Completed steps (${count})`,
|
|
111
|
+
stepLimitReached: "The agent ran out of steps for this turn. Ask it to continue.",
|
|
88
112
|
applyComments: "Apply changes from comments",
|
|
89
113
|
chatPlaceholder: "Describe what you want to change\u2026 (Enter = send, Shift+Enter = new line)",
|
|
90
114
|
stop: "Stop",
|
|
@@ -115,6 +139,17 @@ function mergeLabels(overrides) {
|
|
|
115
139
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
116
140
|
var StoreContext = (0, import_react.createContext)(null);
|
|
117
141
|
var LabelContext = (0, import_react.createContext)(null);
|
|
142
|
+
var TurnContext = (0, import_react.createContext)({
|
|
143
|
+
running: false,
|
|
144
|
+
setRunning: () => {
|
|
145
|
+
},
|
|
146
|
+
locked: false,
|
|
147
|
+
setLocked: () => {
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
function useAgentTurn() {
|
|
151
|
+
return (0, import_react.useContext)(TurnContext);
|
|
152
|
+
}
|
|
118
153
|
function useLabels() {
|
|
119
154
|
return (0, import_react.useContext)(LabelContext) ?? mergeLabels();
|
|
120
155
|
}
|
|
@@ -133,6 +168,9 @@ function useDocumentStore() {
|
|
|
133
168
|
function useCommentStore() {
|
|
134
169
|
return useStores().comments;
|
|
135
170
|
}
|
|
171
|
+
function useImageLibrary() {
|
|
172
|
+
return useStores().images;
|
|
173
|
+
}
|
|
136
174
|
|
|
137
175
|
// src/index.ts
|
|
138
176
|
var import_core = require("@mjml-agent-editor/core");
|
|
@@ -239,6 +277,9 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
|
239
277
|
function Popover({ ...props }) {
|
|
240
278
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Root, { "data-slot": "popover", ...props });
|
|
241
279
|
}
|
|
280
|
+
function PopoverTrigger({ ...props }) {
|
|
281
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
282
|
+
}
|
|
242
283
|
function PopoverAnchor({ ...props }) {
|
|
243
284
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
244
285
|
}
|
|
@@ -263,11 +304,117 @@ function PopoverContent({
|
|
|
263
304
|
) });
|
|
264
305
|
}
|
|
265
306
|
|
|
266
|
-
// src/components/ui/
|
|
307
|
+
// src/components/ui/dialog.tsx
|
|
267
308
|
var React4 = require("react");
|
|
309
|
+
var import_radix_ui3 = require("radix-ui");
|
|
310
|
+
var import_lucide_react2 = require("lucide-react");
|
|
268
311
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
269
|
-
function
|
|
312
|
+
function Dialog({ ...props }) {
|
|
313
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Root, { "data-slot": "dialog", ...props });
|
|
314
|
+
}
|
|
315
|
+
function DialogPortal({ ...props }) {
|
|
316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
|
|
317
|
+
}
|
|
318
|
+
function DialogOverlay({
|
|
319
|
+
className,
|
|
320
|
+
...props
|
|
321
|
+
}) {
|
|
322
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
323
|
+
import_radix_ui3.Dialog.Overlay,
|
|
324
|
+
{
|
|
325
|
+
"data-slot": "dialog-overlay",
|
|
326
|
+
className: cn(
|
|
327
|
+
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
328
|
+
className
|
|
329
|
+
),
|
|
330
|
+
...props
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
function DialogContent({
|
|
335
|
+
className,
|
|
336
|
+
children,
|
|
337
|
+
showCloseButton = true,
|
|
338
|
+
...props
|
|
339
|
+
}) {
|
|
340
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DialogPortal, { children: [
|
|
341
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DialogOverlay, {}),
|
|
342
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
343
|
+
import_radix_ui3.Dialog.Content,
|
|
344
|
+
{
|
|
345
|
+
"data-slot": "dialog-content",
|
|
346
|
+
className: cn(
|
|
347
|
+
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm 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",
|
|
348
|
+
className
|
|
349
|
+
),
|
|
350
|
+
...props,
|
|
351
|
+
children: [
|
|
352
|
+
children,
|
|
353
|
+
showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Button, { variant: "ghost", className: "absolute top-2 right-2", size: "icon-sm", children: [
|
|
354
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.XIcon, {}),
|
|
355
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "sr-only", children: "Close" })
|
|
356
|
+
] }) })
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
] });
|
|
361
|
+
}
|
|
362
|
+
function DialogHeader({ className, ...props }) {
|
|
363
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2", className), ...props });
|
|
364
|
+
}
|
|
365
|
+
function DialogTitle({ className, ...props }) {
|
|
366
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
367
|
+
import_radix_ui3.Dialog.Title,
|
|
368
|
+
{
|
|
369
|
+
"data-slot": "dialog-title",
|
|
370
|
+
className: cn("font-heading text-base leading-none font-medium", className),
|
|
371
|
+
...props
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
function DialogDescription({
|
|
376
|
+
className,
|
|
377
|
+
...props
|
|
378
|
+
}) {
|
|
270
379
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
380
|
+
import_radix_ui3.Dialog.Description,
|
|
381
|
+
{
|
|
382
|
+
"data-slot": "dialog-description",
|
|
383
|
+
className: cn(
|
|
384
|
+
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
385
|
+
className
|
|
386
|
+
),
|
|
387
|
+
...props
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/lib/refusal.ts
|
|
393
|
+
function refusal(error) {
|
|
394
|
+
if (!(error instanceof Error)) return null;
|
|
395
|
+
const status = error.status;
|
|
396
|
+
const deliberate = typeof status === "number" ? status >= 400 && status < 500 : null;
|
|
397
|
+
if (deliberate === false) return null;
|
|
398
|
+
if (error.message.startsWith("{")) {
|
|
399
|
+
try {
|
|
400
|
+
const parsed = JSON.parse(error.message);
|
|
401
|
+
return typeof parsed.error === "string" && parsed.error.trim() !== "" ? parsed.error : null;
|
|
402
|
+
} catch {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const message = error.message.trim();
|
|
407
|
+
return deliberate && message !== "" && !message.startsWith("<") ? error.message : null;
|
|
408
|
+
}
|
|
409
|
+
function saveToastId(docId) {
|
|
410
|
+
return `document-save-${docId}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/components/ui/textarea.tsx
|
|
414
|
+
var React5 = require("react");
|
|
415
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
416
|
+
function Textarea({ className, ...props }) {
|
|
417
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
271
418
|
"textarea",
|
|
272
419
|
{
|
|
273
420
|
"data-slot": "textarea",
|
|
@@ -283,8 +430,8 @@ function Textarea({ className, ...props }) {
|
|
|
283
430
|
// src/components/comments/CanvasComments.tsx
|
|
284
431
|
var import_react2 = require("@grapesjs/react");
|
|
285
432
|
var import_react3 = require("react");
|
|
286
|
-
var
|
|
287
|
-
var
|
|
433
|
+
var import_lucide_react3 = require("lucide-react");
|
|
434
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
288
435
|
var keyOf = (c) => c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;
|
|
289
436
|
var keyOfTarget = (t) => t.objectId ? `obj-${t.objectId}` : `sec-${t.sectionId}`;
|
|
290
437
|
function CanvasComments({
|
|
@@ -438,11 +585,11 @@ function CanvasComments({
|
|
|
438
585
|
console.error(e);
|
|
439
586
|
}
|
|
440
587
|
};
|
|
441
|
-
return /* @__PURE__ */ (0,
|
|
588
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref: containerRef, className: "pointer-events-none absolute inset-0 z-20 overflow-hidden", children: [
|
|
442
589
|
pins.map(({ key, list }) => {
|
|
443
590
|
const pos = positions[key];
|
|
444
591
|
if (!pos) return null;
|
|
445
|
-
return /* @__PURE__ */ (0,
|
|
592
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
446
593
|
"button",
|
|
447
594
|
{
|
|
448
595
|
type: "button",
|
|
@@ -458,8 +605,8 @@ function CanvasComments({
|
|
|
458
605
|
key
|
|
459
606
|
);
|
|
460
607
|
}),
|
|
461
|
-
/* @__PURE__ */ (0,
|
|
462
|
-
/* @__PURE__ */ (0,
|
|
608
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Popover, { open: !!activeKey && !!activePos, onOpenChange: (o) => !o && closePopover(), children: [
|
|
609
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
463
610
|
"div",
|
|
464
611
|
{
|
|
465
612
|
style: {
|
|
@@ -471,45 +618,45 @@ function CanvasComments({
|
|
|
471
618
|
}
|
|
472
619
|
}
|
|
473
620
|
) }),
|
|
474
|
-
/* @__PURE__ */ (0,
|
|
621
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
475
622
|
PopoverContent,
|
|
476
623
|
{
|
|
477
624
|
side: "right",
|
|
478
625
|
align: "start",
|
|
479
626
|
className: "pointer-events-auto w-80 p-0 data-[state=closed]:!animate-none data-[state=closed]:!duration-0",
|
|
480
627
|
children: [
|
|
481
|
-
/* @__PURE__ */ (0,
|
|
482
|
-
/* @__PURE__ */ (0,
|
|
628
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground", children: [
|
|
629
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "min-w-0", children: activeTarget?.objectId ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
483
630
|
"Comment on",
|
|
484
631
|
" ",
|
|
485
|
-
/* @__PURE__ */ (0,
|
|
486
|
-
] }) : /* @__PURE__ */ (0,
|
|
632
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-medium text-foreground", children: activeTarget.objectLabel })
|
|
633
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
487
634
|
labels.commentOnSection,
|
|
488
635
|
" ",
|
|
489
|
-
/* @__PURE__ */ (0,
|
|
636
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { className: "font-mono", children: activeTarget?.sectionId })
|
|
490
637
|
] }) }),
|
|
491
|
-
/* @__PURE__ */ (0,
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
492
639
|
"button",
|
|
493
640
|
{
|
|
494
641
|
type: "button",
|
|
495
642
|
onClick: closePopover,
|
|
496
643
|
"aria-label": labels.closeComments,
|
|
497
644
|
className: "-mr-1 -mt-0.5 shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
498
|
-
children: /* @__PURE__ */ (0,
|
|
645
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.X, { className: "size-4" })
|
|
499
646
|
}
|
|
500
647
|
)
|
|
501
648
|
] }),
|
|
502
|
-
/* @__PURE__ */ (0,
|
|
503
|
-
activeComments.length === 0 && /* @__PURE__ */ (0,
|
|
504
|
-
activeComments.map((c) => /* @__PURE__ */ (0,
|
|
505
|
-
/* @__PURE__ */ (0,
|
|
506
|
-
/* @__PURE__ */ (0,
|
|
649
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "max-h-64 space-y-2 overflow-y-auto p-3", children: [
|
|
650
|
+
activeComments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-muted-foreground", children: labels.noCommentsYet }),
|
|
651
|
+
activeComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2 text-sm", children: [
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "whitespace-pre-wrap", children: c.body }),
|
|
653
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
507
654
|
"button",
|
|
508
655
|
{
|
|
509
656
|
onClick: () => void resolve(c.id),
|
|
510
657
|
className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
|
|
511
658
|
children: [
|
|
512
|
-
/* @__PURE__ */ (0,
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.Check, { className: "size-3" }),
|
|
513
660
|
" ",
|
|
514
661
|
labels.resolveComment
|
|
515
662
|
]
|
|
@@ -517,8 +664,8 @@ function CanvasComments({
|
|
|
517
664
|
)
|
|
518
665
|
] }, c.id))
|
|
519
666
|
] }),
|
|
520
|
-
/* @__PURE__ */ (0,
|
|
521
|
-
/* @__PURE__ */ (0,
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "border-t p-3", children: [
|
|
668
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
522
669
|
Textarea,
|
|
523
670
|
{
|
|
524
671
|
autoFocus: true,
|
|
@@ -534,8 +681,8 @@ function CanvasComments({
|
|
|
534
681
|
}
|
|
535
682
|
}
|
|
536
683
|
),
|
|
537
|
-
/* @__PURE__ */ (0,
|
|
538
|
-
/* @__PURE__ */ (0,
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Button, { size: "sm", disabled: !body.trim(), onClick: () => void submit(), children: [
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.MessageSquarePlus, {}),
|
|
539
686
|
" ",
|
|
540
687
|
labels.addComment
|
|
541
688
|
] }) })
|
|
@@ -547,21 +694,50 @@ function CanvasComments({
|
|
|
547
694
|
] });
|
|
548
695
|
}
|
|
549
696
|
|
|
697
|
+
// src/lib/save-queue.ts
|
|
698
|
+
function createSaveQueue(save, delayMs) {
|
|
699
|
+
let timer = null;
|
|
700
|
+
let tail = Promise.resolve();
|
|
701
|
+
const cancel = () => {
|
|
702
|
+
if (timer) clearTimeout(timer);
|
|
703
|
+
timer = null;
|
|
704
|
+
};
|
|
705
|
+
const run = (task) => {
|
|
706
|
+
const next = tail.then(task);
|
|
707
|
+
tail = next.catch(() => {
|
|
708
|
+
});
|
|
709
|
+
return next;
|
|
710
|
+
};
|
|
711
|
+
return {
|
|
712
|
+
schedule() {
|
|
713
|
+
cancel();
|
|
714
|
+
timer = setTimeout(() => void run(save).catch(() => {
|
|
715
|
+
}), delayMs);
|
|
716
|
+
},
|
|
717
|
+
cancel,
|
|
718
|
+
flush() {
|
|
719
|
+
cancel();
|
|
720
|
+
return run(save);
|
|
721
|
+
},
|
|
722
|
+
run
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
550
726
|
// src/components/editor/EmailEditor.tsx
|
|
551
|
-
var
|
|
552
|
-
var
|
|
727
|
+
var import_sonner2 = require("sonner");
|
|
728
|
+
var import_lucide_react7 = require("lucide-react");
|
|
553
729
|
|
|
554
730
|
// src/components/ui/switch.tsx
|
|
555
|
-
var
|
|
556
|
-
var
|
|
557
|
-
var
|
|
731
|
+
var React6 = require("react");
|
|
732
|
+
var import_radix_ui4 = require("radix-ui");
|
|
733
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
558
734
|
function Switch({
|
|
559
735
|
className,
|
|
560
736
|
size = "default",
|
|
561
737
|
...props
|
|
562
738
|
}) {
|
|
563
|
-
return /* @__PURE__ */ (0,
|
|
564
|
-
|
|
739
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
740
|
+
import_radix_ui4.Switch.Root,
|
|
565
741
|
{
|
|
566
742
|
"data-slot": "switch",
|
|
567
743
|
"data-size": size,
|
|
@@ -570,8 +746,8 @@ function Switch({
|
|
|
570
746
|
className
|
|
571
747
|
),
|
|
572
748
|
...props,
|
|
573
|
-
children: /* @__PURE__ */ (0,
|
|
574
|
-
|
|
749
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
750
|
+
import_radix_ui4.Switch.Thumb,
|
|
575
751
|
{
|
|
576
752
|
"data-slot": "switch-thumb",
|
|
577
753
|
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"
|
|
@@ -582,8 +758,583 @@ function Switch({
|
|
|
582
758
|
}
|
|
583
759
|
|
|
584
760
|
// src/components/editor/EmailEditor.tsx
|
|
761
|
+
var import_lucide_react8 = require("lucide-react");
|
|
762
|
+
|
|
763
|
+
// src/components/editor/ImagePicker.tsx
|
|
764
|
+
var import_react4 = require("react");
|
|
585
765
|
var import_lucide_react4 = require("lucide-react");
|
|
586
|
-
var
|
|
766
|
+
var import_sonner = require("sonner");
|
|
767
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
768
|
+
function ImagePicker({
|
|
769
|
+
open,
|
|
770
|
+
currentUrl,
|
|
771
|
+
onSelect,
|
|
772
|
+
onClose
|
|
773
|
+
}) {
|
|
774
|
+
const library = useImageLibrary();
|
|
775
|
+
const labels = useLabels();
|
|
776
|
+
const [images, setImages] = (0, import_react4.useState)([]);
|
|
777
|
+
const [quota, setQuota] = (0, import_react4.useState)();
|
|
778
|
+
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
779
|
+
const [pending, setPending] = (0, import_react4.useState)(0);
|
|
780
|
+
const [dragging, setDragging] = (0, import_react4.useState)(false);
|
|
781
|
+
const [url, setUrl] = (0, import_react4.useState)("");
|
|
782
|
+
const [confirmId, setConfirmId] = (0, import_react4.useState)(null);
|
|
783
|
+
const fileInput = (0, import_react4.useRef)(null);
|
|
784
|
+
const session = (0, import_react4.useRef)(0);
|
|
785
|
+
const latest = (0, import_react4.useRef)({ labels, onSelect });
|
|
786
|
+
latest.current = { labels, onSelect };
|
|
787
|
+
const lists = (0, import_react4.useRef)(0);
|
|
788
|
+
const refresh = (0, import_react4.useCallback)(async () => {
|
|
789
|
+
if (!library) return;
|
|
790
|
+
const mine = ++lists.current;
|
|
791
|
+
setLoading(true);
|
|
792
|
+
try {
|
|
793
|
+
const result = await library.list();
|
|
794
|
+
if (mine !== lists.current) return;
|
|
795
|
+
setImages(result.images);
|
|
796
|
+
setQuota(result.quota);
|
|
797
|
+
} catch (err) {
|
|
798
|
+
console.error(err);
|
|
799
|
+
if (mine === lists.current) import_sonner.toast.error(latest.current.labels.imagePickerLoadFailed);
|
|
800
|
+
} finally {
|
|
801
|
+
if (mine === lists.current) setLoading(false);
|
|
802
|
+
}
|
|
803
|
+
}, [library]);
|
|
804
|
+
(0, import_react4.useEffect)(() => {
|
|
805
|
+
session.current += 1;
|
|
806
|
+
if (!open) return;
|
|
807
|
+
setUrl("");
|
|
808
|
+
setConfirmId(null);
|
|
809
|
+
void refresh();
|
|
810
|
+
}, [open]);
|
|
811
|
+
(0, import_react4.useEffect)(() => () => void (session.current += 1), []);
|
|
812
|
+
const accepts = (file) => matchesAccept(file, library?.accept);
|
|
813
|
+
const full = quota !== void 0 && quota.used >= quota.limit;
|
|
814
|
+
const upload = async (files) => {
|
|
815
|
+
if (!library || !files?.length) return;
|
|
816
|
+
const mine = session.current;
|
|
817
|
+
const list = Array.from(files).filter((f) => {
|
|
818
|
+
if (accepts(f)) return true;
|
|
819
|
+
import_sonner.toast.error(labels.imagePickerUnsupported(f.name));
|
|
820
|
+
return false;
|
|
821
|
+
});
|
|
822
|
+
if (!list.length) return;
|
|
823
|
+
setPending((n) => n + list.length);
|
|
824
|
+
const results = await Promise.allSettled(list.map(async (f) => library.upload(f)));
|
|
825
|
+
setPending((n) => n - list.length);
|
|
826
|
+
for (const r of results) {
|
|
827
|
+
if (r.status === "rejected") {
|
|
828
|
+
console.error(r.reason);
|
|
829
|
+
import_sonner.toast.error(r.reason instanceof Error ? r.reason.message : labels.imageUploadFailed);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (session.current !== mine) return;
|
|
833
|
+
await refresh();
|
|
834
|
+
const [only] = results;
|
|
835
|
+
if (results.length === 1 && only?.status === "fulfilled" && session.current === mine) {
|
|
836
|
+
latest.current.onSelect(only.value.url);
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
const remove = async (image) => {
|
|
840
|
+
if (!library?.remove) return;
|
|
841
|
+
if (confirmId !== image.id) {
|
|
842
|
+
setConfirmId(image.id);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
setConfirmId(null);
|
|
846
|
+
try {
|
|
847
|
+
await library.remove(image.id);
|
|
848
|
+
await refresh();
|
|
849
|
+
} catch (err) {
|
|
850
|
+
console.error(err);
|
|
851
|
+
import_sonner.toast.error(err instanceof Error ? err.message : labels.imageDeleteFailed);
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
const onDrop = (e) => {
|
|
855
|
+
e.preventDefault();
|
|
856
|
+
setDragging(false);
|
|
857
|
+
if (!full) void upload(e.dataTransfer.files);
|
|
858
|
+
};
|
|
859
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime10.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: [
|
|
860
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DialogTitle, { children: labels.imagePickerTitle }),
|
|
862
|
+
quota && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
|
|
863
|
+
] }),
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
|
|
865
|
+
library && (full ? /* @__PURE__ */ (0, import_jsx_runtime10.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_runtime10.jsxs)(
|
|
866
|
+
"div",
|
|
867
|
+
{
|
|
868
|
+
onDragOver: (e) => {
|
|
869
|
+
e.preventDefault();
|
|
870
|
+
setDragging(true);
|
|
871
|
+
},
|
|
872
|
+
onDragLeave: () => setDragging(false),
|
|
873
|
+
onDrop,
|
|
874
|
+
className: cn(
|
|
875
|
+
"flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors",
|
|
876
|
+
dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
|
|
877
|
+
),
|
|
878
|
+
children: [
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ImageUp, { className: "size-8 text-panel-muted-fg" }),
|
|
880
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
|
|
881
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: labels.imagePickerDrop }),
|
|
882
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
883
|
+
Button,
|
|
884
|
+
{
|
|
885
|
+
type: "button",
|
|
886
|
+
size: "sm",
|
|
887
|
+
className: "bg-brand text-brand-fg hover:bg-brand/90",
|
|
888
|
+
onClick: () => fileInput.current?.click(),
|
|
889
|
+
children: labels.imagePickerBrowse
|
|
890
|
+
}
|
|
891
|
+
)
|
|
892
|
+
] }),
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
894
|
+
"input",
|
|
895
|
+
{
|
|
896
|
+
ref: fileInput,
|
|
897
|
+
type: "file",
|
|
898
|
+
accept: library.accept ?? "image/*",
|
|
899
|
+
multiple: true,
|
|
900
|
+
hidden: true,
|
|
901
|
+
onChange: (e) => {
|
|
902
|
+
void upload(e.target.files);
|
|
903
|
+
e.target.value = "";
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
)
|
|
907
|
+
]
|
|
908
|
+
}
|
|
909
|
+
)),
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
911
|
+
"form",
|
|
912
|
+
{
|
|
913
|
+
className: "flex gap-2",
|
|
914
|
+
onSubmit: (e) => {
|
|
915
|
+
e.preventDefault();
|
|
916
|
+
const value = url.trim();
|
|
917
|
+
if (value) onSelect(value);
|
|
918
|
+
},
|
|
919
|
+
children: [
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative flex-1", children: [
|
|
921
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.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" }),
|
|
922
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
923
|
+
Input,
|
|
924
|
+
{
|
|
925
|
+
value: url,
|
|
926
|
+
onChange: (e) => setUrl(e.target.value),
|
|
927
|
+
placeholder: labels.imagePickerUrlPlaceholder,
|
|
928
|
+
className: "h-9 border-panel-border bg-panel-elevated pl-8 text-panel-fg placeholder:text-panel-muted-fg"
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
] }),
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
933
|
+
Button,
|
|
934
|
+
{
|
|
935
|
+
type: "submit",
|
|
936
|
+
size: "lg",
|
|
937
|
+
variant: "outline",
|
|
938
|
+
disabled: !url.trim(),
|
|
939
|
+
className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
|
|
940
|
+
children: labels.imagePickerUseUrl
|
|
941
|
+
}
|
|
942
|
+
)
|
|
943
|
+
]
|
|
944
|
+
}
|
|
945
|
+
),
|
|
946
|
+
library && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { children: [
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
|
|
948
|
+
loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
|
|
949
|
+
Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
950
|
+
"li",
|
|
951
|
+
{
|
|
952
|
+
className: "flex aspect-[4/3] items-center justify-center rounded-lg border border-panel-border bg-panel-elevated",
|
|
953
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, {})
|
|
954
|
+
},
|
|
955
|
+
`pending-${i}`
|
|
956
|
+
)),
|
|
957
|
+
images.map((image) => {
|
|
958
|
+
const selected = image.url === currentUrl;
|
|
959
|
+
const confirming = confirmId === image.id;
|
|
960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "group relative", children: [
|
|
961
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
962
|
+
"button",
|
|
963
|
+
{
|
|
964
|
+
type: "button",
|
|
965
|
+
onClick: () => onSelect(image.url),
|
|
966
|
+
title: image.name,
|
|
967
|
+
className: cn(
|
|
968
|
+
"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",
|
|
969
|
+
selected ? "border-brand ring-2 ring-brand" : "border-panel-border hover:border-panel-muted-fg"
|
|
970
|
+
),
|
|
971
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
972
|
+
"img",
|
|
973
|
+
{
|
|
974
|
+
src: image.url,
|
|
975
|
+
alt: image.name ?? "",
|
|
976
|
+
loading: "lazy",
|
|
977
|
+
className: "size-full object-contain"
|
|
978
|
+
}
|
|
979
|
+
)
|
|
980
|
+
}
|
|
981
|
+
),
|
|
982
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime10.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_runtime10.jsx)(import_lucide_react4.Check, { className: "size-3" }) }),
|
|
983
|
+
image.name && /* @__PURE__ */ (0, import_jsx_runtime10.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 }),
|
|
984
|
+
library.remove && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
985
|
+
"button",
|
|
986
|
+
{
|
|
987
|
+
type: "button",
|
|
988
|
+
onClick: () => void remove(image),
|
|
989
|
+
onBlur: () => confirming && setConfirmId(null),
|
|
990
|
+
title: confirming ? labels.imagePickerConfirmDelete : labels.imagePickerDelete,
|
|
991
|
+
"aria-label": labels.imagePickerDelete,
|
|
992
|
+
className: cn(
|
|
993
|
+
"absolute top-1.5 right-1.5 flex h-6 items-center gap-1 rounded-md px-1.5 text-xs text-white transition focus-visible:opacity-100",
|
|
994
|
+
confirming ? "bg-red-600 opacity-100" : "bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80"
|
|
995
|
+
),
|
|
996
|
+
children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Trash2, { className: "size-3.5" }),
|
|
998
|
+
confirming && labels.imagePickerConfirmDelete
|
|
999
|
+
]
|
|
1000
|
+
}
|
|
1001
|
+
)
|
|
1002
|
+
] }, image.id);
|
|
1003
|
+
})
|
|
1004
|
+
] })
|
|
1005
|
+
] })
|
|
1006
|
+
] })
|
|
1007
|
+
] }) });
|
|
1008
|
+
}
|
|
1009
|
+
function matchesAccept(file, accept) {
|
|
1010
|
+
if (!accept) return file.type.startsWith("image/");
|
|
1011
|
+
return accept.split(",").some((raw) => {
|
|
1012
|
+
const rule = raw.trim().toLowerCase();
|
|
1013
|
+
if (rule.startsWith(".")) return file.name.toLowerCase().endsWith(rule);
|
|
1014
|
+
if (rule.endsWith("/*")) return file.type.startsWith(rule.slice(0, -1));
|
|
1015
|
+
return file.type === rule;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/components/editor/ColorField.tsx
|
|
1020
|
+
var import_react5 = require("react");
|
|
1021
|
+
var import_react_colorful = require("react-colorful");
|
|
1022
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1023
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1024
|
+
var PRESETS = [
|
|
1025
|
+
"#000000",
|
|
1026
|
+
"#374151",
|
|
1027
|
+
"#6b7280",
|
|
1028
|
+
"#d1d5db",
|
|
1029
|
+
"#f3f4f6",
|
|
1030
|
+
"#ffffff",
|
|
1031
|
+
"#dc2626",
|
|
1032
|
+
"#ea580c",
|
|
1033
|
+
"#ca8a04",
|
|
1034
|
+
"#16a34a",
|
|
1035
|
+
"#0284c7",
|
|
1036
|
+
"#4f46e5",
|
|
1037
|
+
"#9333ea",
|
|
1038
|
+
"#db2777"
|
|
1039
|
+
];
|
|
1040
|
+
var eyeDropper = () => globalThis.EyeDropper;
|
|
1041
|
+
function ColorField({
|
|
1042
|
+
value,
|
|
1043
|
+
placeholder,
|
|
1044
|
+
documentColors,
|
|
1045
|
+
labels,
|
|
1046
|
+
onChange
|
|
1047
|
+
}) {
|
|
1048
|
+
const [text, setText] = (0, import_react5.useState)(value);
|
|
1049
|
+
const [open, setOpen] = (0, import_react5.useState)(false);
|
|
1050
|
+
const [used, setUsed] = (0, import_react5.useState)([]);
|
|
1051
|
+
const pressed = (0, import_react5.useRef)(false);
|
|
1052
|
+
const dragged = (0, import_react5.useRef)(null);
|
|
1053
|
+
const fieldRef = (0, import_react5.useRef)(null);
|
|
1054
|
+
const anchor = (0, import_react5.useRef)({
|
|
1055
|
+
getBoundingClientRect: () => {
|
|
1056
|
+
const field = fieldRef.current?.getBoundingClientRect() ?? new DOMRect();
|
|
1057
|
+
const panel = fieldRef.current?.closest(".editor-dark")?.getBoundingClientRect();
|
|
1058
|
+
return new DOMRect(panel?.right ?? field.right, field.top, 0, field.height);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
(0, import_react5.useEffect)(() => setText(value), [value]);
|
|
1062
|
+
const latestOnChange = (0, import_react5.useRef)(onChange);
|
|
1063
|
+
latestOnChange.current = onChange;
|
|
1064
|
+
const release = (0, import_react5.useCallback)(() => {
|
|
1065
|
+
pressed.current = false;
|
|
1066
|
+
if (dragged.current !== null) latestOnChange.current(dragged.current, false);
|
|
1067
|
+
dragged.current = null;
|
|
1068
|
+
}, []);
|
|
1069
|
+
(0, import_react5.useEffect)(() => {
|
|
1070
|
+
document.addEventListener("lostpointercapture", release);
|
|
1071
|
+
return () => {
|
|
1072
|
+
document.removeEventListener("lostpointercapture", release);
|
|
1073
|
+
release();
|
|
1074
|
+
};
|
|
1075
|
+
}, [release]);
|
|
1076
|
+
const hex = toHex(value) ?? toHex(placeholder ?? "") ?? "#000000";
|
|
1077
|
+
const translucent = hex.length === 9;
|
|
1078
|
+
const empty = !value;
|
|
1079
|
+
const commit = (next) => {
|
|
1080
|
+
dragged.current = null;
|
|
1081
|
+
onChange(next, false);
|
|
1082
|
+
};
|
|
1083
|
+
const drag = (next) => {
|
|
1084
|
+
setText(next);
|
|
1085
|
+
if (!pressed.current) return commit(next);
|
|
1086
|
+
dragged.current = next;
|
|
1087
|
+
onChange(next, true);
|
|
1088
|
+
};
|
|
1089
|
+
const pickFromScreen = async () => {
|
|
1090
|
+
const Ctor = eyeDropper();
|
|
1091
|
+
if (!Ctor) return;
|
|
1092
|
+
try {
|
|
1093
|
+
const { sRGBHex } = await new Ctor().open();
|
|
1094
|
+
commit(sRGBHex);
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1099
|
+
Popover,
|
|
1100
|
+
{
|
|
1101
|
+
open,
|
|
1102
|
+
onOpenChange: (next) => {
|
|
1103
|
+
if (next) setUsed(documentColors());
|
|
1104
|
+
else if (text.trim() !== value) commit(text.trim());
|
|
1105
|
+
setOpen(next);
|
|
1106
|
+
},
|
|
1107
|
+
children: [
|
|
1108
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PopoverAnchor, { virtualRef: anchor }),
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1110
|
+
"div",
|
|
1111
|
+
{
|
|
1112
|
+
ref: fieldRef,
|
|
1113
|
+
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",
|
|
1114
|
+
children: [
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1116
|
+
"input",
|
|
1117
|
+
{
|
|
1118
|
+
value: text,
|
|
1119
|
+
placeholder,
|
|
1120
|
+
spellCheck: false,
|
|
1121
|
+
onChange: (e) => setText(e.target.value),
|
|
1122
|
+
onBlur: () => text !== value && commit(text.trim()),
|
|
1123
|
+
onKeyDown: (e) => {
|
|
1124
|
+
if (e.key === "Enter") commit(text.trim());
|
|
1125
|
+
if (e.key === "Escape") setText(value);
|
|
1126
|
+
},
|
|
1127
|
+
className: "min-w-0 flex-1 bg-transparent font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg"
|
|
1128
|
+
}
|
|
1129
|
+
),
|
|
1130
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1131
|
+
"button",
|
|
1132
|
+
{
|
|
1133
|
+
type: "button",
|
|
1134
|
+
title: labels.colorPickerOpen,
|
|
1135
|
+
"aria-label": labels.colorPickerOpen,
|
|
1136
|
+
className: "relative size-5 shrink-0 overflow-hidden rounded border border-white/20 outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
1137
|
+
children: [
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Checkerboard, {}),
|
|
1139
|
+
!empty && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "absolute inset-0", style: { background: value } })
|
|
1140
|
+
]
|
|
1141
|
+
}
|
|
1142
|
+
) })
|
|
1143
|
+
]
|
|
1144
|
+
}
|
|
1145
|
+
),
|
|
1146
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1147
|
+
PopoverContent,
|
|
1148
|
+
{
|
|
1149
|
+
side: "right",
|
|
1150
|
+
align: "start",
|
|
1151
|
+
sideOffset: 8,
|
|
1152
|
+
className: "editor-dark flex w-52 flex-col gap-2.5 border border-panel-border bg-panel p-2.5 text-panel-fg",
|
|
1153
|
+
children: [
|
|
1154
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1155
|
+
"div",
|
|
1156
|
+
{
|
|
1157
|
+
onPointerDownCapture: (e) => {
|
|
1158
|
+
pressed.current = true;
|
|
1159
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1160
|
+
},
|
|
1161
|
+
children: translucent ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_colorful.HexAlphaColorPicker, { color: hex, onChange: drag, className: "color-field-picker" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_colorful.HexColorPicker, { color: hex, onChange: drag, className: "color-field-picker" })
|
|
1162
|
+
}
|
|
1163
|
+
),
|
|
1164
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1166
|
+
"span",
|
|
1167
|
+
{
|
|
1168
|
+
className: "size-6 shrink-0 rounded border border-white/20",
|
|
1169
|
+
style: { background: hex }
|
|
1170
|
+
}
|
|
1171
|
+
),
|
|
1172
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1173
|
+
"input",
|
|
1174
|
+
{
|
|
1175
|
+
value: text,
|
|
1176
|
+
spellCheck: false,
|
|
1177
|
+
onChange: (e) => setText(e.target.value),
|
|
1178
|
+
onKeyDown: (e) => e.key === "Enter" && commit(text.trim()),
|
|
1179
|
+
onBlur: () => text !== value && commit(text.trim()),
|
|
1180
|
+
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"
|
|
1181
|
+
}
|
|
1182
|
+
),
|
|
1183
|
+
eyeDropper() && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1184
|
+
"button",
|
|
1185
|
+
{
|
|
1186
|
+
type: "button",
|
|
1187
|
+
onClick: () => void pickFromScreen(),
|
|
1188
|
+
title: labels.colorPickerEyedropper,
|
|
1189
|
+
"aria-label": labels.colorPickerEyedropper,
|
|
1190
|
+
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",
|
|
1191
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.Pipette, { className: "size-3" })
|
|
1192
|
+
}
|
|
1193
|
+
)
|
|
1194
|
+
] }),
|
|
1195
|
+
used.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1196
|
+
Swatches,
|
|
1197
|
+
{
|
|
1198
|
+
title: labels.colorPickerDocument,
|
|
1199
|
+
colors: used,
|
|
1200
|
+
current: hex,
|
|
1201
|
+
onPick: commit
|
|
1202
|
+
}
|
|
1203
|
+
),
|
|
1204
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1205
|
+
Swatches,
|
|
1206
|
+
{
|
|
1207
|
+
title: labels.colorPickerPresets,
|
|
1208
|
+
colors: PRESETS,
|
|
1209
|
+
current: hex,
|
|
1210
|
+
onPick: commit,
|
|
1211
|
+
transparentLabel: labels.colorPickerTransparent
|
|
1212
|
+
}
|
|
1213
|
+
)
|
|
1214
|
+
]
|
|
1215
|
+
}
|
|
1216
|
+
)
|
|
1217
|
+
]
|
|
1218
|
+
}
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
function Swatches({
|
|
1222
|
+
title,
|
|
1223
|
+
colors,
|
|
1224
|
+
current,
|
|
1225
|
+
onPick,
|
|
1226
|
+
transparentLabel
|
|
1227
|
+
}) {
|
|
1228
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
|
|
1229
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mb-1 text-[10px] font-medium tracking-wide text-panel-muted-fg uppercase", children: title }),
|
|
1230
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid grid-cols-8 gap-1", children: [
|
|
1231
|
+
colors.map((color) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1232
|
+
"button",
|
|
1233
|
+
{
|
|
1234
|
+
type: "button",
|
|
1235
|
+
title: color,
|
|
1236
|
+
onClick: () => onPick(color),
|
|
1237
|
+
className: cn(
|
|
1238
|
+
"aspect-square rounded border border-white/15 outline-none hover:scale-110 focus-visible:ring-2 focus-visible:ring-brand",
|
|
1239
|
+
color.toLowerCase() === current.toLowerCase() && "ring-2 ring-brand"
|
|
1240
|
+
),
|
|
1241
|
+
style: { background: color }
|
|
1242
|
+
},
|
|
1243
|
+
color
|
|
1244
|
+
)),
|
|
1245
|
+
transparentLabel && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1246
|
+
"button",
|
|
1247
|
+
{
|
|
1248
|
+
type: "button",
|
|
1249
|
+
title: transparentLabel,
|
|
1250
|
+
onClick: () => onPick("transparent"),
|
|
1251
|
+
className: "relative aspect-square overflow-hidden rounded border border-white/15 outline-none hover:scale-110 focus-visible:ring-2 focus-visible:ring-brand",
|
|
1252
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Checkerboard, {})
|
|
1253
|
+
}
|
|
1254
|
+
)
|
|
1255
|
+
] })
|
|
1256
|
+
] });
|
|
1257
|
+
}
|
|
1258
|
+
function Checkerboard() {
|
|
1259
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1260
|
+
"span",
|
|
1261
|
+
{
|
|
1262
|
+
className: "absolute inset-0",
|
|
1263
|
+
style: {
|
|
1264
|
+
backgroundImage: "repeating-conic-gradient(#9ca3af 0% 25%, #e5e7eb 0% 50%)",
|
|
1265
|
+
backgroundSize: "8px 8px"
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
function toHex(value, { opaque = false } = {}) {
|
|
1271
|
+
const v = value.trim().toLowerCase();
|
|
1272
|
+
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(v);
|
|
1273
|
+
if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;
|
|
1274
|
+
if (/^#[0-9a-f]{6}$/.test(v)) return v;
|
|
1275
|
+
if (/^#[0-9a-f]{8}$/.test(v)) return opaque || v.endsWith("ff") ? v.slice(0, 7) : v;
|
|
1276
|
+
const rgb = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)(?:[\s,/]+([\d.]+%?))?/.exec(v);
|
|
1277
|
+
if (rgb) {
|
|
1278
|
+
const channels = [rgb[1], rgb[2], rgb[3]].map((n) => Math.min(255, Number(n)));
|
|
1279
|
+
const alphaText = rgb[4];
|
|
1280
|
+
const alpha = alphaText?.endsWith("%") ? parseFloat(alphaText) / 100 : Number(alphaText ?? 1);
|
|
1281
|
+
if (!opaque && alpha < 1) channels.push(Math.round(Math.max(0, alpha) * 255));
|
|
1282
|
+
return `#${channels.map((n) => n.toString(16).padStart(2, "0")).join("")}`;
|
|
1283
|
+
}
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/components/editor/ImageField.tsx
|
|
1288
|
+
var import_lucide_react6 = require("lucide-react");
|
|
1289
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1290
|
+
function ImageField({
|
|
1291
|
+
value,
|
|
1292
|
+
labels,
|
|
1293
|
+
onChoose,
|
|
1294
|
+
onClear
|
|
1295
|
+
}) {
|
|
1296
|
+
const url = imageUrl(value);
|
|
1297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative", children: [
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1299
|
+
"button",
|
|
1300
|
+
{
|
|
1301
|
+
type: "button",
|
|
1302
|
+
onClick: onChoose,
|
|
1303
|
+
title: labels.chooseImage,
|
|
1304
|
+
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",
|
|
1305
|
+
children: [
|
|
1306
|
+
url ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: url, alt: "", className: "h-24 w-full bg-black/25 object-contain" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex h-16 flex-col items-center justify-center gap-1 text-xs text-panel-muted-fg", children: [
|
|
1307
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.ImageIcon, { className: "size-5" }),
|
|
1308
|
+
labels.chooseImage
|
|
1309
|
+
] }),
|
|
1310
|
+
url && /* @__PURE__ */ (0, import_jsx_runtime12.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 })
|
|
1311
|
+
]
|
|
1312
|
+
}
|
|
1313
|
+
),
|
|
1314
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1315
|
+
"button",
|
|
1316
|
+
{
|
|
1317
|
+
type: "button",
|
|
1318
|
+
onClick: onClear,
|
|
1319
|
+
title: labels.imageFieldRemove,
|
|
1320
|
+
"aria-label": labels.imageFieldRemove,
|
|
1321
|
+
className: "absolute top-1 right-1 flex size-5 items-center justify-center rounded bg-black/60 text-white hover:bg-black/80",
|
|
1322
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.X, { className: "size-3" })
|
|
1323
|
+
}
|
|
1324
|
+
)
|
|
1325
|
+
] });
|
|
1326
|
+
}
|
|
1327
|
+
function imageUrl(value) {
|
|
1328
|
+
const v = value.trim();
|
|
1329
|
+
if (!v || v === "none") return null;
|
|
1330
|
+
const css = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(v);
|
|
1331
|
+
if (css) return css[2] || null;
|
|
1332
|
+
return /^[a-z-]+\(/i.test(v) ? null : v;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/components/editor/EmailEditor.tsx
|
|
1336
|
+
var import_client = require("react-dom/client");
|
|
1337
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
587
1338
|
var SEC_ID_RE = /\bsec-([A-Za-z0-9_-]+)\b/;
|
|
588
1339
|
var OBJ_ID_RE = /\bobj-([A-Za-z0-9_-]+)\b/;
|
|
589
1340
|
var TYPE_LABEL = {
|
|
@@ -596,6 +1347,7 @@ function tagOf(c) {
|
|
|
596
1347
|
return String(c.get("tagName") || c.get("type") || "");
|
|
597
1348
|
}
|
|
598
1349
|
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>';
|
|
1350
|
+
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>';
|
|
599
1351
|
function isSection(c) {
|
|
600
1352
|
return tagOf(c) === "mj-section";
|
|
601
1353
|
}
|
|
@@ -678,6 +1430,75 @@ function wrapSelectionStyle(el, style) {
|
|
|
678
1430
|
} catch {
|
|
679
1431
|
}
|
|
680
1432
|
}
|
|
1433
|
+
function setupStyleFields(editor, labels) {
|
|
1434
|
+
const sm = editor.StyleManager;
|
|
1435
|
+
const base = sm.getType("base").view;
|
|
1436
|
+
const documentColors = () => {
|
|
1437
|
+
const source = `${editor.getHtml()} ${editor.getCss() ?? ""}`;
|
|
1438
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1439
|
+
const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
|
|
1440
|
+
for (const match of source.matchAll(colorValue)) {
|
|
1441
|
+
const hex = toHex(match[1], { opaque: true });
|
|
1442
|
+
if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
|
|
1443
|
+
}
|
|
1444
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
|
|
1445
|
+
};
|
|
1446
|
+
const ownValue = (property) => property.hasValue?.({ noParent: true }) ? String(property.getValue?.() ?? "") : "";
|
|
1447
|
+
const reactView = (field) => base.extend({
|
|
1448
|
+
create({ property }) {
|
|
1449
|
+
const el = document.createElement("div");
|
|
1450
|
+
el.addEventListener("change", (event) => event.stopPropagation());
|
|
1451
|
+
const root = (0, import_client.createRoot)(el);
|
|
1452
|
+
this.__root = root;
|
|
1453
|
+
this.__render = () => root.render(field(property, ownValue(property)));
|
|
1454
|
+
this.__render();
|
|
1455
|
+
return el;
|
|
1456
|
+
},
|
|
1457
|
+
update() {
|
|
1458
|
+
this.__render?.();
|
|
1459
|
+
},
|
|
1460
|
+
destroy() {
|
|
1461
|
+
const root = this.__root;
|
|
1462
|
+
this.__root = void 0;
|
|
1463
|
+
this.__render = void 0;
|
|
1464
|
+
setTimeout(() => root?.unmount());
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
sm.addType("color", {
|
|
1468
|
+
view: reactView((property, value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1469
|
+
ColorField,
|
|
1470
|
+
{
|
|
1471
|
+
value,
|
|
1472
|
+
placeholder: String(property.getDefaultValue?.() ?? ""),
|
|
1473
|
+
documentColors,
|
|
1474
|
+
labels,
|
|
1475
|
+
onChange: (next, partial) => property.upValue(next, { partial })
|
|
1476
|
+
}
|
|
1477
|
+
))
|
|
1478
|
+
});
|
|
1479
|
+
sm.addType("file", {
|
|
1480
|
+
view: reactView((property, value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1481
|
+
ImageField,
|
|
1482
|
+
{
|
|
1483
|
+
value,
|
|
1484
|
+
labels,
|
|
1485
|
+
onClear: () => property.upValue(""),
|
|
1486
|
+
onChoose: () => {
|
|
1487
|
+
const am = editor.AssetManager;
|
|
1488
|
+
am.open({
|
|
1489
|
+
types: ["image"],
|
|
1490
|
+
current: imageUrl(value) ?? void 0,
|
|
1491
|
+
select(asset, complete) {
|
|
1492
|
+
const url = typeof asset === "string" ? asset : asset.getSrc();
|
|
1493
|
+
property.upValue(url, { partial: !complete });
|
|
1494
|
+
if (complete) am.close();
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
))
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
681
1502
|
function setupRichText(editor, labels) {
|
|
682
1503
|
const rte = editor.RichTextEditor;
|
|
683
1504
|
if (!rte || rte.__customActions) return;
|
|
@@ -717,16 +1538,25 @@ function setupRichText(editor, labels) {
|
|
|
717
1538
|
function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
718
1539
|
const documents = useDocumentStore();
|
|
719
1540
|
const labels = useLabels();
|
|
720
|
-
const loadingRef = (0,
|
|
721
|
-
const
|
|
722
|
-
const
|
|
723
|
-
const
|
|
724
|
-
const [
|
|
725
|
-
const [
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
const
|
|
1541
|
+
const loadingRef = (0, import_react7.useRef)(false);
|
|
1542
|
+
const saveStatusRef = (0, import_react7.useRef)("idle");
|
|
1543
|
+
const stateListeners = (0, import_react7.useRef)(/* @__PURE__ */ new Set());
|
|
1544
|
+
const [loading, setLoading] = (0, import_react7.useState)(true);
|
|
1545
|
+
const [sidebarView, setSidebarView] = (0, import_react7.useState)("blocks");
|
|
1546
|
+
const [composeTarget, setComposeTarget] = (0, import_react7.useState)(null);
|
|
1547
|
+
const selectionSource = (0, import_react7.useRef)("other");
|
|
1548
|
+
const editorRef = (0, import_react7.useRef)(null);
|
|
1549
|
+
const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
|
|
1550
|
+
const readOnlyRef = (0, import_react7.useRef)(false);
|
|
1551
|
+
const applyReadOnly = (0, import_react7.useRef)(async () => {
|
|
1552
|
+
});
|
|
1553
|
+
(0, import_react7.useEffect)(() => void applyReadOnly.current(turnRunning), [turnRunning]);
|
|
1554
|
+
(0, import_react7.useEffect)(() => () => setLocked(false), [setLocked]);
|
|
1555
|
+
const [crumbs, setCrumbs] = (0, import_react7.useState)([]);
|
|
1556
|
+
const [selectedImageSrc, setSelectedImageSrc] = (0, import_react7.useState)(null);
|
|
1557
|
+
const [picker, setPicker] = (0, import_react7.useState)(
|
|
1558
|
+
null
|
|
1559
|
+
);
|
|
730
1560
|
const onEditor = async (editor) => {
|
|
731
1561
|
editorRef.current = editor;
|
|
732
1562
|
const buildCrumbs = (c) => {
|
|
@@ -753,11 +1583,12 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
753
1583
|
};
|
|
754
1584
|
const snapshot = () => ({
|
|
755
1585
|
device: editor.getDevice(),
|
|
756
|
-
canUndo: editor.UndoManager.hasUndo(),
|
|
757
|
-
canRedo: editor.UndoManager.hasRedo(),
|
|
1586
|
+
canUndo: !readOnlyRef.current && editor.UndoManager.hasUndo(),
|
|
1587
|
+
canRedo: !readOnlyRef.current && editor.UndoManager.hasRedo(),
|
|
758
1588
|
saveStatus: saveStatusRef.current,
|
|
759
1589
|
zoom: Math.round(editor.Canvas.getZoom?.() ?? 100),
|
|
760
|
-
contentWidth: bodyWidth()
|
|
1590
|
+
contentWidth: bodyWidth(),
|
|
1591
|
+
readOnly: readOnlyRef.current
|
|
761
1592
|
});
|
|
762
1593
|
const notifyState = () => {
|
|
763
1594
|
const s = snapshot();
|
|
@@ -781,6 +1612,14 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
781
1612
|
if (isSection(c)) addIdClass(c, "sec");
|
|
782
1613
|
if (!isCommentable(c)) return;
|
|
783
1614
|
const toolbar = [...c.get("toolbar") ?? []];
|
|
1615
|
+
if (tagOf(c) === "mj-image" && !toolbar.some((t) => t.command === "open-image-picker")) {
|
|
1616
|
+
toolbar.push({
|
|
1617
|
+
command: "open-image-picker",
|
|
1618
|
+
label: IMAGE_SVG,
|
|
1619
|
+
attributes: { title: labels.chooseImage }
|
|
1620
|
+
});
|
|
1621
|
+
c.set("toolbar", toolbar);
|
|
1622
|
+
}
|
|
784
1623
|
if (!toolbar.some((t) => t.command === "open-comments")) {
|
|
785
1624
|
toolbar.push({
|
|
786
1625
|
command: "open-comments",
|
|
@@ -790,6 +1629,21 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
790
1629
|
c.set("toolbar", toolbar);
|
|
791
1630
|
}
|
|
792
1631
|
};
|
|
1632
|
+
editor.Commands.add("open-image-picker", {
|
|
1633
|
+
run(ed) {
|
|
1634
|
+
const target = ed.getSelected();
|
|
1635
|
+
if (!target || tagOf(target) !== "mj-image") return;
|
|
1636
|
+
const am = ed.AssetManager;
|
|
1637
|
+
am.open({
|
|
1638
|
+
target,
|
|
1639
|
+
types: ["image"],
|
|
1640
|
+
select(asset, complete) {
|
|
1641
|
+
target.set({ src: asset.getSrc() });
|
|
1642
|
+
if (complete) am.close();
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
793
1647
|
editor.Commands.add("open-comments", {
|
|
794
1648
|
run(ed) {
|
|
795
1649
|
const target = targetOf(ed.getSelected() ?? void 0);
|
|
@@ -797,6 +1651,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
797
1651
|
}
|
|
798
1652
|
});
|
|
799
1653
|
setupRichText(editor, labels);
|
|
1654
|
+
setupStyleFields(editor, labels);
|
|
800
1655
|
const blockCategory = (label) => {
|
|
801
1656
|
const l = label.toLowerCase();
|
|
802
1657
|
if (/(column|section)/.test(l)) return "Layout";
|
|
@@ -825,6 +1680,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
825
1680
|
if (selectionSource.current === "canvas") setSidebarView("settings");
|
|
826
1681
|
selectionSource.current = "other";
|
|
827
1682
|
buildCrumbs(c);
|
|
1683
|
+
setSelectedImageSrc(c && tagOf(c) === "mj-image" ? String(c.get("src") ?? "") : null);
|
|
828
1684
|
if (c?.get("type") === "mj-image" && !c.getTrait("src")) {
|
|
829
1685
|
c.addTrait(
|
|
830
1686
|
{
|
|
@@ -838,40 +1694,99 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
838
1694
|
);
|
|
839
1695
|
}
|
|
840
1696
|
});
|
|
841
|
-
editor.on("component:deselected", () =>
|
|
1697
|
+
editor.on("component:deselected", () => {
|
|
1698
|
+
const rest = editor.getSelected();
|
|
1699
|
+
buildCrumbs(rest);
|
|
1700
|
+
setSelectedImageSrc(
|
|
1701
|
+
rest && tagOf(rest) === "mj-image" ? String(rest.get("src") ?? "") : null
|
|
1702
|
+
);
|
|
1703
|
+
});
|
|
1704
|
+
editor.on("component:update:src", (c) => {
|
|
1705
|
+
if (c === editor.getSelected()) setSelectedImageSrc(String(c.get("src") ?? ""));
|
|
1706
|
+
});
|
|
1707
|
+
let stored = "";
|
|
842
1708
|
const save = async () => {
|
|
843
|
-
|
|
1709
|
+
const view = editor.getEditing()?.getView();
|
|
1710
|
+
await view?.syncContent?.();
|
|
1711
|
+
const mjml = editor.getHtml();
|
|
1712
|
+
if (mjml === stored) return;
|
|
844
1713
|
setSave("saving");
|
|
845
1714
|
try {
|
|
846
|
-
await documents.save(docId, {
|
|
847
|
-
|
|
848
|
-
projectData: editor.getProjectData()
|
|
849
|
-
});
|
|
1715
|
+
await documents.save(docId, { mjml, projectData: editor.getProjectData() });
|
|
1716
|
+
stored = mjml;
|
|
850
1717
|
setSave("saved");
|
|
851
1718
|
} catch (e) {
|
|
852
1719
|
setSave("error");
|
|
853
|
-
|
|
1720
|
+
import_sonner2.toast.error(refusal(e) ?? labels.documentSaveFailed, { id: saveToastId(docId) });
|
|
854
1721
|
throw e;
|
|
855
1722
|
}
|
|
856
1723
|
};
|
|
1724
|
+
const queue = createSaveQueue(save, 1200);
|
|
857
1725
|
editor.on("update", () => {
|
|
858
1726
|
notifyState();
|
|
859
|
-
if (loadingRef.current)
|
|
860
|
-
if (saveTimer.current) clearTimeout(saveTimer.current);
|
|
861
|
-
saveTimer.current = setTimeout(() => void save().catch(console.error), 1200);
|
|
1727
|
+
if (!loadingRef.current && !readOnlyRef.current) queue.schedule();
|
|
862
1728
|
});
|
|
863
1729
|
editor.on("change:device", notifyState);
|
|
1730
|
+
const blocked = [
|
|
1731
|
+
"core:undo",
|
|
1732
|
+
"core:redo",
|
|
1733
|
+
"core:paste",
|
|
1734
|
+
"core:component-delete",
|
|
1735
|
+
"tlb-delete",
|
|
1736
|
+
"tlb-clone",
|
|
1737
|
+
"tlb-move",
|
|
1738
|
+
"open-assets",
|
|
1739
|
+
"open-image-picker"
|
|
1740
|
+
];
|
|
1741
|
+
for (const id of blocked) {
|
|
1742
|
+
editor.on(`command:run:before:${id}`, ({ options }) => {
|
|
1743
|
+
if (readOnlyRef.current) options.abort = true;
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
const lock = (on) => {
|
|
1747
|
+
readOnlyRef.current = on;
|
|
1748
|
+
setLocked(on);
|
|
1749
|
+
notifyState();
|
|
1750
|
+
};
|
|
1751
|
+
applyReadOnly.current = async (on) => {
|
|
1752
|
+
if (on) {
|
|
1753
|
+
lock(true);
|
|
1754
|
+
queue.cancel();
|
|
1755
|
+
editor.select([]);
|
|
1756
|
+
editor.AssetManager.close();
|
|
1757
|
+
setPicker(null);
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
try {
|
|
1761
|
+
await reload();
|
|
1762
|
+
} catch (e) {
|
|
1763
|
+
console.error(e);
|
|
1764
|
+
import_sonner2.toast.error(labels.documentLoadFailed, {
|
|
1765
|
+
id: `reload-${docId}`,
|
|
1766
|
+
duration: Infinity,
|
|
1767
|
+
action: { label: labels.retry, onClick: () => void applyReadOnly.current(false) }
|
|
1768
|
+
});
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
lock(false);
|
|
1772
|
+
};
|
|
1773
|
+
const reload = () => queue.run(async () => {
|
|
1774
|
+
const fresh = await documents.get(docId);
|
|
1775
|
+
if (fresh.mjml !== stored) loadMjml(fresh.mjml);
|
|
1776
|
+
});
|
|
864
1777
|
const loadMjml = (mjml) => {
|
|
865
1778
|
loadingRef.current = true;
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
editor.getWrapper()?.find(
|
|
871
|
-
|
|
872
|
-
|
|
1779
|
+
try {
|
|
1780
|
+
queue.cancel();
|
|
1781
|
+
stored = mjml;
|
|
1782
|
+
editor.setComponents(mjml || import_core2.STARTER_MJML);
|
|
1783
|
+
editor.getWrapper()?.find("mj-section").forEach(decorate);
|
|
1784
|
+
for (const t of Object.keys(TYPE_LABEL)) {
|
|
1785
|
+
editor.getWrapper()?.find(t).forEach(decorate);
|
|
1786
|
+
}
|
|
1787
|
+
} finally {
|
|
873
1788
|
loadingRef.current = false;
|
|
874
|
-
}
|
|
1789
|
+
}
|
|
875
1790
|
};
|
|
876
1791
|
const ensureHighlightStyles = () => {
|
|
877
1792
|
const cdoc = editor.Canvas.getDocument();
|
|
@@ -908,7 +1823,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
908
1823
|
const doc = await documents.get(docId);
|
|
909
1824
|
loadMjml(doc.mjml);
|
|
910
1825
|
} catch (e) {
|
|
911
|
-
|
|
1826
|
+
import_sonner2.toast.error(labels.documentLoadFailed);
|
|
912
1827
|
console.error(e);
|
|
913
1828
|
} finally {
|
|
914
1829
|
setLoading(false);
|
|
@@ -916,14 +1831,8 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
916
1831
|
onReady({
|
|
917
1832
|
getMjml: () => editor.getHtml(),
|
|
918
1833
|
getCompiledHtml: compiledHtml,
|
|
919
|
-
flushSave:
|
|
920
|
-
|
|
921
|
-
await save();
|
|
922
|
-
},
|
|
923
|
-
reloadFromDb: async () => {
|
|
924
|
-
const fresh = await documents.get(docId);
|
|
925
|
-
if (fresh.mjml !== editor.getHtml()) loadMjml(fresh.mjml);
|
|
926
|
-
},
|
|
1834
|
+
flushSave: () => queue.flush(),
|
|
1835
|
+
reloadFromDb: reload,
|
|
927
1836
|
highlightSection: (sectionId, on) => {
|
|
928
1837
|
ensureHighlightStyles();
|
|
929
1838
|
const el = sectionEl(sectionId);
|
|
@@ -939,10 +1848,12 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
939
1848
|
notifyState();
|
|
940
1849
|
},
|
|
941
1850
|
undo: () => {
|
|
1851
|
+
if (readOnlyRef.current) return;
|
|
942
1852
|
editor.UndoManager.undo();
|
|
943
1853
|
notifyState();
|
|
944
1854
|
},
|
|
945
1855
|
redo: () => {
|
|
1856
|
+
if (readOnlyRef.current) return;
|
|
946
1857
|
editor.UndoManager.redo();
|
|
947
1858
|
notifyState();
|
|
948
1859
|
},
|
|
@@ -951,6 +1862,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
951
1862
|
notifyState();
|
|
952
1863
|
},
|
|
953
1864
|
setContentWidth: (w) => {
|
|
1865
|
+
if (readOnlyRef.current) return;
|
|
954
1866
|
findByTag("mj-body")?.addAttributes({ width: w });
|
|
955
1867
|
notifyState();
|
|
956
1868
|
},
|
|
@@ -961,8 +1873,8 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
961
1873
|
}
|
|
962
1874
|
});
|
|
963
1875
|
};
|
|
964
|
-
return /* @__PURE__ */ (0,
|
|
965
|
-
|
|
1876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1877
|
+
import_react6.default,
|
|
966
1878
|
{
|
|
967
1879
|
grapesjs: import_grapesjs.default,
|
|
968
1880
|
className: "h-full",
|
|
@@ -975,6 +1887,19 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
975
1887
|
styleManager: { appendTo: "#gjs-styles" },
|
|
976
1888
|
traitManager: { appendTo: "#gjs-traits" },
|
|
977
1889
|
layerManager: { appendTo: "#gjs-layers" },
|
|
1890
|
+
assetManager: {
|
|
1891
|
+
// Never inline files: mail clients block `data:` images (see ImageLibrary).
|
|
1892
|
+
embedAsBase64: false,
|
|
1893
|
+
// Our own dialog instead of GrapesJS's modal. GrapesJS still decides when it opens
|
|
1894
|
+
// and what a choice applies to; `select(url, true)` sets the image and closes.
|
|
1895
|
+
custom: {
|
|
1896
|
+
open: (props) => setPicker({
|
|
1897
|
+
current: props.options?.current ?? (String(props.options?.target?.get("src") ?? "") || void 0),
|
|
1898
|
+
select: (url) => props.select(url, true)
|
|
1899
|
+
}),
|
|
1900
|
+
close: () => setPicker(null)
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
978
1903
|
deviceManager: {
|
|
979
1904
|
devices: [
|
|
980
1905
|
{ id: "desktop", name: "Desktop", width: "" },
|
|
@@ -984,19 +1909,26 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
984
1909
|
plugins: [import_grapesjs_mjml.default]
|
|
985
1910
|
},
|
|
986
1911
|
onEditor,
|
|
987
|
-
children: /* @__PURE__ */ (0,
|
|
988
|
-
/* @__PURE__ */ (0,
|
|
1912
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative flex h-full min-h-0 flex-1", inert: readOnly, children: [
|
|
1913
|
+
readOnly && /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsxs)("span", { className: "flex items-center gap-2 rounded-full bg-panel px-3 py-1 text-xs text-panel-fg shadow", children: [
|
|
1914
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Spinner, {}),
|
|
1915
|
+
" ",
|
|
1916
|
+
labels.agentWorking
|
|
1917
|
+
] }) }),
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
989
1919
|
LeftSidebar,
|
|
990
1920
|
{
|
|
991
1921
|
view: sidebarView,
|
|
992
1922
|
onViewChange: setSidebarView,
|
|
993
|
-
markLayersSource: () => selectionSource.current = "layers"
|
|
1923
|
+
markLayersSource: () => selectionSource.current = "layers",
|
|
1924
|
+
selectedImageSrc,
|
|
1925
|
+
onChooseImage: () => editorRef.current?.runCommand("open-image-picker")
|
|
994
1926
|
}
|
|
995
1927
|
),
|
|
996
|
-
/* @__PURE__ */ (0,
|
|
997
|
-
/* @__PURE__ */ (0,
|
|
998
|
-
/* @__PURE__ */ (0,
|
|
999
|
-
/* @__PURE__ */ (0,
|
|
1928
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
|
|
1929
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
|
|
1930
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react6.Canvas, { className: "h-full" }),
|
|
1931
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1000
1932
|
CanvasComments,
|
|
1001
1933
|
{
|
|
1002
1934
|
docId,
|
|
@@ -1006,12 +1938,27 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1006
1938
|
onOpenCountChange
|
|
1007
1939
|
}
|
|
1008
1940
|
),
|
|
1009
|
-
|
|
1010
|
-
|
|
1941
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1942
|
+
ImagePicker,
|
|
1943
|
+
{
|
|
1944
|
+
open: picker !== null,
|
|
1945
|
+
currentUrl: picker?.current,
|
|
1946
|
+
onSelect: (url) => {
|
|
1947
|
+
if (!readOnlyRef.current) picker?.select(url);
|
|
1948
|
+
setPicker(null);
|
|
1949
|
+
},
|
|
1950
|
+
onClose: () => {
|
|
1951
|
+
setPicker(null);
|
|
1952
|
+
editorRef.current?.AssetManager.close();
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
),
|
|
1956
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
|
|
1957
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Spinner, {}),
|
|
1011
1958
|
" Loading editor\u2026"
|
|
1012
1959
|
] })
|
|
1013
1960
|
] }),
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
1961
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
|
|
1015
1962
|
] })
|
|
1016
1963
|
] })
|
|
1017
1964
|
}
|
|
@@ -1022,32 +1969,34 @@ function CollapseSection({
|
|
|
1022
1969
|
defaultOpen = true,
|
|
1023
1970
|
children
|
|
1024
1971
|
}) {
|
|
1025
|
-
const [open, setOpen] = (0,
|
|
1026
|
-
return /* @__PURE__ */ (0,
|
|
1027
|
-
/* @__PURE__ */ (0,
|
|
1972
|
+
const [open, setOpen] = (0, import_react7.useState)(defaultOpen);
|
|
1973
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-panel-border", children: [
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1028
1975
|
"button",
|
|
1029
1976
|
{
|
|
1030
1977
|
type: "button",
|
|
1031
1978
|
onClick: () => setOpen((o) => !o),
|
|
1032
1979
|
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",
|
|
1033
1980
|
children: [
|
|
1034
|
-
/* @__PURE__ */ (0,
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronRight, { className: cn("size-3.5 transition-transform", open && "rotate-90") }),
|
|
1035
1982
|
title
|
|
1036
1983
|
]
|
|
1037
1984
|
}
|
|
1038
1985
|
),
|
|
1039
|
-
/* @__PURE__ */ (0,
|
|
1986
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn(!open && "hidden"), children })
|
|
1040
1987
|
] });
|
|
1041
1988
|
}
|
|
1042
1989
|
function LeftSidebar({
|
|
1043
1990
|
view,
|
|
1044
1991
|
onViewChange,
|
|
1045
|
-
markLayersSource
|
|
1992
|
+
markLayersSource,
|
|
1993
|
+
selectedImageSrc,
|
|
1994
|
+
onChooseImage
|
|
1046
1995
|
}) {
|
|
1047
|
-
const editor = (0,
|
|
1996
|
+
const editor = (0, import_react6.useEditorMaybe)();
|
|
1048
1997
|
const labels = useLabels();
|
|
1049
|
-
const [layersExpanded, setLayersExpanded] = (0,
|
|
1050
|
-
const [blockSearch, setBlockSearch] = (0,
|
|
1998
|
+
const [layersExpanded, setLayersExpanded] = (0, import_react7.useState)(false);
|
|
1999
|
+
const [blockSearch, setBlockSearch] = (0, import_react7.useState)("");
|
|
1051
2000
|
const changeView = (v) => {
|
|
1052
2001
|
if (v === "settings" && editor && !editor.getSelected()) {
|
|
1053
2002
|
const wrapper = editor.getWrapper();
|
|
@@ -1076,12 +2025,12 @@ function LeftSidebar({
|
|
|
1076
2025
|
cat.style.display = anyVisible ? "" : "none";
|
|
1077
2026
|
});
|
|
1078
2027
|
};
|
|
1079
|
-
return /* @__PURE__ */ (0,
|
|
1080
|
-
/* @__PURE__ */ (0,
|
|
2028
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg", children: [
|
|
2029
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex border-b border-panel-border", children: [
|
|
1081
2030
|
["blocks", "Blocks"],
|
|
1082
2031
|
["settings", "Settings"],
|
|
1083
2032
|
["layers", "Layers"]
|
|
1084
|
-
].map(([value, label]) => /* @__PURE__ */ (0,
|
|
2033
|
+
].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1085
2034
|
"button",
|
|
1086
2035
|
{
|
|
1087
2036
|
type: "button",
|
|
@@ -1095,10 +2044,10 @@ function LeftSidebar({
|
|
|
1095
2044
|
},
|
|
1096
2045
|
value
|
|
1097
2046
|
)) }),
|
|
1098
|
-
/* @__PURE__ */ (0,
|
|
1099
|
-
/* @__PURE__ */ (0,
|
|
1100
|
-
/* @__PURE__ */ (0,
|
|
1101
|
-
/* @__PURE__ */ (0,
|
|
2047
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "blocks" && "hidden"), children: [
|
|
2048
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative border-b border-panel-border p-2", children: [
|
|
2049
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react8.Search, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg" }),
|
|
2050
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1102
2051
|
Input,
|
|
1103
2052
|
{
|
|
1104
2053
|
value: blockSearch,
|
|
@@ -1107,23 +2056,58 @@ function LeftSidebar({
|
|
|
1107
2056
|
className: "h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg"
|
|
1108
2057
|
}
|
|
1109
2058
|
),
|
|
1110
|
-
/* @__PURE__ */ (0,
|
|
2059
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.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: "\u2318K" })
|
|
1111
2060
|
] }),
|
|
1112
|
-
/* @__PURE__ */ (0,
|
|
2061
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
|
|
1113
2062
|
] }),
|
|
1114
|
-
/* @__PURE__ */ (0,
|
|
1115
|
-
|
|
1116
|
-
|
|
2063
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: [
|
|
2064
|
+
selectedImageSrc !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-panel-border p-3", children: [
|
|
2065
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2066
|
+
"button",
|
|
2067
|
+
{
|
|
2068
|
+
type: "button",
|
|
2069
|
+
onClick: onChooseImage,
|
|
2070
|
+
title: labels.chooseImage,
|
|
2071
|
+
className: "group relative block w-full overflow-hidden rounded-lg border border-panel-border bg-panel-elevated",
|
|
2072
|
+
children: [
|
|
2073
|
+
selectedImageSrc ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2074
|
+
"img",
|
|
2075
|
+
{
|
|
2076
|
+
src: selectedImageSrc,
|
|
2077
|
+
alt: "",
|
|
2078
|
+
className: "h-32 w-full bg-black/25 object-contain"
|
|
2079
|
+
}
|
|
2080
|
+
) : /* @__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" }) }),
|
|
2081
|
+
/* @__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 })
|
|
2082
|
+
]
|
|
2083
|
+
}
|
|
2084
|
+
),
|
|
2085
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2086
|
+
Button,
|
|
2087
|
+
{
|
|
2088
|
+
type: "button",
|
|
2089
|
+
size: "sm",
|
|
2090
|
+
className: "mt-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
|
|
2091
|
+
onClick: onChooseImage,
|
|
2092
|
+
children: [
|
|
2093
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ImageUp, {}),
|
|
2094
|
+
labels.chooseImage
|
|
2095
|
+
]
|
|
2096
|
+
}
|
|
2097
|
+
)
|
|
2098
|
+
] }),
|
|
2099
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CollapseSection, { title: labels.attributes, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-traits" }) }),
|
|
2100
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CollapseSection, { title: labels.style, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-styles" }) })
|
|
1117
2101
|
] }),
|
|
1118
|
-
/* @__PURE__ */ (0,
|
|
2102
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1119
2103
|
"div",
|
|
1120
2104
|
{
|
|
1121
2105
|
className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
|
|
1122
2106
|
onMouseDownCapture: markLayersSource,
|
|
1123
2107
|
children: [
|
|
1124
|
-
/* @__PURE__ */ (0,
|
|
1125
|
-
/* @__PURE__ */ (0,
|
|
1126
|
-
/* @__PURE__ */ (0,
|
|
2108
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.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: [
|
|
2109
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: labels.expandAll }),
|
|
2110
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1127
2111
|
Switch,
|
|
1128
2112
|
{
|
|
1129
2113
|
checked: layersExpanded,
|
|
@@ -1132,7 +2116,7 @@ function LeftSidebar({
|
|
|
1132
2116
|
}
|
|
1133
2117
|
)
|
|
1134
2118
|
] }),
|
|
1135
|
-
/* @__PURE__ */ (0,
|
|
2119
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-layers", className: "min-h-0 flex-1 overflow-y-auto" })
|
|
1136
2120
|
]
|
|
1137
2121
|
}
|
|
1138
2122
|
)
|
|
@@ -1143,9 +2127,9 @@ function Breadcrumb({
|
|
|
1143
2127
|
onSelect
|
|
1144
2128
|
}) {
|
|
1145
2129
|
const labels = useLabels();
|
|
1146
|
-
return /* @__PURE__ */ (0,
|
|
1147
|
-
i > 0 && /* @__PURE__ */ (0,
|
|
1148
|
-
/* @__PURE__ */ (0,
|
|
2130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)("span", { className: "text-muted-foreground/60", children: labels.selectAnElement }) : crumbs.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-0.5", children: [
|
|
2131
|
+
i > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronRight, { className: "size-3 opacity-40" }),
|
|
2132
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1149
2133
|
"button",
|
|
1150
2134
|
{
|
|
1151
2135
|
type: "button",
|