@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.js
CHANGED
|
@@ -16,19 +16,23 @@ import {
|
|
|
16
16
|
useDocumentStore,
|
|
17
17
|
useImageLibrary,
|
|
18
18
|
useLabels
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-FTEJNUJL.js";
|
|
20
20
|
import {
|
|
21
21
|
Button,
|
|
22
22
|
Input,
|
|
23
23
|
Spinner,
|
|
24
|
+
Tooltip,
|
|
25
|
+
TooltipContent,
|
|
26
|
+
TooltipProvider,
|
|
27
|
+
TooltipTrigger,
|
|
24
28
|
cn
|
|
25
29
|
} from "./chunk-V6RR4VRD.js";
|
|
26
30
|
|
|
27
31
|
// src/components/editor/EmailEditor.tsx
|
|
28
32
|
import grapesjs from "grapesjs";
|
|
29
|
-
import GjsEditor, { Canvas, useEditorMaybe } from "@grapesjs/react";
|
|
33
|
+
import GjsEditor, { Canvas, useEditorMaybe as useEditorMaybe2 } from "@grapesjs/react";
|
|
30
34
|
import grapesjsMJML from "grapesjs-mjml";
|
|
31
|
-
import { useEffect as
|
|
35
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
|
|
32
36
|
import { STARTER_MJML } from "@mjml-agent-editor/core";
|
|
33
37
|
|
|
34
38
|
// src/lib/save-queue.ts
|
|
@@ -60,9 +64,17 @@ function createSaveQueue(save, delayMs) {
|
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
// src/lib/shortcuts.ts
|
|
68
|
+
function opensBlockSearch(e) {
|
|
69
|
+
if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey || e.isComposing) return false;
|
|
70
|
+
const target = e.target;
|
|
71
|
+
if (target?.isContentEditable) return false;
|
|
72
|
+
return !["INPUT", "TEXTAREA", "SELECT", "IFRAME"].includes(target?.tagName ?? "");
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
// src/components/editor/EmailEditor.tsx
|
|
64
76
|
import { toast as toast2 } from "sonner";
|
|
65
|
-
import { ChevronRight
|
|
77
|
+
import { ChevronRight as ChevronRight3, Mail, MousePointerClick } from "lucide-react";
|
|
66
78
|
|
|
67
79
|
// src/components/ui/switch.tsx
|
|
68
80
|
import "react";
|
|
@@ -353,11 +365,1042 @@ function matchesAccept(file, accept) {
|
|
|
353
365
|
});
|
|
354
366
|
}
|
|
355
367
|
|
|
368
|
+
// src/components/editor/inspector/Inspector.tsx
|
|
369
|
+
import { useEffect as useEffect4, useLayoutEffect, useReducer, useRef as useRef4, useState as useState4 } from "react";
|
|
370
|
+
import { useEditorMaybe } from "@grapesjs/react";
|
|
371
|
+
import { ArrowUp, ChevronRight as ChevronRight2, Copy, Monitor, Trash2 as Trash22, X as X2 } from "lucide-react";
|
|
372
|
+
|
|
356
373
|
// src/components/editor/ColorField.tsx
|
|
357
|
-
import { useCallback as useCallback2, useEffect as
|
|
374
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
358
375
|
import { HexAlphaColorPicker, HexColorPicker } from "react-colorful";
|
|
359
376
|
import { Pipette } from "lucide-react";
|
|
377
|
+
|
|
378
|
+
// src/components/editor/inspector/controls.tsx
|
|
379
|
+
import {
|
|
380
|
+
createContext,
|
|
381
|
+
useContext,
|
|
382
|
+
useEffect as useEffect2,
|
|
383
|
+
useId,
|
|
384
|
+
useRef as useRef2,
|
|
385
|
+
useState as useState2
|
|
386
|
+
} from "react";
|
|
387
|
+
import {
|
|
388
|
+
AlignCenter,
|
|
389
|
+
AlignJustify,
|
|
390
|
+
AlignLeft,
|
|
391
|
+
AlignRight,
|
|
392
|
+
ChevronDown,
|
|
393
|
+
ChevronUp,
|
|
394
|
+
CircleHelp,
|
|
395
|
+
Grid2x2,
|
|
396
|
+
RotateCcw
|
|
397
|
+
} from "lucide-react";
|
|
398
|
+
|
|
399
|
+
// src/components/editor/inspector/values.ts
|
|
400
|
+
var SIDES = ["top", "right", "bottom", "left"];
|
|
401
|
+
function expandBox(value) {
|
|
402
|
+
const parts = value.trim().split(/\s+/).filter(Boolean);
|
|
403
|
+
if (parts.length === 0 || parts.length > 4) return null;
|
|
404
|
+
const [t, r = t, b = t, l = r] = parts;
|
|
405
|
+
return [t, r, b, l];
|
|
406
|
+
}
|
|
407
|
+
function readBox(attributes, attr) {
|
|
408
|
+
const short = expandBox(attributes[attr] ?? "") ?? ["", "", "", ""];
|
|
409
|
+
return SIDES.map((side, i) => attributes[`${attr}-${side}`] ?? short[i]);
|
|
410
|
+
}
|
|
411
|
+
function boxShorthand(sides) {
|
|
412
|
+
const [t, r, b, l] = sides.map((s) => s.trim() || "0");
|
|
413
|
+
if (t === b && r === l) return t === r ? t : `${t} ${r}`;
|
|
414
|
+
if (r === l) return `${t} ${r} ${b}`;
|
|
415
|
+
return `${t} ${r} ${b} ${l}`;
|
|
416
|
+
}
|
|
417
|
+
function parseLength(value, units) {
|
|
418
|
+
const match = /^(-?\d*\.?\d+)\s*(px|%|em)?$/.exec(value.trim());
|
|
419
|
+
if (!match) return null;
|
|
420
|
+
const unit = match[2] ?? "";
|
|
421
|
+
return units.includes(unit) ? { amount: match[1], unit } : null;
|
|
422
|
+
}
|
|
423
|
+
var NO_SHARE = "-noshare";
|
|
424
|
+
function parseNetwork(name) {
|
|
425
|
+
return name.endsWith(NO_SHARE) ? { network: name.slice(0, -NO_SHARE.length), share: false } : { network: name, share: name !== "custom" && name !== "" };
|
|
426
|
+
}
|
|
427
|
+
function networkName(network, share) {
|
|
428
|
+
return share || network === "custom" ? network : `${network}${NO_SHARE}`;
|
|
429
|
+
}
|
|
430
|
+
var LINK_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel", "sms"]);
|
|
431
|
+
function isSafeLink(value) {
|
|
432
|
+
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, "");
|
|
433
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(decoded)?.[1];
|
|
434
|
+
return !scheme || LINK_SCHEMES.has(scheme.toLowerCase());
|
|
435
|
+
}
|
|
436
|
+
function isSafeNavbarLink(baseUrl, href) {
|
|
437
|
+
return isSafeLink(baseUrl) && isSafeLink(href) && isSafeLink(baseUrl + href);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/components/editor/inspector/controls.tsx
|
|
360
441
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
442
|
+
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";
|
|
443
|
+
var FieldLabel = createContext({});
|
|
444
|
+
var useFieldLabel = () => {
|
|
445
|
+
const { label, help } = useContext(FieldLabel);
|
|
446
|
+
return { "aria-labelledby": label, "aria-describedby": help };
|
|
447
|
+
};
|
|
448
|
+
function Row({
|
|
449
|
+
label,
|
|
450
|
+
help,
|
|
451
|
+
own,
|
|
452
|
+
wide,
|
|
453
|
+
inline,
|
|
454
|
+
labels,
|
|
455
|
+
onReset,
|
|
456
|
+
children
|
|
457
|
+
}) {
|
|
458
|
+
const id = useId();
|
|
459
|
+
const helpId = useId();
|
|
460
|
+
const [helpOpen, setHelpOpen] = useState2(false);
|
|
461
|
+
const control = /* @__PURE__ */ jsx3(FieldLabel.Provider, { value: { label: id, help: help ? helpId : void 0 }, children });
|
|
462
|
+
const caption = /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1.5 text-[11px] text-panel-muted-fg", children: [
|
|
463
|
+
/* @__PURE__ */ jsx3(
|
|
464
|
+
"span",
|
|
465
|
+
{
|
|
466
|
+
"aria-hidden": true,
|
|
467
|
+
className: cn("size-1.5 shrink-0 rounded-full", own ? "bg-brand" : "bg-transparent")
|
|
468
|
+
}
|
|
469
|
+
),
|
|
470
|
+
/* @__PURE__ */ jsx3("span", { id, className: "truncate", children: label }),
|
|
471
|
+
help && // Its own provider: a host need not wrap the editor in one, as only EditorHeader asked.
|
|
472
|
+
// Opened by a tap as well: Radix opens a tooltip on hover and focus, not on touch.
|
|
473
|
+
/* @__PURE__ */ jsx3(TooltipProvider, { children: /* @__PURE__ */ jsxs2(Tooltip, { open: helpOpen, onOpenChange: setHelpOpen, children: [
|
|
474
|
+
/* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(
|
|
475
|
+
"button",
|
|
476
|
+
{
|
|
477
|
+
type: "button",
|
|
478
|
+
"aria-label": help,
|
|
479
|
+
onClick: () => setHelpOpen((open) => !open),
|
|
480
|
+
className: "-m-0.5 shrink-0 rounded p-0.5 text-panel-muted-fg/70 hover:text-panel-fg focus-visible:text-panel-fg",
|
|
481
|
+
children: /* @__PURE__ */ jsx3(CircleHelp, { className: "size-3" })
|
|
482
|
+
}
|
|
483
|
+
) }),
|
|
484
|
+
/* @__PURE__ */ jsx3(TooltipContent, { side: "top", className: "max-w-56 leading-snug", children: help })
|
|
485
|
+
] }) }),
|
|
486
|
+
own && /* @__PURE__ */ jsx3(
|
|
487
|
+
"button",
|
|
488
|
+
{
|
|
489
|
+
type: "button",
|
|
490
|
+
onClick: onReset,
|
|
491
|
+
title: labels.resetToDefault,
|
|
492
|
+
"aria-label": `${labels.resetToDefault}: ${label}`,
|
|
493
|
+
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",
|
|
494
|
+
children: /* @__PURE__ */ jsx3(RotateCcw, { className: "size-3" })
|
|
495
|
+
}
|
|
496
|
+
)
|
|
497
|
+
] });
|
|
498
|
+
return /* @__PURE__ */ jsxs2("div", { className: "group/row px-3 py-1", children: [
|
|
499
|
+
wide ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
|
|
500
|
+
caption,
|
|
501
|
+
control
|
|
502
|
+
] }) : inline ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2", children: [
|
|
503
|
+
caption,
|
|
504
|
+
control
|
|
505
|
+
] }) : /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-[92px_minmax(0,1fr)] items-center gap-2", children: [
|
|
506
|
+
caption,
|
|
507
|
+
control
|
|
508
|
+
] }),
|
|
509
|
+
help && /* @__PURE__ */ jsx3("span", { id: helpId, hidden: true, children: help })
|
|
510
|
+
] });
|
|
511
|
+
}
|
|
512
|
+
function useDraft(value, commit, source = value) {
|
|
513
|
+
const [draft, setDraft] = useState2(value);
|
|
514
|
+
useEffect2(() => setDraft(value), [value, source]);
|
|
515
|
+
const done = () => {
|
|
516
|
+
if (draft.trim() !== value) commit(draft.trim());
|
|
517
|
+
};
|
|
518
|
+
const onKeyDown = (e) => {
|
|
519
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
520
|
+
if (e.key === "Escape") {
|
|
521
|
+
setDraft(value);
|
|
522
|
+
requestAnimationFrame(() => e.target.blur());
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
return { draft, setDraft, done, onKeyDown };
|
|
526
|
+
}
|
|
527
|
+
function TextInput({
|
|
528
|
+
value,
|
|
529
|
+
placeholder,
|
|
530
|
+
accept,
|
|
531
|
+
onCommit
|
|
532
|
+
}) {
|
|
533
|
+
const [invalid, setInvalid] = useState2(false);
|
|
534
|
+
const field = useFieldLabel();
|
|
535
|
+
const { draft, setDraft, done, onKeyDown } = useDraft(value, (next) => {
|
|
536
|
+
const ok = !accept || accept(next);
|
|
537
|
+
setInvalid(!ok);
|
|
538
|
+
if (ok) onCommit(next);
|
|
539
|
+
});
|
|
540
|
+
return /* @__PURE__ */ jsx3(
|
|
541
|
+
"input",
|
|
542
|
+
{
|
|
543
|
+
className: cn(FIELD, invalid && "border-red-400"),
|
|
544
|
+
...field,
|
|
545
|
+
"aria-invalid": invalid,
|
|
546
|
+
value: draft,
|
|
547
|
+
placeholder,
|
|
548
|
+
onChange: (e) => setDraft(e.target.value),
|
|
549
|
+
onBlur: done,
|
|
550
|
+
onKeyDown
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
var BARE_NUMBER = /^-?\d*\.?\d+$/;
|
|
555
|
+
function normaliseLength(text, units, negative = false, unit = units[0]) {
|
|
556
|
+
if (text === "") return "";
|
|
557
|
+
const parsed = BARE_NUMBER.test(text) ? { amount: text, unit } : parseLength(text, units);
|
|
558
|
+
if (!parsed || !negative && parsed.amount.startsWith("-")) return null;
|
|
559
|
+
return `${parsed.amount}${parsed.unit}`;
|
|
560
|
+
}
|
|
561
|
+
function stepAmount(amount, delta, negative = false) {
|
|
562
|
+
const moved = Math.round((Number(amount) + delta) * 100) / 100;
|
|
563
|
+
const next = String(negative ? moved : Math.max(0, moved));
|
|
564
|
+
return BARE_NUMBER.test(next) ? next : amount;
|
|
565
|
+
}
|
|
566
|
+
var REPEAT_AFTER_MS = 400;
|
|
567
|
+
var REPEAT_EVERY_MS = 60;
|
|
568
|
+
function LengthInput({
|
|
569
|
+
value,
|
|
570
|
+
placeholder,
|
|
571
|
+
units,
|
|
572
|
+
negative = false,
|
|
573
|
+
compact = false,
|
|
574
|
+
labelledBy,
|
|
575
|
+
labels,
|
|
576
|
+
onCommit
|
|
577
|
+
}) {
|
|
578
|
+
const fieldLabel = useFieldLabel();
|
|
579
|
+
const own = parseLength(value, units);
|
|
580
|
+
const fallback = parseLength(placeholder ?? "", units);
|
|
581
|
+
const [picked, setPicked] = useState2(null);
|
|
582
|
+
const unit = own?.unit ?? picked ?? fallback?.unit ?? units[0];
|
|
583
|
+
const [invalid, setInvalid] = useState2(false);
|
|
584
|
+
const commit = useRef2(onCommit);
|
|
585
|
+
commit.current = onCommit;
|
|
586
|
+
const endHold = useRef2(null);
|
|
587
|
+
useEffect2(() => () => endHold.current?.(), []);
|
|
588
|
+
const { draft, setDraft, done, onKeyDown } = useDraft(
|
|
589
|
+
own ? own.amount : value,
|
|
590
|
+
(next) => {
|
|
591
|
+
endHold.current?.();
|
|
592
|
+
const normal = normaliseLength(next, units, negative, unit);
|
|
593
|
+
setInvalid(normal === null);
|
|
594
|
+
if (normal !== null) commit.current(normal);
|
|
595
|
+
},
|
|
596
|
+
value
|
|
597
|
+
);
|
|
598
|
+
const from = () => {
|
|
599
|
+
const text = draft.trim();
|
|
600
|
+
if (BARE_NUMBER.test(text)) return { amount: text, unit };
|
|
601
|
+
if (text === "") return { amount: fallback?.amount ?? "0", unit };
|
|
602
|
+
return parseLength(text, units);
|
|
603
|
+
};
|
|
604
|
+
const stepOnce = (delta) => {
|
|
605
|
+
endHold.current?.();
|
|
606
|
+
const start = from();
|
|
607
|
+
if (!start) return setInvalid(true);
|
|
608
|
+
const amount = stepAmount(start.amount, delta, negative);
|
|
609
|
+
setInvalid(false);
|
|
610
|
+
setDraft(amount);
|
|
611
|
+
commit.current(`${amount}${start.unit}`);
|
|
612
|
+
};
|
|
613
|
+
const onKey = (e) => {
|
|
614
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return onKeyDown(e);
|
|
615
|
+
e.preventDefault();
|
|
616
|
+
stepOnce((e.key === "ArrowUp" ? 1 : -1) * (e.shiftKey ? 10 : 1));
|
|
617
|
+
};
|
|
618
|
+
const press = (direction) => (e) => {
|
|
619
|
+
if (e.button !== 0 || endHold.current) return;
|
|
620
|
+
e.preventDefault();
|
|
621
|
+
const start = from();
|
|
622
|
+
if (!start) return setInvalid(true);
|
|
623
|
+
const delta = direction * (e.shiftKey ? 10 : 1);
|
|
624
|
+
let amount = stepAmount(start.amount, delta, negative);
|
|
625
|
+
const show = () => {
|
|
626
|
+
setInvalid(false);
|
|
627
|
+
setDraft(amount);
|
|
628
|
+
commit.current(`${amount}${start.unit}`, true);
|
|
629
|
+
};
|
|
630
|
+
show();
|
|
631
|
+
let timer = window.setTimeout(function repeat() {
|
|
632
|
+
amount = stepAmount(amount, delta, negative);
|
|
633
|
+
show();
|
|
634
|
+
timer = window.setTimeout(repeat, REPEAT_EVERY_MS);
|
|
635
|
+
}, REPEAT_AFTER_MS);
|
|
636
|
+
const button = e.currentTarget;
|
|
637
|
+
try {
|
|
638
|
+
button.setPointerCapture(e.pointerId);
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
const events = ["pointerup", "pointercancel", "lostpointercapture"];
|
|
642
|
+
const pointerId = e.pointerId;
|
|
643
|
+
const end = () => {
|
|
644
|
+
window.clearTimeout(timer);
|
|
645
|
+
for (const type of events) button.removeEventListener(type, release);
|
|
646
|
+
window.removeEventListener("pointerup", release);
|
|
647
|
+
window.removeEventListener("blur", end);
|
|
648
|
+
endHold.current = null;
|
|
649
|
+
commit.current(`${amount}${start.unit}`);
|
|
650
|
+
};
|
|
651
|
+
const release = (ev) => {
|
|
652
|
+
if (ev.pointerId === void 0 || ev.pointerId === pointerId)
|
|
653
|
+
end();
|
|
654
|
+
};
|
|
655
|
+
endHold.current = end;
|
|
656
|
+
for (const type of events) button.addEventListener(type, release);
|
|
657
|
+
window.addEventListener("pointerup", release);
|
|
658
|
+
window.addEventListener("blur", end);
|
|
659
|
+
};
|
|
660
|
+
const click = (direction) => (e) => {
|
|
661
|
+
if (e.detail === 0) stepOnce(direction);
|
|
662
|
+
};
|
|
663
|
+
const pickUnit = () => {
|
|
664
|
+
endHold.current?.();
|
|
665
|
+
const next = units[(units.indexOf(unit) + 1) % units.length];
|
|
666
|
+
const text = draft.trim();
|
|
667
|
+
if (!BARE_NUMBER.test(text)) return setPicked(next);
|
|
668
|
+
const normal = normaliseLength(text, units, negative, next);
|
|
669
|
+
if (normal === null) return setInvalid(true);
|
|
670
|
+
setPicked(next);
|
|
671
|
+
commit.current(normal);
|
|
672
|
+
};
|
|
673
|
+
const unitName = (u) => u || labels.noUnit;
|
|
674
|
+
const readable = draft.trim() === "" || BARE_NUMBER.test(draft.trim());
|
|
675
|
+
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";
|
|
676
|
+
return /* @__PURE__ */ jsxs2(
|
|
677
|
+
"div",
|
|
678
|
+
{
|
|
679
|
+
className: cn(
|
|
680
|
+
"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",
|
|
681
|
+
invalid && "border-red-400"
|
|
682
|
+
),
|
|
683
|
+
children: [
|
|
684
|
+
/* @__PURE__ */ jsx3(
|
|
685
|
+
"input",
|
|
686
|
+
{
|
|
687
|
+
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",
|
|
688
|
+
value: draft,
|
|
689
|
+
placeholder: fallback ? fallback.amount : placeholder,
|
|
690
|
+
inputMode: "decimal",
|
|
691
|
+
...fieldLabel,
|
|
692
|
+
"aria-labelledby": labelledBy ?? fieldLabel["aria-labelledby"],
|
|
693
|
+
"aria-invalid": invalid,
|
|
694
|
+
onChange: (e) => {
|
|
695
|
+
endHold.current?.();
|
|
696
|
+
setDraft(e.target.value);
|
|
697
|
+
},
|
|
698
|
+
onBlur: done,
|
|
699
|
+
onKeyDown: onKey
|
|
700
|
+
}
|
|
701
|
+
),
|
|
702
|
+
readable && (units.length > 1 && !compact ? /* @__PURE__ */ jsx3(
|
|
703
|
+
"button",
|
|
704
|
+
{
|
|
705
|
+
type: "button",
|
|
706
|
+
onClick: pickUnit,
|
|
707
|
+
title: labels.changeUnit(unitName(unit)),
|
|
708
|
+
"aria-label": labels.changeUnit(unitName(unit)),
|
|
709
|
+
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",
|
|
710
|
+
children: unitName(unit)
|
|
711
|
+
}
|
|
712
|
+
) : (
|
|
713
|
+
// Narrow, the unit gives its place to the arrows while they show; a unit is typed.
|
|
714
|
+
/* @__PURE__ */ jsx3(
|
|
715
|
+
"span",
|
|
716
|
+
{
|
|
717
|
+
"aria-hidden": true,
|
|
718
|
+
className: cn(
|
|
719
|
+
"shrink-0 px-0.5 font-mono text-[10px] text-panel-muted-fg",
|
|
720
|
+
compact && "group-focus-within/length:hidden group-hover/length:hidden"
|
|
721
|
+
),
|
|
722
|
+
children: unitName(unit)
|
|
723
|
+
}
|
|
724
|
+
)
|
|
725
|
+
)),
|
|
726
|
+
/* @__PURE__ */ jsxs2(
|
|
727
|
+
"span",
|
|
728
|
+
{
|
|
729
|
+
className: cn(
|
|
730
|
+
"h-full shrink-0 flex-col border-l border-panel-border",
|
|
731
|
+
compact ? "hidden group-focus-within/length:flex group-hover/length:flex" : "flex"
|
|
732
|
+
),
|
|
733
|
+
children: [
|
|
734
|
+
/* @__PURE__ */ jsx3(
|
|
735
|
+
"button",
|
|
736
|
+
{
|
|
737
|
+
type: "button",
|
|
738
|
+
tabIndex: -1,
|
|
739
|
+
"aria-label": labels.increase,
|
|
740
|
+
onPointerDown: press(1),
|
|
741
|
+
onClick: click(1),
|
|
742
|
+
className: arrow,
|
|
743
|
+
children: /* @__PURE__ */ jsx3(ChevronUp, { className: "size-2.5" })
|
|
744
|
+
}
|
|
745
|
+
),
|
|
746
|
+
/* @__PURE__ */ jsx3(
|
|
747
|
+
"button",
|
|
748
|
+
{
|
|
749
|
+
type: "button",
|
|
750
|
+
tabIndex: -1,
|
|
751
|
+
"aria-label": labels.decrease,
|
|
752
|
+
onPointerDown: press(-1),
|
|
753
|
+
onClick: click(-1),
|
|
754
|
+
className: arrow,
|
|
755
|
+
children: /* @__PURE__ */ jsx3(ChevronDown, { className: "size-2.5" })
|
|
756
|
+
}
|
|
757
|
+
)
|
|
758
|
+
]
|
|
759
|
+
}
|
|
760
|
+
)
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
function BoxInput({
|
|
766
|
+
attributes,
|
|
767
|
+
attr,
|
|
768
|
+
placeholder,
|
|
769
|
+
units,
|
|
770
|
+
labels,
|
|
771
|
+
onCommit
|
|
772
|
+
}) {
|
|
773
|
+
const sides = readBox(attributes, attr);
|
|
774
|
+
const defaults = readBox(placeholder ? { [attr]: placeholder } : {}, attr);
|
|
775
|
+
const symmetric = sides[0] === sides[2] && sides[1] === sides[3];
|
|
776
|
+
const [each, setEach] = useState2(!symmetric);
|
|
777
|
+
useEffect2(() => {
|
|
778
|
+
if (!symmetric) setEach(true);
|
|
779
|
+
}, [symmetric]);
|
|
780
|
+
const set = (indices, v, partial) => {
|
|
781
|
+
const next = sides.map((s, i) => s || defaults[i]);
|
|
782
|
+
for (const i of indices) next[i] = v || defaults[i];
|
|
783
|
+
onCommit(next.every((s, i) => s === defaults[i]) ? "" : boxShorthand(next), partial);
|
|
784
|
+
};
|
|
785
|
+
const fieldLabel = useFieldLabel();
|
|
786
|
+
const sideId = useId();
|
|
787
|
+
const sideNames = [labels.sideTop, labels.sideRight, labels.sideBottom, labels.sideLeft];
|
|
788
|
+
const pairs = each ? SIDES.map((_, i) => [sideNames[i], [i]]) : [
|
|
789
|
+
[labels.sidesVertical, [0, 2]],
|
|
790
|
+
[labels.sidesHorizontal, [1, 3]]
|
|
791
|
+
];
|
|
792
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-start gap-1.5", children: [
|
|
793
|
+
/* @__PURE__ */ jsx3("div", { className: cn("grid flex-1 gap-1.5", each ? "grid-cols-4" : "grid-cols-2"), children: pairs.map(([name, indices]) => /* @__PURE__ */ jsxs2("label", { className: "flex min-w-0 flex-col gap-0.5", title: name, children: [
|
|
794
|
+
/* @__PURE__ */ jsx3(
|
|
795
|
+
LengthInput,
|
|
796
|
+
{
|
|
797
|
+
value: sides[indices[0]],
|
|
798
|
+
placeholder: defaults[indices[0]] || "0",
|
|
799
|
+
units,
|
|
800
|
+
compact: each,
|
|
801
|
+
labels,
|
|
802
|
+
labelledBy: [fieldLabel["aria-labelledby"], `${sideId}-${indices[0]}`].filter(Boolean).join(" "),
|
|
803
|
+
onCommit: (v, partial) => set(indices, v, partial)
|
|
804
|
+
}
|
|
805
|
+
),
|
|
806
|
+
/* @__PURE__ */ jsx3(
|
|
807
|
+
"span",
|
|
808
|
+
{
|
|
809
|
+
id: `${sideId}-${indices[0]}`,
|
|
810
|
+
className: "truncate text-center text-[10px] text-panel-muted-fg/80",
|
|
811
|
+
children: name
|
|
812
|
+
}
|
|
813
|
+
)
|
|
814
|
+
] }, name)) }),
|
|
815
|
+
/* @__PURE__ */ jsx3(
|
|
816
|
+
"button",
|
|
817
|
+
{
|
|
818
|
+
type: "button",
|
|
819
|
+
onClick: () => setEach((v) => !v),
|
|
820
|
+
disabled: each && !symmetric,
|
|
821
|
+
"aria-pressed": each,
|
|
822
|
+
title: labels.editEachSide,
|
|
823
|
+
"aria-label": labels.editEachSide,
|
|
824
|
+
className: cn(
|
|
825
|
+
"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",
|
|
826
|
+
each && "border-brand text-panel-fg"
|
|
827
|
+
),
|
|
828
|
+
children: /* @__PURE__ */ jsx3(Grid2x2, { className: "size-3.5" })
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
] });
|
|
832
|
+
}
|
|
833
|
+
var ALIGN_ICONS = {
|
|
834
|
+
left: /* @__PURE__ */ jsx3(AlignLeft, { className: "size-3.5" }),
|
|
835
|
+
center: /* @__PURE__ */ jsx3(AlignCenter, { className: "size-3.5" }),
|
|
836
|
+
right: /* @__PURE__ */ jsx3(AlignRight, { className: "size-3.5" }),
|
|
837
|
+
justify: /* @__PURE__ */ jsx3(AlignJustify, { className: "size-3.5" })
|
|
838
|
+
};
|
|
839
|
+
function Segmented({
|
|
840
|
+
value,
|
|
841
|
+
fallback,
|
|
842
|
+
options,
|
|
843
|
+
name,
|
|
844
|
+
onCommit
|
|
845
|
+
}) {
|
|
846
|
+
const current = value || fallback;
|
|
847
|
+
const iconsOnly = options.every((o) => o.value in ALIGN_ICONS);
|
|
848
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex h-7 overflow-hidden rounded-md border border-panel-border bg-panel-elevated", children: options.map((o) => {
|
|
849
|
+
const active = current === o.value;
|
|
850
|
+
return /* @__PURE__ */ jsx3(
|
|
851
|
+
"button",
|
|
852
|
+
{
|
|
853
|
+
type: "button",
|
|
854
|
+
"aria-pressed": active,
|
|
855
|
+
title: name(o),
|
|
856
|
+
"aria-label": name(o),
|
|
857
|
+
onClick: () => o.value !== value && onCommit(o.value),
|
|
858
|
+
className: cn(
|
|
859
|
+
"flex min-w-0 flex-1 items-center justify-center px-1 text-[11px] text-panel-muted-fg transition-colors hover:text-panel-fg",
|
|
860
|
+
active && (value ? "bg-brand text-brand-fg hover:text-brand-fg" : "bg-panel-hover text-panel-fg")
|
|
861
|
+
),
|
|
862
|
+
children: iconsOnly ? ALIGN_ICONS[o.value] : /* @__PURE__ */ jsx3("span", { className: "truncate", children: name(o) })
|
|
863
|
+
},
|
|
864
|
+
o.value
|
|
865
|
+
);
|
|
866
|
+
}) });
|
|
867
|
+
}
|
|
868
|
+
function Select({
|
|
869
|
+
value,
|
|
870
|
+
fallback,
|
|
871
|
+
options,
|
|
872
|
+
name,
|
|
873
|
+
onCommit
|
|
874
|
+
}) {
|
|
875
|
+
const known = !value || options.some((o) => o.value === value);
|
|
876
|
+
const fallbackOption = options.find((o) => o.value === fallback);
|
|
877
|
+
const field = useFieldLabel();
|
|
878
|
+
return /* @__PURE__ */ jsxs2(
|
|
879
|
+
"select",
|
|
880
|
+
{
|
|
881
|
+
className: cn(FIELD, "px-1.5"),
|
|
882
|
+
...field,
|
|
883
|
+
value: value || "",
|
|
884
|
+
onChange: (e) => onCommit(e.target.value),
|
|
885
|
+
children: [
|
|
886
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: fallbackOption ? name(fallbackOption) : "\u2014" }),
|
|
887
|
+
!known && /* @__PURE__ */ jsx3("option", { value, children: value }),
|
|
888
|
+
options.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: name(o) }, o.value))
|
|
889
|
+
]
|
|
890
|
+
}
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
function Toggle({
|
|
894
|
+
checked,
|
|
895
|
+
label,
|
|
896
|
+
onCommit
|
|
897
|
+
}) {
|
|
898
|
+
return /* @__PURE__ */ jsx3(
|
|
899
|
+
Switch,
|
|
900
|
+
{
|
|
901
|
+
checked,
|
|
902
|
+
onCheckedChange: onCommit,
|
|
903
|
+
"aria-label": label,
|
|
904
|
+
className: "justify-self-end data-checked:bg-brand data-unchecked:bg-panel-border"
|
|
905
|
+
}
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// src/components/editor/mjml-attributes.ts
|
|
910
|
+
var ALLOWED_ATTRIBUTES = {
|
|
911
|
+
"mj-body": [
|
|
912
|
+
"background-color",
|
|
913
|
+
"width"
|
|
914
|
+
],
|
|
915
|
+
"mj-wrapper": [
|
|
916
|
+
"background-color",
|
|
917
|
+
"background-position",
|
|
918
|
+
"background-position-x",
|
|
919
|
+
"background-position-y",
|
|
920
|
+
"background-repeat",
|
|
921
|
+
"background-size",
|
|
922
|
+
"background-url",
|
|
923
|
+
"border",
|
|
924
|
+
"border-bottom",
|
|
925
|
+
"border-left",
|
|
926
|
+
"border-radius",
|
|
927
|
+
"border-right",
|
|
928
|
+
"border-top",
|
|
929
|
+
"direction",
|
|
930
|
+
"full-width",
|
|
931
|
+
"gap",
|
|
932
|
+
"padding",
|
|
933
|
+
"padding-bottom",
|
|
934
|
+
"padding-left",
|
|
935
|
+
"padding-right",
|
|
936
|
+
"padding-top",
|
|
937
|
+
"text-align",
|
|
938
|
+
"text-padding"
|
|
939
|
+
],
|
|
940
|
+
"mj-section": [
|
|
941
|
+
"background-color",
|
|
942
|
+
"background-position",
|
|
943
|
+
"background-position-x",
|
|
944
|
+
"background-position-y",
|
|
945
|
+
"background-repeat",
|
|
946
|
+
"background-size",
|
|
947
|
+
"background-url",
|
|
948
|
+
"border",
|
|
949
|
+
"border-bottom",
|
|
950
|
+
"border-left",
|
|
951
|
+
"border-radius",
|
|
952
|
+
"border-right",
|
|
953
|
+
"border-top",
|
|
954
|
+
"direction",
|
|
955
|
+
"full-width",
|
|
956
|
+
"padding",
|
|
957
|
+
"padding-bottom",
|
|
958
|
+
"padding-left",
|
|
959
|
+
"padding-right",
|
|
960
|
+
"padding-top",
|
|
961
|
+
"text-align",
|
|
962
|
+
"text-padding"
|
|
963
|
+
],
|
|
964
|
+
"mj-column": [
|
|
965
|
+
"background-color",
|
|
966
|
+
"border",
|
|
967
|
+
"border-bottom",
|
|
968
|
+
"border-left",
|
|
969
|
+
"border-radius",
|
|
970
|
+
"border-right",
|
|
971
|
+
"border-top",
|
|
972
|
+
"direction",
|
|
973
|
+
"inner-background-color",
|
|
974
|
+
"inner-border",
|
|
975
|
+
"inner-border-bottom",
|
|
976
|
+
"inner-border-left",
|
|
977
|
+
"inner-border-radius",
|
|
978
|
+
"inner-border-right",
|
|
979
|
+
"inner-border-top",
|
|
980
|
+
"padding",
|
|
981
|
+
"padding-bottom",
|
|
982
|
+
"padding-left",
|
|
983
|
+
"padding-right",
|
|
984
|
+
"padding-top",
|
|
985
|
+
"vertical-align",
|
|
986
|
+
"width"
|
|
987
|
+
],
|
|
988
|
+
"mj-group": [
|
|
989
|
+
"background-color",
|
|
990
|
+
"direction",
|
|
991
|
+
"vertical-align",
|
|
992
|
+
"width"
|
|
993
|
+
],
|
|
994
|
+
"mj-text": [
|
|
995
|
+
"align",
|
|
996
|
+
"background-color",
|
|
997
|
+
"color",
|
|
998
|
+
"container-background-color",
|
|
999
|
+
"font-family",
|
|
1000
|
+
"font-size",
|
|
1001
|
+
"font-style",
|
|
1002
|
+
"font-weight",
|
|
1003
|
+
"height",
|
|
1004
|
+
"letter-spacing",
|
|
1005
|
+
"line-height",
|
|
1006
|
+
"padding",
|
|
1007
|
+
"padding-bottom",
|
|
1008
|
+
"padding-left",
|
|
1009
|
+
"padding-right",
|
|
1010
|
+
"padding-top",
|
|
1011
|
+
"text-decoration",
|
|
1012
|
+
"text-transform",
|
|
1013
|
+
"vertical-align"
|
|
1014
|
+
],
|
|
1015
|
+
"mj-button": [
|
|
1016
|
+
"align",
|
|
1017
|
+
"background-color",
|
|
1018
|
+
"border",
|
|
1019
|
+
"border-bottom",
|
|
1020
|
+
"border-left",
|
|
1021
|
+
"border-radius",
|
|
1022
|
+
"border-right",
|
|
1023
|
+
"border-top",
|
|
1024
|
+
"color",
|
|
1025
|
+
"container-background-color",
|
|
1026
|
+
"font-family",
|
|
1027
|
+
"font-size",
|
|
1028
|
+
"font-style",
|
|
1029
|
+
"font-weight",
|
|
1030
|
+
"height",
|
|
1031
|
+
"href",
|
|
1032
|
+
"inner-padding",
|
|
1033
|
+
"letter-spacing",
|
|
1034
|
+
"line-height",
|
|
1035
|
+
"name",
|
|
1036
|
+
"padding",
|
|
1037
|
+
"padding-bottom",
|
|
1038
|
+
"padding-left",
|
|
1039
|
+
"padding-right",
|
|
1040
|
+
"padding-top",
|
|
1041
|
+
"rel",
|
|
1042
|
+
"target",
|
|
1043
|
+
"text-align",
|
|
1044
|
+
"text-decoration",
|
|
1045
|
+
"text-transform",
|
|
1046
|
+
"title",
|
|
1047
|
+
"vertical-align",
|
|
1048
|
+
"width"
|
|
1049
|
+
],
|
|
1050
|
+
"mj-image": [
|
|
1051
|
+
"align",
|
|
1052
|
+
"alt",
|
|
1053
|
+
"border",
|
|
1054
|
+
"border-bottom",
|
|
1055
|
+
"border-left",
|
|
1056
|
+
"border-radius",
|
|
1057
|
+
"border-right",
|
|
1058
|
+
"border-top",
|
|
1059
|
+
"container-background-color",
|
|
1060
|
+
"fluid-on-mobile",
|
|
1061
|
+
"font-size",
|
|
1062
|
+
"height",
|
|
1063
|
+
"href",
|
|
1064
|
+
"max-height",
|
|
1065
|
+
"name",
|
|
1066
|
+
"padding",
|
|
1067
|
+
"padding-bottom",
|
|
1068
|
+
"padding-left",
|
|
1069
|
+
"padding-right",
|
|
1070
|
+
"padding-top",
|
|
1071
|
+
"rel",
|
|
1072
|
+
"sizes",
|
|
1073
|
+
"src",
|
|
1074
|
+
"srcset",
|
|
1075
|
+
"target",
|
|
1076
|
+
"title",
|
|
1077
|
+
"usemap",
|
|
1078
|
+
"width"
|
|
1079
|
+
],
|
|
1080
|
+
"mj-divider": [
|
|
1081
|
+
"align",
|
|
1082
|
+
"border-color",
|
|
1083
|
+
"border-style",
|
|
1084
|
+
"border-width",
|
|
1085
|
+
"container-background-color",
|
|
1086
|
+
"padding",
|
|
1087
|
+
"padding-bottom",
|
|
1088
|
+
"padding-left",
|
|
1089
|
+
"padding-right",
|
|
1090
|
+
"padding-top",
|
|
1091
|
+
"width"
|
|
1092
|
+
],
|
|
1093
|
+
"mj-spacer": [
|
|
1094
|
+
"border",
|
|
1095
|
+
"border-bottom",
|
|
1096
|
+
"border-left",
|
|
1097
|
+
"border-right",
|
|
1098
|
+
"border-top",
|
|
1099
|
+
"container-background-color",
|
|
1100
|
+
"height",
|
|
1101
|
+
"padding",
|
|
1102
|
+
"padding-bottom",
|
|
1103
|
+
"padding-left",
|
|
1104
|
+
"padding-right",
|
|
1105
|
+
"padding-top"
|
|
1106
|
+
],
|
|
1107
|
+
"mj-social": [
|
|
1108
|
+
"align",
|
|
1109
|
+
"border-radius",
|
|
1110
|
+
"color",
|
|
1111
|
+
"container-background-color",
|
|
1112
|
+
"font-family",
|
|
1113
|
+
"font-size",
|
|
1114
|
+
"font-style",
|
|
1115
|
+
"font-weight",
|
|
1116
|
+
"icon-height",
|
|
1117
|
+
"icon-padding",
|
|
1118
|
+
"icon-size",
|
|
1119
|
+
"inner-padding",
|
|
1120
|
+
"line-height",
|
|
1121
|
+
"mode",
|
|
1122
|
+
"padding",
|
|
1123
|
+
"padding-bottom",
|
|
1124
|
+
"padding-left",
|
|
1125
|
+
"padding-right",
|
|
1126
|
+
"padding-top",
|
|
1127
|
+
"table-layout",
|
|
1128
|
+
"text-decoration",
|
|
1129
|
+
"text-padding",
|
|
1130
|
+
"vertical-align"
|
|
1131
|
+
],
|
|
1132
|
+
"mj-social-element": [
|
|
1133
|
+
"align",
|
|
1134
|
+
"alt",
|
|
1135
|
+
"background-color",
|
|
1136
|
+
"border-radius",
|
|
1137
|
+
"color",
|
|
1138
|
+
"font-family",
|
|
1139
|
+
"font-size",
|
|
1140
|
+
"font-style",
|
|
1141
|
+
"font-weight",
|
|
1142
|
+
"href",
|
|
1143
|
+
"icon-height",
|
|
1144
|
+
"icon-padding",
|
|
1145
|
+
"icon-position",
|
|
1146
|
+
"icon-size",
|
|
1147
|
+
"line-height",
|
|
1148
|
+
"name",
|
|
1149
|
+
"padding",
|
|
1150
|
+
"padding-bottom",
|
|
1151
|
+
"padding-left",
|
|
1152
|
+
"padding-right",
|
|
1153
|
+
"padding-top",
|
|
1154
|
+
"rel",
|
|
1155
|
+
"sizes",
|
|
1156
|
+
"src",
|
|
1157
|
+
"srcset",
|
|
1158
|
+
"target",
|
|
1159
|
+
"text-decoration",
|
|
1160
|
+
"text-padding",
|
|
1161
|
+
"title",
|
|
1162
|
+
"vertical-align"
|
|
1163
|
+
],
|
|
1164
|
+
"mj-hero": [
|
|
1165
|
+
"background-color",
|
|
1166
|
+
"background-height",
|
|
1167
|
+
"background-position",
|
|
1168
|
+
"background-url",
|
|
1169
|
+
"background-width",
|
|
1170
|
+
"border-radius",
|
|
1171
|
+
"container-background-color",
|
|
1172
|
+
"height",
|
|
1173
|
+
"inner-background-color",
|
|
1174
|
+
"inner-padding",
|
|
1175
|
+
"inner-padding-bottom",
|
|
1176
|
+
"inner-padding-left",
|
|
1177
|
+
"inner-padding-right",
|
|
1178
|
+
"inner-padding-top",
|
|
1179
|
+
"mode",
|
|
1180
|
+
"padding",
|
|
1181
|
+
"padding-bottom",
|
|
1182
|
+
"padding-left",
|
|
1183
|
+
"padding-right",
|
|
1184
|
+
"padding-top",
|
|
1185
|
+
"vertical-align"
|
|
1186
|
+
],
|
|
1187
|
+
"mj-navbar": [
|
|
1188
|
+
"align",
|
|
1189
|
+
"base-url",
|
|
1190
|
+
"hamburger",
|
|
1191
|
+
"ico-align",
|
|
1192
|
+
"ico-close",
|
|
1193
|
+
"ico-color",
|
|
1194
|
+
"ico-font-family",
|
|
1195
|
+
"ico-font-size",
|
|
1196
|
+
"ico-line-height",
|
|
1197
|
+
"ico-open",
|
|
1198
|
+
"ico-padding",
|
|
1199
|
+
"ico-padding-bottom",
|
|
1200
|
+
"ico-padding-left",
|
|
1201
|
+
"ico-padding-right",
|
|
1202
|
+
"ico-padding-top",
|
|
1203
|
+
"ico-text-decoration",
|
|
1204
|
+
"ico-text-transform",
|
|
1205
|
+
"padding",
|
|
1206
|
+
"padding-bottom",
|
|
1207
|
+
"padding-left",
|
|
1208
|
+
"padding-right",
|
|
1209
|
+
"padding-top"
|
|
1210
|
+
],
|
|
1211
|
+
"mj-navbar-link": [
|
|
1212
|
+
"color",
|
|
1213
|
+
"font-family",
|
|
1214
|
+
"font-size",
|
|
1215
|
+
"font-style",
|
|
1216
|
+
"font-weight",
|
|
1217
|
+
"href",
|
|
1218
|
+
"letter-spacing",
|
|
1219
|
+
"line-height",
|
|
1220
|
+
"name",
|
|
1221
|
+
"padding",
|
|
1222
|
+
"padding-bottom",
|
|
1223
|
+
"padding-left",
|
|
1224
|
+
"padding-right",
|
|
1225
|
+
"padding-top",
|
|
1226
|
+
"rel",
|
|
1227
|
+
"target",
|
|
1228
|
+
"text-decoration",
|
|
1229
|
+
"text-transform"
|
|
1230
|
+
]
|
|
1231
|
+
};
|
|
1232
|
+
var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["mj-class", "css-class", "id", "style"]);
|
|
1233
|
+
var START_TAG = /<[a-z][^\s/>]*(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
|
|
1234
|
+
var ATTRIBUTE = /\s+([^\s"'>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?/g;
|
|
1235
|
+
function withoutEditorProps(mjml) {
|
|
1236
|
+
return mjml.replace(
|
|
1237
|
+
START_TAG,
|
|
1238
|
+
(tag) => tag.replace(
|
|
1239
|
+
ATTRIBUTE,
|
|
1240
|
+
(attribute, name) => name.toLowerCase().startsWith("data-gjs-") ? "" : attribute
|
|
1241
|
+
)
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
function illegalAttributes(tag, attributes) {
|
|
1245
|
+
const allowed = ALLOWED_ATTRIBUTES[tag];
|
|
1246
|
+
if (!allowed) return [];
|
|
1247
|
+
return Object.keys(attributes).filter((a) => !ALWAYS_ALLOWED.has(a) && !allowed.includes(a));
|
|
1248
|
+
}
|
|
1249
|
+
var NAMED_COLORS = /* @__PURE__ */ new Set([
|
|
1250
|
+
"aliceblue",
|
|
1251
|
+
"antiquewhite",
|
|
1252
|
+
"aqua",
|
|
1253
|
+
"aquamarine",
|
|
1254
|
+
"azure",
|
|
1255
|
+
"beige",
|
|
1256
|
+
"bisque",
|
|
1257
|
+
"black",
|
|
1258
|
+
"blanchedalmond",
|
|
1259
|
+
"blue",
|
|
1260
|
+
"blueviolet",
|
|
1261
|
+
"brown",
|
|
1262
|
+
"burlywood",
|
|
1263
|
+
"cadetblue",
|
|
1264
|
+
"chartreuse",
|
|
1265
|
+
"chocolate",
|
|
1266
|
+
"coral",
|
|
1267
|
+
"cornflowerblue",
|
|
1268
|
+
"cornsilk",
|
|
1269
|
+
"crimson",
|
|
1270
|
+
"cyan",
|
|
1271
|
+
"darkblue",
|
|
1272
|
+
"darkcyan",
|
|
1273
|
+
"darkgoldenrod",
|
|
1274
|
+
"darkgray",
|
|
1275
|
+
"darkgreen",
|
|
1276
|
+
"darkgrey",
|
|
1277
|
+
"darkkhaki",
|
|
1278
|
+
"darkmagenta",
|
|
1279
|
+
"darkolivegreen",
|
|
1280
|
+
"darkorange",
|
|
1281
|
+
"darkorchid",
|
|
1282
|
+
"darkred",
|
|
1283
|
+
"darksalmon",
|
|
1284
|
+
"darkseagreen",
|
|
1285
|
+
"darkslateblue",
|
|
1286
|
+
"darkslategray",
|
|
1287
|
+
"darkslategrey",
|
|
1288
|
+
"darkturquoise",
|
|
1289
|
+
"darkviolet",
|
|
1290
|
+
"deeppink",
|
|
1291
|
+
"deepskyblue",
|
|
1292
|
+
"dimgray",
|
|
1293
|
+
"dimgrey",
|
|
1294
|
+
"dodgerblue",
|
|
1295
|
+
"firebrick",
|
|
1296
|
+
"floralwhite",
|
|
1297
|
+
"forestgreen",
|
|
1298
|
+
"fuchsia",
|
|
1299
|
+
"gainsboro",
|
|
1300
|
+
"ghostwhite",
|
|
1301
|
+
"gold",
|
|
1302
|
+
"goldenrod",
|
|
1303
|
+
"gray",
|
|
1304
|
+
"green",
|
|
1305
|
+
"greenyellow",
|
|
1306
|
+
"grey",
|
|
1307
|
+
"honeydew",
|
|
1308
|
+
"hotpink",
|
|
1309
|
+
"indianred",
|
|
1310
|
+
"indigo",
|
|
1311
|
+
"inherit",
|
|
1312
|
+
"ivory",
|
|
1313
|
+
"khaki",
|
|
1314
|
+
"lavender",
|
|
1315
|
+
"lavenderblush",
|
|
1316
|
+
"lawngreen",
|
|
1317
|
+
"lemonchiffon",
|
|
1318
|
+
"lightblue",
|
|
1319
|
+
"lightcoral",
|
|
1320
|
+
"lightcyan",
|
|
1321
|
+
"lightgoldenrodyellow",
|
|
1322
|
+
"lightgray",
|
|
1323
|
+
"lightgreen",
|
|
1324
|
+
"lightgrey",
|
|
1325
|
+
"lightpink",
|
|
1326
|
+
"lightsalmon",
|
|
1327
|
+
"lightseagreen",
|
|
1328
|
+
"lightskyblue",
|
|
1329
|
+
"lightslategray",
|
|
1330
|
+
"lightslategrey",
|
|
1331
|
+
"lightsteelblue",
|
|
1332
|
+
"lightyellow",
|
|
1333
|
+
"lime",
|
|
1334
|
+
"limegreen",
|
|
1335
|
+
"linen",
|
|
1336
|
+
"magenta",
|
|
1337
|
+
"maroon",
|
|
1338
|
+
"mediumaquamarine",
|
|
1339
|
+
"mediumblue",
|
|
1340
|
+
"mediumorchid",
|
|
1341
|
+
"mediumpurple",
|
|
1342
|
+
"mediumseagreen",
|
|
1343
|
+
"mediumslateblue",
|
|
1344
|
+
"mediumspringgreen",
|
|
1345
|
+
"mediumturquoise",
|
|
1346
|
+
"mediumvioletred",
|
|
1347
|
+
"midnightblue",
|
|
1348
|
+
"mintcream",
|
|
1349
|
+
"mistyrose",
|
|
1350
|
+
"moccasin",
|
|
1351
|
+
"navajowhite",
|
|
1352
|
+
"navy",
|
|
1353
|
+
"oldlace",
|
|
1354
|
+
"olive",
|
|
1355
|
+
"olivedrab",
|
|
1356
|
+
"orange",
|
|
1357
|
+
"orangered",
|
|
1358
|
+
"orchid",
|
|
1359
|
+
"palegoldenrod",
|
|
1360
|
+
"palegreen",
|
|
1361
|
+
"paleturquoise",
|
|
1362
|
+
"palevioletred",
|
|
1363
|
+
"papayawhip",
|
|
1364
|
+
"peachpuff",
|
|
1365
|
+
"peru",
|
|
1366
|
+
"pink",
|
|
1367
|
+
"plum",
|
|
1368
|
+
"powderblue",
|
|
1369
|
+
"purple",
|
|
1370
|
+
"rebeccapurple",
|
|
1371
|
+
"red",
|
|
1372
|
+
"rosybrown",
|
|
1373
|
+
"royalblue",
|
|
1374
|
+
"saddlebrown",
|
|
1375
|
+
"salmon",
|
|
1376
|
+
"sandybrown",
|
|
1377
|
+
"seagreen",
|
|
1378
|
+
"seashell",
|
|
1379
|
+
"sienna",
|
|
1380
|
+
"silver",
|
|
1381
|
+
"skyblue",
|
|
1382
|
+
"slateblue",
|
|
1383
|
+
"slategray",
|
|
1384
|
+
"slategrey",
|
|
1385
|
+
"snow",
|
|
1386
|
+
"springgreen",
|
|
1387
|
+
"steelblue",
|
|
1388
|
+
"tan",
|
|
1389
|
+
"teal",
|
|
1390
|
+
"thistle",
|
|
1391
|
+
"tomato",
|
|
1392
|
+
"transparent",
|
|
1393
|
+
"turquoise",
|
|
1394
|
+
"violet",
|
|
1395
|
+
"wheat",
|
|
1396
|
+
"white",
|
|
1397
|
+
"whitesmoke",
|
|
1398
|
+
"yellow",
|
|
1399
|
+
"yellowgreen"
|
|
1400
|
+
]);
|
|
1401
|
+
|
|
1402
|
+
// src/components/editor/ColorField.tsx
|
|
1403
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
361
1404
|
var PRESETS = [
|
|
362
1405
|
"#000000",
|
|
363
1406
|
"#374151",
|
|
@@ -378,32 +1421,33 @@ var eyeDropper = () => globalThis.EyeDropper;
|
|
|
378
1421
|
function ColorField({
|
|
379
1422
|
value,
|
|
380
1423
|
placeholder,
|
|
381
|
-
documentColors,
|
|
1424
|
+
documentColors: documentColors2,
|
|
382
1425
|
labels,
|
|
383
1426
|
onChange
|
|
384
1427
|
}) {
|
|
385
|
-
const
|
|
386
|
-
const [
|
|
387
|
-
const [
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
const
|
|
1428
|
+
const field = useFieldLabel();
|
|
1429
|
+
const [text, setText] = useState3(value);
|
|
1430
|
+
const [open, setOpen] = useState3(false);
|
|
1431
|
+
const [used, setUsed] = useState3([]);
|
|
1432
|
+
const pressed = useRef3(false);
|
|
1433
|
+
const dragged = useRef3(null);
|
|
1434
|
+
const fieldRef = useRef3(null);
|
|
1435
|
+
const anchor = useRef3({
|
|
392
1436
|
getBoundingClientRect: () => {
|
|
393
|
-
const
|
|
1437
|
+
const field2 = fieldRef.current?.getBoundingClientRect() ?? new DOMRect();
|
|
394
1438
|
const panel = fieldRef.current?.closest(".editor-dark")?.getBoundingClientRect();
|
|
395
|
-
return new DOMRect(panel?.right ??
|
|
1439
|
+
return new DOMRect(panel?.right ?? field2.right, field2.top, 0, field2.height);
|
|
396
1440
|
}
|
|
397
1441
|
});
|
|
398
|
-
|
|
399
|
-
const latestOnChange =
|
|
1442
|
+
useEffect3(() => setText(value), [value]);
|
|
1443
|
+
const latestOnChange = useRef3(onChange);
|
|
400
1444
|
latestOnChange.current = onChange;
|
|
401
1445
|
const release = useCallback2(() => {
|
|
402
1446
|
pressed.current = false;
|
|
403
1447
|
if (dragged.current !== null) latestOnChange.current(dragged.current, false);
|
|
404
1448
|
dragged.current = null;
|
|
405
1449
|
}, []);
|
|
406
|
-
|
|
1450
|
+
useEffect3(() => {
|
|
407
1451
|
document.addEventListener("lostpointercapture", release);
|
|
408
1452
|
return () => {
|
|
409
1453
|
document.removeEventListener("lostpointercapture", release);
|
|
@@ -412,12 +1456,16 @@ function ColorField({
|
|
|
412
1456
|
}, [release]);
|
|
413
1457
|
const hex = toHex(value) ?? toHex(placeholder ?? "") ?? "#000000";
|
|
414
1458
|
const translucent = hex.length === 9;
|
|
415
|
-
const
|
|
1459
|
+
const shown = value || placeholder || "";
|
|
416
1460
|
const commit = (next) => {
|
|
417
1461
|
dragged.current = null;
|
|
418
|
-
|
|
1462
|
+
const color = mjmlColor(next);
|
|
1463
|
+
if (color === null) return setText(value);
|
|
1464
|
+
setText(color);
|
|
1465
|
+
onChange(color, false);
|
|
419
1466
|
};
|
|
420
|
-
const drag = (
|
|
1467
|
+
const drag = (picked) => {
|
|
1468
|
+
const next = mjmlColor(picked) ?? picked;
|
|
421
1469
|
setText(next);
|
|
422
1470
|
if (!pressed.current) return commit(next);
|
|
423
1471
|
dragged.current = next;
|
|
@@ -432,29 +1480,30 @@ function ColorField({
|
|
|
432
1480
|
} catch {
|
|
433
1481
|
}
|
|
434
1482
|
};
|
|
435
|
-
return /* @__PURE__ */
|
|
1483
|
+
return /* @__PURE__ */ jsxs3(
|
|
436
1484
|
Popover,
|
|
437
1485
|
{
|
|
438
1486
|
open,
|
|
439
1487
|
onOpenChange: (next) => {
|
|
440
|
-
if (next) setUsed(
|
|
1488
|
+
if (next) setUsed(documentColors2());
|
|
441
1489
|
else if (text.trim() !== value) commit(text.trim());
|
|
442
1490
|
setOpen(next);
|
|
443
1491
|
},
|
|
444
1492
|
children: [
|
|
445
|
-
/* @__PURE__ */
|
|
446
|
-
/* @__PURE__ */
|
|
1493
|
+
/* @__PURE__ */ jsx4(PopoverAnchor, { virtualRef: anchor }),
|
|
1494
|
+
/* @__PURE__ */ jsxs3(
|
|
447
1495
|
"div",
|
|
448
1496
|
{
|
|
449
1497
|
ref: fieldRef,
|
|
450
1498
|
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",
|
|
451
1499
|
children: [
|
|
452
|
-
/* @__PURE__ */
|
|
1500
|
+
/* @__PURE__ */ jsx4(
|
|
453
1501
|
"input",
|
|
454
1502
|
{
|
|
455
1503
|
value: text,
|
|
456
1504
|
placeholder,
|
|
457
1505
|
spellCheck: false,
|
|
1506
|
+
...field,
|
|
458
1507
|
onChange: (e) => setText(e.target.value),
|
|
459
1508
|
onBlur: () => text !== value && commit(text.trim()),
|
|
460
1509
|
onKeyDown: (e) => {
|
|
@@ -464,7 +1513,7 @@ function ColorField({
|
|
|
464
1513
|
className: "min-w-0 flex-1 bg-transparent font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg"
|
|
465
1514
|
}
|
|
466
1515
|
),
|
|
467
|
-
/* @__PURE__ */
|
|
1516
|
+
/* @__PURE__ */ jsx4(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
|
|
468
1517
|
"button",
|
|
469
1518
|
{
|
|
470
1519
|
type: "button",
|
|
@@ -472,15 +1521,15 @@ function ColorField({
|
|
|
472
1521
|
"aria-label": labels.colorPickerOpen,
|
|
473
1522
|
className: "relative size-5 shrink-0 overflow-hidden rounded border border-white/20 outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
474
1523
|
children: [
|
|
475
|
-
/* @__PURE__ */
|
|
476
|
-
|
|
1524
|
+
/* @__PURE__ */ jsx4(Checkerboard, {}),
|
|
1525
|
+
mjmlColor(shown) && /* @__PURE__ */ jsx4("span", { className: "absolute inset-0", style: { background: shown } })
|
|
477
1526
|
]
|
|
478
1527
|
}
|
|
479
1528
|
) })
|
|
480
1529
|
]
|
|
481
1530
|
}
|
|
482
1531
|
),
|
|
483
|
-
/* @__PURE__ */
|
|
1532
|
+
/* @__PURE__ */ jsxs3(
|
|
484
1533
|
PopoverContent,
|
|
485
1534
|
{
|
|
486
1535
|
side: "right",
|
|
@@ -488,36 +1537,37 @@ function ColorField({
|
|
|
488
1537
|
sideOffset: 8,
|
|
489
1538
|
className: "editor-dark flex w-52 flex-col gap-2.5 border border-panel-border bg-panel p-2.5 text-panel-fg",
|
|
490
1539
|
children: [
|
|
491
|
-
/* @__PURE__ */
|
|
1540
|
+
/* @__PURE__ */ jsx4(
|
|
492
1541
|
"div",
|
|
493
1542
|
{
|
|
494
1543
|
onPointerDownCapture: (e) => {
|
|
495
1544
|
pressed.current = true;
|
|
496
1545
|
e.target.setPointerCapture(e.pointerId);
|
|
497
1546
|
},
|
|
498
|
-
children: translucent ? /* @__PURE__ */
|
|
1547
|
+
children: translucent ? /* @__PURE__ */ jsx4(HexAlphaColorPicker, { color: hex, onChange: drag, className: "color-field-picker" }) : /* @__PURE__ */ jsx4(HexColorPicker, { color: hex, onChange: drag, className: "color-field-picker" })
|
|
499
1548
|
}
|
|
500
1549
|
),
|
|
501
|
-
/* @__PURE__ */
|
|
502
|
-
/* @__PURE__ */
|
|
1550
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
|
|
1551
|
+
/* @__PURE__ */ jsx4(
|
|
503
1552
|
"span",
|
|
504
1553
|
{
|
|
505
1554
|
className: "size-6 shrink-0 rounded border border-white/20",
|
|
506
1555
|
style: { background: hex }
|
|
507
1556
|
}
|
|
508
1557
|
),
|
|
509
|
-
/* @__PURE__ */
|
|
1558
|
+
/* @__PURE__ */ jsx4(
|
|
510
1559
|
"input",
|
|
511
1560
|
{
|
|
512
1561
|
value: text,
|
|
513
1562
|
spellCheck: false,
|
|
1563
|
+
...field,
|
|
514
1564
|
onChange: (e) => setText(e.target.value),
|
|
515
1565
|
onKeyDown: (e) => e.key === "Enter" && commit(text.trim()),
|
|
516
1566
|
onBlur: () => text !== value && commit(text.trim()),
|
|
517
1567
|
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"
|
|
518
1568
|
}
|
|
519
1569
|
),
|
|
520
|
-
eyeDropper() && /* @__PURE__ */
|
|
1570
|
+
eyeDropper() && /* @__PURE__ */ jsx4(
|
|
521
1571
|
"button",
|
|
522
1572
|
{
|
|
523
1573
|
type: "button",
|
|
@@ -525,11 +1575,11 @@ function ColorField({
|
|
|
525
1575
|
title: labels.colorPickerEyedropper,
|
|
526
1576
|
"aria-label": labels.colorPickerEyedropper,
|
|
527
1577
|
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",
|
|
528
|
-
children: /* @__PURE__ */
|
|
1578
|
+
children: /* @__PURE__ */ jsx4(Pipette, { className: "size-3" })
|
|
529
1579
|
}
|
|
530
1580
|
)
|
|
531
1581
|
] }),
|
|
532
|
-
used.length > 0 && /* @__PURE__ */
|
|
1582
|
+
used.length > 0 && /* @__PURE__ */ jsx4(
|
|
533
1583
|
Swatches,
|
|
534
1584
|
{
|
|
535
1585
|
title: labels.colorPickerDocument,
|
|
@@ -538,7 +1588,7 @@ function ColorField({
|
|
|
538
1588
|
onPick: commit
|
|
539
1589
|
}
|
|
540
1590
|
),
|
|
541
|
-
/* @__PURE__ */
|
|
1591
|
+
/* @__PURE__ */ jsx4(
|
|
542
1592
|
Swatches,
|
|
543
1593
|
{
|
|
544
1594
|
title: labels.colorPickerPresets,
|
|
@@ -562,10 +1612,10 @@ function Swatches({
|
|
|
562
1612
|
onPick,
|
|
563
1613
|
transparentLabel
|
|
564
1614
|
}) {
|
|
565
|
-
return /* @__PURE__ */
|
|
566
|
-
/* @__PURE__ */
|
|
567
|
-
/* @__PURE__ */
|
|
568
|
-
colors.map((color) => /* @__PURE__ */
|
|
1615
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
1616
|
+
/* @__PURE__ */ jsx4("p", { className: "mb-1 text-[10px] font-medium tracking-wide text-panel-muted-fg uppercase", children: title }),
|
|
1617
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-8 gap-1", children: [
|
|
1618
|
+
colors.map((color) => /* @__PURE__ */ jsx4(
|
|
569
1619
|
"button",
|
|
570
1620
|
{
|
|
571
1621
|
type: "button",
|
|
@@ -579,21 +1629,21 @@ function Swatches({
|
|
|
579
1629
|
},
|
|
580
1630
|
color
|
|
581
1631
|
)),
|
|
582
|
-
transparentLabel && /* @__PURE__ */
|
|
1632
|
+
transparentLabel && /* @__PURE__ */ jsx4(
|
|
583
1633
|
"button",
|
|
584
1634
|
{
|
|
585
1635
|
type: "button",
|
|
586
1636
|
title: transparentLabel,
|
|
587
1637
|
onClick: () => onPick("transparent"),
|
|
588
1638
|
className: "relative aspect-square overflow-hidden rounded border border-white/15 outline-none hover:scale-110 focus-visible:ring-2 focus-visible:ring-brand",
|
|
589
|
-
children: /* @__PURE__ */
|
|
1639
|
+
children: /* @__PURE__ */ jsx4(Checkerboard, {})
|
|
590
1640
|
}
|
|
591
1641
|
)
|
|
592
1642
|
] })
|
|
593
1643
|
] });
|
|
594
1644
|
}
|
|
595
1645
|
function Checkerboard() {
|
|
596
|
-
return /* @__PURE__ */
|
|
1646
|
+
return /* @__PURE__ */ jsx4(
|
|
597
1647
|
"span",
|
|
598
1648
|
{
|
|
599
1649
|
className: "absolute inset-0",
|
|
@@ -604,6 +1654,16 @@ function Checkerboard() {
|
|
|
604
1654
|
}
|
|
605
1655
|
);
|
|
606
1656
|
}
|
|
1657
|
+
function mjmlColor(value) {
|
|
1658
|
+
const v = value.trim();
|
|
1659
|
+
if (v === "") return "";
|
|
1660
|
+
if (NAMED_COLORS.has(v.toLowerCase())) return v.toLowerCase();
|
|
1661
|
+
const hex = toHex(v);
|
|
1662
|
+
if (!hex) return null;
|
|
1663
|
+
if (hex.length === 7) return hex;
|
|
1664
|
+
const [r, g, b, a] = [1, 3, 5, 7].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
1665
|
+
return `rgba(${r},${g},${b},${Number((a / 255).toFixed(2))})`;
|
|
1666
|
+
}
|
|
607
1667
|
function toHex(value, { opaque = false } = {}) {
|
|
608
1668
|
const v = value.trim().toLowerCase();
|
|
609
1669
|
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(v);
|
|
@@ -623,7 +1683,7 @@ function toHex(value, { opaque = false } = {}) {
|
|
|
623
1683
|
|
|
624
1684
|
// src/components/editor/ImageField.tsx
|
|
625
1685
|
import { ImageIcon, X } from "lucide-react";
|
|
626
|
-
import { jsx as
|
|
1686
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
627
1687
|
function ImageField({
|
|
628
1688
|
value,
|
|
629
1689
|
labels,
|
|
@@ -631,8 +1691,8 @@ function ImageField({
|
|
|
631
1691
|
onClear
|
|
632
1692
|
}) {
|
|
633
1693
|
const url = imageUrl(value);
|
|
634
|
-
return /* @__PURE__ */
|
|
635
|
-
/* @__PURE__ */
|
|
1694
|
+
return /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
|
|
1695
|
+
/* @__PURE__ */ jsxs4(
|
|
636
1696
|
"button",
|
|
637
1697
|
{
|
|
638
1698
|
type: "button",
|
|
@@ -640,15 +1700,15 @@ function ImageField({
|
|
|
640
1700
|
title: labels.chooseImage,
|
|
641
1701
|
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",
|
|
642
1702
|
children: [
|
|
643
|
-
url ? /* @__PURE__ */
|
|
644
|
-
/* @__PURE__ */
|
|
1703
|
+
url ? /* @__PURE__ */ jsx5("img", { src: url, alt: "", className: "h-24 w-full bg-black/25 object-contain" }) : /* @__PURE__ */ jsxs4("span", { className: "flex h-16 flex-col items-center justify-center gap-1 text-xs text-panel-muted-fg", children: [
|
|
1704
|
+
/* @__PURE__ */ jsx5(ImageIcon, { className: "size-5" }),
|
|
645
1705
|
labels.chooseImage
|
|
646
1706
|
] }),
|
|
647
|
-
url && /* @__PURE__ */
|
|
1707
|
+
url && /* @__PURE__ */ jsx5("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 })
|
|
648
1708
|
]
|
|
649
1709
|
}
|
|
650
1710
|
),
|
|
651
|
-
value && /* @__PURE__ */
|
|
1711
|
+
value && /* @__PURE__ */ jsx5(
|
|
652
1712
|
"button",
|
|
653
1713
|
{
|
|
654
1714
|
type: "button",
|
|
@@ -656,7 +1716,7 @@ function ImageField({
|
|
|
656
1716
|
title: labels.imageFieldRemove,
|
|
657
1717
|
"aria-label": labels.imageFieldRemove,
|
|
658
1718
|
className: "absolute top-1 right-1 flex size-5 items-center justify-center rounded bg-black/60 text-white hover:bg-black/80",
|
|
659
|
-
children: /* @__PURE__ */
|
|
1719
|
+
children: /* @__PURE__ */ jsx5(X, { className: "size-3" })
|
|
660
1720
|
}
|
|
661
1721
|
)
|
|
662
1722
|
] });
|
|
@@ -669,75 +1729,1280 @@ function imageUrl(value) {
|
|
|
669
1729
|
return /^[a-z-]+\(/i.test(v) ? null : v;
|
|
670
1730
|
}
|
|
671
1731
|
|
|
672
|
-
// src/components/editor/
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
var
|
|
676
|
-
var
|
|
677
|
-
var
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
|
|
1732
|
+
// src/components/editor/inspector/schema.ts
|
|
1733
|
+
var PX = ["px"];
|
|
1734
|
+
var PX_PCT = ["px", "%"];
|
|
1735
|
+
var LINE_HEIGHT = ["", "px", "%"];
|
|
1736
|
+
var LETTER_SPACING = ["px", "em"];
|
|
1737
|
+
var ALIGN = [
|
|
1738
|
+
{ value: "left", label: "alignLeft" },
|
|
1739
|
+
{ value: "center", label: "alignCenter" },
|
|
1740
|
+
{ value: "right", label: "alignRight" }
|
|
1741
|
+
];
|
|
1742
|
+
var VERTICAL_ALIGN = [
|
|
1743
|
+
{ value: "top", label: "alignTop" },
|
|
1744
|
+
{ value: "middle", label: "alignMiddle" },
|
|
1745
|
+
{ value: "bottom", label: "alignBottom" }
|
|
1746
|
+
];
|
|
1747
|
+
var FONTS = [
|
|
1748
|
+
{ value: "Ubuntu, Helvetica, Arial, sans-serif", label: "Ubuntu" },
|
|
1749
|
+
{ value: "Arial, Helvetica, sans-serif", label: "Arial" },
|
|
1750
|
+
{ value: "Helvetica, Arial, sans-serif", label: "Helvetica" },
|
|
1751
|
+
{ value: "Verdana, Geneva, sans-serif", label: "Verdana" },
|
|
1752
|
+
{ value: "Tahoma, Geneva, sans-serif", label: "Tahoma" },
|
|
1753
|
+
{ value: "'Trebuchet MS', Helvetica, sans-serif", label: "Trebuchet MS" },
|
|
1754
|
+
{ value: "Georgia, 'Times New Roman', serif", label: "Georgia" },
|
|
1755
|
+
{ value: "'Times New Roman', Times, serif", label: "Times New Roman" },
|
|
1756
|
+
{ value: "'Courier New', Courier, monospace", label: "Courier New" }
|
|
1757
|
+
];
|
|
1758
|
+
var WEIGHTS = [
|
|
1759
|
+
{ value: "normal", label: "weightRegular" },
|
|
1760
|
+
{ value: "500", label: "weightMedium" },
|
|
1761
|
+
{ value: "600", label: "weightSemibold" },
|
|
1762
|
+
{ value: "bold", label: "weightBold" }
|
|
1763
|
+
];
|
|
1764
|
+
var TEXT_TRANSFORM = [
|
|
1765
|
+
{ value: "none", label: "caseNone" },
|
|
1766
|
+
{ value: "uppercase", label: "caseUpper" },
|
|
1767
|
+
{ value: "lowercase", label: "caseLower" },
|
|
1768
|
+
{ value: "capitalize", label: "caseCapitalize" }
|
|
1769
|
+
];
|
|
1770
|
+
var TARGET = [
|
|
1771
|
+
{ value: "_blank", label: "targetNewTab" },
|
|
1772
|
+
{ value: "_self", label: "targetSameTab" }
|
|
1773
|
+
];
|
|
1774
|
+
var BACKGROUND_POSITION = [
|
|
1775
|
+
{ value: "top left", label: "positionTopLeft" },
|
|
1776
|
+
{ value: "top center", label: "positionTopCenter" },
|
|
1777
|
+
{ value: "top right", label: "positionTopRight" },
|
|
1778
|
+
{ value: "center left", label: "positionCenterLeft" },
|
|
1779
|
+
{ value: "center center", label: "positionCenter" },
|
|
1780
|
+
{ value: "center right", label: "positionCenterRight" },
|
|
1781
|
+
{ value: "bottom left", label: "positionBottomLeft" },
|
|
1782
|
+
{ value: "bottom center", label: "positionBottomCenter" },
|
|
1783
|
+
{ value: "bottom right", label: "positionBottomRight" }
|
|
1784
|
+
];
|
|
1785
|
+
var NETWORKS = [
|
|
1786
|
+
{ value: "facebook", label: "Facebook" },
|
|
1787
|
+
{ value: "instagram", label: "Instagram" },
|
|
1788
|
+
{ value: "x", label: "X" },
|
|
1789
|
+
{ value: "linkedin", label: "LinkedIn" },
|
|
1790
|
+
{ value: "youtube", label: "YouTube" },
|
|
1791
|
+
{ value: "pinterest", label: "Pinterest" },
|
|
1792
|
+
{ value: "github", label: "GitHub" },
|
|
1793
|
+
{ value: "web", label: "networkWebsite" },
|
|
1794
|
+
{ value: "snapchat", label: "Snapchat" },
|
|
1795
|
+
{ value: "vimeo", label: "Vimeo" },
|
|
1796
|
+
{ value: "medium", label: "Medium" },
|
|
1797
|
+
{ value: "dribbble", label: "Dribbble" },
|
|
1798
|
+
{ value: "soundcloud", label: "SoundCloud" },
|
|
1799
|
+
{ value: "tumblr", label: "Tumblr" },
|
|
1800
|
+
{ value: "xing", label: "Xing" },
|
|
1801
|
+
{ value: "custom", label: "networkCustom" }
|
|
1802
|
+
];
|
|
1803
|
+
var hasBackgroundImage = (a) => !!a["background-url"];
|
|
1804
|
+
var SECTION_FIELDS = [
|
|
1805
|
+
{
|
|
1806
|
+
attr: "full-width",
|
|
1807
|
+
label: "fullWidth",
|
|
1808
|
+
group: "layout",
|
|
1809
|
+
control: { type: "toggle", on: "full-width" }
|
|
1810
|
+
},
|
|
1811
|
+
{
|
|
1812
|
+
attr: "padding",
|
|
1813
|
+
label: "padding",
|
|
1814
|
+
group: "spacing",
|
|
1815
|
+
control: { type: "box", units: PX_PCT },
|
|
1816
|
+
default: "20px 0"
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
attr: "background-color",
|
|
1820
|
+
label: "backgroundColor",
|
|
1821
|
+
group: "style",
|
|
1822
|
+
control: { type: "color" }
|
|
1823
|
+
},
|
|
1824
|
+
{ attr: "background-url", label: "backgroundImage", group: "style", control: { type: "image" } },
|
|
1825
|
+
{
|
|
1826
|
+
attr: "background-size",
|
|
1827
|
+
label: "backgroundSize",
|
|
1828
|
+
group: "style",
|
|
1829
|
+
control: {
|
|
1830
|
+
type: "segmented",
|
|
1831
|
+
options: [
|
|
1832
|
+
{ value: "cover", label: "sizeCover" },
|
|
1833
|
+
{ value: "contain", label: "sizeContain" },
|
|
1834
|
+
{ value: "auto", label: "sizeAuto" }
|
|
1835
|
+
]
|
|
1836
|
+
},
|
|
1837
|
+
default: "auto",
|
|
1838
|
+
when: hasBackgroundImage
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
attr: "background-repeat",
|
|
1842
|
+
label: "backgroundRepeat",
|
|
1843
|
+
group: "style",
|
|
1844
|
+
control: {
|
|
1845
|
+
type: "segmented",
|
|
1846
|
+
options: [
|
|
1847
|
+
{ value: "repeat", label: "repeatTile" },
|
|
1848
|
+
{ value: "no-repeat", label: "repeatOnce" }
|
|
1849
|
+
]
|
|
1850
|
+
},
|
|
1851
|
+
default: "repeat",
|
|
1852
|
+
when: hasBackgroundImage
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
attr: "background-position",
|
|
1856
|
+
label: "backgroundPosition",
|
|
1857
|
+
group: "style",
|
|
1858
|
+
control: { type: "select", options: BACKGROUND_POSITION },
|
|
1859
|
+
default: "top center",
|
|
1860
|
+
when: hasBackgroundImage
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
attr: "border-radius",
|
|
1864
|
+
label: "cornerRadius",
|
|
1865
|
+
group: "style",
|
|
1866
|
+
control: { type: "length", units: PX_PCT }
|
|
1867
|
+
}
|
|
1868
|
+
];
|
|
1869
|
+
var SCHEMA = {
|
|
1870
|
+
"mj-body": [
|
|
1871
|
+
{ attr: "width", label: "contentWidth", group: "layout", control: { type: "length", units: PX }, default: "600px" },
|
|
1872
|
+
{ attr: "background-color", label: "pageBackground", group: "style", control: { type: "color" } }
|
|
1873
|
+
],
|
|
1874
|
+
"mj-wrapper": [
|
|
1875
|
+
...SECTION_FIELDS,
|
|
1876
|
+
{ attr: "gap", label: "gap", group: "advanced", control: { type: "length", units: PX } },
|
|
1877
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
1878
|
+
],
|
|
1879
|
+
"mj-section": [
|
|
1880
|
+
...SECTION_FIELDS,
|
|
1881
|
+
{ attr: "text-align", label: "alignColumns", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1882
|
+
{ attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } },
|
|
1883
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
1884
|
+
],
|
|
1885
|
+
"mj-column": [
|
|
1886
|
+
{ attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
|
|
1887
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
1888
|
+
{ attr: "background-color", label: "backgroundColor", group: "style", control: { type: "color" } },
|
|
1889
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
1890
|
+
{ attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
1891
|
+
{ attr: "inner-background-color", label: "innerBackground", group: "advanced", control: { type: "color" } },
|
|
1892
|
+
{ attr: "inner-border-radius", label: "innerCornerRadius", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
1893
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } },
|
|
1894
|
+
{ attr: "inner-border", label: "innerBorder", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
1895
|
+
],
|
|
1896
|
+
"mj-text": [
|
|
1897
|
+
{
|
|
1898
|
+
attr: "align",
|
|
1899
|
+
label: "align",
|
|
1900
|
+
group: "layout",
|
|
1901
|
+
control: { type: "segmented", options: [...ALIGN, { value: "justify", label: "alignJustify" }] },
|
|
1902
|
+
default: "left"
|
|
1903
|
+
},
|
|
1904
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1905
|
+
{ attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
1906
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
1907
|
+
{ attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS } },
|
|
1908
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
1909
|
+
{ attr: "line-height", label: "lineHeight", group: "style", control: { type: "length", units: LINE_HEIGHT }, default: "1" },
|
|
1910
|
+
{ attr: "container-background-color", label: "blockBackground", group: "style", control: { type: "color" } },
|
|
1911
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
1912
|
+
{ attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM } }
|
|
1913
|
+
],
|
|
1914
|
+
"mj-button": [
|
|
1915
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
1916
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1917
|
+
{ attr: "inner-padding", label: "buttonPadding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1918
|
+
{ attr: "padding", label: "spaceAround", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1919
|
+
{ attr: "background-color", label: "buttonColor", group: "style", control: { type: "color" }, default: "#414141" },
|
|
1920
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#ffffff" },
|
|
1921
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
1922
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
|
|
1923
|
+
{ attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
1924
|
+
{ attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
1925
|
+
{ attr: "font-weight", label: "fontWeight", group: "advanced", control: { type: "select", options: WEIGHTS }, default: "normal" },
|
|
1926
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
1927
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
1928
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
1929
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } },
|
|
1930
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "none" },
|
|
1931
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
1932
|
+
{ attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM }, default: "none" },
|
|
1933
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "120%" }
|
|
1934
|
+
],
|
|
1935
|
+
"mj-image": [
|
|
1936
|
+
{ attr: "src", label: "image", group: "content", control: { type: "image" } },
|
|
1937
|
+
{ attr: "alt", label: "altText", group: "content", control: { type: "text" } },
|
|
1938
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
1939
|
+
{ attr: "width", label: "width", group: "layout", control: { type: "length", units: PX } },
|
|
1940
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1941
|
+
{ attr: "fluid-on-mobile", label: "fluidOnMobile", group: "layout", control: { type: "toggle", on: "true" } },
|
|
1942
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1943
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
1944
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
1945
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
1946
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
1947
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "0" },
|
|
1948
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
1949
|
+
],
|
|
1950
|
+
"mj-divider": [
|
|
1951
|
+
{ attr: "width", label: "width", group: "layout", control: { type: "length", units: PX_PCT }, default: "100%" },
|
|
1952
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1953
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1954
|
+
{ attr: "border-color", label: "lineColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
1955
|
+
{ attr: "border-width", label: "thickness", group: "style", control: { type: "length", units: PX }, default: "4px" },
|
|
1956
|
+
{
|
|
1957
|
+
attr: "border-style",
|
|
1958
|
+
label: "lineStyle",
|
|
1959
|
+
group: "style",
|
|
1960
|
+
control: {
|
|
1961
|
+
type: "segmented",
|
|
1962
|
+
options: [
|
|
1963
|
+
{ value: "solid", label: "lineSolid" },
|
|
1964
|
+
{ value: "dashed", label: "lineDashed" },
|
|
1965
|
+
{ value: "dotted", label: "lineDotted" }
|
|
1966
|
+
]
|
|
1967
|
+
},
|
|
1968
|
+
default: "solid"
|
|
1969
|
+
},
|
|
1970
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
1971
|
+
],
|
|
1972
|
+
"mj-spacer": [
|
|
1973
|
+
{ attr: "height", label: "height", group: "layout", control: { type: "length", units: PX }, default: "20px" },
|
|
1974
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
1975
|
+
],
|
|
1976
|
+
"mj-social": [
|
|
1977
|
+
{
|
|
1978
|
+
attr: "mode",
|
|
1979
|
+
label: "socialMode",
|
|
1980
|
+
group: "layout",
|
|
1981
|
+
control: {
|
|
1982
|
+
type: "segmented",
|
|
1983
|
+
options: [
|
|
1984
|
+
{ value: "horizontal", label: "modeHorizontal" },
|
|
1985
|
+
{ value: "vertical", label: "modeVertical" }
|
|
1986
|
+
]
|
|
1987
|
+
},
|
|
1988
|
+
default: "horizontal"
|
|
1989
|
+
},
|
|
1990
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1991
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1992
|
+
{ attr: "inner-padding", label: "iconSpacing", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
1993
|
+
{ attr: "icon-size", label: "iconSize", group: "style", control: { type: "length", units: PX_PCT }, default: "20px" },
|
|
1994
|
+
// A single value: mj-social's border-radius is not a 4-side shorthand.
|
|
1995
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
|
|
1996
|
+
{ attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
1997
|
+
{ attr: "font-size", label: "fontSize", group: "advanced", control: { type: "length", units: PX }, default: "13px" },
|
|
1998
|
+
{ attr: "color", label: "textColor", group: "advanced", control: { type: "color" }, default: "#333333" },
|
|
1999
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" },
|
|
2000
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2001
|
+
],
|
|
2002
|
+
"mj-social-element": [
|
|
2003
|
+
{ attr: "name", label: "network", group: "content", control: { type: "network", networks: NETWORKS } },
|
|
2004
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2005
|
+
{
|
|
2006
|
+
attr: "src",
|
|
2007
|
+
label: "customIcon",
|
|
2008
|
+
group: "content",
|
|
2009
|
+
control: { type: "image" },
|
|
2010
|
+
when: (a) => (a.name ?? "").startsWith("custom") || !!a.src
|
|
2011
|
+
},
|
|
2012
|
+
{ attr: "alt", label: "altText", group: "content", control: { type: "text" } },
|
|
2013
|
+
{ attr: "background-color", label: "iconColor", group: "style", control: { type: "color" } },
|
|
2014
|
+
{ attr: "icon-size", label: "iconSize", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
2015
|
+
{ attr: "border-radius", label: "cornerRadius", group: "advanced", control: { type: "length", units: PX }, default: "3px" },
|
|
2016
|
+
{ attr: "padding", label: "padding", group: "advanced", control: { type: "box", units: PX_PCT }, default: "4px" },
|
|
2017
|
+
{ attr: "icon-position", label: "iconPosition", group: "advanced", control: { type: "segmented", options: [{ value: "left", label: "alignLeft" }, { value: "right", label: "alignRight" }] }, default: "left" },
|
|
2018
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
2019
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2020
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } }
|
|
2021
|
+
],
|
|
2022
|
+
"mj-hero": [
|
|
2023
|
+
{ attr: "background-url", label: "backgroundImage", group: "content", control: { type: "image" } },
|
|
2024
|
+
{
|
|
2025
|
+
attr: "mode",
|
|
2026
|
+
label: "heroMode",
|
|
2027
|
+
group: "content",
|
|
2028
|
+
control: {
|
|
2029
|
+
type: "segmented",
|
|
2030
|
+
options: [
|
|
2031
|
+
{ value: "fixed-height", label: "heroFixed" },
|
|
2032
|
+
{ value: "fluid-height", label: "heroFluid" }
|
|
2033
|
+
]
|
|
2034
|
+
},
|
|
2035
|
+
default: "fixed-height"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
attr: "height",
|
|
2039
|
+
label: "height",
|
|
2040
|
+
group: "layout",
|
|
2041
|
+
control: { type: "length", units: PX_PCT },
|
|
2042
|
+
default: "0px",
|
|
2043
|
+
when: (a) => (a.mode ?? "fixed-height") === "fixed-height"
|
|
2044
|
+
},
|
|
2045
|
+
{
|
|
2046
|
+
attr: "background-width",
|
|
2047
|
+
label: "backgroundWidth",
|
|
2048
|
+
group: "layout",
|
|
2049
|
+
control: { type: "length", units: PX_PCT },
|
|
2050
|
+
when: (a) => a.mode === "fluid-height"
|
|
2051
|
+
},
|
|
2052
|
+
{
|
|
2053
|
+
attr: "background-height",
|
|
2054
|
+
label: "backgroundHeight",
|
|
2055
|
+
group: "layout",
|
|
2056
|
+
control: { type: "length", units: PX_PCT },
|
|
2057
|
+
when: (a) => a.mode === "fluid-height"
|
|
2058
|
+
},
|
|
2059
|
+
{ attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
|
|
2060
|
+
{ attr: "background-position", label: "backgroundPosition", group: "layout", control: { type: "select", options: BACKGROUND_POSITION }, default: "center center" },
|
|
2061
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "0px" },
|
|
2062
|
+
{ attr: "background-color", label: "fallbackColor", group: "style", control: { type: "color" }, default: "#ffffff" },
|
|
2063
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
2064
|
+
{ attr: "inner-padding", label: "contentPadding", group: "advanced", control: { type: "box", units: PX_PCT } },
|
|
2065
|
+
{ attr: "inner-background-color", label: "contentBackground", group: "advanced", control: { type: "color" } }
|
|
2066
|
+
],
|
|
2067
|
+
"mj-navbar": [
|
|
2068
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2069
|
+
{ attr: "hamburger", label: "hamburger", group: "layout", control: { type: "toggle", on: "hamburger" } },
|
|
2070
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
2071
|
+
{ attr: "ico-color", label: "menuIconColor", group: "advanced", control: { type: "color" }, default: "#000000", when: (a) => !!a.hamburger },
|
|
2072
|
+
{ attr: "ico-font-size", label: "menuIconSize", group: "advanced", control: { type: "length", units: PX_PCT }, default: "30px", when: (a) => !!a.hamburger },
|
|
2073
|
+
{ attr: "ico-align", label: "menuIconAlign", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center", when: (a) => !!a.hamburger },
|
|
2074
|
+
{ attr: "base-url", label: "baseUrl", group: "advanced", control: { type: "link" } }
|
|
2075
|
+
],
|
|
2076
|
+
"mj-navbar-link": [
|
|
2077
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2078
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "15px 10px" },
|
|
2079
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
2080
|
+
{ attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
2081
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
2082
|
+
{ attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS }, default: "normal" },
|
|
2083
|
+
{ attr: "text-transform", label: "textTransform", group: "style", control: { type: "select", options: TEXT_TRANSFORM }, default: "uppercase" },
|
|
2084
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2085
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
2086
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
2087
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" }
|
|
2088
|
+
]
|
|
2089
|
+
};
|
|
2090
|
+
|
|
2091
|
+
// src/components/editor/layers.ts
|
|
2092
|
+
var svg = (paths) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
|
|
2093
|
+
var ICONS = {
|
|
2094
|
+
"mj-wrapper": svg(
|
|
2095
|
+
'<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"/>'
|
|
2096
|
+
),
|
|
2097
|
+
"mj-section": svg('<rect width="20" height="12" x="2" y="6" rx="2"/>'),
|
|
2098
|
+
"mj-hero": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/>'),
|
|
2099
|
+
"mj-group": svg(
|
|
2100
|
+
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>'
|
|
2101
|
+
),
|
|
2102
|
+
"mj-column": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M12 3v18"/>'),
|
|
2103
|
+
"mj-text": svg('<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>'),
|
|
2104
|
+
"mj-image": svg(
|
|
2105
|
+
'<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"/>'
|
|
2106
|
+
),
|
|
2107
|
+
"mj-button": svg('<rect width="20" height="10" x="2" y="7" rx="3"/><path d="M8 12h8"/>'),
|
|
2108
|
+
"mj-divider": svg('<path d="M5 12h14"/>'),
|
|
2109
|
+
"mj-spacer": svg('<path d="M12 3v18"/><path d="m8 17 4 4 4-4"/><path d="m8 7 4-4 4 4"/>'),
|
|
2110
|
+
"mj-social": svg(
|
|
2111
|
+
'<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"/>'
|
|
2112
|
+
),
|
|
2113
|
+
"mj-social-element": svg(
|
|
2114
|
+
'<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/>'
|
|
2115
|
+
),
|
|
2116
|
+
"mj-navbar": svg('<path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/>'),
|
|
2117
|
+
"mj-navbar-link": svg(
|
|
2118
|
+
'<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"/>'
|
|
2119
|
+
),
|
|
2120
|
+
"mj-raw": svg('<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>')
|
|
682
2121
|
};
|
|
683
2122
|
function tagOf(c) {
|
|
684
2123
|
return String(c.get("tagName") || c.get("type") || "");
|
|
685
2124
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
return t === "mj-section" || t in TYPE_LABEL;
|
|
694
|
-
}
|
|
695
|
-
function classMatch(c, re) {
|
|
696
|
-
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
697
|
-
return cssClass.match(re)?.[1] ?? null;
|
|
698
|
-
}
|
|
699
|
-
function sectionIdOf(c) {
|
|
700
|
-
return classMatch(c, SEC_ID_RE);
|
|
2125
|
+
function textOf(c) {
|
|
2126
|
+
const template = document.createElement("template");
|
|
2127
|
+
template.innerHTML = c.getInnerHTML();
|
|
2128
|
+
template.content.querySelectorAll("br, p, div, li, tr, h1, h2, h3, h4, h5, h6").forEach((el) => el.after("\n"));
|
|
2129
|
+
const lines = (template.content.textContent ?? "").split("\n");
|
|
2130
|
+
const first = lines.map((l) => l.replace(/\s+/g, " ").trim()).find(Boolean) ?? "";
|
|
2131
|
+
return first.slice(0, 60);
|
|
701
2132
|
}
|
|
702
|
-
function
|
|
703
|
-
|
|
704
|
-
if (existing) return existing;
|
|
705
|
-
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
706
|
-
const id = Math.random().toString(36).slice(2, 10);
|
|
707
|
-
c.addAttributes({ "css-class": `${cssClass} ${prefix}-${id}`.trim() });
|
|
708
|
-
return id;
|
|
2133
|
+
function escapeHtml(s) {
|
|
2134
|
+
return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
709
2135
|
}
|
|
710
|
-
function
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
2136
|
+
function layerName(c, labels) {
|
|
2137
|
+
const tag = tagOf(c);
|
|
2138
|
+
const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
2139
|
+
const attributes = c.getAttributes();
|
|
2140
|
+
switch (tag) {
|
|
2141
|
+
case "mj-text":
|
|
2142
|
+
case "mj-button":
|
|
2143
|
+
case "mj-navbar-link":
|
|
2144
|
+
return textOf(c) || kind;
|
|
2145
|
+
case "mj-image":
|
|
2146
|
+
return attributes.alt?.trim() || kind;
|
|
2147
|
+
case "mj-social-element": {
|
|
2148
|
+
const { network } = parseNetwork(String(attributes.name ?? ""));
|
|
2149
|
+
const option = NETWORKS.find((o) => o.value === network && network !== "custom");
|
|
2150
|
+
return textOf(c) || option && (labels.inspectorLabels[option.label] ?? option.label) || kind;
|
|
2151
|
+
}
|
|
2152
|
+
case "mj-section":
|
|
2153
|
+
case "mj-wrapper":
|
|
2154
|
+
case "mj-hero": {
|
|
2155
|
+
let words = "";
|
|
2156
|
+
c.onAll((d) => {
|
|
2157
|
+
if (!words && d !== c && ["mj-text", "mj-button"].includes(tagOf(d))) words = textOf(d);
|
|
2158
|
+
});
|
|
2159
|
+
return words || kind;
|
|
2160
|
+
}
|
|
2161
|
+
case "mj-column": {
|
|
2162
|
+
const columns = c.parent()?.components().filter((s) => tagOf(s) === "mj-column") ?? [];
|
|
2163
|
+
return columns.length > 1 ? `${kind} ${columns.indexOf(c) + 1}` : kind;
|
|
2164
|
+
}
|
|
2165
|
+
default:
|
|
2166
|
+
return kind;
|
|
715
2167
|
}
|
|
716
|
-
return null;
|
|
717
2168
|
}
|
|
718
|
-
var
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
"mj-spacer": "Spacer",
|
|
727
|
-
"mj-hero": "Hero",
|
|
728
|
-
"mj-navbar": "Navbar",
|
|
729
|
-
"mj-social": "Social",
|
|
730
|
-
"mj-social-element": "Social",
|
|
731
|
-
"mj-wrapper": "Wrapper"
|
|
732
|
-
};
|
|
733
|
-
function crumbName(c) {
|
|
734
|
-
const t = tagOf(c);
|
|
735
|
-
return CRUMB_NAME[t] ?? t.replace(/^mj-/, "") ?? "Element";
|
|
2169
|
+
var CONTENT = /* @__PURE__ */ new Set(["mj-text", "mj-button", "mj-navbar-link", "mj-social-element", "mj-raw"]);
|
|
2170
|
+
function badgeLabel(labels) {
|
|
2171
|
+
return (c) => {
|
|
2172
|
+
const tag = tagOf(c);
|
|
2173
|
+
const icon = ICONS[tag] ? `<div class="gjs-badge__icon">${ICONS[tag]}</div>` : "";
|
|
2174
|
+
const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
2175
|
+
return `${icon}<div class="gjs-badge__name">${escapeHtml(kind)}</div>`;
|
|
2176
|
+
};
|
|
736
2177
|
}
|
|
737
|
-
function
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
|
|
2178
|
+
function setupLayerNames(editor, labels) {
|
|
2179
|
+
const hideContent = (c) => c.onAll((d) => d !== c && d.set("layerable", false, { avoidStore: true }));
|
|
2180
|
+
for (const type of editor.Components.getTypes()) {
|
|
2181
|
+
if (!type.id.startsWith("mj-")) continue;
|
|
2182
|
+
editor.Components.addType(type.id, {
|
|
2183
|
+
model: {
|
|
2184
|
+
// Ours whatever the element says: GrapesJS writes it into the row's markup as it is.
|
|
2185
|
+
getIcon() {
|
|
2186
|
+
return ICONS[type.id] ?? "";
|
|
2187
|
+
},
|
|
2188
|
+
// GrapesJS writes the name into the row's markup unescaped, and it is document text.
|
|
2189
|
+
getName() {
|
|
2190
|
+
return escapeHtml(layerName(this, labels));
|
|
2191
|
+
},
|
|
2192
|
+
...CONTENT.has(type.id) && {
|
|
2193
|
+
// Editing a text replaces its children, so they are hidden again as they arrive.
|
|
2194
|
+
init() {
|
|
2195
|
+
hideContent(this);
|
|
2196
|
+
this.listenTo(this.components(), "add reset", () => hideContent(this));
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
},
|
|
2200
|
+
// Runs the plugin's own `init` as well.
|
|
2201
|
+
extendFn: ["init"]
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
let pending = false;
|
|
2205
|
+
editor.on("update component:add component:remove", () => {
|
|
2206
|
+
if (pending) return;
|
|
2207
|
+
pending = true;
|
|
2208
|
+
requestAnimationFrame(() => {
|
|
2209
|
+
pending = false;
|
|
2210
|
+
editor.Layers.getRoot()?.onAll((c) => {
|
|
2211
|
+
const view = c.viewLayer;
|
|
2212
|
+
const el = view?.getInputName?.();
|
|
2213
|
+
const name = layerName(c, labels);
|
|
2214
|
+
if (!el || el.textContent === name) return;
|
|
2215
|
+
el.textContent = name;
|
|
2216
|
+
el.parentElement?.setAttribute("title", name);
|
|
2217
|
+
});
|
|
2218
|
+
});
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// src/components/editor/inspector/StructureSection.tsx
|
|
2223
|
+
import { AtSign, ChevronRight, Link2 as Link22, Plus } from "lucide-react";
|
|
2224
|
+
|
|
2225
|
+
// src/components/editor/inspector/structure.ts
|
|
2226
|
+
function tagOf2(c) {
|
|
2227
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2228
|
+
}
|
|
2229
|
+
var COLUMN_RATIOS = {
|
|
2230
|
+
2: [
|
|
2231
|
+
[1, 1],
|
|
2232
|
+
[1, 2],
|
|
2233
|
+
[2, 1]
|
|
2234
|
+
],
|
|
2235
|
+
3: [
|
|
2236
|
+
[1, 1, 1],
|
|
2237
|
+
[1, 2, 1]
|
|
2238
|
+
]
|
|
2239
|
+
};
|
|
2240
|
+
function columnsOf(section) {
|
|
2241
|
+
const children = section.components().models;
|
|
2242
|
+
return children.length > 0 && children.every((c) => tagOf2(c) === "mj-column") ? children : null;
|
|
2243
|
+
}
|
|
2244
|
+
function ratioWidths(ratio) {
|
|
2245
|
+
if (ratio.every((r) => r === ratio[0])) return null;
|
|
2246
|
+
const total = ratio.reduce((a, b) => a + b, 0);
|
|
2247
|
+
return ratio.map((r) => `${Math.round(r / total * 1e4) / 100}%`);
|
|
2248
|
+
}
|
|
2249
|
+
function currentRatio(columns) {
|
|
2250
|
+
const widths = columns.map((c) => String(c.getAttributes().width ?? ""));
|
|
2251
|
+
return COLUMN_RATIOS[columns.length]?.find((ratio) => {
|
|
2252
|
+
const want = ratioWidths(ratio);
|
|
2253
|
+
if (!want) return widths.every((w) => w === "");
|
|
2254
|
+
return widths.every(
|
|
2255
|
+
(w, i) => /%$/.test(w) && Math.abs(parseFloat(w) - parseFloat(want[i])) < 0.5
|
|
2256
|
+
);
|
|
2257
|
+
}) ?? null;
|
|
2258
|
+
}
|
|
2259
|
+
function setRatio(columns, ratio) {
|
|
2260
|
+
const widths = ratioWidths(ratio);
|
|
2261
|
+
columns.forEach((c, i) => {
|
|
2262
|
+
if (widths) c.addAttributes({ width: widths[i] });
|
|
2263
|
+
else c.removeAttributes(["width"]);
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
var PROFILE_URLS = {
|
|
2267
|
+
facebook: "https://facebook.com/",
|
|
2268
|
+
instagram: "https://instagram.com/",
|
|
2269
|
+
x: "https://x.com/",
|
|
2270
|
+
linkedin: "https://linkedin.com/",
|
|
2271
|
+
youtube: "https://youtube.com/",
|
|
2272
|
+
pinterest: "https://pinterest.com/",
|
|
2273
|
+
github: "https://github.com/"
|
|
2274
|
+
};
|
|
2275
|
+
function addNetwork(social) {
|
|
2276
|
+
const present = new Set(
|
|
2277
|
+
social.components().map((c) => String(c.getAttributes().name ?? "").replace(/-noshare$/, ""))
|
|
2278
|
+
);
|
|
2279
|
+
const network = Object.keys(PROFILE_URLS).find((n) => !present.has(n)) ?? "facebook";
|
|
2280
|
+
return social.append(
|
|
2281
|
+
`<mj-social-element name="${network}-noshare" href="${PROFILE_URLS[network]}"></mj-social-element>`
|
|
2282
|
+
)[0];
|
|
2283
|
+
}
|
|
2284
|
+
function addNavbarLink(navbar, text) {
|
|
2285
|
+
return navbar.append(`<mj-navbar-link>${escapeHtml2(text)}</mj-navbar-link>`)[0];
|
|
2286
|
+
}
|
|
2287
|
+
function escapeHtml2(s) {
|
|
2288
|
+
return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// src/components/editor/inspector/StructureSection.tsx
|
|
2292
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2293
|
+
function tagOf3(c) {
|
|
2294
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2295
|
+
}
|
|
2296
|
+
function StructureSection({
|
|
2297
|
+
editor,
|
|
2298
|
+
component,
|
|
2299
|
+
labels,
|
|
2300
|
+
locked
|
|
2301
|
+
}) {
|
|
2302
|
+
switch (tagOf3(component)) {
|
|
2303
|
+
case "mj-section":
|
|
2304
|
+
return /* @__PURE__ */ jsx6(ColumnWidths, { component, labels, locked });
|
|
2305
|
+
case "mj-social":
|
|
2306
|
+
return /* @__PURE__ */ jsx6(
|
|
2307
|
+
Children,
|
|
2308
|
+
{
|
|
2309
|
+
editor,
|
|
2310
|
+
component,
|
|
2311
|
+
labels,
|
|
2312
|
+
locked,
|
|
2313
|
+
title: labels.socialNetworks,
|
|
2314
|
+
add: labels.addNetwork,
|
|
2315
|
+
icon: /* @__PURE__ */ jsx6(AtSign, { className: "size-3.5 shrink-0" }),
|
|
2316
|
+
onAdd: () => editor.select(addNetwork(component))
|
|
2317
|
+
}
|
|
2318
|
+
);
|
|
2319
|
+
case "mj-navbar":
|
|
2320
|
+
return /* @__PURE__ */ jsx6(
|
|
2321
|
+
Children,
|
|
2322
|
+
{
|
|
2323
|
+
editor,
|
|
2324
|
+
component,
|
|
2325
|
+
labels,
|
|
2326
|
+
locked,
|
|
2327
|
+
title: labels.navbarLinks,
|
|
2328
|
+
add: labels.addLink,
|
|
2329
|
+
icon: /* @__PURE__ */ jsx6(Link22, { className: "size-3.5 shrink-0" }),
|
|
2330
|
+
onAdd: () => editor.select(addNavbarLink(component, labels.newLinkText))
|
|
2331
|
+
}
|
|
2332
|
+
);
|
|
2333
|
+
default:
|
|
2334
|
+
return null;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
function Heading({ children }) {
|
|
2338
|
+
return /* @__PURE__ */ jsx6("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children });
|
|
2339
|
+
}
|
|
2340
|
+
function ColumnWidths({
|
|
2341
|
+
component,
|
|
2342
|
+
labels,
|
|
2343
|
+
locked
|
|
2344
|
+
}) {
|
|
2345
|
+
const columns = columnsOf(component);
|
|
2346
|
+
const ratios = columns && COLUMN_RATIOS[columns.length];
|
|
2347
|
+
if (!columns || !ratios) return null;
|
|
2348
|
+
const current = currentRatio(columns);
|
|
2349
|
+
return /* @__PURE__ */ jsxs5("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
2350
|
+
/* @__PURE__ */ jsx6(Heading, { children: labels.columnWidths }),
|
|
2351
|
+
/* @__PURE__ */ jsx6("div", { className: "flex gap-1.5 px-3 py-1", children: ratios.map((ratio) => {
|
|
2352
|
+
const name = labels.columnRatio(ratio.join(":"));
|
|
2353
|
+
const active = current?.join() === ratio.join();
|
|
2354
|
+
return /* @__PURE__ */ jsx6(
|
|
2355
|
+
"button",
|
|
2356
|
+
{
|
|
2357
|
+
type: "button",
|
|
2358
|
+
disabled: locked,
|
|
2359
|
+
"aria-pressed": active,
|
|
2360
|
+
title: name,
|
|
2361
|
+
"aria-label": name,
|
|
2362
|
+
onClick: () => !active && setRatio(columns, ratio),
|
|
2363
|
+
className: cn(
|
|
2364
|
+
"flex h-8 flex-1 items-center gap-0.5 rounded-md border px-1.5 transition-colors",
|
|
2365
|
+
active ? "border-brand bg-brand/15" : "border-panel-border bg-panel-elevated hover:border-panel-muted-fg"
|
|
2366
|
+
),
|
|
2367
|
+
children: ratio.map((part, i) => /* @__PURE__ */ jsx6(
|
|
2368
|
+
"span",
|
|
2369
|
+
{
|
|
2370
|
+
style: { flexGrow: part },
|
|
2371
|
+
className: cn(
|
|
2372
|
+
"h-4 basis-0 rounded-sm",
|
|
2373
|
+
active ? "bg-brand" : "bg-panel-muted-fg/50"
|
|
2374
|
+
)
|
|
2375
|
+
},
|
|
2376
|
+
i
|
|
2377
|
+
))
|
|
2378
|
+
},
|
|
2379
|
+
ratio.join()
|
|
2380
|
+
);
|
|
2381
|
+
}) })
|
|
2382
|
+
] });
|
|
2383
|
+
}
|
|
2384
|
+
function Children({
|
|
2385
|
+
editor,
|
|
2386
|
+
component,
|
|
2387
|
+
labels,
|
|
2388
|
+
locked,
|
|
2389
|
+
title,
|
|
2390
|
+
add,
|
|
2391
|
+
icon,
|
|
2392
|
+
onAdd
|
|
2393
|
+
}) {
|
|
2394
|
+
const children = component.components().models;
|
|
2395
|
+
return /* @__PURE__ */ jsxs5("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
2396
|
+
/* @__PURE__ */ jsx6(Heading, { children: title }),
|
|
2397
|
+
/* @__PURE__ */ jsx6("ul", { className: "flex flex-col px-2", children: children.map((child) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs5(
|
|
2398
|
+
"button",
|
|
2399
|
+
{
|
|
2400
|
+
type: "button",
|
|
2401
|
+
onClick: () => editor.select(child),
|
|
2402
|
+
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",
|
|
2403
|
+
children: [
|
|
2404
|
+
/* @__PURE__ */ jsx6("span", { className: "text-panel-muted-fg", children: icon }),
|
|
2405
|
+
/* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate", children: layerName(child, labels) }),
|
|
2406
|
+
/* @__PURE__ */ jsx6(ChevronRight, { className: "size-3 shrink-0 text-panel-muted-fg opacity-0 group-hover:opacity-100" })
|
|
2407
|
+
]
|
|
2408
|
+
}
|
|
2409
|
+
) }, child.cid)) }),
|
|
2410
|
+
/* @__PURE__ */ jsxs5(
|
|
2411
|
+
"button",
|
|
2412
|
+
{
|
|
2413
|
+
type: "button",
|
|
2414
|
+
disabled: locked,
|
|
2415
|
+
onClick: onAdd,
|
|
2416
|
+
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",
|
|
2417
|
+
children: [
|
|
2418
|
+
/* @__PURE__ */ jsx6(Plus, { className: "size-3.5" }),
|
|
2419
|
+
add
|
|
2420
|
+
]
|
|
2421
|
+
}
|
|
2422
|
+
)
|
|
2423
|
+
] });
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// src/components/editor/inspector/write.ts
|
|
2427
|
+
function same(a, b) {
|
|
2428
|
+
const keys = Object.keys(a);
|
|
2429
|
+
return keys.length === Object.keys(b).length && keys.every((k) => k in b && a[k] === b[k]);
|
|
2430
|
+
}
|
|
2431
|
+
function createWriter(editor, component, blocked = () => false) {
|
|
2432
|
+
const isImage = String(component.get("tagName") || component.get("type")) === "mj-image";
|
|
2433
|
+
const current = () => {
|
|
2434
|
+
const { __p: _marker, style: _style, ...rest } = component.get("attributes");
|
|
2435
|
+
return rest;
|
|
2436
|
+
};
|
|
2437
|
+
let drag = null;
|
|
2438
|
+
const record = (start, end) => {
|
|
2439
|
+
component.setAttributes(start, { avoidStore: true });
|
|
2440
|
+
if (!same(end, start)) component.setAttributes(end);
|
|
2441
|
+
delete component.get("attributes").__p;
|
|
2442
|
+
};
|
|
2443
|
+
return async (attr, value, { partial, remove = [] } = {}) => {
|
|
2444
|
+
if (editor.getEditing() === component) {
|
|
2445
|
+
const view = component.getView();
|
|
2446
|
+
await view?.syncContent?.();
|
|
2447
|
+
}
|
|
2448
|
+
if (blocked()) return;
|
|
2449
|
+
if (drag && drag.attr !== attr) {
|
|
2450
|
+
const { start: start2 } = drag;
|
|
2451
|
+
drag = null;
|
|
2452
|
+
record(start2, current());
|
|
2453
|
+
}
|
|
2454
|
+
if (attr === "src" && isImage) return void component.set({ src: value });
|
|
2455
|
+
const next = current();
|
|
2456
|
+
for (const name of remove) delete next[name];
|
|
2457
|
+
if (value === "") delete next[attr];
|
|
2458
|
+
else next[attr] = value;
|
|
2459
|
+
if (partial) {
|
|
2460
|
+
drag ??= { attr, start: current() };
|
|
2461
|
+
component.setAttributes(next, { avoidStore: true });
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
const start = drag?.start ?? current();
|
|
2465
|
+
drag = null;
|
|
2466
|
+
record(start, next);
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
// src/components/editor/inspector/Inspector.tsx
|
|
2471
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2472
|
+
var GROUPS = ["content", "layout", "spacing", "style"];
|
|
2473
|
+
var HIDDEN = /* @__PURE__ */ new Set(["css-class", "mj-class", "id", "style"]);
|
|
2474
|
+
function tagOf4(c) {
|
|
2475
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2476
|
+
}
|
|
2477
|
+
function attributesOf(c) {
|
|
2478
|
+
const out = {};
|
|
2479
|
+
for (const [k, v] of Object.entries(c.getAttributes())) {
|
|
2480
|
+
if (v !== void 0 && v !== null && v !== false) out[k] = String(v);
|
|
2481
|
+
}
|
|
2482
|
+
if (tagOf4(c) === "mj-image") out.src = String(c.get("src") ?? "");
|
|
2483
|
+
return out;
|
|
2484
|
+
}
|
|
2485
|
+
function documentColors(editor) {
|
|
2486
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2487
|
+
const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
|
|
2488
|
+
for (const match of editor.getHtml().matchAll(colorValue)) {
|
|
2489
|
+
const hex = toHex(match[1], { opaque: true });
|
|
2490
|
+
if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
|
|
2491
|
+
}
|
|
2492
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
|
|
2493
|
+
}
|
|
2494
|
+
function Inspector({
|
|
2495
|
+
onEmailSettings,
|
|
2496
|
+
empty
|
|
2497
|
+
}) {
|
|
2498
|
+
const editor = useEditorMaybe();
|
|
2499
|
+
const labels = useLabels();
|
|
2500
|
+
const { locked } = useAgentTurn();
|
|
2501
|
+
const lockedNow = useRef4(locked);
|
|
2502
|
+
useLayoutEffect(() => {
|
|
2503
|
+
lockedNow.current = locked;
|
|
2504
|
+
});
|
|
2505
|
+
const [, refresh] = useReducer((n) => n + 1, 0);
|
|
2506
|
+
const root = useRef4(null);
|
|
2507
|
+
const refocus = useRef4(false);
|
|
2508
|
+
useEffect4(() => {
|
|
2509
|
+
if (!editor) return;
|
|
2510
|
+
const events = "component:update change:device";
|
|
2511
|
+
const toggled = () => {
|
|
2512
|
+
if (root.current?.contains(document.activeElement)) refocus.current = true;
|
|
2513
|
+
refresh();
|
|
2514
|
+
};
|
|
2515
|
+
editor.on(events, refresh);
|
|
2516
|
+
editor.on("component:toggled", toggled);
|
|
2517
|
+
return () => {
|
|
2518
|
+
editor.off(events, refresh);
|
|
2519
|
+
editor.off("component:toggled", toggled);
|
|
2520
|
+
};
|
|
2521
|
+
}, [editor]);
|
|
2522
|
+
useEffect4(() => {
|
|
2523
|
+
if (!refocus.current) return;
|
|
2524
|
+
refocus.current = false;
|
|
2525
|
+
root.current?.querySelector("[data-focus-target]")?.focus();
|
|
2526
|
+
});
|
|
2527
|
+
if (!editor) return null;
|
|
2528
|
+
const selected = editor.getSelectedAll();
|
|
2529
|
+
const component = selected[0];
|
|
2530
|
+
return /* @__PURE__ */ jsx7("div", { ref: root, children: selected.length === 0 ? /* @__PURE__ */ jsx7("div", { "data-focus-target": true, tabIndex: -1, className: "outline-none", children: empty }) : selected.length > 1 ? /* @__PURE__ */ jsx7(
|
|
2531
|
+
"p",
|
|
2532
|
+
{
|
|
2533
|
+
"data-focus-target": true,
|
|
2534
|
+
tabIndex: -1,
|
|
2535
|
+
className: "px-4 py-8 text-center text-xs text-panel-muted-fg outline-none",
|
|
2536
|
+
children: labels.multipleSelected(selected.length)
|
|
2537
|
+
}
|
|
2538
|
+
) : /* @__PURE__ */ jsx7(
|
|
2539
|
+
ElementSettings,
|
|
2540
|
+
{
|
|
2541
|
+
editor,
|
|
2542
|
+
component,
|
|
2543
|
+
labels,
|
|
2544
|
+
locked,
|
|
2545
|
+
lockedNow,
|
|
2546
|
+
onEmailSettings
|
|
2547
|
+
},
|
|
2548
|
+
component.cid
|
|
2549
|
+
) });
|
|
2550
|
+
}
|
|
2551
|
+
function ElementSettings({
|
|
2552
|
+
editor,
|
|
2553
|
+
component,
|
|
2554
|
+
labels,
|
|
2555
|
+
locked,
|
|
2556
|
+
lockedNow,
|
|
2557
|
+
onEmailSettings
|
|
2558
|
+
}) {
|
|
2559
|
+
const [advancedOpen, setAdvancedOpen] = useState4(false);
|
|
2560
|
+
const tag = tagOf4(component);
|
|
2561
|
+
const fields = SCHEMA[tag] ?? [];
|
|
2562
|
+
const attributes = attributesOf(component);
|
|
2563
|
+
const visible = fields.filter((f) => !f.when || f.when(attributes));
|
|
2564
|
+
const t = (key) => labels.inspectorLabels[key] ?? key;
|
|
2565
|
+
const optionName = (o) => labels.inspectorLabels[o.label] ?? o.label;
|
|
2566
|
+
const [writer] = useState4(() => createWriter(editor, component, () => lockedNow.current));
|
|
2567
|
+
const write = (attr, value, options) => lockedNow.current ? Promise.resolve() : writer(attr, value, options);
|
|
2568
|
+
const writeBox = (attr, shorthand, partial) => write(attr, shorthand, { remove: SIDES.map((s) => `${attr}-${s}`), partial });
|
|
2569
|
+
const chooseImage = (attr) => {
|
|
2570
|
+
if (attr === "src" && tag === "mj-image") return void editor.runCommand("open-image-picker");
|
|
2571
|
+
const am = editor.AssetManager;
|
|
2572
|
+
am.open({
|
|
2573
|
+
types: ["image"],
|
|
2574
|
+
current: imageUrl(attributes[attr] ?? "") ?? void 0,
|
|
2575
|
+
select(asset, complete) {
|
|
2576
|
+
void write(attr, typeof asset === "string" ? asset : asset.getSrc());
|
|
2577
|
+
if (complete) am.close();
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
};
|
|
2581
|
+
const safeLink = (attr) => (link) => {
|
|
2582
|
+
if (tag === "mj-navbar-link" && attr === "href") {
|
|
2583
|
+
return isSafeNavbarLink(String(component.parent()?.getAttributes()["base-url"] ?? ""), link);
|
|
2584
|
+
}
|
|
2585
|
+
if (tag === "mj-navbar" && attr === "base-url") {
|
|
2586
|
+
return component.components().every((c) => isSafeNavbarLink(link, String(c.getAttributes().href ?? "")));
|
|
2587
|
+
}
|
|
2588
|
+
return isSafeLink(link);
|
|
2589
|
+
};
|
|
2590
|
+
const control = (f) => {
|
|
2591
|
+
const value = attributes[f.attr] ?? "";
|
|
2592
|
+
const c = f.control;
|
|
2593
|
+
switch (c.type) {
|
|
2594
|
+
case "text":
|
|
2595
|
+
return /* @__PURE__ */ jsx7(
|
|
2596
|
+
TextInput,
|
|
2597
|
+
{
|
|
2598
|
+
value,
|
|
2599
|
+
placeholder: c.placeholder ?? f.default,
|
|
2600
|
+
onCommit: (v) => write(f.attr, v)
|
|
2601
|
+
}
|
|
2602
|
+
);
|
|
2603
|
+
case "link":
|
|
2604
|
+
return /* @__PURE__ */ jsx7(
|
|
2605
|
+
TextInput,
|
|
2606
|
+
{
|
|
2607
|
+
value,
|
|
2608
|
+
placeholder: "https://",
|
|
2609
|
+
accept: safeLink(f.attr),
|
|
2610
|
+
onCommit: (v) => write(f.attr, v)
|
|
2611
|
+
}
|
|
2612
|
+
);
|
|
2613
|
+
case "color":
|
|
2614
|
+
return /* @__PURE__ */ jsx7(
|
|
2615
|
+
ColorField,
|
|
2616
|
+
{
|
|
2617
|
+
value,
|
|
2618
|
+
placeholder: f.default,
|
|
2619
|
+
documentColors: () => documentColors(editor),
|
|
2620
|
+
labels,
|
|
2621
|
+
onChange: (v, partial) => write(f.attr, v, { partial })
|
|
2622
|
+
}
|
|
2623
|
+
);
|
|
2624
|
+
case "image":
|
|
2625
|
+
return /* @__PURE__ */ jsx7(
|
|
2626
|
+
ImageField,
|
|
2627
|
+
{
|
|
2628
|
+
value,
|
|
2629
|
+
labels,
|
|
2630
|
+
onChoose: () => chooseImage(f.attr),
|
|
2631
|
+
onClear: () => write(f.attr, "")
|
|
2632
|
+
}
|
|
2633
|
+
);
|
|
2634
|
+
case "length":
|
|
2635
|
+
return /* @__PURE__ */ jsx7(
|
|
2636
|
+
LengthInput,
|
|
2637
|
+
{
|
|
2638
|
+
value,
|
|
2639
|
+
placeholder: f.default,
|
|
2640
|
+
units: c.units,
|
|
2641
|
+
negative: c.negative,
|
|
2642
|
+
labels,
|
|
2643
|
+
onCommit: (v, partial) => write(f.attr, v, { partial })
|
|
2644
|
+
}
|
|
2645
|
+
);
|
|
2646
|
+
case "box":
|
|
2647
|
+
return /* @__PURE__ */ jsx7(
|
|
2648
|
+
BoxInput,
|
|
2649
|
+
{
|
|
2650
|
+
attributes,
|
|
2651
|
+
attr: f.attr,
|
|
2652
|
+
placeholder: f.default,
|
|
2653
|
+
units: c.units,
|
|
2654
|
+
labels,
|
|
2655
|
+
onCommit: (v, partial) => writeBox(f.attr, v, partial)
|
|
2656
|
+
}
|
|
2657
|
+
);
|
|
2658
|
+
case "segmented":
|
|
2659
|
+
return /* @__PURE__ */ jsx7(
|
|
2660
|
+
Segmented,
|
|
2661
|
+
{
|
|
2662
|
+
value,
|
|
2663
|
+
fallback: f.default,
|
|
2664
|
+
options: c.options,
|
|
2665
|
+
name: optionName,
|
|
2666
|
+
onCommit: (v) => write(f.attr, v)
|
|
2667
|
+
}
|
|
2668
|
+
);
|
|
2669
|
+
case "select":
|
|
2670
|
+
return /* @__PURE__ */ jsx7(
|
|
2671
|
+
Select,
|
|
2672
|
+
{
|
|
2673
|
+
value,
|
|
2674
|
+
fallback: f.default,
|
|
2675
|
+
options: c.options,
|
|
2676
|
+
name: optionName,
|
|
2677
|
+
onCommit: (v) => write(f.attr, v)
|
|
2678
|
+
}
|
|
2679
|
+
);
|
|
2680
|
+
case "toggle":
|
|
2681
|
+
return /* @__PURE__ */ jsx7(
|
|
2682
|
+
Toggle,
|
|
2683
|
+
{
|
|
2684
|
+
checked: value === c.on,
|
|
2685
|
+
label: t(f.label),
|
|
2686
|
+
onCommit: (on) => write(f.attr, on ? c.on : "")
|
|
2687
|
+
}
|
|
2688
|
+
);
|
|
2689
|
+
case "network": {
|
|
2690
|
+
const { network, share } = parseNetwork(value);
|
|
2691
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1.5", children: [
|
|
2692
|
+
/* @__PURE__ */ jsx7(
|
|
2693
|
+
Select,
|
|
2694
|
+
{
|
|
2695
|
+
value: network,
|
|
2696
|
+
options: c.networks,
|
|
2697
|
+
name: optionName,
|
|
2698
|
+
onCommit: (n) => write(f.attr, n ? networkName(n, share) : "")
|
|
2699
|
+
}
|
|
2700
|
+
),
|
|
2701
|
+
network && network !== "custom" && /* @__PURE__ */ jsx7(
|
|
2702
|
+
Segmented,
|
|
2703
|
+
{
|
|
2704
|
+
value: share ? "share" : "profile",
|
|
2705
|
+
options: [
|
|
2706
|
+
{ value: "profile", label: labels.linkTypeProfile },
|
|
2707
|
+
{ value: "share", label: labels.linkTypeShare }
|
|
2708
|
+
],
|
|
2709
|
+
name: (o) => o.label,
|
|
2710
|
+
onCommit: (v) => write(f.attr, networkName(network, v === "share"))
|
|
2711
|
+
}
|
|
2712
|
+
)
|
|
2713
|
+
] });
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
const own = (f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)].some((a) => attributes[a] !== void 0) : !!attributes[f.attr];
|
|
2718
|
+
const reset = (f) => f.control.type === "box" ? writeBox(f.attr, "") : write(f.attr, "");
|
|
2719
|
+
const wide = (f) => ["color", "image", "link", "text", "box", "network"].includes(f.control.type);
|
|
2720
|
+
const row = (f) => /* @__PURE__ */ jsx7(
|
|
2721
|
+
Row,
|
|
2722
|
+
{
|
|
2723
|
+
label: t(f.label),
|
|
2724
|
+
help: labels.inspectorLabels[`${f.label}.help`],
|
|
2725
|
+
own: own(f),
|
|
2726
|
+
wide: wide(f),
|
|
2727
|
+
inline: f.control.type === "toggle",
|
|
2728
|
+
labels,
|
|
2729
|
+
onReset: () => void reset(f),
|
|
2730
|
+
children: control(f)
|
|
2731
|
+
},
|
|
2732
|
+
f.attr
|
|
2733
|
+
);
|
|
2734
|
+
const named = new Set(
|
|
2735
|
+
fields.flatMap(
|
|
2736
|
+
(f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)] : [f.attr]
|
|
2737
|
+
)
|
|
2738
|
+
);
|
|
2739
|
+
const others = Object.entries(attributes).filter(([k]) => !named.has(k) && !HIDDEN.has(k));
|
|
2740
|
+
const advanced = visible.filter((f) => f.group === "advanced");
|
|
2741
|
+
const advancedCount = advanced.length + others.length;
|
|
2742
|
+
return /* @__PURE__ */ jsxs6("div", { className: "pb-6", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx7(
|
|
2744
|
+
Header,
|
|
2745
|
+
{
|
|
2746
|
+
editor,
|
|
2747
|
+
component,
|
|
2748
|
+
labels,
|
|
2749
|
+
onEmailSettings
|
|
2750
|
+
}
|
|
2751
|
+
),
|
|
2752
|
+
editor.getDevice() !== "Desktop" && /* @__PURE__ */ jsxs6("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: [
|
|
2753
|
+
/* @__PURE__ */ jsx7(Monitor, { className: "size-3.5 shrink-0" }),
|
|
2754
|
+
labels.allScreenSizes
|
|
2755
|
+
] }),
|
|
2756
|
+
/* @__PURE__ */ jsx7(StructureSection, { editor, component, labels, locked }),
|
|
2757
|
+
GROUPS.map((group) => {
|
|
2758
|
+
const inGroup = visible.filter((f) => f.group === group);
|
|
2759
|
+
if (inGroup.length === 0) return null;
|
|
2760
|
+
return /* @__PURE__ */ jsxs6("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
2761
|
+
/* @__PURE__ */ jsx7("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children: groupName(group, labels) }),
|
|
2762
|
+
inGroup.map(row)
|
|
2763
|
+
] }, group);
|
|
2764
|
+
}),
|
|
2765
|
+
advancedCount > 0 && /* @__PURE__ */ jsxs6("section", { className: "py-1", children: [
|
|
2766
|
+
/* @__PURE__ */ jsxs6(
|
|
2767
|
+
"button",
|
|
2768
|
+
{
|
|
2769
|
+
type: "button",
|
|
2770
|
+
onClick: () => setAdvancedOpen((o) => !o),
|
|
2771
|
+
"aria-expanded": advancedOpen,
|
|
2772
|
+
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",
|
|
2773
|
+
children: [
|
|
2774
|
+
/* @__PURE__ */ jsx7(
|
|
2775
|
+
ChevronRight2,
|
|
2776
|
+
{
|
|
2777
|
+
className: cn("size-3 transition-transform", advancedOpen && "rotate-90")
|
|
2778
|
+
}
|
|
2779
|
+
),
|
|
2780
|
+
labels.groupAdvanced,
|
|
2781
|
+
/* @__PURE__ */ jsxs6("span", { className: "font-normal normal-case", children: [
|
|
2782
|
+
"(",
|
|
2783
|
+
advancedCount,
|
|
2784
|
+
")"
|
|
2785
|
+
] })
|
|
2786
|
+
]
|
|
2787
|
+
}
|
|
2788
|
+
),
|
|
2789
|
+
advancedOpen && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2790
|
+
advanced.map(row),
|
|
2791
|
+
others.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "px-3 pt-2", children: [
|
|
2792
|
+
/* @__PURE__ */ jsx7("p", { className: "pb-1 text-[11px] text-panel-muted-fg", children: labels.otherAttributes }),
|
|
2793
|
+
/* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-1", children: others.map(([k, v]) => /* @__PURE__ */ jsxs6(
|
|
2794
|
+
"li",
|
|
2795
|
+
{
|
|
2796
|
+
className: "flex items-center gap-2 rounded-md bg-panel-elevated px-2 py-1 font-mono text-[11px]",
|
|
2797
|
+
children: [
|
|
2798
|
+
/* @__PURE__ */ jsx7("span", { className: "text-panel-muted-fg", children: k }),
|
|
2799
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate text-panel-fg", title: v, children: v }),
|
|
2800
|
+
/* @__PURE__ */ jsx7(
|
|
2801
|
+
"button",
|
|
2802
|
+
{
|
|
2803
|
+
type: "button",
|
|
2804
|
+
onClick: () => void write(k, ""),
|
|
2805
|
+
title: labels.removeAttribute,
|
|
2806
|
+
"aria-label": `${labels.removeAttribute}: ${k}`,
|
|
2807
|
+
className: "text-panel-muted-fg hover:text-panel-fg",
|
|
2808
|
+
children: /* @__PURE__ */ jsx7(X2, { className: "size-3" })
|
|
2809
|
+
}
|
|
2810
|
+
)
|
|
2811
|
+
]
|
|
2812
|
+
},
|
|
2813
|
+
k
|
|
2814
|
+
)) })
|
|
2815
|
+
] })
|
|
2816
|
+
] })
|
|
2817
|
+
] })
|
|
2818
|
+
] });
|
|
2819
|
+
}
|
|
2820
|
+
function groupName(group, labels) {
|
|
2821
|
+
return {
|
|
2822
|
+
content: labels.groupContent,
|
|
2823
|
+
layout: labels.groupLayout,
|
|
2824
|
+
spacing: labels.groupSpacing,
|
|
2825
|
+
style: labels.groupStyle
|
|
2826
|
+
}[group];
|
|
2827
|
+
}
|
|
2828
|
+
function Header({
|
|
2829
|
+
editor,
|
|
2830
|
+
component,
|
|
2831
|
+
labels,
|
|
2832
|
+
onEmailSettings
|
|
2833
|
+
}) {
|
|
2834
|
+
const tag = tagOf4(component);
|
|
2835
|
+
const isBody = tag === "mj-body";
|
|
2836
|
+
const name = isBody ? labels.emailSettings : labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
2837
|
+
const words = tag === "mj-column" ? "" : layerName(component, labels);
|
|
2838
|
+
const excerpt = words === name ? "" : words;
|
|
2839
|
+
const parent = component.parent();
|
|
2840
|
+
const action = "flex size-7 items-center justify-center rounded-md text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
|
|
2841
|
+
return /* @__PURE__ */ jsxs6("div", { className: "sticky top-0 z-10 flex items-center gap-2 border-b border-panel-border bg-panel px-3 py-2", children: [
|
|
2842
|
+
/* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
|
|
2843
|
+
/* @__PURE__ */ jsx7(
|
|
2844
|
+
"p",
|
|
2845
|
+
{
|
|
2846
|
+
"data-focus-target": true,
|
|
2847
|
+
tabIndex: -1,
|
|
2848
|
+
className: "truncate text-sm font-medium text-panel-fg outline-none",
|
|
2849
|
+
children: name
|
|
2850
|
+
}
|
|
2851
|
+
),
|
|
2852
|
+
excerpt && !isBody && /* @__PURE__ */ jsxs6("p", { className: "truncate text-[11px] text-panel-muted-fg", children: [
|
|
2853
|
+
"\u201C",
|
|
2854
|
+
excerpt,
|
|
2855
|
+
"\u201D"
|
|
2856
|
+
] })
|
|
2857
|
+
] }),
|
|
2858
|
+
!isBody && parent && /* @__PURE__ */ jsx7(
|
|
2859
|
+
"button",
|
|
2860
|
+
{
|
|
2861
|
+
type: "button",
|
|
2862
|
+
className: action,
|
|
2863
|
+
title: labels.selectParent,
|
|
2864
|
+
"aria-label": labels.selectParent,
|
|
2865
|
+
onClick: () => tagOf4(parent) === "mj-body" ? onEmailSettings() : editor.select(parent),
|
|
2866
|
+
children: /* @__PURE__ */ jsx7(ArrowUp, { className: "size-3.5" })
|
|
2867
|
+
}
|
|
2868
|
+
),
|
|
2869
|
+
!isBody && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2870
|
+
/* @__PURE__ */ jsx7(
|
|
2871
|
+
"button",
|
|
2872
|
+
{
|
|
2873
|
+
type: "button",
|
|
2874
|
+
className: action,
|
|
2875
|
+
title: labels.duplicate,
|
|
2876
|
+
"aria-label": labels.duplicate,
|
|
2877
|
+
onClick: () => editor.runCommand("tlb-clone"),
|
|
2878
|
+
children: /* @__PURE__ */ jsx7(Copy, { className: "size-3.5" })
|
|
2879
|
+
}
|
|
2880
|
+
),
|
|
2881
|
+
/* @__PURE__ */ jsx7(
|
|
2882
|
+
"button",
|
|
2883
|
+
{
|
|
2884
|
+
type: "button",
|
|
2885
|
+
className: action,
|
|
2886
|
+
title: labels.deleteElement,
|
|
2887
|
+
"aria-label": labels.deleteElement,
|
|
2888
|
+
onClick: () => editor.runCommand("tlb-delete"),
|
|
2889
|
+
children: /* @__PURE__ */ jsx7(Trash22, { className: "size-3.5" })
|
|
2890
|
+
}
|
|
2891
|
+
)
|
|
2892
|
+
] })
|
|
2893
|
+
] });
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
// src/components/editor/ids.ts
|
|
2897
|
+
var ID_RE = { sec: /\bsec-([A-Za-z0-9_-]+)\b/, obj: /\bobj-([A-Za-z0-9_-]+)\b/ };
|
|
2898
|
+
function idOf(c, prefix) {
|
|
2899
|
+
return String(c.getAttributes()["css-class"] ?? "").match(ID_RE[prefix])?.[1] ?? null;
|
|
2900
|
+
}
|
|
2901
|
+
var newId = () => Math.random().toString(36).slice(2, 10);
|
|
2902
|
+
function sectionIdOf(c) {
|
|
2903
|
+
return idOf(c, "sec");
|
|
2904
|
+
}
|
|
2905
|
+
function addIdClass(c, prefix) {
|
|
2906
|
+
const existing = idOf(c, prefix);
|
|
2907
|
+
if (existing) return existing;
|
|
2908
|
+
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
2909
|
+
const id = newId();
|
|
2910
|
+
c.addAttributes({ "css-class": `${cssClass} ${prefix}-${id}`.trim() });
|
|
2911
|
+
return id;
|
|
2912
|
+
}
|
|
2913
|
+
function freshenCopiedIds(root, added) {
|
|
2914
|
+
const used = (prefix, id, except) => {
|
|
2915
|
+
let found = false;
|
|
2916
|
+
root?.onAll((o) => {
|
|
2917
|
+
if (o !== except && idOf(o, prefix) === id) found = true;
|
|
2918
|
+
});
|
|
2919
|
+
return found;
|
|
2920
|
+
};
|
|
2921
|
+
added.onAll((c) => {
|
|
2922
|
+
for (const prefix of ["sec", "obj"]) {
|
|
2923
|
+
const id = idOf(c, prefix);
|
|
2924
|
+
if (!id || !used(prefix, id, c)) continue;
|
|
2925
|
+
const cssClass = String(c.getAttributes()["css-class"]);
|
|
2926
|
+
c.addAttributes({ "css-class": cssClass.replace(`${prefix}-${id}`, `${prefix}-${newId()}`) });
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// src/components/editor/plugin-types.ts
|
|
2932
|
+
function fixPluginTypes(editor) {
|
|
2933
|
+
for (const type of editor.Components.getTypes()) {
|
|
2934
|
+
if (!type.id.startsWith("mj-")) continue;
|
|
2935
|
+
const base = type.model.prototype.getAttrToHTML;
|
|
2936
|
+
editor.Components.addType(type.id, {
|
|
2937
|
+
model: {
|
|
2938
|
+
defaults: { "style-default": {} },
|
|
2939
|
+
getAttrToHTML() {
|
|
2940
|
+
const attributes = base.call(this);
|
|
2941
|
+
delete attributes.__p;
|
|
2942
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
2943
|
+
if (typeof value === "string") attributes[name] = value.replace(/"/g, """);
|
|
2944
|
+
}
|
|
2945
|
+
return attributes;
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// src/components/editor/EmailEditor.tsx
|
|
2953
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2954
|
+
var TYPE_LABEL = {
|
|
2955
|
+
"mj-text": "Text",
|
|
2956
|
+
"mj-button": "Button",
|
|
2957
|
+
"mj-image": "Image",
|
|
2958
|
+
"mj-column": "Column"
|
|
2959
|
+
};
|
|
2960
|
+
function tagOf5(c) {
|
|
2961
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2962
|
+
}
|
|
2963
|
+
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>';
|
|
2964
|
+
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>';
|
|
2965
|
+
function isSection(c) {
|
|
2966
|
+
return tagOf5(c) === "mj-section";
|
|
2967
|
+
}
|
|
2968
|
+
function isCommentable(c) {
|
|
2969
|
+
const t = tagOf5(c);
|
|
2970
|
+
return t === "mj-section" || t in TYPE_LABEL;
|
|
2971
|
+
}
|
|
2972
|
+
function closestSection(c) {
|
|
2973
|
+
let cur = c;
|
|
2974
|
+
while (cur) {
|
|
2975
|
+
if (isSection(cur)) return cur;
|
|
2976
|
+
cur = cur.parent() ?? void 0;
|
|
2977
|
+
}
|
|
2978
|
+
return null;
|
|
2979
|
+
}
|
|
2980
|
+
function crumbName(c, labels) {
|
|
2981
|
+
const t = tagOf5(c);
|
|
2982
|
+
return labels.elementNames[t] ?? t.replace(/^mj-/, "");
|
|
2983
|
+
}
|
|
2984
|
+
var BLOCKS = [
|
|
2985
|
+
"mj-1-column",
|
|
2986
|
+
"mj-2-columns",
|
|
2987
|
+
"mj-3-columns",
|
|
2988
|
+
"mj-text",
|
|
2989
|
+
"mj-image",
|
|
2990
|
+
"mj-button",
|
|
2991
|
+
"mj-divider",
|
|
2992
|
+
"mj-spacer",
|
|
2993
|
+
"mj-social-group",
|
|
2994
|
+
"mj-navbar"
|
|
2995
|
+
];
|
|
2996
|
+
var LAYOUT_BLOCKS = /* @__PURE__ */ new Set(["mj-1-column", "mj-2-columns", "mj-3-columns"]);
|
|
2997
|
+
var SOCIAL_BLOCK = `<mj-social icon-size="24px" mode="horizontal">
|
|
2998
|
+
<mj-social-element name="facebook-noshare" href="https://facebook.com/"></mj-social-element>
|
|
2999
|
+
<mj-social-element name="instagram-noshare" href="https://instagram.com/"></mj-social-element>
|
|
3000
|
+
<mj-social-element name="x-noshare" href="https://x.com/"></mj-social-element>
|
|
3001
|
+
</mj-social>`;
|
|
3002
|
+
function objectLabel(c) {
|
|
3003
|
+
const base = TYPE_LABEL[tagOf5(c)] ?? tagOf5(c);
|
|
3004
|
+
const text = String(c.getEl?.()?.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 30);
|
|
3005
|
+
return text ? `${base}: \u201C${text}\u201D` : base;
|
|
741
3006
|
}
|
|
742
3007
|
var RTE_FONTS = [
|
|
743
3008
|
"Arial",
|
|
@@ -767,75 +3032,6 @@ function wrapSelectionStyle(el, style) {
|
|
|
767
3032
|
} catch {
|
|
768
3033
|
}
|
|
769
3034
|
}
|
|
770
|
-
function setupStyleFields(editor, labels) {
|
|
771
|
-
const sm = editor.StyleManager;
|
|
772
|
-
const base = sm.getType("base").view;
|
|
773
|
-
const documentColors = () => {
|
|
774
|
-
const source = `${editor.getHtml()} ${editor.getCss() ?? ""}`;
|
|
775
|
-
const counts = /* @__PURE__ */ new Map();
|
|
776
|
-
const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
|
|
777
|
-
for (const match of source.matchAll(colorValue)) {
|
|
778
|
-
const hex = toHex(match[1], { opaque: true });
|
|
779
|
-
if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
|
|
780
|
-
}
|
|
781
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
|
|
782
|
-
};
|
|
783
|
-
const ownValue = (property) => property.hasValue?.({ noParent: true }) ? String(property.getValue?.() ?? "") : "";
|
|
784
|
-
const reactView = (field) => base.extend({
|
|
785
|
-
create({ property }) {
|
|
786
|
-
const el = document.createElement("div");
|
|
787
|
-
el.addEventListener("change", (event) => event.stopPropagation());
|
|
788
|
-
const root = createRoot(el);
|
|
789
|
-
this.__root = root;
|
|
790
|
-
this.__render = () => root.render(field(property, ownValue(property)));
|
|
791
|
-
this.__render();
|
|
792
|
-
return el;
|
|
793
|
-
},
|
|
794
|
-
update() {
|
|
795
|
-
this.__render?.();
|
|
796
|
-
},
|
|
797
|
-
destroy() {
|
|
798
|
-
const root = this.__root;
|
|
799
|
-
this.__root = void 0;
|
|
800
|
-
this.__render = void 0;
|
|
801
|
-
setTimeout(() => root?.unmount());
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
sm.addType("color", {
|
|
805
|
-
view: reactView((property, value) => /* @__PURE__ */ jsx5(
|
|
806
|
-
ColorField,
|
|
807
|
-
{
|
|
808
|
-
value,
|
|
809
|
-
placeholder: String(property.getDefaultValue?.() ?? ""),
|
|
810
|
-
documentColors,
|
|
811
|
-
labels,
|
|
812
|
-
onChange: (next, partial) => property.upValue(next, { partial })
|
|
813
|
-
}
|
|
814
|
-
))
|
|
815
|
-
});
|
|
816
|
-
sm.addType("file", {
|
|
817
|
-
view: reactView((property, value) => /* @__PURE__ */ jsx5(
|
|
818
|
-
ImageField,
|
|
819
|
-
{
|
|
820
|
-
value,
|
|
821
|
-
labels,
|
|
822
|
-
onClear: () => property.upValue(""),
|
|
823
|
-
onChoose: () => {
|
|
824
|
-
const am = editor.AssetManager;
|
|
825
|
-
am.open({
|
|
826
|
-
types: ["image"],
|
|
827
|
-
current: imageUrl(value) ?? void 0,
|
|
828
|
-
select(asset, complete) {
|
|
829
|
-
const url = typeof asset === "string" ? asset : asset.getSrc();
|
|
830
|
-
property.upValue(url, { partial: !complete });
|
|
831
|
-
if (complete) am.close();
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
))
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
3035
|
function setupRichText(editor, labels) {
|
|
840
3036
|
const rte = editor.RichTextEditor;
|
|
841
3037
|
if (!rte || rte.__customActions) return;
|
|
@@ -875,23 +3071,29 @@ function setupRichText(editor, labels) {
|
|
|
875
3071
|
function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
876
3072
|
const documents = useDocumentStore();
|
|
877
3073
|
const labels = useLabels();
|
|
878
|
-
const loadingRef =
|
|
879
|
-
const saveStatusRef =
|
|
880
|
-
const stateListeners =
|
|
881
|
-
const [loading, setLoading] =
|
|
882
|
-
const [sidebarView, setSidebarView] =
|
|
883
|
-
const
|
|
884
|
-
const
|
|
885
|
-
|
|
3074
|
+
const loadingRef = useRef5(false);
|
|
3075
|
+
const saveStatusRef = useRef5("idle");
|
|
3076
|
+
const stateListeners = useRef5(/* @__PURE__ */ new Set());
|
|
3077
|
+
const [loading, setLoading] = useState5(true);
|
|
3078
|
+
const [sidebarView, setSidebarView] = useState5("blocks");
|
|
3079
|
+
const blockSearchRef = useRef5(null);
|
|
3080
|
+
const onBlockSearchKey = (e) => {
|
|
3081
|
+
if (readOnlyRef.current || !opensBlockSearch(e)) return;
|
|
3082
|
+
e.preventDefault();
|
|
3083
|
+
setSidebarView("blocks");
|
|
3084
|
+
requestAnimationFrame(() => blockSearchRef.current?.focus());
|
|
3085
|
+
};
|
|
3086
|
+
const [composeTarget, setComposeTarget] = useState5(null);
|
|
3087
|
+
const selectionSource = useRef5("other");
|
|
3088
|
+
const editorRef = useRef5(null);
|
|
886
3089
|
const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
|
|
887
|
-
const readOnlyRef =
|
|
888
|
-
const applyReadOnly =
|
|
3090
|
+
const readOnlyRef = useRef5(false);
|
|
3091
|
+
const applyReadOnly = useRef5(async () => {
|
|
889
3092
|
});
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const [crumbs, setCrumbs] =
|
|
893
|
-
const [
|
|
894
|
-
const [picker, setPicker] = useState3(
|
|
3093
|
+
useEffect5(() => void applyReadOnly.current(turnRunning), [turnRunning]);
|
|
3094
|
+
useEffect5(() => () => setLocked(false), [setLocked]);
|
|
3095
|
+
const [crumbs, setCrumbs] = useState5([]);
|
|
3096
|
+
const [picker, setPicker] = useState5(
|
|
895
3097
|
null
|
|
896
3098
|
);
|
|
897
3099
|
const onEditor = async (editor) => {
|
|
@@ -900,7 +3102,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
900
3102
|
const chain = [];
|
|
901
3103
|
let cur = c;
|
|
902
3104
|
while (cur) {
|
|
903
|
-
const t =
|
|
3105
|
+
const t = tagOf5(cur);
|
|
904
3106
|
if (!t || t === "mj-body" || t === "wrapper" || t === "Wrapper") break;
|
|
905
3107
|
chain.unshift(cur);
|
|
906
3108
|
cur = cur.parent() ?? void 0;
|
|
@@ -949,7 +3151,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
949
3151
|
if (isSection(c)) addIdClass(c, "sec");
|
|
950
3152
|
if (!isCommentable(c)) return;
|
|
951
3153
|
const toolbar = [...c.get("toolbar") ?? []];
|
|
952
|
-
if (
|
|
3154
|
+
if (tagOf5(c) === "mj-image" && !toolbar.some((t) => t.command === "open-image-picker")) {
|
|
953
3155
|
toolbar.push({
|
|
954
3156
|
command: "open-image-picker",
|
|
955
3157
|
label: IMAGE_SVG,
|
|
@@ -957,11 +3159,11 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
957
3159
|
});
|
|
958
3160
|
c.set("toolbar", toolbar);
|
|
959
3161
|
}
|
|
960
|
-
if (!toolbar.some((t) => t.command === "open-comments")) {
|
|
3162
|
+
if (closestSection(c) && !toolbar.some((t) => t.command === "open-comments")) {
|
|
961
3163
|
toolbar.push({
|
|
962
3164
|
command: "open-comments",
|
|
963
3165
|
label: SPARKLES_SVG,
|
|
964
|
-
attributes: { title:
|
|
3166
|
+
attributes: { title: labels.commentOnElement, "data-role": "sparkles" }
|
|
965
3167
|
});
|
|
966
3168
|
c.set("toolbar", toolbar);
|
|
967
3169
|
}
|
|
@@ -969,7 +3171,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
969
3171
|
editor.Commands.add("open-image-picker", {
|
|
970
3172
|
run(ed) {
|
|
971
3173
|
const target = ed.getSelected();
|
|
972
|
-
if (!target ||
|
|
3174
|
+
if (!target || tagOf5(target) !== "mj-image") return;
|
|
973
3175
|
const am = ed.AssetManager;
|
|
974
3176
|
am.open({
|
|
975
3177
|
target,
|
|
@@ -988,21 +3190,28 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
988
3190
|
}
|
|
989
3191
|
});
|
|
990
3192
|
setupRichText(editor, labels);
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
return
|
|
3193
|
+
fixPluginTypes(editor);
|
|
3194
|
+
setupLayerNames(editor, labels);
|
|
3195
|
+
const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-column"]);
|
|
3196
|
+
const showBodyInLayers = () => {
|
|
3197
|
+
const body = findByTag("mj-body");
|
|
3198
|
+
if (!body) return;
|
|
3199
|
+
editor.Layers.setRoot(body);
|
|
3200
|
+
body.onAll((c) => {
|
|
3201
|
+
if (STRUCTURE.has(tagOf5(c))) editor.Layers.setOpen(c, true);
|
|
3202
|
+
});
|
|
997
3203
|
};
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
(
|
|
1001
|
-
|
|
3204
|
+
const keepStructureOpen = () => {
|
|
3205
|
+
const layers = editor.Layers;
|
|
3206
|
+
const opened = layers.model?.get("opened") ?? {};
|
|
3207
|
+
for (const [cid, c] of Object.entries(opened)) {
|
|
3208
|
+
if (STRUCTURE.has(tagOf5(c))) delete opened[cid];
|
|
1002
3209
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
3210
|
+
};
|
|
3211
|
+
editor.on("component:add", (c) => {
|
|
3212
|
+
if (!loadingRef.current) freshenCopiedIds(editor.getWrapper(), c);
|
|
3213
|
+
decorate(c);
|
|
3214
|
+
});
|
|
1006
3215
|
editor.on("load", () => {
|
|
1007
3216
|
const body = editor.Canvas.getBody();
|
|
1008
3217
|
body?.addEventListener(
|
|
@@ -1012,34 +3221,17 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1012
3221
|
},
|
|
1013
3222
|
true
|
|
1014
3223
|
);
|
|
3224
|
+
editor.Canvas.getDocument()?.addEventListener("keydown", onBlockSearchKey);
|
|
3225
|
+
showBodyInLayers();
|
|
1015
3226
|
});
|
|
1016
3227
|
editor.on("component:selected", (c) => {
|
|
3228
|
+
keepStructureOpen();
|
|
1017
3229
|
if (selectionSource.current === "canvas") setSidebarView("settings");
|
|
1018
3230
|
selectionSource.current = "other";
|
|
1019
3231
|
buildCrumbs(c);
|
|
1020
|
-
setSelectedImageSrc(c && tagOf(c) === "mj-image" ? String(c.get("src") ?? "") : null);
|
|
1021
|
-
if (c?.get("type") === "mj-image" && !c.getTrait("src")) {
|
|
1022
|
-
c.addTrait(
|
|
1023
|
-
{
|
|
1024
|
-
type: "text",
|
|
1025
|
-
name: "src",
|
|
1026
|
-
label: "Image (URL)",
|
|
1027
|
-
placeholder: "https://\u2026",
|
|
1028
|
-
changeProp: true
|
|
1029
|
-
},
|
|
1030
|
-
{ at: 0 }
|
|
1031
|
-
);
|
|
1032
|
-
}
|
|
1033
3232
|
});
|
|
1034
3233
|
editor.on("component:deselected", () => {
|
|
1035
|
-
|
|
1036
|
-
buildCrumbs(rest);
|
|
1037
|
-
setSelectedImageSrc(
|
|
1038
|
-
rest && tagOf(rest) === "mj-image" ? String(rest.get("src") ?? "") : null
|
|
1039
|
-
);
|
|
1040
|
-
});
|
|
1041
|
-
editor.on("component:update:src", (c) => {
|
|
1042
|
-
if (c === editor.getSelected()) setSelectedImageSrc(String(c.get("src") ?? ""));
|
|
3234
|
+
buildCrumbs(editor.getSelected());
|
|
1043
3235
|
});
|
|
1044
3236
|
let stored = "";
|
|
1045
3237
|
const save = async () => {
|
|
@@ -1113,17 +3305,30 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1113
3305
|
});
|
|
1114
3306
|
const loadMjml = (mjml) => {
|
|
1115
3307
|
loadingRef.current = true;
|
|
3308
|
+
let repaired = false;
|
|
1116
3309
|
try {
|
|
1117
3310
|
queue.cancel();
|
|
1118
3311
|
stored = mjml;
|
|
1119
|
-
|
|
3312
|
+
const source = withoutEditorProps(mjml || STARTER_MJML);
|
|
3313
|
+
repaired = source !== (mjml || STARTER_MJML);
|
|
3314
|
+
editor.setComponents(source);
|
|
1120
3315
|
editor.getWrapper()?.find("mj-section").forEach(decorate);
|
|
1121
3316
|
for (const t of Object.keys(TYPE_LABEL)) {
|
|
1122
3317
|
editor.getWrapper()?.find(t).forEach(decorate);
|
|
1123
3318
|
}
|
|
3319
|
+
editor.getWrapper()?.onAll((c) => {
|
|
3320
|
+
const illegal = illegalAttributes(tagOf5(c), c.getAttributes());
|
|
3321
|
+
if (illegal.length === 0) return;
|
|
3322
|
+
c.removeAttributes(illegal);
|
|
3323
|
+
repaired = true;
|
|
3324
|
+
});
|
|
3325
|
+
showBodyInLayers();
|
|
3326
|
+
editor.UndoManager.clear();
|
|
1124
3327
|
} finally {
|
|
1125
3328
|
loadingRef.current = false;
|
|
1126
3329
|
}
|
|
3330
|
+
notifyState();
|
|
3331
|
+
if (repaired && !readOnlyRef.current) queue.schedule();
|
|
1127
3332
|
};
|
|
1128
3333
|
const ensureHighlightStyles = () => {
|
|
1129
3334
|
const cdoc = editor.Canvas.getDocument();
|
|
@@ -1210,7 +3415,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1210
3415
|
}
|
|
1211
3416
|
});
|
|
1212
3417
|
};
|
|
1213
|
-
return /* @__PURE__ */
|
|
3418
|
+
return /* @__PURE__ */ jsx8(
|
|
1214
3419
|
GjsEditor,
|
|
1215
3420
|
{
|
|
1216
3421
|
grapesjs,
|
|
@@ -1225,9 +3430,9 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1225
3430
|
fromElement: false,
|
|
1226
3431
|
panels: { defaults: [] },
|
|
1227
3432
|
blockManager: { appendTo: "#gjs-blocks" },
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
layerManager: { appendTo: "#gjs-layers" },
|
|
3433
|
+
// The eye hides a layer by writing `display="none"` into the MJML, which is illegal.
|
|
3434
|
+
// The root row is mj-body, which the sidebar reaches as "Email settings" instead.
|
|
3435
|
+
layerManager: { appendTo: "#gjs-layers", hidable: false, showWrapper: false },
|
|
1231
3436
|
assetManager: {
|
|
1232
3437
|
// Never inline files: mail clients block `data:` images (see ImageLibrary).
|
|
1233
3438
|
embedAsBase64: false,
|
|
@@ -1241,110 +3446,106 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1241
3446
|
close: () => setPicker(null)
|
|
1242
3447
|
}
|
|
1243
3448
|
},
|
|
3449
|
+
canvas: { customBadgeLabel: badgeLabel(labels) },
|
|
1244
3450
|
deviceManager: {
|
|
1245
3451
|
devices: [
|
|
1246
3452
|
{ id: "desktop", name: "Desktop", width: "" },
|
|
1247
3453
|
{ id: "mobile", name: "Mobile", width: "375px", widthMedia: "480px" }
|
|
1248
3454
|
]
|
|
1249
3455
|
},
|
|
1250
|
-
plugins: [
|
|
3456
|
+
plugins: [
|
|
3457
|
+
(editor) => grapesjsMJML(editor, {
|
|
3458
|
+
blocks: BLOCKS,
|
|
3459
|
+
block: (id) => ({
|
|
3460
|
+
label: labels.blockLabels[id] ?? id,
|
|
3461
|
+
// By id: a regex on the label broke as soon as a host translated it.
|
|
3462
|
+
category: LAYOUT_BLOCKS.has(id) ? labels.blockCategoryLayout : labels.blockCategoryContent,
|
|
3463
|
+
...id === "mj-social-group" && { content: SOCIAL_BLOCK }
|
|
3464
|
+
})
|
|
3465
|
+
})
|
|
3466
|
+
]
|
|
1251
3467
|
},
|
|
1252
3468
|
onEditor,
|
|
1253
|
-
children: /* @__PURE__ */
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
" ",
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
),
|
|
1269
|
-
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
|
|
1270
|
-
/* @__PURE__ */ jsxs4("div", { className: "relative min-h-0 flex-1", children: [
|
|
1271
|
-
/* @__PURE__ */ jsx5(Canvas, { className: "h-full" }),
|
|
1272
|
-
/* @__PURE__ */ jsx5(
|
|
1273
|
-
CanvasComments,
|
|
1274
|
-
{
|
|
1275
|
-
docId,
|
|
1276
|
-
composeTarget,
|
|
1277
|
-
onComposeConsumed: () => setComposeTarget(null),
|
|
1278
|
-
refreshSignal: commentsRefresh,
|
|
1279
|
-
onOpenCountChange
|
|
1280
|
-
}
|
|
1281
|
-
),
|
|
1282
|
-
/* @__PURE__ */ jsx5(
|
|
1283
|
-
ImagePicker,
|
|
3469
|
+
children: /* @__PURE__ */ jsxs7(
|
|
3470
|
+
"div",
|
|
3471
|
+
{
|
|
3472
|
+
className: "relative flex h-full min-h-0 flex-1",
|
|
3473
|
+
inert: readOnly,
|
|
3474
|
+
onKeyDown: (e) => onBlockSearchKey(e.nativeEvent),
|
|
3475
|
+
children: [
|
|
3476
|
+
readOnly && /* @__PURE__ */ jsx8("div", { className: "absolute inset-0 z-30 flex cursor-not-allowed items-start justify-center bg-white/20 pt-3", children: /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-2 rounded-full bg-panel px-3 py-1 text-xs text-panel-fg shadow", children: [
|
|
3477
|
+
/* @__PURE__ */ jsx8(Spinner, {}),
|
|
3478
|
+
" ",
|
|
3479
|
+
labels.agentWorking
|
|
3480
|
+
] }) }),
|
|
3481
|
+
/* @__PURE__ */ jsx8(
|
|
3482
|
+
LeftSidebar,
|
|
1284
3483
|
{
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
setPicker(null);
|
|
1290
|
-
},
|
|
1291
|
-
onClose: () => {
|
|
1292
|
-
setPicker(null);
|
|
1293
|
-
editorRef.current?.AssetManager.close();
|
|
1294
|
-
}
|
|
3484
|
+
view: sidebarView,
|
|
3485
|
+
onViewChange: setSidebarView,
|
|
3486
|
+
markLayersSource: () => selectionSource.current = "layers",
|
|
3487
|
+
blockSearchRef
|
|
1295
3488
|
}
|
|
1296
3489
|
),
|
|
1297
|
-
|
|
1298
|
-
/* @__PURE__ */
|
|
1299
|
-
|
|
3490
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
|
|
3491
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative min-h-0 flex-1", children: [
|
|
3492
|
+
/* @__PURE__ */ jsx8(Canvas, { className: "h-full" }),
|
|
3493
|
+
/* @__PURE__ */ jsx8(
|
|
3494
|
+
CanvasComments,
|
|
3495
|
+
{
|
|
3496
|
+
docId,
|
|
3497
|
+
composeTarget,
|
|
3498
|
+
onComposeConsumed: () => setComposeTarget(null),
|
|
3499
|
+
refreshSignal: commentsRefresh,
|
|
3500
|
+
onOpenCountChange
|
|
3501
|
+
}
|
|
3502
|
+
),
|
|
3503
|
+
/* @__PURE__ */ jsx8(
|
|
3504
|
+
ImagePicker,
|
|
3505
|
+
{
|
|
3506
|
+
open: picker !== null,
|
|
3507
|
+
currentUrl: picker?.current,
|
|
3508
|
+
onSelect: (url) => {
|
|
3509
|
+
if (!readOnlyRef.current) picker?.select(url);
|
|
3510
|
+
setPicker(null);
|
|
3511
|
+
},
|
|
3512
|
+
onClose: () => {
|
|
3513
|
+
setPicker(null);
|
|
3514
|
+
editorRef.current?.AssetManager.close();
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
),
|
|
3518
|
+
loading && /* @__PURE__ */ jsxs7("div", { className: "absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
|
|
3519
|
+
/* @__PURE__ */ jsx8(Spinner, {}),
|
|
3520
|
+
" ",
|
|
3521
|
+
labels.loadingEditor
|
|
3522
|
+
] })
|
|
3523
|
+
] }),
|
|
3524
|
+
/* @__PURE__ */ jsx8(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
|
|
1300
3525
|
] })
|
|
1301
|
-
]
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
] })
|
|
3526
|
+
]
|
|
3527
|
+
}
|
|
3528
|
+
)
|
|
1305
3529
|
}
|
|
1306
3530
|
);
|
|
1307
3531
|
}
|
|
1308
|
-
function CollapseSection({
|
|
1309
|
-
title,
|
|
1310
|
-
defaultOpen = true,
|
|
1311
|
-
children
|
|
1312
|
-
}) {
|
|
1313
|
-
const [open, setOpen] = useState3(defaultOpen);
|
|
1314
|
-
return /* @__PURE__ */ jsxs4("div", { className: "border-b border-panel-border", children: [
|
|
1315
|
-
/* @__PURE__ */ jsxs4(
|
|
1316
|
-
"button",
|
|
1317
|
-
{
|
|
1318
|
-
type: "button",
|
|
1319
|
-
onClick: () => setOpen((o) => !o),
|
|
1320
|
-
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",
|
|
1321
|
-
children: [
|
|
1322
|
-
/* @__PURE__ */ jsx5(ChevronRight, { className: cn("size-3.5 transition-transform", open && "rotate-90") }),
|
|
1323
|
-
title
|
|
1324
|
-
]
|
|
1325
|
-
}
|
|
1326
|
-
),
|
|
1327
|
-
/* @__PURE__ */ jsx5("div", { className: cn(!open && "hidden"), children })
|
|
1328
|
-
] });
|
|
1329
|
-
}
|
|
1330
3532
|
function LeftSidebar({
|
|
1331
3533
|
view,
|
|
1332
3534
|
onViewChange,
|
|
1333
3535
|
markLayersSource,
|
|
1334
|
-
|
|
1335
|
-
onChooseImage
|
|
3536
|
+
blockSearchRef
|
|
1336
3537
|
}) {
|
|
1337
|
-
const editor =
|
|
3538
|
+
const editor = useEditorMaybe2();
|
|
1338
3539
|
const labels = useLabels();
|
|
1339
|
-
const [layersExpanded, setLayersExpanded] =
|
|
1340
|
-
const [blockSearch, setBlockSearch] =
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
if (body)
|
|
1346
|
-
}
|
|
1347
|
-
|
|
3540
|
+
const [layersExpanded, setLayersExpanded] = useState5(false);
|
|
3541
|
+
const [blockSearch, setBlockSearch] = useState5("");
|
|
3542
|
+
const [noBlockMatches, setNoBlockMatches] = useState5(false);
|
|
3543
|
+
const selectBody = () => {
|
|
3544
|
+
let body;
|
|
3545
|
+
editor?.getWrapper()?.onAll((c) => {
|
|
3546
|
+
if (!body && tagOf5(c) === "mj-body") body = c;
|
|
3547
|
+
});
|
|
3548
|
+
if (body) editor?.select(body);
|
|
1348
3549
|
};
|
|
1349
3550
|
const toggleLayers = (expand) => {
|
|
1350
3551
|
setLayersExpanded(expand);
|
|
@@ -1365,17 +3566,22 @@ function LeftSidebar({
|
|
|
1365
3566
|
);
|
|
1366
3567
|
cat.style.display = anyVisible ? "" : "none";
|
|
1367
3568
|
});
|
|
3569
|
+
setNoBlockMatches(
|
|
3570
|
+
[...root.querySelectorAll(".gjs-block")].every(
|
|
3571
|
+
(b) => b.style.display === "none"
|
|
3572
|
+
)
|
|
3573
|
+
);
|
|
1368
3574
|
};
|
|
1369
|
-
return /* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1371
|
-
["blocks",
|
|
1372
|
-
["settings",
|
|
1373
|
-
["layers",
|
|
1374
|
-
].map(([value, label]) => /* @__PURE__ */
|
|
3575
|
+
return /* @__PURE__ */ jsxs7("div", { className: "editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg", children: [
|
|
3576
|
+
/* @__PURE__ */ jsx8("div", { className: "flex border-b border-panel-border", children: [
|
|
3577
|
+
["blocks", labels.tabBlocks],
|
|
3578
|
+
["settings", labels.tabSettings],
|
|
3579
|
+
["layers", labels.tabLayers]
|
|
3580
|
+
].map(([value, label]) => /* @__PURE__ */ jsx8(
|
|
1375
3581
|
"button",
|
|
1376
3582
|
{
|
|
1377
3583
|
type: "button",
|
|
1378
|
-
onClick: () =>
|
|
3584
|
+
onClick: () => onViewChange(value),
|
|
1379
3585
|
className: cn(
|
|
1380
3586
|
"relative flex-1 px-3 py-2.5 text-sm transition-colors",
|
|
1381
3587
|
"after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:transition-colors",
|
|
@@ -1385,70 +3591,57 @@ function LeftSidebar({
|
|
|
1385
3591
|
},
|
|
1386
3592
|
value
|
|
1387
3593
|
)) }),
|
|
1388
|
-
/* @__PURE__ */
|
|
1389
|
-
/* @__PURE__ */
|
|
1390
|
-
/* @__PURE__ */
|
|
1391
|
-
/* @__PURE__ */
|
|
3594
|
+
/* @__PURE__ */ jsxs7("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "blocks" && "hidden"), children: [
|
|
3595
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative border-b border-panel-border p-2", children: [
|
|
3596
|
+
/* @__PURE__ */ jsx8(Search, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg" }),
|
|
3597
|
+
/* @__PURE__ */ jsx8(
|
|
1392
3598
|
Input,
|
|
1393
3599
|
{
|
|
3600
|
+
ref: blockSearchRef,
|
|
1394
3601
|
value: blockSearch,
|
|
1395
3602
|
onChange: (e) => filterBlocks(e.target.value),
|
|
1396
3603
|
placeholder: labels.searchBlocks,
|
|
1397
3604
|
className: "h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg"
|
|
1398
3605
|
}
|
|
1399
3606
|
),
|
|
1400
|
-
/* @__PURE__ */
|
|
3607
|
+
/* @__PURE__ */ jsx8("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: "/" })
|
|
1401
3608
|
] }),
|
|
1402
|
-
/* @__PURE__ */
|
|
3609
|
+
noBlockMatches && /* @__PURE__ */ jsx8("p", { className: "px-3 py-6 text-center text-xs text-panel-muted-fg", children: labels.noBlocksMatch }),
|
|
3610
|
+
/* @__PURE__ */ jsx8("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
|
|
1403
3611
|
] }),
|
|
1404
|
-
/* @__PURE__ */
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
)
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
type: "button",
|
|
1430
|
-
size: "sm",
|
|
1431
|
-
className: "mt-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
|
|
1432
|
-
onClick: onChooseImage,
|
|
1433
|
-
children: [
|
|
1434
|
-
/* @__PURE__ */ jsx5(ImageUp2, {}),
|
|
1435
|
-
labels.chooseImage
|
|
1436
|
-
]
|
|
1437
|
-
}
|
|
1438
|
-
)
|
|
1439
|
-
] }),
|
|
1440
|
-
/* @__PURE__ */ jsx5(CollapseSection, { title: labels.attributes, children: /* @__PURE__ */ jsx5("div", { id: "gjs-traits" }) }),
|
|
1441
|
-
/* @__PURE__ */ jsx5(CollapseSection, { title: labels.style, children: /* @__PURE__ */ jsx5("div", { id: "gjs-styles" }) })
|
|
1442
|
-
] }),
|
|
1443
|
-
/* @__PURE__ */ jsxs4(
|
|
3612
|
+
/* @__PURE__ */ jsx8("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: /* @__PURE__ */ jsx8(
|
|
3613
|
+
Inspector,
|
|
3614
|
+
{
|
|
3615
|
+
onEmailSettings: selectBody,
|
|
3616
|
+
empty: /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center gap-3 px-6 py-10 text-center", children: [
|
|
3617
|
+
/* @__PURE__ */ jsx8(MousePointerClick, { className: "size-6 text-panel-muted-fg" }),
|
|
3618
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm text-panel-muted-fg", children: labels.settingsEmpty }),
|
|
3619
|
+
/* @__PURE__ */ jsxs7(
|
|
3620
|
+
Button,
|
|
3621
|
+
{
|
|
3622
|
+
type: "button",
|
|
3623
|
+
size: "sm",
|
|
3624
|
+
variant: "outline",
|
|
3625
|
+
className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
|
|
3626
|
+
onClick: selectBody,
|
|
3627
|
+
children: [
|
|
3628
|
+
/* @__PURE__ */ jsx8(Mail, {}),
|
|
3629
|
+
labels.emailSettings
|
|
3630
|
+
]
|
|
3631
|
+
}
|
|
3632
|
+
)
|
|
3633
|
+
] })
|
|
3634
|
+
}
|
|
3635
|
+
) }),
|
|
3636
|
+
/* @__PURE__ */ jsxs7(
|
|
1444
3637
|
"div",
|
|
1445
3638
|
{
|
|
1446
3639
|
className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
|
|
1447
3640
|
onMouseDownCapture: markLayersSource,
|
|
1448
3641
|
children: [
|
|
1449
|
-
/* @__PURE__ */
|
|
1450
|
-
/* @__PURE__ */
|
|
1451
|
-
/* @__PURE__ */
|
|
3642
|
+
/* @__PURE__ */ jsxs7("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: [
|
|
3643
|
+
/* @__PURE__ */ jsx8("span", { children: labels.expandAll }),
|
|
3644
|
+
/* @__PURE__ */ jsx8(
|
|
1452
3645
|
Switch,
|
|
1453
3646
|
{
|
|
1454
3647
|
checked: layersExpanded,
|
|
@@ -1457,7 +3650,16 @@ function LeftSidebar({
|
|
|
1457
3650
|
}
|
|
1458
3651
|
)
|
|
1459
3652
|
] }),
|
|
1460
|
-
/* @__PURE__ */
|
|
3653
|
+
/* @__PURE__ */ jsx8(
|
|
3654
|
+
"div",
|
|
3655
|
+
{
|
|
3656
|
+
id: "gjs-layers",
|
|
3657
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
3658
|
+
onDoubleClickCapture: (e) => {
|
|
3659
|
+
if (e.target.closest("[data-name]")) e.stopPropagation();
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
)
|
|
1461
3663
|
]
|
|
1462
3664
|
}
|
|
1463
3665
|
)
|
|
@@ -1468,9 +3670,9 @@ function Breadcrumb({
|
|
|
1468
3670
|
onSelect
|
|
1469
3671
|
}) {
|
|
1470
3672
|
const labels = useLabels();
|
|
1471
|
-
return /* @__PURE__ */
|
|
1472
|
-
i > 0 && /* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
3673
|
+
return /* @__PURE__ */ jsx8("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__ */ jsx8("span", { className: "text-muted-foreground/60", children: labels.selectAnElement }) : crumbs.map((c, i) => /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-0.5", children: [
|
|
3674
|
+
i > 0 && /* @__PURE__ */ jsx8(ChevronRight3, { className: "size-3 opacity-40" }),
|
|
3675
|
+
/* @__PURE__ */ jsx8(
|
|
1474
3676
|
"button",
|
|
1475
3677
|
{
|
|
1476
3678
|
type: "button",
|
|
@@ -1479,7 +3681,7 @@ function Breadcrumb({
|
|
|
1479
3681
|
"rounded px-1.5 py-0.5 hover:bg-muted hover:text-foreground",
|
|
1480
3682
|
i === crumbs.length - 1 && "font-medium text-brand"
|
|
1481
3683
|
),
|
|
1482
|
-
children: crumbName(c)
|
|
3684
|
+
children: crumbName(c, labels)
|
|
1483
3685
|
}
|
|
1484
3686
|
)
|
|
1485
3687
|
] }, i)) });
|