best-email-builder 0.3.0 → 0.5.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 +190 -0
- package/README.md +290 -20
- package/dist/ai-server.js +68 -0
- package/dist/ai-server.js.map +1 -0
- package/dist/ai.js +852 -0
- package/dist/ai.js.map +1 -0
- package/dist/builder.js +16 -8
- package/dist/chunk-2RZHGAH5.js +64 -0
- package/dist/chunk-2RZHGAH5.js.map +1 -0
- package/dist/chunk-3LKF4QBL.js +595 -0
- package/dist/chunk-3LKF4QBL.js.map +1 -0
- package/dist/chunk-5SASKCUC.js +251 -0
- package/dist/chunk-5SASKCUC.js.map +1 -0
- package/dist/{chunk-JOC752I3.js → chunk-6D3SLT5W.js} +27 -29
- package/dist/chunk-6D3SLT5W.js.map +1 -0
- package/dist/chunk-7U6BAKM7.js +119 -0
- package/dist/chunk-7U6BAKM7.js.map +1 -0
- package/dist/chunk-DFNHICTF.js +1359 -0
- package/dist/chunk-DFNHICTF.js.map +1 -0
- package/dist/chunk-ECHGJZ2W.js +327 -0
- package/dist/chunk-ECHGJZ2W.js.map +1 -0
- package/dist/chunk-KXJJSVN5.js +98 -0
- package/dist/chunk-KXJJSVN5.js.map +1 -0
- package/dist/{chunk-ZSUHOCCE.js → chunk-LXR4QTGP.js} +123 -579
- package/dist/chunk-LXR4QTGP.js.map +1 -0
- package/dist/{chunk-BVOVAEEJ.js → chunk-LYMJ2TBX.js} +2 -2
- package/dist/chunk-MPX5CWTZ.js +1687 -0
- package/dist/chunk-MPX5CWTZ.js.map +1 -0
- package/dist/chunk-PB7YZ3WP.js +5635 -0
- package/dist/chunk-PB7YZ3WP.js.map +1 -0
- package/dist/chunk-UFKCLU2D.js +255 -0
- package/dist/chunk-UFKCLU2D.js.map +1 -0
- package/dist/index.js +51 -20
- package/dist/index.js.map +1 -1
- package/dist/library.js +12 -7
- package/dist/server.js +513 -214
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +12 -0
- package/dist/types/ai/GenerateButton.d.ts +4 -0
- package/dist/types/ai/RewriteText.d.ts +5 -0
- package/dist/types/ai/SubjectIdeas.d.ts +25 -0
- package/dist/types/ai/chip.d.ts +1 -0
- package/dist/types/ai/claude.d.ts +21 -0
- package/dist/types/ai/createAi.d.ts +66 -0
- package/dist/types/ai/draft.d.ts +163 -0
- package/dist/types/ai/generator.d.ts +123 -0
- package/dist/types/ai/i18n/fr.d.ts +2 -0
- package/dist/types/ai/i18n/index.d.ts +2 -0
- package/dist/types/ai/index.d.ts +9 -0
- package/dist/types/ai/keys.d.ts +46 -0
- package/dist/types/ai/prompt.d.ts +76 -0
- package/dist/types/ai/providers.d.ts +67 -0
- package/dist/types/ai/purposes.d.ts +48 -0
- package/dist/types/ai/rewrite.d.ts +83 -0
- package/dist/types/ai/server.d.ts +9 -0
- package/dist/types/ai/subjects.d.ts +97 -0
- package/dist/types/blocks/shared.d.ts +20 -0
- package/dist/types/core/EmailEditor.d.ts +72 -19
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/core/mjmlDefaults.d.ts +13 -0
- package/dist/types/core/scrollbars.d.ts +1 -1
- package/dist/types/defaultEditor.d.ts +4 -1
- package/dist/types/engine/NativeAdapter.d.ts +28 -0
- package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
- package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
- package/dist/types/engine/canvas/icons.d.ts +14 -0
- package/dist/types/engine/canvas/morph.d.ts +21 -0
- package/dist/types/engine/canvasMjml.d.ts +30 -0
- package/dist/types/engine/mjml.d.ts +31 -0
- package/dist/types/engine/model.d.ts +130 -0
- package/dist/types/engine/placement.d.ts +69 -0
- package/dist/types/engine/richText.d.ts +15 -0
- package/dist/types/engine/unwrapModule.d.ts +29 -0
- package/dist/types/features/brand/BrandButton.d.ts +4 -0
- package/dist/types/features/brand/brand.d.ts +108 -0
- package/dist/types/features/brand/extract.d.ts +61 -0
- package/dist/types/features/brand/index.d.ts +2 -0
- package/dist/types/features/brand/messages.d.ts +13 -0
- package/dist/types/features/brand/useBrand.d.ts +22 -0
- package/dist/types/features/preview/PreviewButton.d.ts +1 -1
- package/dist/types/features/preview/darkMode.d.ts +65 -0
- package/dist/types/features/variables/VariablePicker.d.ts +3 -2
- package/dist/types/i18n/index.d.ts +8 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/server/brand.d.ts +32 -0
- package/dist/types/server/brandImport.d.ts +93 -0
- package/dist/types/server/compileMjml.d.ts +2 -0
- package/dist/types/server/handler.d.ts +26 -0
- package/dist/types/server/index.d.ts +5 -1
- package/dist/types/storage/client.d.ts +57 -3
- package/dist/types/ui/EmailBuilder.d.ts +5 -1
- package/dist/types/ui/EmailStudio.d.ts +6 -1
- package/dist/types/ui/PropertiesPanel.d.ts +2 -1
- package/dist/types/ui/fields.d.ts +2 -1
- package/dist/types/ui/primitives/button.d.ts +1 -1
- package/dist/types/ui/primitives/color-picker.d.ts +2 -1
- package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
- package/dist/types/ui/useLayoutWidth.d.ts +1 -1
- package/package.json +27 -7
- package/dist/chunk-3M36VVXS.js +0 -665
- package/dist/chunk-3M36VVXS.js.map +0 -1
- package/dist/chunk-57CTQNTK.js +0 -56
- package/dist/chunk-57CTQNTK.js.map +0 -1
- package/dist/chunk-ILRAQTHS.js +0 -3677
- package/dist/chunk-ILRAQTHS.js.map +0 -1
- package/dist/chunk-JOC752I3.js.map +0 -1
- package/dist/chunk-ZSUHOCCE.js.map +0 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
- package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
- package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
- /package/dist/{chunk-BVOVAEEJ.js.map → chunk-LYMJ2TBX.js.map} +0 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DARK_PAGE,
|
|
3
|
+
simulateDarkMode
|
|
4
|
+
} from "./chunk-UFKCLU2D.js";
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
SectionLabel,
|
|
13
|
+
Tabs,
|
|
14
|
+
TabsList,
|
|
15
|
+
TabsTrigger,
|
|
16
|
+
cn,
|
|
17
|
+
useUi
|
|
18
|
+
} from "./chunk-LXR4QTGP.js";
|
|
19
|
+
import {
|
|
20
|
+
EMAIL_MOBILE_WIDTH
|
|
21
|
+
} from "./chunk-7U6BAKM7.js";
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_BUTTON_URL,
|
|
24
|
+
FONT_STACK,
|
|
25
|
+
GROUPS,
|
|
26
|
+
PALETTE,
|
|
27
|
+
cleanBrand,
|
|
28
|
+
identity,
|
|
29
|
+
inWhiteSection,
|
|
30
|
+
prop
|
|
31
|
+
} from "./chunk-DFNHICTF.js";
|
|
32
|
+
|
|
33
|
+
// src/features/templates/starter.js
|
|
34
|
+
function escapeXml(text) {
|
|
35
|
+
return String(text ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
36
|
+
}
|
|
37
|
+
var SIDE = 'padding-left="32px" padding-right="32px"';
|
|
38
|
+
var TEXT = `font-family="${FONT_STACK}" color="${PALETTE.ink}"`;
|
|
39
|
+
function senderHeaderMjml(sender, t) {
|
|
40
|
+
const name = escapeXml(sender.name || t("Your company"));
|
|
41
|
+
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>`;
|
|
42
|
+
}
|
|
43
|
+
function footerSectionMjml(sender, t) {
|
|
44
|
+
const name = escapeXml(sender.name || t("Your company"));
|
|
45
|
+
const footer = [name, sender.address ? escapeXml(sender.address) : ""].filter(Boolean).join(" \xB7 ");
|
|
46
|
+
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>`;
|
|
47
|
+
}
|
|
48
|
+
function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = identity } = {}) {
|
|
49
|
+
return [
|
|
50
|
+
"<mjml>",
|
|
51
|
+
`<mj-body background-color="${PALETTE.canvas}" width="600px">`,
|
|
52
|
+
// Header: logo or sender name.
|
|
53
|
+
`<mj-section background-color="${PALETTE.surface}" padding-top="28px" padding-bottom="8px">`,
|
|
54
|
+
"<mj-column>",
|
|
55
|
+
senderHeaderMjml(sender, t),
|
|
56
|
+
"</mj-column>",
|
|
57
|
+
"</mj-section>",
|
|
58
|
+
// Body: headline, text, button.
|
|
59
|
+
`<mj-section background-color="${PALETTE.surface}" padding-top="8px" padding-bottom="28px">`,
|
|
60
|
+
"<mj-column>",
|
|
61
|
+
`<mj-text ${TEXT} font-size="24px" line-height="32px" font-weight="bold" ${SIDE}>${escapeXml(t("Your headline"))}</mj-text>`,
|
|
62
|
+
`<mj-text ${TEXT} font-size="15px" line-height="24px" ${SIDE}>${escapeXml(t("Hello {name},", { name: "{{contact.firstname}}" }))}<br /><br />${escapeXml(t("Your text."))}</mj-text>`,
|
|
63
|
+
`<mj-button href="${escapeXml(buttonUrl)}" background-color="${PALETTE.navy}" color="${PALETTE.white}" font-family="${FONT_STACK}" font-size="15px" font-weight="bold" border-radius="6px" inner-padding="12px 28px" align="left" ${SIDE}>${escapeXml(t("Learn more"))}</mj-button>`,
|
|
64
|
+
"</mj-column>",
|
|
65
|
+
"</mj-section>",
|
|
66
|
+
// Footer: sender and unsubscribe link.
|
|
67
|
+
footerSectionMjml(sender, t),
|
|
68
|
+
"</mj-body>",
|
|
69
|
+
"</mjml>"
|
|
70
|
+
].join("");
|
|
71
|
+
}
|
|
72
|
+
var STARTER_MJML = createStarterMjml();
|
|
73
|
+
|
|
74
|
+
// src/blocks/social/index.js
|
|
75
|
+
var SOCIAL_NETWORKS = [
|
|
76
|
+
{ value: "facebook-noshare", label: "Facebook", href: "https://www.facebook.com/" },
|
|
77
|
+
{ value: "instagram-noshare", label: "Instagram", href: "https://www.instagram.com/" },
|
|
78
|
+
{ value: "linkedin-noshare", label: "LinkedIn", href: "https://www.linkedin.com/" },
|
|
79
|
+
{ value: "x-noshare", label: "X", href: "https://x.com/" },
|
|
80
|
+
{ value: "youtube-noshare", label: "YouTube", href: "https://www.youtube.com/" },
|
|
81
|
+
{ value: "github-noshare", label: "GitHub", href: "https://github.com/" }
|
|
82
|
+
];
|
|
83
|
+
var DEFAULT_NETWORKS = ["facebook-noshare", "instagram-noshare", "linkedin-noshare", "x-noshare"];
|
|
84
|
+
var MJML = '<mj-social mode="horizontal" align="center" icon-size="28px" border-radius="14px" inner-padding="6px">' + DEFAULT_NETWORKS.map((name) => {
|
|
85
|
+
const network = SOCIAL_NETWORKS.find((n) => n.value === name);
|
|
86
|
+
return `<mj-social-element name="${name}" href="${network?.href}"></mj-social-element>`;
|
|
87
|
+
}).join("") + "</mj-social>";
|
|
88
|
+
var socialBlock = {
|
|
89
|
+
id: "social",
|
|
90
|
+
label: "Social links",
|
|
91
|
+
description: "Icons linking to your profiles",
|
|
92
|
+
icon: "Share2",
|
|
93
|
+
kind: "content",
|
|
94
|
+
tagName: "mj-social",
|
|
95
|
+
order: 60,
|
|
96
|
+
mjml: MJML,
|
|
97
|
+
sectionMjml: inWhiteSection(MJML),
|
|
98
|
+
properties: [
|
|
99
|
+
{ key: "icon-size", label: "Icon size", type: "size", group: GROUPS.layout, min: 16, max: 48, unit: "px" },
|
|
100
|
+
prop.radius(24),
|
|
101
|
+
prop.align(),
|
|
102
|
+
{
|
|
103
|
+
key: "mode",
|
|
104
|
+
label: "Direction",
|
|
105
|
+
type: "select",
|
|
106
|
+
group: GROUPS.layout,
|
|
107
|
+
options: [
|
|
108
|
+
{ value: "horizontal", label: "Horizontal" },
|
|
109
|
+
{ value: "vertical", label: "Vertical" }
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
prop.padding("inner-padding", "Space around each icon"),
|
|
113
|
+
prop.padding()
|
|
114
|
+
]
|
|
115
|
+
};
|
|
116
|
+
var socialElementBlock = {
|
|
117
|
+
id: "social-element",
|
|
118
|
+
label: "Social link",
|
|
119
|
+
icon: "Link",
|
|
120
|
+
kind: "structure",
|
|
121
|
+
tagName: "mj-social-element",
|
|
122
|
+
order: 903,
|
|
123
|
+
properties: [
|
|
124
|
+
{
|
|
125
|
+
key: "name",
|
|
126
|
+
label: "Network",
|
|
127
|
+
type: "select",
|
|
128
|
+
group: GROUPS.content,
|
|
129
|
+
options: SOCIAL_NETWORKS.map(({ value, label }) => ({ value, label }))
|
|
130
|
+
},
|
|
131
|
+
prop.href("Profile URL"),
|
|
132
|
+
prop.background("background-color", "Icon background")
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/features/templates/gallery.js
|
|
137
|
+
var WHITE_SECTION = `background-color="${PALETTE.surface}"`;
|
|
138
|
+
function section(inner, { top = 8, bottom = 8, x = 0 } = {}) {
|
|
139
|
+
const sides = x ? ` padding-left="${x}px" padding-right="${x}px"` : "";
|
|
140
|
+
return `<mj-section ${WHITE_SECTION} padding-top="${top}px" padding-bottom="${bottom}px"${sides}>${inner}</mj-section>`;
|
|
141
|
+
}
|
|
142
|
+
function column(inner, attrs = "") {
|
|
143
|
+
return `<mj-column${attrs ? ` ${attrs}` : ""}>${inner}</mj-column>`;
|
|
144
|
+
}
|
|
145
|
+
function heading(text, { size = 24, color = PALETTE.navy, align = "left", side = true } = {}) {
|
|
146
|
+
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>`;
|
|
147
|
+
}
|
|
148
|
+
function paragraph(html, { size = 15, color = PALETTE.ink, align = "left", side = true } = {}) {
|
|
149
|
+
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>`;
|
|
150
|
+
}
|
|
151
|
+
function button(label, { align = "left", background = PALETTE.navy, color = PALETTE.white, side = true } = {}) {
|
|
152
|
+
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>`;
|
|
153
|
+
}
|
|
154
|
+
var IMAGE = '<mj-image align="center"></mj-image>';
|
|
155
|
+
function header(sender, t) {
|
|
156
|
+
return `<mj-section ${WHITE_SECTION} padding-top="28px" padding-bottom="8px">${column(senderHeaderMjml(sender, t))}</mj-section>`;
|
|
157
|
+
}
|
|
158
|
+
function banner({ title, text, cta, background = PALETTE.navy }) {
|
|
159
|
+
return `<mj-section background-color="${background}" background-size="cover" background-position="center center" background-repeat="no-repeat" padding-top="48px" padding-bottom="48px">` + column(
|
|
160
|
+
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 }) : "")
|
|
161
|
+
) + "</mj-section>";
|
|
162
|
+
}
|
|
163
|
+
function social() {
|
|
164
|
+
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("");
|
|
165
|
+
return section(
|
|
166
|
+
column(
|
|
167
|
+
`<mj-social mode="horizontal" align="center" icon-size="28px" border-radius="14px" inner-padding="6px">${elements}</mj-social>`
|
|
168
|
+
),
|
|
169
|
+
{ top: 16, bottom: 16 }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
function email(body) {
|
|
173
|
+
return `<mjml><mj-body background-color="${PALETTE.canvas}" width="600px">${body}</mj-body></mjml>`;
|
|
174
|
+
}
|
|
175
|
+
var esc = escapeXml;
|
|
176
|
+
var TEMPLATE_GALLERY = [
|
|
177
|
+
{
|
|
178
|
+
id: "blank",
|
|
179
|
+
name: "Blank",
|
|
180
|
+
description: "An empty page to build from scratch",
|
|
181
|
+
create: () => email("")
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "simple",
|
|
185
|
+
name: "Simple",
|
|
186
|
+
description: "Logo, headline, text and a button",
|
|
187
|
+
create: ({ sender, t }) => createStarterMjml({ sender, t })
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "newsletter",
|
|
191
|
+
name: "Newsletter",
|
|
192
|
+
description: "Monthly news: banner, two articles, highlights",
|
|
193
|
+
meta: (t) => ({
|
|
194
|
+
subject: t("The news of the month"),
|
|
195
|
+
preheader: t("What changed, what is coming, and two stories worth your time.")
|
|
196
|
+
}),
|
|
197
|
+
create: ({ sender, t }) => email(
|
|
198
|
+
header(sender, t) + banner({
|
|
199
|
+
title: esc(t("The news of the month")),
|
|
200
|
+
text: esc(t("What changed, what is coming, and two stories worth your time.")),
|
|
201
|
+
cta: null
|
|
202
|
+
}) + [1, 2].map(
|
|
203
|
+
(n) => section(
|
|
204
|
+
column(IMAGE, 'vertical-align="middle"') + column(
|
|
205
|
+
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.")), {
|
|
206
|
+
side: false
|
|
207
|
+
}) + button(esc(t("Read more")), { side: false }),
|
|
208
|
+
'vertical-align="middle"'
|
|
209
|
+
),
|
|
210
|
+
{ top: 16, bottom: 16, x: 16 }
|
|
211
|
+
)
|
|
212
|
+
).join("") + section(column(heading(esc(t("In brief")), { size: 20 })), { top: 20, bottom: 0 }) + section(
|
|
213
|
+
[t("A new feature"), t("An event"), t("A tip")].map(
|
|
214
|
+
(title) => column(
|
|
215
|
+
heading(esc(title), { size: 16, side: false }) + paragraph(esc(t("One sentence about it.")), { size: 14, side: false })
|
|
216
|
+
)
|
|
217
|
+
).join(""),
|
|
218
|
+
{ bottom: 20, x: 16 }
|
|
219
|
+
) + social() + footerSectionMjml(sender, t)
|
|
220
|
+
)
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "welcome",
|
|
224
|
+
name: "Welcome",
|
|
225
|
+
description: "Greets a new customer and lists the first steps",
|
|
226
|
+
meta: (t) => ({ subject: t("Welcome aboard!"), preheader: t("Three steps to get started.") }),
|
|
227
|
+
create: ({ sender, t }) => email(
|
|
228
|
+
header(sender, t) + section(
|
|
229
|
+
column(
|
|
230
|
+
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.")))
|
|
231
|
+
),
|
|
232
|
+
{ top: 16, bottom: 8 }
|
|
233
|
+
) + section(
|
|
234
|
+
[
|
|
235
|
+
[t("1. Complete your profile"), t("So we can tailor what we send you.")],
|
|
236
|
+
[t("2. Explore"), t("Take a tour of what you can do.")],
|
|
237
|
+
[t("3. Ask us anything"), t("Reply to this email: a person reads it.")]
|
|
238
|
+
].map(
|
|
239
|
+
([title, text]) => column(
|
|
240
|
+
heading(esc(title), { size: 16, side: false }) + paragraph(esc(text), { size: 14, side: false }),
|
|
241
|
+
`background-color="${PALETTE.canvas}" padding-top="12px" padding-bottom="12px" padding-left="12px" padding-right="12px"`
|
|
242
|
+
)
|
|
243
|
+
).join(""),
|
|
244
|
+
{ x: 24 }
|
|
245
|
+
) + section(column(button(esc(t("Get started")), { align: "center" })), { top: 16, bottom: 28 }) + footerSectionMjml(sender, t)
|
|
246
|
+
)
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "promotion",
|
|
250
|
+
name: "Promotion",
|
|
251
|
+
description: "A sale with a discount code and two products",
|
|
252
|
+
meta: (t) => ({ subject: t("30% off this weekend only"), preheader: t("With the code SPRING30, until Sunday midnight.") }),
|
|
253
|
+
create: ({ sender, t }) => email(
|
|
254
|
+
header(sender, t) + banner({
|
|
255
|
+
title: esc(t("30% off everything")),
|
|
256
|
+
text: esc(t("This weekend only, with the code SPRING30.")),
|
|
257
|
+
cta: esc(t("Shop the sale")),
|
|
258
|
+
background: PALETTE.brand
|
|
259
|
+
}) + section(
|
|
260
|
+
[t("Product one"), t("Product two")].map(
|
|
261
|
+
(name) => column(
|
|
262
|
+
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 })
|
|
263
|
+
)
|
|
264
|
+
).join(""),
|
|
265
|
+
{ top: 20, bottom: 20, x: 16 }
|
|
266
|
+
) + section(
|
|
267
|
+
column(
|
|
268
|
+
paragraph(esc(t("Offer valid until Sunday midnight, while stocks last.")), {
|
|
269
|
+
size: 12,
|
|
270
|
+
color: "#6d7484",
|
|
271
|
+
align: "center"
|
|
272
|
+
})
|
|
273
|
+
)
|
|
274
|
+
) + footerSectionMjml(sender, t)
|
|
275
|
+
)
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: "event",
|
|
279
|
+
name: "Event invitation",
|
|
280
|
+
description: "Date, place and a button to book a seat",
|
|
281
|
+
meta: (t) => ({ subject: t("You're invited"), preheader: t("Save the date \u2014 seats are limited.") }),
|
|
282
|
+
create: ({ sender, t }) => email(
|
|
283
|
+
header(sender, t) + banner({
|
|
284
|
+
title: esc(t("You're invited")),
|
|
285
|
+
text: esc(t("An evening of talks and drinks with the team and our customers.")),
|
|
286
|
+
cta: null
|
|
287
|
+
}) + section(
|
|
288
|
+
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 })),
|
|
289
|
+
{ top: 24, x: 16 }
|
|
290
|
+
) + section(
|
|
291
|
+
column(
|
|
292
|
+
paragraph(esc(t("Seats are limited: book yours now, it takes ten seconds."))) + button(esc(t("Book my seat")), { align: "left" })
|
|
293
|
+
),
|
|
294
|
+
{ bottom: 28 }
|
|
295
|
+
) + footerSectionMjml(sender, t)
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
];
|
|
299
|
+
function buildGallery({ templates = TEMPLATE_GALLERY, sender = {}, t = identity } = {}) {
|
|
300
|
+
return templates.map((template) => {
|
|
301
|
+
const meta = template.meta ? template.meta(t) : {};
|
|
302
|
+
return {
|
|
303
|
+
id: template.id,
|
|
304
|
+
name: t(template.name),
|
|
305
|
+
description: t(template.description),
|
|
306
|
+
mjml: template.create({ sender, t }),
|
|
307
|
+
subject: meta.subject || "",
|
|
308
|
+
preheader: meta.preheader || ""
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
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";
|
|
313
|
+
function withThumbnailImages(mjml) {
|
|
314
|
+
return String(mjml || "").replace(/<mj-image(?![^>]*\ssrc=)([^>]*)>/g, `<mj-image src="${THUMBNAIL_IMAGE}"$1>`);
|
|
315
|
+
}
|
|
316
|
+
function withPlaceholderImages(html) {
|
|
317
|
+
return String(html || "").replace(/<img(?![^>]*\ssrc=)(\s[^>]*)?>/g, (_m, attrs = "") => `<img src="${THUMBNAIL_IMAGE}"${attrs}>`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/features/preview/PreviewButton.jsx
|
|
321
|
+
import { useMemo, useState } from "react";
|
|
322
|
+
import { AlertTriangle, CheckCircle2, Eye, Info, Loader2, Monitor, Moon, RefreshCw, Smartphone, Sun } from "lucide-react";
|
|
323
|
+
|
|
324
|
+
// src/core/scrollbars.js
|
|
325
|
+
var FRAME_SCROLLBAR_CSS = `
|
|
326
|
+
::-webkit-scrollbar{width:5.6px;height:5.6px;background:transparent}
|
|
327
|
+
::-webkit-scrollbar-button{display:none;width:0;height:0}
|
|
328
|
+
::-webkit-scrollbar-track,::-webkit-scrollbar-track-piece,::-webkit-scrollbar-corner{background:transparent;border:0;box-shadow:none}
|
|
329
|
+
::-webkit-scrollbar-thumb{background-color:rgba(156,163,175,.45);border:1.4px solid transparent;background-clip:content-box;border-radius:9999px;min-height:2rem;min-width:2rem}
|
|
330
|
+
::-webkit-scrollbar-thumb:hover{background-color:rgba(107,114,128,.7)}
|
|
331
|
+
@supports not selector(::-webkit-scrollbar){*{scrollbar-width:thin;scrollbar-color:rgba(156,163,175,.45) transparent}}
|
|
332
|
+
@media (hover:hover) and (pointer:fine){
|
|
333
|
+
::-webkit-scrollbar-thumb{background-color:transparent}
|
|
334
|
+
*:hover::-webkit-scrollbar-thumb{background-color:rgba(156,163,175,.45)}
|
|
335
|
+
*:hover::-webkit-scrollbar-thumb:hover{background-color:rgba(107,114,128,.7)}
|
|
336
|
+
@supports not selector(::-webkit-scrollbar){*{scrollbar-color:transparent transparent}*:hover{scrollbar-color:rgba(156,163,175,.45) transparent}}
|
|
337
|
+
}`;
|
|
338
|
+
|
|
339
|
+
// src/features/preview/PreviewButton.jsx
|
|
340
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
341
|
+
var LEVEL_ICON = { warning: AlertTriangle, info: Info };
|
|
342
|
+
function QualityList({ quality = [], warnings = [] }) {
|
|
343
|
+
const { t } = useUi();
|
|
344
|
+
if (!quality.length && !warnings.length) {
|
|
345
|
+
return /* @__PURE__ */ jsxs("p", { className: "eb:flex eb:items-center eb:gap-1.5 eb:text-[12px] eb:text-foreground-lighter", children: [
|
|
346
|
+
/* @__PURE__ */ jsx(CheckCircle2, { className: "eb:size-3.5 eb:text-success", "aria-hidden": "true" }),
|
|
347
|
+
t("Nothing to fix.")
|
|
348
|
+
] });
|
|
349
|
+
}
|
|
350
|
+
return /* @__PURE__ */ jsxs("ul", { className: "eb:space-y-1.5", "aria-label": t("Points to check"), children: [
|
|
351
|
+
quality.map((issue) => {
|
|
352
|
+
const Icon = LEVEL_ICON[issue.level] || Info;
|
|
353
|
+
return /* @__PURE__ */ jsxs("li", { className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-foreground", children: [
|
|
354
|
+
/* @__PURE__ */ jsx(
|
|
355
|
+
Icon,
|
|
356
|
+
{
|
|
357
|
+
className: cn(
|
|
358
|
+
"eb:mt-0.5 eb:size-3.5 eb:shrink-0",
|
|
359
|
+
issue.level === "warning" ? "eb:text-warning" : "eb:text-foreground-muted"
|
|
360
|
+
),
|
|
361
|
+
"aria-hidden": "true"
|
|
362
|
+
}
|
|
363
|
+
),
|
|
364
|
+
issue.message
|
|
365
|
+
] }, issue.code);
|
|
366
|
+
}),
|
|
367
|
+
warnings.slice(0, 5).map((w, i) => /* @__PURE__ */ jsxs("li", { className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-foreground-lighter", children: [
|
|
368
|
+
/* @__PURE__ */ jsx(Info, { className: "eb:mt-0.5 eb:size-3.5 eb:shrink-0 eb:text-foreground-muted", "aria-hidden": "true" }),
|
|
369
|
+
"MJML: ",
|
|
370
|
+
w.message
|
|
371
|
+
] }, `mjml-${i}`))
|
|
372
|
+
] });
|
|
373
|
+
}
|
|
374
|
+
var PREVIEW_SCROLLBAR_CSS = `<style data-preview-scrollbar>${FRAME_SCROLLBAR_CSS}</style>`;
|
|
375
|
+
var withPreviewScrollbars = (html) => !html ? "" : /<\/head>/i.test(html) ? html.replace(/<\/head>/i, `${PREVIEW_SCROLLBAR_CSS}</head>`) : PREVIEW_SCROLLBAR_CSS + html;
|
|
376
|
+
function EmailPreview({ html, loading = false, error = null, device = "desktop", onDeviceChange, onRefresh, hint = true }) {
|
|
377
|
+
const { t } = useUi();
|
|
378
|
+
const isMobile = device === "mobile";
|
|
379
|
+
const [scheme, setScheme] = useState("light");
|
|
380
|
+
const dark = scheme === "dark";
|
|
381
|
+
const srcDoc = useMemo(() => withPreviewScrollbars(dark ? simulateDarkMode(html) : html), [html, dark]);
|
|
382
|
+
return /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:h-full eb:min-h-0 eb:flex-col", children: [
|
|
383
|
+
/* @__PURE__ */ jsxs("div", { className: "eb:mb-2 eb:flex eb:shrink-0 eb:flex-wrap eb:items-center eb:gap-2", children: [
|
|
384
|
+
/* @__PURE__ */ jsx(Tabs, { value: device, onValueChange: (value) => onDeviceChange?.(value), children: /* @__PURE__ */ jsxs(TabsList, { "aria-label": t("Device"), children: [
|
|
385
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "desktop", children: [
|
|
386
|
+
/* @__PURE__ */ jsx(Monitor, {}),
|
|
387
|
+
t("Desktop")
|
|
388
|
+
] }),
|
|
389
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "mobile", children: [
|
|
390
|
+
/* @__PURE__ */ jsx(Smartphone, {}),
|
|
391
|
+
t("Mobile")
|
|
392
|
+
] })
|
|
393
|
+
] }) }),
|
|
394
|
+
/* @__PURE__ */ jsx(Tabs, { value: scheme, onValueChange: setScheme, children: /* @__PURE__ */ jsxs(TabsList, { "aria-label": t("Color scheme"), children: [
|
|
395
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "light", "aria-label": t("Light mode"), title: t("Light mode"), children: /* @__PURE__ */ jsx(Sun, {}) }),
|
|
396
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "dark", "aria-label": t("Dark mode"), title: t("Dark mode"), children: /* @__PURE__ */ jsx(Moon, {}) })
|
|
397
|
+
] }) }),
|
|
398
|
+
/* @__PURE__ */ jsx("span", { className: "eb:flex-1" }),
|
|
399
|
+
onRefresh && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm-compact", onClick: onRefresh, disabled: loading, children: [
|
|
400
|
+
loading ? /* @__PURE__ */ jsx(Loader2, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, {}),
|
|
401
|
+
t("Refresh")
|
|
402
|
+
] })
|
|
403
|
+
] }),
|
|
404
|
+
/* @__PURE__ */ jsx("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-auto eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:p-3", children: error ? /* @__PURE__ */ jsx("div", { className: "eb:flex eb:h-full eb:items-center eb:justify-center eb:p-6 eb:text-center eb:text-[13px] eb:text-destructive", children: error }) : !srcDoc ? /* @__PURE__ */ jsx("div", { className: "eb:flex eb:h-full eb:items-center eb:justify-center eb:p-6 eb:text-center eb:text-[13px] eb:text-foreground-lighter", children: loading ? t("Rendering\u2026") : t("Nothing to show yet.") }) : (
|
|
405
|
+
// Narrows and widens like the editor's canvas (width .35s ease).
|
|
406
|
+
/* @__PURE__ */ jsx(
|
|
407
|
+
"div",
|
|
408
|
+
{
|
|
409
|
+
className: cn(
|
|
410
|
+
"eb:mx-auto eb:max-w-full eb:transition-[width,box-shadow] eb:duration-[350ms] eb:ease-in-out eb:motion-reduce:transition-none",
|
|
411
|
+
isMobile && "eb:shadow-sm"
|
|
412
|
+
),
|
|
413
|
+
style: { width: isMobile ? EMAIL_MOBILE_WIDTH : "100%", background: dark ? DARK_PAGE : "#ffffff" },
|
|
414
|
+
"data-device": device,
|
|
415
|
+
"data-scheme": scheme,
|
|
416
|
+
children: /* @__PURE__ */ jsx(
|
|
417
|
+
"iframe",
|
|
418
|
+
{
|
|
419
|
+
sandbox: "",
|
|
420
|
+
srcDoc,
|
|
421
|
+
title: t("Email preview"),
|
|
422
|
+
className: "eb:block eb:w-full eb:border-0 eb:transition-[height] eb:duration-[350ms] eb:ease-in-out eb:motion-reduce:transition-none",
|
|
423
|
+
style: { height: isMobile ? 760 : 900, background: dark ? DARK_PAGE : "#ffffff" }
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
) }),
|
|
429
|
+
dark ? /* @__PURE__ */ jsx("p", { className: "eb:mt-2 eb:shrink-0 eb:text-[11px] eb:leading-relaxed eb:text-foreground-lighter", "data-testid": "eb-dark-hint", children: t("Dark mode as Gmail and Outlook apps show it: light backgrounds turn dark, dark text turns light, images stay as they are.") }) : hint && /* @__PURE__ */ jsx("p", { className: "eb:mt-2 eb:shrink-0 eb:text-[11px] eb:leading-relaxed eb:text-foreground-lighter", children: t("Indicative preview \u2014 check it with \u201CSend test\u201D.") })
|
|
430
|
+
] });
|
|
431
|
+
}
|
|
432
|
+
function PreviewButton({ context }) {
|
|
433
|
+
const { editor, ready, meta, storage } = context;
|
|
434
|
+
const { t } = useUi();
|
|
435
|
+
const [open, setOpen] = useState(false);
|
|
436
|
+
const [device, setDevice] = useState("desktop");
|
|
437
|
+
const [state, setState] = useState({ loading: false, html: "", subject: null, preheader: null, warnings: [], quality: [], error: null });
|
|
438
|
+
const refresh = async () => {
|
|
439
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
440
|
+
try {
|
|
441
|
+
const data = await storage.render({ mjml: editor.exportMjml(), subject: meta.subject, preheader: meta.preheader });
|
|
442
|
+
setState({
|
|
443
|
+
loading: false,
|
|
444
|
+
html: data.html,
|
|
445
|
+
// Subject and preview text with their variables filled, when the backend returns them.
|
|
446
|
+
subject: data.subject ?? null,
|
|
447
|
+
preheader: data.preheader ?? null,
|
|
448
|
+
warnings: data.warnings || [],
|
|
449
|
+
quality: data.quality || [],
|
|
450
|
+
error: null
|
|
451
|
+
});
|
|
452
|
+
} catch (error) {
|
|
453
|
+
setState({ loading: false, html: "", subject: null, preheader: null, warnings: [], quality: [], error: error.message });
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
457
|
+
/* @__PURE__ */ jsxs(
|
|
458
|
+
Button,
|
|
459
|
+
{
|
|
460
|
+
variant: "outline",
|
|
461
|
+
size: "sm-compact",
|
|
462
|
+
disabled: !ready,
|
|
463
|
+
onClick: () => {
|
|
464
|
+
setOpen(true);
|
|
465
|
+
refresh();
|
|
466
|
+
},
|
|
467
|
+
children: [
|
|
468
|
+
/* @__PURE__ */ jsx(Eye, {}),
|
|
469
|
+
t("Preview")
|
|
470
|
+
]
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "eb:flex eb:h-[85vh] eb:max-h-[85vh] eb:flex-col eb:gap-4 eb:sm:max-w-[67.2rem]", children: [
|
|
474
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:items-center eb:gap-3", children: [
|
|
475
|
+
/* @__PURE__ */ jsx("span", { className: "eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary", children: /* @__PURE__ */ jsx(Eye, { className: "eb:size-5" }) }),
|
|
476
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
477
|
+
/* @__PURE__ */ jsx(DialogTitle, { className: "eb:text-base eb:font-normal", children: t("Email preview") }),
|
|
478
|
+
/* @__PURE__ */ jsx(DialogDescription, { className: "eb:text-[13px]", children: t("Final rendering, variables filled with samples.") })
|
|
479
|
+
] })
|
|
480
|
+
] }) }),
|
|
481
|
+
/* @__PURE__ */ jsxs("div", { className: "eb:grid eb:min-h-0 eb:flex-1 eb:grid-cols-1 eb:gap-4 eb:md:grid-cols-[minmax(0,1fr)_260px]", children: [
|
|
482
|
+
/* @__PURE__ */ jsx(
|
|
483
|
+
EmailPreview,
|
|
484
|
+
{
|
|
485
|
+
html: state.html,
|
|
486
|
+
loading: state.loading,
|
|
487
|
+
error: state.error,
|
|
488
|
+
device,
|
|
489
|
+
onDeviceChange: setDevice,
|
|
490
|
+
onRefresh: refresh,
|
|
491
|
+
hint: typeof storage.sendTest === "function"
|
|
492
|
+
}
|
|
493
|
+
),
|
|
494
|
+
/* @__PURE__ */ jsxs("aside", { className: "eb:min-h-0 eb:space-y-5 eb:overflow-y-auto", "aria-busy": state.loading, children: [
|
|
495
|
+
/* @__PURE__ */ jsxs("section", { className: "eb:space-y-2", children: [
|
|
496
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: t("Inbox") }),
|
|
497
|
+
/* @__PURE__ */ jsxs("div", { className: "eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:px-3 eb:py-2.5", children: [
|
|
498
|
+
/* @__PURE__ */ jsx("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: (state.subject ?? meta.subject) || t("No subject") }),
|
|
499
|
+
/* @__PURE__ */ jsx("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: (state.preheader ?? meta.preheader) || t("No preview text") })
|
|
500
|
+
] })
|
|
501
|
+
] }),
|
|
502
|
+
/* @__PURE__ */ jsxs("section", { className: "eb:space-y-2", children: [
|
|
503
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: t("To check") }),
|
|
504
|
+
!state.loading && !state.error && /* @__PURE__ */ jsx(QualityList, { quality: state.quality, warnings: state.warnings })
|
|
505
|
+
] })
|
|
506
|
+
] })
|
|
507
|
+
] })
|
|
508
|
+
] }) })
|
|
509
|
+
] });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/features/brand/useBrand.js
|
|
513
|
+
import { useCallback, useEffect, useState as useState2 } from "react";
|
|
514
|
+
var entries = /* @__PURE__ */ new WeakMap();
|
|
515
|
+
function brandOf(storage) {
|
|
516
|
+
let entry = entries.get(storage);
|
|
517
|
+
if (entry) return entry;
|
|
518
|
+
const created = { value: {}, loaded: Promise.resolve({}), listeners: /* @__PURE__ */ new Set() };
|
|
519
|
+
created.loaded = typeof storage?.loadBrand === "function" ? Promise.resolve().then(() => storage.loadBrand()).then(
|
|
520
|
+
(brand) => cleanBrand(brand),
|
|
521
|
+
() => (
|
|
522
|
+
/** @type {Brand} */
|
|
523
|
+
{}
|
|
524
|
+
)
|
|
525
|
+
).then((brand) => {
|
|
526
|
+
created.value = brand;
|
|
527
|
+
created.listeners.forEach((listener) => listener(brand));
|
|
528
|
+
return brand;
|
|
529
|
+
}) : created.loaded;
|
|
530
|
+
if (storage && typeof storage === "object") entries.set(storage, created);
|
|
531
|
+
return created;
|
|
532
|
+
}
|
|
533
|
+
function useBrand(storage) {
|
|
534
|
+
const [brand, setState] = useState2(() => brandOf(storage).value);
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
const entry = brandOf(storage);
|
|
537
|
+
setState(entry.value);
|
|
538
|
+
entry.listeners.add(setState);
|
|
539
|
+
return () => {
|
|
540
|
+
entry.listeners.delete(setState);
|
|
541
|
+
};
|
|
542
|
+
}, [storage]);
|
|
543
|
+
const setBrand = useCallback(
|
|
544
|
+
(value) => {
|
|
545
|
+
const entry = brandOf(storage);
|
|
546
|
+
entry.value = cleanBrand(value);
|
|
547
|
+
entry.listeners.forEach((listener) => listener(entry.value));
|
|
548
|
+
},
|
|
549
|
+
[storage]
|
|
550
|
+
);
|
|
551
|
+
return { brand, setBrand };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/ui/useLayoutWidth.js
|
|
555
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
556
|
+
var LAYOUT_BREAKPOINTS = { wide: 1180, medium: 760 };
|
|
557
|
+
function layoutFor(width) {
|
|
558
|
+
if (width >= LAYOUT_BREAKPOINTS.wide) return "wide";
|
|
559
|
+
if (width >= LAYOUT_BREAKPOINTS.medium) return "medium";
|
|
560
|
+
return "narrow";
|
|
561
|
+
}
|
|
562
|
+
function useLayoutWidth(ref) {
|
|
563
|
+
const [layout, setLayout] = useState3(
|
|
564
|
+
/** @type {'wide'|'medium'|'narrow'} */
|
|
565
|
+
"wide"
|
|
566
|
+
);
|
|
567
|
+
useEffect2(() => {
|
|
568
|
+
const el = ref.current;
|
|
569
|
+
if (!el || typeof ResizeObserver === "undefined") return void 0;
|
|
570
|
+
const measure = () => setLayout(layoutFor(el.getBoundingClientRect().width));
|
|
571
|
+
measure();
|
|
572
|
+
const observer = new ResizeObserver(measure);
|
|
573
|
+
observer.observe(el);
|
|
574
|
+
return () => observer.disconnect();
|
|
575
|
+
}, [ref]);
|
|
576
|
+
return layout;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export {
|
|
580
|
+
socialBlock,
|
|
581
|
+
socialElementBlock,
|
|
582
|
+
FRAME_SCROLLBAR_CSS,
|
|
583
|
+
EmailPreview,
|
|
584
|
+
PreviewButton,
|
|
585
|
+
createStarterMjml,
|
|
586
|
+
STARTER_MJML,
|
|
587
|
+
TEMPLATE_GALLERY,
|
|
588
|
+
buildGallery,
|
|
589
|
+
withThumbnailImages,
|
|
590
|
+
withPlaceholderImages,
|
|
591
|
+
brandOf,
|
|
592
|
+
useBrand,
|
|
593
|
+
useLayoutWidth
|
|
594
|
+
};
|
|
595
|
+
//# sourceMappingURL=chunk-3LKF4QBL.js.map
|