@mjml-agent-editor/editor 0.1.17 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -5
- package/dist/canvas.cjs +3409 -624
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.d.cts +30 -2
- package/dist/canvas.d.ts +30 -2
- package/dist/canvas.js +2946 -386
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-VJRKDFM4.js → chunk-K7KQC7LX.js} +201 -2
- package/dist/chunk-K7KQC7LX.js.map +1 -0
- package/dist/index.cjs +200 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -4
- package/dist/index.d.ts +69 -4
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/src/components/editor/editor-theme.css +55 -80
- package/dist/chunk-VJRKDFM4.js.map +0 -1
package/dist/canvas.js
CHANGED
|
@@ -16,20 +16,48 @@ import {
|
|
|
16
16
|
useDocumentStore,
|
|
17
17
|
useImageLibrary,
|
|
18
18
|
useLabels
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-K7KQC7LX.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
|
-
import
|
|
29
|
-
import GjsEditor, { Canvas, useEditorMaybe } from "@grapesjs/react";
|
|
30
|
-
import
|
|
31
|
-
import { useEffect as
|
|
32
|
-
import { STARTER_MJML } from "@mjml-agent-editor/core";
|
|
32
|
+
import grapesjs2 from "grapesjs";
|
|
33
|
+
import GjsEditor, { Canvas, useEditorMaybe as useEditorMaybe2 } from "@grapesjs/react";
|
|
34
|
+
import grapesjsMJML2 from "grapesjs-mjml";
|
|
35
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
|
|
36
|
+
import { listSections, STARTER_MJML } from "@mjml-agent-editor/core";
|
|
37
|
+
|
|
38
|
+
// src/lib/save-baseline.ts
|
|
39
|
+
function createSaveBaseline() {
|
|
40
|
+
let stored = "";
|
|
41
|
+
let loaded = null;
|
|
42
|
+
return {
|
|
43
|
+
/** The MJML the store holds, as far as this editor knows. */
|
|
44
|
+
get stored() {
|
|
45
|
+
return stored;
|
|
46
|
+
},
|
|
47
|
+
/** `canvas` is what the load serialises to, or null when it repaired something to write. */
|
|
48
|
+
load(mjml, canvas) {
|
|
49
|
+
stored = mjml;
|
|
50
|
+
loaded = canvas;
|
|
51
|
+
},
|
|
52
|
+
saved(mjml) {
|
|
53
|
+
stored = mjml;
|
|
54
|
+
loaded = null;
|
|
55
|
+
},
|
|
56
|
+
changed(mjml) {
|
|
57
|
+
return mjml !== stored && mjml !== loaded;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
33
61
|
|
|
34
62
|
// src/lib/save-queue.ts
|
|
35
63
|
function createSaveQueue(save, delayMs) {
|
|
@@ -60,9 +88,50 @@ function createSaveQueue(save, delayMs) {
|
|
|
60
88
|
};
|
|
61
89
|
}
|
|
62
90
|
|
|
91
|
+
// src/lib/turn-follower.ts
|
|
92
|
+
function createTurnFollower() {
|
|
93
|
+
let running = false;
|
|
94
|
+
let mounted = true;
|
|
95
|
+
let apply = null;
|
|
96
|
+
return {
|
|
97
|
+
turn(on) {
|
|
98
|
+
if (on === running) return;
|
|
99
|
+
running = on;
|
|
100
|
+
if (mounted) apply?.(on);
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* Whether the canvas is still mounted. An unmounted one's late reload must not unlock
|
|
104
|
+
* the shared state a canvas mounted since has locked.
|
|
105
|
+
*/
|
|
106
|
+
get mounted() {
|
|
107
|
+
return mounted;
|
|
108
|
+
},
|
|
109
|
+
// Both, as React mounts twice in development.
|
|
110
|
+
mount() {
|
|
111
|
+
mounted = true;
|
|
112
|
+
},
|
|
113
|
+
unmount() {
|
|
114
|
+
mounted = false;
|
|
115
|
+
},
|
|
116
|
+
/** A turn already under way is applied at once; none means nothing to apply. */
|
|
117
|
+
ready(fn) {
|
|
118
|
+
apply = fn;
|
|
119
|
+
if (running) fn(true);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/lib/shortcuts.ts
|
|
125
|
+
function opensBlockSearch(e) {
|
|
126
|
+
if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey || e.isComposing) return false;
|
|
127
|
+
const target = e.target;
|
|
128
|
+
if (target?.isContentEditable) return false;
|
|
129
|
+
return !["INPUT", "TEXTAREA", "SELECT", "IFRAME"].includes(target?.tagName ?? "");
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
// src/components/editor/EmailEditor.tsx
|
|
64
133
|
import { toast as toast2 } from "sonner";
|
|
65
|
-
import { ChevronRight
|
|
134
|
+
import { ChevronRight as ChevronRight3, Mail, MousePointerClick } from "lucide-react";
|
|
66
135
|
|
|
67
136
|
// src/components/ui/switch.tsx
|
|
68
137
|
import "react";
|
|
@@ -353,11 +422,1051 @@ function matchesAccept(file, accept) {
|
|
|
353
422
|
});
|
|
354
423
|
}
|
|
355
424
|
|
|
425
|
+
// src/components/editor/inspector/Inspector.tsx
|
|
426
|
+
import { useEffect as useEffect4, useLayoutEffect, useReducer, useRef as useRef4, useState as useState4 } from "react";
|
|
427
|
+
import { useEditorMaybe } from "@grapesjs/react";
|
|
428
|
+
import { ArrowUp, ChevronRight as ChevronRight2, Copy, Monitor, Trash2 as Trash22, X as X2 } from "lucide-react";
|
|
429
|
+
|
|
356
430
|
// src/components/editor/ColorField.tsx
|
|
357
|
-
import { useCallback as useCallback2, useEffect as
|
|
431
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
358
432
|
import { HexAlphaColorPicker, HexColorPicker } from "react-colorful";
|
|
359
433
|
import { Pipette } from "lucide-react";
|
|
434
|
+
|
|
435
|
+
// src/components/editor/inspector/controls.tsx
|
|
436
|
+
import {
|
|
437
|
+
createContext,
|
|
438
|
+
useContext,
|
|
439
|
+
useEffect as useEffect2,
|
|
440
|
+
useId,
|
|
441
|
+
useRef as useRef2,
|
|
442
|
+
useState as useState2
|
|
443
|
+
} from "react";
|
|
444
|
+
import {
|
|
445
|
+
AlignCenter,
|
|
446
|
+
AlignJustify,
|
|
447
|
+
AlignLeft,
|
|
448
|
+
AlignRight,
|
|
449
|
+
ChevronDown,
|
|
450
|
+
ChevronUp,
|
|
451
|
+
CircleHelp,
|
|
452
|
+
Grid2x2,
|
|
453
|
+
RotateCcw
|
|
454
|
+
} from "lucide-react";
|
|
455
|
+
|
|
456
|
+
// src/components/editor/inspector/values.ts
|
|
457
|
+
import { isSafeLink, isSafeNavbarLink } from "@mjml-agent-editor/core";
|
|
458
|
+
var SIDES = ["top", "right", "bottom", "left"];
|
|
459
|
+
function expandBox(value) {
|
|
460
|
+
const parts = value.trim().split(/\s+/).filter(Boolean);
|
|
461
|
+
if (parts.length === 0 || parts.length > 4) return null;
|
|
462
|
+
const [t, r = t, b = t, l = r] = parts;
|
|
463
|
+
return [t, r, b, l];
|
|
464
|
+
}
|
|
465
|
+
function readBox(attributes, attr) {
|
|
466
|
+
const short = expandBox(attributes[attr] ?? "") ?? ["", "", "", ""];
|
|
467
|
+
return SIDES.map((side, i) => attributes[`${attr}-${side}`] ?? short[i]);
|
|
468
|
+
}
|
|
469
|
+
function boxShorthand(sides) {
|
|
470
|
+
const [t, r, b, l] = sides.map((s) => s.trim() || "0");
|
|
471
|
+
if (t === b && r === l) return t === r ? t : `${t} ${r}`;
|
|
472
|
+
if (r === l) return `${t} ${r} ${b}`;
|
|
473
|
+
return `${t} ${r} ${b} ${l}`;
|
|
474
|
+
}
|
|
475
|
+
function parseLength(value, units) {
|
|
476
|
+
const match = /^(-?\d*\.?\d+)\s*(px|%|em)?$/.exec(value.trim());
|
|
477
|
+
if (!match) return null;
|
|
478
|
+
const unit = match[2] ?? "";
|
|
479
|
+
return units.includes(unit) ? { amount: match[1], unit } : null;
|
|
480
|
+
}
|
|
481
|
+
var NO_SHARE = "-noshare";
|
|
482
|
+
function parseNetwork(name) {
|
|
483
|
+
return name.endsWith(NO_SHARE) ? { network: name.slice(0, -NO_SHARE.length), share: false } : { network: name, share: name !== "custom" && name !== "" };
|
|
484
|
+
}
|
|
485
|
+
function networkName(network, share) {
|
|
486
|
+
return share || network === "custom" ? network : `${network}${NO_SHARE}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/components/editor/inspector/controls.tsx
|
|
360
490
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
491
|
+
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";
|
|
492
|
+
var FieldLabel = createContext({});
|
|
493
|
+
var useFieldLabel = () => {
|
|
494
|
+
const { label, help } = useContext(FieldLabel);
|
|
495
|
+
return { "aria-labelledby": label, "aria-describedby": help };
|
|
496
|
+
};
|
|
497
|
+
function Row({
|
|
498
|
+
label,
|
|
499
|
+
help,
|
|
500
|
+
own,
|
|
501
|
+
wide,
|
|
502
|
+
inline,
|
|
503
|
+
labels,
|
|
504
|
+
onReset,
|
|
505
|
+
children
|
|
506
|
+
}) {
|
|
507
|
+
const id = useId();
|
|
508
|
+
const helpId = useId();
|
|
509
|
+
const [helpOpen, setHelpOpen] = useState2(false);
|
|
510
|
+
const control = /* @__PURE__ */ jsx3(FieldLabel.Provider, { value: { label: id, help: help ? helpId : void 0 }, children });
|
|
511
|
+
const caption = /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1.5 text-[11px] text-panel-muted-fg", children: [
|
|
512
|
+
/* @__PURE__ */ jsx3(
|
|
513
|
+
"span",
|
|
514
|
+
{
|
|
515
|
+
"aria-hidden": true,
|
|
516
|
+
className: cn("size-1.5 shrink-0 rounded-full", own ? "bg-brand" : "bg-transparent")
|
|
517
|
+
}
|
|
518
|
+
),
|
|
519
|
+
/* @__PURE__ */ jsx3("span", { id, className: "truncate", children: label }),
|
|
520
|
+
help && // Its own provider: a host need not wrap the editor in one, as only EditorHeader asked.
|
|
521
|
+
// Opened by a tap as well: Radix opens a tooltip on hover and focus, not on touch.
|
|
522
|
+
/* @__PURE__ */ jsx3(TooltipProvider, { children: /* @__PURE__ */ jsxs2(Tooltip, { open: helpOpen, onOpenChange: setHelpOpen, children: [
|
|
523
|
+
/* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(
|
|
524
|
+
"button",
|
|
525
|
+
{
|
|
526
|
+
type: "button",
|
|
527
|
+
"aria-label": help,
|
|
528
|
+
onClick: () => setHelpOpen((open) => !open),
|
|
529
|
+
className: "-m-0.5 shrink-0 rounded p-0.5 text-panel-muted-fg/70 hover:text-panel-fg focus-visible:text-panel-fg",
|
|
530
|
+
children: /* @__PURE__ */ jsx3(CircleHelp, { className: "size-3" })
|
|
531
|
+
}
|
|
532
|
+
) }),
|
|
533
|
+
/* @__PURE__ */ jsx3(TooltipContent, { side: "top", className: "max-w-56 leading-snug", children: help })
|
|
534
|
+
] }) }),
|
|
535
|
+
own && /* @__PURE__ */ jsx3(
|
|
536
|
+
"button",
|
|
537
|
+
{
|
|
538
|
+
type: "button",
|
|
539
|
+
onClick: onReset,
|
|
540
|
+
title: labels.resetToDefault,
|
|
541
|
+
"aria-label": `${labels.resetToDefault}: ${label}`,
|
|
542
|
+
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",
|
|
543
|
+
children: /* @__PURE__ */ jsx3(RotateCcw, { className: "size-3" })
|
|
544
|
+
}
|
|
545
|
+
)
|
|
546
|
+
] });
|
|
547
|
+
return /* @__PURE__ */ jsxs2("div", { className: "group/row px-3 py-1", children: [
|
|
548
|
+
wide ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
|
|
549
|
+
caption,
|
|
550
|
+
control
|
|
551
|
+
] }) : inline ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2", children: [
|
|
552
|
+
caption,
|
|
553
|
+
control
|
|
554
|
+
] }) : /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-[92px_minmax(0,1fr)] items-center gap-2", children: [
|
|
555
|
+
caption,
|
|
556
|
+
control
|
|
557
|
+
] }),
|
|
558
|
+
help && /* @__PURE__ */ jsx3("span", { id: helpId, hidden: true, children: help })
|
|
559
|
+
] });
|
|
560
|
+
}
|
|
561
|
+
function useDraft(value, commit, source = value) {
|
|
562
|
+
const [draft, setDraft] = useState2(value);
|
|
563
|
+
useEffect2(() => setDraft(value), [value, source]);
|
|
564
|
+
const done = () => {
|
|
565
|
+
if (draft.trim() !== value) commit(draft.trim());
|
|
566
|
+
};
|
|
567
|
+
const onKeyDown = (e) => {
|
|
568
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
569
|
+
if (e.key === "Escape") {
|
|
570
|
+
setDraft(value);
|
|
571
|
+
requestAnimationFrame(() => e.target.blur());
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
return { draft, setDraft, done, onKeyDown };
|
|
575
|
+
}
|
|
576
|
+
function TextInput({
|
|
577
|
+
value,
|
|
578
|
+
placeholder,
|
|
579
|
+
accept,
|
|
580
|
+
onCommit
|
|
581
|
+
}) {
|
|
582
|
+
const [invalid, setInvalid] = useState2(false);
|
|
583
|
+
const field = useFieldLabel();
|
|
584
|
+
const { draft, setDraft, done, onKeyDown } = useDraft(value, (next) => {
|
|
585
|
+
const ok = !accept || accept(next);
|
|
586
|
+
setInvalid(!ok);
|
|
587
|
+
if (ok) onCommit(next);
|
|
588
|
+
});
|
|
589
|
+
return /* @__PURE__ */ jsx3(
|
|
590
|
+
"input",
|
|
591
|
+
{
|
|
592
|
+
className: cn(FIELD, invalid && "border-red-400"),
|
|
593
|
+
...field,
|
|
594
|
+
"aria-invalid": invalid,
|
|
595
|
+
value: draft,
|
|
596
|
+
placeholder,
|
|
597
|
+
onChange: (e) => setDraft(e.target.value),
|
|
598
|
+
onBlur: done,
|
|
599
|
+
onKeyDown
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
var BARE_NUMBER = /^-?\d*\.?\d+$/;
|
|
604
|
+
function normaliseLength(text, units, negative = false, unit = units[0]) {
|
|
605
|
+
if (text === "") return "";
|
|
606
|
+
const parsed = BARE_NUMBER.test(text) ? { amount: text, unit } : parseLength(text, units);
|
|
607
|
+
if (!parsed || !negative && parsed.amount.startsWith("-")) return null;
|
|
608
|
+
return `${parsed.amount}${parsed.unit}`;
|
|
609
|
+
}
|
|
610
|
+
function stepAmount(amount, delta, negative = false) {
|
|
611
|
+
const moved = Math.round((Number(amount) + delta) * 100) / 100;
|
|
612
|
+
const next = String(negative ? moved : Math.max(0, moved));
|
|
613
|
+
return BARE_NUMBER.test(next) ? next : amount;
|
|
614
|
+
}
|
|
615
|
+
var REPEAT_AFTER_MS = 400;
|
|
616
|
+
var REPEAT_EVERY_MS = 60;
|
|
617
|
+
function LengthInput({
|
|
618
|
+
value,
|
|
619
|
+
placeholder,
|
|
620
|
+
units,
|
|
621
|
+
negative = false,
|
|
622
|
+
compact = false,
|
|
623
|
+
labelledBy,
|
|
624
|
+
labels,
|
|
625
|
+
onCommit
|
|
626
|
+
}) {
|
|
627
|
+
const fieldLabel = useFieldLabel();
|
|
628
|
+
const own = parseLength(value, units);
|
|
629
|
+
const fallback = parseLength(placeholder ?? "", units);
|
|
630
|
+
const [picked, setPicked] = useState2(null);
|
|
631
|
+
const unit = own?.unit ?? picked ?? fallback?.unit ?? units[0];
|
|
632
|
+
const [invalid, setInvalid] = useState2(false);
|
|
633
|
+
const commit = useRef2(onCommit);
|
|
634
|
+
commit.current = onCommit;
|
|
635
|
+
const endHold = useRef2(null);
|
|
636
|
+
useEffect2(() => () => endHold.current?.(), []);
|
|
637
|
+
const { draft, setDraft, done, onKeyDown } = useDraft(
|
|
638
|
+
own ? own.amount : value,
|
|
639
|
+
(next) => {
|
|
640
|
+
endHold.current?.();
|
|
641
|
+
const normal = normaliseLength(next, units, negative, unit);
|
|
642
|
+
setInvalid(normal === null);
|
|
643
|
+
if (normal !== null) commit.current(normal);
|
|
644
|
+
},
|
|
645
|
+
value
|
|
646
|
+
);
|
|
647
|
+
const from = () => {
|
|
648
|
+
const text = draft.trim();
|
|
649
|
+
if (BARE_NUMBER.test(text)) return { amount: text, unit };
|
|
650
|
+
if (text === "") return { amount: fallback?.amount ?? "0", unit };
|
|
651
|
+
return parseLength(text, units);
|
|
652
|
+
};
|
|
653
|
+
const stepOnce = (delta) => {
|
|
654
|
+
endHold.current?.();
|
|
655
|
+
const start = from();
|
|
656
|
+
if (!start) return setInvalid(true);
|
|
657
|
+
const amount = stepAmount(start.amount, delta, negative);
|
|
658
|
+
setInvalid(false);
|
|
659
|
+
setDraft(amount);
|
|
660
|
+
commit.current(`${amount}${start.unit}`);
|
|
661
|
+
};
|
|
662
|
+
const onKey = (e) => {
|
|
663
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return onKeyDown(e);
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
stepOnce((e.key === "ArrowUp" ? 1 : -1) * (e.shiftKey ? 10 : 1));
|
|
666
|
+
};
|
|
667
|
+
const press = (direction) => (e) => {
|
|
668
|
+
if (e.button !== 0 || endHold.current) return;
|
|
669
|
+
e.preventDefault();
|
|
670
|
+
const start = from();
|
|
671
|
+
if (!start) return setInvalid(true);
|
|
672
|
+
const delta = direction * (e.shiftKey ? 10 : 1);
|
|
673
|
+
let amount = stepAmount(start.amount, delta, negative);
|
|
674
|
+
const show = () => {
|
|
675
|
+
setInvalid(false);
|
|
676
|
+
setDraft(amount);
|
|
677
|
+
commit.current(`${amount}${start.unit}`, true);
|
|
678
|
+
};
|
|
679
|
+
show();
|
|
680
|
+
let timer = window.setTimeout(function repeat() {
|
|
681
|
+
amount = stepAmount(amount, delta, negative);
|
|
682
|
+
show();
|
|
683
|
+
timer = window.setTimeout(repeat, REPEAT_EVERY_MS);
|
|
684
|
+
}, REPEAT_AFTER_MS);
|
|
685
|
+
const button = e.currentTarget;
|
|
686
|
+
try {
|
|
687
|
+
button.setPointerCapture(e.pointerId);
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
const events = ["pointerup", "pointercancel", "lostpointercapture"];
|
|
691
|
+
const pointerId = e.pointerId;
|
|
692
|
+
const end = () => {
|
|
693
|
+
window.clearTimeout(timer);
|
|
694
|
+
for (const type of events) button.removeEventListener(type, release);
|
|
695
|
+
window.removeEventListener("pointerup", release);
|
|
696
|
+
window.removeEventListener("blur", end);
|
|
697
|
+
endHold.current = null;
|
|
698
|
+
commit.current(`${amount}${start.unit}`);
|
|
699
|
+
};
|
|
700
|
+
const release = (ev) => {
|
|
701
|
+
if (ev.pointerId === void 0 || ev.pointerId === pointerId)
|
|
702
|
+
end();
|
|
703
|
+
};
|
|
704
|
+
endHold.current = end;
|
|
705
|
+
for (const type of events) button.addEventListener(type, release);
|
|
706
|
+
window.addEventListener("pointerup", release);
|
|
707
|
+
window.addEventListener("blur", end);
|
|
708
|
+
};
|
|
709
|
+
const click = (direction) => (e) => {
|
|
710
|
+
if (e.detail === 0) stepOnce(direction);
|
|
711
|
+
};
|
|
712
|
+
const pickUnit = () => {
|
|
713
|
+
endHold.current?.();
|
|
714
|
+
const next = units[(units.indexOf(unit) + 1) % units.length];
|
|
715
|
+
const text = draft.trim();
|
|
716
|
+
if (!BARE_NUMBER.test(text)) return setPicked(next);
|
|
717
|
+
const normal = normaliseLength(text, units, negative, next);
|
|
718
|
+
if (normal === null) return setInvalid(true);
|
|
719
|
+
setPicked(next);
|
|
720
|
+
commit.current(normal);
|
|
721
|
+
};
|
|
722
|
+
const unitName = (u) => u || labels.noUnit;
|
|
723
|
+
const readable = draft.trim() === "" || BARE_NUMBER.test(draft.trim());
|
|
724
|
+
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";
|
|
725
|
+
return /* @__PURE__ */ jsxs2(
|
|
726
|
+
"div",
|
|
727
|
+
{
|
|
728
|
+
className: cn(
|
|
729
|
+
"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",
|
|
730
|
+
invalid && "border-red-400"
|
|
731
|
+
),
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsx3(
|
|
734
|
+
"input",
|
|
735
|
+
{
|
|
736
|
+
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",
|
|
737
|
+
value: draft,
|
|
738
|
+
placeholder: fallback ? fallback.amount : placeholder,
|
|
739
|
+
inputMode: "decimal",
|
|
740
|
+
...fieldLabel,
|
|
741
|
+
"aria-labelledby": labelledBy ?? fieldLabel["aria-labelledby"],
|
|
742
|
+
"aria-invalid": invalid,
|
|
743
|
+
onChange: (e) => {
|
|
744
|
+
endHold.current?.();
|
|
745
|
+
setDraft(e.target.value);
|
|
746
|
+
},
|
|
747
|
+
onBlur: done,
|
|
748
|
+
onKeyDown: onKey
|
|
749
|
+
}
|
|
750
|
+
),
|
|
751
|
+
readable && (units.length > 1 && !compact ? /* @__PURE__ */ jsx3(
|
|
752
|
+
"button",
|
|
753
|
+
{
|
|
754
|
+
type: "button",
|
|
755
|
+
onClick: pickUnit,
|
|
756
|
+
title: labels.changeUnit(unitName(unit)),
|
|
757
|
+
"aria-label": labels.changeUnit(unitName(unit)),
|
|
758
|
+
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",
|
|
759
|
+
children: unitName(unit)
|
|
760
|
+
}
|
|
761
|
+
) : (
|
|
762
|
+
// Narrow, the unit gives its place to the arrows while they show; a unit is typed.
|
|
763
|
+
/* @__PURE__ */ jsx3(
|
|
764
|
+
"span",
|
|
765
|
+
{
|
|
766
|
+
"aria-hidden": true,
|
|
767
|
+
className: cn(
|
|
768
|
+
"shrink-0 px-0.5 font-mono text-[10px] text-panel-muted-fg",
|
|
769
|
+
compact && "group-focus-within/length:hidden group-hover/length:hidden"
|
|
770
|
+
),
|
|
771
|
+
children: unitName(unit)
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
)),
|
|
775
|
+
/* @__PURE__ */ jsxs2(
|
|
776
|
+
"span",
|
|
777
|
+
{
|
|
778
|
+
className: cn(
|
|
779
|
+
"h-full shrink-0 flex-col border-l border-panel-border",
|
|
780
|
+
compact ? "hidden group-focus-within/length:flex group-hover/length:flex" : "flex"
|
|
781
|
+
),
|
|
782
|
+
children: [
|
|
783
|
+
/* @__PURE__ */ jsx3(
|
|
784
|
+
"button",
|
|
785
|
+
{
|
|
786
|
+
type: "button",
|
|
787
|
+
tabIndex: -1,
|
|
788
|
+
"aria-label": labels.increase,
|
|
789
|
+
onPointerDown: press(1),
|
|
790
|
+
onClick: click(1),
|
|
791
|
+
className: arrow,
|
|
792
|
+
children: /* @__PURE__ */ jsx3(ChevronUp, { className: "size-2.5" })
|
|
793
|
+
}
|
|
794
|
+
),
|
|
795
|
+
/* @__PURE__ */ jsx3(
|
|
796
|
+
"button",
|
|
797
|
+
{
|
|
798
|
+
type: "button",
|
|
799
|
+
tabIndex: -1,
|
|
800
|
+
"aria-label": labels.decrease,
|
|
801
|
+
onPointerDown: press(-1),
|
|
802
|
+
onClick: click(-1),
|
|
803
|
+
className: arrow,
|
|
804
|
+
children: /* @__PURE__ */ jsx3(ChevronDown, { className: "size-2.5" })
|
|
805
|
+
}
|
|
806
|
+
)
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
)
|
|
810
|
+
]
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
function BoxInput({
|
|
815
|
+
attributes,
|
|
816
|
+
attr,
|
|
817
|
+
placeholder,
|
|
818
|
+
units,
|
|
819
|
+
labels,
|
|
820
|
+
onCommit
|
|
821
|
+
}) {
|
|
822
|
+
const sides = readBox(attributes, attr);
|
|
823
|
+
const defaults = readBox(placeholder ? { [attr]: placeholder } : {}, attr);
|
|
824
|
+
const symmetric = sides[0] === sides[2] && sides[1] === sides[3];
|
|
825
|
+
const [each, setEach] = useState2(!symmetric);
|
|
826
|
+
useEffect2(() => {
|
|
827
|
+
if (!symmetric) setEach(true);
|
|
828
|
+
}, [symmetric]);
|
|
829
|
+
const set = (indices, v, partial) => {
|
|
830
|
+
const next = sides.map((s, i) => s || defaults[i]);
|
|
831
|
+
for (const i of indices) next[i] = v || defaults[i];
|
|
832
|
+
onCommit(next.every((s, i) => s === defaults[i]) ? "" : boxShorthand(next), partial);
|
|
833
|
+
};
|
|
834
|
+
const fieldLabel = useFieldLabel();
|
|
835
|
+
const sideId = useId();
|
|
836
|
+
const sideNames = [labels.sideTop, labels.sideRight, labels.sideBottom, labels.sideLeft];
|
|
837
|
+
const pairs = each ? SIDES.map((_, i) => [sideNames[i], [i]]) : [
|
|
838
|
+
[labels.sidesVertical, [0, 2]],
|
|
839
|
+
[labels.sidesHorizontal, [1, 3]]
|
|
840
|
+
];
|
|
841
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-start gap-1.5", children: [
|
|
842
|
+
/* @__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: [
|
|
843
|
+
/* @__PURE__ */ jsx3(
|
|
844
|
+
LengthInput,
|
|
845
|
+
{
|
|
846
|
+
value: sides[indices[0]],
|
|
847
|
+
placeholder: defaults[indices[0]] || "0",
|
|
848
|
+
units,
|
|
849
|
+
compact: each,
|
|
850
|
+
labels,
|
|
851
|
+
labelledBy: [fieldLabel["aria-labelledby"], `${sideId}-${indices[0]}`].filter(Boolean).join(" "),
|
|
852
|
+
onCommit: (v, partial) => set(indices, v, partial)
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
/* @__PURE__ */ jsx3(
|
|
856
|
+
"span",
|
|
857
|
+
{
|
|
858
|
+
id: `${sideId}-${indices[0]}`,
|
|
859
|
+
className: "truncate text-center text-[10px] text-panel-muted-fg/80",
|
|
860
|
+
children: name
|
|
861
|
+
}
|
|
862
|
+
)
|
|
863
|
+
] }, name)) }),
|
|
864
|
+
/* @__PURE__ */ jsx3(
|
|
865
|
+
"button",
|
|
866
|
+
{
|
|
867
|
+
type: "button",
|
|
868
|
+
onClick: () => setEach((v) => !v),
|
|
869
|
+
disabled: each && !symmetric,
|
|
870
|
+
"aria-pressed": each,
|
|
871
|
+
title: labels.editEachSide,
|
|
872
|
+
"aria-label": labels.editEachSide,
|
|
873
|
+
className: cn(
|
|
874
|
+
"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",
|
|
875
|
+
each && "border-brand text-panel-fg"
|
|
876
|
+
),
|
|
877
|
+
children: /* @__PURE__ */ jsx3(Grid2x2, { className: "size-3.5" })
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
] });
|
|
881
|
+
}
|
|
882
|
+
var ALIGN_ICONS = {
|
|
883
|
+
left: /* @__PURE__ */ jsx3(AlignLeft, { className: "size-3.5" }),
|
|
884
|
+
center: /* @__PURE__ */ jsx3(AlignCenter, { className: "size-3.5" }),
|
|
885
|
+
right: /* @__PURE__ */ jsx3(AlignRight, { className: "size-3.5" }),
|
|
886
|
+
justify: /* @__PURE__ */ jsx3(AlignJustify, { className: "size-3.5" })
|
|
887
|
+
};
|
|
888
|
+
function Segmented({
|
|
889
|
+
value,
|
|
890
|
+
fallback,
|
|
891
|
+
options,
|
|
892
|
+
name,
|
|
893
|
+
onCommit
|
|
894
|
+
}) {
|
|
895
|
+
const current = value || fallback;
|
|
896
|
+
const iconsOnly = options.every((o) => o.value in ALIGN_ICONS);
|
|
897
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex h-7 overflow-hidden rounded-md border border-panel-border bg-panel-elevated", children: options.map((o) => {
|
|
898
|
+
const active = current === o.value;
|
|
899
|
+
return /* @__PURE__ */ jsx3(
|
|
900
|
+
"button",
|
|
901
|
+
{
|
|
902
|
+
type: "button",
|
|
903
|
+
"aria-pressed": active,
|
|
904
|
+
title: name(o),
|
|
905
|
+
"aria-label": name(o),
|
|
906
|
+
onClick: () => o.value !== value && onCommit(o.value),
|
|
907
|
+
className: cn(
|
|
908
|
+
"flex min-w-0 flex-1 items-center justify-center px-1 text-[11px] text-panel-muted-fg transition-colors hover:text-panel-fg",
|
|
909
|
+
active && (value ? "bg-brand text-brand-fg hover:text-brand-fg" : "bg-panel-hover text-panel-fg")
|
|
910
|
+
),
|
|
911
|
+
children: iconsOnly ? ALIGN_ICONS[o.value] : /* @__PURE__ */ jsx3("span", { className: "truncate", children: name(o) })
|
|
912
|
+
},
|
|
913
|
+
o.value
|
|
914
|
+
);
|
|
915
|
+
}) });
|
|
916
|
+
}
|
|
917
|
+
function Select({
|
|
918
|
+
value,
|
|
919
|
+
fallback,
|
|
920
|
+
options,
|
|
921
|
+
name,
|
|
922
|
+
onCommit
|
|
923
|
+
}) {
|
|
924
|
+
const known = !value || options.some((o) => o.value === value);
|
|
925
|
+
const fallbackOption = options.find((o) => o.value === fallback);
|
|
926
|
+
const field = useFieldLabel();
|
|
927
|
+
return /* @__PURE__ */ jsxs2(
|
|
928
|
+
"select",
|
|
929
|
+
{
|
|
930
|
+
className: cn(FIELD, "px-1.5"),
|
|
931
|
+
...field,
|
|
932
|
+
value: value || "",
|
|
933
|
+
onChange: (e) => onCommit(e.target.value),
|
|
934
|
+
children: [
|
|
935
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: fallbackOption ? name(fallbackOption) : "\u2014" }),
|
|
936
|
+
!known && /* @__PURE__ */ jsx3("option", { value, children: value }),
|
|
937
|
+
options.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: name(o) }, o.value))
|
|
938
|
+
]
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
function Toggle({
|
|
943
|
+
checked,
|
|
944
|
+
label,
|
|
945
|
+
onCommit
|
|
946
|
+
}) {
|
|
947
|
+
return /* @__PURE__ */ jsx3(
|
|
948
|
+
Switch,
|
|
949
|
+
{
|
|
950
|
+
checked,
|
|
951
|
+
onCheckedChange: onCommit,
|
|
952
|
+
"aria-label": label,
|
|
953
|
+
className: "justify-self-end data-checked:bg-brand data-unchecked:bg-panel-border"
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/components/editor/mjml-attributes.ts
|
|
959
|
+
var ALLOWED_ATTRIBUTES = {
|
|
960
|
+
"mj-body": [
|
|
961
|
+
"background-color",
|
|
962
|
+
"width"
|
|
963
|
+
],
|
|
964
|
+
"mj-wrapper": [
|
|
965
|
+
"background-color",
|
|
966
|
+
"background-position",
|
|
967
|
+
"background-position-x",
|
|
968
|
+
"background-position-y",
|
|
969
|
+
"background-repeat",
|
|
970
|
+
"background-size",
|
|
971
|
+
"background-url",
|
|
972
|
+
"border",
|
|
973
|
+
"border-bottom",
|
|
974
|
+
"border-left",
|
|
975
|
+
"border-radius",
|
|
976
|
+
"border-right",
|
|
977
|
+
"border-top",
|
|
978
|
+
"direction",
|
|
979
|
+
"full-width",
|
|
980
|
+
"gap",
|
|
981
|
+
"padding",
|
|
982
|
+
"padding-bottom",
|
|
983
|
+
"padding-left",
|
|
984
|
+
"padding-right",
|
|
985
|
+
"padding-top",
|
|
986
|
+
"text-align",
|
|
987
|
+
"text-padding"
|
|
988
|
+
],
|
|
989
|
+
"mj-section": [
|
|
990
|
+
"background-color",
|
|
991
|
+
"background-position",
|
|
992
|
+
"background-position-x",
|
|
993
|
+
"background-position-y",
|
|
994
|
+
"background-repeat",
|
|
995
|
+
"background-size",
|
|
996
|
+
"background-url",
|
|
997
|
+
"border",
|
|
998
|
+
"border-bottom",
|
|
999
|
+
"border-left",
|
|
1000
|
+
"border-radius",
|
|
1001
|
+
"border-right",
|
|
1002
|
+
"border-top",
|
|
1003
|
+
"direction",
|
|
1004
|
+
"full-width",
|
|
1005
|
+
"padding",
|
|
1006
|
+
"padding-bottom",
|
|
1007
|
+
"padding-left",
|
|
1008
|
+
"padding-right",
|
|
1009
|
+
"padding-top",
|
|
1010
|
+
"text-align",
|
|
1011
|
+
"text-padding"
|
|
1012
|
+
],
|
|
1013
|
+
"mj-column": [
|
|
1014
|
+
"background-color",
|
|
1015
|
+
"border",
|
|
1016
|
+
"border-bottom",
|
|
1017
|
+
"border-left",
|
|
1018
|
+
"border-radius",
|
|
1019
|
+
"border-right",
|
|
1020
|
+
"border-top",
|
|
1021
|
+
"direction",
|
|
1022
|
+
"inner-background-color",
|
|
1023
|
+
"inner-border",
|
|
1024
|
+
"inner-border-bottom",
|
|
1025
|
+
"inner-border-left",
|
|
1026
|
+
"inner-border-radius",
|
|
1027
|
+
"inner-border-right",
|
|
1028
|
+
"inner-border-top",
|
|
1029
|
+
"padding",
|
|
1030
|
+
"padding-bottom",
|
|
1031
|
+
"padding-left",
|
|
1032
|
+
"padding-right",
|
|
1033
|
+
"padding-top",
|
|
1034
|
+
"vertical-align",
|
|
1035
|
+
"width"
|
|
1036
|
+
],
|
|
1037
|
+
"mj-group": [
|
|
1038
|
+
"background-color",
|
|
1039
|
+
"direction",
|
|
1040
|
+
"vertical-align",
|
|
1041
|
+
"width"
|
|
1042
|
+
],
|
|
1043
|
+
"mj-text": [
|
|
1044
|
+
"align",
|
|
1045
|
+
"background-color",
|
|
1046
|
+
"color",
|
|
1047
|
+
"container-background-color",
|
|
1048
|
+
"font-family",
|
|
1049
|
+
"font-size",
|
|
1050
|
+
"font-style",
|
|
1051
|
+
"font-weight",
|
|
1052
|
+
"height",
|
|
1053
|
+
"letter-spacing",
|
|
1054
|
+
"line-height",
|
|
1055
|
+
"padding",
|
|
1056
|
+
"padding-bottom",
|
|
1057
|
+
"padding-left",
|
|
1058
|
+
"padding-right",
|
|
1059
|
+
"padding-top",
|
|
1060
|
+
"text-decoration",
|
|
1061
|
+
"text-transform",
|
|
1062
|
+
"vertical-align"
|
|
1063
|
+
],
|
|
1064
|
+
"mj-button": [
|
|
1065
|
+
"align",
|
|
1066
|
+
"background-color",
|
|
1067
|
+
"border",
|
|
1068
|
+
"border-bottom",
|
|
1069
|
+
"border-left",
|
|
1070
|
+
"border-radius",
|
|
1071
|
+
"border-right",
|
|
1072
|
+
"border-top",
|
|
1073
|
+
"color",
|
|
1074
|
+
"container-background-color",
|
|
1075
|
+
"font-family",
|
|
1076
|
+
"font-size",
|
|
1077
|
+
"font-style",
|
|
1078
|
+
"font-weight",
|
|
1079
|
+
"height",
|
|
1080
|
+
"href",
|
|
1081
|
+
"inner-padding",
|
|
1082
|
+
"letter-spacing",
|
|
1083
|
+
"line-height",
|
|
1084
|
+
"name",
|
|
1085
|
+
"padding",
|
|
1086
|
+
"padding-bottom",
|
|
1087
|
+
"padding-left",
|
|
1088
|
+
"padding-right",
|
|
1089
|
+
"padding-top",
|
|
1090
|
+
"rel",
|
|
1091
|
+
"target",
|
|
1092
|
+
"text-align",
|
|
1093
|
+
"text-decoration",
|
|
1094
|
+
"text-transform",
|
|
1095
|
+
"title",
|
|
1096
|
+
"vertical-align",
|
|
1097
|
+
"width"
|
|
1098
|
+
],
|
|
1099
|
+
"mj-image": [
|
|
1100
|
+
"align",
|
|
1101
|
+
"alt",
|
|
1102
|
+
"border",
|
|
1103
|
+
"border-bottom",
|
|
1104
|
+
"border-left",
|
|
1105
|
+
"border-radius",
|
|
1106
|
+
"border-right",
|
|
1107
|
+
"border-top",
|
|
1108
|
+
"container-background-color",
|
|
1109
|
+
"fluid-on-mobile",
|
|
1110
|
+
"font-size",
|
|
1111
|
+
"height",
|
|
1112
|
+
"href",
|
|
1113
|
+
"max-height",
|
|
1114
|
+
"name",
|
|
1115
|
+
"padding",
|
|
1116
|
+
"padding-bottom",
|
|
1117
|
+
"padding-left",
|
|
1118
|
+
"padding-right",
|
|
1119
|
+
"padding-top",
|
|
1120
|
+
"rel",
|
|
1121
|
+
"sizes",
|
|
1122
|
+
"src",
|
|
1123
|
+
"srcset",
|
|
1124
|
+
"target",
|
|
1125
|
+
"title",
|
|
1126
|
+
"usemap",
|
|
1127
|
+
"width"
|
|
1128
|
+
],
|
|
1129
|
+
"mj-divider": [
|
|
1130
|
+
"align",
|
|
1131
|
+
"border-color",
|
|
1132
|
+
"border-style",
|
|
1133
|
+
"border-width",
|
|
1134
|
+
"container-background-color",
|
|
1135
|
+
"padding",
|
|
1136
|
+
"padding-bottom",
|
|
1137
|
+
"padding-left",
|
|
1138
|
+
"padding-right",
|
|
1139
|
+
"padding-top",
|
|
1140
|
+
"width"
|
|
1141
|
+
],
|
|
1142
|
+
"mj-spacer": [
|
|
1143
|
+
"border",
|
|
1144
|
+
"border-bottom",
|
|
1145
|
+
"border-left",
|
|
1146
|
+
"border-right",
|
|
1147
|
+
"border-top",
|
|
1148
|
+
"container-background-color",
|
|
1149
|
+
"height",
|
|
1150
|
+
"padding",
|
|
1151
|
+
"padding-bottom",
|
|
1152
|
+
"padding-left",
|
|
1153
|
+
"padding-right",
|
|
1154
|
+
"padding-top"
|
|
1155
|
+
],
|
|
1156
|
+
"mj-social": [
|
|
1157
|
+
"align",
|
|
1158
|
+
"border-radius",
|
|
1159
|
+
"color",
|
|
1160
|
+
"container-background-color",
|
|
1161
|
+
"font-family",
|
|
1162
|
+
"font-size",
|
|
1163
|
+
"font-style",
|
|
1164
|
+
"font-weight",
|
|
1165
|
+
"icon-height",
|
|
1166
|
+
"icon-padding",
|
|
1167
|
+
"icon-size",
|
|
1168
|
+
"inner-padding",
|
|
1169
|
+
"line-height",
|
|
1170
|
+
"mode",
|
|
1171
|
+
"padding",
|
|
1172
|
+
"padding-bottom",
|
|
1173
|
+
"padding-left",
|
|
1174
|
+
"padding-right",
|
|
1175
|
+
"padding-top",
|
|
1176
|
+
"table-layout",
|
|
1177
|
+
"text-decoration",
|
|
1178
|
+
"text-padding",
|
|
1179
|
+
"vertical-align"
|
|
1180
|
+
],
|
|
1181
|
+
"mj-social-element": [
|
|
1182
|
+
"align",
|
|
1183
|
+
"alt",
|
|
1184
|
+
"background-color",
|
|
1185
|
+
"border-radius",
|
|
1186
|
+
"color",
|
|
1187
|
+
"font-family",
|
|
1188
|
+
"font-size",
|
|
1189
|
+
"font-style",
|
|
1190
|
+
"font-weight",
|
|
1191
|
+
"href",
|
|
1192
|
+
"icon-height",
|
|
1193
|
+
"icon-padding",
|
|
1194
|
+
"icon-position",
|
|
1195
|
+
"icon-size",
|
|
1196
|
+
"line-height",
|
|
1197
|
+
"name",
|
|
1198
|
+
"padding",
|
|
1199
|
+
"padding-bottom",
|
|
1200
|
+
"padding-left",
|
|
1201
|
+
"padding-right",
|
|
1202
|
+
"padding-top",
|
|
1203
|
+
"rel",
|
|
1204
|
+
"sizes",
|
|
1205
|
+
"src",
|
|
1206
|
+
"srcset",
|
|
1207
|
+
"target",
|
|
1208
|
+
"text-decoration",
|
|
1209
|
+
"text-padding",
|
|
1210
|
+
"title",
|
|
1211
|
+
"vertical-align"
|
|
1212
|
+
],
|
|
1213
|
+
"mj-hero": [
|
|
1214
|
+
"background-color",
|
|
1215
|
+
"background-height",
|
|
1216
|
+
"background-position",
|
|
1217
|
+
"background-url",
|
|
1218
|
+
"background-width",
|
|
1219
|
+
"border-radius",
|
|
1220
|
+
"container-background-color",
|
|
1221
|
+
"height",
|
|
1222
|
+
"inner-background-color",
|
|
1223
|
+
"inner-padding",
|
|
1224
|
+
"inner-padding-bottom",
|
|
1225
|
+
"inner-padding-left",
|
|
1226
|
+
"inner-padding-right",
|
|
1227
|
+
"inner-padding-top",
|
|
1228
|
+
"mode",
|
|
1229
|
+
"padding",
|
|
1230
|
+
"padding-bottom",
|
|
1231
|
+
"padding-left",
|
|
1232
|
+
"padding-right",
|
|
1233
|
+
"padding-top",
|
|
1234
|
+
"vertical-align"
|
|
1235
|
+
],
|
|
1236
|
+
"mj-navbar": [
|
|
1237
|
+
"align",
|
|
1238
|
+
"base-url",
|
|
1239
|
+
"hamburger",
|
|
1240
|
+
"ico-align",
|
|
1241
|
+
"ico-close",
|
|
1242
|
+
"ico-color",
|
|
1243
|
+
"ico-font-family",
|
|
1244
|
+
"ico-font-size",
|
|
1245
|
+
"ico-line-height",
|
|
1246
|
+
"ico-open",
|
|
1247
|
+
"ico-padding",
|
|
1248
|
+
"ico-padding-bottom",
|
|
1249
|
+
"ico-padding-left",
|
|
1250
|
+
"ico-padding-right",
|
|
1251
|
+
"ico-padding-top",
|
|
1252
|
+
"ico-text-decoration",
|
|
1253
|
+
"ico-text-transform",
|
|
1254
|
+
"padding",
|
|
1255
|
+
"padding-bottom",
|
|
1256
|
+
"padding-left",
|
|
1257
|
+
"padding-right",
|
|
1258
|
+
"padding-top"
|
|
1259
|
+
],
|
|
1260
|
+
"mj-navbar-link": [
|
|
1261
|
+
"color",
|
|
1262
|
+
"font-family",
|
|
1263
|
+
"font-size",
|
|
1264
|
+
"font-style",
|
|
1265
|
+
"font-weight",
|
|
1266
|
+
"href",
|
|
1267
|
+
"letter-spacing",
|
|
1268
|
+
"line-height",
|
|
1269
|
+
"name",
|
|
1270
|
+
"padding",
|
|
1271
|
+
"padding-bottom",
|
|
1272
|
+
"padding-left",
|
|
1273
|
+
"padding-right",
|
|
1274
|
+
"padding-top",
|
|
1275
|
+
"rel",
|
|
1276
|
+
"target",
|
|
1277
|
+
"text-decoration",
|
|
1278
|
+
"text-transform"
|
|
1279
|
+
]
|
|
1280
|
+
};
|
|
1281
|
+
var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["mj-class", "css-class", "id", "style"]);
|
|
1282
|
+
var START_TAG = /<[a-z][^\s/>]*(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
|
|
1283
|
+
var ATTRIBUTE = /\s+([^\s"'>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?/g;
|
|
1284
|
+
function withoutEditorProps(mjml) {
|
|
1285
|
+
return mjml.replace(
|
|
1286
|
+
START_TAG,
|
|
1287
|
+
(tag) => tag.replace(
|
|
1288
|
+
ATTRIBUTE,
|
|
1289
|
+
(attribute, name) => name.toLowerCase().startsWith("data-gjs-") ? "" : attribute
|
|
1290
|
+
)
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
function withClosedTags(mjml) {
|
|
1294
|
+
return mjml.replace(START_TAG, (tag) => {
|
|
1295
|
+
const selfClosed = /^<(mj-[\w-]+)([\s\S]*?)\s*\/>$/i.exec(tag);
|
|
1296
|
+
return selfClosed ? `<${selfClosed[1]}${selfClosed[2]}></${selfClosed[1]}>` : tag;
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
var TABLE = /<!--[\s\S]*?(?:-->|$)|(<mj-table\b(?:[^>"']|"[^"]*"|'[^']*')*>)((?:<!--[\s\S]*?-->|(?!<!--)[\s\S])*?)(<\/mj-table\s*>)/gi;
|
|
1300
|
+
function withTableRowsKept(mjml) {
|
|
1301
|
+
return mjml.replace(
|
|
1302
|
+
TABLE,
|
|
1303
|
+
(match, open, rows, close) => open ? `${open}<template>${rows}</template>${close}` : match
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
function sourceForEditor(mjml) {
|
|
1307
|
+
const cleaned = withoutEditorProps(mjml);
|
|
1308
|
+
return { mjml: withTableRowsKept(withClosedTags(cleaned)), repaired: cleaned !== mjml };
|
|
1309
|
+
}
|
|
1310
|
+
function illegalAttributes(tag, attributes) {
|
|
1311
|
+
const allowed = ALLOWED_ATTRIBUTES[tag];
|
|
1312
|
+
if (!allowed) return [];
|
|
1313
|
+
return Object.keys(attributes).filter((a) => !ALWAYS_ALLOWED.has(a) && !allowed.includes(a));
|
|
1314
|
+
}
|
|
1315
|
+
var NAMED_COLORS = /* @__PURE__ */ new Set([
|
|
1316
|
+
"aliceblue",
|
|
1317
|
+
"antiquewhite",
|
|
1318
|
+
"aqua",
|
|
1319
|
+
"aquamarine",
|
|
1320
|
+
"azure",
|
|
1321
|
+
"beige",
|
|
1322
|
+
"bisque",
|
|
1323
|
+
"black",
|
|
1324
|
+
"blanchedalmond",
|
|
1325
|
+
"blue",
|
|
1326
|
+
"blueviolet",
|
|
1327
|
+
"brown",
|
|
1328
|
+
"burlywood",
|
|
1329
|
+
"cadetblue",
|
|
1330
|
+
"chartreuse",
|
|
1331
|
+
"chocolate",
|
|
1332
|
+
"coral",
|
|
1333
|
+
"cornflowerblue",
|
|
1334
|
+
"cornsilk",
|
|
1335
|
+
"crimson",
|
|
1336
|
+
"cyan",
|
|
1337
|
+
"darkblue",
|
|
1338
|
+
"darkcyan",
|
|
1339
|
+
"darkgoldenrod",
|
|
1340
|
+
"darkgray",
|
|
1341
|
+
"darkgreen",
|
|
1342
|
+
"darkgrey",
|
|
1343
|
+
"darkkhaki",
|
|
1344
|
+
"darkmagenta",
|
|
1345
|
+
"darkolivegreen",
|
|
1346
|
+
"darkorange",
|
|
1347
|
+
"darkorchid",
|
|
1348
|
+
"darkred",
|
|
1349
|
+
"darksalmon",
|
|
1350
|
+
"darkseagreen",
|
|
1351
|
+
"darkslateblue",
|
|
1352
|
+
"darkslategray",
|
|
1353
|
+
"darkslategrey",
|
|
1354
|
+
"darkturquoise",
|
|
1355
|
+
"darkviolet",
|
|
1356
|
+
"deeppink",
|
|
1357
|
+
"deepskyblue",
|
|
1358
|
+
"dimgray",
|
|
1359
|
+
"dimgrey",
|
|
1360
|
+
"dodgerblue",
|
|
1361
|
+
"firebrick",
|
|
1362
|
+
"floralwhite",
|
|
1363
|
+
"forestgreen",
|
|
1364
|
+
"fuchsia",
|
|
1365
|
+
"gainsboro",
|
|
1366
|
+
"ghostwhite",
|
|
1367
|
+
"gold",
|
|
1368
|
+
"goldenrod",
|
|
1369
|
+
"gray",
|
|
1370
|
+
"green",
|
|
1371
|
+
"greenyellow",
|
|
1372
|
+
"grey",
|
|
1373
|
+
"honeydew",
|
|
1374
|
+
"hotpink",
|
|
1375
|
+
"indianred",
|
|
1376
|
+
"indigo",
|
|
1377
|
+
"inherit",
|
|
1378
|
+
"ivory",
|
|
1379
|
+
"khaki",
|
|
1380
|
+
"lavender",
|
|
1381
|
+
"lavenderblush",
|
|
1382
|
+
"lawngreen",
|
|
1383
|
+
"lemonchiffon",
|
|
1384
|
+
"lightblue",
|
|
1385
|
+
"lightcoral",
|
|
1386
|
+
"lightcyan",
|
|
1387
|
+
"lightgoldenrodyellow",
|
|
1388
|
+
"lightgray",
|
|
1389
|
+
"lightgreen",
|
|
1390
|
+
"lightgrey",
|
|
1391
|
+
"lightpink",
|
|
1392
|
+
"lightsalmon",
|
|
1393
|
+
"lightseagreen",
|
|
1394
|
+
"lightskyblue",
|
|
1395
|
+
"lightslategray",
|
|
1396
|
+
"lightslategrey",
|
|
1397
|
+
"lightsteelblue",
|
|
1398
|
+
"lightyellow",
|
|
1399
|
+
"lime",
|
|
1400
|
+
"limegreen",
|
|
1401
|
+
"linen",
|
|
1402
|
+
"magenta",
|
|
1403
|
+
"maroon",
|
|
1404
|
+
"mediumaquamarine",
|
|
1405
|
+
"mediumblue",
|
|
1406
|
+
"mediumorchid",
|
|
1407
|
+
"mediumpurple",
|
|
1408
|
+
"mediumseagreen",
|
|
1409
|
+
"mediumslateblue",
|
|
1410
|
+
"mediumspringgreen",
|
|
1411
|
+
"mediumturquoise",
|
|
1412
|
+
"mediumvioletred",
|
|
1413
|
+
"midnightblue",
|
|
1414
|
+
"mintcream",
|
|
1415
|
+
"mistyrose",
|
|
1416
|
+
"moccasin",
|
|
1417
|
+
"navajowhite",
|
|
1418
|
+
"navy",
|
|
1419
|
+
"oldlace",
|
|
1420
|
+
"olive",
|
|
1421
|
+
"olivedrab",
|
|
1422
|
+
"orange",
|
|
1423
|
+
"orangered",
|
|
1424
|
+
"orchid",
|
|
1425
|
+
"palegoldenrod",
|
|
1426
|
+
"palegreen",
|
|
1427
|
+
"paleturquoise",
|
|
1428
|
+
"palevioletred",
|
|
1429
|
+
"papayawhip",
|
|
1430
|
+
"peachpuff",
|
|
1431
|
+
"peru",
|
|
1432
|
+
"pink",
|
|
1433
|
+
"plum",
|
|
1434
|
+
"powderblue",
|
|
1435
|
+
"purple",
|
|
1436
|
+
"rebeccapurple",
|
|
1437
|
+
"red",
|
|
1438
|
+
"rosybrown",
|
|
1439
|
+
"royalblue",
|
|
1440
|
+
"saddlebrown",
|
|
1441
|
+
"salmon",
|
|
1442
|
+
"sandybrown",
|
|
1443
|
+
"seagreen",
|
|
1444
|
+
"seashell",
|
|
1445
|
+
"sienna",
|
|
1446
|
+
"silver",
|
|
1447
|
+
"skyblue",
|
|
1448
|
+
"slateblue",
|
|
1449
|
+
"slategray",
|
|
1450
|
+
"slategrey",
|
|
1451
|
+
"snow",
|
|
1452
|
+
"springgreen",
|
|
1453
|
+
"steelblue",
|
|
1454
|
+
"tan",
|
|
1455
|
+
"teal",
|
|
1456
|
+
"thistle",
|
|
1457
|
+
"tomato",
|
|
1458
|
+
"transparent",
|
|
1459
|
+
"turquoise",
|
|
1460
|
+
"violet",
|
|
1461
|
+
"wheat",
|
|
1462
|
+
"white",
|
|
1463
|
+
"whitesmoke",
|
|
1464
|
+
"yellow",
|
|
1465
|
+
"yellowgreen"
|
|
1466
|
+
]);
|
|
1467
|
+
|
|
1468
|
+
// src/components/editor/ColorField.tsx
|
|
1469
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
361
1470
|
var PRESETS = [
|
|
362
1471
|
"#000000",
|
|
363
1472
|
"#374151",
|
|
@@ -378,32 +1487,33 @@ var eyeDropper = () => globalThis.EyeDropper;
|
|
|
378
1487
|
function ColorField({
|
|
379
1488
|
value,
|
|
380
1489
|
placeholder,
|
|
381
|
-
documentColors,
|
|
1490
|
+
documentColors: documentColors2,
|
|
382
1491
|
labels,
|
|
383
1492
|
onChange
|
|
384
1493
|
}) {
|
|
385
|
-
const
|
|
386
|
-
const [
|
|
387
|
-
const [
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
const
|
|
1494
|
+
const field = useFieldLabel();
|
|
1495
|
+
const [text, setText] = useState3(value);
|
|
1496
|
+
const [open, setOpen] = useState3(false);
|
|
1497
|
+
const [used, setUsed] = useState3([]);
|
|
1498
|
+
const pressed = useRef3(false);
|
|
1499
|
+
const dragged = useRef3(null);
|
|
1500
|
+
const fieldRef = useRef3(null);
|
|
1501
|
+
const anchor = useRef3({
|
|
392
1502
|
getBoundingClientRect: () => {
|
|
393
|
-
const
|
|
1503
|
+
const field2 = fieldRef.current?.getBoundingClientRect() ?? new DOMRect();
|
|
394
1504
|
const panel = fieldRef.current?.closest(".editor-dark")?.getBoundingClientRect();
|
|
395
|
-
return new DOMRect(panel?.right ??
|
|
1505
|
+
return new DOMRect(panel?.right ?? field2.right, field2.top, 0, field2.height);
|
|
396
1506
|
}
|
|
397
1507
|
});
|
|
398
|
-
|
|
399
|
-
const latestOnChange =
|
|
1508
|
+
useEffect3(() => setText(value), [value]);
|
|
1509
|
+
const latestOnChange = useRef3(onChange);
|
|
400
1510
|
latestOnChange.current = onChange;
|
|
401
1511
|
const release = useCallback2(() => {
|
|
402
1512
|
pressed.current = false;
|
|
403
1513
|
if (dragged.current !== null) latestOnChange.current(dragged.current, false);
|
|
404
1514
|
dragged.current = null;
|
|
405
1515
|
}, []);
|
|
406
|
-
|
|
1516
|
+
useEffect3(() => {
|
|
407
1517
|
document.addEventListener("lostpointercapture", release);
|
|
408
1518
|
return () => {
|
|
409
1519
|
document.removeEventListener("lostpointercapture", release);
|
|
@@ -412,12 +1522,16 @@ function ColorField({
|
|
|
412
1522
|
}, [release]);
|
|
413
1523
|
const hex = toHex(value) ?? toHex(placeholder ?? "") ?? "#000000";
|
|
414
1524
|
const translucent = hex.length === 9;
|
|
415
|
-
const
|
|
1525
|
+
const shown = value || placeholder || "";
|
|
416
1526
|
const commit = (next) => {
|
|
417
1527
|
dragged.current = null;
|
|
418
|
-
|
|
1528
|
+
const color = mjmlColor(next);
|
|
1529
|
+
if (color === null) return setText(value);
|
|
1530
|
+
setText(color);
|
|
1531
|
+
onChange(color, false);
|
|
419
1532
|
};
|
|
420
|
-
const drag = (
|
|
1533
|
+
const drag = (picked) => {
|
|
1534
|
+
const next = mjmlColor(picked) ?? picked;
|
|
421
1535
|
setText(next);
|
|
422
1536
|
if (!pressed.current) return commit(next);
|
|
423
1537
|
dragged.current = next;
|
|
@@ -432,29 +1546,30 @@ function ColorField({
|
|
|
432
1546
|
} catch {
|
|
433
1547
|
}
|
|
434
1548
|
};
|
|
435
|
-
return /* @__PURE__ */
|
|
1549
|
+
return /* @__PURE__ */ jsxs3(
|
|
436
1550
|
Popover,
|
|
437
1551
|
{
|
|
438
1552
|
open,
|
|
439
1553
|
onOpenChange: (next) => {
|
|
440
|
-
if (next) setUsed(
|
|
1554
|
+
if (next) setUsed(documentColors2());
|
|
441
1555
|
else if (text.trim() !== value) commit(text.trim());
|
|
442
1556
|
setOpen(next);
|
|
443
1557
|
},
|
|
444
1558
|
children: [
|
|
445
|
-
/* @__PURE__ */
|
|
446
|
-
/* @__PURE__ */
|
|
1559
|
+
/* @__PURE__ */ jsx4(PopoverAnchor, { virtualRef: anchor }),
|
|
1560
|
+
/* @__PURE__ */ jsxs3(
|
|
447
1561
|
"div",
|
|
448
1562
|
{
|
|
449
1563
|
ref: fieldRef,
|
|
450
1564
|
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
1565
|
children: [
|
|
452
|
-
/* @__PURE__ */
|
|
1566
|
+
/* @__PURE__ */ jsx4(
|
|
453
1567
|
"input",
|
|
454
1568
|
{
|
|
455
1569
|
value: text,
|
|
456
1570
|
placeholder,
|
|
457
1571
|
spellCheck: false,
|
|
1572
|
+
...field,
|
|
458
1573
|
onChange: (e) => setText(e.target.value),
|
|
459
1574
|
onBlur: () => text !== value && commit(text.trim()),
|
|
460
1575
|
onKeyDown: (e) => {
|
|
@@ -464,7 +1579,7 @@ function ColorField({
|
|
|
464
1579
|
className: "min-w-0 flex-1 bg-transparent font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg"
|
|
465
1580
|
}
|
|
466
1581
|
),
|
|
467
|
-
/* @__PURE__ */
|
|
1582
|
+
/* @__PURE__ */ jsx4(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
|
|
468
1583
|
"button",
|
|
469
1584
|
{
|
|
470
1585
|
type: "button",
|
|
@@ -472,15 +1587,15 @@ function ColorField({
|
|
|
472
1587
|
"aria-label": labels.colorPickerOpen,
|
|
473
1588
|
className: "relative size-5 shrink-0 overflow-hidden rounded border border-white/20 outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
474
1589
|
children: [
|
|
475
|
-
/* @__PURE__ */
|
|
476
|
-
|
|
1590
|
+
/* @__PURE__ */ jsx4(Checkerboard, {}),
|
|
1591
|
+
mjmlColor(shown) && /* @__PURE__ */ jsx4("span", { className: "absolute inset-0", style: { background: shown } })
|
|
477
1592
|
]
|
|
478
1593
|
}
|
|
479
1594
|
) })
|
|
480
1595
|
]
|
|
481
1596
|
}
|
|
482
1597
|
),
|
|
483
|
-
/* @__PURE__ */
|
|
1598
|
+
/* @__PURE__ */ jsxs3(
|
|
484
1599
|
PopoverContent,
|
|
485
1600
|
{
|
|
486
1601
|
side: "right",
|
|
@@ -488,36 +1603,37 @@ function ColorField({
|
|
|
488
1603
|
sideOffset: 8,
|
|
489
1604
|
className: "editor-dark flex w-52 flex-col gap-2.5 border border-panel-border bg-panel p-2.5 text-panel-fg",
|
|
490
1605
|
children: [
|
|
491
|
-
/* @__PURE__ */
|
|
1606
|
+
/* @__PURE__ */ jsx4(
|
|
492
1607
|
"div",
|
|
493
1608
|
{
|
|
494
1609
|
onPointerDownCapture: (e) => {
|
|
495
1610
|
pressed.current = true;
|
|
496
1611
|
e.target.setPointerCapture(e.pointerId);
|
|
497
1612
|
},
|
|
498
|
-
children: translucent ? /* @__PURE__ */
|
|
1613
|
+
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
1614
|
}
|
|
500
1615
|
),
|
|
501
|
-
/* @__PURE__ */
|
|
502
|
-
/* @__PURE__ */
|
|
1616
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
|
|
1617
|
+
/* @__PURE__ */ jsx4(
|
|
503
1618
|
"span",
|
|
504
1619
|
{
|
|
505
1620
|
className: "size-6 shrink-0 rounded border border-white/20",
|
|
506
1621
|
style: { background: hex }
|
|
507
1622
|
}
|
|
508
1623
|
),
|
|
509
|
-
/* @__PURE__ */
|
|
1624
|
+
/* @__PURE__ */ jsx4(
|
|
510
1625
|
"input",
|
|
511
1626
|
{
|
|
512
1627
|
value: text,
|
|
513
1628
|
spellCheck: false,
|
|
1629
|
+
...field,
|
|
514
1630
|
onChange: (e) => setText(e.target.value),
|
|
515
1631
|
onKeyDown: (e) => e.key === "Enter" && commit(text.trim()),
|
|
516
1632
|
onBlur: () => text !== value && commit(text.trim()),
|
|
517
1633
|
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
1634
|
}
|
|
519
1635
|
),
|
|
520
|
-
eyeDropper() && /* @__PURE__ */
|
|
1636
|
+
eyeDropper() && /* @__PURE__ */ jsx4(
|
|
521
1637
|
"button",
|
|
522
1638
|
{
|
|
523
1639
|
type: "button",
|
|
@@ -525,11 +1641,11 @@ function ColorField({
|
|
|
525
1641
|
title: labels.colorPickerEyedropper,
|
|
526
1642
|
"aria-label": labels.colorPickerEyedropper,
|
|
527
1643
|
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__ */
|
|
1644
|
+
children: /* @__PURE__ */ jsx4(Pipette, { className: "size-3" })
|
|
529
1645
|
}
|
|
530
1646
|
)
|
|
531
1647
|
] }),
|
|
532
|
-
used.length > 0 && /* @__PURE__ */
|
|
1648
|
+
used.length > 0 && /* @__PURE__ */ jsx4(
|
|
533
1649
|
Swatches,
|
|
534
1650
|
{
|
|
535
1651
|
title: labels.colorPickerDocument,
|
|
@@ -538,7 +1654,7 @@ function ColorField({
|
|
|
538
1654
|
onPick: commit
|
|
539
1655
|
}
|
|
540
1656
|
),
|
|
541
|
-
/* @__PURE__ */
|
|
1657
|
+
/* @__PURE__ */ jsx4(
|
|
542
1658
|
Swatches,
|
|
543
1659
|
{
|
|
544
1660
|
title: labels.colorPickerPresets,
|
|
@@ -562,10 +1678,10 @@ function Swatches({
|
|
|
562
1678
|
onPick,
|
|
563
1679
|
transparentLabel
|
|
564
1680
|
}) {
|
|
565
|
-
return /* @__PURE__ */
|
|
566
|
-
/* @__PURE__ */
|
|
567
|
-
/* @__PURE__ */
|
|
568
|
-
colors.map((color) => /* @__PURE__ */
|
|
1681
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
1682
|
+
/* @__PURE__ */ jsx4("p", { className: "mb-1 text-[10px] font-medium tracking-wide text-panel-muted-fg uppercase", children: title }),
|
|
1683
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-8 gap-1", children: [
|
|
1684
|
+
colors.map((color) => /* @__PURE__ */ jsx4(
|
|
569
1685
|
"button",
|
|
570
1686
|
{
|
|
571
1687
|
type: "button",
|
|
@@ -579,21 +1695,21 @@ function Swatches({
|
|
|
579
1695
|
},
|
|
580
1696
|
color
|
|
581
1697
|
)),
|
|
582
|
-
transparentLabel && /* @__PURE__ */
|
|
1698
|
+
transparentLabel && /* @__PURE__ */ jsx4(
|
|
583
1699
|
"button",
|
|
584
1700
|
{
|
|
585
1701
|
type: "button",
|
|
586
1702
|
title: transparentLabel,
|
|
587
1703
|
onClick: () => onPick("transparent"),
|
|
588
1704
|
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__ */
|
|
1705
|
+
children: /* @__PURE__ */ jsx4(Checkerboard, {})
|
|
590
1706
|
}
|
|
591
1707
|
)
|
|
592
1708
|
] })
|
|
593
1709
|
] });
|
|
594
1710
|
}
|
|
595
1711
|
function Checkerboard() {
|
|
596
|
-
return /* @__PURE__ */
|
|
1712
|
+
return /* @__PURE__ */ jsx4(
|
|
597
1713
|
"span",
|
|
598
1714
|
{
|
|
599
1715
|
className: "absolute inset-0",
|
|
@@ -604,6 +1720,16 @@ function Checkerboard() {
|
|
|
604
1720
|
}
|
|
605
1721
|
);
|
|
606
1722
|
}
|
|
1723
|
+
function mjmlColor(value) {
|
|
1724
|
+
const v = value.trim();
|
|
1725
|
+
if (v === "") return "";
|
|
1726
|
+
if (NAMED_COLORS.has(v.toLowerCase())) return v.toLowerCase();
|
|
1727
|
+
const hex = toHex(v);
|
|
1728
|
+
if (!hex) return null;
|
|
1729
|
+
if (hex.length === 7) return hex;
|
|
1730
|
+
const [r, g, b, a] = [1, 3, 5, 7].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
1731
|
+
return `rgba(${r},${g},${b},${Number((a / 255).toFixed(2))})`;
|
|
1732
|
+
}
|
|
607
1733
|
function toHex(value, { opaque = false } = {}) {
|
|
608
1734
|
const v = value.trim().toLowerCase();
|
|
609
1735
|
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(v);
|
|
@@ -623,7 +1749,7 @@ function toHex(value, { opaque = false } = {}) {
|
|
|
623
1749
|
|
|
624
1750
|
// src/components/editor/ImageField.tsx
|
|
625
1751
|
import { ImageIcon, X } from "lucide-react";
|
|
626
|
-
import { jsx as
|
|
1752
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
627
1753
|
function ImageField({
|
|
628
1754
|
value,
|
|
629
1755
|
labels,
|
|
@@ -631,8 +1757,8 @@ function ImageField({
|
|
|
631
1757
|
onClear
|
|
632
1758
|
}) {
|
|
633
1759
|
const url = imageUrl(value);
|
|
634
|
-
return /* @__PURE__ */
|
|
635
|
-
/* @__PURE__ */
|
|
1760
|
+
return /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
|
|
1761
|
+
/* @__PURE__ */ jsxs4(
|
|
636
1762
|
"button",
|
|
637
1763
|
{
|
|
638
1764
|
type: "button",
|
|
@@ -640,15 +1766,15 @@ function ImageField({
|
|
|
640
1766
|
title: labels.chooseImage,
|
|
641
1767
|
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
1768
|
children: [
|
|
643
|
-
url ? /* @__PURE__ */
|
|
644
|
-
/* @__PURE__ */
|
|
1769
|
+
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: [
|
|
1770
|
+
/* @__PURE__ */ jsx5(ImageIcon, { className: "size-5" }),
|
|
645
1771
|
labels.chooseImage
|
|
646
1772
|
] }),
|
|
647
|
-
url && /* @__PURE__ */
|
|
1773
|
+
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
1774
|
]
|
|
649
1775
|
}
|
|
650
1776
|
),
|
|
651
|
-
value && /* @__PURE__ */
|
|
1777
|
+
value && /* @__PURE__ */ jsx5(
|
|
652
1778
|
"button",
|
|
653
1779
|
{
|
|
654
1780
|
type: "button",
|
|
@@ -656,7 +1782,7 @@ function ImageField({
|
|
|
656
1782
|
title: labels.imageFieldRemove,
|
|
657
1783
|
"aria-label": labels.imageFieldRemove,
|
|
658
1784
|
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__ */
|
|
1785
|
+
children: /* @__PURE__ */ jsx5(X, { className: "size-3" })
|
|
660
1786
|
}
|
|
661
1787
|
)
|
|
662
1788
|
] });
|
|
@@ -669,44 +1795,1530 @@ function imageUrl(value) {
|
|
|
669
1795
|
return /^[a-z-]+\(/i.test(v) ? null : v;
|
|
670
1796
|
}
|
|
671
1797
|
|
|
672
|
-
// src/components/editor/
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
var
|
|
676
|
-
var
|
|
677
|
-
var
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
|
|
1798
|
+
// src/components/editor/inspector/schema.ts
|
|
1799
|
+
var PX = ["px"];
|
|
1800
|
+
var PX_PCT = ["px", "%"];
|
|
1801
|
+
var LINE_HEIGHT = ["", "px", "%"];
|
|
1802
|
+
var LETTER_SPACING = ["px", "em"];
|
|
1803
|
+
var ALIGN = [
|
|
1804
|
+
{ value: "left", label: "alignLeft" },
|
|
1805
|
+
{ value: "center", label: "alignCenter" },
|
|
1806
|
+
{ value: "right", label: "alignRight" }
|
|
1807
|
+
];
|
|
1808
|
+
var VERTICAL_ALIGN = [
|
|
1809
|
+
{ value: "top", label: "alignTop" },
|
|
1810
|
+
{ value: "middle", label: "alignMiddle" },
|
|
1811
|
+
{ value: "bottom", label: "alignBottom" }
|
|
1812
|
+
];
|
|
1813
|
+
var FONTS = [
|
|
1814
|
+
{ value: "Ubuntu, Helvetica, Arial, sans-serif", label: "Ubuntu" },
|
|
1815
|
+
{ value: "Arial, Helvetica, sans-serif", label: "Arial" },
|
|
1816
|
+
{ value: "Helvetica, Arial, sans-serif", label: "Helvetica" },
|
|
1817
|
+
{ value: "Verdana, Geneva, sans-serif", label: "Verdana" },
|
|
1818
|
+
{ value: "Tahoma, Geneva, sans-serif", label: "Tahoma" },
|
|
1819
|
+
{ value: "'Trebuchet MS', Helvetica, sans-serif", label: "Trebuchet MS" },
|
|
1820
|
+
{ value: "Georgia, 'Times New Roman', serif", label: "Georgia" },
|
|
1821
|
+
{ value: "'Times New Roman', Times, serif", label: "Times New Roman" },
|
|
1822
|
+
{ value: "'Courier New', Courier, monospace", label: "Courier New" }
|
|
1823
|
+
];
|
|
1824
|
+
var WEIGHTS = [
|
|
1825
|
+
{ value: "normal", label: "weightRegular" },
|
|
1826
|
+
{ value: "500", label: "weightMedium" },
|
|
1827
|
+
{ value: "600", label: "weightSemibold" },
|
|
1828
|
+
{ value: "bold", label: "weightBold" }
|
|
1829
|
+
];
|
|
1830
|
+
var TEXT_TRANSFORM = [
|
|
1831
|
+
{ value: "none", label: "caseNone" },
|
|
1832
|
+
{ value: "uppercase", label: "caseUpper" },
|
|
1833
|
+
{ value: "lowercase", label: "caseLower" },
|
|
1834
|
+
{ value: "capitalize", label: "caseCapitalize" }
|
|
1835
|
+
];
|
|
1836
|
+
var TARGET = [
|
|
1837
|
+
{ value: "_blank", label: "targetNewTab" },
|
|
1838
|
+
{ value: "_self", label: "targetSameTab" }
|
|
1839
|
+
];
|
|
1840
|
+
var BACKGROUND_POSITION = [
|
|
1841
|
+
{ value: "top left", label: "positionTopLeft" },
|
|
1842
|
+
{ value: "top center", label: "positionTopCenter" },
|
|
1843
|
+
{ value: "top right", label: "positionTopRight" },
|
|
1844
|
+
{ value: "center left", label: "positionCenterLeft" },
|
|
1845
|
+
{ value: "center center", label: "positionCenter" },
|
|
1846
|
+
{ value: "center right", label: "positionCenterRight" },
|
|
1847
|
+
{ value: "bottom left", label: "positionBottomLeft" },
|
|
1848
|
+
{ value: "bottom center", label: "positionBottomCenter" },
|
|
1849
|
+
{ value: "bottom right", label: "positionBottomRight" }
|
|
1850
|
+
];
|
|
1851
|
+
var NETWORKS = [
|
|
1852
|
+
{ value: "facebook", label: "Facebook" },
|
|
1853
|
+
{ value: "instagram", label: "Instagram" },
|
|
1854
|
+
{ value: "x", label: "X" },
|
|
1855
|
+
{ value: "linkedin", label: "LinkedIn" },
|
|
1856
|
+
{ value: "youtube", label: "YouTube" },
|
|
1857
|
+
{ value: "pinterest", label: "Pinterest" },
|
|
1858
|
+
{ value: "github", label: "GitHub" },
|
|
1859
|
+
{ value: "web", label: "networkWebsite" },
|
|
1860
|
+
{ value: "snapchat", label: "Snapchat" },
|
|
1861
|
+
{ value: "vimeo", label: "Vimeo" },
|
|
1862
|
+
{ value: "medium", label: "Medium" },
|
|
1863
|
+
{ value: "dribbble", label: "Dribbble" },
|
|
1864
|
+
{ value: "soundcloud", label: "SoundCloud" },
|
|
1865
|
+
{ value: "tumblr", label: "Tumblr" },
|
|
1866
|
+
{ value: "xing", label: "Xing" },
|
|
1867
|
+
{ value: "custom", label: "networkCustom" }
|
|
1868
|
+
];
|
|
1869
|
+
var hasBackgroundImage = (a) => !!a["background-url"];
|
|
1870
|
+
var SECTION_FIELDS = [
|
|
1871
|
+
{
|
|
1872
|
+
attr: "full-width",
|
|
1873
|
+
label: "fullWidth",
|
|
1874
|
+
group: "layout",
|
|
1875
|
+
control: { type: "toggle", on: "full-width" }
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
attr: "padding",
|
|
1879
|
+
label: "padding",
|
|
1880
|
+
group: "spacing",
|
|
1881
|
+
control: { type: "box", units: PX_PCT },
|
|
1882
|
+
default: "20px 0"
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
attr: "background-color",
|
|
1886
|
+
label: "backgroundColor",
|
|
1887
|
+
group: "style",
|
|
1888
|
+
control: { type: "color" }
|
|
1889
|
+
},
|
|
1890
|
+
{ attr: "background-url", label: "backgroundImage", group: "style", control: { type: "image" } },
|
|
1891
|
+
{
|
|
1892
|
+
attr: "background-size",
|
|
1893
|
+
label: "backgroundSize",
|
|
1894
|
+
group: "style",
|
|
1895
|
+
control: {
|
|
1896
|
+
type: "segmented",
|
|
1897
|
+
options: [
|
|
1898
|
+
{ value: "cover", label: "sizeCover" },
|
|
1899
|
+
{ value: "contain", label: "sizeContain" },
|
|
1900
|
+
{ value: "auto", label: "sizeAuto" }
|
|
1901
|
+
]
|
|
1902
|
+
},
|
|
1903
|
+
default: "auto",
|
|
1904
|
+
when: hasBackgroundImage
|
|
1905
|
+
},
|
|
1906
|
+
{
|
|
1907
|
+
attr: "background-repeat",
|
|
1908
|
+
label: "backgroundRepeat",
|
|
1909
|
+
group: "style",
|
|
1910
|
+
control: {
|
|
1911
|
+
type: "segmented",
|
|
1912
|
+
options: [
|
|
1913
|
+
{ value: "repeat", label: "repeatTile" },
|
|
1914
|
+
{ value: "no-repeat", label: "repeatOnce" }
|
|
1915
|
+
]
|
|
1916
|
+
},
|
|
1917
|
+
default: "repeat",
|
|
1918
|
+
when: hasBackgroundImage
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
attr: "background-position",
|
|
1922
|
+
label: "backgroundPosition",
|
|
1923
|
+
group: "style",
|
|
1924
|
+
control: { type: "select", options: BACKGROUND_POSITION },
|
|
1925
|
+
default: "top center",
|
|
1926
|
+
when: hasBackgroundImage
|
|
1927
|
+
},
|
|
1928
|
+
{
|
|
1929
|
+
attr: "border-radius",
|
|
1930
|
+
label: "cornerRadius",
|
|
1931
|
+
group: "style",
|
|
1932
|
+
control: { type: "length", units: PX_PCT }
|
|
1933
|
+
}
|
|
1934
|
+
];
|
|
1935
|
+
var SCHEMA = {
|
|
1936
|
+
"mj-body": [
|
|
1937
|
+
{ attr: "width", label: "contentWidth", group: "layout", control: { type: "length", units: PX }, default: "600px" },
|
|
1938
|
+
{ attr: "background-color", label: "pageBackground", group: "style", control: { type: "color" } }
|
|
1939
|
+
],
|
|
1940
|
+
"mj-wrapper": [
|
|
1941
|
+
...SECTION_FIELDS,
|
|
1942
|
+
{ attr: "gap", label: "gap", group: "advanced", control: { type: "length", units: PX } },
|
|
1943
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
1944
|
+
],
|
|
1945
|
+
"mj-section": [
|
|
1946
|
+
...SECTION_FIELDS,
|
|
1947
|
+
{ attr: "text-align", label: "alignColumns", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1948
|
+
{ attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } },
|
|
1949
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
1950
|
+
],
|
|
1951
|
+
"mj-column": [
|
|
1952
|
+
{ attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
|
|
1953
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
1954
|
+
{ attr: "background-color", label: "backgroundColor", group: "style", control: { type: "color" } },
|
|
1955
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
1956
|
+
{ attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
1957
|
+
{ attr: "inner-background-color", label: "innerBackground", group: "advanced", control: { type: "color" } },
|
|
1958
|
+
{ attr: "inner-border-radius", label: "innerCornerRadius", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
1959
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } },
|
|
1960
|
+
{ attr: "inner-border", label: "innerBorder", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
|
|
1961
|
+
],
|
|
1962
|
+
"mj-text": [
|
|
1963
|
+
{
|
|
1964
|
+
attr: "align",
|
|
1965
|
+
label: "align",
|
|
1966
|
+
group: "layout",
|
|
1967
|
+
control: { type: "segmented", options: [...ALIGN, { value: "justify", label: "alignJustify" }] },
|
|
1968
|
+
default: "left"
|
|
1969
|
+
},
|
|
1970
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1971
|
+
{ attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
1972
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
1973
|
+
{ attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS } },
|
|
1974
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
1975
|
+
{ attr: "line-height", label: "lineHeight", group: "style", control: { type: "length", units: LINE_HEIGHT }, default: "1" },
|
|
1976
|
+
{ attr: "container-background-color", label: "blockBackground", group: "style", control: { type: "color" } },
|
|
1977
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
1978
|
+
{ attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM } }
|
|
1979
|
+
],
|
|
1980
|
+
"mj-button": [
|
|
1981
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
1982
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
1983
|
+
{ attr: "inner-padding", label: "buttonPadding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1984
|
+
{ attr: "padding", label: "spaceAround", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
1985
|
+
{ attr: "background-color", label: "buttonColor", group: "style", control: { type: "color" }, default: "#414141" },
|
|
1986
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#ffffff" },
|
|
1987
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
1988
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
|
|
1989
|
+
{ attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
1990
|
+
{ attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
1991
|
+
{ attr: "font-weight", label: "fontWeight", group: "advanced", control: { type: "select", options: WEIGHTS }, default: "normal" },
|
|
1992
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
1993
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
1994
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
1995
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } },
|
|
1996
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "none" },
|
|
1997
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
1998
|
+
{ attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM }, default: "none" },
|
|
1999
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "120%" }
|
|
2000
|
+
],
|
|
2001
|
+
"mj-image": [
|
|
2002
|
+
{ attr: "src", label: "image", group: "content", control: { type: "image" } },
|
|
2003
|
+
{ attr: "alt", label: "altText", group: "content", control: { type: "text" } },
|
|
2004
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2005
|
+
{ attr: "width", label: "width", group: "layout", control: { type: "length", units: PX } },
|
|
2006
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2007
|
+
{ attr: "fluid-on-mobile", label: "fluidOnMobile", group: "layout", control: { type: "toggle", on: "true" } },
|
|
2008
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2009
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
2010
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2011
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
2012
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
2013
|
+
{ attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "0" },
|
|
2014
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2015
|
+
],
|
|
2016
|
+
"mj-divider": [
|
|
2017
|
+
{ attr: "width", label: "width", group: "layout", control: { type: "length", units: PX_PCT }, default: "100%" },
|
|
2018
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2019
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2020
|
+
{ attr: "border-color", label: "lineColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
2021
|
+
{ attr: "border-width", label: "thickness", group: "style", control: { type: "length", units: PX }, default: "4px" },
|
|
2022
|
+
{
|
|
2023
|
+
attr: "border-style",
|
|
2024
|
+
label: "lineStyle",
|
|
2025
|
+
group: "style",
|
|
2026
|
+
control: {
|
|
2027
|
+
type: "segmented",
|
|
2028
|
+
options: [
|
|
2029
|
+
{ value: "solid", label: "lineSolid" },
|
|
2030
|
+
{ value: "dashed", label: "lineDashed" },
|
|
2031
|
+
{ value: "dotted", label: "lineDotted" }
|
|
2032
|
+
]
|
|
2033
|
+
},
|
|
2034
|
+
default: "solid"
|
|
2035
|
+
},
|
|
2036
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2037
|
+
],
|
|
2038
|
+
"mj-spacer": [
|
|
2039
|
+
{ attr: "height", label: "height", group: "layout", control: { type: "length", units: PX }, default: "20px" },
|
|
2040
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2041
|
+
],
|
|
2042
|
+
"mj-social": [
|
|
2043
|
+
{
|
|
2044
|
+
attr: "mode",
|
|
2045
|
+
label: "socialMode",
|
|
2046
|
+
group: "layout",
|
|
2047
|
+
control: {
|
|
2048
|
+
type: "segmented",
|
|
2049
|
+
options: [
|
|
2050
|
+
{ value: "horizontal", label: "modeHorizontal" },
|
|
2051
|
+
{ value: "vertical", label: "modeVertical" }
|
|
2052
|
+
]
|
|
2053
|
+
},
|
|
2054
|
+
default: "horizontal"
|
|
2055
|
+
},
|
|
2056
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2057
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
|
|
2058
|
+
{ attr: "inner-padding", label: "iconSpacing", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
2059
|
+
{ attr: "icon-size", label: "iconSize", group: "style", control: { type: "length", units: PX_PCT }, default: "20px" },
|
|
2060
|
+
// A single value: mj-social's border-radius is not a 4-side shorthand.
|
|
2061
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
|
|
2062
|
+
{ attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
2063
|
+
{ attr: "font-size", label: "fontSize", group: "advanced", control: { type: "length", units: PX }, default: "13px" },
|
|
2064
|
+
{ attr: "color", label: "textColor", group: "advanced", control: { type: "color" }, default: "#333333" },
|
|
2065
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" },
|
|
2066
|
+
{ attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
|
|
2067
|
+
],
|
|
2068
|
+
"mj-social-element": [
|
|
2069
|
+
{ attr: "name", label: "network", group: "content", control: { type: "network", networks: NETWORKS } },
|
|
2070
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2071
|
+
{
|
|
2072
|
+
attr: "src",
|
|
2073
|
+
label: "customIcon",
|
|
2074
|
+
group: "content",
|
|
2075
|
+
control: { type: "image" },
|
|
2076
|
+
when: (a) => (a.name ?? "").startsWith("custom") || !!a.src
|
|
2077
|
+
},
|
|
2078
|
+
{ attr: "alt", label: "altText", group: "content", control: { type: "text" } },
|
|
2079
|
+
{ attr: "background-color", label: "iconColor", group: "style", control: { type: "color" } },
|
|
2080
|
+
{ attr: "icon-size", label: "iconSize", group: "advanced", control: { type: "length", units: PX_PCT } },
|
|
2081
|
+
{ attr: "border-radius", label: "cornerRadius", group: "advanced", control: { type: "length", units: PX }, default: "3px" },
|
|
2082
|
+
{ attr: "padding", label: "padding", group: "advanced", control: { type: "box", units: PX_PCT }, default: "4px" },
|
|
2083
|
+
{ attr: "icon-position", label: "iconPosition", group: "advanced", control: { type: "segmented", options: [{ value: "left", label: "alignLeft" }, { value: "right", label: "alignRight" }] }, default: "left" },
|
|
2084
|
+
{ attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
|
|
2085
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2086
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } }
|
|
2087
|
+
],
|
|
2088
|
+
"mj-hero": [
|
|
2089
|
+
{ attr: "background-url", label: "backgroundImage", group: "content", control: { type: "image" } },
|
|
2090
|
+
{
|
|
2091
|
+
attr: "mode",
|
|
2092
|
+
label: "heroMode",
|
|
2093
|
+
group: "content",
|
|
2094
|
+
control: {
|
|
2095
|
+
type: "segmented",
|
|
2096
|
+
options: [
|
|
2097
|
+
{ value: "fixed-height", label: "heroFixed" },
|
|
2098
|
+
{ value: "fluid-height", label: "heroFluid" }
|
|
2099
|
+
]
|
|
2100
|
+
},
|
|
2101
|
+
default: "fixed-height"
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
attr: "height",
|
|
2105
|
+
label: "height",
|
|
2106
|
+
group: "layout",
|
|
2107
|
+
control: { type: "length", units: PX_PCT },
|
|
2108
|
+
default: "0px",
|
|
2109
|
+
when: (a) => (a.mode ?? "fixed-height") === "fixed-height"
|
|
2110
|
+
},
|
|
2111
|
+
{
|
|
2112
|
+
attr: "background-width",
|
|
2113
|
+
label: "backgroundWidth",
|
|
2114
|
+
group: "layout",
|
|
2115
|
+
control: { type: "length", units: PX_PCT },
|
|
2116
|
+
when: (a) => a.mode === "fluid-height"
|
|
2117
|
+
},
|
|
2118
|
+
{
|
|
2119
|
+
attr: "background-height",
|
|
2120
|
+
label: "backgroundHeight",
|
|
2121
|
+
group: "layout",
|
|
2122
|
+
control: { type: "length", units: PX_PCT },
|
|
2123
|
+
when: (a) => a.mode === "fluid-height"
|
|
2124
|
+
},
|
|
2125
|
+
{ attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
|
|
2126
|
+
{ attr: "background-position", label: "backgroundPosition", group: "layout", control: { type: "select", options: BACKGROUND_POSITION }, default: "center center" },
|
|
2127
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "0px" },
|
|
2128
|
+
{ attr: "background-color", label: "fallbackColor", group: "style", control: { type: "color" }, default: "#ffffff" },
|
|
2129
|
+
{ attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
|
|
2130
|
+
{ attr: "inner-padding", label: "contentPadding", group: "advanced", control: { type: "box", units: PX_PCT } },
|
|
2131
|
+
{ attr: "inner-background-color", label: "contentBackground", group: "advanced", control: { type: "color" } }
|
|
2132
|
+
],
|
|
2133
|
+
"mj-navbar": [
|
|
2134
|
+
{ attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
|
|
2135
|
+
{ attr: "hamburger", label: "hamburger", group: "layout", control: { type: "toggle", on: "hamburger" } },
|
|
2136
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
|
|
2137
|
+
{ attr: "ico-color", label: "menuIconColor", group: "advanced", control: { type: "color" }, default: "#000000", when: (a) => !!a.hamburger },
|
|
2138
|
+
{ attr: "ico-font-size", label: "menuIconSize", group: "advanced", control: { type: "length", units: PX_PCT }, default: "30px", when: (a) => !!a.hamburger },
|
|
2139
|
+
{ attr: "ico-align", label: "menuIconAlign", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center", when: (a) => !!a.hamburger },
|
|
2140
|
+
{ attr: "base-url", label: "baseUrl", group: "advanced", control: { type: "link" } }
|
|
2141
|
+
],
|
|
2142
|
+
"mj-navbar-link": [
|
|
2143
|
+
{ attr: "href", label: "link", group: "content", control: { type: "link" } },
|
|
2144
|
+
{ attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "15px 10px" },
|
|
2145
|
+
{ attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
|
|
2146
|
+
{ attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
|
|
2147
|
+
{ attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
|
|
2148
|
+
{ attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS }, default: "normal" },
|
|
2149
|
+
{ attr: "text-transform", label: "textTransform", group: "style", control: { type: "select", options: TEXT_TRANSFORM }, default: "uppercase" },
|
|
2150
|
+
{ attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
|
|
2151
|
+
{ attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
|
|
2152
|
+
{ attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
|
|
2153
|
+
{ attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" }
|
|
2154
|
+
]
|
|
2155
|
+
};
|
|
2156
|
+
|
|
2157
|
+
// src/components/editor/layers.ts
|
|
2158
|
+
var svg = (paths) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
|
|
2159
|
+
var ICONS = {
|
|
2160
|
+
"mj-wrapper": svg(
|
|
2161
|
+
'<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"/>'
|
|
2162
|
+
),
|
|
2163
|
+
"mj-section": svg('<rect width="20" height="12" x="2" y="6" rx="2"/>'),
|
|
2164
|
+
"mj-hero": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/>'),
|
|
2165
|
+
"mj-group": svg(
|
|
2166
|
+
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>'
|
|
2167
|
+
),
|
|
2168
|
+
"mj-column": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M12 3v18"/>'),
|
|
2169
|
+
"mj-text": svg('<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>'),
|
|
2170
|
+
"mj-image": svg(
|
|
2171
|
+
'<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"/>'
|
|
2172
|
+
),
|
|
2173
|
+
"mj-button": svg('<rect width="20" height="10" x="2" y="7" rx="3"/><path d="M8 12h8"/>'),
|
|
2174
|
+
"mj-divider": svg('<path d="M5 12h14"/>'),
|
|
2175
|
+
"mj-spacer": svg('<path d="M12 3v18"/><path d="m8 17 4 4 4-4"/><path d="m8 7 4-4 4 4"/>'),
|
|
2176
|
+
"mj-social": svg(
|
|
2177
|
+
'<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"/>'
|
|
2178
|
+
),
|
|
2179
|
+
"mj-social-element": svg(
|
|
2180
|
+
'<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/>'
|
|
2181
|
+
),
|
|
2182
|
+
"mj-navbar": svg('<path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/>'),
|
|
2183
|
+
"mj-navbar-link": svg(
|
|
2184
|
+
'<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"/>'
|
|
2185
|
+
),
|
|
2186
|
+
"mj-raw": svg('<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
|
|
2187
|
+
"mj-table": svg(
|
|
2188
|
+
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M12 3v18"/>'
|
|
2189
|
+
)
|
|
682
2190
|
};
|
|
683
2191
|
function tagOf(c) {
|
|
684
2192
|
return String(c.get("tagName") || c.get("type") || "");
|
|
685
2193
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
2194
|
+
function textOf(c) {
|
|
2195
|
+
const template = document.createElement("template");
|
|
2196
|
+
template.innerHTML = c.getInnerHTML();
|
|
2197
|
+
template.content.querySelectorAll("br, p, div, li, tr, h1, h2, h3, h4, h5, h6").forEach((el) => el.after("\n"));
|
|
2198
|
+
const lines = (template.content.textContent ?? "").split("\n");
|
|
2199
|
+
const first = lines.map((l) => l.replace(/\s+/g, " ").trim()).find(Boolean) ?? "";
|
|
2200
|
+
return first.slice(0, 60);
|
|
690
2201
|
}
|
|
691
|
-
function
|
|
692
|
-
|
|
693
|
-
return t === "mj-section" || t in TYPE_LABEL;
|
|
2202
|
+
function escapeHtml(s) {
|
|
2203
|
+
return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
694
2204
|
}
|
|
695
|
-
function
|
|
696
|
-
const
|
|
697
|
-
|
|
2205
|
+
function layerName(c, labels) {
|
|
2206
|
+
const tag = tagOf(c);
|
|
2207
|
+
const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
2208
|
+
const attributes = c.getAttributes();
|
|
2209
|
+
switch (tag) {
|
|
2210
|
+
case "mj-text":
|
|
2211
|
+
case "mj-button":
|
|
2212
|
+
case "mj-navbar-link":
|
|
2213
|
+
return textOf(c) || kind;
|
|
2214
|
+
case "mj-image":
|
|
2215
|
+
return attributes.alt?.trim() || kind;
|
|
2216
|
+
case "mj-social-element": {
|
|
2217
|
+
const { network } = parseNetwork(String(attributes.name ?? ""));
|
|
2218
|
+
const option = NETWORKS.find((o) => o.value === network && network !== "custom");
|
|
2219
|
+
return textOf(c) || option && (labels.inspectorLabels[option.label] ?? option.label) || kind;
|
|
2220
|
+
}
|
|
2221
|
+
case "mj-section":
|
|
2222
|
+
case "mj-wrapper":
|
|
2223
|
+
case "mj-hero": {
|
|
2224
|
+
let words = "";
|
|
2225
|
+
c.onAll((d) => {
|
|
2226
|
+
if (!words && d !== c && ["mj-text", "mj-button"].includes(tagOf(d))) words = textOf(d);
|
|
2227
|
+
});
|
|
2228
|
+
return words || kind;
|
|
2229
|
+
}
|
|
2230
|
+
case "mj-column": {
|
|
2231
|
+
const columns = c.parent()?.components().filter((s) => tagOf(s) === "mj-column") ?? [];
|
|
2232
|
+
return columns.length > 1 ? `${kind} ${columns.indexOf(c) + 1}` : kind;
|
|
2233
|
+
}
|
|
2234
|
+
default:
|
|
2235
|
+
return kind;
|
|
2236
|
+
}
|
|
698
2237
|
}
|
|
2238
|
+
var CONTENT = /* @__PURE__ */ new Set(["mj-text", "mj-button", "mj-navbar-link", "mj-social-element", "mj-raw"]);
|
|
2239
|
+
function badgeLabel(labels) {
|
|
2240
|
+
return (c) => {
|
|
2241
|
+
const tag = tagOf(c);
|
|
2242
|
+
const icon = ICONS[tag] ? `<div class="gjs-badge__icon">${ICONS[tag]}</div>` : "";
|
|
2243
|
+
const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
2244
|
+
return `${icon}<div class="gjs-badge__name">${escapeHtml(kind)}</div>`;
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
function setupLayerNames(editor, labels) {
|
|
2248
|
+
const hideContent = (c) => c.onAll((d) => d !== c && d.set("layerable", false, { avoidStore: true }));
|
|
2249
|
+
for (const type of editor.Components.getTypes()) {
|
|
2250
|
+
if (!type.id.startsWith("mj-")) continue;
|
|
2251
|
+
editor.Components.addType(type.id, {
|
|
2252
|
+
model: {
|
|
2253
|
+
// Ours whatever the element says: GrapesJS writes it into the row's markup as it is.
|
|
2254
|
+
getIcon() {
|
|
2255
|
+
return ICONS[type.id] ?? "";
|
|
2256
|
+
},
|
|
2257
|
+
// GrapesJS writes the name into the row's markup unescaped, and it is document text.
|
|
2258
|
+
getName() {
|
|
2259
|
+
return escapeHtml(layerName(this, labels));
|
|
2260
|
+
},
|
|
2261
|
+
...CONTENT.has(type.id) && {
|
|
2262
|
+
// Editing a text replaces its children, so they are hidden again as they arrive.
|
|
2263
|
+
init() {
|
|
2264
|
+
hideContent(this);
|
|
2265
|
+
this.listenTo(this.components(), "add reset", () => hideContent(this));
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
},
|
|
2269
|
+
// Runs the plugin's own `init` as well.
|
|
2270
|
+
extendFn: ["init"]
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
let pending = false;
|
|
2274
|
+
editor.on("update component:add component:remove", () => {
|
|
2275
|
+
if (pending) return;
|
|
2276
|
+
pending = true;
|
|
2277
|
+
requestAnimationFrame(() => {
|
|
2278
|
+
pending = false;
|
|
2279
|
+
editor.Layers.getRoot()?.onAll((c) => {
|
|
2280
|
+
const view = c.viewLayer;
|
|
2281
|
+
const el = view?.getInputName?.();
|
|
2282
|
+
const name = layerName(c, labels);
|
|
2283
|
+
if (!el || el.textContent === name) return;
|
|
2284
|
+
el.textContent = name;
|
|
2285
|
+
el.parentElement?.setAttribute("title", name);
|
|
2286
|
+
});
|
|
2287
|
+
});
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// src/components/editor/inspector/StructureSection.tsx
|
|
2292
|
+
import { AtSign, ChevronRight, Link2 as Link22, Plus } from "lucide-react";
|
|
2293
|
+
|
|
2294
|
+
// src/components/editor/inspector/structure.ts
|
|
2295
|
+
function tagOf2(c) {
|
|
2296
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2297
|
+
}
|
|
2298
|
+
var COLUMN_RATIOS = {
|
|
2299
|
+
2: [
|
|
2300
|
+
[1, 1],
|
|
2301
|
+
[1, 2],
|
|
2302
|
+
[2, 1]
|
|
2303
|
+
],
|
|
2304
|
+
3: [
|
|
2305
|
+
[1, 1, 1],
|
|
2306
|
+
[1, 2, 1]
|
|
2307
|
+
]
|
|
2308
|
+
};
|
|
2309
|
+
function columnsOf(section) {
|
|
2310
|
+
const children = section.components().models;
|
|
2311
|
+
return children.length > 0 && children.every((c) => tagOf2(c) === "mj-column") ? children : null;
|
|
2312
|
+
}
|
|
2313
|
+
function ratioWidths(ratio) {
|
|
2314
|
+
if (ratio.every((r) => r === ratio[0])) return null;
|
|
2315
|
+
const total = ratio.reduce((a, b) => a + b, 0);
|
|
2316
|
+
return ratio.map((r) => `${Math.round(r / total * 1e4) / 100}%`);
|
|
2317
|
+
}
|
|
2318
|
+
function currentRatio(columns) {
|
|
2319
|
+
const widths = columns.map((c) => String(c.getAttributes().width ?? ""));
|
|
2320
|
+
return COLUMN_RATIOS[columns.length]?.find((ratio) => {
|
|
2321
|
+
const want = ratioWidths(ratio);
|
|
2322
|
+
if (!want) return widths.every((w) => w === "");
|
|
2323
|
+
return widths.every(
|
|
2324
|
+
(w, i) => /%$/.test(w) && Math.abs(parseFloat(w) - parseFloat(want[i])) < 0.5
|
|
2325
|
+
);
|
|
2326
|
+
}) ?? null;
|
|
2327
|
+
}
|
|
2328
|
+
function setRatio(columns, ratio) {
|
|
2329
|
+
const widths = ratioWidths(ratio);
|
|
2330
|
+
columns.forEach((c, i) => {
|
|
2331
|
+
if (widths) c.addAttributes({ width: widths[i] });
|
|
2332
|
+
else c.removeAttributes(["width"]);
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
var PROFILE_URLS = {
|
|
2336
|
+
facebook: "https://facebook.com/",
|
|
2337
|
+
instagram: "https://instagram.com/",
|
|
2338
|
+
x: "https://x.com/",
|
|
2339
|
+
linkedin: "https://linkedin.com/",
|
|
2340
|
+
youtube: "https://youtube.com/",
|
|
2341
|
+
pinterest: "https://pinterest.com/",
|
|
2342
|
+
github: "https://github.com/"
|
|
2343
|
+
};
|
|
2344
|
+
function addNetwork(social) {
|
|
2345
|
+
const present = new Set(
|
|
2346
|
+
social.components().map((c) => String(c.getAttributes().name ?? "").replace(/-noshare$/, ""))
|
|
2347
|
+
);
|
|
2348
|
+
const network = Object.keys(PROFILE_URLS).find((n) => !present.has(n)) ?? "facebook";
|
|
2349
|
+
return social.append(
|
|
2350
|
+
`<mj-social-element name="${network}-noshare" href="${PROFILE_URLS[network]}"></mj-social-element>`
|
|
2351
|
+
)[0];
|
|
2352
|
+
}
|
|
2353
|
+
function addNavbarLink(navbar, text) {
|
|
2354
|
+
return navbar.append(`<mj-navbar-link>${escapeHtml2(text)}</mj-navbar-link>`)[0];
|
|
2355
|
+
}
|
|
2356
|
+
function escapeHtml2(s) {
|
|
2357
|
+
return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// src/components/editor/inspector/StructureSection.tsx
|
|
2361
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2362
|
+
function tagOf3(c) {
|
|
2363
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2364
|
+
}
|
|
2365
|
+
function StructureSection({
|
|
2366
|
+
editor,
|
|
2367
|
+
component,
|
|
2368
|
+
labels,
|
|
2369
|
+
locked
|
|
2370
|
+
}) {
|
|
2371
|
+
switch (tagOf3(component)) {
|
|
2372
|
+
case "mj-section":
|
|
2373
|
+
return /* @__PURE__ */ jsx6(ColumnWidths, { component, labels, locked });
|
|
2374
|
+
case "mj-social":
|
|
2375
|
+
return /* @__PURE__ */ jsx6(
|
|
2376
|
+
Children,
|
|
2377
|
+
{
|
|
2378
|
+
editor,
|
|
2379
|
+
component,
|
|
2380
|
+
labels,
|
|
2381
|
+
locked,
|
|
2382
|
+
title: labels.socialNetworks,
|
|
2383
|
+
add: labels.addNetwork,
|
|
2384
|
+
icon: /* @__PURE__ */ jsx6(AtSign, { className: "size-3.5 shrink-0" }),
|
|
2385
|
+
onAdd: () => editor.select(addNetwork(component))
|
|
2386
|
+
}
|
|
2387
|
+
);
|
|
2388
|
+
case "mj-navbar":
|
|
2389
|
+
return /* @__PURE__ */ jsx6(
|
|
2390
|
+
Children,
|
|
2391
|
+
{
|
|
2392
|
+
editor,
|
|
2393
|
+
component,
|
|
2394
|
+
labels,
|
|
2395
|
+
locked,
|
|
2396
|
+
title: labels.navbarLinks,
|
|
2397
|
+
add: labels.addLink,
|
|
2398
|
+
icon: /* @__PURE__ */ jsx6(Link22, { className: "size-3.5 shrink-0" }),
|
|
2399
|
+
onAdd: () => editor.select(addNavbarLink(component, labels.newLinkText))
|
|
2400
|
+
}
|
|
2401
|
+
);
|
|
2402
|
+
default:
|
|
2403
|
+
return null;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
function Heading({ children }) {
|
|
2407
|
+
return /* @__PURE__ */ jsx6("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children });
|
|
2408
|
+
}
|
|
2409
|
+
function ColumnWidths({
|
|
2410
|
+
component,
|
|
2411
|
+
labels,
|
|
2412
|
+
locked
|
|
2413
|
+
}) {
|
|
2414
|
+
const columns = columnsOf(component);
|
|
2415
|
+
const ratios = columns && COLUMN_RATIOS[columns.length];
|
|
2416
|
+
if (!columns || !ratios) return null;
|
|
2417
|
+
const current = currentRatio(columns);
|
|
2418
|
+
return /* @__PURE__ */ jsxs5("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
2419
|
+
/* @__PURE__ */ jsx6(Heading, { children: labels.columnWidths }),
|
|
2420
|
+
/* @__PURE__ */ jsx6("div", { className: "flex gap-1.5 px-3 py-1", children: ratios.map((ratio) => {
|
|
2421
|
+
const name = labels.columnRatio(ratio.join(":"));
|
|
2422
|
+
const active = current?.join() === ratio.join();
|
|
2423
|
+
return /* @__PURE__ */ jsx6(
|
|
2424
|
+
"button",
|
|
2425
|
+
{
|
|
2426
|
+
type: "button",
|
|
2427
|
+
disabled: locked,
|
|
2428
|
+
"aria-pressed": active,
|
|
2429
|
+
title: name,
|
|
2430
|
+
"aria-label": name,
|
|
2431
|
+
onClick: () => !active && setRatio(columns, ratio),
|
|
2432
|
+
className: cn(
|
|
2433
|
+
"flex h-8 flex-1 items-center gap-0.5 rounded-md border px-1.5 transition-colors",
|
|
2434
|
+
active ? "border-brand bg-brand/15" : "border-panel-border bg-panel-elevated hover:border-panel-muted-fg"
|
|
2435
|
+
),
|
|
2436
|
+
children: ratio.map((part, i) => /* @__PURE__ */ jsx6(
|
|
2437
|
+
"span",
|
|
2438
|
+
{
|
|
2439
|
+
style: { flexGrow: part },
|
|
2440
|
+
className: cn(
|
|
2441
|
+
"h-4 basis-0 rounded-sm",
|
|
2442
|
+
active ? "bg-brand" : "bg-panel-muted-fg/50"
|
|
2443
|
+
)
|
|
2444
|
+
},
|
|
2445
|
+
i
|
|
2446
|
+
))
|
|
2447
|
+
},
|
|
2448
|
+
ratio.join()
|
|
2449
|
+
);
|
|
2450
|
+
}) })
|
|
2451
|
+
] });
|
|
2452
|
+
}
|
|
2453
|
+
function Children({
|
|
2454
|
+
editor,
|
|
2455
|
+
component,
|
|
2456
|
+
labels,
|
|
2457
|
+
locked,
|
|
2458
|
+
title,
|
|
2459
|
+
add,
|
|
2460
|
+
icon,
|
|
2461
|
+
onAdd
|
|
2462
|
+
}) {
|
|
2463
|
+
const children = component.components().models;
|
|
2464
|
+
return /* @__PURE__ */ jsxs5("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
2465
|
+
/* @__PURE__ */ jsx6(Heading, { children: title }),
|
|
2466
|
+
/* @__PURE__ */ jsx6("ul", { className: "flex flex-col px-2", children: children.map((child) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs5(
|
|
2467
|
+
"button",
|
|
2468
|
+
{
|
|
2469
|
+
type: "button",
|
|
2470
|
+
onClick: () => editor.select(child),
|
|
2471
|
+
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",
|
|
2472
|
+
children: [
|
|
2473
|
+
/* @__PURE__ */ jsx6("span", { className: "text-panel-muted-fg", children: icon }),
|
|
2474
|
+
/* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate", children: layerName(child, labels) }),
|
|
2475
|
+
/* @__PURE__ */ jsx6(ChevronRight, { className: "size-3 shrink-0 text-panel-muted-fg opacity-0 group-hover:opacity-100" })
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
) }, child.cid)) }),
|
|
2479
|
+
/* @__PURE__ */ jsxs5(
|
|
2480
|
+
"button",
|
|
2481
|
+
{
|
|
2482
|
+
type: "button",
|
|
2483
|
+
disabled: locked,
|
|
2484
|
+
onClick: onAdd,
|
|
2485
|
+
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",
|
|
2486
|
+
children: [
|
|
2487
|
+
/* @__PURE__ */ jsx6(Plus, { className: "size-3.5" }),
|
|
2488
|
+
add
|
|
2489
|
+
]
|
|
2490
|
+
}
|
|
2491
|
+
)
|
|
2492
|
+
] });
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// src/components/editor/inspector/write.ts
|
|
2496
|
+
function same(a, b) {
|
|
2497
|
+
const keys = Object.keys(a);
|
|
2498
|
+
return keys.length === Object.keys(b).length && keys.every((k) => k in b && a[k] === b[k]);
|
|
2499
|
+
}
|
|
2500
|
+
function createWriter(editor, component, blocked = () => false) {
|
|
2501
|
+
const isImage = String(component.get("tagName") || component.get("type")) === "mj-image";
|
|
2502
|
+
const current = () => {
|
|
2503
|
+
const { __p: _marker, style: _style, ...rest } = component.get("attributes");
|
|
2504
|
+
return rest;
|
|
2505
|
+
};
|
|
2506
|
+
let drag = null;
|
|
2507
|
+
const record = (start, end) => {
|
|
2508
|
+
component.setAttributes(start, { avoidStore: true });
|
|
2509
|
+
if (!same(end, start)) component.setAttributes(end);
|
|
2510
|
+
delete component.get("attributes").__p;
|
|
2511
|
+
};
|
|
2512
|
+
return async (attr, value, { partial, remove = [] } = {}) => {
|
|
2513
|
+
if (editor.getEditing() === component) {
|
|
2514
|
+
const view = component.getView();
|
|
2515
|
+
await view?.syncContent?.();
|
|
2516
|
+
}
|
|
2517
|
+
if (blocked()) return;
|
|
2518
|
+
if (drag && drag.attr !== attr) {
|
|
2519
|
+
const { start: start2 } = drag;
|
|
2520
|
+
drag = null;
|
|
2521
|
+
record(start2, current());
|
|
2522
|
+
}
|
|
2523
|
+
if (attr === "src" && isImage) return void component.set({ src: value });
|
|
2524
|
+
const next = current();
|
|
2525
|
+
for (const name of remove) delete next[name];
|
|
2526
|
+
if (value === "") delete next[attr];
|
|
2527
|
+
else next[attr] = value;
|
|
2528
|
+
if (partial) {
|
|
2529
|
+
drag ??= { attr, start: current() };
|
|
2530
|
+
component.setAttributes(next, { avoidStore: true });
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
const start = drag?.start ?? current();
|
|
2534
|
+
drag = null;
|
|
2535
|
+
record(start, next);
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// src/components/editor/inspector/Inspector.tsx
|
|
2540
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2541
|
+
var GROUPS = ["content", "layout", "spacing", "style"];
|
|
2542
|
+
var HIDDEN = /* @__PURE__ */ new Set(["css-class", "mj-class", "id", "style"]);
|
|
2543
|
+
function tagOf4(c) {
|
|
2544
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
2545
|
+
}
|
|
2546
|
+
function attributesOf(c) {
|
|
2547
|
+
const out = {};
|
|
2548
|
+
for (const [k, v] of Object.entries(c.getAttributes())) {
|
|
2549
|
+
if (v !== void 0 && v !== null && v !== false) out[k] = String(v);
|
|
2550
|
+
}
|
|
2551
|
+
if (tagOf4(c) === "mj-image") out.src = String(c.get("src") ?? "");
|
|
2552
|
+
return out;
|
|
2553
|
+
}
|
|
2554
|
+
function documentColors(editor) {
|
|
2555
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2556
|
+
const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
|
|
2557
|
+
for (const match of editor.getHtml().matchAll(colorValue)) {
|
|
2558
|
+
const hex = toHex(match[1], { opaque: true });
|
|
2559
|
+
if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
|
|
2560
|
+
}
|
|
2561
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
|
|
2562
|
+
}
|
|
2563
|
+
function Inspector({
|
|
2564
|
+
onEmailSettings,
|
|
2565
|
+
empty
|
|
2566
|
+
}) {
|
|
2567
|
+
const editor = useEditorMaybe();
|
|
2568
|
+
const labels = useLabels();
|
|
2569
|
+
const { locked } = useAgentTurn();
|
|
2570
|
+
const lockedNow = useRef4(locked);
|
|
2571
|
+
useLayoutEffect(() => {
|
|
2572
|
+
lockedNow.current = locked;
|
|
2573
|
+
});
|
|
2574
|
+
const [, refresh] = useReducer((n) => n + 1, 0);
|
|
2575
|
+
const root = useRef4(null);
|
|
2576
|
+
const refocus = useRef4(false);
|
|
2577
|
+
useEffect4(() => {
|
|
2578
|
+
if (!editor) return;
|
|
2579
|
+
const events = "component:update change:device";
|
|
2580
|
+
const toggled = () => {
|
|
2581
|
+
if (root.current?.contains(document.activeElement)) refocus.current = true;
|
|
2582
|
+
refresh();
|
|
2583
|
+
};
|
|
2584
|
+
editor.on(events, refresh);
|
|
2585
|
+
editor.on("component:toggled", toggled);
|
|
2586
|
+
return () => {
|
|
2587
|
+
editor.off(events, refresh);
|
|
2588
|
+
editor.off("component:toggled", toggled);
|
|
2589
|
+
};
|
|
2590
|
+
}, [editor]);
|
|
2591
|
+
useEffect4(() => {
|
|
2592
|
+
if (!refocus.current) return;
|
|
2593
|
+
refocus.current = false;
|
|
2594
|
+
root.current?.querySelector("[data-focus-target]")?.focus();
|
|
2595
|
+
});
|
|
2596
|
+
if (!editor) return null;
|
|
2597
|
+
const selected = editor.getSelectedAll();
|
|
2598
|
+
const component = selected[0];
|
|
2599
|
+
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(
|
|
2600
|
+
"p",
|
|
2601
|
+
{
|
|
2602
|
+
"data-focus-target": true,
|
|
2603
|
+
tabIndex: -1,
|
|
2604
|
+
className: "px-4 py-8 text-center text-xs text-panel-muted-fg outline-none",
|
|
2605
|
+
children: labels.multipleSelected(selected.length)
|
|
2606
|
+
}
|
|
2607
|
+
) : /* @__PURE__ */ jsx7(
|
|
2608
|
+
ElementSettings,
|
|
2609
|
+
{
|
|
2610
|
+
editor,
|
|
2611
|
+
component,
|
|
2612
|
+
labels,
|
|
2613
|
+
locked,
|
|
2614
|
+
lockedNow,
|
|
2615
|
+
onEmailSettings
|
|
2616
|
+
},
|
|
2617
|
+
component.cid
|
|
2618
|
+
) });
|
|
2619
|
+
}
|
|
2620
|
+
function ElementSettings({
|
|
2621
|
+
editor,
|
|
2622
|
+
component,
|
|
2623
|
+
labels,
|
|
2624
|
+
locked,
|
|
2625
|
+
lockedNow,
|
|
2626
|
+
onEmailSettings
|
|
2627
|
+
}) {
|
|
2628
|
+
const [advancedOpen, setAdvancedOpen] = useState4(false);
|
|
2629
|
+
const tag = tagOf4(component);
|
|
2630
|
+
const fields = SCHEMA[tag] ?? [];
|
|
2631
|
+
const attributes = attributesOf(component);
|
|
2632
|
+
const visible = fields.filter((f) => !f.when || f.when(attributes));
|
|
2633
|
+
const t = (key) => labels.inspectorLabels[key] ?? key;
|
|
2634
|
+
const optionName = (o) => labels.inspectorLabels[o.label] ?? o.label;
|
|
2635
|
+
const [writer] = useState4(() => createWriter(editor, component, () => lockedNow.current));
|
|
2636
|
+
const write = (attr, value, options) => lockedNow.current ? Promise.resolve() : writer(attr, value, options);
|
|
2637
|
+
const writeBox = (attr, shorthand, partial) => write(attr, shorthand, { remove: SIDES.map((s) => `${attr}-${s}`), partial });
|
|
2638
|
+
const chooseImage = (attr) => {
|
|
2639
|
+
if (attr === "src" && tag === "mj-image") return void editor.runCommand("open-image-picker");
|
|
2640
|
+
const am = editor.AssetManager;
|
|
2641
|
+
am.open({
|
|
2642
|
+
types: ["image"],
|
|
2643
|
+
current: imageUrl(attributes[attr] ?? "") ?? void 0,
|
|
2644
|
+
select(asset, complete) {
|
|
2645
|
+
void write(attr, typeof asset === "string" ? asset : asset.getSrc());
|
|
2646
|
+
if (complete) am.close();
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
};
|
|
2650
|
+
const safeLink = (attr) => (link) => {
|
|
2651
|
+
if (tag === "mj-navbar-link" && attr === "href") {
|
|
2652
|
+
return isSafeNavbarLink(String(component.parent()?.getAttributes()["base-url"] ?? ""), link);
|
|
2653
|
+
}
|
|
2654
|
+
if (tag === "mj-navbar" && attr === "base-url") {
|
|
2655
|
+
return component.components().every((c) => isSafeNavbarLink(link, String(c.getAttributes().href ?? "")));
|
|
2656
|
+
}
|
|
2657
|
+
return isSafeLink(link);
|
|
2658
|
+
};
|
|
2659
|
+
const control = (f) => {
|
|
2660
|
+
const value = attributes[f.attr] ?? "";
|
|
2661
|
+
const c = f.control;
|
|
2662
|
+
switch (c.type) {
|
|
2663
|
+
case "text":
|
|
2664
|
+
return /* @__PURE__ */ jsx7(
|
|
2665
|
+
TextInput,
|
|
2666
|
+
{
|
|
2667
|
+
value,
|
|
2668
|
+
placeholder: c.placeholder ?? f.default,
|
|
2669
|
+
onCommit: (v) => write(f.attr, v)
|
|
2670
|
+
}
|
|
2671
|
+
);
|
|
2672
|
+
case "link":
|
|
2673
|
+
return /* @__PURE__ */ jsx7(
|
|
2674
|
+
TextInput,
|
|
2675
|
+
{
|
|
2676
|
+
value,
|
|
2677
|
+
placeholder: "https://",
|
|
2678
|
+
accept: safeLink(f.attr),
|
|
2679
|
+
onCommit: (v) => write(f.attr, v)
|
|
2680
|
+
}
|
|
2681
|
+
);
|
|
2682
|
+
case "color":
|
|
2683
|
+
return /* @__PURE__ */ jsx7(
|
|
2684
|
+
ColorField,
|
|
2685
|
+
{
|
|
2686
|
+
value,
|
|
2687
|
+
placeholder: f.default,
|
|
2688
|
+
documentColors: () => documentColors(editor),
|
|
2689
|
+
labels,
|
|
2690
|
+
onChange: (v, partial) => write(f.attr, v, { partial })
|
|
2691
|
+
}
|
|
2692
|
+
);
|
|
2693
|
+
case "image":
|
|
2694
|
+
return /* @__PURE__ */ jsx7(
|
|
2695
|
+
ImageField,
|
|
2696
|
+
{
|
|
2697
|
+
value,
|
|
2698
|
+
labels,
|
|
2699
|
+
onChoose: () => chooseImage(f.attr),
|
|
2700
|
+
onClear: () => write(f.attr, "")
|
|
2701
|
+
}
|
|
2702
|
+
);
|
|
2703
|
+
case "length":
|
|
2704
|
+
return /* @__PURE__ */ jsx7(
|
|
2705
|
+
LengthInput,
|
|
2706
|
+
{
|
|
2707
|
+
value,
|
|
2708
|
+
placeholder: f.default,
|
|
2709
|
+
units: c.units,
|
|
2710
|
+
negative: c.negative,
|
|
2711
|
+
labels,
|
|
2712
|
+
onCommit: (v, partial) => write(f.attr, v, { partial })
|
|
2713
|
+
}
|
|
2714
|
+
);
|
|
2715
|
+
case "box":
|
|
2716
|
+
return /* @__PURE__ */ jsx7(
|
|
2717
|
+
BoxInput,
|
|
2718
|
+
{
|
|
2719
|
+
attributes,
|
|
2720
|
+
attr: f.attr,
|
|
2721
|
+
placeholder: f.default,
|
|
2722
|
+
units: c.units,
|
|
2723
|
+
labels,
|
|
2724
|
+
onCommit: (v, partial) => writeBox(f.attr, v, partial)
|
|
2725
|
+
}
|
|
2726
|
+
);
|
|
2727
|
+
case "segmented":
|
|
2728
|
+
return /* @__PURE__ */ jsx7(
|
|
2729
|
+
Segmented,
|
|
2730
|
+
{
|
|
2731
|
+
value,
|
|
2732
|
+
fallback: f.default,
|
|
2733
|
+
options: c.options,
|
|
2734
|
+
name: optionName,
|
|
2735
|
+
onCommit: (v) => write(f.attr, v)
|
|
2736
|
+
}
|
|
2737
|
+
);
|
|
2738
|
+
case "select":
|
|
2739
|
+
return /* @__PURE__ */ jsx7(
|
|
2740
|
+
Select,
|
|
2741
|
+
{
|
|
2742
|
+
value,
|
|
2743
|
+
fallback: f.default,
|
|
2744
|
+
options: c.options,
|
|
2745
|
+
name: optionName,
|
|
2746
|
+
onCommit: (v) => write(f.attr, v)
|
|
2747
|
+
}
|
|
2748
|
+
);
|
|
2749
|
+
case "toggle":
|
|
2750
|
+
return /* @__PURE__ */ jsx7(
|
|
2751
|
+
Toggle,
|
|
2752
|
+
{
|
|
2753
|
+
checked: value === c.on,
|
|
2754
|
+
label: t(f.label),
|
|
2755
|
+
onCommit: (on) => write(f.attr, on ? c.on : "")
|
|
2756
|
+
}
|
|
2757
|
+
);
|
|
2758
|
+
case "network": {
|
|
2759
|
+
const { network, share } = parseNetwork(value);
|
|
2760
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1.5", children: [
|
|
2761
|
+
/* @__PURE__ */ jsx7(
|
|
2762
|
+
Select,
|
|
2763
|
+
{
|
|
2764
|
+
value: network,
|
|
2765
|
+
options: c.networks,
|
|
2766
|
+
name: optionName,
|
|
2767
|
+
onCommit: (n) => write(f.attr, n ? networkName(n, share) : "")
|
|
2768
|
+
}
|
|
2769
|
+
),
|
|
2770
|
+
network && network !== "custom" && /* @__PURE__ */ jsx7(
|
|
2771
|
+
Segmented,
|
|
2772
|
+
{
|
|
2773
|
+
value: share ? "share" : "profile",
|
|
2774
|
+
options: [
|
|
2775
|
+
{ value: "profile", label: labels.linkTypeProfile },
|
|
2776
|
+
{ value: "share", label: labels.linkTypeShare }
|
|
2777
|
+
],
|
|
2778
|
+
name: (o) => o.label,
|
|
2779
|
+
onCommit: (v) => write(f.attr, networkName(network, v === "share"))
|
|
2780
|
+
}
|
|
2781
|
+
)
|
|
2782
|
+
] });
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
};
|
|
2786
|
+
const own = (f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)].some((a) => attributes[a] !== void 0) : !!attributes[f.attr];
|
|
2787
|
+
const reset = (f) => f.control.type === "box" ? writeBox(f.attr, "") : write(f.attr, "");
|
|
2788
|
+
const wide = (f) => ["color", "image", "link", "text", "box", "network"].includes(f.control.type);
|
|
2789
|
+
const row = (f) => /* @__PURE__ */ jsx7(
|
|
2790
|
+
Row,
|
|
2791
|
+
{
|
|
2792
|
+
label: t(f.label),
|
|
2793
|
+
help: labels.inspectorLabels[`${f.label}.help`],
|
|
2794
|
+
own: own(f),
|
|
2795
|
+
wide: wide(f),
|
|
2796
|
+
inline: f.control.type === "toggle",
|
|
2797
|
+
labels,
|
|
2798
|
+
onReset: () => void reset(f),
|
|
2799
|
+
children: control(f)
|
|
2800
|
+
},
|
|
2801
|
+
f.attr
|
|
2802
|
+
);
|
|
2803
|
+
const named = new Set(
|
|
2804
|
+
fields.flatMap(
|
|
2805
|
+
(f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)] : [f.attr]
|
|
2806
|
+
)
|
|
2807
|
+
);
|
|
2808
|
+
const others = Object.entries(attributes).filter(([k]) => !named.has(k) && !HIDDEN.has(k));
|
|
2809
|
+
const advanced = visible.filter((f) => f.group === "advanced");
|
|
2810
|
+
const advancedCount = advanced.length + others.length;
|
|
2811
|
+
return /* @__PURE__ */ jsxs6("div", { className: "pb-6", children: [
|
|
2812
|
+
/* @__PURE__ */ jsx7(
|
|
2813
|
+
Header,
|
|
2814
|
+
{
|
|
2815
|
+
editor,
|
|
2816
|
+
component,
|
|
2817
|
+
labels,
|
|
2818
|
+
onEmailSettings
|
|
2819
|
+
}
|
|
2820
|
+
),
|
|
2821
|
+
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: [
|
|
2822
|
+
/* @__PURE__ */ jsx7(Monitor, { className: "size-3.5 shrink-0" }),
|
|
2823
|
+
labels.allScreenSizes
|
|
2824
|
+
] }),
|
|
2825
|
+
/* @__PURE__ */ jsx7(StructureSection, { editor, component, labels, locked }),
|
|
2826
|
+
GROUPS.map((group) => {
|
|
2827
|
+
const inGroup = visible.filter((f) => f.group === group);
|
|
2828
|
+
if (inGroup.length === 0) return null;
|
|
2829
|
+
return /* @__PURE__ */ jsxs6("section", { className: "border-b border-panel-border/60 py-2", children: [
|
|
2830
|
+
/* @__PURE__ */ jsx7("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children: groupName(group, labels) }),
|
|
2831
|
+
inGroup.map(row)
|
|
2832
|
+
] }, group);
|
|
2833
|
+
}),
|
|
2834
|
+
advancedCount > 0 && /* @__PURE__ */ jsxs6("section", { className: "py-1", children: [
|
|
2835
|
+
/* @__PURE__ */ jsxs6(
|
|
2836
|
+
"button",
|
|
2837
|
+
{
|
|
2838
|
+
type: "button",
|
|
2839
|
+
onClick: () => setAdvancedOpen((o) => !o),
|
|
2840
|
+
"aria-expanded": advancedOpen,
|
|
2841
|
+
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",
|
|
2842
|
+
children: [
|
|
2843
|
+
/* @__PURE__ */ jsx7(
|
|
2844
|
+
ChevronRight2,
|
|
2845
|
+
{
|
|
2846
|
+
className: cn("size-3 transition-transform", advancedOpen && "rotate-90")
|
|
2847
|
+
}
|
|
2848
|
+
),
|
|
2849
|
+
labels.groupAdvanced,
|
|
2850
|
+
/* @__PURE__ */ jsxs6("span", { className: "font-normal normal-case", children: [
|
|
2851
|
+
"(",
|
|
2852
|
+
advancedCount,
|
|
2853
|
+
")"
|
|
2854
|
+
] })
|
|
2855
|
+
]
|
|
2856
|
+
}
|
|
2857
|
+
),
|
|
2858
|
+
advancedOpen && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2859
|
+
advanced.map(row),
|
|
2860
|
+
others.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "px-3 pt-2", children: [
|
|
2861
|
+
/* @__PURE__ */ jsx7("p", { className: "pb-1 text-[11px] text-panel-muted-fg", children: labels.otherAttributes }),
|
|
2862
|
+
/* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-1", children: others.map(([k, v]) => /* @__PURE__ */ jsxs6(
|
|
2863
|
+
"li",
|
|
2864
|
+
{
|
|
2865
|
+
className: "flex items-center gap-2 rounded-md bg-panel-elevated px-2 py-1 font-mono text-[11px]",
|
|
2866
|
+
children: [
|
|
2867
|
+
/* @__PURE__ */ jsx7("span", { className: "text-panel-muted-fg", children: k }),
|
|
2868
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate text-panel-fg", title: v, children: v }),
|
|
2869
|
+
/* @__PURE__ */ jsx7(
|
|
2870
|
+
"button",
|
|
2871
|
+
{
|
|
2872
|
+
type: "button",
|
|
2873
|
+
onClick: () => void write(k, ""),
|
|
2874
|
+
title: labels.removeAttribute,
|
|
2875
|
+
"aria-label": `${labels.removeAttribute}: ${k}`,
|
|
2876
|
+
className: "text-panel-muted-fg hover:text-panel-fg",
|
|
2877
|
+
children: /* @__PURE__ */ jsx7(X2, { className: "size-3" })
|
|
2878
|
+
}
|
|
2879
|
+
)
|
|
2880
|
+
]
|
|
2881
|
+
},
|
|
2882
|
+
k
|
|
2883
|
+
)) })
|
|
2884
|
+
] })
|
|
2885
|
+
] })
|
|
2886
|
+
] })
|
|
2887
|
+
] });
|
|
2888
|
+
}
|
|
2889
|
+
function groupName(group, labels) {
|
|
2890
|
+
return {
|
|
2891
|
+
content: labels.groupContent,
|
|
2892
|
+
layout: labels.groupLayout,
|
|
2893
|
+
spacing: labels.groupSpacing,
|
|
2894
|
+
style: labels.groupStyle
|
|
2895
|
+
}[group];
|
|
2896
|
+
}
|
|
2897
|
+
function Header({
|
|
2898
|
+
editor,
|
|
2899
|
+
component,
|
|
2900
|
+
labels,
|
|
2901
|
+
onEmailSettings
|
|
2902
|
+
}) {
|
|
2903
|
+
const tag = tagOf4(component);
|
|
2904
|
+
const isBody = tag === "mj-body";
|
|
2905
|
+
const name = isBody ? labels.emailSettings : labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
|
|
2906
|
+
const words = tag === "mj-column" ? "" : layerName(component, labels);
|
|
2907
|
+
const excerpt = words === name ? "" : words;
|
|
2908
|
+
const parent = component.parent();
|
|
2909
|
+
const action = "flex size-7 items-center justify-center rounded-md text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
|
|
2910
|
+
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: [
|
|
2911
|
+
/* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
|
|
2912
|
+
/* @__PURE__ */ jsx7(
|
|
2913
|
+
"p",
|
|
2914
|
+
{
|
|
2915
|
+
"data-focus-target": true,
|
|
2916
|
+
tabIndex: -1,
|
|
2917
|
+
className: "truncate text-sm font-medium text-panel-fg outline-none",
|
|
2918
|
+
children: name
|
|
2919
|
+
}
|
|
2920
|
+
),
|
|
2921
|
+
excerpt && !isBody && /* @__PURE__ */ jsxs6("p", { className: "truncate text-[11px] text-panel-muted-fg", children: [
|
|
2922
|
+
"\u201C",
|
|
2923
|
+
excerpt,
|
|
2924
|
+
"\u201D"
|
|
2925
|
+
] })
|
|
2926
|
+
] }),
|
|
2927
|
+
!isBody && parent && /* @__PURE__ */ jsx7(
|
|
2928
|
+
"button",
|
|
2929
|
+
{
|
|
2930
|
+
type: "button",
|
|
2931
|
+
className: action,
|
|
2932
|
+
title: labels.selectParent,
|
|
2933
|
+
"aria-label": labels.selectParent,
|
|
2934
|
+
onClick: () => tagOf4(parent) === "mj-body" ? onEmailSettings() : editor.select(parent),
|
|
2935
|
+
children: /* @__PURE__ */ jsx7(ArrowUp, { className: "size-3.5" })
|
|
2936
|
+
}
|
|
2937
|
+
),
|
|
2938
|
+
!isBody && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2939
|
+
/* @__PURE__ */ jsx7(
|
|
2940
|
+
"button",
|
|
2941
|
+
{
|
|
2942
|
+
type: "button",
|
|
2943
|
+
className: action,
|
|
2944
|
+
title: labels.duplicate,
|
|
2945
|
+
"aria-label": labels.duplicate,
|
|
2946
|
+
onClick: () => editor.runCommand("tlb-clone"),
|
|
2947
|
+
children: /* @__PURE__ */ jsx7(Copy, { className: "size-3.5" })
|
|
2948
|
+
}
|
|
2949
|
+
),
|
|
2950
|
+
/* @__PURE__ */ jsx7(
|
|
2951
|
+
"button",
|
|
2952
|
+
{
|
|
2953
|
+
type: "button",
|
|
2954
|
+
className: action,
|
|
2955
|
+
title: labels.deleteElement,
|
|
2956
|
+
"aria-label": labels.deleteElement,
|
|
2957
|
+
onClick: () => editor.runCommand("tlb-delete"),
|
|
2958
|
+
children: /* @__PURE__ */ jsx7(Trash22, { className: "size-3.5" })
|
|
2959
|
+
}
|
|
2960
|
+
)
|
|
2961
|
+
] })
|
|
2962
|
+
] });
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/components/editor/blocks.ts
|
|
2966
|
+
import grapesjs from "grapesjs";
|
|
2967
|
+
import grapesjsMJML from "grapesjs-mjml";
|
|
2968
|
+
|
|
2969
|
+
// src/components/editor/ids.ts
|
|
2970
|
+
var ID_RE = {
|
|
2971
|
+
sec: /(?:^|\s)sec-([A-Za-z0-9_-]+)(?=\s|$)/,
|
|
2972
|
+
obj: /(?:^|\s)obj-([A-Za-z0-9_-]+)(?=\s|$)/
|
|
2973
|
+
};
|
|
2974
|
+
function idOf(c, prefix) {
|
|
2975
|
+
return String(c.getAttributes()["css-class"] ?? "").match(ID_RE[prefix])?.[1] ?? null;
|
|
2976
|
+
}
|
|
2977
|
+
var newId = () => Math.random().toString(36).slice(2, 10);
|
|
699
2978
|
function sectionIdOf(c) {
|
|
700
|
-
return
|
|
2979
|
+
return idOf(c, "sec");
|
|
701
2980
|
}
|
|
702
2981
|
function addIdClass(c, prefix) {
|
|
703
|
-
const existing =
|
|
2982
|
+
const existing = idOf(c, prefix);
|
|
704
2983
|
if (existing) return existing;
|
|
705
2984
|
const cssClass = String(c.getAttributes()["css-class"] ?? "");
|
|
706
|
-
const id =
|
|
2985
|
+
const id = newId();
|
|
707
2986
|
c.addAttributes({ "css-class": `${cssClass} ${prefix}-${id}`.trim() });
|
|
708
2987
|
return id;
|
|
709
2988
|
}
|
|
2989
|
+
function freshenCopiedIds(root, added) {
|
|
2990
|
+
const used = (prefix, id, except) => {
|
|
2991
|
+
let found = false;
|
|
2992
|
+
root?.onAll((o) => {
|
|
2993
|
+
if (o !== except && idOf(o, prefix) === id) found = true;
|
|
2994
|
+
});
|
|
2995
|
+
return found;
|
|
2996
|
+
};
|
|
2997
|
+
added.onAll((c) => {
|
|
2998
|
+
for (const prefix of ["sec", "obj"]) {
|
|
2999
|
+
const id = idOf(c, prefix);
|
|
3000
|
+
if (!id || !used(prefix, id, c)) continue;
|
|
3001
|
+
const cssClass = String(c.getAttributes()["css-class"]);
|
|
3002
|
+
const fresh = cssClass.replace(
|
|
3003
|
+
ID_RE[prefix],
|
|
3004
|
+
(token) => token.replace(`${prefix}-${id}`, `${prefix}-${newId()}`)
|
|
3005
|
+
);
|
|
3006
|
+
c.addAttributes({ "css-class": fresh });
|
|
3007
|
+
}
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
// src/components/editor/insert-block.ts
|
|
3012
|
+
var addsBlock = (e) => (e.key === "Enter" || e.key === " ") && !e.repeat && !e.ctrlKey && !e.metaKey && !e.altKey;
|
|
3013
|
+
var tileAttributes = (id) => ({ tabindex: "0", role: "button", "data-block": id });
|
|
3014
|
+
var tagOf5 = (c) => String(c.get("tagName") ?? "");
|
|
3015
|
+
function closest(c, tag) {
|
|
3016
|
+
for (let cur = c; cur; cur = cur.parent() ?? void 0) if (tagOf5(cur) === tag) return cur;
|
|
3017
|
+
return void 0;
|
|
3018
|
+
}
|
|
3019
|
+
function childOf(parent, c) {
|
|
3020
|
+
let cur = c;
|
|
3021
|
+
while (cur.parent() && cur.parent() !== parent) cur = cur.parent();
|
|
3022
|
+
return cur;
|
|
3023
|
+
}
|
|
3024
|
+
function insertBlock(editor, block) {
|
|
3025
|
+
const content = block.getContent();
|
|
3026
|
+
let body;
|
|
3027
|
+
editor.getWrapper()?.onAll((c) => {
|
|
3028
|
+
if (!body && tagOf5(c) === "mj-body") body = c;
|
|
3029
|
+
});
|
|
3030
|
+
if (typeof content !== "string" || !body) return void 0;
|
|
3031
|
+
const selected = editor.getSelected();
|
|
3032
|
+
const section = closest(selected, "mj-section");
|
|
3033
|
+
const sections = section?.parent() ?? body;
|
|
3034
|
+
const afterSection = section ? { at: section.index() + 1 } : {};
|
|
3035
|
+
const column = closest(selected, "mj-column");
|
|
3036
|
+
let added;
|
|
3037
|
+
if (/^\s*<mj-section\b/i.test(content)) {
|
|
3038
|
+
added = sections.append(content, afterSection)[0];
|
|
3039
|
+
} else if (column && selected) {
|
|
3040
|
+
const at = selected === column ? {} : { at: childOf(column, selected).index() + 1 };
|
|
3041
|
+
added = column.append(content, at)[0];
|
|
3042
|
+
} else {
|
|
3043
|
+
const [wrapper] = sections.append(
|
|
3044
|
+
`<mj-section><mj-column>${content}</mj-column></mj-section>`,
|
|
3045
|
+
afterSection
|
|
3046
|
+
);
|
|
3047
|
+
added = wrapper?.components().at(0)?.components().at(0);
|
|
3048
|
+
}
|
|
3049
|
+
if (!added) return void 0;
|
|
3050
|
+
editor.select(added);
|
|
3051
|
+
if (block.get("activate")) added.getView()?.onActive(void 0);
|
|
3052
|
+
return added;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// src/components/editor/blocks.ts
|
|
3056
|
+
var EDITABLE_TAGS = /* @__PURE__ */ new Set([
|
|
3057
|
+
"mj-section",
|
|
3058
|
+
"mj-group",
|
|
3059
|
+
"mj-column",
|
|
3060
|
+
"mj-text",
|
|
3061
|
+
"mj-image",
|
|
3062
|
+
"mj-button",
|
|
3063
|
+
"mj-divider",
|
|
3064
|
+
"mj-spacer",
|
|
3065
|
+
"mj-social",
|
|
3066
|
+
"mj-social-element",
|
|
3067
|
+
"mj-navbar",
|
|
3068
|
+
"mj-navbar-link"
|
|
3069
|
+
]);
|
|
3070
|
+
var CONTENT_TAGS = /* @__PURE__ */ new Set(["mj-text", "mj-button", "mj-social-element", "mj-navbar-link"]);
|
|
3071
|
+
var EDITOR_INTERNALS = /data-gjs-|__p\b|custom-name/i;
|
|
3072
|
+
var DROPPED_MARKUP = /<(?:script|style|link|meta|iframe|object|embed|form)\b/i;
|
|
3073
|
+
var INLINE_IMAGE = /\b(?:src|srcset|background-url|icon)\s*=\s*(?:"[^"]*data:|'[^']*data:|data:)/i;
|
|
3074
|
+
var LINK_IN_MARKUP = /[\s/"'](?:xlink:)?(?:href|action|formaction)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
3075
|
+
var validator;
|
|
3076
|
+
function blockProblems(mjml, editor = validator ??= grapesjs.init({
|
|
3077
|
+
headless: true,
|
|
3078
|
+
storageManager: false,
|
|
3079
|
+
telemetry: false,
|
|
3080
|
+
plugins: [grapesjsMJML]
|
|
3081
|
+
})) {
|
|
3082
|
+
if (EDITOR_INTERNALS.test(mjml)) return ["it contains GrapesJS's own attributes"];
|
|
3083
|
+
if (INLINE_IMAGE.test(mjml)) return ["it inlines an image as a data: URL"];
|
|
3084
|
+
let tree;
|
|
3085
|
+
try {
|
|
3086
|
+
tree = editor.runCommand("mjml-code-to-html", {
|
|
3087
|
+
mjml: `<mjml><mj-body>${withClosedTags(mjml)}</mj-body></mjml>`,
|
|
3088
|
+
validationLevel: "strict"
|
|
3089
|
+
}).json;
|
|
3090
|
+
} catch (e) {
|
|
3091
|
+
const errors = e.errors;
|
|
3092
|
+
if (!errors) throw e;
|
|
3093
|
+
return errors.map((error) => error.formattedMessage ?? error.message ?? String(error));
|
|
3094
|
+
}
|
|
3095
|
+
const problems = [];
|
|
3096
|
+
const top = tree.children?.find((c) => c.tagName === "mj-body")?.children ?? [];
|
|
3097
|
+
if (top.length !== 1 || top[0].tagName !== "mj-section") {
|
|
3098
|
+
problems.push("it is not exactly one mj-section");
|
|
3099
|
+
}
|
|
3100
|
+
const visit = (node, parent) => {
|
|
3101
|
+
const tag = node.tagName;
|
|
3102
|
+
const attributes = node.attributes ?? {};
|
|
3103
|
+
const content = node.content ?? "";
|
|
3104
|
+
if (!EDITABLE_TAGS.has(tag)) problems.push(`${tag} is not editable here`);
|
|
3105
|
+
const cssClass = attributes["css-class"] ?? "";
|
|
3106
|
+
if (ID_RE.sec.test(cssClass) || ID_RE.obj.test(cssClass)) {
|
|
3107
|
+
problems.push(`${tag} carries a sec-* or obj-* id`);
|
|
3108
|
+
}
|
|
3109
|
+
if ("mj-class" in attributes) problems.push(`${tag} uses mj-class`);
|
|
3110
|
+
const links = [attributes.href ?? ""];
|
|
3111
|
+
for (const match of content.matchAll(LINK_IN_MARKUP)) {
|
|
3112
|
+
links.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
3113
|
+
}
|
|
3114
|
+
const linked = tag === "mj-navbar-link" ? isSafeNavbarLink(parent?.attributes?.["base-url"] ?? "", attributes.href ?? "") : true;
|
|
3115
|
+
if (!linked || !links.every(isSafeLink)) {
|
|
3116
|
+
problems.push(`${tag} has a link scheme mail must not carry`);
|
|
3117
|
+
}
|
|
3118
|
+
if (CONTENT_TAGS.has(tag) ? /<mj-/i.test(content) || DROPPED_MARKUP.test(content) : content.trim()) {
|
|
3119
|
+
problems.push(`${tag} holds content the editor does not keep`);
|
|
3120
|
+
}
|
|
3121
|
+
node.children?.forEach((child) => visit(child, node));
|
|
3122
|
+
};
|
|
3123
|
+
top.forEach((node) => visit(node));
|
|
3124
|
+
return problems;
|
|
3125
|
+
}
|
|
3126
|
+
var escapeHtml3 = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3127
|
+
function registerBlocks(editor, blocks, category, media) {
|
|
3128
|
+
for (const block of blocks) {
|
|
3129
|
+
const problems = editor.Blocks.get(block.id) ? [`the id is already taken`] : blockProblems(block.mjml, editor);
|
|
3130
|
+
if (problems.length > 0) {
|
|
3131
|
+
console.error(`Block "${block.id}" is left out of the palette: ${problems.join("; ")}`);
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
editor.Blocks.add(block.id, {
|
|
3135
|
+
// GrapesJS writes the label into the tile as markup.
|
|
3136
|
+
label: escapeHtml3(block.label),
|
|
3137
|
+
category,
|
|
3138
|
+
media: block.media ?? media,
|
|
3139
|
+
content: withClosedTags(block.mjml),
|
|
3140
|
+
attributes: tileAttributes(block.id)
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
// src/components/editor/builtin-blocks.ts
|
|
3146
|
+
var BUILTIN_BLOCKS = [
|
|
3147
|
+
"mj-1-column",
|
|
3148
|
+
"mj-2-columns",
|
|
3149
|
+
"mj-3-columns",
|
|
3150
|
+
"mj-text",
|
|
3151
|
+
"mj-image",
|
|
3152
|
+
"mj-button",
|
|
3153
|
+
"mj-divider",
|
|
3154
|
+
"mj-spacer",
|
|
3155
|
+
"mj-social-group",
|
|
3156
|
+
"mj-navbar"
|
|
3157
|
+
];
|
|
3158
|
+
var LAYOUT_BLOCKS = /* @__PURE__ */ new Set(["mj-1-column", "mj-2-columns", "mj-3-columns"]);
|
|
3159
|
+
var CONTENT2 = {
|
|
3160
|
+
// The plugin's version links Google+ and shares the page: a bare network name makes the
|
|
3161
|
+
// icon open a "share this link" dialog, while `-noshare` links to the profile a footer
|
|
3162
|
+
// means. Without `alt` each link is an unnamed image to a screen reader.
|
|
3163
|
+
"mj-social-group": `<mj-social icon-size="24px" mode="horizontal">
|
|
3164
|
+
<mj-social-element name="facebook-noshare" href="https://facebook.com/" alt="Facebook"></mj-social-element>
|
|
3165
|
+
<mj-social-element name="instagram-noshare" href="https://instagram.com/" alt="Instagram"></mj-social-element>
|
|
3166
|
+
<mj-social-element name="x-noshare" href="https://x.com/" alt="X"></mj-social-element>
|
|
3167
|
+
</mj-social>`,
|
|
3168
|
+
// The plugin's sample is a via.placeholder.com picture, stored in the email and loaded by
|
|
3169
|
+
// every reader's mail client. Dropping one opens the image picker instead.
|
|
3170
|
+
"mj-image": "<mj-image></mj-image>"
|
|
3171
|
+
};
|
|
3172
|
+
function builtinBlock(labels) {
|
|
3173
|
+
return (id) => ({
|
|
3174
|
+
label: labels.blockLabels[id] ?? id,
|
|
3175
|
+
// By id: a regex on the label broke as soon as a host translated it.
|
|
3176
|
+
category: LAYOUT_BLOCKS.has(id) ? labels.blockCategoryLayout : labels.blockCategoryContent,
|
|
3177
|
+
...id in CONTENT2 && { content: CONTENT2[id] },
|
|
3178
|
+
attributes: tileAttributes(id)
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
var CANVAS_CSS = 'img:not([src]), img[src=""] { min-height: 120px; background: #f4f4f5; outline: 1px dashed #a1a1aa; outline-offset: -1px; }';
|
|
3182
|
+
|
|
3183
|
+
// src/components/editor/table-type.ts
|
|
3184
|
+
import { isSafeLink as isSafeLink2 } from "@mjml-agent-editor/core";
|
|
3185
|
+
var DROPPED = "script, style, link, iframe, frame, object, embed, applet, base, meta, animate, set, svg, math, template";
|
|
3186
|
+
var LINK_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "xlink:href", "action", "formaction"]);
|
|
3187
|
+
function cleanRows(rows) {
|
|
3188
|
+
rows.querySelectorAll(DROPPED).forEach((el) => el.remove());
|
|
3189
|
+
rows.querySelectorAll("*").forEach((el) => {
|
|
3190
|
+
for (const { name, value } of [...el.attributes]) {
|
|
3191
|
+
const lower = name.toLowerCase();
|
|
3192
|
+
const script = /^(java|vb)script:/i.test(value.replace(/[\u0000-\u0020]/g, ""));
|
|
3193
|
+
if (lower.startsWith("on") || script || LINK_ATTRIBUTES.has(lower) && !isSafeLink2(value)) {
|
|
3194
|
+
el.removeAttribute(name);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
var ROW_STYLE = "pointer-events: all; display: table; width: 100%;";
|
|
3200
|
+
function tablePreview(doc, attributes, rows) {
|
|
3201
|
+
const attr = (name, fallback) => String(attributes[name] ?? fallback);
|
|
3202
|
+
const template = doc.createElement("template");
|
|
3203
|
+
template.innerHTML = `<table>${rows}</table>`;
|
|
3204
|
+
const table = doc.createElement("table");
|
|
3205
|
+
table.setAttribute("cellpadding", String(parseInt(attr("cellpadding", "0"), 10) || 0));
|
|
3206
|
+
table.setAttribute("cellspacing", String(parseInt(attr("cellspacing", "0"), 10) || 0));
|
|
3207
|
+
Object.assign(table.style, {
|
|
3208
|
+
width: attr("width", "100%"),
|
|
3209
|
+
border: attr("border", "none"),
|
|
3210
|
+
color: attr("color", "#000000"),
|
|
3211
|
+
fontFamily: attr("font-family", "Ubuntu, Helvetica, Arial, sans-serif"),
|
|
3212
|
+
fontSize: attr("font-size", "13px"),
|
|
3213
|
+
lineHeight: attr("line-height", "22px"),
|
|
3214
|
+
tableLayout: attr("table-layout", "auto")
|
|
3215
|
+
});
|
|
3216
|
+
for (const row of template.content.querySelectorAll("tr")) {
|
|
3217
|
+
const tr = table.insertRow();
|
|
3218
|
+
for (const cell2 of row.querySelectorAll(":scope > th, :scope > td")) {
|
|
3219
|
+
const copy = doc.createElement(cell2.tagName.toLowerCase());
|
|
3220
|
+
copy.textContent = cell2.textContent;
|
|
3221
|
+
const align = cell2.getAttribute("align");
|
|
3222
|
+
if (align && /^(left|right|center)$/i.test(align)) copy.setAttribute("align", align);
|
|
3223
|
+
tr.append(copy);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
const cell = doc.createElement("td");
|
|
3227
|
+
cell.style.padding = attr("padding", "10px 25px");
|
|
3228
|
+
cell.append(table);
|
|
3229
|
+
return cell;
|
|
3230
|
+
}
|
|
3231
|
+
function addTableType(editor) {
|
|
3232
|
+
const base = editor.Components.getType("default").view.prototype;
|
|
3233
|
+
editor.Components.addType("mj-table", {
|
|
3234
|
+
isComponent: (el) => {
|
|
3235
|
+
if (el.tagName?.toLowerCase() !== "mj-table") return false;
|
|
3236
|
+
const rows = el.querySelector(":scope > template");
|
|
3237
|
+
if (rows) cleanRows(rows.content);
|
|
3238
|
+
return { type: "mj-table", content: rows?.innerHTML ?? "", components: [] };
|
|
3239
|
+
},
|
|
3240
|
+
model: { defaults: { tagName: "mj-table", droppable: false, editable: false } },
|
|
3241
|
+
view: {
|
|
3242
|
+
tagName: "tr",
|
|
3243
|
+
// GrapesJS clears the element's attributes, style among them, when the model's change.
|
|
3244
|
+
updateAttributes(...args) {
|
|
3245
|
+
base.updateAttributes.apply(this, args);
|
|
3246
|
+
this.el.style.cssText = ROW_STYLE;
|
|
3247
|
+
},
|
|
3248
|
+
updateContent() {
|
|
3249
|
+
const { el, model } = this;
|
|
3250
|
+
el.style.cssText = ROW_STYLE;
|
|
3251
|
+
el.replaceChildren(
|
|
3252
|
+
tablePreview(el.ownerDocument, model.getAttributes(), String(model.get("content") ?? ""))
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
// src/components/editor/plugin-types.ts
|
|
3260
|
+
var COLUMN_CHILDREN = /* @__PURE__ */ new Set([
|
|
3261
|
+
"mj-accordion",
|
|
3262
|
+
"mj-button",
|
|
3263
|
+
"mj-carousel",
|
|
3264
|
+
"mj-divider",
|
|
3265
|
+
"mj-image",
|
|
3266
|
+
"mj-navbar",
|
|
3267
|
+
"mj-raw",
|
|
3268
|
+
"mj-social",
|
|
3269
|
+
"mj-spacer",
|
|
3270
|
+
"mj-table",
|
|
3271
|
+
"mj-text"
|
|
3272
|
+
]);
|
|
3273
|
+
function fixPluginTypes(editor) {
|
|
3274
|
+
addTableType(editor);
|
|
3275
|
+
for (const type of editor.Components.getTypes()) {
|
|
3276
|
+
if (!type.id.startsWith("mj-") || type.id === "mj-table") continue;
|
|
3277
|
+
const base = type.model.prototype.getAttrToHTML;
|
|
3278
|
+
editor.Components.addType(type.id, {
|
|
3279
|
+
model: {
|
|
3280
|
+
defaults: {
|
|
3281
|
+
"style-default": {},
|
|
3282
|
+
...type.id === "mj-column" && {
|
|
3283
|
+
droppable: (source) => COLUMN_CHILDREN.has(String(source.get("tagName")))
|
|
3284
|
+
},
|
|
3285
|
+
// GrapesJS's own is an SVG's markup, which the canvas wrote into `src` and saved.
|
|
3286
|
+
...type.id === "mj-image" && { src: "" }
|
|
3287
|
+
},
|
|
3288
|
+
getAttrToHTML() {
|
|
3289
|
+
const attributes = base.call(this);
|
|
3290
|
+
delete attributes.__p;
|
|
3291
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
3292
|
+
if (typeof value === "string") attributes[name] = value.replace(/"/g, """);
|
|
3293
|
+
}
|
|
3294
|
+
return attributes;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// src/components/editor/EmailEditor.tsx
|
|
3302
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3303
|
+
var TYPE_LABEL = {
|
|
3304
|
+
"mj-text": "Text",
|
|
3305
|
+
"mj-button": "Button",
|
|
3306
|
+
"mj-image": "Image",
|
|
3307
|
+
"mj-column": "Column"
|
|
3308
|
+
};
|
|
3309
|
+
function tagOf6(c) {
|
|
3310
|
+
return String(c.get("tagName") || c.get("type") || "");
|
|
3311
|
+
}
|
|
3312
|
+
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>';
|
|
3313
|
+
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>';
|
|
3314
|
+
var SECTION_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="7" x="3" y="3" rx="1"/><rect width="9" height="7" x="3" y="14" rx="1"/><rect width="5" height="7" x="16" y="14" rx="1"/></svg>';
|
|
3315
|
+
function isSection(c) {
|
|
3316
|
+
return tagOf6(c) === "mj-section";
|
|
3317
|
+
}
|
|
3318
|
+
function isCommentable(c) {
|
|
3319
|
+
const t = tagOf6(c);
|
|
3320
|
+
return t === "mj-section" || t in TYPE_LABEL;
|
|
3321
|
+
}
|
|
710
3322
|
function closestSection(c) {
|
|
711
3323
|
let cur = c;
|
|
712
3324
|
while (cur) {
|
|
@@ -715,27 +3327,12 @@ function closestSection(c) {
|
|
|
715
3327
|
}
|
|
716
3328
|
return null;
|
|
717
3329
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
"mj-group": "Group",
|
|
722
|
-
"mj-text": "Text",
|
|
723
|
-
"mj-button": "Button",
|
|
724
|
-
"mj-image": "Image",
|
|
725
|
-
"mj-divider": "Divider",
|
|
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";
|
|
3330
|
+
function crumbName(c, labels) {
|
|
3331
|
+
const t = tagOf6(c);
|
|
3332
|
+
return labels.elementNames[t] ?? t.replace(/^mj-/, "");
|
|
736
3333
|
}
|
|
737
3334
|
function objectLabel(c) {
|
|
738
|
-
const base = TYPE_LABEL[
|
|
3335
|
+
const base = TYPE_LABEL[tagOf6(c)] ?? tagOf6(c);
|
|
739
3336
|
const text = String(c.getEl?.()?.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 30);
|
|
740
3337
|
return text ? `${base}: \u201C${text}\u201D` : base;
|
|
741
3338
|
}
|
|
@@ -767,75 +3364,6 @@ function wrapSelectionStyle(el, style) {
|
|
|
767
3364
|
} catch {
|
|
768
3365
|
}
|
|
769
3366
|
}
|
|
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
3367
|
function setupRichText(editor, labels) {
|
|
840
3368
|
const rte = editor.RichTextEditor;
|
|
841
3369
|
if (!rte || rte.__customActions) return;
|
|
@@ -872,26 +3400,45 @@ function setupRichText(editor, labels) {
|
|
|
872
3400
|
result: (r, action) => r.exec("hiliteColor", val(action, "input"))
|
|
873
3401
|
});
|
|
874
3402
|
}
|
|
875
|
-
function EmailEditor({
|
|
3403
|
+
function EmailEditor({
|
|
3404
|
+
docId,
|
|
3405
|
+
onReady,
|
|
3406
|
+
commentsRefresh,
|
|
3407
|
+
onOpenCountChange,
|
|
3408
|
+
blocks = []
|
|
3409
|
+
}) {
|
|
876
3410
|
const documents = useDocumentStore();
|
|
877
3411
|
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
|
-
|
|
3412
|
+
const loadingRef = useRef5(false);
|
|
3413
|
+
const saveStatusRef = useRef5("idle");
|
|
3414
|
+
const stateListeners = useRef5(/* @__PURE__ */ new Set());
|
|
3415
|
+
const [loading, setLoading] = useState5(true);
|
|
3416
|
+
const [sidebarView, setSidebarView] = useState5("blocks");
|
|
3417
|
+
const blockSearchRef = useRef5(null);
|
|
3418
|
+
const onBlockSearchKey = (e) => {
|
|
3419
|
+
if (readOnlyRef.current || !opensBlockSearch(e)) return;
|
|
3420
|
+
e.preventDefault();
|
|
3421
|
+
setSidebarView("blocks");
|
|
3422
|
+
requestAnimationFrame(() => blockSearchRef.current?.focus());
|
|
3423
|
+
};
|
|
3424
|
+
const [composeTarget, setComposeTarget] = useState5(null);
|
|
3425
|
+
const selectionSource = useRef5("other");
|
|
3426
|
+
const editorRef = useRef5(null);
|
|
886
3427
|
const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
|
|
887
|
-
const readOnlyRef =
|
|
888
|
-
const applyReadOnly =
|
|
3428
|
+
const readOnlyRef = useRef5(false);
|
|
3429
|
+
const applyReadOnly = useRef5(async () => {
|
|
889
3430
|
});
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
3431
|
+
const [turn] = useState5(createTurnFollower);
|
|
3432
|
+
useEffect5(() => turn.turn(turnRunning), [turn, turnRunning]);
|
|
3433
|
+
useEffect5(() => {
|
|
3434
|
+
turn.mount();
|
|
3435
|
+
return () => {
|
|
3436
|
+
turn.unmount();
|
|
3437
|
+
setLocked(false);
|
|
3438
|
+
};
|
|
3439
|
+
}, [turn, setLocked]);
|
|
3440
|
+
const [crumbs, setCrumbs] = useState5([]);
|
|
3441
|
+
const [picker, setPicker] = useState5(
|
|
895
3442
|
null
|
|
896
3443
|
);
|
|
897
3444
|
const onEditor = async (editor) => {
|
|
@@ -900,7 +3447,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
900
3447
|
const chain = [];
|
|
901
3448
|
let cur = c;
|
|
902
3449
|
while (cur) {
|
|
903
|
-
const t =
|
|
3450
|
+
const t = tagOf6(cur);
|
|
904
3451
|
if (!t || t === "mj-body" || t === "wrapper" || t === "Wrapper") break;
|
|
905
3452
|
chain.unshift(cur);
|
|
906
3453
|
cur = cur.parent() ?? void 0;
|
|
@@ -949,7 +3496,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
949
3496
|
if (isSection(c)) addIdClass(c, "sec");
|
|
950
3497
|
if (!isCommentable(c)) return;
|
|
951
3498
|
const toolbar = [...c.get("toolbar") ?? []];
|
|
952
|
-
if (
|
|
3499
|
+
if (tagOf6(c) === "mj-image" && !toolbar.some((t) => t.command === "open-image-picker")) {
|
|
953
3500
|
toolbar.push({
|
|
954
3501
|
command: "open-image-picker",
|
|
955
3502
|
label: IMAGE_SVG,
|
|
@@ -957,11 +3504,11 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
957
3504
|
});
|
|
958
3505
|
c.set("toolbar", toolbar);
|
|
959
3506
|
}
|
|
960
|
-
if (!toolbar.some((t) => t.command === "open-comments")) {
|
|
3507
|
+
if (closestSection(c) && !toolbar.some((t) => t.command === "open-comments")) {
|
|
961
3508
|
toolbar.push({
|
|
962
3509
|
command: "open-comments",
|
|
963
3510
|
label: SPARKLES_SVG,
|
|
964
|
-
attributes: { title:
|
|
3511
|
+
attributes: { title: labels.commentOnElement, "data-role": "sparkles" }
|
|
965
3512
|
});
|
|
966
3513
|
c.set("toolbar", toolbar);
|
|
967
3514
|
}
|
|
@@ -969,7 +3516,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
969
3516
|
editor.Commands.add("open-image-picker", {
|
|
970
3517
|
run(ed) {
|
|
971
3518
|
const target = ed.getSelected();
|
|
972
|
-
if (!target ||
|
|
3519
|
+
if (!target || tagOf6(target) !== "mj-image") return;
|
|
973
3520
|
const am = ed.AssetManager;
|
|
974
3521
|
am.open({
|
|
975
3522
|
target,
|
|
@@ -988,21 +3535,29 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
988
3535
|
}
|
|
989
3536
|
});
|
|
990
3537
|
setupRichText(editor, labels);
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
3538
|
+
fixPluginTypes(editor);
|
|
3539
|
+
registerBlocks(editor, blocks, labels.blockCategorySections, SECTION_SVG);
|
|
3540
|
+
setupLayerNames(editor, labels);
|
|
3541
|
+
const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-column"]);
|
|
3542
|
+
const showBodyInLayers = () => {
|
|
3543
|
+
const body = findByTag("mj-body");
|
|
3544
|
+
if (!body) return;
|
|
3545
|
+
editor.Layers.setRoot(body);
|
|
3546
|
+
body.onAll((c) => {
|
|
3547
|
+
if (STRUCTURE.has(tagOf6(c))) editor.Layers.setOpen(c, true);
|
|
3548
|
+
});
|
|
997
3549
|
};
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
(
|
|
1001
|
-
|
|
3550
|
+
const keepStructureOpen = () => {
|
|
3551
|
+
const layers = editor.Layers;
|
|
3552
|
+
const opened = layers.model?.get("opened") ?? {};
|
|
3553
|
+
for (const [cid, c] of Object.entries(opened)) {
|
|
3554
|
+
if (STRUCTURE.has(tagOf6(c))) delete opened[cid];
|
|
1002
3555
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
3556
|
+
};
|
|
3557
|
+
editor.on("component:add", (c) => {
|
|
3558
|
+
if (!loadingRef.current) freshenCopiedIds(editor.getWrapper(), c);
|
|
3559
|
+
decorate(c);
|
|
3560
|
+
});
|
|
1006
3561
|
editor.on("load", () => {
|
|
1007
3562
|
const body = editor.Canvas.getBody();
|
|
1008
3563
|
body?.addEventListener(
|
|
@@ -1012,45 +3567,28 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1012
3567
|
},
|
|
1013
3568
|
true
|
|
1014
3569
|
);
|
|
3570
|
+
editor.Canvas.getDocument()?.addEventListener("keydown", onBlockSearchKey);
|
|
3571
|
+
showBodyInLayers();
|
|
1015
3572
|
});
|
|
1016
3573
|
editor.on("component:selected", (c) => {
|
|
3574
|
+
keepStructureOpen();
|
|
1017
3575
|
if (selectionSource.current === "canvas") setSidebarView("settings");
|
|
1018
3576
|
selectionSource.current = "other";
|
|
1019
3577
|
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
3578
|
});
|
|
1034
3579
|
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") ?? ""));
|
|
3580
|
+
buildCrumbs(editor.getSelected());
|
|
1043
3581
|
});
|
|
1044
|
-
|
|
3582
|
+
const baseline = createSaveBaseline();
|
|
1045
3583
|
const save = async () => {
|
|
1046
3584
|
const view = editor.getEditing()?.getView();
|
|
1047
3585
|
await view?.syncContent?.();
|
|
1048
3586
|
const mjml = editor.getHtml();
|
|
1049
|
-
if (mjml
|
|
3587
|
+
if (!baseline.changed(mjml)) return;
|
|
1050
3588
|
setSave("saving");
|
|
1051
3589
|
try {
|
|
1052
3590
|
await documents.save(docId, { mjml, projectData: editor.getProjectData() });
|
|
1053
|
-
|
|
3591
|
+
baseline.saved(mjml);
|
|
1054
3592
|
setSave("saved");
|
|
1055
3593
|
} catch (e) {
|
|
1056
3594
|
setSave("error");
|
|
@@ -1081,6 +3619,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1081
3619
|
});
|
|
1082
3620
|
}
|
|
1083
3621
|
const lock = (on) => {
|
|
3622
|
+
if (!turn.mounted) return;
|
|
1084
3623
|
readOnlyRef.current = on;
|
|
1085
3624
|
setLocked(on);
|
|
1086
3625
|
notifyState();
|
|
@@ -1106,24 +3645,36 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1106
3645
|
return;
|
|
1107
3646
|
}
|
|
1108
3647
|
lock(false);
|
|
3648
|
+
queue.schedule();
|
|
1109
3649
|
};
|
|
1110
3650
|
const reload = () => queue.run(async () => {
|
|
1111
3651
|
const fresh = await documents.get(docId);
|
|
1112
|
-
if (fresh.mjml !== stored) loadMjml(fresh.mjml);
|
|
3652
|
+
if (fresh.mjml !== baseline.stored) loadMjml(fresh.mjml);
|
|
1113
3653
|
});
|
|
1114
3654
|
const loadMjml = (mjml) => {
|
|
1115
3655
|
loadingRef.current = true;
|
|
3656
|
+
let repaired = false;
|
|
3657
|
+
const adds = !mjml || listSections(mjml).some((s) => !s.sectionId);
|
|
1116
3658
|
try {
|
|
1117
3659
|
queue.cancel();
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
3660
|
+
baseline.load(mjml, null);
|
|
3661
|
+
const source = sourceForEditor(mjml || STARTER_MJML);
|
|
3662
|
+
repaired = source.repaired;
|
|
3663
|
+
editor.setComponents(source.mjml);
|
|
3664
|
+
editor.getWrapper()?.onAll((c) => {
|
|
3665
|
+
const illegal = illegalAttributes(tagOf6(c), c.getAttributes());
|
|
3666
|
+
if (illegal.length === 0) return;
|
|
3667
|
+
c.removeAttributes(illegal);
|
|
3668
|
+
repaired = true;
|
|
3669
|
+
});
|
|
3670
|
+
if (!repaired && !adds) baseline.load(mjml, editor.getHtml());
|
|
3671
|
+
showBodyInLayers();
|
|
3672
|
+
editor.UndoManager.clear();
|
|
1124
3673
|
} finally {
|
|
1125
3674
|
loadingRef.current = false;
|
|
1126
3675
|
}
|
|
3676
|
+
notifyState();
|
|
3677
|
+
if ((repaired || adds) && !readOnlyRef.current) queue.schedule();
|
|
1127
3678
|
};
|
|
1128
3679
|
const ensureHighlightStyles = () => {
|
|
1129
3680
|
const cdoc = editor.Canvas.getDocument();
|
|
@@ -1145,7 +3696,10 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1145
3696
|
cdoc.head.appendChild(style);
|
|
1146
3697
|
};
|
|
1147
3698
|
const sectionEl = (sectionId) => {
|
|
1148
|
-
|
|
3699
|
+
let comp;
|
|
3700
|
+
editor.getWrapper()?.onAll((c) => {
|
|
3701
|
+
if (!comp && isSection(c) && sectionIdOf(c) === sectionId) comp = c;
|
|
3702
|
+
});
|
|
1149
3703
|
return comp?.getEl?.() ?? void 0;
|
|
1150
3704
|
};
|
|
1151
3705
|
const compiledHtml = () => {
|
|
@@ -1156,9 +3710,9 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1156
3710
|
return editor.getHtml();
|
|
1157
3711
|
}
|
|
1158
3712
|
};
|
|
3713
|
+
turn.ready((on) => void applyReadOnly.current(on));
|
|
1159
3714
|
try {
|
|
1160
|
-
|
|
1161
|
-
loadMjml(doc.mjml);
|
|
3715
|
+
await queue.run(async () => loadMjml((await documents.get(docId)).mjml));
|
|
1162
3716
|
} catch (e) {
|
|
1163
3717
|
toast2.error(labels.documentLoadFailed);
|
|
1164
3718
|
console.error(e);
|
|
@@ -1210,10 +3764,10 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1210
3764
|
}
|
|
1211
3765
|
});
|
|
1212
3766
|
};
|
|
1213
|
-
return /* @__PURE__ */
|
|
3767
|
+
return /* @__PURE__ */ jsx8(
|
|
1214
3768
|
GjsEditor,
|
|
1215
3769
|
{
|
|
1216
|
-
grapesjs,
|
|
3770
|
+
grapesjs: grapesjs2,
|
|
1217
3771
|
className: "h-full",
|
|
1218
3772
|
options: {
|
|
1219
3773
|
height: "100%",
|
|
@@ -1222,12 +3776,13 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1222
3776
|
// app.grapesjs.com and leave a session-storage entry, which a host would then have
|
|
1223
3777
|
// to disclose and, for the entry, ask consent for.
|
|
1224
3778
|
telemetry: false,
|
|
3779
|
+
canvasCss: CANVAS_CSS,
|
|
1225
3780
|
fromElement: false,
|
|
1226
3781
|
panels: { defaults: [] },
|
|
1227
3782
|
blockManager: { appendTo: "#gjs-blocks" },
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
layerManager: { appendTo: "#gjs-layers" },
|
|
3783
|
+
// The eye hides a layer by writing `display="none"` into the MJML, which is illegal.
|
|
3784
|
+
// The root row is mj-body, which the sidebar reaches as "Email settings" instead.
|
|
3785
|
+
layerManager: { appendTo: "#gjs-layers", hidable: false, showWrapper: false },
|
|
1231
3786
|
assetManager: {
|
|
1232
3787
|
// Never inline files: mail clients block `data:` images (see ImageLibrary).
|
|
1233
3788
|
embedAsBase64: false,
|
|
@@ -1241,115 +3796,118 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
1241
3796
|
close: () => setPicker(null)
|
|
1242
3797
|
}
|
|
1243
3798
|
},
|
|
3799
|
+
canvas: {
|
|
3800
|
+
customBadgeLabel: badgeLabel(labels),
|
|
3801
|
+
// A file or text dragged in from outside lands as `<img>` or `<div>`, which no MJML
|
|
3802
|
+
// validator accepts; drags from the palette are unaffected.
|
|
3803
|
+
allowExternalDrop: false
|
|
3804
|
+
},
|
|
1244
3805
|
deviceManager: {
|
|
1245
3806
|
devices: [
|
|
1246
3807
|
{ id: "desktop", name: "Desktop", width: "" },
|
|
1247
3808
|
{ id: "mobile", name: "Mobile", width: "375px", widthMedia: "480px" }
|
|
1248
3809
|
]
|
|
1249
3810
|
},
|
|
1250
|
-
plugins: [
|
|
3811
|
+
plugins: [
|
|
3812
|
+
(editor) => grapesjsMJML2(editor, {
|
|
3813
|
+
blocks: BUILTIN_BLOCKS,
|
|
3814
|
+
block: builtinBlock(labels)
|
|
3815
|
+
})
|
|
3816
|
+
]
|
|
1251
3817
|
},
|
|
1252
3818
|
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,
|
|
3819
|
+
children: /* @__PURE__ */ jsxs7(
|
|
3820
|
+
"div",
|
|
3821
|
+
{
|
|
3822
|
+
className: "relative flex h-full min-h-0 flex-1",
|
|
3823
|
+
inert: readOnly,
|
|
3824
|
+
onKeyDown: (e) => onBlockSearchKey(e.nativeEvent),
|
|
3825
|
+
children: [
|
|
3826
|
+
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: [
|
|
3827
|
+
/* @__PURE__ */ jsx8(Spinner, {}),
|
|
3828
|
+
" ",
|
|
3829
|
+
labels.agentWorking
|
|
3830
|
+
] }) }),
|
|
3831
|
+
/* @__PURE__ */ jsx8(
|
|
3832
|
+
LeftSidebar,
|
|
1284
3833
|
{
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
setPicker(null);
|
|
1290
|
-
},
|
|
1291
|
-
onClose: () => {
|
|
1292
|
-
setPicker(null);
|
|
1293
|
-
editorRef.current?.AssetManager.close();
|
|
1294
|
-
}
|
|
3834
|
+
view: sidebarView,
|
|
3835
|
+
onViewChange: setSidebarView,
|
|
3836
|
+
markLayersSource: () => selectionSource.current = "layers",
|
|
3837
|
+
blockSearchRef
|
|
1295
3838
|
}
|
|
1296
3839
|
),
|
|
1297
|
-
|
|
1298
|
-
/* @__PURE__ */
|
|
1299
|
-
|
|
3840
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
|
|
3841
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative min-h-0 flex-1", children: [
|
|
3842
|
+
/* @__PURE__ */ jsx8(Canvas, { className: "h-full" }),
|
|
3843
|
+
/* @__PURE__ */ jsx8(
|
|
3844
|
+
CanvasComments,
|
|
3845
|
+
{
|
|
3846
|
+
docId,
|
|
3847
|
+
composeTarget,
|
|
3848
|
+
onComposeConsumed: () => setComposeTarget(null),
|
|
3849
|
+
refreshSignal: commentsRefresh,
|
|
3850
|
+
onOpenCountChange
|
|
3851
|
+
}
|
|
3852
|
+
),
|
|
3853
|
+
/* @__PURE__ */ jsx8(
|
|
3854
|
+
ImagePicker,
|
|
3855
|
+
{
|
|
3856
|
+
open: picker !== null,
|
|
3857
|
+
currentUrl: picker?.current,
|
|
3858
|
+
onSelect: (url) => {
|
|
3859
|
+
if (!readOnlyRef.current) picker?.select(url);
|
|
3860
|
+
setPicker(null);
|
|
3861
|
+
},
|
|
3862
|
+
onClose: () => {
|
|
3863
|
+
setPicker(null);
|
|
3864
|
+
editorRef.current?.AssetManager.close();
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
),
|
|
3868
|
+
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: [
|
|
3869
|
+
/* @__PURE__ */ jsx8(Spinner, {}),
|
|
3870
|
+
" ",
|
|
3871
|
+
labels.loadingEditor
|
|
3872
|
+
] })
|
|
3873
|
+
] }),
|
|
3874
|
+
/* @__PURE__ */ jsx8(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
|
|
1300
3875
|
] })
|
|
1301
|
-
]
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
] })
|
|
3876
|
+
]
|
|
3877
|
+
}
|
|
3878
|
+
)
|
|
1305
3879
|
}
|
|
1306
3880
|
);
|
|
1307
3881
|
}
|
|
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
3882
|
function LeftSidebar({
|
|
1331
3883
|
view,
|
|
1332
3884
|
onViewChange,
|
|
1333
3885
|
markLayersSource,
|
|
1334
|
-
|
|
1335
|
-
onChooseImage
|
|
3886
|
+
blockSearchRef
|
|
1336
3887
|
}) {
|
|
1337
|
-
const editor =
|
|
3888
|
+
const editor = useEditorMaybe2();
|
|
1338
3889
|
const labels = useLabels();
|
|
1339
|
-
const [layersExpanded, setLayersExpanded] =
|
|
1340
|
-
const [blockSearch, setBlockSearch] =
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
if (body)
|
|
1346
|
-
}
|
|
1347
|
-
|
|
3890
|
+
const [layersExpanded, setLayersExpanded] = useState5(false);
|
|
3891
|
+
const [blockSearch, setBlockSearch] = useState5("");
|
|
3892
|
+
const [noBlockMatches, setNoBlockMatches] = useState5(false);
|
|
3893
|
+
const selectBody = () => {
|
|
3894
|
+
let body;
|
|
3895
|
+
editor?.getWrapper()?.onAll((c) => {
|
|
3896
|
+
if (!body && tagOf6(c) === "mj-body") body = c;
|
|
3897
|
+
});
|
|
3898
|
+
if (body) editor?.select(body);
|
|
1348
3899
|
};
|
|
1349
3900
|
const toggleLayers = (expand) => {
|
|
1350
3901
|
setLayersExpanded(expand);
|
|
1351
3902
|
editor?.getWrapper()?.onAll((c) => c.set("open", expand));
|
|
1352
3903
|
};
|
|
3904
|
+
const onTileKey = (e) => {
|
|
3905
|
+
const tile = e.target.closest("[data-block]");
|
|
3906
|
+
const block = tile && editor?.Blocks.get(tile.dataset.block ?? "");
|
|
3907
|
+
if (!editor || !block || !addsBlock(e)) return;
|
|
3908
|
+
e.preventDefault();
|
|
3909
|
+
if (insertBlock(editor, block)) toast2(labels.blockAdded(tile.textContent?.trim() ?? ""));
|
|
3910
|
+
};
|
|
1353
3911
|
const filterBlocks = (q) => {
|
|
1354
3912
|
setBlockSearch(q);
|
|
1355
3913
|
const root = document.getElementById("gjs-blocks");
|
|
@@ -1365,17 +3923,22 @@ function LeftSidebar({
|
|
|
1365
3923
|
);
|
|
1366
3924
|
cat.style.display = anyVisible ? "" : "none";
|
|
1367
3925
|
});
|
|
3926
|
+
setNoBlockMatches(
|
|
3927
|
+
[...root.querySelectorAll(".gjs-block")].every(
|
|
3928
|
+
(b) => b.style.display === "none"
|
|
3929
|
+
)
|
|
3930
|
+
);
|
|
1368
3931
|
};
|
|
1369
|
-
return /* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1371
|
-
["blocks",
|
|
1372
|
-
["settings",
|
|
1373
|
-
["layers",
|
|
1374
|
-
].map(([value, label]) => /* @__PURE__ */
|
|
3932
|
+
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: [
|
|
3933
|
+
/* @__PURE__ */ jsx8("div", { className: "flex border-b border-panel-border", children: [
|
|
3934
|
+
["blocks", labels.tabBlocks],
|
|
3935
|
+
["settings", labels.tabSettings],
|
|
3936
|
+
["layers", labels.tabLayers]
|
|
3937
|
+
].map(([value, label]) => /* @__PURE__ */ jsx8(
|
|
1375
3938
|
"button",
|
|
1376
3939
|
{
|
|
1377
3940
|
type: "button",
|
|
1378
|
-
onClick: () =>
|
|
3941
|
+
onClick: () => onViewChange(value),
|
|
1379
3942
|
className: cn(
|
|
1380
3943
|
"relative flex-1 px-3 py-2.5 text-sm transition-colors",
|
|
1381
3944
|
"after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:transition-colors",
|
|
@@ -1385,70 +3948,57 @@ function LeftSidebar({
|
|
|
1385
3948
|
},
|
|
1386
3949
|
value
|
|
1387
3950
|
)) }),
|
|
1388
|
-
/* @__PURE__ */
|
|
1389
|
-
/* @__PURE__ */
|
|
1390
|
-
/* @__PURE__ */
|
|
1391
|
-
/* @__PURE__ */
|
|
3951
|
+
/* @__PURE__ */ jsxs7("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "blocks" && "hidden"), children: [
|
|
3952
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative border-b border-panel-border p-2", children: [
|
|
3953
|
+
/* @__PURE__ */ jsx8(Search, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg" }),
|
|
3954
|
+
/* @__PURE__ */ jsx8(
|
|
1392
3955
|
Input,
|
|
1393
3956
|
{
|
|
3957
|
+
ref: blockSearchRef,
|
|
1394
3958
|
value: blockSearch,
|
|
1395
3959
|
onChange: (e) => filterBlocks(e.target.value),
|
|
1396
3960
|
placeholder: labels.searchBlocks,
|
|
1397
3961
|
className: "h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg"
|
|
1398
3962
|
}
|
|
1399
3963
|
),
|
|
1400
|
-
/* @__PURE__ */
|
|
1401
|
-
] }),
|
|
1402
|
-
/* @__PURE__ */ jsx5("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
|
|
1403
|
-
] }),
|
|
1404
|
-
/* @__PURE__ */ jsxs4("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: [
|
|
1405
|
-
selectedImageSrc !== null && /* @__PURE__ */ jsxs4("div", { className: "border-b border-panel-border p-3", children: [
|
|
1406
|
-
/* @__PURE__ */ jsxs4(
|
|
1407
|
-
"button",
|
|
1408
|
-
{
|
|
1409
|
-
type: "button",
|
|
1410
|
-
onClick: onChooseImage,
|
|
1411
|
-
title: labels.chooseImage,
|
|
1412
|
-
className: "group relative block w-full overflow-hidden rounded-lg border border-panel-border bg-panel-elevated",
|
|
1413
|
-
children: [
|
|
1414
|
-
selectedImageSrc ? /* @__PURE__ */ jsx5(
|
|
1415
|
-
"img",
|
|
1416
|
-
{
|
|
1417
|
-
src: selectedImageSrc,
|
|
1418
|
-
alt: "",
|
|
1419
|
-
className: "h-32 w-full bg-black/25 object-contain"
|
|
1420
|
-
}
|
|
1421
|
-
) : /* @__PURE__ */ jsx5("span", { className: "flex h-32 items-center justify-center text-panel-muted-fg", children: /* @__PURE__ */ jsx5(ImageIcon2, { className: "size-8" }) }),
|
|
1422
|
-
/* @__PURE__ */ jsx5("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 text-sm font-medium text-white opacity-0 transition group-hover:opacity-100", children: labels.chooseImage })
|
|
1423
|
-
]
|
|
1424
|
-
}
|
|
1425
|
-
),
|
|
1426
|
-
/* @__PURE__ */ jsxs4(
|
|
1427
|
-
Button,
|
|
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
|
-
)
|
|
3964
|
+
/* @__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: "/" })
|
|
1439
3965
|
] }),
|
|
1440
|
-
/* @__PURE__ */
|
|
1441
|
-
/* @__PURE__ */
|
|
3966
|
+
noBlockMatches && /* @__PURE__ */ jsx8("p", { className: "px-3 py-6 text-center text-xs text-panel-muted-fg", children: labels.noBlocksMatch }),
|
|
3967
|
+
/* @__PURE__ */ jsx8("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto", onKeyDown: onTileKey })
|
|
1442
3968
|
] }),
|
|
1443
|
-
/* @__PURE__ */
|
|
3969
|
+
/* @__PURE__ */ jsx8("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: /* @__PURE__ */ jsx8(
|
|
3970
|
+
Inspector,
|
|
3971
|
+
{
|
|
3972
|
+
onEmailSettings: selectBody,
|
|
3973
|
+
empty: /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center gap-3 px-6 py-10 text-center", children: [
|
|
3974
|
+
/* @__PURE__ */ jsx8(MousePointerClick, { className: "size-6 text-panel-muted-fg" }),
|
|
3975
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm text-panel-muted-fg", children: labels.settingsEmpty }),
|
|
3976
|
+
/* @__PURE__ */ jsxs7(
|
|
3977
|
+
Button,
|
|
3978
|
+
{
|
|
3979
|
+
type: "button",
|
|
3980
|
+
size: "sm",
|
|
3981
|
+
variant: "outline",
|
|
3982
|
+
className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
|
|
3983
|
+
onClick: selectBody,
|
|
3984
|
+
children: [
|
|
3985
|
+
/* @__PURE__ */ jsx8(Mail, {}),
|
|
3986
|
+
labels.emailSettings
|
|
3987
|
+
]
|
|
3988
|
+
}
|
|
3989
|
+
)
|
|
3990
|
+
] })
|
|
3991
|
+
}
|
|
3992
|
+
) }),
|
|
3993
|
+
/* @__PURE__ */ jsxs7(
|
|
1444
3994
|
"div",
|
|
1445
3995
|
{
|
|
1446
3996
|
className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
|
|
1447
3997
|
onMouseDownCapture: markLayersSource,
|
|
1448
3998
|
children: [
|
|
1449
|
-
/* @__PURE__ */
|
|
1450
|
-
/* @__PURE__ */
|
|
1451
|
-
/* @__PURE__ */
|
|
3999
|
+
/* @__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: [
|
|
4000
|
+
/* @__PURE__ */ jsx8("span", { children: labels.expandAll }),
|
|
4001
|
+
/* @__PURE__ */ jsx8(
|
|
1452
4002
|
Switch,
|
|
1453
4003
|
{
|
|
1454
4004
|
checked: layersExpanded,
|
|
@@ -1457,7 +4007,16 @@ function LeftSidebar({
|
|
|
1457
4007
|
}
|
|
1458
4008
|
)
|
|
1459
4009
|
] }),
|
|
1460
|
-
/* @__PURE__ */
|
|
4010
|
+
/* @__PURE__ */ jsx8(
|
|
4011
|
+
"div",
|
|
4012
|
+
{
|
|
4013
|
+
id: "gjs-layers",
|
|
4014
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
4015
|
+
onDoubleClickCapture: (e) => {
|
|
4016
|
+
if (e.target.closest("[data-name]")) e.stopPropagation();
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
)
|
|
1461
4020
|
]
|
|
1462
4021
|
}
|
|
1463
4022
|
)
|
|
@@ -1468,9 +4027,9 @@ function Breadcrumb({
|
|
|
1468
4027
|
onSelect
|
|
1469
4028
|
}) {
|
|
1470
4029
|
const labels = useLabels();
|
|
1471
|
-
return /* @__PURE__ */
|
|
1472
|
-
i > 0 && /* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
4030
|
+
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: [
|
|
4031
|
+
i > 0 && /* @__PURE__ */ jsx8(ChevronRight3, { className: "size-3 opacity-40" }),
|
|
4032
|
+
/* @__PURE__ */ jsx8(
|
|
1474
4033
|
"button",
|
|
1475
4034
|
{
|
|
1476
4035
|
type: "button",
|
|
@@ -1479,12 +4038,13 @@ function Breadcrumb({
|
|
|
1479
4038
|
"rounded px-1.5 py-0.5 hover:bg-muted hover:text-foreground",
|
|
1480
4039
|
i === crumbs.length - 1 && "font-medium text-brand"
|
|
1481
4040
|
),
|
|
1482
|
-
children: crumbName(c)
|
|
4041
|
+
children: crumbName(c, labels)
|
|
1483
4042
|
}
|
|
1484
4043
|
)
|
|
1485
4044
|
] }, i)) });
|
|
1486
4045
|
}
|
|
1487
4046
|
export {
|
|
1488
|
-
EmailEditor
|
|
4047
|
+
EmailEditor,
|
|
4048
|
+
blockProblems
|
|
1489
4049
|
};
|
|
1490
4050
|
//# sourceMappingURL=canvas.js.map
|