best-email-builder 0.1.0 → 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 +43 -0
- package/README.md +11 -1
- package/dist/{chunk-2PMZMNKA.js → chunk-MTALS5GC.js} +98 -3
- package/dist/chunk-MTALS5GC.js.map +1 -0
- package/dist/index.js +977 -299
- 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/adapters/grapesjs/unwrapModule.d.ts +18 -0
- 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/primitives/button.d.ts +1 -1
- package/dist/types/ui/useLayoutWidth.d.ts +10 -0
- package/package.json +8 -7
- package/dist/chunk-2PMZMNKA.js.map +0 -1
- package/dist/chunk-3EOCXTI2.js +0 -652
- package/dist/chunk-3EOCXTI2.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");
|
|
@@ -426,12 +431,23 @@ function buildEngineConfig({ container, mjmlPlugin, locales = {}, locale = "en"
|
|
|
426
431
|
};
|
|
427
432
|
}
|
|
428
433
|
|
|
434
|
+
// src/adapters/grapesjs/unwrapModule.js
|
|
435
|
+
function unwrapModule(mod) {
|
|
436
|
+
let value = mod;
|
|
437
|
+
for (let depth = 0; depth < 3 && isInteropLayer(value); depth += 1) value = value.default;
|
|
438
|
+
return value;
|
|
439
|
+
}
|
|
440
|
+
function isInteropLayer(value) {
|
|
441
|
+
if (value === null || typeof value !== "object" || !("default" in value)) return false;
|
|
442
|
+
return value.__esModule === true || Object.prototype.toString.call(value) === "[object Module]";
|
|
443
|
+
}
|
|
444
|
+
|
|
429
445
|
// src/adapters/grapesjs/GrapesJsAdapter.js
|
|
430
446
|
var ENGINE_NAME = "grapesjs-mjml";
|
|
431
|
-
var ENGINE_VERSION = "0.
|
|
447
|
+
var ENGINE_VERSION = "0.23.6";
|
|
432
448
|
var BLOCK_PREFIX = "eb-";
|
|
433
449
|
var LOCKED_TYPES = /* @__PURE__ */ new Set(["mjml", "mj-head", "mj-body", "wrapper"]);
|
|
434
|
-
var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button"]);
|
|
450
|
+
var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button", "mj-navbar-link"]);
|
|
435
451
|
var RICH_TYPES = /* @__PURE__ */ new Set(["mj-text", "mj-button"]);
|
|
436
452
|
var ENGINE_LOCALES = {
|
|
437
453
|
fr: () => Promise.all([import("grapesjs/locale/fr.mjs"), import("grapesjs-mjml/locale/fr")])
|
|
@@ -443,11 +459,10 @@ async function defaultLoadEngine(locale) {
|
|
|
443
459
|
import("grapesjs-mjml"),
|
|
444
460
|
loadLocales ? loadLocales() : Promise.resolve(null)
|
|
445
461
|
]);
|
|
446
|
-
const unwrap = (mod) => mod.default || mod;
|
|
447
462
|
return {
|
|
448
|
-
grapesjs:
|
|
449
|
-
mjmlPlugin:
|
|
450
|
-
locales: localeModules ? { engine:
|
|
463
|
+
grapesjs: unwrapModule(grapesjs),
|
|
464
|
+
mjmlPlugin: unwrapModule(mjml),
|
|
465
|
+
locales: localeModules ? { engine: unwrapModule(localeModules[0]), mjml: unwrapModule(localeModules[1]) } : {}
|
|
451
466
|
};
|
|
452
467
|
}
|
|
453
468
|
var ENGINE_SVG = /^\s*<svg[\s>]/i;
|
|
@@ -571,11 +586,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
|
|
|
571
586
|
function placementFor(block, target) {
|
|
572
587
|
const root = body();
|
|
573
588
|
if (!root) throw new Error("GrapesJsAdapter: email body not found");
|
|
574
|
-
const
|
|
589
|
+
const section2 = target ? closest(target, ["mj-section", "mj-wrapper", "mj-hero"]) : null;
|
|
575
590
|
const afterSection = () => {
|
|
576
|
-
if (
|
|
577
|
-
const coll =
|
|
578
|
-
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 };
|
|
579
594
|
}
|
|
580
595
|
return { collection: root.components(), at: root.components().length, wrap: false };
|
|
581
596
|
};
|
|
@@ -584,11 +599,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
|
|
|
584
599
|
if (type === "mj-column") {
|
|
585
600
|
return { collection: target.components(), at: target.components().length, wrap: false };
|
|
586
601
|
}
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
602
|
+
const column2 = target ? closest(target, ["mj-column"]) : null;
|
|
603
|
+
if (column2 && target !== column2) {
|
|
589
604
|
let child = target;
|
|
590
|
-
while (child.parent() && child.parent() !==
|
|
591
|
-
const coll =
|
|
605
|
+
while (child.parent() && child.parent() !== column2) child = child.parent();
|
|
606
|
+
const coll = column2.components();
|
|
592
607
|
return { collection: coll, at: coll.indexOf(child) + 1, wrap: false };
|
|
593
608
|
}
|
|
594
609
|
return { ...afterSection(), wrap: true };
|
|
@@ -803,8 +818,9 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
|
|
|
803
818
|
exportMjml() {
|
|
804
819
|
return String(requireEditor().runCommand("mjml-code") || "").replace(PLACEHOLDER_SRC_ATTR, "").trim();
|
|
805
820
|
},
|
|
806
|
-
exportHtml() {
|
|
807
|
-
const
|
|
821
|
+
exportHtml(mjml = null) {
|
|
822
|
+
const source = mjml ?? adapter.exportMjml();
|
|
823
|
+
const result = requireEditor().runCommand("mjml-code-to-html", { mjml: source }) || {};
|
|
808
824
|
return {
|
|
809
825
|
html: String(result.html || ""),
|
|
810
826
|
errors: (result.errors || []).map((e) => ({
|
|
@@ -971,6 +987,30 @@ var sectionBlock = {
|
|
|
971
987
|
order: 901,
|
|
972
988
|
properties: [
|
|
973
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
|
+
},
|
|
974
1014
|
prop.padding("padding", "Padding"),
|
|
975
1015
|
prop.radius(24)
|
|
976
1016
|
]
|
|
@@ -1019,6 +1059,16 @@ var textBlock = {
|
|
|
1019
1059
|
prop.fontFamily(),
|
|
1020
1060
|
prop.fontSize(10, 48),
|
|
1021
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
|
+
},
|
|
1022
1072
|
prop.color("Text"),
|
|
1023
1073
|
prop.background("container-background-color", "Background"),
|
|
1024
1074
|
{ ...prop.align(), options: [...prop.align().options || [], { value: "justify", label: "Justify" }] },
|
|
@@ -1162,6 +1212,133 @@ var spacerBlock = {
|
|
|
1162
1212
|
]
|
|
1163
1213
|
};
|
|
1164
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
|
+
|
|
1165
1342
|
// src/blocks/columns/index.js
|
|
1166
1343
|
function sectionWith(count) {
|
|
1167
1344
|
const columns = Array.from(
|
|
@@ -1186,20 +1363,41 @@ function layout(count, icon) {
|
|
|
1186
1363
|
var oneColumnBlock = layout(1, "Square");
|
|
1187
1364
|
var twoColumnsBlock = layout(2, "Columns2");
|
|
1188
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
|
+
};
|
|
1189
1379
|
|
|
1190
1380
|
// src/blocks/index.js
|
|
1191
1381
|
var BLOCKS = [
|
|
1382
|
+
headingBlock,
|
|
1192
1383
|
textBlock,
|
|
1193
1384
|
imageBlock,
|
|
1194
1385
|
buttonBlock,
|
|
1195
1386
|
dividerBlock,
|
|
1196
1387
|
spacerBlock,
|
|
1388
|
+
socialBlock,
|
|
1389
|
+
navbarBlock,
|
|
1197
1390
|
oneColumnBlock,
|
|
1198
1391
|
twoColumnsBlock,
|
|
1199
1392
|
threeColumnsBlock,
|
|
1393
|
+
fourColumnsBlock,
|
|
1394
|
+
imageTextBlock,
|
|
1395
|
+
heroBlock,
|
|
1200
1396
|
bodyBlock,
|
|
1201
1397
|
sectionBlock,
|
|
1202
|
-
columnBlock
|
|
1398
|
+
columnBlock,
|
|
1399
|
+
socialElementBlock,
|
|
1400
|
+
navbarLinkBlock
|
|
1203
1401
|
];
|
|
1204
1402
|
var emailBlocks = createRegistry("emailBlocks", BLOCKS);
|
|
1205
1403
|
|
|
@@ -2497,7 +2695,7 @@ function TestEmailButton({ context }) {
|
|
|
2497
2695
|
r.error ? ` \u2014 ${r.error}` : ""
|
|
2498
2696
|
] })
|
|
2499
2697
|
] }, r.email)),
|
|
2500
|
-
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}`))
|
|
2501
2699
|
] }),
|
|
2502
2700
|
/* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
|
|
2503
2701
|
/* @__PURE__ */ jsx11(Button, { variant: "outline", onClick: () => setOpen(false), children: t("Close") }),
|
|
@@ -2519,17 +2717,9 @@ var testEmailFeature = {
|
|
|
2519
2717
|
isAvailable: (context) => typeof context.storage?.sendTest === "function"
|
|
2520
2718
|
};
|
|
2521
2719
|
|
|
2522
|
-
// src/features/
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
function isFeatureEnabled(id) {
|
|
2526
|
-
const feature = emailBuilderFeatures.get(id);
|
|
2527
|
-
return Boolean(feature && feature.enabled !== false);
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
// src/ui/EmailBuilder.jsx
|
|
2531
|
-
import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef7, useState as useState12 } from "react";
|
|
2532
|
-
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";
|
|
2533
2723
|
|
|
2534
2724
|
// src/features/templates/starter.js
|
|
2535
2725
|
function escapeXml(text) {
|
|
@@ -2537,17 +2727,23 @@ function escapeXml(text) {
|
|
|
2537
2727
|
}
|
|
2538
2728
|
var SIDE = 'padding-left="32px" padding-right="32px"';
|
|
2539
2729
|
var TEXT = `font-family="${FONT_STACK}" color="${PALETTE.ink}"`;
|
|
2540
|
-
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) {
|
|
2541
2735
|
const name = escapeXml(sender.name || t("Your company"));
|
|
2542
2736
|
const footer = [name, sender.address ? escapeXml(sender.address) : ""].filter(Boolean).join(" \xB7 ");
|
|
2543
|
-
|
|
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 } = {}) {
|
|
2544
2740
|
return [
|
|
2545
2741
|
"<mjml>",
|
|
2546
2742
|
`<mj-body background-color="${PALETTE.canvas}" width="600px">`,
|
|
2547
2743
|
// Header: logo or sender name.
|
|
2548
2744
|
`<mj-section background-color="${PALETTE.surface}" padding-top="28px" padding-bottom="8px">`,
|
|
2549
2745
|
"<mj-column>",
|
|
2550
|
-
|
|
2746
|
+
senderHeaderMjml(sender, t),
|
|
2551
2747
|
"</mj-column>",
|
|
2552
2748
|
"</mj-section>",
|
|
2553
2749
|
// Body: headline, text, button.
|
|
@@ -2559,17 +2755,316 @@ function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = id
|
|
|
2559
2755
|
"</mj-column>",
|
|
2560
2756
|
"</mj-section>",
|
|
2561
2757
|
// Footer: sender and unsubscribe link.
|
|
2562
|
-
|
|
2563
|
-
"<mj-column>",
|
|
2564
|
-
`<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>`,
|
|
2565
|
-
"</mj-column>",
|
|
2566
|
-
"</mj-section>",
|
|
2758
|
+
footerSectionMjml(sender, t),
|
|
2567
2759
|
"</mj-body>",
|
|
2568
2760
|
"</mjml>"
|
|
2569
2761
|
].join("");
|
|
2570
2762
|
}
|
|
2571
2763
|
var STARTER_MJML = createStarterMjml();
|
|
2572
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
|
+
|
|
2573
3068
|
// src/features/autosave/scheduler.js
|
|
2574
3069
|
function createAutosaveScheduler({
|
|
2575
3070
|
run,
|
|
@@ -2869,7 +3364,7 @@ function createMemoryStorage({ render, sendTest, assetsPath = DEFAULT_ASSETS_PAT
|
|
|
2869
3364
|
|
|
2870
3365
|
// src/ui/EmailCanvas.jsx
|
|
2871
3366
|
import { useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
2872
|
-
import { jsx as
|
|
3367
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2873
3368
|
function EmailCanvas({ editor, document = null, onReady, onError, className }) {
|
|
2874
3369
|
const ref = useRef3(null);
|
|
2875
3370
|
const documentRef = useRef3(document);
|
|
@@ -2888,23 +3383,35 @@ function EmailCanvas({ editor, document = null, onReady, onError, className }) {
|
|
|
2888
3383
|
editor.destroy();
|
|
2889
3384
|
};
|
|
2890
3385
|
}, [editor]);
|
|
2891
|
-
return /* @__PURE__ */
|
|
3386
|
+
return /* @__PURE__ */ jsx13("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
|
|
2892
3387
|
}
|
|
2893
3388
|
|
|
2894
3389
|
// src/ui/BlockSidebar.jsx
|
|
2895
3390
|
import {
|
|
2896
3391
|
Columns2,
|
|
2897
3392
|
Columns3,
|
|
3393
|
+
Columns4,
|
|
3394
|
+
GalleryHorizontal,
|
|
3395
|
+
Heading,
|
|
2898
3396
|
Image as ImageIcon,
|
|
3397
|
+
LayoutPanelLeft,
|
|
3398
|
+
Menu,
|
|
2899
3399
|
MoveVertical,
|
|
2900
3400
|
RectangleHorizontal,
|
|
2901
3401
|
SeparatorHorizontal,
|
|
3402
|
+
Share2,
|
|
2902
3403
|
Square,
|
|
2903
3404
|
Type
|
|
2904
3405
|
} from "lucide-react";
|
|
2905
|
-
import { jsx as
|
|
3406
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2906
3407
|
var ICONS = {
|
|
3408
|
+
Heading,
|
|
2907
3409
|
Type,
|
|
3410
|
+
Share2,
|
|
3411
|
+
Menu,
|
|
3412
|
+
Columns4,
|
|
3413
|
+
LayoutPanelLeft,
|
|
3414
|
+
GalleryHorizontal,
|
|
2908
3415
|
Image: ImageIcon,
|
|
2909
3416
|
RectangleHorizontal,
|
|
2910
3417
|
SeparatorHorizontal,
|
|
@@ -2920,7 +3427,7 @@ var GROUPS2 = [
|
|
|
2920
3427
|
function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
|
|
2921
3428
|
const t = useT();
|
|
2922
3429
|
const Icon = ICONS[block.icon] || Square;
|
|
2923
|
-
return /* @__PURE__ */
|
|
3430
|
+
return /* @__PURE__ */ jsx14(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs11(
|
|
2924
3431
|
"button",
|
|
2925
3432
|
{
|
|
2926
3433
|
type: "button",
|
|
@@ -2940,26 +3447,28 @@ function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
|
|
|
2940
3447
|
disabled && "eb:cursor-not-allowed eb:opacity-50"
|
|
2941
3448
|
),
|
|
2942
3449
|
children: [
|
|
2943
|
-
/* @__PURE__ */
|
|
2944
|
-
/* @__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) })
|
|
2945
3452
|
]
|
|
2946
3453
|
}
|
|
2947
3454
|
) });
|
|
2948
3455
|
}
|
|
2949
|
-
function BlockSidebar({ editor, disabled = false }) {
|
|
3456
|
+
function BlockSidebar({ editor, disabled = false, onAdded }) {
|
|
2950
3457
|
const t = useT();
|
|
2951
3458
|
const blocks = editor.listBlocks();
|
|
2952
|
-
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) => {
|
|
2953
3460
|
const items = blocks.filter((b) => b.kind === group.kind && b.mjml);
|
|
2954
3461
|
if (!items.length) return null;
|
|
2955
|
-
return /* @__PURE__ */
|
|
2956
|
-
/* @__PURE__ */
|
|
2957
|
-
/* @__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(
|
|
2958
3465
|
BlockTile,
|
|
2959
3466
|
{
|
|
2960
3467
|
block,
|
|
2961
3468
|
disabled,
|
|
2962
|
-
onAdd: (id) =>
|
|
3469
|
+
onAdd: (id) => {
|
|
3470
|
+
if (editor.addBlock(id)) onAdded?.(id);
|
|
3471
|
+
},
|
|
2963
3472
|
onDragStart: (id, event) => editor.startBlockDrag(id, event),
|
|
2964
3473
|
onDragEnd: (cancel) => editor.endBlockDrag(cancel)
|
|
2965
3474
|
},
|
|
@@ -2971,15 +3480,15 @@ function BlockSidebar({ editor, disabled = false }) {
|
|
|
2971
3480
|
|
|
2972
3481
|
// src/ui/ElementActions.jsx
|
|
2973
3482
|
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Copy, Trash2 as Trash22 } from "lucide-react";
|
|
2974
|
-
import { jsx as
|
|
3483
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2975
3484
|
function ElementActions({ editor, selection }) {
|
|
2976
3485
|
const t = useT();
|
|
2977
3486
|
if (!selection || !selection.canRemove && !selection.canDuplicate) return null;
|
|
2978
3487
|
const horizontal = selection.tagName === "mj-column";
|
|
2979
3488
|
const [Prev, Next] = horizontal ? [ArrowLeft, ArrowRight] : [ArrowUp, ArrowDown];
|
|
2980
3489
|
const [prevLabel, nextLabel] = horizontal ? [t("Move left"), t("Move right")] : [t("Move up"), t("Move down")];
|
|
2981
|
-
return /* @__PURE__ */
|
|
2982
|
-
/* @__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(
|
|
2983
3492
|
Button,
|
|
2984
3493
|
{
|
|
2985
3494
|
variant: "ghost",
|
|
@@ -2987,10 +3496,10 @@ function ElementActions({ editor, selection }) {
|
|
|
2987
3496
|
"aria-label": prevLabel,
|
|
2988
3497
|
disabled: !selection.canMoveUp,
|
|
2989
3498
|
onClick: () => editor.move(-1, selection.id),
|
|
2990
|
-
children: /* @__PURE__ */
|
|
3499
|
+
children: /* @__PURE__ */ jsx15(Prev, {})
|
|
2991
3500
|
}
|
|
2992
3501
|
) }),
|
|
2993
|
-
/* @__PURE__ */
|
|
3502
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx15(
|
|
2994
3503
|
Button,
|
|
2995
3504
|
{
|
|
2996
3505
|
variant: "ghost",
|
|
@@ -2998,10 +3507,10 @@ function ElementActions({ editor, selection }) {
|
|
|
2998
3507
|
"aria-label": nextLabel,
|
|
2999
3508
|
disabled: !selection.canMoveDown,
|
|
3000
3509
|
onClick: () => editor.move(1, selection.id),
|
|
3001
|
-
children: /* @__PURE__ */
|
|
3510
|
+
children: /* @__PURE__ */ jsx15(Next, {})
|
|
3002
3511
|
}
|
|
3003
3512
|
) }),
|
|
3004
|
-
/* @__PURE__ */
|
|
3513
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx15(
|
|
3005
3514
|
Button,
|
|
3006
3515
|
{
|
|
3007
3516
|
variant: "ghost",
|
|
@@ -3009,10 +3518,10 @@ function ElementActions({ editor, selection }) {
|
|
|
3009
3518
|
"aria-label": t("Duplicate"),
|
|
3010
3519
|
disabled: !selection.canDuplicate,
|
|
3011
3520
|
onClick: () => editor.duplicate(selection.id),
|
|
3012
|
-
children: /* @__PURE__ */
|
|
3521
|
+
children: /* @__PURE__ */ jsx15(Copy, {})
|
|
3013
3522
|
}
|
|
3014
3523
|
) }),
|
|
3015
|
-
/* @__PURE__ */
|
|
3524
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx15(
|
|
3016
3525
|
Button,
|
|
3017
3526
|
{
|
|
3018
3527
|
variant: "ghost",
|
|
@@ -3021,24 +3530,24 @@ function ElementActions({ editor, selection }) {
|
|
|
3021
3530
|
disabled: !selection.canRemove,
|
|
3022
3531
|
onClick: () => editor.remove(selection.id),
|
|
3023
3532
|
className: "eb:ml-auto eb:text-foreground-muted eb:hover:bg-destructive/10 eb:hover:text-destructive",
|
|
3024
|
-
children: /* @__PURE__ */
|
|
3533
|
+
children: /* @__PURE__ */ jsx15(Trash22, {})
|
|
3025
3534
|
}
|
|
3026
3535
|
) })
|
|
3027
3536
|
] });
|
|
3028
3537
|
}
|
|
3029
3538
|
|
|
3030
3539
|
// src/ui/PropertiesPanel.jsx
|
|
3031
|
-
import { useMemo as
|
|
3540
|
+
import { useMemo as useMemo6, useRef as useRef5 } from "react";
|
|
3032
3541
|
import { X as X2 } from "lucide-react";
|
|
3033
3542
|
|
|
3034
3543
|
// src/ui/fields.jsx
|
|
3035
|
-
import { useEffect as useEffect5, useRef as useRef4, useState as
|
|
3544
|
+
import { useEffect as useEffect5, useRef as useRef4, useState as useState10 } from "react";
|
|
3036
3545
|
|
|
3037
3546
|
// src/ui/primitives/color-picker.jsx
|
|
3038
|
-
import { useState as
|
|
3547
|
+
import { useState as useState9 } from "react";
|
|
3039
3548
|
import { HexAlphaColorPicker } from "react-colorful";
|
|
3040
3549
|
import { Check } from "lucide-react";
|
|
3041
|
-
import { jsx as
|
|
3550
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3042
3551
|
var PRESET_COLORS = [
|
|
3043
3552
|
"#8B5CF6",
|
|
3044
3553
|
"#A78BFA",
|
|
@@ -3069,34 +3578,34 @@ function normalizeHex(v) {
|
|
|
3069
3578
|
}
|
|
3070
3579
|
function ColorPicker({ value, onChange, id, className }) {
|
|
3071
3580
|
const t = useT();
|
|
3072
|
-
const [open, setOpen] =
|
|
3581
|
+
const [open, setOpen] = useState9(false);
|
|
3073
3582
|
const valid = isValidHex(value);
|
|
3074
3583
|
const normalized = valid ? normalizeHex(value) : "";
|
|
3075
3584
|
const emit = (hex) => onChange(normalizeHex(hex));
|
|
3076
|
-
return /* @__PURE__ */
|
|
3077
|
-
/* @__PURE__ */
|
|
3078
|
-
/* @__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(
|
|
3079
3588
|
"button",
|
|
3080
3589
|
{
|
|
3081
3590
|
type: "button",
|
|
3082
3591
|
"aria-label": t("Pick a colour"),
|
|
3083
3592
|
title: t("Pick a colour"),
|
|
3084
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]",
|
|
3085
|
-
children: /* @__PURE__ */
|
|
3594
|
+
children: /* @__PURE__ */ jsx16("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" }, "aria-hidden": true })
|
|
3086
3595
|
}
|
|
3087
3596
|
) }),
|
|
3088
|
-
/* @__PURE__ */
|
|
3089
|
-
/* @__PURE__ */
|
|
3090
|
-
/* @__PURE__ */
|
|
3091
|
-
/* @__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(
|
|
3092
3601
|
"span",
|
|
3093
3602
|
{
|
|
3094
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",
|
|
3095
3604
|
"aria-hidden": true,
|
|
3096
|
-
children: /* @__PURE__ */
|
|
3605
|
+
children: /* @__PURE__ */ jsx16("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
|
|
3097
3606
|
}
|
|
3098
3607
|
),
|
|
3099
|
-
/* @__PURE__ */
|
|
3608
|
+
/* @__PURE__ */ jsx16(
|
|
3100
3609
|
Input,
|
|
3101
3610
|
{
|
|
3102
3611
|
value: value || "",
|
|
@@ -3108,7 +3617,7 @@ function ColorPicker({ value, onChange, id, className }) {
|
|
|
3108
3617
|
}
|
|
3109
3618
|
)
|
|
3110
3619
|
] }),
|
|
3111
|
-
/* @__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(
|
|
3112
3621
|
"button",
|
|
3113
3622
|
{
|
|
3114
3623
|
type: "button",
|
|
@@ -3117,13 +3626,13 @@ function ColorPicker({ value, onChange, id, className }) {
|
|
|
3117
3626
|
"aria-label": c,
|
|
3118
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",
|
|
3119
3628
|
style: { backgroundColor: c },
|
|
3120
|
-
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)]" })
|
|
3121
3630
|
},
|
|
3122
3631
|
c
|
|
3123
3632
|
)) })
|
|
3124
3633
|
] })
|
|
3125
3634
|
] }),
|
|
3126
|
-
/* @__PURE__ */
|
|
3635
|
+
/* @__PURE__ */ jsx16(
|
|
3127
3636
|
Input,
|
|
3128
3637
|
{
|
|
3129
3638
|
id,
|
|
@@ -3139,7 +3648,7 @@ function ColorPicker({ value, onChange, id, className }) {
|
|
|
3139
3648
|
|
|
3140
3649
|
// src/ui/fields.jsx
|
|
3141
3650
|
import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader24, Upload } from "lucide-react";
|
|
3142
|
-
import { Fragment as
|
|
3651
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3143
3652
|
var COLOR_DEBOUNCE_MS = 250;
|
|
3144
3653
|
function emailColor(value) {
|
|
3145
3654
|
const v = String(value || "").trim();
|
|
@@ -3148,7 +3657,7 @@ function emailColor(value) {
|
|
|
3148
3657
|
return null;
|
|
3149
3658
|
}
|
|
3150
3659
|
function useDraft(value, toDraft) {
|
|
3151
|
-
const [draft, setDraft] =
|
|
3660
|
+
const [draft, setDraft] = useState10(() => toDraft(value));
|
|
3152
3661
|
const ref = useRef4(null);
|
|
3153
3662
|
const convert = useRef4(toDraft);
|
|
3154
3663
|
convert.current = toDraft;
|
|
@@ -3161,10 +3670,10 @@ function useDraft(value, toDraft) {
|
|
|
3161
3670
|
}
|
|
3162
3671
|
var asText = (v) => v || "";
|
|
3163
3672
|
function FieldLabel({ id, children }) {
|
|
3164
|
-
return /* @__PURE__ */
|
|
3673
|
+
return /* @__PURE__ */ jsx17(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
|
|
3165
3674
|
}
|
|
3166
3675
|
function FieldError({ children }) {
|
|
3167
|
-
return children ? /* @__PURE__ */
|
|
3676
|
+
return children ? /* @__PURE__ */ jsx17("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
|
|
3168
3677
|
}
|
|
3169
3678
|
function TextField({ id, property, value, onCommit, addon = null, error = null }) {
|
|
3170
3679
|
const t = useT();
|
|
@@ -3195,13 +3704,13 @@ function TextField({ id, property, value, onCommit, addon = null, error = null }
|
|
|
3195
3704
|
onKeyDown: keyDown,
|
|
3196
3705
|
"aria-invalid": invalid || void 0
|
|
3197
3706
|
};
|
|
3198
|
-
return /* @__PURE__ */
|
|
3199
|
-
/* @__PURE__ */
|
|
3200
|
-
property.type === "textarea" ? /* @__PURE__ */
|
|
3201
|
-
/* @__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" }),
|
|
3202
3711
|
addon
|
|
3203
|
-
] }) : /* @__PURE__ */
|
|
3204
|
-
/* @__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 })
|
|
3205
3714
|
] });
|
|
3206
3715
|
}
|
|
3207
3716
|
function ImageField({ id, property, value, onCommit, services }) {
|
|
@@ -3210,8 +3719,8 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3210
3719
|
/** @type {HTMLInputElement|null} */
|
|
3211
3720
|
null
|
|
3212
3721
|
);
|
|
3213
|
-
const [busy, setBusy] =
|
|
3214
|
-
const [error, setError] =
|
|
3722
|
+
const [busy, setBusy] = useState10(false);
|
|
3723
|
+
const [error, setError] = useState10(
|
|
3215
3724
|
/** @type {string|null} */
|
|
3216
3725
|
null
|
|
3217
3726
|
);
|
|
@@ -3233,8 +3742,8 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3233
3742
|
setBusy(false);
|
|
3234
3743
|
}
|
|
3235
3744
|
};
|
|
3236
|
-
const addon = upload ? /* @__PURE__ */
|
|
3237
|
-
/* @__PURE__ */
|
|
3745
|
+
const addon = upload ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
3746
|
+
/* @__PURE__ */ jsx17(
|
|
3238
3747
|
"input",
|
|
3239
3748
|
{
|
|
3240
3749
|
ref: inputRef,
|
|
@@ -3247,7 +3756,7 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3247
3756
|
onChange: pick
|
|
3248
3757
|
}
|
|
3249
3758
|
),
|
|
3250
|
-
/* @__PURE__ */
|
|
3759
|
+
/* @__PURE__ */ jsxs14(
|
|
3251
3760
|
Button,
|
|
3252
3761
|
{
|
|
3253
3762
|
type: "button",
|
|
@@ -3256,13 +3765,13 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
3256
3765
|
disabled: busy,
|
|
3257
3766
|
onClick: () => inputRef.current?.click(),
|
|
3258
3767
|
children: [
|
|
3259
|
-
busy ? /* @__PURE__ */
|
|
3768
|
+
busy ? /* @__PURE__ */ jsx17(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx17(Upload, {}),
|
|
3260
3769
|
t("Upload")
|
|
3261
3770
|
]
|
|
3262
3771
|
}
|
|
3263
3772
|
)
|
|
3264
3773
|
] }) : null;
|
|
3265
|
-
return /* @__PURE__ */
|
|
3774
|
+
return /* @__PURE__ */ jsx17(TextField, { id, property, value, onCommit, addon, error });
|
|
3266
3775
|
}
|
|
3267
3776
|
var asNumber = (v) => {
|
|
3268
3777
|
const n = Number.parseFloat(String(v || ""));
|
|
@@ -3284,10 +3793,10 @@ function SizeField({ id, property, value, onCommit }) {
|
|
|
3284
3793
|
const next = `${n}${property.unit || "px"}`;
|
|
3285
3794
|
if (next !== value) onCommit(next);
|
|
3286
3795
|
};
|
|
3287
|
-
return /* @__PURE__ */
|
|
3288
|
-
/* @__PURE__ */
|
|
3289
|
-
/* @__PURE__ */
|
|
3290
|
-
/* @__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(
|
|
3291
3800
|
Input,
|
|
3292
3801
|
{
|
|
3293
3802
|
id,
|
|
@@ -3305,7 +3814,7 @@ function SizeField({ id, property, value, onCommit }) {
|
|
|
3305
3814
|
className: "eb:pr-9 eb:tabular-nums"
|
|
3306
3815
|
}
|
|
3307
3816
|
),
|
|
3308
|
-
/* @__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" })
|
|
3309
3818
|
] })
|
|
3310
3819
|
] });
|
|
3311
3820
|
}
|
|
@@ -3328,18 +3837,18 @@ function ColorField({ id, property, value, onCommit }) {
|
|
|
3328
3837
|
}
|
|
3329
3838
|
}, COLOR_DEBOUNCE_MS);
|
|
3330
3839
|
};
|
|
3331
|
-
return /* @__PURE__ */
|
|
3332
|
-
/* @__PURE__ */
|
|
3333
|
-
/* @__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 })
|
|
3334
3843
|
] });
|
|
3335
3844
|
}
|
|
3336
3845
|
var DEFAULT_OPTION = "__default__";
|
|
3337
3846
|
function SelectField({ id, property, value, onCommit }) {
|
|
3338
3847
|
const t = useT();
|
|
3339
3848
|
const current = value || DEFAULT_OPTION;
|
|
3340
|
-
return /* @__PURE__ */
|
|
3341
|
-
/* @__PURE__ */
|
|
3342
|
-
/* @__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(
|
|
3343
3852
|
Select,
|
|
3344
3853
|
{
|
|
3345
3854
|
value: current,
|
|
@@ -3348,10 +3857,10 @@ function SelectField({ id, property, value, onCommit }) {
|
|
|
3348
3857
|
if (v !== (value || "")) onCommit(v || null);
|
|
3349
3858
|
},
|
|
3350
3859
|
children: [
|
|
3351
|
-
/* @__PURE__ */
|
|
3352
|
-
/* @__PURE__ */
|
|
3353
|
-
!(property.options || []).some((o) => !o.value) && /* @__PURE__ */
|
|
3354
|
-
(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))
|
|
3355
3864
|
] })
|
|
3356
3865
|
]
|
|
3357
3866
|
}
|
|
@@ -3361,9 +3870,9 @@ function SelectField({ id, property, value, onCommit }) {
|
|
|
3361
3870
|
var ALIGN_ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify: AlignJustify };
|
|
3362
3871
|
function AlignField({ id, property, value, onCommit }) {
|
|
3363
3872
|
const t = useT();
|
|
3364
|
-
return /* @__PURE__ */
|
|
3365
|
-
/* @__PURE__ */
|
|
3366
|
-
/* @__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(
|
|
3367
3876
|
"div",
|
|
3368
3877
|
{
|
|
3369
3878
|
id,
|
|
@@ -3373,7 +3882,7 @@ function AlignField({ id, property, value, onCommit }) {
|
|
|
3373
3882
|
children: (property.options || []).map((option) => {
|
|
3374
3883
|
const Icon = ALIGN_ICONS[option.value] || AlignLeft;
|
|
3375
3884
|
const active = value === option.value;
|
|
3376
|
-
return /* @__PURE__ */
|
|
3885
|
+
return /* @__PURE__ */ jsx17(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx17(
|
|
3377
3886
|
"button",
|
|
3378
3887
|
{
|
|
3379
3888
|
type: "button",
|
|
@@ -3385,7 +3894,7 @@ function AlignField({ id, property, value, onCommit }) {
|
|
|
3385
3894
|
"eb:inline-flex eb:h-7 eb:w-7 eb:items-center eb:justify-center eb:rounded-lg2 eb:transition-colors",
|
|
3386
3895
|
active ? "eb:bg-primary eb:text-primary-foreground" : "eb:text-foreground-lighter eb:hover:bg-surface-200 eb:hover:text-foreground"
|
|
3387
3896
|
),
|
|
3388
|
-
children: /* @__PURE__ */
|
|
3897
|
+
children: /* @__PURE__ */ jsx17(Icon, { className: "eb:h-3.5 eb:w-3.5" })
|
|
3389
3898
|
}
|
|
3390
3899
|
) }, option.value);
|
|
3391
3900
|
})
|
|
@@ -3407,10 +3916,10 @@ function SpacingField({ id, property, value, onCommit }) {
|
|
|
3407
3916
|
const next = formatSpacing(sides.map((s) => `${Math.max(0, Math.min(200, Number.parseFloat(s) || 0))}px`));
|
|
3408
3917
|
if (next !== (value || "")) onCommit(next);
|
|
3409
3918
|
};
|
|
3410
|
-
return /* @__PURE__ */
|
|
3411
|
-
/* @__PURE__ */
|
|
3412
|
-
/* @__PURE__ */
|
|
3413
|
-
/* @__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(
|
|
3414
3923
|
Input,
|
|
3415
3924
|
{
|
|
3416
3925
|
id: `${id}-${side}`,
|
|
@@ -3428,7 +3937,7 @@ function SpacingField({ id, property, value, onCommit }) {
|
|
|
3428
3937
|
className: "eb:px-2 eb:text-center eb:tabular-nums"
|
|
3429
3938
|
}
|
|
3430
3939
|
),
|
|
3431
|
-
/* @__PURE__ */
|
|
3940
|
+
/* @__PURE__ */ jsx17("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
|
|
3432
3941
|
] }, side)) })
|
|
3433
3942
|
] });
|
|
3434
3943
|
}
|
|
@@ -3446,15 +3955,15 @@ var FIELDS = {
|
|
|
3446
3955
|
function PropertyField({ property, value, onCommit, idPrefix = "eb", services = null }) {
|
|
3447
3956
|
const Field = FIELDS[property.type] || TextField;
|
|
3448
3957
|
const id = `${idPrefix}-${property.key}`;
|
|
3449
|
-
return /* @__PURE__ */
|
|
3958
|
+
return /* @__PURE__ */ jsx17(Field, { id, property, value, onCommit, services });
|
|
3450
3959
|
}
|
|
3451
3960
|
|
|
3452
3961
|
// src/features/variables/VariableInsertButton.jsx
|
|
3453
|
-
import { useState as
|
|
3962
|
+
import { useState as useState11 } from "react";
|
|
3454
3963
|
import { Braces as Braces3 } from "lucide-react";
|
|
3455
|
-
import { jsx as
|
|
3964
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3456
3965
|
function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
|
|
3457
|
-
const [open, setOpen] =
|
|
3966
|
+
const [open, setOpen] = useState11(false);
|
|
3458
3967
|
const insert = (variable) => {
|
|
3459
3968
|
const input = inputRef.current;
|
|
3460
3969
|
const text = String(value || "");
|
|
@@ -3470,14 +3979,14 @@ function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
|
|
|
3470
3979
|
input.setSelectionRange(start + token.length, start + token.length);
|
|
3471
3980
|
});
|
|
3472
3981
|
};
|
|
3473
|
-
return /* @__PURE__ */
|
|
3474
|
-
/* @__PURE__ */
|
|
3475
|
-
/* @__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" }) })
|
|
3476
3985
|
] });
|
|
3477
3986
|
}
|
|
3478
3987
|
|
|
3479
3988
|
// src/ui/PropertiesPanel.jsx
|
|
3480
|
-
import { Fragment as
|
|
3989
|
+
import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3481
3990
|
var GROUP_ORDER = Object.values(GROUPS);
|
|
3482
3991
|
function grouped(properties) {
|
|
3483
3992
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3491,9 +4000,9 @@ function grouped(properties) {
|
|
|
3491
4000
|
}
|
|
3492
4001
|
function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
|
|
3493
4002
|
const t = useT();
|
|
3494
|
-
return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */
|
|
3495
|
-
/* @__PURE__ */
|
|
3496
|
-
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(
|
|
3497
4006
|
PropertyField,
|
|
3498
4007
|
{
|
|
3499
4008
|
idPrefix,
|
|
@@ -3507,33 +4016,33 @@ function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
|
|
|
3507
4016
|
] }, group));
|
|
3508
4017
|
}
|
|
3509
4018
|
var LABEL_PROPERTY = { key: "content", label: "Label", type: "text", group: GROUPS.content };
|
|
3510
|
-
function PanelHeader({ title, onClose }) {
|
|
4019
|
+
function PanelHeader({ title, onClose, closeLabel = "Deselect" }) {
|
|
3511
4020
|
const t = useT();
|
|
3512
|
-
return /* @__PURE__ */
|
|
3513
|
-
/* @__PURE__ */
|
|
3514
|
-
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, {}) })
|
|
3515
4024
|
] });
|
|
3516
4025
|
}
|
|
3517
|
-
function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null }) {
|
|
4026
|
+
function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, onDismiss = null }) {
|
|
3518
4027
|
const t = useT();
|
|
3519
4028
|
const subjectRef = useRef5(null);
|
|
3520
4029
|
const preheaderRef = useRef5(null);
|
|
3521
4030
|
const rootId = editor.getRootId();
|
|
3522
|
-
const root =
|
|
4031
|
+
const root = useMemo6(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
|
|
3523
4032
|
const bodyBlock2 = root?.blockId ? editor.getBlock(root.blockId) : null;
|
|
3524
4033
|
const field = (key) => (e) => onMetaChange({ ...meta, [key]: e.target.value });
|
|
3525
|
-
return /* @__PURE__ */
|
|
3526
|
-
/* @__PURE__ */
|
|
3527
|
-
/* @__PURE__ */
|
|
3528
|
-
/* @__PURE__ */
|
|
3529
|
-
/* @__PURE__ */
|
|
3530
|
-
/* @__PURE__ */
|
|
3531
|
-
/* @__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: [
|
|
3532
4041
|
t("Name"),
|
|
3533
4042
|
" ",
|
|
3534
|
-
/* @__PURE__ */
|
|
4043
|
+
/* @__PURE__ */ jsx19("span", { className: "eb:text-destructive", children: "*" })
|
|
3535
4044
|
] }),
|
|
3536
|
-
/* @__PURE__ */
|
|
4045
|
+
/* @__PURE__ */ jsx19(
|
|
3537
4046
|
Input,
|
|
3538
4047
|
{
|
|
3539
4048
|
id: "eb-meta-name",
|
|
@@ -3543,12 +4052,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3543
4052
|
"aria-invalid": showErrors && !meta.name.trim() || void 0
|
|
3544
4053
|
}
|
|
3545
4054
|
),
|
|
3546
|
-
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") })
|
|
3547
4056
|
] }),
|
|
3548
|
-
/* @__PURE__ */
|
|
3549
|
-
/* @__PURE__ */
|
|
3550
|
-
/* @__PURE__ */
|
|
3551
|
-
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(
|
|
3552
4061
|
VariableInsertButton,
|
|
3553
4062
|
{
|
|
3554
4063
|
variables,
|
|
@@ -3559,12 +4068,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3559
4068
|
}
|
|
3560
4069
|
)
|
|
3561
4070
|
] }),
|
|
3562
|
-
/* @__PURE__ */
|
|
4071
|
+
/* @__PURE__ */ jsx19(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") })
|
|
3563
4072
|
] }),
|
|
3564
|
-
/* @__PURE__ */
|
|
3565
|
-
/* @__PURE__ */
|
|
3566
|
-
/* @__PURE__ */
|
|
3567
|
-
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(
|
|
3568
4077
|
VariableInsertButton,
|
|
3569
4078
|
{
|
|
3570
4079
|
variables,
|
|
@@ -3575,7 +4084,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3575
4084
|
}
|
|
3576
4085
|
)
|
|
3577
4086
|
] }),
|
|
3578
|
-
/* @__PURE__ */
|
|
4087
|
+
/* @__PURE__ */ jsx19(
|
|
3579
4088
|
Textarea,
|
|
3580
4089
|
{
|
|
3581
4090
|
ref: preheaderRef,
|
|
@@ -3589,7 +4098,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
3589
4098
|
)
|
|
3590
4099
|
] })
|
|
3591
4100
|
] }),
|
|
3592
|
-
root && bodyBlock2 && /* @__PURE__ */
|
|
4101
|
+
root && bodyBlock2 && /* @__PURE__ */ jsx19(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
|
|
3593
4102
|
] })
|
|
3594
4103
|
] });
|
|
3595
4104
|
}
|
|
@@ -3602,11 +4111,12 @@ function PropertiesPanel({
|
|
|
3602
4111
|
actions = null,
|
|
3603
4112
|
showErrors = false,
|
|
3604
4113
|
services = null,
|
|
3605
|
-
variables = null
|
|
4114
|
+
variables = null,
|
|
4115
|
+
onDismiss = null
|
|
3606
4116
|
}) {
|
|
3607
4117
|
const t = useT();
|
|
3608
4118
|
const block = selection?.blockId ? editor.getBlock(selection.blockId) : null;
|
|
3609
|
-
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(
|
|
3610
4120
|
TemplateSettings,
|
|
3611
4121
|
{
|
|
3612
4122
|
editor,
|
|
@@ -3614,15 +4124,23 @@ function PropertiesPanel({
|
|
|
3614
4124
|
onMetaChange,
|
|
3615
4125
|
changes,
|
|
3616
4126
|
showErrors,
|
|
3617
|
-
variables
|
|
4127
|
+
variables,
|
|
4128
|
+
onDismiss
|
|
3618
4129
|
}
|
|
3619
|
-
) : /* @__PURE__ */
|
|
3620
|
-
/* @__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
|
+
),
|
|
3621
4139
|
actions,
|
|
3622
|
-
/* @__PURE__ */
|
|
3623
|
-
selection.
|
|
3624
|
-
/* @__PURE__ */
|
|
3625
|
-
/* @__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(
|
|
3626
4144
|
PropertyField,
|
|
3627
4145
|
{
|
|
3628
4146
|
idPrefix: "eb-prop",
|
|
@@ -3633,15 +4151,15 @@ function PropertiesPanel({
|
|
|
3633
4151
|
`${selection.id}:content`
|
|
3634
4152
|
)
|
|
3635
4153
|
] }),
|
|
3636
|
-
selection.tagName === "mj-text" && /* @__PURE__ */
|
|
3637
|
-
/* @__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 })
|
|
3638
4156
|
] })
|
|
3639
4157
|
] }) });
|
|
3640
4158
|
}
|
|
3641
4159
|
|
|
3642
4160
|
// src/ui/Toolbar.jsx
|
|
3643
4161
|
import { ArrowLeft as ArrowLeft2, Monitor as Monitor2, Redo2, Smartphone as Smartphone2, Undo2 } from "lucide-react";
|
|
3644
|
-
import { jsx as
|
|
4162
|
+
import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3645
4163
|
function Toolbar({
|
|
3646
4164
|
title,
|
|
3647
4165
|
status,
|
|
@@ -3653,44 +4171,112 @@ function Toolbar({
|
|
|
3653
4171
|
device,
|
|
3654
4172
|
onDeviceChange,
|
|
3655
4173
|
onBack,
|
|
3656
|
-
actions = null
|
|
4174
|
+
actions = null,
|
|
4175
|
+
primary = null,
|
|
4176
|
+
compact = false
|
|
3657
4177
|
}) {
|
|
3658
4178
|
const t = useT();
|
|
3659
|
-
|
|
3660
|
-
/* @__PURE__ */
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
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, {}) }) })
|
|
3668
4203
|
] }),
|
|
3669
|
-
/* @__PURE__ */
|
|
3670
|
-
/* @__PURE__ */
|
|
3671
|
-
/* @__PURE__ */
|
|
3672
|
-
|
|
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")
|
|
3673
4208
|
] }),
|
|
3674
|
-
/* @__PURE__ */
|
|
3675
|
-
/* @__PURE__ */
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
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
|
+
)
|
|
3686
4272
|
] });
|
|
3687
4273
|
}
|
|
3688
4274
|
|
|
3689
4275
|
// src/ui/useEmailEditorState.js
|
|
3690
|
-
import { useCallback as useCallback2, useEffect as
|
|
4276
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef6, useState as useState12 } from "react";
|
|
3691
4277
|
var DIRTY_CHECK_MS = 150;
|
|
3692
4278
|
function useEmailEditorState(editor) {
|
|
3693
|
-
const [state, setState] =
|
|
4279
|
+
const [state, setState] = useState12(() => ({
|
|
3694
4280
|
status: editor?.status || "idle",
|
|
3695
4281
|
selection: null,
|
|
3696
4282
|
canUndo: false,
|
|
@@ -3724,7 +4310,7 @@ function useEmailEditorState(editor) {
|
|
|
3724
4310
|
forced.current = true;
|
|
3725
4311
|
setState((s) => ({ ...s, dirty: true }));
|
|
3726
4312
|
}, []);
|
|
3727
|
-
|
|
4313
|
+
useEffect7(() => {
|
|
3728
4314
|
if (!editor) return void 0;
|
|
3729
4315
|
const patch = (values) => setState((s) => ({ ...s, ...values }));
|
|
3730
4316
|
const measure = () => {
|
|
@@ -3770,8 +4356,33 @@ function useEmailEditorState(editor) {
|
|
|
3770
4356
|
return { ...state, markClean, markDirty, checkDirty };
|
|
3771
4357
|
}
|
|
3772
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
|
+
|
|
3773
4384
|
// src/ui/EmailBuilder.jsx
|
|
3774
|
-
import { Fragment as
|
|
4385
|
+
import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3775
4386
|
var EMPTY_META = { name: "", subject: "", preheader: "", description: "" };
|
|
3776
4387
|
function metaOf(template) {
|
|
3777
4388
|
return {
|
|
@@ -3789,6 +4400,7 @@ function Builder({
|
|
|
3789
4400
|
variables: variablesProp,
|
|
3790
4401
|
sender,
|
|
3791
4402
|
starterMjml,
|
|
4403
|
+
templates: galleryProp,
|
|
3792
4404
|
testRecipients,
|
|
3793
4405
|
initialMjml,
|
|
3794
4406
|
initialMeta,
|
|
@@ -3799,11 +4411,11 @@ function Builder({
|
|
|
3799
4411
|
className
|
|
3800
4412
|
}) {
|
|
3801
4413
|
const { t, locale, notify, confirm } = useUi();
|
|
3802
|
-
const editor =
|
|
3803
|
-
const storage =
|
|
3804
|
-
const systemVariables =
|
|
3805
|
-
const [customVariables, setCustomVariables] =
|
|
3806
|
-
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]);
|
|
3807
4419
|
const canManageVariables = typeof storage.saveVariable === "function" && typeof storage.listVariables === "function";
|
|
3808
4420
|
const reloadVariables = useCallback3(async () => {
|
|
3809
4421
|
if (typeof storage.listVariables !== "function") return;
|
|
@@ -3813,23 +4425,37 @@ function Builder({
|
|
|
3813
4425
|
notify("error", error.message);
|
|
3814
4426
|
}
|
|
3815
4427
|
}, [storage, notify]);
|
|
3816
|
-
|
|
4428
|
+
useEffect9(() => {
|
|
3817
4429
|
reloadVariables();
|
|
3818
4430
|
}, [reloadVariables]);
|
|
3819
|
-
const starter =
|
|
3820
|
-
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]);
|
|
3821
4437
|
const state = useEmailEditorState(editor);
|
|
3822
4438
|
const autosaveOn = isFeatureEnabled("autosave");
|
|
3823
4439
|
const autosaveOptions = emailBuilderFeatures.get("autosave")?.options || {};
|
|
3824
|
-
const [template, setTemplate] =
|
|
3825
|
-
const [boot, setBoot] =
|
|
3826
|
-
const [meta, setMeta] =
|
|
3827
|
-
const [savedMeta, setSavedMeta] =
|
|
3828
|
-
const [saving, setSaving] =
|
|
3829
|
-
const [showErrors, setShowErrors] =
|
|
3830
|
-
const [autosaveBlocked, setAutosaveBlocked] =
|
|
3831
|
-
const [engineError, setEngineError] =
|
|
3832
|
-
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]);
|
|
3833
4459
|
const ready = state.status === "ready";
|
|
3834
4460
|
const metaDirty = !sameMeta(meta, savedMeta);
|
|
3835
4461
|
const dirty = state.dirty || metaDirty;
|
|
@@ -3838,7 +4464,7 @@ function Builder({
|
|
|
3838
4464
|
[locale]
|
|
3839
4465
|
);
|
|
3840
4466
|
const booted = useRef7(false);
|
|
3841
|
-
|
|
4467
|
+
useEffect9(() => {
|
|
3842
4468
|
if (booted.current) return;
|
|
3843
4469
|
booted.current = true;
|
|
3844
4470
|
(async () => {
|
|
@@ -3887,7 +4513,7 @@ function Builder({
|
|
|
3887
4513
|
if (!boot.document) editor.loadMjml(initialMjml || starter);
|
|
3888
4514
|
}, [editor, boot.document, starter, initialMjml]);
|
|
3889
4515
|
const { markDirty, markClean, checkDirty } = state;
|
|
3890
|
-
|
|
4516
|
+
useEffect9(() => {
|
|
3891
4517
|
if (ready && boot.draft) markDirty();
|
|
3892
4518
|
}, [ready, boot.draft, markDirty]);
|
|
3893
4519
|
const isDirty = useCallback3(
|
|
@@ -3896,17 +4522,17 @@ function Builder({
|
|
|
3896
4522
|
);
|
|
3897
4523
|
const reportDirty = useRef7(onDirtyChange);
|
|
3898
4524
|
reportDirty.current = onDirtyChange;
|
|
3899
|
-
|
|
4525
|
+
useEffect9(() => {
|
|
3900
4526
|
reportDirty.current?.(dirty);
|
|
3901
4527
|
}, [dirty]);
|
|
3902
|
-
|
|
4528
|
+
useEffect9(() => {
|
|
3903
4529
|
if (!dirtyCheckRef) return void 0;
|
|
3904
4530
|
dirtyCheckRef.current = isDirty;
|
|
3905
4531
|
return () => {
|
|
3906
4532
|
dirtyCheckRef.current = null;
|
|
3907
4533
|
};
|
|
3908
4534
|
}, [dirtyCheckRef, isDirty]);
|
|
3909
|
-
|
|
4535
|
+
useEffect9(() => {
|
|
3910
4536
|
if (!dirty) return void 0;
|
|
3911
4537
|
const warn = (e) => {
|
|
3912
4538
|
e.preventDefault();
|
|
@@ -3917,7 +4543,7 @@ function Builder({
|
|
|
3917
4543
|
}, [dirty]);
|
|
3918
4544
|
const savingRef = useRef7(false);
|
|
3919
4545
|
const latest = useRef7({});
|
|
3920
|
-
latest.current = { template, meta, onSaved };
|
|
4546
|
+
latest.current = { template, meta, onSaved, compact };
|
|
3921
4547
|
const save = useCallback3(
|
|
3922
4548
|
async ({ auto = false } = {}) => {
|
|
3923
4549
|
if (savingRef.current || editor.status !== "ready") return false;
|
|
@@ -3926,6 +4552,7 @@ function Builder({
|
|
|
3926
4552
|
if (!auto) {
|
|
3927
4553
|
setShowErrors(true);
|
|
3928
4554
|
editor.select(null);
|
|
4555
|
+
setDrawer((d) => d === null && latest.current.compact ? "properties" : d);
|
|
3929
4556
|
notify("error", t("Give the template a name before saving it."));
|
|
3930
4557
|
}
|
|
3931
4558
|
return false;
|
|
@@ -3998,7 +4625,7 @@ function Builder({
|
|
|
3998
4625
|
},
|
|
3999
4626
|
[editor, storage, markClean, notify, confirm, t]
|
|
4000
4627
|
);
|
|
4001
|
-
|
|
4628
|
+
useEffect9(() => {
|
|
4002
4629
|
const onKey = (e) => {
|
|
4003
4630
|
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "s") {
|
|
4004
4631
|
e.preventDefault();
|
|
@@ -4008,11 +4635,11 @@ function Builder({
|
|
|
4008
4635
|
window.addEventListener("keydown", onKey, true);
|
|
4009
4636
|
return () => window.removeEventListener("keydown", onKey, true);
|
|
4010
4637
|
}, [save]);
|
|
4011
|
-
|
|
4638
|
+
useEffect9(() => {
|
|
4012
4639
|
if (!ready) return void 0;
|
|
4013
4640
|
return editor.addShortcut("save", "mod+s", () => save());
|
|
4014
4641
|
}, [ready, editor, save]);
|
|
4015
|
-
const scheduler =
|
|
4642
|
+
const scheduler = useMemo7(
|
|
4016
4643
|
() => createAutosaveScheduler({
|
|
4017
4644
|
run: () => save({ auto: true }),
|
|
4018
4645
|
idleMs: autosaveOptions.idleMs,
|
|
@@ -4020,13 +4647,13 @@ function Builder({
|
|
|
4020
4647
|
}),
|
|
4021
4648
|
[save, autosaveOptions.idleMs, autosaveOptions.maxWaitMs]
|
|
4022
4649
|
);
|
|
4023
|
-
|
|
4024
|
-
|
|
4650
|
+
useEffect9(() => () => scheduler.dispose(), [scheduler]);
|
|
4651
|
+
useEffect9(() => {
|
|
4025
4652
|
if (!autosaveOn || !ready || !template.id || autosaveBlocked) return;
|
|
4026
4653
|
if (dirty) scheduler.notify();
|
|
4027
4654
|
else scheduler.reset();
|
|
4028
4655
|
}, [autosaveOn, ready, template.id, autosaveBlocked, dirty, state.changes, meta, scheduler]);
|
|
4029
|
-
|
|
4656
|
+
useEffect9(() => {
|
|
4030
4657
|
if (!autosaveOn || !ready || template.id || !dirty) return void 0;
|
|
4031
4658
|
const timer = setTimeout(() => {
|
|
4032
4659
|
if (editor.status === "ready") saveLocalDraft({ document: editor.getDocument(), meta });
|
|
@@ -4051,6 +4678,8 @@ function Builder({
|
|
|
4051
4678
|
storage,
|
|
4052
4679
|
template,
|
|
4053
4680
|
meta,
|
|
4681
|
+
setMeta,
|
|
4682
|
+
templates,
|
|
4054
4683
|
ready,
|
|
4055
4684
|
isDirty,
|
|
4056
4685
|
markDirty,
|
|
@@ -4064,15 +4693,32 @@ function Builder({
|
|
|
4064
4693
|
const toolbarFeatures = emailBuilderFeatures.list(
|
|
4065
4694
|
(f) => f.enabled !== false && Boolean(f.toolbar) && (f.isAvailable ? f.isAvailable(featureContext) : true)
|
|
4066
4695
|
);
|
|
4067
|
-
|
|
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(
|
|
4068
4712
|
"div",
|
|
4069
4713
|
{
|
|
4714
|
+
ref: rootRef,
|
|
4070
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),
|
|
4071
4716
|
"data-testid": "email-builder",
|
|
4072
4717
|
"data-status": state.status,
|
|
4718
|
+
"data-layout": layout2,
|
|
4073
4719
|
"data-template-id": template.id || "",
|
|
4074
4720
|
children: [
|
|
4075
|
-
/* @__PURE__ */
|
|
4721
|
+
/* @__PURE__ */ jsx22(
|
|
4076
4722
|
Toolbar,
|
|
4077
4723
|
{
|
|
4078
4724
|
title: meta.name,
|
|
@@ -4085,80 +4731,110 @@ function Builder({
|
|
|
4085
4731
|
device: state.device,
|
|
4086
4732
|
onDeviceChange: (device) => editor.setDevice(device),
|
|
4087
4733
|
onBack: onClose ? back : void 0,
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
] })
|
|
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")
|
|
4097
4742
|
] })
|
|
4098
4743
|
}
|
|
4099
4744
|
),
|
|
4100
|
-
/* @__PURE__ */
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
{
|
|
4109
|
-
variant: "outline",
|
|
4110
|
-
size: "sm-compact",
|
|
4111
|
-
onClick: () => {
|
|
4112
|
-
setEngineError(null);
|
|
4113
|
-
setAttempt((n) => n + 1);
|
|
4114
|
-
},
|
|
4115
|
-
children: t("Retry")
|
|
4116
|
-
}
|
|
4117
|
-
)
|
|
4118
|
-
] }) : /* @__PURE__ */ jsxs17(Fragment6, { children: [
|
|
4119
|
-
boot.status === "ready" && /* @__PURE__ */ jsx20(
|
|
4120
|
-
EmailCanvas,
|
|
4121
|
-
{
|
|
4122
|
-
editor,
|
|
4123
|
-
document: boot.document,
|
|
4124
|
-
onReady,
|
|
4125
|
-
onError: (error) => setEngineError(error.message)
|
|
4126
|
-
},
|
|
4127
|
-
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"
|
|
4128
4753
|
),
|
|
4129
|
-
|
|
4130
|
-
/* @__PURE__ */
|
|
4131
|
-
/* @__PURE__ */
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
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,
|
|
4136
4812
|
{
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
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
|
+
]
|
|
4146
4822
|
}
|
|
4147
4823
|
)
|
|
4148
4824
|
] }),
|
|
4149
|
-
/* @__PURE__ */
|
|
4825
|
+
/* @__PURE__ */ jsx22(NoticeArea, {})
|
|
4150
4826
|
]
|
|
4151
4827
|
}
|
|
4152
4828
|
);
|
|
4153
4829
|
}
|
|
4154
4830
|
function EmailBuilder({ locale = "en", messages, onNotify, ...props }) {
|
|
4155
|
-
return /* @__PURE__ */
|
|
4831
|
+
return /* @__PURE__ */ jsx22(UiProvider, { locale, messages, onNotify, children: /* @__PURE__ */ jsx22(Builder, { ...props, templateId: props.templateId ?? null }) });
|
|
4156
4832
|
}
|
|
4157
4833
|
|
|
4158
4834
|
// src/ui/EmailBuilderBoundary.jsx
|
|
4159
4835
|
import { Component } from "react";
|
|
4160
4836
|
import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft3, RefreshCw as RefreshCw2 } from "lucide-react";
|
|
4161
|
-
import { jsx as
|
|
4837
|
+
import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
4162
4838
|
var isChunkError = (error) => error?.name === "ChunkLoadError" || /Loading (CSS )?chunk .* failed/i.test(String(error?.message || ""));
|
|
4163
4839
|
var english = (text) => text;
|
|
4164
4840
|
var EmailBuilderBoundary = class extends Component {
|
|
@@ -4183,23 +4859,23 @@ var EmailBuilderBoundary = class extends Component {
|
|
|
4183
4859
|
const { error, attempt } = this.state;
|
|
4184
4860
|
const t = this.props.t || english;
|
|
4185
4861
|
if (!error) {
|
|
4186
|
-
return /* @__PURE__ */
|
|
4862
|
+
return /* @__PURE__ */ jsx23("div", { style: { height: "100%" }, children: this.props.children }, attempt);
|
|
4187
4863
|
}
|
|
4188
|
-
return /* @__PURE__ */
|
|
4864
|
+
return /* @__PURE__ */ jsxs20(
|
|
4189
4865
|
"div",
|
|
4190
4866
|
{
|
|
4191
4867
|
role: "alert",
|
|
4192
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",
|
|
4193
4869
|
children: [
|
|
4194
|
-
/* @__PURE__ */
|
|
4195
|
-
/* @__PURE__ */
|
|
4196
|
-
/* @__PURE__ */
|
|
4197
|
-
this.props.onClose && /* @__PURE__ */
|
|
4198
|
-
/* @__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, {}),
|
|
4199
4875
|
t("Back")
|
|
4200
4876
|
] }),
|
|
4201
|
-
/* @__PURE__ */
|
|
4202
|
-
/* @__PURE__ */
|
|
4877
|
+
/* @__PURE__ */ jsxs20(Button, { size: "sm", onClick: this.retry, children: [
|
|
4878
|
+
/* @__PURE__ */ jsx23(RefreshCw2, {}),
|
|
4203
4879
|
t("Retry")
|
|
4204
4880
|
] })
|
|
4205
4881
|
] })
|
|
@@ -4232,10 +4908,12 @@ export {
|
|
|
4232
4908
|
LOCALES,
|
|
4233
4909
|
STARTER_MJML,
|
|
4234
4910
|
StorageError,
|
|
4911
|
+
TEMPLATE_GALLERY,
|
|
4235
4912
|
UiProvider,
|
|
4236
4913
|
absoluteAssetUrls,
|
|
4237
4914
|
applyVariables,
|
|
4238
4915
|
applyVariablesToText,
|
|
4916
|
+
buildGallery,
|
|
4239
4917
|
createDefaultEmailEditor,
|
|
4240
4918
|
createDocument,
|
|
4241
4919
|
createEmailEditor,
|