best-email-builder 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +11 -1
- package/dist/{chunk-2DZ6BRIJ.js → chunk-MTALS5GC.js} +98 -3
- package/dist/chunk-MTALS5GC.js.map +1 -0
- package/dist/index.js +963 -295
- package/dist/index.js.map +1 -1
- package/dist/server.js +13 -13
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +1 -1
- package/dist/types/blocks/columns/index.d.ts +3 -0
- package/dist/types/blocks/heading/index.d.ts +3 -0
- package/dist/types/blocks/hero/index.d.ts +3 -0
- package/dist/types/blocks/navbar/index.d.ts +6 -0
- package/dist/types/blocks/social/index.d.ts +13 -0
- package/dist/types/core/EmailEditor.d.ts +12 -4
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/features/templates/TemplateGallery.d.ts +3 -0
- package/dist/types/features/templates/gallery.d.ts +41 -0
- package/dist/types/features/templates/index.d.ts +2 -0
- package/dist/types/features/templates/starter.d.ts +22 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/server/compileMjml.d.ts +3 -3
- package/dist/types/server/render.d.ts +4 -4
- package/dist/types/server/templates.d.ts +2 -2
- package/dist/types/ui/BlockSidebar.d.ts +2 -1
- package/dist/types/ui/Drawer.d.ts +19 -0
- package/dist/types/ui/EmailBuilder.d.ts +2 -0
- package/dist/types/ui/PropertiesPanel.d.ts +2 -1
- package/dist/types/ui/Toolbar.d.ts +9 -2
- package/dist/types/ui/useLayoutWidth.d.ts +10 -0
- package/package.json +6 -6
- package/dist/chunk-2DZ6BRIJ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
unknownVariables,
|
|
30
30
|
usedVariables,
|
|
31
31
|
validateCustomVariable
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-MTALS5GC.js";
|
|
33
33
|
|
|
34
34
|
// src/core/events.js
|
|
35
35
|
function createEmitter({ onError } = {}) {
|
|
@@ -136,7 +136,7 @@ function createEmailEditor({ adapter, blocks, features = null }) {
|
|
|
136
136
|
return resolved;
|
|
137
137
|
}
|
|
138
138
|
function blockForTag(tagName) {
|
|
139
|
-
const all = blocks.list();
|
|
139
|
+
const all = blocks.list((b) => !b.insertOnly);
|
|
140
140
|
return all.find((b) => b.tagName === tagName && b.kind === "structure") || all.find((b) => b.tagName === tagName) || null;
|
|
141
141
|
}
|
|
142
142
|
function toEmailSelection(selection) {
|
|
@@ -302,9 +302,14 @@ function createEmailEditor({ adapter, blocks, features = null }) {
|
|
|
302
302
|
requireReady("exportMjml");
|
|
303
303
|
return adapter.exportMjml();
|
|
304
304
|
},
|
|
305
|
-
|
|
305
|
+
/**
|
|
306
|
+
* The email (or the given MJML) compiled by the CANVAS compiler — for
|
|
307
|
+
* display only (thumbnails, inspection). The HTML that is stored and sent
|
|
308
|
+
* is always compiled by the server.
|
|
309
|
+
*/
|
|
310
|
+
exportHtml(mjml = null) {
|
|
306
311
|
requireReady("exportHtml");
|
|
307
|
-
return adapter.exportHtml();
|
|
312
|
+
return adapter.exportHtml(mjml === null || mjml === void 0 ? null : String(mjml));
|
|
308
313
|
},
|
|
309
314
|
setDevice(device) {
|
|
310
315
|
requireReady("setDevice");
|
|
@@ -439,10 +444,10 @@ function isInteropLayer(value) {
|
|
|
439
444
|
|
|
440
445
|
// src/adapters/grapesjs/GrapesJsAdapter.js
|
|
441
446
|
var ENGINE_NAME = "grapesjs-mjml";
|
|
442
|
-
var ENGINE_VERSION = "0.
|
|
447
|
+
var ENGINE_VERSION = "0.23.6";
|
|
443
448
|
var BLOCK_PREFIX = "eb-";
|
|
444
449
|
var LOCKED_TYPES = /* @__PURE__ */ new Set(["mjml", "mj-head", "mj-body", "wrapper"]);
|
|
445
|
-
var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button"]);
|
|
450
|
+
var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button", "mj-navbar-link"]);
|
|
446
451
|
var RICH_TYPES = /* @__PURE__ */ new Set(["mj-text", "mj-button"]);
|
|
447
452
|
var ENGINE_LOCALES = {
|
|
448
453
|
fr: () => Promise.all([import("grapesjs/locale/fr.mjs"), import("grapesjs-mjml/locale/fr")])
|
|
@@ -581,11 +586,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
|
|
|
581
586
|
function placementFor(block, target) {
|
|
582
587
|
const root = body();
|
|
583
588
|
if (!root) throw new Error("GrapesJsAdapter: email body not found");
|
|
584
|
-
const
|
|
589
|
+
const section2 = target ? closest(target, ["mj-section", "mj-wrapper", "mj-hero"]) : null;
|
|
585
590
|
const afterSection = () => {
|
|
586
|
-
if (
|
|
587
|
-
const coll =
|
|
588
|
-
return { collection: coll, at: coll.indexOf(
|
|
591
|
+
if (section2 && section2.parent()) {
|
|
592
|
+
const coll = section2.parent().components();
|
|
593
|
+
return { collection: coll, at: coll.indexOf(section2) + 1, wrap: false };
|
|
589
594
|
}
|
|
590
595
|
return { collection: root.components(), at: root.components().length, wrap: false };
|
|
591
596
|
};
|
|
@@ -594,11 +599,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
|
|
|
594
599
|
if (type === "mj-column") {
|
|
595
600
|
return { collection: target.components(), at: target.components().length, wrap: false };
|
|
596
601
|
}
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
602
|
+
const column2 = target ? closest(target, ["mj-column"]) : null;
|
|
603
|
+
if (column2 && target !== column2) {
|
|
599
604
|
let child = target;
|
|
600
|
-
while (child.parent() && child.parent() !==
|
|
601
|
-
const coll =
|
|
605
|
+
while (child.parent() && child.parent() !== column2) child = child.parent();
|
|
606
|
+
const coll = column2.components();
|
|
602
607
|
return { collection: coll, at: coll.indexOf(child) + 1, wrap: false };
|
|
603
608
|
}
|
|
604
609
|
return { ...afterSection(), wrap: true };
|
|
@@ -813,8 +818,9 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
|
|
|
813
818
|
exportMjml() {
|
|
814
819
|
return String(requireEditor().runCommand("mjml-code") || "").replace(PLACEHOLDER_SRC_ATTR, "").trim();
|
|
815
820
|
},
|
|
816
|
-
exportHtml() {
|
|
817
|
-
const
|
|
821
|
+
exportHtml(mjml = null) {
|
|
822
|
+
const source = mjml ?? adapter.exportMjml();
|
|
823
|
+
const result = requireEditor().runCommand("mjml-code-to-html", { mjml: source }) || {};
|
|
818
824
|
return {
|
|
819
825
|
html: String(result.html || ""),
|
|
820
826
|
errors: (result.errors || []).map((e) => ({
|
|
@@ -981,6 +987,30 @@ var sectionBlock = {
|
|
|
981
987
|
order: 901,
|
|
982
988
|
properties: [
|
|
983
989
|
prop.background("background-color", "Background"),
|
|
990
|
+
// A background image keeps the color under it: shown when images are blocked.
|
|
991
|
+
{ key: "background-url", label: "Background image", type: "image", group: GROUPS.colors },
|
|
992
|
+
{
|
|
993
|
+
key: "background-size",
|
|
994
|
+
label: "Image size",
|
|
995
|
+
type: "select",
|
|
996
|
+
group: GROUPS.colors,
|
|
997
|
+
options: [
|
|
998
|
+
{ value: "cover", label: "Fill the section" },
|
|
999
|
+
{ value: "contain", label: "Whole image" },
|
|
1000
|
+
{ value: "auto", label: "Original size" }
|
|
1001
|
+
]
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
key: "background-position",
|
|
1005
|
+
label: "Image position",
|
|
1006
|
+
type: "select",
|
|
1007
|
+
group: GROUPS.colors,
|
|
1008
|
+
options: [
|
|
1009
|
+
{ value: "center center", label: "Center" },
|
|
1010
|
+
{ value: "center top", label: "Top" },
|
|
1011
|
+
{ value: "center bottom", label: "Bottom" }
|
|
1012
|
+
]
|
|
1013
|
+
},
|
|
984
1014
|
prop.padding("padding", "Padding"),
|
|
985
1015
|
prop.radius(24)
|
|
986
1016
|
]
|
|
@@ -1029,6 +1059,16 @@ var textBlock = {
|
|
|
1029
1059
|
prop.fontFamily(),
|
|
1030
1060
|
prop.fontSize(10, 48),
|
|
1031
1061
|
prop.lineHeight(),
|
|
1062
|
+
{
|
|
1063
|
+
key: "font-weight",
|
|
1064
|
+
label: "Weight",
|
|
1065
|
+
type: "select",
|
|
1066
|
+
group: GROUPS.typography,
|
|
1067
|
+
options: [
|
|
1068
|
+
{ value: "normal", label: "Normal" },
|
|
1069
|
+
{ value: "bold", label: "Bold" }
|
|
1070
|
+
]
|
|
1071
|
+
},
|
|
1032
1072
|
prop.color("Text"),
|
|
1033
1073
|
prop.background("container-background-color", "Background"),
|
|
1034
1074
|
{ ...prop.align(), options: [...prop.align().options || [], { value: "justify", label: "Justify" }] },
|
|
@@ -1172,6 +1212,133 @@ var spacerBlock = {
|
|
|
1172
1212
|
]
|
|
1173
1213
|
};
|
|
1174
1214
|
|
|
1215
|
+
// src/blocks/heading/index.js
|
|
1216
|
+
var MJML6 = `<mj-text font-family="${FONT_STACK}" font-size="26px" line-height="34px" font-weight="bold" color="${PALETTE.navy}" align="left">Your headline</mj-text>`;
|
|
1217
|
+
var headingBlock = {
|
|
1218
|
+
id: "heading",
|
|
1219
|
+
label: "Heading",
|
|
1220
|
+
description: "Large bold title",
|
|
1221
|
+
icon: "Heading",
|
|
1222
|
+
kind: "content",
|
|
1223
|
+
tagName: "mj-text",
|
|
1224
|
+
insertOnly: true,
|
|
1225
|
+
order: 5,
|
|
1226
|
+
mjml: MJML6,
|
|
1227
|
+
sectionMjml: inWhiteSection(MJML6),
|
|
1228
|
+
editableContent: true,
|
|
1229
|
+
properties: []
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
// src/blocks/social/index.js
|
|
1233
|
+
var SOCIAL_NETWORKS = [
|
|
1234
|
+
{ value: "facebook-noshare", label: "Facebook", href: "https://www.facebook.com/" },
|
|
1235
|
+
{ value: "instagram-noshare", label: "Instagram", href: "https://www.instagram.com/" },
|
|
1236
|
+
{ value: "linkedin-noshare", label: "LinkedIn", href: "https://www.linkedin.com/" },
|
|
1237
|
+
{ value: "x-noshare", label: "X", href: "https://x.com/" },
|
|
1238
|
+
{ value: "youtube-noshare", label: "YouTube", href: "https://www.youtube.com/" },
|
|
1239
|
+
{ value: "github-noshare", label: "GitHub", href: "https://github.com/" }
|
|
1240
|
+
];
|
|
1241
|
+
var DEFAULT_NETWORKS = ["facebook-noshare", "instagram-noshare", "linkedin-noshare", "x-noshare"];
|
|
1242
|
+
var MJML7 = '<mj-social mode="horizontal" align="center" icon-size="28px" border-radius="14px" inner-padding="6px">' + DEFAULT_NETWORKS.map((name) => {
|
|
1243
|
+
const network = SOCIAL_NETWORKS.find((n) => n.value === name);
|
|
1244
|
+
return `<mj-social-element name="${name}" href="${network?.href}"></mj-social-element>`;
|
|
1245
|
+
}).join("") + "</mj-social>";
|
|
1246
|
+
var socialBlock = {
|
|
1247
|
+
id: "social",
|
|
1248
|
+
label: "Social links",
|
|
1249
|
+
description: "Icons linking to your profiles",
|
|
1250
|
+
icon: "Share2",
|
|
1251
|
+
kind: "content",
|
|
1252
|
+
tagName: "mj-social",
|
|
1253
|
+
order: 60,
|
|
1254
|
+
mjml: MJML7,
|
|
1255
|
+
sectionMjml: inWhiteSection(MJML7),
|
|
1256
|
+
properties: [
|
|
1257
|
+
{ key: "icon-size", label: "Icon size", type: "size", group: GROUPS.layout, min: 16, max: 48, unit: "px" },
|
|
1258
|
+
prop.radius(24),
|
|
1259
|
+
prop.align(),
|
|
1260
|
+
{
|
|
1261
|
+
key: "mode",
|
|
1262
|
+
label: "Direction",
|
|
1263
|
+
type: "select",
|
|
1264
|
+
group: GROUPS.layout,
|
|
1265
|
+
options: [
|
|
1266
|
+
{ value: "horizontal", label: "Horizontal" },
|
|
1267
|
+
{ value: "vertical", label: "Vertical" }
|
|
1268
|
+
]
|
|
1269
|
+
},
|
|
1270
|
+
prop.padding("inner-padding", "Space around each icon"),
|
|
1271
|
+
prop.padding()
|
|
1272
|
+
]
|
|
1273
|
+
};
|
|
1274
|
+
var socialElementBlock = {
|
|
1275
|
+
id: "social-element",
|
|
1276
|
+
label: "Social link",
|
|
1277
|
+
icon: "Link",
|
|
1278
|
+
kind: "structure",
|
|
1279
|
+
tagName: "mj-social-element",
|
|
1280
|
+
order: 903,
|
|
1281
|
+
properties: [
|
|
1282
|
+
{
|
|
1283
|
+
key: "name",
|
|
1284
|
+
label: "Network",
|
|
1285
|
+
type: "select",
|
|
1286
|
+
group: GROUPS.content,
|
|
1287
|
+
options: SOCIAL_NETWORKS.map(({ value, label }) => ({ value, label }))
|
|
1288
|
+
},
|
|
1289
|
+
prop.href("Profile URL"),
|
|
1290
|
+
prop.background("background-color", "Icon background")
|
|
1291
|
+
]
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// src/blocks/navbar/index.js
|
|
1295
|
+
function link(label, href) {
|
|
1296
|
+
return `<mj-navbar-link href="${href}" font-family="${FONT_STACK}" font-size="14px" color="${PALETTE.navy}" padding-top="8px" padding-right="12px" padding-bottom="8px" padding-left="12px">${label}</mj-navbar-link>`;
|
|
1297
|
+
}
|
|
1298
|
+
var MJML8 = '<mj-navbar align="center">' + link("Shop", "https://example.com/shop") + link("Blog", "https://example.com/blog") + link("Contact", "https://example.com/contact") + "</mj-navbar>";
|
|
1299
|
+
var navbarBlock = {
|
|
1300
|
+
id: "navbar",
|
|
1301
|
+
label: "Menu",
|
|
1302
|
+
description: "Row of links to your site",
|
|
1303
|
+
icon: "Menu",
|
|
1304
|
+
kind: "content",
|
|
1305
|
+
tagName: "mj-navbar",
|
|
1306
|
+
order: 70,
|
|
1307
|
+
mjml: MJML8,
|
|
1308
|
+
sectionMjml: inWhiteSection(MJML8),
|
|
1309
|
+
properties: [prop.align()]
|
|
1310
|
+
};
|
|
1311
|
+
var navbarLinkBlock = {
|
|
1312
|
+
id: "navbar-link",
|
|
1313
|
+
label: "Menu link",
|
|
1314
|
+
icon: "Link",
|
|
1315
|
+
kind: "structure",
|
|
1316
|
+
tagName: "mj-navbar-link",
|
|
1317
|
+
order: 904,
|
|
1318
|
+
editableContent: true,
|
|
1319
|
+
properties: [
|
|
1320
|
+
prop.href("Link URL"),
|
|
1321
|
+
prop.color("Text"),
|
|
1322
|
+
prop.fontFamily(),
|
|
1323
|
+
prop.fontSize(10, 24),
|
|
1324
|
+
prop.padding()
|
|
1325
|
+
]
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
// src/blocks/hero/index.js
|
|
1329
|
+
var MJML9 = `<mj-section background-color="${PALETTE.navy}" background-size="cover" background-position="center center" background-repeat="no-repeat" padding-top="56px" padding-bottom="56px"><mj-column><mj-text font-family="${FONT_STACK}" font-size="30px" line-height="38px" font-weight="bold" color="${PALETTE.white}" align="center" padding-left="32px" padding-right="32px">Your big announcement</mj-text><mj-text font-family="${FONT_STACK}" font-size="16px" line-height="24px" color="${PALETTE.border}" align="center" padding-left="32px" padding-right="32px">One sentence that makes people want to read on.</mj-text><mj-button href="${DEFAULT_BUTTON_URL}" background-color="${PALETTE.white}" color="${PALETTE.navy}" font-family="${FONT_STACK}" font-size="15px" font-weight="bold" border-radius="6px" inner-padding="12px 28px" align="center">Learn more</mj-button></mj-column></mj-section>`;
|
|
1330
|
+
var heroBlock = {
|
|
1331
|
+
id: "hero",
|
|
1332
|
+
label: "Hero banner",
|
|
1333
|
+
description: "Background image with a title and a button",
|
|
1334
|
+
icon: "GalleryHorizontal",
|
|
1335
|
+
kind: "layout",
|
|
1336
|
+
tagName: "mj-section",
|
|
1337
|
+
order: 110,
|
|
1338
|
+
mjml: MJML9,
|
|
1339
|
+
properties: []
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1175
1342
|
// src/blocks/columns/index.js
|
|
1176
1343
|
function sectionWith(count) {
|
|
1177
1344
|
const columns = Array.from(
|
|
@@ -1196,20 +1363,41 @@ function layout(count, icon) {
|
|
|
1196
1363
|
var oneColumnBlock = layout(1, "Square");
|
|
1197
1364
|
var twoColumnsBlock = layout(2, "Columns2");
|
|
1198
1365
|
var threeColumnsBlock = layout(3, "Columns3");
|
|
1366
|
+
var fourColumnsBlock = layout(4, "Columns4");
|
|
1367
|
+
var IMAGE_TEXT_MJML = `<mj-section background-color="${PALETTE.surface}"><mj-column vertical-align="middle"><mj-image align="center"></mj-image></mj-column><mj-column vertical-align="middle"><mj-text font-family="${FONT_STACK}" font-size="20px" line-height="28px" font-weight="bold" color="${PALETTE.navy}" align="left">Title</mj-text>` + textMjml("A few words about it.") + `<mj-button href="${DEFAULT_BUTTON_URL}" background-color="${PALETTE.navy}" color="${PALETTE.white}" font-family="${FONT_STACK}" font-size="14px" font-weight="bold" border-radius="6px" inner-padding="10px 22px" align="left">Learn more</mj-button></mj-column></mj-section>`;
|
|
1368
|
+
var imageTextBlock = {
|
|
1369
|
+
id: "image-text",
|
|
1370
|
+
label: "Image + text",
|
|
1371
|
+
description: "Image beside a title, text and button",
|
|
1372
|
+
icon: "LayoutPanelLeft",
|
|
1373
|
+
kind: "layout",
|
|
1374
|
+
tagName: "mj-section",
|
|
1375
|
+
order: 105,
|
|
1376
|
+
mjml: IMAGE_TEXT_MJML,
|
|
1377
|
+
properties: []
|
|
1378
|
+
};
|
|
1199
1379
|
|
|
1200
1380
|
// src/blocks/index.js
|
|
1201
1381
|
var BLOCKS = [
|
|
1382
|
+
headingBlock,
|
|
1202
1383
|
textBlock,
|
|
1203
1384
|
imageBlock,
|
|
1204
1385
|
buttonBlock,
|
|
1205
1386
|
dividerBlock,
|
|
1206
1387
|
spacerBlock,
|
|
1388
|
+
socialBlock,
|
|
1389
|
+
navbarBlock,
|
|
1207
1390
|
oneColumnBlock,
|
|
1208
1391
|
twoColumnsBlock,
|
|
1209
1392
|
threeColumnsBlock,
|
|
1393
|
+
fourColumnsBlock,
|
|
1394
|
+
imageTextBlock,
|
|
1395
|
+
heroBlock,
|
|
1210
1396
|
bodyBlock,
|
|
1211
1397
|
sectionBlock,
|
|
1212
|
-
columnBlock
|
|
1398
|
+
columnBlock,
|
|
1399
|
+
socialElementBlock,
|
|
1400
|
+
navbarLinkBlock
|
|
1213
1401
|
];
|
|
1214
1402
|
var emailBlocks = createRegistry("emailBlocks", BLOCKS);
|
|
1215
1403
|
|
|
@@ -2507,7 +2695,7 @@ function TestEmailButton({ context }) {
|
|
|
2507
2695
|
r.error ? ` \u2014 ${r.error}` : ""
|
|
2508
2696
|
] })
|
|
2509
2697
|
] }, r.email)),
|
|
2510
|
-
report.invalid.map((
|
|
2698
|
+
report.invalid.map((email2) => /* @__PURE__ */ jsx11("li", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Ignored (invalid): {email}", { email: email2 }) }, `invalid-${email2}`))
|
|
2511
2699
|
] }),
|
|
2512
2700
|
/* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
|
|
2513
2701
|
/* @__PURE__ */ jsx11(Button, { variant: "outline", onClick: () => setOpen(false), children: t("Close") }),
|
|
@@ -2529,17 +2717,9 @@ var testEmailFeature = {
|
|
|
2529
2717
|
isAvailable: (context) => typeof context.storage?.sendTest === "function"
|
|
2530
2718
|
};
|
|
2531
2719
|
|
|
2532
|
-
// src/features/
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
function isFeatureEnabled(id) {
|
|
2536
|
-
const feature = emailBuilderFeatures.get(id);
|
|
2537
|
-
return Boolean(feature && feature.enabled !== false);
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
// src/ui/EmailBuilder.jsx
|
|
2541
|
-
import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef7, useState as useState12 } from "react";
|
|
2542
|
-
import { AlertTriangle as AlertTriangle2, Loader2 as Loader25, Save } from "lucide-react";
|
|
2720
|
+
// src/features/templates/TemplateGallery.jsx
|
|
2721
|
+
import { useMemo as useMemo5, useState as useState8 } from "react";
|
|
2722
|
+
import { LayoutTemplate } from "lucide-react";
|
|
2543
2723
|
|
|
2544
2724
|
// src/features/templates/starter.js
|
|
2545
2725
|
function escapeXml(text) {
|
|
@@ -2547,17 +2727,23 @@ function escapeXml(text) {
|
|
|
2547
2727
|
}
|
|
2548
2728
|
var SIDE = 'padding-left="32px" padding-right="32px"';
|
|
2549
2729
|
var TEXT = `font-family="${FONT_STACK}" color="${PALETTE.ink}"`;
|
|
2550
|
-
function
|
|
2730
|
+
function senderHeaderMjml(sender, t) {
|
|
2731
|
+
const name = escapeXml(sender.name || t("Your company"));
|
|
2732
|
+
return sender.logoUrl ? `<mj-image src="${escapeXml(sender.logoUrl)}" alt="${name}" width="${Number(sender.logoWidth) || 140}px" align="left" ${SIDE}></mj-image>` : `<mj-text ${TEXT} font-size="18px" line-height="24px" font-weight="bold" ${SIDE}>${name}</mj-text>`;
|
|
2733
|
+
}
|
|
2734
|
+
function footerSectionMjml(sender, t) {
|
|
2551
2735
|
const name = escapeXml(sender.name || t("Your company"));
|
|
2552
2736
|
const footer = [name, sender.address ? escapeXml(sender.address) : ""].filter(Boolean).join(" \xB7 ");
|
|
2553
|
-
|
|
2737
|
+
return `<mj-section padding-top="20px" padding-bottom="28px"><mj-column><mj-text font-family="${FONT_STACK}" font-size="12px" line-height="18px" color="#6d7484" align="center" ${SIDE}>${footer}<br /><a href="{{ unsubscribe }}" style="color:#6d7484;text-decoration:underline;">${escapeXml(t("Unsubscribe"))}</a></mj-text></mj-column></mj-section>`;
|
|
2738
|
+
}
|
|
2739
|
+
function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = identity } = {}) {
|
|
2554
2740
|
return [
|
|
2555
2741
|
"<mjml>",
|
|
2556
2742
|
`<mj-body background-color="${PALETTE.canvas}" width="600px">`,
|
|
2557
2743
|
// Header: logo or sender name.
|
|
2558
2744
|
`<mj-section background-color="${PALETTE.surface}" padding-top="28px" padding-bottom="8px">`,
|
|
2559
2745
|
"<mj-column>",
|
|
2560
|
-
|
|
2746
|
+
senderHeaderMjml(sender, t),
|
|
2561
2747
|
"</mj-column>",
|
|
2562
2748
|
"</mj-section>",
|
|
2563
2749
|
// Body: headline, text, button.
|
|
@@ -2569,17 +2755,316 @@ function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = id
|
|
|
2569
2755
|
"</mj-column>",
|
|
2570
2756
|
"</mj-section>",
|
|
2571
2757
|
// Footer: sender and unsubscribe link.
|
|
2572
|
-
|
|
2573
|
-
"<mj-column>",
|
|
2574
|
-
`<mj-text font-family="${FONT_STACK}" font-size="12px" line-height="18px" color="#6d7484" align="center" ${SIDE}>${footer}<br /><a href="{{ unsubscribe }}" style="color:#6d7484;text-decoration:underline;">${escapeXml(t("Unsubscribe"))}</a></mj-text>`,
|
|
2575
|
-
"</mj-column>",
|
|
2576
|
-
"</mj-section>",
|
|
2758
|
+
footerSectionMjml(sender, t),
|
|
2577
2759
|
"</mj-body>",
|
|
2578
2760
|
"</mjml>"
|
|
2579
2761
|
].join("");
|
|
2580
2762
|
}
|
|
2581
2763
|
var STARTER_MJML = createStarterMjml();
|
|
2582
2764
|
|
|
2765
|
+
// src/features/templates/gallery.js
|
|
2766
|
+
var WHITE_SECTION = `background-color="${PALETTE.surface}"`;
|
|
2767
|
+
function section(inner, { top = 8, bottom = 8, x = 0 } = {}) {
|
|
2768
|
+
const sides = x ? ` padding-left="${x}px" padding-right="${x}px"` : "";
|
|
2769
|
+
return `<mj-section ${WHITE_SECTION} padding-top="${top}px" padding-bottom="${bottom}px"${sides}>${inner}</mj-section>`;
|
|
2770
|
+
}
|
|
2771
|
+
function column(inner, attrs = "") {
|
|
2772
|
+
return `<mj-column${attrs ? ` ${attrs}` : ""}>${inner}</mj-column>`;
|
|
2773
|
+
}
|
|
2774
|
+
function heading(text, { size = 24, color = PALETTE.navy, align = "left", side = true } = {}) {
|
|
2775
|
+
return `<mj-text font-family="${FONT_STACK}" font-size="${size}px" line-height="${Math.round(size * 1.3)}px" font-weight="bold" color="${color}" align="${align}"${side ? ` ${SIDE}` : ""}>${text}</mj-text>`;
|
|
2776
|
+
}
|
|
2777
|
+
function paragraph(html, { size = 15, color = PALETTE.ink, align = "left", side = true } = {}) {
|
|
2778
|
+
return `<mj-text font-family="${FONT_STACK}" font-size="${size}px" line-height="${Math.round(size * 1.6)}px" color="${color}" align="${align}"${side ? ` ${SIDE}` : ""}>${html}</mj-text>`;
|
|
2779
|
+
}
|
|
2780
|
+
function button(label, { align = "left", background = PALETTE.navy, color = PALETTE.white, side = true } = {}) {
|
|
2781
|
+
return `<mj-button href="${DEFAULT_BUTTON_URL}" background-color="${background}" color="${color}" font-family="${FONT_STACK}" font-size="15px" font-weight="bold" border-radius="6px" inner-padding="12px 28px" align="${align}"${side ? ` ${SIDE}` : ""}>${label}</mj-button>`;
|
|
2782
|
+
}
|
|
2783
|
+
var IMAGE = '<mj-image align="center"></mj-image>';
|
|
2784
|
+
function header(sender, t) {
|
|
2785
|
+
return `<mj-section ${WHITE_SECTION} padding-top="28px" padding-bottom="8px">${column(senderHeaderMjml(sender, t))}</mj-section>`;
|
|
2786
|
+
}
|
|
2787
|
+
function banner({ title, text, cta, background = PALETTE.navy }) {
|
|
2788
|
+
return `<mj-section background-color="${background}" background-size="cover" background-position="center center" background-repeat="no-repeat" padding-top="48px" padding-bottom="48px">` + column(
|
|
2789
|
+
heading(title, { size: 30, color: PALETTE.white, align: "center" }) + paragraph(text, { size: 16, color: PALETTE.border, align: "center" }) + (cta ? button(cta, { align: "center", background: PALETTE.white, color: PALETTE.navy }) : "")
|
|
2790
|
+
) + "</mj-section>";
|
|
2791
|
+
}
|
|
2792
|
+
function social() {
|
|
2793
|
+
const elements = ["facebook-noshare", "instagram-noshare", "linkedin-noshare"].map((name) => `<mj-social-element name="${name}" href="${SOCIAL_NETWORKS.find((n) => n.value === name)?.href}"></mj-social-element>`).join("");
|
|
2794
|
+
return section(
|
|
2795
|
+
column(
|
|
2796
|
+
`<mj-social mode="horizontal" align="center" icon-size="28px" border-radius="14px" inner-padding="6px">${elements}</mj-social>`
|
|
2797
|
+
),
|
|
2798
|
+
{ top: 16, bottom: 16 }
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
function email(body) {
|
|
2802
|
+
return `<mjml><mj-body background-color="${PALETTE.canvas}" width="600px">${body}</mj-body></mjml>`;
|
|
2803
|
+
}
|
|
2804
|
+
var esc = escapeXml;
|
|
2805
|
+
var TEMPLATE_GALLERY = [
|
|
2806
|
+
{
|
|
2807
|
+
id: "blank",
|
|
2808
|
+
name: "Blank",
|
|
2809
|
+
description: "An empty page to build from scratch",
|
|
2810
|
+
create: () => email("")
|
|
2811
|
+
},
|
|
2812
|
+
{
|
|
2813
|
+
id: "simple",
|
|
2814
|
+
name: "Simple",
|
|
2815
|
+
description: "Logo, headline, text and a button",
|
|
2816
|
+
create: ({ sender, t }) => createStarterMjml({ sender, t })
|
|
2817
|
+
},
|
|
2818
|
+
{
|
|
2819
|
+
id: "newsletter",
|
|
2820
|
+
name: "Newsletter",
|
|
2821
|
+
description: "Monthly news: banner, two articles, highlights",
|
|
2822
|
+
meta: (t) => ({
|
|
2823
|
+
subject: t("The news of the month"),
|
|
2824
|
+
preheader: t("What changed, what is coming, and two stories worth your time.")
|
|
2825
|
+
}),
|
|
2826
|
+
create: ({ sender, t }) => email(
|
|
2827
|
+
header(sender, t) + banner({
|
|
2828
|
+
title: esc(t("The news of the month")),
|
|
2829
|
+
text: esc(t("What changed, what is coming, and two stories worth your time.")),
|
|
2830
|
+
cta: null
|
|
2831
|
+
}) + [1, 2].map(
|
|
2832
|
+
(n) => section(
|
|
2833
|
+
column(IMAGE, 'vertical-align="middle"') + column(
|
|
2834
|
+
heading(esc(t("Article {n}", { n })), { size: 20, side: false }) + paragraph(esc(t("Two or three sentences that sum up the story and make people want the rest.")), {
|
|
2835
|
+
side: false
|
|
2836
|
+
}) + button(esc(t("Read more")), { side: false }),
|
|
2837
|
+
'vertical-align="middle"'
|
|
2838
|
+
),
|
|
2839
|
+
{ top: 16, bottom: 16, x: 16 }
|
|
2840
|
+
)
|
|
2841
|
+
).join("") + section(column(heading(esc(t("In brief")), { size: 20 })), { top: 20, bottom: 0 }) + section(
|
|
2842
|
+
[t("A new feature"), t("An event"), t("A tip")].map(
|
|
2843
|
+
(title) => column(
|
|
2844
|
+
heading(esc(title), { size: 16, side: false }) + paragraph(esc(t("One sentence about it.")), { size: 14, side: false })
|
|
2845
|
+
)
|
|
2846
|
+
).join(""),
|
|
2847
|
+
{ bottom: 20, x: 16 }
|
|
2848
|
+
) + social() + footerSectionMjml(sender, t)
|
|
2849
|
+
)
|
|
2850
|
+
},
|
|
2851
|
+
{
|
|
2852
|
+
id: "welcome",
|
|
2853
|
+
name: "Welcome",
|
|
2854
|
+
description: "Greets a new customer and lists the first steps",
|
|
2855
|
+
meta: (t) => ({ subject: t("Welcome aboard!"), preheader: t("Three steps to get started.") }),
|
|
2856
|
+
create: ({ sender, t }) => email(
|
|
2857
|
+
header(sender, t) + section(
|
|
2858
|
+
column(
|
|
2859
|
+
heading(esc(t("Welcome, {name}!", { name: "{{contact.firstname}}" })), { size: 28 }) + paragraph(esc(t("Thank you for joining us. Here is how to get the most out of your account.")))
|
|
2860
|
+
),
|
|
2861
|
+
{ top: 16, bottom: 8 }
|
|
2862
|
+
) + section(
|
|
2863
|
+
[
|
|
2864
|
+
[t("1. Complete your profile"), t("So we can tailor what we send you.")],
|
|
2865
|
+
[t("2. Explore"), t("Take a tour of what you can do.")],
|
|
2866
|
+
[t("3. Ask us anything"), t("Reply to this email: a person reads it.")]
|
|
2867
|
+
].map(
|
|
2868
|
+
([title, text]) => column(
|
|
2869
|
+
heading(esc(title), { size: 16, side: false }) + paragraph(esc(text), { size: 14, side: false }),
|
|
2870
|
+
`background-color="${PALETTE.canvas}" padding-top="12px" padding-bottom="12px" padding-left="12px" padding-right="12px"`
|
|
2871
|
+
)
|
|
2872
|
+
).join(""),
|
|
2873
|
+
{ x: 24 }
|
|
2874
|
+
) + section(column(button(esc(t("Get started")), { align: "center" })), { top: 16, bottom: 28 }) + footerSectionMjml(sender, t)
|
|
2875
|
+
)
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
id: "promotion",
|
|
2879
|
+
name: "Promotion",
|
|
2880
|
+
description: "A sale with a discount code and two products",
|
|
2881
|
+
meta: (t) => ({ subject: t("30% off this weekend only"), preheader: t("With the code SPRING30, until Sunday midnight.") }),
|
|
2882
|
+
create: ({ sender, t }) => email(
|
|
2883
|
+
header(sender, t) + banner({
|
|
2884
|
+
title: esc(t("30% off everything")),
|
|
2885
|
+
text: esc(t("This weekend only, with the code SPRING30.")),
|
|
2886
|
+
cta: esc(t("Shop the sale")),
|
|
2887
|
+
background: PALETTE.brand
|
|
2888
|
+
}) + section(
|
|
2889
|
+
[t("Product one"), t("Product two")].map(
|
|
2890
|
+
(name) => column(
|
|
2891
|
+
IMAGE + heading(esc(name), { size: 18, side: false }) + paragraph(esc(t("\u20AC49 instead of \u20AC70")), { size: 15, side: false }) + button(esc(t("Buy")), { side: false })
|
|
2892
|
+
)
|
|
2893
|
+
).join(""),
|
|
2894
|
+
{ top: 20, bottom: 20, x: 16 }
|
|
2895
|
+
) + section(
|
|
2896
|
+
column(
|
|
2897
|
+
paragraph(esc(t("Offer valid until Sunday midnight, while stocks last.")), {
|
|
2898
|
+
size: 12,
|
|
2899
|
+
color: "#6d7484",
|
|
2900
|
+
align: "center"
|
|
2901
|
+
})
|
|
2902
|
+
)
|
|
2903
|
+
) + footerSectionMjml(sender, t)
|
|
2904
|
+
)
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
id: "event",
|
|
2908
|
+
name: "Event invitation",
|
|
2909
|
+
description: "Date, place and a button to book a seat",
|
|
2910
|
+
meta: (t) => ({ subject: t("You're invited"), preheader: t("Save the date \u2014 seats are limited.") }),
|
|
2911
|
+
create: ({ sender, t }) => email(
|
|
2912
|
+
header(sender, t) + banner({
|
|
2913
|
+
title: esc(t("You're invited")),
|
|
2914
|
+
text: esc(t("An evening of talks and drinks with the team and our customers.")),
|
|
2915
|
+
cta: null
|
|
2916
|
+
}) + section(
|
|
2917
|
+
column(heading(esc(t("When")), { size: 16, side: false }) + paragraph(esc(t("Thursday, 7 pm")), { side: false })) + column(heading(esc(t("Where")), { size: 16, side: false }) + paragraph(esc(t("Our offices, 1 Main Street")), { side: false })),
|
|
2918
|
+
{ top: 24, x: 16 }
|
|
2919
|
+
) + section(
|
|
2920
|
+
column(
|
|
2921
|
+
paragraph(esc(t("Seats are limited: book yours now, it takes ten seconds."))) + button(esc(t("Book my seat")), { align: "left" })
|
|
2922
|
+
),
|
|
2923
|
+
{ bottom: 28 }
|
|
2924
|
+
) + footerSectionMjml(sender, t)
|
|
2925
|
+
)
|
|
2926
|
+
}
|
|
2927
|
+
];
|
|
2928
|
+
function buildGallery({ templates = TEMPLATE_GALLERY, sender = {}, t = identity } = {}) {
|
|
2929
|
+
return templates.map((template) => {
|
|
2930
|
+
const meta = template.meta ? template.meta(t) : {};
|
|
2931
|
+
return {
|
|
2932
|
+
id: template.id,
|
|
2933
|
+
name: t(template.name),
|
|
2934
|
+
description: t(template.description),
|
|
2935
|
+
mjml: template.create({ sender, t }),
|
|
2936
|
+
subject: meta.subject || "",
|
|
2937
|
+
preheader: meta.preheader || ""
|
|
2938
|
+
};
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
var THUMBNAIL_IMAGE = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 300'%3E%3Crect width='600' height='300' fill='%23dfe6f0'/%3E%3C/svg%3E";
|
|
2942
|
+
function withThumbnailImages(mjml) {
|
|
2943
|
+
return String(mjml || "").replace(/<mj-image(?![^>]*\ssrc=)([^>]*)>/g, `<mj-image src="${THUMBNAIL_IMAGE}"$1>`);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
// src/features/templates/TemplateGallery.jsx
|
|
2947
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2948
|
+
var THUMBNAIL_SCALE = 0.38;
|
|
2949
|
+
var EMAIL_WIDTH = 600;
|
|
2950
|
+
function Thumbnail({ html, title }) {
|
|
2951
|
+
return /* @__PURE__ */ jsx12(
|
|
2952
|
+
"div",
|
|
2953
|
+
{
|
|
2954
|
+
className: "eb:relative eb:h-[220px] eb:overflow-hidden eb:rounded-md eb:border eb:border-border eb:bg-surface-75",
|
|
2955
|
+
"aria-hidden": "true",
|
|
2956
|
+
children: /* @__PURE__ */ jsx12(
|
|
2957
|
+
"iframe",
|
|
2958
|
+
{
|
|
2959
|
+
title,
|
|
2960
|
+
sandbox: "",
|
|
2961
|
+
srcDoc: html,
|
|
2962
|
+
tabIndex: -1,
|
|
2963
|
+
loading: "lazy",
|
|
2964
|
+
className: "eb:pointer-events-none eb:absolute eb:top-0 eb:left-1/2 eb:origin-top eb:border-0",
|
|
2965
|
+
style: {
|
|
2966
|
+
width: EMAIL_WIDTH,
|
|
2967
|
+
height: 220 / THUMBNAIL_SCALE,
|
|
2968
|
+
transform: `translateX(-50%) scale(${THUMBNAIL_SCALE})`
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
)
|
|
2972
|
+
}
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
function TemplateGalleryButton({ context }) {
|
|
2976
|
+
const { editor, ready, templates, isDirty, markDirty, template, setMeta } = context;
|
|
2977
|
+
const { t, confirm, notify } = useUi();
|
|
2978
|
+
const [open, setOpen] = useState8(false);
|
|
2979
|
+
const cards = useMemo5(() => {
|
|
2980
|
+
if (!open || !ready) return [];
|
|
2981
|
+
return templates.map((entry) => {
|
|
2982
|
+
try {
|
|
2983
|
+
return { ...entry, html: editor.exportHtml(withThumbnailImages(entry.mjml)).html };
|
|
2984
|
+
} catch {
|
|
2985
|
+
return { ...entry, html: "" };
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
}, [open, ready, templates, editor]);
|
|
2989
|
+
const apply = async (entry) => {
|
|
2990
|
+
if (isDirty()) {
|
|
2991
|
+
setOpen(false);
|
|
2992
|
+
const ok = await confirm({
|
|
2993
|
+
title: t("Replace the current email?"),
|
|
2994
|
+
description: t("Unsaved changes will be replaced."),
|
|
2995
|
+
confirmLabel: t("Replace"),
|
|
2996
|
+
destructive: true
|
|
2997
|
+
});
|
|
2998
|
+
if (!ok) return;
|
|
2999
|
+
}
|
|
3000
|
+
editor.loadMjml(entry.mjml);
|
|
3001
|
+
setMeta((meta) => ({
|
|
3002
|
+
...meta,
|
|
3003
|
+
subject: meta.subject || entry.subject,
|
|
3004
|
+
preheader: meta.preheader || entry.preheader
|
|
3005
|
+
}));
|
|
3006
|
+
if (template.id) markDirty();
|
|
3007
|
+
setOpen(false);
|
|
3008
|
+
notify("success", t("\u201C{name}\u201D template applied.", { name: entry.name }));
|
|
3009
|
+
};
|
|
3010
|
+
return /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
3011
|
+
/* @__PURE__ */ jsxs10(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setOpen(true), children: [
|
|
3012
|
+
/* @__PURE__ */ jsx12(LayoutTemplate, {}),
|
|
3013
|
+
t("Templates")
|
|
3014
|
+
] }),
|
|
3015
|
+
/* @__PURE__ */ jsx12(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs10(DialogContent, { className: "eb:max-h-[calc(100dvh-2rem)] eb:overflow-y-auto eb:sm:max-w-4xl", "data-testid": "eb-template-gallery", children: [
|
|
3016
|
+
/* @__PURE__ */ jsxs10(DialogHeader, { children: [
|
|
3017
|
+
/* @__PURE__ */ jsx12(DialogTitle, { children: t("Start from a template") }),
|
|
3018
|
+
/* @__PURE__ */ jsx12(DialogDescription, { children: t("Picking a template replaces the email being edited.") })
|
|
3019
|
+
] }),
|
|
3020
|
+
/* @__PURE__ */ jsx12("ul", { className: "eb:grid eb:grid-cols-1 eb:gap-3 eb:sm:grid-cols-2 eb:md:grid-cols-3", children: cards.map((card) => /* @__PURE__ */ jsx12("li", { children: /* @__PURE__ */ jsxs10(
|
|
3021
|
+
"button",
|
|
3022
|
+
{
|
|
3023
|
+
type: "button",
|
|
3024
|
+
onClick: () => apply(card),
|
|
3025
|
+
"data-template-id": card.id,
|
|
3026
|
+
className: "eb:group eb:flex eb:w-full eb:flex-col eb:gap-2 eb:rounded-lg2 eb:border eb:border-border eb:bg-background eb:p-2 eb:text-left eb:transition-colors eb:hover:border-primary/50 eb:hover:bg-primary/5 eb:focus-visible:outline-none eb:focus-visible:ring-2 eb:focus-visible:ring-ring/50",
|
|
3027
|
+
children: [
|
|
3028
|
+
/* @__PURE__ */ jsx12(Thumbnail, { html: card.html, title: card.name }),
|
|
3029
|
+
/* @__PURE__ */ jsxs10("span", { className: "eb:px-1", children: [
|
|
3030
|
+
/* @__PURE__ */ jsx12("span", { className: "eb:block eb:text-[13px] eb:font-medium eb:text-foreground", children: card.name }),
|
|
3031
|
+
/* @__PURE__ */ jsx12("span", { className: "eb:block eb:text-[12px] eb:text-foreground-muted", children: card.description })
|
|
3032
|
+
] })
|
|
3033
|
+
]
|
|
3034
|
+
}
|
|
3035
|
+
) }, card.id)) })
|
|
3036
|
+
] }) })
|
|
3037
|
+
] });
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// src/features/templates/index.js
|
|
3041
|
+
var templatesFeature = {
|
|
3042
|
+
id: "templates",
|
|
3043
|
+
label: "Templates",
|
|
3044
|
+
order: 5,
|
|
3045
|
+
toolbar: TemplateGalleryButton,
|
|
3046
|
+
isAvailable: (context) => Array.isArray(context.templates) && context.templates.length > 0
|
|
3047
|
+
};
|
|
3048
|
+
|
|
3049
|
+
// src/features/index.js
|
|
3050
|
+
var FEATURES = [
|
|
3051
|
+
templatesFeature,
|
|
3052
|
+
variablesFeature,
|
|
3053
|
+
autosaveFeature,
|
|
3054
|
+
versionsFeature,
|
|
3055
|
+
previewFeature,
|
|
3056
|
+
testEmailFeature
|
|
3057
|
+
];
|
|
3058
|
+
var emailBuilderFeatures = createRegistry("emailBuilderFeatures", FEATURES);
|
|
3059
|
+
function isFeatureEnabled(id) {
|
|
3060
|
+
const feature = emailBuilderFeatures.get(id);
|
|
3061
|
+
return Boolean(feature && feature.enabled !== false);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// src/ui/EmailBuilder.jsx
|
|
3065
|
+
import { useCallback as useCallback3, useEffect as useEffect9, useMemo as useMemo7, useRef as useRef7, useState as useState14 } from "react";
|
|
3066
|
+
import { AlertTriangle as AlertTriangle2, Loader2 as Loader25, Plus as Plus2, Save, SlidersHorizontal } from "lucide-react";
|
|
3067
|
+
|
|
2583
3068
|
// src/features/autosave/scheduler.js
|
|
2584
3069
|
function createAutosaveScheduler({
|
|
2585
3070
|
run,
|
|
@@ -2879,7 +3364,7 @@ function createMemoryStorage({ render, sendTest, assetsPath = DEFAULT_ASSETS_PAT
|
|
|
2879
3364
|
|
|
2880
3365
|
// src/ui/EmailCanvas.jsx
|
|
2881
3366
|
import { useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
2882
|
-
import { jsx as
|
|
3367
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2883
3368
|
function EmailCanvas({ editor, document = null, onReady, onError, className }) {
|
|
2884
3369
|
const ref = useRef3(null);
|
|
2885
3370
|
const documentRef = useRef3(document);
|
|
@@ -2898,23 +3383,35 @@ function EmailCanvas({ editor, document = null, onReady, onError, className }) {
|
|
|
2898
3383
|
editor.destroy();
|
|
2899
3384
|
};
|
|
2900
3385
|
}, [editor]);
|
|
2901
|
-
return /* @__PURE__ */
|
|
3386
|
+
return /* @__PURE__ */ jsx13("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
|
|
2902
3387
|
}
|
|
2903
3388
|
|
|
2904
3389
|
// src/ui/BlockSidebar.jsx
|
|
2905
3390
|
import {
|
|
2906
3391
|
Columns2,
|
|
2907
3392
|
Columns3,
|
|
3393
|
+
Columns4,
|
|
3394
|
+
GalleryHorizontal,
|
|
3395
|
+
Heading,
|
|
2908
3396
|
Image as ImageIcon,
|
|
3397
|
+
LayoutPanelLeft,
|
|
3398
|
+
Menu,
|
|
2909
3399
|
MoveVertical,
|
|
2910
3400
|
RectangleHorizontal,
|
|
2911
3401
|
SeparatorHorizontal,
|
|
3402
|
+
Share2,
|
|
2912
3403
|
Square,
|
|
2913
3404
|
Type
|
|
2914
3405
|
} from "lucide-react";
|
|
2915
|
-
import { jsx as
|
|
3406
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2916
3407
|
var ICONS = {
|
|
3408
|
+
Heading,
|
|
2917
3409
|
Type,
|
|
3410
|
+
Share2,
|
|
3411
|
+
Menu,
|
|
3412
|
+
Columns4,
|
|
3413
|
+
LayoutPanelLeft,
|
|
3414
|
+
GalleryHorizontal,
|
|
2918
3415
|
Image: ImageIcon,
|
|
2919
3416
|
RectangleHorizontal,
|
|
2920
3417
|
SeparatorHorizontal,
|
|
@@ -2930,7 +3427,7 @@ var GROUPS2 = [
|
|
|
2930
3427
|
function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
|
|
2931
3428
|
const t = useT();
|
|
2932
3429
|
const Icon = ICONS[block.icon] || Square;
|
|
2933
|
-
return /* @__PURE__ */
|
|
3430
|
+
return /* @__PURE__ */ jsx14(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs11(
|
|
2934
3431
|
"button",
|
|
2935
3432
|
{
|
|
2936
3433
|
type: "button",
|
|
@@ -2950,26 +3447,28 @@ function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
|
|
|
2950
3447
|
disabled && "eb:cursor-not-allowed eb:opacity-50"
|
|
2951
3448
|
),
|
|
2952
3449
|
children: [
|
|
2953
|
-
/* @__PURE__ */
|
|
2954
|
-
/* @__PURE__ */
|
|
3450
|
+
/* @__PURE__ */ jsx14(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
|
|
3451
|
+
/* @__PURE__ */ jsx14("span", { className: "eb:text-[12px] eb:text-foreground", children: t(block.label) })
|
|
2955
3452
|
]
|
|
2956
3453
|
}
|
|
2957
3454
|
) });
|
|
2958
3455
|
}
|
|
2959
|
-
function BlockSidebar({ editor, disabled = false }) {
|
|
3456
|
+
function BlockSidebar({ editor, disabled = false, onAdded }) {
|
|
2960
3457
|
const t = useT();
|
|
2961
3458
|
const blocks = editor.listBlocks();
|
|
2962
|
-
return /* @__PURE__ */
|
|
3459
|
+
return /* @__PURE__ */ jsx14("aside", { "aria-label": t("Blocks"), className: "eb:min-h-0 eb:space-y-5 eb:overflow-y-auto eb:border-r eb:border-border eb:p-3", children: GROUPS2.map((group) => {
|
|
2963
3460
|
const items = blocks.filter((b) => b.kind === group.kind && b.mjml);
|
|
2964
3461
|
if (!items.length) return null;
|
|
2965
|
-
return /* @__PURE__ */
|
|
2966
|
-
/* @__PURE__ */
|
|
2967
|
-
/* @__PURE__ */
|
|
3462
|
+
return /* @__PURE__ */ jsxs11("section", { className: "eb:space-y-2", children: [
|
|
3463
|
+
/* @__PURE__ */ jsx14(SectionLabel, { children: t(group.title) }),
|
|
3464
|
+
/* @__PURE__ */ jsx14("div", { className: "eb:grid eb:grid-cols-2 eb:gap-2", children: items.map((block) => /* @__PURE__ */ jsx14(
|
|
2968
3465
|
BlockTile,
|
|
2969
3466
|
{
|
|
2970
3467
|
block,
|
|
2971
3468
|
disabled,
|
|
2972
|
-
onAdd: (id) =>
|
|
3469
|
+
onAdd: (id) => {
|
|
3470
|
+
if (editor.addBlock(id)) onAdded?.(id);
|
|
3471
|
+
},
|
|
2973
3472
|
onDragStart: (id, event) => editor.startBlockDrag(id, event),
|
|
2974
3473
|
onDragEnd: (cancel) => editor.endBlockDrag(cancel)
|
|
2975
3474
|
},
|
|
@@ -2981,15 +3480,15 @@ function BlockSidebar({ editor, disabled = false }) {
|
|
|
2981
3480
|
|
|
2982
3481
|
// src/ui/ElementActions.jsx
|
|
2983
3482
|
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Copy, Trash2 as Trash22 } from "lucide-react";
|
|
2984
|
-
import { jsx as
|
|
3483
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2985
3484
|
function ElementActions({ editor, selection }) {
|
|
2986
3485
|
const t = useT();
|
|
2987
3486
|
if (!selection || !selection.canRemove && !selection.canDuplicate) return null;
|
|
2988
3487
|
const horizontal = selection.tagName === "mj-column";
|
|
2989
3488
|
const [Prev, Next] = horizontal ? [ArrowLeft, ArrowRight] : [ArrowUp, ArrowDown];
|
|
2990
3489
|
const [prevLabel, nextLabel] = horizontal ? [t("Move left"), t("Move right")] : [t("Move up"), t("Move down")];
|
|
2991
|
-
return /* @__PURE__ */
|
|
2992
|
-
/* @__PURE__ */
|
|
3490
|
+
return /* @__PURE__ */ jsxs12("div", { className: "eb:flex eb:items-center eb:gap-0.5 eb:border-b eb:border-border eb:px-3 eb:py-1.5", role: "toolbar", "aria-label": t("Element actions"), children: [
|
|
3491
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: prevLabel, children: /* @__PURE__ */ jsx15(
|
|
2993
3492
|
Button,
|
|
2994
3493
|
{
|
|
2995
3494
|
variant: "ghost",
|
|
@@ -2997,10 +3496,10 @@ function ElementActions({ editor, selection }) {
|
|
|
2997
3496
|
"aria-label": prevLabel,
|
|
2998
3497
|
disabled: !selection.canMoveUp,
|
|
2999
3498
|
onClick: () => editor.move(-1, selection.id),
|
|
3000
|
-
children: /* @__PURE__ */
|
|
3499
|
+
children: /* @__PURE__ */ jsx15(Prev, {})
|
|
3001
3500
|
}
|
|
3002
3501
|
) }),
|
|
3003
|
-
/* @__PURE__ */
|
|
3502
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx15(
|
|
3004
3503
|
Button,
|
|
3005
3504
|
{
|
|
3006
3505
|
variant: "ghost",
|
|
@@ -3008,10 +3507,10 @@ function ElementActions({ editor, selection }) {
|
|
|
3008
3507
|
"aria-label": nextLabel,
|
|
3009
3508
|
disabled: !selection.canMoveDown,
|
|
3010
3509
|
onClick: () => editor.move(1, selection.id),
|
|
3011
|
-
children: /* @__PURE__ */
|
|
3510
|
+
children: /* @__PURE__ */ jsx15(Next, {})
|
|
3012
3511
|
}
|
|
3013
3512
|
) }),
|
|
3014
|
-
/* @__PURE__ */
|
|
3513
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx15(
|
|
3015
3514
|
Button,
|
|
3016
3515
|
{
|
|
3017
3516
|
variant: "ghost",
|
|
@@ -3019,10 +3518,10 @@ function ElementActions({ editor, selection }) {
|
|
|
3019
3518
|
"aria-label": t("Duplicate"),
|
|
3020
3519
|
disabled: !selection.canDuplicate,
|
|
3021
3520
|
onClick: () => editor.duplicate(selection.id),
|
|
3022
|
-
children: /* @__PURE__ */
|
|
3521
|
+
children: /* @__PURE__ */ jsx15(Copy, {})
|
|
3023
3522
|
}
|
|
3024
3523
|
) }),
|
|
3025
|
-
/* @__PURE__ */
|
|
3524
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx15(
|
|
3026
3525
|
Button,
|
|
3027
3526
|
{
|
|
3028
3527
|
variant: "ghost",
|
|
@@ -3031,24 +3530,24 @@ function ElementActions({ editor, selection }) {
|
|
|
3031
3530
|
disabled: !selection.canRemove,
|
|
3032
3531
|
onClick: () => editor.remove(selection.id),
|
|
3033
3532
|
className: "eb:ml-auto eb:text-foreground-muted eb:hover:bg-destructive/10 eb:hover:text-destructive",
|
|
3034
|
-
children: /* @__PURE__ */
|
|
3533
|
+
children: /* @__PURE__ */ jsx15(Trash22, {})
|
|
3035
3534
|
}
|
|
3036
3535
|
) })
|
|
3037
3536
|
] });
|
|
3038
3537
|
}
|
|
3039
3538
|
|
|
3040
3539
|
// src/ui/PropertiesPanel.jsx
|
|
3041
|
-
import { useMemo as
|
|
3540
|
+
import { useMemo as useMemo6, useRef as useRef5 } from "react";
|
|
3042
3541
|
import { X as X2 } from "lucide-react";
|
|
3043
3542
|
|
|
3044
3543
|
// src/ui/fields.jsx
|
|
3045
|
-
import { useEffect as useEffect5, useRef as useRef4, useState as
|
|
3544
|
+
import { useEffect as useEffect5, useRef as useRef4, useState as useState10 } from "react";
|
|
3046
3545
|
|
|
3047
3546
|
// src/ui/primitives/color-picker.jsx
|
|
3048
|
-
import { useState as
|
|
3547
|
+
import { useState as useState9 } from "react";
|
|
3049
3548
|
import { HexAlphaColorPicker } from "react-colorful";
|
|
3050
3549
|
import { Check } from "lucide-react";
|
|
3051
|
-
import { jsx as
|
|
3550
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3052
3551
|
var PRESET_COLORS = [
|
|
3053
3552
|
"#8B5CF6",
|
|
3054
3553
|
"#A78BFA",
|
|
@@ -3079,34 +3578,34 @@ function normalizeHex(v) {
|
|
|
3079
3578
|
}
|
|
3080
3579
|
function ColorPicker({ value, onChange, id, className }) {
|
|
3081
3580
|
const t = useT();
|
|
3082
|
-
const [open, setOpen] =
|
|
3581
|
+
const [open, setOpen] = useState9(false);
|
|
3083
3582
|
const valid = isValidHex(value);
|
|
3084
3583
|
const normalized = valid ? normalizeHex(value) : "";
|
|
3085
3584
|
const emit = (hex) => onChange(normalizeHex(hex));
|
|
3086
|
-
return /* @__PURE__ */
|
|
3087
|
-
/* @__PURE__ */
|
|
3088
|
-
/* @__PURE__ */
|
|
3585
|
+
return /* @__PURE__ */ jsxs13("div", { className: cn("eb:flex eb:gap-2", className), children: [
|
|
3586
|
+
/* @__PURE__ */ jsxs13(Popover, { open, onOpenChange: setOpen, children: [
|
|
3587
|
+
/* @__PURE__ */ jsx16(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx16(
|
|
3089
3588
|
"button",
|
|
3090
3589
|
{
|
|
3091
3590
|
type: "button",
|
|
3092
3591
|
"aria-label": t("Pick a colour"),
|
|
3093
3592
|
title: t("Pick a colour"),
|
|
3094
3593
|
className: "eb-checker eb:relative eb:h-8 eb:w-[44px] eb:shrink-0 eb:overflow-hidden eb:rounded-lg2 eb:shadow-sm eb:ring-1 eb:ring-black/10 eb:ring-inset eb:transition-transform eb:hover:scale-[1.04]",
|
|
3095
|
-
children: /* @__PURE__ */
|
|
3594
|
+
children: /* @__PURE__ */ jsx16("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" }, "aria-hidden": true })
|
|
3096
3595
|
}
|
|
3097
3596
|
) }),
|
|
3098
|
-
/* @__PURE__ */
|
|
3099
|
-
/* @__PURE__ */
|
|
3100
|
-
/* @__PURE__ */
|
|
3101
|
-
/* @__PURE__ */
|
|
3597
|
+
/* @__PURE__ */ jsxs13(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
|
|
3598
|
+
/* @__PURE__ */ jsx16("div", { className: "eb-picker", children: /* @__PURE__ */ jsx16(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
|
|
3599
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
|
|
3600
|
+
/* @__PURE__ */ jsx16(
|
|
3102
3601
|
"span",
|
|
3103
3602
|
{
|
|
3104
3603
|
className: "eb-checker eb:relative eb:size-8 eb:shrink-0 eb:overflow-hidden eb:rounded-lg2 eb:ring-1 eb:ring-black/10 eb:ring-inset",
|
|
3105
3604
|
"aria-hidden": true,
|
|
3106
|
-
children: /* @__PURE__ */
|
|
3605
|
+
children: /* @__PURE__ */ jsx16("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
|
|
3107
3606
|
}
|
|
3108
3607
|
),
|
|
3109
|
-
/* @__PURE__ */
|
|
3608
|
+
/* @__PURE__ */ jsx16(
|
|
3110
3609
|
Input,
|
|
3111
3610
|
{
|
|
3112
3611
|
value: value || "",
|
|
@@ -3118,7 +3617,7 @@ function ColorPicker({ value, onChange, id, className }) {
|
|
|
3118
3617
|
}
|
|
3119
3618
|
)
|
|
3120
3619
|
] }),
|
|
3121
|
-
/* @__PURE__ */
|
|
3620
|
+
/* @__PURE__ */ jsx16("div", { className: "eb:grid eb:grid-cols-6 eb:gap-1.5 eb:border-t eb:border-border eb:pt-2.5", children: PRESET_COLORS.map((c) => /* @__PURE__ */ jsx16(
|
|
3122
3621
|
"button",
|
|
3123
3622
|
{
|
|
3124
3623
|
type: "button",
|
|
@@ -3127,13 +3626,13 @@ function ColorPicker({ value, onChange, id, className }) {
|
|
|
3127
3626
|
"aria-label": c,
|
|
3128
3627
|
className: "eb:relative eb:size-6 eb:rounded-lg2 eb:ring-1 eb:ring-black/10 eb:ring-inset eb:transition-transform eb:hover:scale-110",
|
|
3129
3628
|
style: { backgroundColor: c },
|
|
3130
|
-
children: normalized === c && /* @__PURE__ */
|
|
3629
|
+
children: normalized === c && /* @__PURE__ */ jsx16(Check, { className: "eb:absolute eb:inset-0 eb:m-auto eb:size-3.5 eb:text-white eb:drop-shadow-[0_1px_1px_rgba(0,0,0,0.6)]" })
|
|
3131
3630
|
},
|
|
3132
3631
|
c
|
|
3133
3632
|
)) })
|
|
3134
3633
|
] })
|
|
3135
3634
|
] }),
|
|
3136
|
-
/* @__PURE__ */
|
|
3635
|
+
/* @__PURE__ */ jsx16(
|
|
3137
3636
|
Input,
|
|
3138
3637
|
{
|
|
3139
3638
|
id,
|
|
@@ -3149,7 +3648,7 @@ function ColorPicker({ value, onChange, id, className }) {
|
|
|
3149
3648
|
|
|
3150
3649
|
// src/ui/fields.jsx
|
|
3151
3650
|
import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader24, Upload } from "lucide-react";
|
|
3152
|
-
import { Fragment as
|
|
3651
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3153
3652
|
var COLOR_DEBOUNCE_MS = 250;
|
|
3154
3653
|
function emailColor(value) {
|
|
3155
3654
|
const v = String(value || "").trim();
|
|
@@ -3158,7 +3657,7 @@ function emailColor(value) {
|
|
|
3158
3657
|
return null;
|
|
3159
3658
|
}
|
|
3160
3659
|
function useDraft(value, toDraft) {
|
|
3161
|
-
const [draft, setDraft] =
|
|
3660
|
+
const [draft, setDraft] = useState10(() => toDraft(value));
|
|
3162
3661
|
const ref = useRef4(null);
|
|
3163
3662
|
const convert = useRef4(toDraft);
|
|
3164
3663
|
convert.current = toDraft;
|
|
@@ -3171,10 +3670,10 @@ function useDraft(value, toDraft) {
|
|
|
3171
3670
|
}
|
|
3172
3671
|
var asText = (v) => v || "";
|
|
3173
3672
|
function FieldLabel({ id, children }) {
|
|
3174
|
-
return /* @__PURE__ */
|
|
3673
|
+
return /* @__PURE__ */ jsx17(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
|
|
3175
3674
|
}
|
|
3176
3675
|
function FieldError({ children }) {
|
|
3177
|
-
return children ? /* @__PURE__ */
|
|
3676
|
+
return children ? /* @__PURE__ */ jsx17("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
|
|
3178
3677
|
}
|
|
3179
3678
|
function TextField({ id, property, value, onCommit, addon = null, error = null }) {
|
|
3180
3679
|
const t = useT();
|
|
@@ -3205,13 +3704,13 @@ function TextField({ id, property, value, onCommit, addon = null, error = null }
|
|
|
3205
3704
|
onKeyDown: keyDown,
|
|
3206
3705
|
"aria-invalid": invalid || void 0
|
|
3207
3706
|
};
|
|
3208
|
-
return /* @__PURE__ */
|
|
3209
|
-
/* @__PURE__ */
|
|
3210
|
-
property.type === "textarea" ? /* @__PURE__ */
|
|
3211
|
-
/* @__PURE__ */
|
|
3707
|
+
return /* @__PURE__ */ jsxs14("div", { ref, className: "eb:space-y-1.5", children: [
|
|
3708
|
+
/* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
|
|
3709
|
+
property.type === "textarea" ? /* @__PURE__ */ jsx17(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:gap-2", children: [
|
|
3710
|
+
/* @__PURE__ */ jsx17(Input, { ...shared, className: "eb:min-w-0 eb:flex-1" }),
|
|
3212
3711
|
addon
|
|
3213
|
-
] }) : /* @__PURE__ */
|
|
3214
|
-
/* @__PURE__ */
|
|
3712
|
+
] }) : /* @__PURE__ */ jsx17(Input, { ...shared }),
|
|
3713
|
+
/* @__PURE__ */ jsx17(FieldError, { children: invalid ? kind === "image" ? t("An https:// address is required") : t("Address not allowed") : error })
|
|
3215
3714
|
] });
|
|
3216
3715
|
}
|
|
3217
3716
|
function ImageField({ id, property, value, onCommit, services }) {
|
|
@@ -3220,8 +3719,8 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3220
3719
|
/** @type {HTMLInputElement|null} */
|
|
3221
3720
|
null
|
|
3222
3721
|
);
|
|
3223
|
-
const [busy, setBusy] =
|
|
3224
|
-
const [error, setError] =
|
|
3722
|
+
const [busy, setBusy] = useState10(false);
|
|
3723
|
+
const [error, setError] = useState10(
|
|
3225
3724
|
/** @type {string|null} */
|
|
3226
3725
|
null
|
|
3227
3726
|
);
|
|
@@ -3243,8 +3742,8 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3243
3742
|
setBusy(false);
|
|
3244
3743
|
}
|
|
3245
3744
|
};
|
|
3246
|
-
const addon = upload ? /* @__PURE__ */
|
|
3247
|
-
/* @__PURE__ */
|
|
3745
|
+
const addon = upload ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
3746
|
+
/* @__PURE__ */ jsx17(
|
|
3248
3747
|
"input",
|
|
3249
3748
|
{
|
|
3250
3749
|
ref: inputRef,
|
|
@@ -3257,7 +3756,7 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3257
3756
|
onChange: pick
|
|
3258
3757
|
}
|
|
3259
3758
|
),
|
|
3260
|
-
/* @__PURE__ */
|
|
3759
|
+
/* @__PURE__ */ jsxs14(
|
|
3261
3760
|
Button,
|
|
3262
3761
|
{
|
|
3263
3762
|
type: "button",
|
|
@@ -3266,13 +3765,13 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3266
3765
|
disabled: busy,
|
|
3267
3766
|
onClick: () => inputRef.current?.click(),
|
|
3268
3767
|
children: [
|
|
3269
|
-
busy ? /* @__PURE__ */
|
|
3768
|
+
busy ? /* @__PURE__ */ jsx17(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx17(Upload, {}),
|
|
3270
3769
|
t("Upload")
|
|
3271
3770
|
]
|
|
3272
3771
|
}
|
|
3273
3772
|
)
|
|
3274
3773
|
] }) : null;
|
|
3275
|
-
return /* @__PURE__ */
|
|
3774
|
+
return /* @__PURE__ */ jsx17(TextField, { id, property, value, onCommit, addon, error });
|
|
3276
3775
|
}
|
|
3277
3776
|
var asNumber = (v) => {
|
|
3278
3777
|
const n = Number.parseFloat(String(v || ""));
|
|
@@ -3294,10 +3793,10 @@ function SizeField({ id, property, value, onCommit }) {
|
|
|
3294
3793
|
const next = `${n}${property.unit || "px"}`;
|
|
3295
3794
|
if (next !== value) onCommit(next);
|
|
3296
3795
|
};
|
|
3297
|
-
return /* @__PURE__ */
|
|
3298
|
-
/* @__PURE__ */
|
|
3299
|
-
/* @__PURE__ */
|
|
3300
|
-
/* @__PURE__ */
|
|
3796
|
+
return /* @__PURE__ */ jsxs14("div", { ref, className: "eb:space-y-1.5", children: [
|
|
3797
|
+
/* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
|
|
3798
|
+
/* @__PURE__ */ jsxs14("div", { className: "eb:relative", children: [
|
|
3799
|
+
/* @__PURE__ */ jsx17(
|
|
3301
3800
|
Input,
|
|
3302
3801
|
{
|
|
3303
3802
|
id,
|
|
@@ -3315,7 +3814,7 @@ function SizeField({ id, property, value, onCommit }) {
|
|
|
3315
3814
|
className: "eb:pr-9 eb:tabular-nums"
|
|
3316
3815
|
}
|
|
3317
3816
|
),
|
|
3318
|
-
/* @__PURE__ */
|
|
3817
|
+
/* @__PURE__ */ jsx17("span", { className: "eb:pointer-events-none eb:absolute eb:right-3 eb:top-1/2 eb:-translate-y-1/2 eb:text-[12px] eb:text-foreground-muted", children: property.unit || "px" })
|
|
3319
3818
|
] })
|
|
3320
3819
|
] });
|
|
3321
3820
|
}
|
|
@@ -3338,18 +3837,18 @@ function ColorField({ id, property, value, onCommit }) {
|
|
|
3338
3837
|
}
|
|
3339
3838
|
}, COLOR_DEBOUNCE_MS);
|
|
3340
3839
|
};
|
|
3341
|
-
return /* @__PURE__ */
|
|
3342
|
-
/* @__PURE__ */
|
|
3343
|
-
/* @__PURE__ */
|
|
3840
|
+
return /* @__PURE__ */ jsxs14("div", { ref, className: "eb:space-y-1.5", children: [
|
|
3841
|
+
/* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
|
|
3842
|
+
/* @__PURE__ */ jsx17(ColorPicker, { id, value: draft, onChange: change })
|
|
3344
3843
|
] });
|
|
3345
3844
|
}
|
|
3346
3845
|
var DEFAULT_OPTION = "__default__";
|
|
3347
3846
|
function SelectField({ id, property, value, onCommit }) {
|
|
3348
3847
|
const t = useT();
|
|
3349
3848
|
const current = value || DEFAULT_OPTION;
|
|
3350
|
-
return /* @__PURE__ */
|
|
3351
|
-
/* @__PURE__ */
|
|
3352
|
-
/* @__PURE__ */
|
|
3849
|
+
return /* @__PURE__ */ jsxs14("div", { className: "eb:space-y-1.5", children: [
|
|
3850
|
+
/* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
|
|
3851
|
+
/* @__PURE__ */ jsxs14(
|
|
3353
3852
|
Select,
|
|
3354
3853
|
{
|
|
3355
3854
|
value: current,
|
|
@@ -3358,10 +3857,10 @@ function SelectField({ id, property, value, onCommit }) {
|
|
|
3358
3857
|
if (v !== (value || "")) onCommit(v || null);
|
|
3359
3858
|
},
|
|
3360
3859
|
children: [
|
|
3361
|
-
/* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
3363
|
-
!(property.options || []).some((o) => !o.value) && /* @__PURE__ */
|
|
3364
|
-
(property.options || []).map((o) => /* @__PURE__ */
|
|
3860
|
+
/* @__PURE__ */ jsx17(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx17(SelectValue, { placeholder: t("Default") }) }),
|
|
3861
|
+
/* @__PURE__ */ jsxs14(SelectContent, { children: [
|
|
3862
|
+
!(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx17(SelectItem, { value: DEFAULT_OPTION, children: t("Default") }),
|
|
3863
|
+
(property.options || []).map((o) => /* @__PURE__ */ jsx17(SelectItem, { value: o.value || DEFAULT_OPTION, children: t(o.label) }, o.value || DEFAULT_OPTION))
|
|
3365
3864
|
] })
|
|
3366
3865
|
]
|
|
3367
3866
|
}
|
|
@@ -3371,9 +3870,9 @@ function SelectField({ id, property, value, onCommit }) {
|
|
|
3371
3870
|
var ALIGN_ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify: AlignJustify };
|
|
3372
3871
|
function AlignField({ id, property, value, onCommit }) {
|
|
3373
3872
|
const t = useT();
|
|
3374
|
-
return /* @__PURE__ */
|
|
3375
|
-
/* @__PURE__ */
|
|
3376
|
-
/* @__PURE__ */
|
|
3873
|
+
return /* @__PURE__ */ jsxs14("div", { className: "eb:space-y-1.5", children: [
|
|
3874
|
+
/* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
|
|
3875
|
+
/* @__PURE__ */ jsx17(
|
|
3377
3876
|
"div",
|
|
3378
3877
|
{
|
|
3379
3878
|
id,
|
|
@@ -3383,7 +3882,7 @@ function AlignField({ id, property, value, onCommit }) {
|
|
|
3383
3882
|
children: (property.options || []).map((option) => {
|
|
3384
3883
|
const Icon = ALIGN_ICONS[option.value] || AlignLeft;
|
|
3385
3884
|
const active = value === option.value;
|
|
3386
|
-
return /* @__PURE__ */
|
|
3885
|
+
return /* @__PURE__ */ jsx17(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx17(
|
|
3387
3886
|
"button",
|
|
3388
3887
|
{
|
|
3389
3888
|
type: "button",
|
|
@@ -3395,7 +3894,7 @@ function AlignField({ id, property, value, onCommit }) {
|
|
|
3395
3894
|
"eb:inline-flex eb:h-7 eb:w-7 eb:items-center eb:justify-center eb:rounded-lg2 eb:transition-colors",
|
|
3396
3895
|
active ? "eb:bg-primary eb:text-primary-foreground" : "eb:text-foreground-lighter eb:hover:bg-surface-200 eb:hover:text-foreground"
|
|
3397
3896
|
),
|
|
3398
|
-
children: /* @__PURE__ */
|
|
3897
|
+
children: /* @__PURE__ */ jsx17(Icon, { className: "eb:h-3.5 eb:w-3.5" })
|
|
3399
3898
|
}
|
|
3400
3899
|
) }, option.value);
|
|
3401
3900
|
})
|
|
@@ -3417,10 +3916,10 @@ function SpacingField({ id, property, value, onCommit }) {
|
|
|
3417
3916
|
const next = formatSpacing(sides.map((s) => `${Math.max(0, Math.min(200, Number.parseFloat(s) || 0))}px`));
|
|
3418
3917
|
if (next !== (value || "")) onCommit(next);
|
|
3419
3918
|
};
|
|
3420
|
-
return /* @__PURE__ */
|
|
3421
|
-
/* @__PURE__ */
|
|
3422
|
-
/* @__PURE__ */
|
|
3423
|
-
/* @__PURE__ */
|
|
3919
|
+
return /* @__PURE__ */ jsxs14("fieldset", { ref, className: "eb:space-y-1.5", children: [
|
|
3920
|
+
/* @__PURE__ */ jsx17("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
|
|
3921
|
+
/* @__PURE__ */ jsx17("div", { className: "eb:grid eb:grid-cols-4 eb:gap-1.5", children: SIDE_LABELS.map(([side, label], i) => /* @__PURE__ */ jsxs14("div", { className: "eb:space-y-1", children: [
|
|
3922
|
+
/* @__PURE__ */ jsx17(
|
|
3424
3923
|
Input,
|
|
3425
3924
|
{
|
|
3426
3925
|
id: `${id}-${side}`,
|
|
@@ -3438,7 +3937,7 @@ function SpacingField({ id, property, value, onCommit }) {
|
|
|
3438
3937
|
className: "eb:px-2 eb:text-center eb:tabular-nums"
|
|
3439
3938
|
}
|
|
3440
3939
|
),
|
|
3441
|
-
/* @__PURE__ */
|
|
3940
|
+
/* @__PURE__ */ jsx17("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
|
|
3442
3941
|
] }, side)) })
|
|
3443
3942
|
] });
|
|
3444
3943
|
}
|
|
@@ -3456,15 +3955,15 @@ var FIELDS = {
|
|
|
3456
3955
|
function PropertyField({ property, value, onCommit, idPrefix = "eb", services = null }) {
|
|
3457
3956
|
const Field = FIELDS[property.type] || TextField;
|
|
3458
3957
|
const id = `${idPrefix}-${property.key}`;
|
|
3459
|
-
return /* @__PURE__ */
|
|
3958
|
+
return /* @__PURE__ */ jsx17(Field, { id, property, value, onCommit, services });
|
|
3460
3959
|
}
|
|
3461
3960
|
|
|
3462
3961
|
// src/features/variables/VariableInsertButton.jsx
|
|
3463
|
-
import { useState as
|
|
3962
|
+
import { useState as useState11 } from "react";
|
|
3464
3963
|
import { Braces as Braces3 } from "lucide-react";
|
|
3465
|
-
import { jsx as
|
|
3964
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3466
3965
|
function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
|
|
3467
|
-
const [open, setOpen] =
|
|
3966
|
+
const [open, setOpen] = useState11(false);
|
|
3468
3967
|
const insert = (variable) => {
|
|
3469
3968
|
const input = inputRef.current;
|
|
3470
3969
|
const text = String(value || "");
|
|
@@ -3480,14 +3979,14 @@ function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
|
|
|
3480
3979
|
input.setSelectionRange(start + token.length, start + token.length);
|
|
3481
3980
|
});
|
|
3482
3981
|
};
|
|
3483
|
-
return /* @__PURE__ */
|
|
3484
|
-
/* @__PURE__ */
|
|
3485
|
-
/* @__PURE__ */
|
|
3982
|
+
return /* @__PURE__ */ jsxs15(Popover, { open, onOpenChange: setOpen, children: [
|
|
3983
|
+
/* @__PURE__ */ jsx18(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx18(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx18(Braces3, {}) }) }),
|
|
3984
|
+
/* @__PURE__ */ jsx18(PopoverContent, { align: "end", className: "eb:w-72 eb:p-0", onCloseAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx18(VariablePicker, { variables, onPick: insert, filter: (v) => v.kind !== "link" }) })
|
|
3486
3985
|
] });
|
|
3487
3986
|
}
|
|
3488
3987
|
|
|
3489
3988
|
// src/ui/PropertiesPanel.jsx
|
|
3490
|
-
import { Fragment as
|
|
3989
|
+
import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3491
3990
|
var GROUP_ORDER = Object.values(GROUPS);
|
|
3492
3991
|
function grouped(properties) {
|
|
3493
3992
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3501,9 +4000,9 @@ function grouped(properties) {
|
|
|
3501
4000
|
}
|
|
3502
4001
|
function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
|
|
3503
4002
|
const t = useT();
|
|
3504
|
-
return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */
|
|
3505
|
-
/* @__PURE__ */
|
|
3506
|
-
properties.map((property) => /* @__PURE__ */
|
|
4003
|
+
return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
|
|
4004
|
+
/* @__PURE__ */ jsx19(SectionLabel, { children: t(group) }),
|
|
4005
|
+
properties.map((property) => /* @__PURE__ */ jsx19(
|
|
3507
4006
|
PropertyField,
|
|
3508
4007
|
{
|
|
3509
4008
|
idPrefix,
|
|
@@ -3517,33 +4016,33 @@ function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
|
|
|
3517
4016
|
] }, group));
|
|
3518
4017
|
}
|
|
3519
4018
|
var LABEL_PROPERTY = { key: "content", label: "Label", type: "text", group: GROUPS.content };
|
|
3520
|
-
function PanelHeader({ title, onClose }) {
|
|
4019
|
+
function PanelHeader({ title, onClose, closeLabel = "Deselect" }) {
|
|
3521
4020
|
const t = useT();
|
|
3522
|
-
return /* @__PURE__ */
|
|
3523
|
-
/* @__PURE__ */
|
|
3524
|
-
onClose && /* @__PURE__ */
|
|
4021
|
+
return /* @__PURE__ */ jsxs16("header", { className: "eb:sticky eb:top-0 eb:z-10 eb:flex eb:h-11 eb:shrink-0 eb:items-center eb:gap-2 eb:border-b eb:border-border eb:bg-background eb:px-4", children: [
|
|
4022
|
+
/* @__PURE__ */ jsx19("h3", { className: "eb:min-w-0 eb:flex-1 eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx19(TruncatedText, { children: title }) }),
|
|
4023
|
+
onClose && /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t(closeLabel), children: /* @__PURE__ */ jsx19(X2, {}) })
|
|
3525
4024
|
] });
|
|
3526
4025
|
}
|
|
3527
|
-
function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null }) {
|
|
4026
|
+
function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, onDismiss = null }) {
|
|
3528
4027
|
const t = useT();
|
|
3529
4028
|
const subjectRef = useRef5(null);
|
|
3530
4029
|
const preheaderRef = useRef5(null);
|
|
3531
4030
|
const rootId = editor.getRootId();
|
|
3532
|
-
const root =
|
|
4031
|
+
const root = useMemo6(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
|
|
3533
4032
|
const bodyBlock2 = root?.blockId ? editor.getBlock(root.blockId) : null;
|
|
3534
4033
|
const field = (key) => (e) => onMetaChange({ ...meta, [key]: e.target.value });
|
|
3535
|
-
return /* @__PURE__ */
|
|
3536
|
-
/* @__PURE__ */
|
|
3537
|
-
/* @__PURE__ */
|
|
3538
|
-
/* @__PURE__ */
|
|
3539
|
-
/* @__PURE__ */
|
|
3540
|
-
/* @__PURE__ */
|
|
3541
|
-
/* @__PURE__ */
|
|
4034
|
+
return /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
4035
|
+
/* @__PURE__ */ jsx19(PanelHeader, { title: t("Email"), onClose: onDismiss, closeLabel: "Close" }),
|
|
4036
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:space-y-5 eb:p-4", children: [
|
|
4037
|
+
/* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
|
|
4038
|
+
/* @__PURE__ */ jsx19(SectionLabel, { children: t("Template") }),
|
|
4039
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
|
|
4040
|
+
/* @__PURE__ */ jsxs16(Label, { htmlFor: "eb-meta-name", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: [
|
|
3542
4041
|
t("Name"),
|
|
3543
4042
|
" ",
|
|
3544
|
-
/* @__PURE__ */
|
|
4043
|
+
/* @__PURE__ */ jsx19("span", { className: "eb:text-destructive", children: "*" })
|
|
3545
4044
|
] }),
|
|
3546
|
-
/* @__PURE__ */
|
|
4045
|
+
/* @__PURE__ */ jsx19(
|
|
3547
4046
|
Input,
|
|
3548
4047
|
{
|
|
3549
4048
|
id: "eb-meta-name",
|
|
@@ -3553,12 +4052,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3553
4052
|
"aria-invalid": showErrors && !meta.name.trim() || void 0
|
|
3554
4053
|
}
|
|
3555
4054
|
),
|
|
3556
|
-
showErrors && !meta.name.trim() && /* @__PURE__ */
|
|
4055
|
+
showErrors && !meta.name.trim() && /* @__PURE__ */ jsx19("p", { className: "eb:text-[12px] eb:text-destructive", children: t("Name is required") })
|
|
3557
4056
|
] }),
|
|
3558
|
-
/* @__PURE__ */
|
|
3559
|
-
/* @__PURE__ */
|
|
3560
|
-
/* @__PURE__ */
|
|
3561
|
-
variables && /* @__PURE__ */
|
|
4057
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
|
|
4058
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
|
|
4059
|
+
/* @__PURE__ */ jsx19(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
|
|
4060
|
+
variables && /* @__PURE__ */ jsx19(
|
|
3562
4061
|
VariableInsertButton,
|
|
3563
4062
|
{
|
|
3564
4063
|
variables,
|
|
@@ -3569,12 +4068,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3569
4068
|
}
|
|
3570
4069
|
)
|
|
3571
4070
|
] }),
|
|
3572
|
-
/* @__PURE__ */
|
|
4071
|
+
/* @__PURE__ */ jsx19(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") })
|
|
3573
4072
|
] }),
|
|
3574
|
-
/* @__PURE__ */
|
|
3575
|
-
/* @__PURE__ */
|
|
3576
|
-
/* @__PURE__ */
|
|
3577
|
-
variables && /* @__PURE__ */
|
|
4073
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
|
|
4074
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
|
|
4075
|
+
/* @__PURE__ */ jsx19(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
|
|
4076
|
+
variables && /* @__PURE__ */ jsx19(
|
|
3578
4077
|
VariableInsertButton,
|
|
3579
4078
|
{
|
|
3580
4079
|
variables,
|
|
@@ -3585,7 +4084,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3585
4084
|
}
|
|
3586
4085
|
)
|
|
3587
4086
|
] }),
|
|
3588
|
-
/* @__PURE__ */
|
|
4087
|
+
/* @__PURE__ */ jsx19(
|
|
3589
4088
|
Textarea,
|
|
3590
4089
|
{
|
|
3591
4090
|
ref: preheaderRef,
|
|
@@ -3599,7 +4098,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3599
4098
|
)
|
|
3600
4099
|
] })
|
|
3601
4100
|
] }),
|
|
3602
|
-
root && bodyBlock2 && /* @__PURE__ */
|
|
4101
|
+
root && bodyBlock2 && /* @__PURE__ */ jsx19(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
|
|
3603
4102
|
] })
|
|
3604
4103
|
] });
|
|
3605
4104
|
}
|
|
@@ -3612,11 +4111,12 @@ function PropertiesPanel({
|
|
|
3612
4111
|
actions = null,
|
|
3613
4112
|
showErrors = false,
|
|
3614
4113
|
services = null,
|
|
3615
|
-
variables = null
|
|
4114
|
+
variables = null,
|
|
4115
|
+
onDismiss = null
|
|
3616
4116
|
}) {
|
|
3617
4117
|
const t = useT();
|
|
3618
4118
|
const block = selection?.blockId ? editor.getBlock(selection.blockId) : null;
|
|
3619
|
-
return /* @__PURE__ */
|
|
4119
|
+
return /* @__PURE__ */ jsx19("aside", { "aria-label": t("Properties"), className: "eb:min-h-0 eb:overflow-y-auto eb:border-l eb:border-border", "data-testid": "eb-properties", children: !selection ? /* @__PURE__ */ jsx19(
|
|
3620
4120
|
TemplateSettings,
|
|
3621
4121
|
{
|
|
3622
4122
|
editor,
|
|
@@ -3624,15 +4124,23 @@ function PropertiesPanel({
|
|
|
3624
4124
|
onMetaChange,
|
|
3625
4125
|
changes,
|
|
3626
4126
|
showErrors,
|
|
3627
|
-
variables
|
|
4127
|
+
variables,
|
|
4128
|
+
onDismiss
|
|
3628
4129
|
}
|
|
3629
|
-
) : /* @__PURE__ */
|
|
3630
|
-
/* @__PURE__ */
|
|
4130
|
+
) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
4131
|
+
/* @__PURE__ */ jsx19(
|
|
4132
|
+
PanelHeader,
|
|
4133
|
+
{
|
|
4134
|
+
title: t(selection.label),
|
|
4135
|
+
onClose: onDismiss || (() => editor.select(null)),
|
|
4136
|
+
closeLabel: onDismiss ? "Close" : "Deselect"
|
|
4137
|
+
}
|
|
4138
|
+
),
|
|
3631
4139
|
actions,
|
|
3632
|
-
/* @__PURE__ */
|
|
3633
|
-
selection.
|
|
3634
|
-
/* @__PURE__ */
|
|
3635
|
-
/* @__PURE__ */
|
|
4140
|
+
/* @__PURE__ */ jsxs16("div", { className: "eb:space-y-5 eb:p-4", children: [
|
|
4141
|
+
selection.content !== null && selection.content !== void 0 && /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
|
|
4142
|
+
/* @__PURE__ */ jsx19(SectionLabel, { children: t(GROUPS.content) }),
|
|
4143
|
+
/* @__PURE__ */ jsx19(
|
|
3636
4144
|
PropertyField,
|
|
3637
4145
|
{
|
|
3638
4146
|
idPrefix: "eb-prop",
|
|
@@ -3643,15 +4151,15 @@ function PropertiesPanel({
|
|
|
3643
4151
|
`${selection.id}:content`
|
|
3644
4152
|
)
|
|
3645
4153
|
] }),
|
|
3646
|
-
selection.tagName === "mj-text" && /* @__PURE__ */
|
|
3647
|
-
/* @__PURE__ */
|
|
4154
|
+
selection.tagName === "mj-text" && /* @__PURE__ */ jsx19("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
|
|
4155
|
+
/* @__PURE__ */ jsx19(PropertyGroups, { editor, element: selection, block, idPrefix: "eb-prop", services })
|
|
3648
4156
|
] })
|
|
3649
4157
|
] }) });
|
|
3650
4158
|
}
|
|
3651
4159
|
|
|
3652
4160
|
// src/ui/Toolbar.jsx
|
|
3653
4161
|
import { ArrowLeft as ArrowLeft2, Monitor as Monitor2, Redo2, Smartphone as Smartphone2, Undo2 } from "lucide-react";
|
|
3654
|
-
import { jsx as
|
|
4162
|
+
import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3655
4163
|
function Toolbar({
|
|
3656
4164
|
title,
|
|
3657
4165
|
status,
|
|
@@ -3663,44 +4171,112 @@ function Toolbar({
|
|
|
3663
4171
|
device,
|
|
3664
4172
|
onDeviceChange,
|
|
3665
4173
|
onBack,
|
|
3666
|
-
actions = null
|
|
4174
|
+
actions = null,
|
|
4175
|
+
primary = null,
|
|
4176
|
+
compact = false
|
|
3667
4177
|
}) {
|
|
3668
4178
|
const t = useT();
|
|
3669
|
-
|
|
3670
|
-
/* @__PURE__ */
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
4179
|
+
const identity2 = /* @__PURE__ */ jsxs17(Fragment7, { children: [
|
|
4180
|
+
/* @__PURE__ */ jsxs17(
|
|
4181
|
+
Button,
|
|
4182
|
+
{
|
|
4183
|
+
variant: "ghost",
|
|
4184
|
+
size: compact ? "icon-sm-compact" : "sm-compact",
|
|
4185
|
+
onClick: onBack,
|
|
4186
|
+
"aria-label": compact ? t("Back") : void 0,
|
|
4187
|
+
children: [
|
|
4188
|
+
/* @__PURE__ */ jsx20(ArrowLeft2, {}),
|
|
4189
|
+
!compact && t("Back")
|
|
4190
|
+
]
|
|
4191
|
+
}
|
|
4192
|
+
),
|
|
4193
|
+
/* @__PURE__ */ jsx20(Separator, { orientation: "vertical", className: "eb:h-5" }),
|
|
4194
|
+
/* @__PURE__ */ jsxs17("div", { className: cn("eb:min-w-0 eb:flex-1", !compact && "eb:max-w-[320px]"), children: [
|
|
4195
|
+
/* @__PURE__ */ jsx20("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx20(TruncatedText, { children: title || t("New template") }) }),
|
|
4196
|
+
/* @__PURE__ */ jsx20("p", { className: "eb:text-[11px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: status })
|
|
4197
|
+
] })
|
|
4198
|
+
] });
|
|
4199
|
+
const editing = /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: [
|
|
4200
|
+
/* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
|
|
4201
|
+
/* @__PURE__ */ jsx20(TooltipWrap, { label: t("Undo (Ctrl+Z)"), children: /* @__PURE__ */ jsx20(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onUndo, disabled: !ready || !canUndo, "aria-label": t("Undo"), children: /* @__PURE__ */ jsx20(Undo2, {}) }) }),
|
|
4202
|
+
/* @__PURE__ */ jsx20(TooltipWrap, { label: t("Redo (Ctrl+Y)"), children: /* @__PURE__ */ jsx20(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onRedo, disabled: !ready || !canRedo, "aria-label": t("Redo"), children: /* @__PURE__ */ jsx20(Redo2, {}) }) })
|
|
3678
4203
|
] }),
|
|
3679
|
-
/* @__PURE__ */
|
|
3680
|
-
/* @__PURE__ */
|
|
3681
|
-
/* @__PURE__ */
|
|
3682
|
-
|
|
4204
|
+
/* @__PURE__ */ jsx20(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs17(TabsList, { "aria-label": t("Device"), children: [
|
|
4205
|
+
/* @__PURE__ */ jsxs17(TabsTrigger, { value: "desktop", disabled: !ready, "aria-label": compact ? t("Desktop") : void 0, children: [
|
|
4206
|
+
/* @__PURE__ */ jsx20(Monitor2, {}),
|
|
4207
|
+
!compact && t("Desktop")
|
|
3683
4208
|
] }),
|
|
3684
|
-
/* @__PURE__ */
|
|
3685
|
-
/* @__PURE__ */
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
4209
|
+
/* @__PURE__ */ jsxs17(TabsTrigger, { value: "mobile", disabled: !ready, "aria-label": compact ? t("Mobile") : void 0, children: [
|
|
4210
|
+
/* @__PURE__ */ jsx20(Smartphone2, {}),
|
|
4211
|
+
!compact && t("Mobile")
|
|
4212
|
+
] })
|
|
4213
|
+
] }) })
|
|
4214
|
+
] });
|
|
4215
|
+
if (compact) {
|
|
4216
|
+
return /* @__PURE__ */ jsxs17("div", { className: "eb:shrink-0 eb:border-b eb:border-border", "data-layout": "compact", children: [
|
|
4217
|
+
/* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:h-12 eb:items-center eb:gap-2 eb:px-2", children: [
|
|
4218
|
+
identity2,
|
|
4219
|
+
primary
|
|
4220
|
+
] }),
|
|
4221
|
+
/* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:items-center eb:gap-2 eb:overflow-x-auto eb:px-2 eb:pb-2", "data-testid": "eb-toolbar-actions", children: [
|
|
4222
|
+
editing,
|
|
4223
|
+
/* @__PURE__ */ jsx20(Separator, { orientation: "vertical", className: "eb:h-5 eb:shrink-0" }),
|
|
4224
|
+
/* @__PURE__ */ jsx20("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: actions })
|
|
4225
|
+
] })
|
|
4226
|
+
] });
|
|
4227
|
+
}
|
|
4228
|
+
return /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:h-12 eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-3", children: [
|
|
4229
|
+
identity2,
|
|
4230
|
+
editing,
|
|
4231
|
+
/* @__PURE__ */ jsxs17("div", { className: "eb:ml-auto eb:flex eb:items-center eb:gap-2", "data-testid": "eb-toolbar-actions", children: [
|
|
4232
|
+
actions,
|
|
4233
|
+
primary
|
|
4234
|
+
] })
|
|
4235
|
+
] });
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
// src/ui/Drawer.jsx
|
|
4239
|
+
import { useEffect as useEffect6 } from "react";
|
|
4240
|
+
import { X as X3 } from "lucide-react";
|
|
4241
|
+
import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4242
|
+
function Drawer({ open, side = "left", title, onClose, children, testId, header: header2 = true }) {
|
|
4243
|
+
const t = useT();
|
|
4244
|
+
useEffect6(() => {
|
|
4245
|
+
if (!open) return void 0;
|
|
4246
|
+
const onKey = (e) => {
|
|
4247
|
+
if (e.key === "Escape") onClose();
|
|
4248
|
+
};
|
|
4249
|
+
window.addEventListener("keydown", onKey);
|
|
4250
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
4251
|
+
}, [open, onClose]);
|
|
4252
|
+
return /* @__PURE__ */ jsxs18("div", { className: cn("eb:absolute eb:inset-0 eb:z-20", !open && "eb:hidden"), "data-testid": testId, "data-open": open ? "true" : "false", children: [
|
|
4253
|
+
/* @__PURE__ */ jsx21("div", { className: "eb:absolute eb:inset-0 eb:bg-black/30", onClick: onClose, "aria-hidden": "true" }),
|
|
4254
|
+
/* @__PURE__ */ jsxs18(
|
|
4255
|
+
"section",
|
|
4256
|
+
{
|
|
4257
|
+
role: "dialog",
|
|
4258
|
+
"aria-label": title,
|
|
4259
|
+
className: cn(
|
|
4260
|
+
"eb:absolute eb:inset-y-0 eb:flex eb:w-[min(340px,88%)] eb:flex-col eb:bg-background eb:shadow-lg",
|
|
4261
|
+
side === "left" ? "eb:left-0 eb:border-r eb:border-border" : "eb:right-0 eb:border-l eb:border-border"
|
|
4262
|
+
),
|
|
4263
|
+
children: [
|
|
4264
|
+
header2 && /* @__PURE__ */ jsxs18("header", { className: "eb:flex eb:h-10 eb:shrink-0 eb:items-center eb:justify-between eb:border-b eb:border-border eb:px-3", children: [
|
|
4265
|
+
/* @__PURE__ */ jsx21("h3", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: title }),
|
|
4266
|
+
/* @__PURE__ */ jsx21(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx21(X3, {}) })
|
|
4267
|
+
] }),
|
|
4268
|
+
/* @__PURE__ */ jsx21("div", { className: "eb:grid eb:min-h-0 eb:flex-1", children })
|
|
4269
|
+
]
|
|
4270
|
+
}
|
|
4271
|
+
)
|
|
3696
4272
|
] });
|
|
3697
4273
|
}
|
|
3698
4274
|
|
|
3699
4275
|
// src/ui/useEmailEditorState.js
|
|
3700
|
-
import { useCallback as useCallback2, useEffect as
|
|
4276
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef6, useState as useState12 } from "react";
|
|
3701
4277
|
var DIRTY_CHECK_MS = 150;
|
|
3702
4278
|
function useEmailEditorState(editor) {
|
|
3703
|
-
const [state, setState] =
|
|
4279
|
+
const [state, setState] = useState12(() => ({
|
|
3704
4280
|
status: editor?.status || "idle",
|
|
3705
4281
|
selection: null,
|
|
3706
4282
|
canUndo: false,
|
|
@@ -3734,7 +4310,7 @@ function useEmailEditorState(editor) {
|
|
|
3734
4310
|
forced.current = true;
|
|
3735
4311
|
setState((s) => ({ ...s, dirty: true }));
|
|
3736
4312
|
}, []);
|
|
3737
|
-
|
|
4313
|
+
useEffect7(() => {
|
|
3738
4314
|
if (!editor) return void 0;
|
|
3739
4315
|
const patch = (values) => setState((s) => ({ ...s, ...values }));
|
|
3740
4316
|
const measure = () => {
|
|
@@ -3780,8 +4356,33 @@ function useEmailEditorState(editor) {
|
|
|
3780
4356
|
return { ...state, markClean, markDirty, checkDirty };
|
|
3781
4357
|
}
|
|
3782
4358
|
|
|
4359
|
+
// src/ui/useLayoutWidth.js
|
|
4360
|
+
import { useEffect as useEffect8, useState as useState13 } from "react";
|
|
4361
|
+
var LAYOUT_BREAKPOINTS = { wide: 1180, medium: 760 };
|
|
4362
|
+
function layoutFor(width) {
|
|
4363
|
+
if (width >= LAYOUT_BREAKPOINTS.wide) return "wide";
|
|
4364
|
+
if (width >= LAYOUT_BREAKPOINTS.medium) return "medium";
|
|
4365
|
+
return "narrow";
|
|
4366
|
+
}
|
|
4367
|
+
function useLayoutWidth(ref) {
|
|
4368
|
+
const [layout2, setLayout] = useState13(
|
|
4369
|
+
/** @type {'wide'|'medium'|'narrow'} */
|
|
4370
|
+
"wide"
|
|
4371
|
+
);
|
|
4372
|
+
useEffect8(() => {
|
|
4373
|
+
const el = ref.current;
|
|
4374
|
+
if (!el || typeof ResizeObserver === "undefined") return void 0;
|
|
4375
|
+
const measure = () => setLayout(layoutFor(el.getBoundingClientRect().width));
|
|
4376
|
+
measure();
|
|
4377
|
+
const observer = new ResizeObserver(measure);
|
|
4378
|
+
observer.observe(el);
|
|
4379
|
+
return () => observer.disconnect();
|
|
4380
|
+
}, [ref]);
|
|
4381
|
+
return layout2;
|
|
4382
|
+
}
|
|
4383
|
+
|
|
3783
4384
|
// src/ui/EmailBuilder.jsx
|
|
3784
|
-
import { Fragment as
|
|
4385
|
+
import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3785
4386
|
var EMPTY_META = { name: "", subject: "", preheader: "", description: "" };
|
|
3786
4387
|
function metaOf(template) {
|
|
3787
4388
|
return {
|
|
@@ -3799,6 +4400,7 @@ function Builder({
|
|
|
3799
4400
|
variables: variablesProp,
|
|
3800
4401
|
sender,
|
|
3801
4402
|
starterMjml,
|
|
4403
|
+
templates: galleryProp,
|
|
3802
4404
|
testRecipients,
|
|
3803
4405
|
initialMjml,
|
|
3804
4406
|
initialMeta,
|
|
@@ -3809,11 +4411,11 @@ function Builder({
|
|
|
3809
4411
|
className
|
|
3810
4412
|
}) {
|
|
3811
4413
|
const { t, locale, notify, confirm } = useUi();
|
|
3812
|
-
const editor =
|
|
3813
|
-
const storage =
|
|
3814
|
-
const systemVariables =
|
|
3815
|
-
const [customVariables, setCustomVariables] =
|
|
3816
|
-
const variables =
|
|
4414
|
+
const editor = useMemo7(() => injected || createDefaultEmailEditor({ locale }), [injected, locale]);
|
|
4415
|
+
const storage = useMemo7(() => injectedStorage || createHttpStorage(), [injectedStorage]);
|
|
4416
|
+
const systemVariables = useMemo7(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
|
|
4417
|
+
const [customVariables, setCustomVariables] = useState14([]);
|
|
4418
|
+
const variables = useMemo7(() => mergeVariables(systemVariables, customVariables), [systemVariables, customVariables]);
|
|
3817
4419
|
const canManageVariables = typeof storage.saveVariable === "function" && typeof storage.listVariables === "function";
|
|
3818
4420
|
const reloadVariables = useCallback3(async () => {
|
|
3819
4421
|
if (typeof storage.listVariables !== "function") return;
|
|
@@ -3823,23 +4425,37 @@ function Builder({
|
|
|
3823
4425
|
notify("error", error.message);
|
|
3824
4426
|
}
|
|
3825
4427
|
}, [storage, notify]);
|
|
3826
|
-
|
|
4428
|
+
useEffect9(() => {
|
|
3827
4429
|
reloadVariables();
|
|
3828
4430
|
}, [reloadVariables]);
|
|
3829
|
-
const starter =
|
|
3830
|
-
const
|
|
4431
|
+
const starter = useMemo7(() => starterMjml || createStarterMjml({ sender, t }), [starterMjml, sender, t]);
|
|
4432
|
+
const templates = useMemo7(
|
|
4433
|
+
() => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: sender || {}, t }),
|
|
4434
|
+
[galleryProp, sender, t]
|
|
4435
|
+
);
|
|
4436
|
+
const services = useMemo7(() => ({ uploadImage: (file) => storage.uploadImage(file) }), [storage]);
|
|
3831
4437
|
const state = useEmailEditorState(editor);
|
|
3832
4438
|
const autosaveOn = isFeatureEnabled("autosave");
|
|
3833
4439
|
const autosaveOptions = emailBuilderFeatures.get("autosave")?.options || {};
|
|
3834
|
-
const [template, setTemplate] =
|
|
3835
|
-
const [boot, setBoot] =
|
|
3836
|
-
const [meta, setMeta] =
|
|
3837
|
-
const [savedMeta, setSavedMeta] =
|
|
3838
|
-
const [saving, setSaving] =
|
|
3839
|
-
const [showErrors, setShowErrors] =
|
|
3840
|
-
const [autosaveBlocked, setAutosaveBlocked] =
|
|
3841
|
-
const [engineError, setEngineError] =
|
|
3842
|
-
const [attempt, setAttempt] =
|
|
4440
|
+
const [template, setTemplate] = useState14({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
|
|
4441
|
+
const [boot, setBoot] = useState14({ status: "loading", document: null, draft: false, message: null });
|
|
4442
|
+
const [meta, setMeta] = useState14(EMPTY_META);
|
|
4443
|
+
const [savedMeta, setSavedMeta] = useState14(EMPTY_META);
|
|
4444
|
+
const [saving, setSaving] = useState14(null);
|
|
4445
|
+
const [showErrors, setShowErrors] = useState14(false);
|
|
4446
|
+
const [autosaveBlocked, setAutosaveBlocked] = useState14(null);
|
|
4447
|
+
const [engineError, setEngineError] = useState14(null);
|
|
4448
|
+
const [attempt, setAttempt] = useState14(0);
|
|
4449
|
+
const rootRef = useRef7(null);
|
|
4450
|
+
const layout2 = useLayoutWidth(rootRef);
|
|
4451
|
+
const compact = layout2 !== "wide";
|
|
4452
|
+
const blocksInDrawer = layout2 === "narrow";
|
|
4453
|
+
const [drawer, setDrawer] = useState14(null);
|
|
4454
|
+
const closeDrawer = useCallback3(() => setDrawer(null), []);
|
|
4455
|
+
useEffect9(() => {
|
|
4456
|
+
if (!compact) setDrawer(null);
|
|
4457
|
+
else if (!blocksInDrawer) setDrawer((d) => d === "blocks" ? null : d);
|
|
4458
|
+
}, [compact, blocksInDrawer]);
|
|
3843
4459
|
const ready = state.status === "ready";
|
|
3844
4460
|
const metaDirty = !sameMeta(meta, savedMeta);
|
|
3845
4461
|
const dirty = state.dirty || metaDirty;
|
|
@@ -3848,7 +4464,7 @@ function Builder({
|
|
|
3848
4464
|
[locale]
|
|
3849
4465
|
);
|
|
3850
4466
|
const booted = useRef7(false);
|
|
3851
|
-
|
|
4467
|
+
useEffect9(() => {
|
|
3852
4468
|
if (booted.current) return;
|
|
3853
4469
|
booted.current = true;
|
|
3854
4470
|
(async () => {
|
|
@@ -3897,7 +4513,7 @@ function Builder({
|
|
|
3897
4513
|
if (!boot.document) editor.loadMjml(initialMjml || starter);
|
|
3898
4514
|
}, [editor, boot.document, starter, initialMjml]);
|
|
3899
4515
|
const { markDirty, markClean, checkDirty } = state;
|
|
3900
|
-
|
|
4516
|
+
useEffect9(() => {
|
|
3901
4517
|
if (ready && boot.draft) markDirty();
|
|
3902
4518
|
}, [ready, boot.draft, markDirty]);
|
|
3903
4519
|
const isDirty = useCallback3(
|
|
@@ -3906,17 +4522,17 @@ function Builder({
|
|
|
3906
4522
|
);
|
|
3907
4523
|
const reportDirty = useRef7(onDirtyChange);
|
|
3908
4524
|
reportDirty.current = onDirtyChange;
|
|
3909
|
-
|
|
4525
|
+
useEffect9(() => {
|
|
3910
4526
|
reportDirty.current?.(dirty);
|
|
3911
4527
|
}, [dirty]);
|
|
3912
|
-
|
|
4528
|
+
useEffect9(() => {
|
|
3913
4529
|
if (!dirtyCheckRef) return void 0;
|
|
3914
4530
|
dirtyCheckRef.current = isDirty;
|
|
3915
4531
|
return () => {
|
|
3916
4532
|
dirtyCheckRef.current = null;
|
|
3917
4533
|
};
|
|
3918
4534
|
}, [dirtyCheckRef, isDirty]);
|
|
3919
|
-
|
|
4535
|
+
useEffect9(() => {
|
|
3920
4536
|
if (!dirty) return void 0;
|
|
3921
4537
|
const warn = (e) => {
|
|
3922
4538
|
e.preventDefault();
|
|
@@ -3927,7 +4543,7 @@ function Builder({
|
|
|
3927
4543
|
}, [dirty]);
|
|
3928
4544
|
const savingRef = useRef7(false);
|
|
3929
4545
|
const latest = useRef7({});
|
|
3930
|
-
latest.current = { template, meta, onSaved };
|
|
4546
|
+
latest.current = { template, meta, onSaved, compact };
|
|
3931
4547
|
const save = useCallback3(
|
|
3932
4548
|
async ({ auto = false } = {}) => {
|
|
3933
4549
|
if (savingRef.current || editor.status !== "ready") return false;
|
|
@@ -3936,6 +4552,7 @@ function Builder({
|
|
|
3936
4552
|
if (!auto) {
|
|
3937
4553
|
setShowErrors(true);
|
|
3938
4554
|
editor.select(null);
|
|
4555
|
+
setDrawer((d) => d === null && latest.current.compact ? "properties" : d);
|
|
3939
4556
|
notify("error", t("Give the template a name before saving it."));
|
|
3940
4557
|
}
|
|
3941
4558
|
return false;
|
|
@@ -4008,7 +4625,7 @@ function Builder({
|
|
|
4008
4625
|
},
|
|
4009
4626
|
[editor, storage, markClean, notify, confirm, t]
|
|
4010
4627
|
);
|
|
4011
|
-
|
|
4628
|
+
useEffect9(() => {
|
|
4012
4629
|
const onKey = (e) => {
|
|
4013
4630
|
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "s") {
|
|
4014
4631
|
e.preventDefault();
|
|
@@ -4018,11 +4635,11 @@ function Builder({
|
|
|
4018
4635
|
window.addEventListener("keydown", onKey, true);
|
|
4019
4636
|
return () => window.removeEventListener("keydown", onKey, true);
|
|
4020
4637
|
}, [save]);
|
|
4021
|
-
|
|
4638
|
+
useEffect9(() => {
|
|
4022
4639
|
if (!ready) return void 0;
|
|
4023
4640
|
return editor.addShortcut("save", "mod+s", () => save());
|
|
4024
4641
|
}, [ready, editor, save]);
|
|
4025
|
-
const scheduler =
|
|
4642
|
+
const scheduler = useMemo7(
|
|
4026
4643
|
() => createAutosaveScheduler({
|
|
4027
4644
|
run: () => save({ auto: true }),
|
|
4028
4645
|
idleMs: autosaveOptions.idleMs,
|
|
@@ -4030,13 +4647,13 @@ function Builder({
|
|
|
4030
4647
|
}),
|
|
4031
4648
|
[save, autosaveOptions.idleMs, autosaveOptions.maxWaitMs]
|
|
4032
4649
|
);
|
|
4033
|
-
|
|
4034
|
-
|
|
4650
|
+
useEffect9(() => () => scheduler.dispose(), [scheduler]);
|
|
4651
|
+
useEffect9(() => {
|
|
4035
4652
|
if (!autosaveOn || !ready || !template.id || autosaveBlocked) return;
|
|
4036
4653
|
if (dirty) scheduler.notify();
|
|
4037
4654
|
else scheduler.reset();
|
|
4038
4655
|
}, [autosaveOn, ready, template.id, autosaveBlocked, dirty, state.changes, meta, scheduler]);
|
|
4039
|
-
|
|
4656
|
+
useEffect9(() => {
|
|
4040
4657
|
if (!autosaveOn || !ready || template.id || !dirty) return void 0;
|
|
4041
4658
|
const timer = setTimeout(() => {
|
|
4042
4659
|
if (editor.status === "ready") saveLocalDraft({ document: editor.getDocument(), meta });
|
|
@@ -4061,6 +4678,8 @@ function Builder({
|
|
|
4061
4678
|
storage,
|
|
4062
4679
|
template,
|
|
4063
4680
|
meta,
|
|
4681
|
+
setMeta,
|
|
4682
|
+
templates,
|
|
4064
4683
|
ready,
|
|
4065
4684
|
isDirty,
|
|
4066
4685
|
markDirty,
|
|
@@ -4074,15 +4693,32 @@ function Builder({
|
|
|
4074
4693
|
const toolbarFeatures = emailBuilderFeatures.list(
|
|
4075
4694
|
(f) => f.enabled !== false && Boolean(f.toolbar) && (f.isAvailable ? f.isAvailable(featureContext) : true)
|
|
4076
4695
|
);
|
|
4077
|
-
|
|
4696
|
+
const properties = /* @__PURE__ */ jsx22(
|
|
4697
|
+
PropertiesPanel,
|
|
4698
|
+
{
|
|
4699
|
+
onDismiss: compact ? closeDrawer : null,
|
|
4700
|
+
editor,
|
|
4701
|
+
selection: ready ? state.selection : null,
|
|
4702
|
+
meta,
|
|
4703
|
+
onMetaChange: setMeta,
|
|
4704
|
+
changes: state.changes,
|
|
4705
|
+
showErrors,
|
|
4706
|
+
actions: /* @__PURE__ */ jsx22(ElementActions, { editor, selection: state.selection }),
|
|
4707
|
+
services,
|
|
4708
|
+
variables
|
|
4709
|
+
}
|
|
4710
|
+
);
|
|
4711
|
+
return /* @__PURE__ */ jsxs19(
|
|
4078
4712
|
"div",
|
|
4079
4713
|
{
|
|
4714
|
+
ref: rootRef,
|
|
4080
4715
|
className: cn("eb-root eb:relative eb:flex eb:h-full eb:min-h-0 eb:flex-col eb:bg-background eb:text-foreground", className),
|
|
4081
4716
|
"data-testid": "email-builder",
|
|
4082
4717
|
"data-status": state.status,
|
|
4718
|
+
"data-layout": layout2,
|
|
4083
4719
|
"data-template-id": template.id || "",
|
|
4084
4720
|
children: [
|
|
4085
|
-
/* @__PURE__ */
|
|
4721
|
+
/* @__PURE__ */ jsx22(
|
|
4086
4722
|
Toolbar,
|
|
4087
4723
|
{
|
|
4088
4724
|
title: meta.name,
|
|
@@ -4095,80 +4731,110 @@ function Builder({
|
|
|
4095
4731
|
device: state.device,
|
|
4096
4732
|
onDeviceChange: (device) => editor.setDevice(device),
|
|
4097
4733
|
onBack: onClose ? back : void 0,
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
] })
|
|
4734
|
+
compact,
|
|
4735
|
+
actions: toolbarFeatures.map((feature) => {
|
|
4736
|
+
const Action = feature.toolbar;
|
|
4737
|
+
return /* @__PURE__ */ jsx22(Action, { context: featureContext }, feature.id);
|
|
4738
|
+
}),
|
|
4739
|
+
primary: /* @__PURE__ */ jsxs19(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
|
|
4740
|
+
saving === "manual" ? /* @__PURE__ */ jsx22(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx22(Save, {}),
|
|
4741
|
+
t("Save")
|
|
4107
4742
|
] })
|
|
4108
4743
|
}
|
|
4109
4744
|
),
|
|
4110
|
-
/* @__PURE__ */
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
{
|
|
4119
|
-
variant: "outline",
|
|
4120
|
-
size: "sm-compact",
|
|
4121
|
-
onClick: () => {
|
|
4122
|
-
setEngineError(null);
|
|
4123
|
-
setAttempt((n) => n + 1);
|
|
4124
|
-
},
|
|
4125
|
-
children: t("Retry")
|
|
4126
|
-
}
|
|
4127
|
-
)
|
|
4128
|
-
] }) : /* @__PURE__ */ jsxs17(Fragment6, { children: [
|
|
4129
|
-
boot.status === "ready" && /* @__PURE__ */ jsx20(
|
|
4130
|
-
EmailCanvas,
|
|
4131
|
-
{
|
|
4132
|
-
editor,
|
|
4133
|
-
document: boot.document,
|
|
4134
|
-
onReady,
|
|
4135
|
-
onError: (error) => setEngineError(error.message)
|
|
4136
|
-
},
|
|
4137
|
-
attempt
|
|
4745
|
+
/* @__PURE__ */ jsxs19(
|
|
4746
|
+
"div",
|
|
4747
|
+
{
|
|
4748
|
+
className: cn(
|
|
4749
|
+
"eb:relative eb:grid eb:min-h-0 eb:flex-1 eb:grid-rows-1",
|
|
4750
|
+
layout2 === "wide" && "eb:grid-cols-[224px_minmax(0,1fr)_296px]",
|
|
4751
|
+
layout2 === "medium" && "eb:grid-cols-[200px_minmax(0,1fr)]",
|
|
4752
|
+
layout2 === "narrow" && "eb:grid-cols-1"
|
|
4138
4753
|
),
|
|
4139
|
-
|
|
4140
|
-
/* @__PURE__ */
|
|
4141
|
-
/* @__PURE__ */
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4754
|
+
children: [
|
|
4755
|
+
!blocksInDrawer && /* @__PURE__ */ jsx22(BlockSidebar, { editor, disabled: !ready, onAdded: compact ? () => setDrawer("properties") : void 0 }),
|
|
4756
|
+
/* @__PURE__ */ jsx22("div", { className: "eb:relative eb:min-w-0 eb:bg-surface-75", "aria-busy": !ready, children: boot.status === "error" || engineError ? /* @__PURE__ */ jsxs19("div", { role: "alert", className: "eb:flex eb:h-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:p-6 eb:text-center", children: [
|
|
4757
|
+
/* @__PURE__ */ jsx22(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
|
|
4758
|
+
/* @__PURE__ */ jsx22("p", { className: "eb:text-[13px] eb:text-foreground", children: boot.status === "error" ? t("The template could not be loaded.") : t("The builder could not start.") }),
|
|
4759
|
+
/* @__PURE__ */ jsx22("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
|
|
4760
|
+
engineError && /* @__PURE__ */ jsx22(
|
|
4761
|
+
Button,
|
|
4762
|
+
{
|
|
4763
|
+
variant: "outline",
|
|
4764
|
+
size: "sm-compact",
|
|
4765
|
+
onClick: () => {
|
|
4766
|
+
setEngineError(null);
|
|
4767
|
+
setAttempt((n) => n + 1);
|
|
4768
|
+
},
|
|
4769
|
+
children: t("Retry")
|
|
4770
|
+
}
|
|
4771
|
+
)
|
|
4772
|
+
] }) : /* @__PURE__ */ jsxs19(Fragment8, { children: [
|
|
4773
|
+
boot.status === "ready" && /* @__PURE__ */ jsx22(
|
|
4774
|
+
EmailCanvas,
|
|
4775
|
+
{
|
|
4776
|
+
editor,
|
|
4777
|
+
document: boot.document,
|
|
4778
|
+
onReady,
|
|
4779
|
+
onError: (error) => setEngineError(error.message)
|
|
4780
|
+
},
|
|
4781
|
+
attempt
|
|
4782
|
+
),
|
|
4783
|
+
!ready && /* @__PURE__ */ jsxs19("div", { className: "eb:absolute eb:inset-0 eb:flex eb:items-center eb:justify-center eb:bg-surface-75", role: "status", "aria-live": "polite", children: [
|
|
4784
|
+
/* @__PURE__ */ jsx22(Loader25, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
|
|
4785
|
+
/* @__PURE__ */ jsx22("span", { className: "eb:sr-only", children: t("Loading the builder") })
|
|
4786
|
+
] })
|
|
4787
|
+
] }) }),
|
|
4788
|
+
!compact && properties,
|
|
4789
|
+
blocksInDrawer && /* @__PURE__ */ jsx22(Drawer, { open: drawer === "blocks", side: "left", title: t("Add a block"), onClose: closeDrawer, testId: "eb-blocks-drawer", children: /* @__PURE__ */ jsx22(BlockSidebar, { editor, disabled: !ready, onAdded: () => setDrawer("properties") }) }),
|
|
4790
|
+
compact && /* @__PURE__ */ jsx22(
|
|
4791
|
+
Drawer,
|
|
4792
|
+
{
|
|
4793
|
+
open: drawer === "properties",
|
|
4794
|
+
side: "right",
|
|
4795
|
+
title: t("Properties"),
|
|
4796
|
+
onClose: closeDrawer,
|
|
4797
|
+
testId: "eb-properties-drawer",
|
|
4798
|
+
header: false,
|
|
4799
|
+
children: properties
|
|
4800
|
+
}
|
|
4801
|
+
)
|
|
4802
|
+
]
|
|
4803
|
+
}
|
|
4804
|
+
),
|
|
4805
|
+
compact && /* @__PURE__ */ jsxs19("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2 eb:border-t eb:border-border eb:px-2 eb:py-2", "data-testid": "eb-compact-bar", children: [
|
|
4806
|
+
blocksInDrawer && /* @__PURE__ */ jsxs19(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setDrawer("blocks"), children: [
|
|
4807
|
+
/* @__PURE__ */ jsx22(Plus2, {}),
|
|
4808
|
+
t("Add a block")
|
|
4809
|
+
] }),
|
|
4810
|
+
/* @__PURE__ */ jsxs19(
|
|
4811
|
+
Button,
|
|
4146
4812
|
{
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4813
|
+
variant: state.selection ? "default" : "outline",
|
|
4814
|
+
size: "sm-compact",
|
|
4815
|
+
className: "eb:ml-auto eb:min-w-0",
|
|
4816
|
+
disabled: !ready,
|
|
4817
|
+
onClick: () => setDrawer("properties"),
|
|
4818
|
+
children: [
|
|
4819
|
+
/* @__PURE__ */ jsx22(SlidersHorizontal, {}),
|
|
4820
|
+
/* @__PURE__ */ jsx22("span", { className: "eb:truncate", children: ready && state.selection ? t("Edit: {element}", { element: t(state.selection.label) }) : t("Email settings") })
|
|
4821
|
+
]
|
|
4156
4822
|
}
|
|
4157
4823
|
)
|
|
4158
4824
|
] }),
|
|
4159
|
-
/* @__PURE__ */
|
|
4825
|
+
/* @__PURE__ */ jsx22(NoticeArea, {})
|
|
4160
4826
|
]
|
|
4161
4827
|
}
|
|
4162
4828
|
);
|
|
4163
4829
|
}
|
|
4164
4830
|
function EmailBuilder({ locale = "en", messages, onNotify, ...props }) {
|
|
4165
|
-
return /* @__PURE__ */
|
|
4831
|
+
return /* @__PURE__ */ jsx22(UiProvider, { locale, messages, onNotify, children: /* @__PURE__ */ jsx22(Builder, { ...props, templateId: props.templateId ?? null }) });
|
|
4166
4832
|
}
|
|
4167
4833
|
|
|
4168
4834
|
// src/ui/EmailBuilderBoundary.jsx
|
|
4169
4835
|
import { Component } from "react";
|
|
4170
4836
|
import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft3, RefreshCw as RefreshCw2 } from "lucide-react";
|
|
4171
|
-
import { jsx as
|
|
4837
|
+
import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
4172
4838
|
var isChunkError = (error) => error?.name === "ChunkLoadError" || /Loading (CSS )?chunk .* failed/i.test(String(error?.message || ""));
|
|
4173
4839
|
var english = (text) => text;
|
|
4174
4840
|
var EmailBuilderBoundary = class extends Component {
|
|
@@ -4193,23 +4859,23 @@ var EmailBuilderBoundary = class extends Component {
|
|
|
4193
4859
|
const { error, attempt } = this.state;
|
|
4194
4860
|
const t = this.props.t || english;
|
|
4195
4861
|
if (!error) {
|
|
4196
|
-
return /* @__PURE__ */
|
|
4862
|
+
return /* @__PURE__ */ jsx23("div", { style: { height: "100%" }, children: this.props.children }, attempt);
|
|
4197
4863
|
}
|
|
4198
|
-
return /* @__PURE__ */
|
|
4864
|
+
return /* @__PURE__ */ jsxs20(
|
|
4199
4865
|
"div",
|
|
4200
4866
|
{
|
|
4201
4867
|
role: "alert",
|
|
4202
4868
|
className: "eb-root eb:flex eb:h-full eb:flex-col eb:items-center eb:justify-center eb:gap-3 eb:p-6 eb:text-center",
|
|
4203
4869
|
children: [
|
|
4204
|
-
/* @__PURE__ */
|
|
4205
|
-
/* @__PURE__ */
|
|
4206
|
-
/* @__PURE__ */
|
|
4207
|
-
this.props.onClose && /* @__PURE__ */
|
|
4208
|
-
/* @__PURE__ */
|
|
4870
|
+
/* @__PURE__ */ jsx23(AlertTriangle3, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
|
|
4871
|
+
/* @__PURE__ */ jsx23("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
|
|
4872
|
+
/* @__PURE__ */ jsxs20("div", { className: "eb:flex eb:gap-2", children: [
|
|
4873
|
+
this.props.onClose && /* @__PURE__ */ jsxs20(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
|
|
4874
|
+
/* @__PURE__ */ jsx23(ArrowLeft3, {}),
|
|
4209
4875
|
t("Back")
|
|
4210
4876
|
] }),
|
|
4211
|
-
/* @__PURE__ */
|
|
4212
|
-
/* @__PURE__ */
|
|
4877
|
+
/* @__PURE__ */ jsxs20(Button, { size: "sm", onClick: this.retry, children: [
|
|
4878
|
+
/* @__PURE__ */ jsx23(RefreshCw2, {}),
|
|
4213
4879
|
t("Retry")
|
|
4214
4880
|
] })
|
|
4215
4881
|
] })
|
|
@@ -4242,10 +4908,12 @@ export {
|
|
|
4242
4908
|
LOCALES,
|
|
4243
4909
|
STARTER_MJML,
|
|
4244
4910
|
StorageError,
|
|
4911
|
+
TEMPLATE_GALLERY,
|
|
4245
4912
|
UiProvider,
|
|
4246
4913
|
absoluteAssetUrls,
|
|
4247
4914
|
applyVariables,
|
|
4248
4915
|
applyVariablesToText,
|
|
4916
|
+
buildGallery,
|
|
4249
4917
|
createDefaultEmailEditor,
|
|
4250
4918
|
createDocument,
|
|
4251
4919
|
createEmailEditor,
|