best-email-builder 0.4.0 → 0.5.1
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 +88 -0
- package/README.md +130 -44
- 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-2RKVJQSD.js +595 -0
- package/dist/chunk-2RKVJQSD.js.map +1 -0
- package/dist/{chunk-NAEGU6PR.js → chunk-2Y7CDXZM.js} +18 -29
- package/dist/chunk-2Y7CDXZM.js.map +1 -0
- package/dist/chunk-4U7L2NYQ.js +356 -0
- package/dist/chunk-4U7L2NYQ.js.map +1 -0
- package/dist/{chunk-MY6WPDT4.js → chunk-7QQH6MTV.js} +473 -994
- package/dist/chunk-7QQH6MTV.js.map +1 -0
- package/dist/{chunk-JNHFZXDL.js → chunk-BAA5XMWY.js} +645 -646
- package/dist/chunk-BAA5XMWY.js.map +1 -0
- package/dist/{chunk-TGDZ4NFI.js → chunk-CNJZLZRH.js} +3 -44
- package/dist/chunk-CNJZLZRH.js.map +1 -0
- package/dist/chunk-H4W65GBX.js +119 -0
- package/dist/chunk-H4W65GBX.js.map +1 -0
- package/dist/{chunk-HHLOE6OZ.js → chunk-JI36FNWE.js} +12 -333
- package/dist/chunk-JI36FNWE.js.map +1 -0
- package/dist/{chunk-AT73Y7JA.js → chunk-N7DEVAIP.js} +49 -581
- package/dist/chunk-N7DEVAIP.js.map +1 -0
- package/dist/chunk-OWVXKMLI.js +121 -0
- package/dist/chunk-OWVXKMLI.js.map +1 -0
- package/dist/chunk-SAIW46IC.js +98 -0
- package/dist/chunk-SAIW46IC.js.map +1 -0
- package/dist/chunk-UFKCLU2D.js +255 -0
- package/dist/chunk-UFKCLU2D.js.map +1 -0
- package/dist/chunk-W424KPCR.js +251 -0
- package/dist/chunk-W424KPCR.js.map +1 -0
- package/dist/index.js +31 -37
- package/dist/index.js.map +1 -1
- package/dist/library.js +12 -7
- package/dist/server.js +40 -98
- 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/SubjectIdeas.d.ts +25 -0
- package/dist/types/ai/createAi.d.ts +66 -0
- package/dist/types/{features/generate → ai}/draft.d.ts +56 -3
- package/dist/types/{features/generate → ai}/generator.d.ts +7 -2
- 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/providers.d.ts +75 -0
- package/dist/types/{features/generate → ai}/rewrite.d.ts +1 -1
- package/dist/types/ai/server.d.ts +9 -0
- package/dist/types/{features/generate → ai}/subjects.d.ts +18 -4
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/features/preview/darkMode.d.ts +33 -16
- 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 +1 -7
- package/dist/types/renderer/prepareMjml.d.ts +8 -2
- package/dist/types/server/handler.d.ts +5 -4
- package/dist/types/server/index.d.ts +2 -9
- package/dist/types/server/render.d.ts +5 -2
- package/dist/types/storage/client.d.ts +22 -40
- 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/primitives/searchShortcut.d.ts +16 -0
- package/package.json +15 -1
- package/dist/chunk-AT73Y7JA.js.map +0 -1
- package/dist/chunk-HHLOE6OZ.js.map +0 -1
- package/dist/chunk-JNHFZXDL.js.map +0 -1
- package/dist/chunk-MY6WPDT4.js.map +0 -1
- package/dist/chunk-NAEGU6PR.js.map +0 -1
- package/dist/chunk-TGDZ4NFI.js.map +0 -1
- package/dist/types/features/generate/GenerateButton.d.ts +0 -3
- package/dist/types/features/generate/SubjectIdeas.d.ts +0 -16
- package/dist/types/features/generate/index.d.ts +0 -2
- /package/dist/types/{features/generate → ai}/RewriteText.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/chip.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/claude.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/prompt.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/purposes.d.ts +0 -0
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FRAME_SCROLLBAR_CSS,
|
|
3
|
+
PreviewButton,
|
|
4
|
+
TEMPLATE_GALLERY,
|
|
5
|
+
brandOf,
|
|
6
|
+
buildGallery,
|
|
7
|
+
createStarterMjml,
|
|
8
|
+
socialBlock,
|
|
9
|
+
socialElementBlock,
|
|
10
|
+
useBrand,
|
|
11
|
+
useLayoutWidth,
|
|
12
|
+
withThumbnailImages
|
|
13
|
+
} from "./chunk-2RKVJQSD.js";
|
|
14
|
+
import {
|
|
15
|
+
BRAND_NOTES,
|
|
16
|
+
countImagesWithoutPicture,
|
|
17
|
+
logoWidthFor
|
|
18
|
+
} from "./chunk-4U7L2NYQ.js";
|
|
19
|
+
import {
|
|
20
|
+
cleanRichText,
|
|
21
|
+
escapeText,
|
|
22
|
+
textOfHtml
|
|
23
|
+
} from "./chunk-SAIW46IC.js";
|
|
1
24
|
import {
|
|
2
25
|
Button,
|
|
3
26
|
Dialog,
|
|
@@ -5,18 +28,14 @@ import {
|
|
|
5
28
|
DialogDescription,
|
|
6
29
|
DialogHeader,
|
|
7
30
|
DialogTitle,
|
|
8
|
-
DropdownMenu,
|
|
9
|
-
DropdownMenuContent,
|
|
10
|
-
DropdownMenuItem,
|
|
11
|
-
DropdownMenuTrigger,
|
|
12
|
-
FRAME_SCROLLBAR_CSS,
|
|
13
31
|
Input,
|
|
14
32
|
Label,
|
|
15
33
|
NoticeArea,
|
|
16
34
|
Popover,
|
|
17
35
|
PopoverContent,
|
|
18
36
|
PopoverTrigger,
|
|
19
|
-
|
|
37
|
+
SEARCH_KEYS,
|
|
38
|
+
SearchKeyHint,
|
|
20
39
|
SectionLabel,
|
|
21
40
|
Select,
|
|
22
41
|
SelectContent,
|
|
@@ -25,7 +44,6 @@ import {
|
|
|
25
44
|
SelectValue,
|
|
26
45
|
Separator,
|
|
27
46
|
Skeleton,
|
|
28
|
-
TEMPLATE_GALLERY,
|
|
29
47
|
Tabs,
|
|
30
48
|
TabsList,
|
|
31
49
|
TabsTrigger,
|
|
@@ -33,42 +51,34 @@ import {
|
|
|
33
51
|
TooltipWrap,
|
|
34
52
|
TruncatedText,
|
|
35
53
|
UiProvider,
|
|
36
|
-
brandOf,
|
|
37
|
-
buildGallery,
|
|
38
54
|
cn,
|
|
39
55
|
createHttpStorage,
|
|
40
|
-
|
|
41
|
-
socialBlock,
|
|
42
|
-
socialElementBlock,
|
|
43
|
-
useBrand,
|
|
44
|
-
useLayoutWidth,
|
|
56
|
+
useSearchShortcut,
|
|
45
57
|
useT,
|
|
46
|
-
useUi
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
useUi
|
|
59
|
+
} from "./chunk-N7DEVAIP.js";
|
|
60
|
+
import {
|
|
61
|
+
EMAIL_BUILDER_VERSION,
|
|
62
|
+
EMAIL_DEVICES,
|
|
63
|
+
createDocument,
|
|
64
|
+
imageUploadError,
|
|
65
|
+
readDocument
|
|
66
|
+
} from "./chunk-H4W65GBX.js";
|
|
49
67
|
import {
|
|
50
|
-
BRAND_NOTES,
|
|
51
|
-
INLINE_TAGS,
|
|
52
68
|
LONGHAND_SPACING,
|
|
53
|
-
|
|
54
|
-
MAX_REWRITE_INSTRUCTION,
|
|
55
|
-
REWRITE_ACTIONS,
|
|
56
|
-
REWRITE_LANGUAGES,
|
|
57
|
-
emailWords,
|
|
69
|
+
MJML_DEFAULTS,
|
|
58
70
|
formatSpacing,
|
|
59
|
-
isSafeUrl,
|
|
60
|
-
logoWidthFor,
|
|
61
71
|
parseSpacing,
|
|
62
|
-
purposesByCategory,
|
|
63
72
|
toAttributes,
|
|
64
73
|
toValues
|
|
65
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-OWVXKMLI.js";
|
|
75
|
+
import {
|
|
76
|
+
isSafeUrl
|
|
77
|
+
} from "./chunk-W424KPCR.js";
|
|
66
78
|
import {
|
|
67
79
|
CUSTOM_GROUP,
|
|
68
80
|
DEFAULT_BUTTON_URL,
|
|
69
81
|
DEFAULT_VARIABLES,
|
|
70
|
-
EMAIL_BUILDER_VERSION,
|
|
71
|
-
EMAIL_DEVICES,
|
|
72
82
|
FONT_OPTIONS,
|
|
73
83
|
FONT_STACK,
|
|
74
84
|
GROUPS,
|
|
@@ -84,7 +94,6 @@ import {
|
|
|
84
94
|
cloneWithNewIds,
|
|
85
95
|
closestOfTags,
|
|
86
96
|
contrastRatio,
|
|
87
|
-
createDocument,
|
|
88
97
|
createNode,
|
|
89
98
|
defineVariables,
|
|
90
99
|
escapeXml,
|
|
@@ -93,11 +102,11 @@ import {
|
|
|
93
102
|
fromProject,
|
|
94
103
|
hasBrand,
|
|
95
104
|
identity,
|
|
96
|
-
imageUploadError,
|
|
97
105
|
inWhiteSection,
|
|
98
106
|
insertNodes,
|
|
99
107
|
keyFromLabel,
|
|
100
108
|
mergeVariables,
|
|
109
|
+
messagesOf,
|
|
101
110
|
moveNode,
|
|
102
111
|
parentOf,
|
|
103
112
|
parseMjml,
|
|
@@ -105,7 +114,6 @@ import {
|
|
|
105
114
|
pathTo,
|
|
106
115
|
positionOf,
|
|
107
116
|
prop,
|
|
108
|
-
readDocument,
|
|
109
117
|
readableOn,
|
|
110
118
|
removeNode,
|
|
111
119
|
serializeMjml,
|
|
@@ -115,7 +123,7 @@ import {
|
|
|
115
123
|
tokenOf,
|
|
116
124
|
translatorFor,
|
|
117
125
|
validateCustomVariable
|
|
118
|
-
} from "./chunk-
|
|
126
|
+
} from "./chunk-JI36FNWE.js";
|
|
119
127
|
|
|
120
128
|
// src/core/events.js
|
|
121
129
|
function createEmitter({ onError } = {}) {
|
|
@@ -148,62 +156,6 @@ function createEmitter({ onError } = {}) {
|
|
|
148
156
|
};
|
|
149
157
|
}
|
|
150
158
|
|
|
151
|
-
// src/core/mjmlDefaults.js
|
|
152
|
-
var MJML_DEFAULTS = {
|
|
153
|
-
"mj-body": { width: "600px" },
|
|
154
|
-
"mj-section": {
|
|
155
|
-
"background-repeat": "repeat",
|
|
156
|
-
"background-size": "auto",
|
|
157
|
-
"background-position": "top center",
|
|
158
|
-
padding: "20px 0",
|
|
159
|
-
"text-align": "center"
|
|
160
|
-
},
|
|
161
|
-
"mj-wrapper": { "background-repeat": "repeat", "background-size": "auto", "background-position": "top center", padding: "20px 0" },
|
|
162
|
-
"mj-column": { "vertical-align": "top" },
|
|
163
|
-
"mj-text": {
|
|
164
|
-
align: "left",
|
|
165
|
-
color: "#000000",
|
|
166
|
-
"font-family": "Ubuntu, Helvetica, Arial, sans-serif",
|
|
167
|
-
"font-size": "13px",
|
|
168
|
-
"line-height": "1",
|
|
169
|
-
padding: "10px 25px"
|
|
170
|
-
},
|
|
171
|
-
"mj-image": { align: "center", height: "auto", padding: "10px 25px" },
|
|
172
|
-
"mj-button": {
|
|
173
|
-
align: "center",
|
|
174
|
-
"background-color": "#414141",
|
|
175
|
-
"border-radius": "3px",
|
|
176
|
-
color: "#ffffff",
|
|
177
|
-
"font-family": "Ubuntu, Helvetica, Arial, sans-serif",
|
|
178
|
-
"font-size": "13px",
|
|
179
|
-
"font-weight": "normal",
|
|
180
|
-
"inner-padding": "10px 25px",
|
|
181
|
-
"line-height": "120%",
|
|
182
|
-
padding: "10px 25px"
|
|
183
|
-
},
|
|
184
|
-
"mj-divider": { "border-color": "#000000", "border-style": "solid", "border-width": "4px", padding: "10px 25px", width: "100%", align: "center" },
|
|
185
|
-
"mj-spacer": { height: "20px" },
|
|
186
|
-
"mj-social": {
|
|
187
|
-
align: "center",
|
|
188
|
-
"border-radius": "3px",
|
|
189
|
-
"icon-size": "20px",
|
|
190
|
-
mode: "horizontal",
|
|
191
|
-
padding: "10px 25px"
|
|
192
|
-
},
|
|
193
|
-
"mj-social-element": { "border-radius": "3px", padding: "4px" },
|
|
194
|
-
"mj-navbar": { align: "center" },
|
|
195
|
-
"mj-navbar-link": {
|
|
196
|
-
color: "#000000",
|
|
197
|
-
"font-family": "Ubuntu, Helvetica, Arial, sans-serif",
|
|
198
|
-
"font-size": "13px",
|
|
199
|
-
"font-weight": "normal",
|
|
200
|
-
"line-height": "22px",
|
|
201
|
-
padding: "15px 10px",
|
|
202
|
-
"text-transform": "uppercase"
|
|
203
|
-
},
|
|
204
|
-
"mj-hero": { "background-color": "#ffffff", padding: "0px" }
|
|
205
|
-
};
|
|
206
|
-
|
|
207
159
|
// src/core/EmailEditor.js
|
|
208
160
|
function resolveBlock(block, t = identity) {
|
|
209
161
|
const text = (value) => typeof value === "function" ? String(value(t)) : value;
|
|
@@ -298,7 +250,7 @@ function createEmailEditor({ adapter, blocks, features = null, t = identity }) {
|
|
|
298
250
|
get adapterName() {
|
|
299
251
|
return adapter.name;
|
|
300
252
|
},
|
|
301
|
-
async initialize({ container, document
|
|
253
|
+
async initialize({ container, document = null }) {
|
|
302
254
|
if (!container) throw new Error("EmailEditor: missing container");
|
|
303
255
|
if (status !== "idle") {
|
|
304
256
|
throw new Error(`EmailEditor: initialization refused (status "${status}")`);
|
|
@@ -309,7 +261,7 @@ function createEmailEditor({ adapter, blocks, features = null, t = identity }) {
|
|
|
309
261
|
await adapter.initialize({
|
|
310
262
|
container,
|
|
311
263
|
blocks: blocks.list(),
|
|
312
|
-
document:
|
|
264
|
+
document: document ? readDocument(document) : null,
|
|
313
265
|
t,
|
|
314
266
|
labelOf: (selection) => t(labelFor(selection, blockForTag(selection.tagName))),
|
|
315
267
|
onEvent: (type, payload) => {
|
|
@@ -417,18 +369,18 @@ function createEmailEditor({ adapter, blocks, features = null, t = identity }) {
|
|
|
417
369
|
*/
|
|
418
370
|
loadDocument(raw, { mjml: mjml6 = null } = {}) {
|
|
419
371
|
requireReady("loadDocument");
|
|
420
|
-
const
|
|
421
|
-
if (
|
|
372
|
+
const document = readDocument(raw);
|
|
373
|
+
if (document.engine !== adapter.name) {
|
|
422
374
|
if (mjml6) {
|
|
423
375
|
adapter.loadMjml(String(mjml6));
|
|
424
376
|
adapter.clearHistory();
|
|
425
377
|
return;
|
|
426
378
|
}
|
|
427
379
|
throw new Error(
|
|
428
|
-
`EmailEditor: document written for engine "${
|
|
380
|
+
`EmailEditor: document written for engine "${document.engine}", editor runs on "${adapter.name}"`
|
|
429
381
|
);
|
|
430
382
|
}
|
|
431
|
-
adapter.loadProject(
|
|
383
|
+
adapter.loadProject(document.project);
|
|
432
384
|
adapter.clearHistory();
|
|
433
385
|
},
|
|
434
386
|
loadMjml(mjml6) {
|
|
@@ -1244,6 +1196,11 @@ function VariablePicker({ variables, onPick, onCreate = null, onEdit = null, fil
|
|
|
1244
1196
|
const [query, setQuery] = useState3("");
|
|
1245
1197
|
const [active, setActive] = useState3(0);
|
|
1246
1198
|
const listRef = useRef(null);
|
|
1199
|
+
const searchRef = useRef(
|
|
1200
|
+
/** @type {HTMLInputElement|null} */
|
|
1201
|
+
null
|
|
1202
|
+
);
|
|
1203
|
+
useSearchShortcut(searchRef);
|
|
1247
1204
|
const matches = useMemo2(() => {
|
|
1248
1205
|
const q = query.trim().toLowerCase();
|
|
1249
1206
|
return variables.filter((v) => filter ? filter(v) : true).filter(
|
|
@@ -1275,15 +1232,18 @@ function VariablePicker({ variables, onPick, onCreate = null, onEdit = null, fil
|
|
|
1275
1232
|
/* @__PURE__ */ jsx3(
|
|
1276
1233
|
"input",
|
|
1277
1234
|
{
|
|
1235
|
+
ref: searchRef,
|
|
1278
1236
|
autoFocus: true,
|
|
1279
1237
|
value: query,
|
|
1280
1238
|
onChange: (e) => setQuery(e.target.value),
|
|
1281
1239
|
onKeyDown,
|
|
1282
1240
|
placeholder: t("Search variables\u2026"),
|
|
1283
1241
|
"aria-label": t("Search variables\u2026"),
|
|
1284
|
-
|
|
1242
|
+
"aria-keyshortcuts": SEARCH_KEYS,
|
|
1243
|
+
className: "eb:h-8 eb:w-full eb:rounded-md eb:bg-surface-75 eb:pr-14 eb:pl-7 eb:text-[13px] eb:text-foreground eb:outline-none eb:placeholder:text-foreground-muted eb:focus-visible:ring-2 eb:focus-visible:ring-ring/40"
|
|
1285
1244
|
}
|
|
1286
|
-
)
|
|
1245
|
+
),
|
|
1246
|
+
/* @__PURE__ */ jsx3(SearchKeyHint, { className: "eb:absolute eb:top-1/2 eb:right-4 eb:-translate-y-1/2" })
|
|
1287
1247
|
] }),
|
|
1288
1248
|
/* @__PURE__ */ jsxs3("div", { ref: listRef, role: "listbox", "aria-label": t("Variables"), className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:p-1", children: [
|
|
1289
1249
|
ordered.length === 0 && /* @__PURE__ */ jsxs3("div", { className: "eb:px-2 eb:py-3 eb:text-center eb:text-[12px] eb:text-foreground-muted", children: [
|
|
@@ -1694,222 +1654,15 @@ var templatesFeature = {
|
|
|
1694
1654
|
isAvailable: (context) => Array.isArray(context.templates) && context.templates.length > 0
|
|
1695
1655
|
};
|
|
1696
1656
|
|
|
1697
|
-
// src/features/generate/GenerateButton.jsx
|
|
1698
|
-
import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef2, useState as useState7 } from "react";
|
|
1699
|
-
import { Check, Loader2 as Loader23, Search as Search2, Sparkles, TriangleAlert } from "lucide-react";
|
|
1700
|
-
|
|
1701
|
-
// src/features/generate/chip.js
|
|
1702
|
-
var chipClassName = (selected = false) => cn(
|
|
1703
|
-
"eb:inline-flex eb:h-7 eb:items-center eb:gap-1 eb:rounded-full eb:border eb:px-3 eb:text-[12px] eb:font-medium eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50 eb:disabled:opacity-50",
|
|
1704
|
-
selected ? "eb:border-primary eb:bg-primary eb:text-primary-foreground" : "eb:border-border eb:bg-background eb:text-foreground eb:hover:border-primary/50 eb:hover:bg-primary/5"
|
|
1705
|
-
);
|
|
1706
|
-
|
|
1707
|
-
// src/features/generate/GenerateButton.jsx
|
|
1708
|
-
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1709
|
-
var fold = (text) => text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
1710
|
-
function GenerateButton({ context }) {
|
|
1711
|
-
const { editor, ready, storage, isDirty, markDirty, template, setMeta, variables, sender, brand } = context;
|
|
1712
|
-
const { t, locale, notify } = useUi();
|
|
1713
|
-
const [open, setOpen] = useState7(false);
|
|
1714
|
-
const [purpose, setPurpose] = useState7(
|
|
1715
|
-
/** @type {string|null} */
|
|
1716
|
-
null
|
|
1717
|
-
);
|
|
1718
|
-
const [details, setDetails] = useState7("");
|
|
1719
|
-
const [query, setQuery] = useState7("");
|
|
1720
|
-
const [busy, setBusy] = useState7(false);
|
|
1721
|
-
const [error, setError] = useState7("");
|
|
1722
|
-
const [seconds, setSeconds] = useState7(0);
|
|
1723
|
-
const [dirty, setDirty] = useState7(false);
|
|
1724
|
-
const abortRef = useRef2(
|
|
1725
|
-
/** @type {AbortController|null} */
|
|
1726
|
-
null
|
|
1727
|
-
);
|
|
1728
|
-
const searchRef = useRef2(
|
|
1729
|
-
/** @type {HTMLInputElement|null} */
|
|
1730
|
-
null
|
|
1731
|
-
);
|
|
1732
|
-
useEffect4(() => () => abortRef.current?.abort(), []);
|
|
1733
|
-
useEffect4(() => {
|
|
1734
|
-
if (!busy) return void 0;
|
|
1735
|
-
setSeconds(0);
|
|
1736
|
-
const timer = setInterval(() => setSeconds((s) => s + 1), 1e3);
|
|
1737
|
-
return () => clearInterval(timer);
|
|
1738
|
-
}, [busy]);
|
|
1739
|
-
const groups = useMemo4(() => {
|
|
1740
|
-
const words = fold(query.trim()).split(/\s+/).filter(Boolean);
|
|
1741
|
-
return purposesByCategory().map((group) => ({
|
|
1742
|
-
...group,
|
|
1743
|
-
purposes: group.purposes.filter((entry) => {
|
|
1744
|
-
const label = fold(`${t(entry.label)} ${entry.label} ${t(group.label)}`);
|
|
1745
|
-
return words.every((word) => label.includes(word));
|
|
1746
|
-
})
|
|
1747
|
-
})).filter((group) => group.purposes.length > 0);
|
|
1748
|
-
}, [query, t]);
|
|
1749
|
-
const openDialog = () => {
|
|
1750
|
-
setError("");
|
|
1751
|
-
setDirty(isDirty());
|
|
1752
|
-
setOpen(true);
|
|
1753
|
-
};
|
|
1754
|
-
const close = (next) => {
|
|
1755
|
-
if (next) return;
|
|
1756
|
-
abortRef.current?.abort();
|
|
1757
|
-
setOpen(false);
|
|
1758
|
-
};
|
|
1759
|
-
const generate = async () => {
|
|
1760
|
-
if (busy || !purpose && !details.trim()) return;
|
|
1761
|
-
setError("");
|
|
1762
|
-
setBusy(true);
|
|
1763
|
-
const controller = new AbortController();
|
|
1764
|
-
abortRef.current = controller;
|
|
1765
|
-
try {
|
|
1766
|
-
const email = await storage.generate(
|
|
1767
|
-
{
|
|
1768
|
-
purpose,
|
|
1769
|
-
details: details.trim(),
|
|
1770
|
-
language: String(locale || "en").slice(0, 2),
|
|
1771
|
-
sender: sender || {},
|
|
1772
|
-
...hasBrand(brand) ? { brand: { primary: brand.primary, heading: brand.heading, font: brand.font } } : {},
|
|
1773
|
-
variables: (variables || []).map((variable) => ({ key: variable.key, label: variable.label }))
|
|
1774
|
-
},
|
|
1775
|
-
{ signal: controller.signal }
|
|
1776
|
-
);
|
|
1777
|
-
if (controller.signal.aborted) return;
|
|
1778
|
-
editor.loadMjml(hasBrand(brand) ? brandTemplate(email.mjml, brand, { sender, t }) : email.mjml);
|
|
1779
|
-
setMeta((meta) => ({
|
|
1780
|
-
...meta,
|
|
1781
|
-
name: meta.name || email.name,
|
|
1782
|
-
subject: email.subject || meta.subject,
|
|
1783
|
-
preheader: email.preheader || meta.preheader
|
|
1784
|
-
}));
|
|
1785
|
-
if (template.id) markDirty();
|
|
1786
|
-
setOpen(false);
|
|
1787
|
-
notify("success", t("Your email is ready. Edit anything, then save."));
|
|
1788
|
-
} catch (err) {
|
|
1789
|
-
if (!controller.signal.aborted) setError(t(err?.message || "The email could not be generated."));
|
|
1790
|
-
} finally {
|
|
1791
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
1792
|
-
setBusy(false);
|
|
1793
|
-
}
|
|
1794
|
-
};
|
|
1795
|
-
const cancel = () => {
|
|
1796
|
-
abortRef.current?.abort();
|
|
1797
|
-
setBusy(false);
|
|
1798
|
-
};
|
|
1799
|
-
const canGenerate = Boolean(purpose || details.trim());
|
|
1800
|
-
return /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
1801
|
-
/* @__PURE__ */ jsxs7(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, children: [
|
|
1802
|
-
/* @__PURE__ */ jsx7(Sparkles, {}),
|
|
1803
|
-
t("Generate")
|
|
1804
|
-
] }),
|
|
1805
|
-
/* @__PURE__ */ jsx7(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs7(
|
|
1806
|
-
DialogContent,
|
|
1807
|
-
{
|
|
1808
|
-
className: "eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl",
|
|
1809
|
-
"data-testid": "eb-generate",
|
|
1810
|
-
onOpenAutoFocus: (event) => {
|
|
1811
|
-
event.preventDefault();
|
|
1812
|
-
searchRef.current?.focus();
|
|
1813
|
-
},
|
|
1814
|
-
children: [
|
|
1815
|
-
/* @__PURE__ */ jsxs7(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-3", children: [
|
|
1816
|
-
/* @__PURE__ */ jsx7(DialogTitle, { children: t("Generate an email") }),
|
|
1817
|
-
/* @__PURE__ */ jsx7(DialogDescription, { children: t("Pick what it is for. AI writes a complete email that opens here, ready to edit.") })
|
|
1818
|
-
] }),
|
|
1819
|
-
/* @__PURE__ */ jsx7("div", { className: "eb:px-6 eb:pb-3", children: /* @__PURE__ */ jsxs7("div", { className: "eb:relative", children: [
|
|
1820
|
-
/* @__PURE__ */ jsx7(Search2, { className: "eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted", "aria-hidden": "true" }),
|
|
1821
|
-
/* @__PURE__ */ jsx7(
|
|
1822
|
-
Input,
|
|
1823
|
-
{
|
|
1824
|
-
ref: searchRef,
|
|
1825
|
-
type: "search",
|
|
1826
|
-
value: query,
|
|
1827
|
-
onChange: (event) => setQuery(event.target.value),
|
|
1828
|
-
placeholder: t("Search: sale, password, birthday\u2026"),
|
|
1829
|
-
"aria-label": t("Search"),
|
|
1830
|
-
className: "eb:pl-8",
|
|
1831
|
-
disabled: busy
|
|
1832
|
-
}
|
|
1833
|
-
)
|
|
1834
|
-
] }) }),
|
|
1835
|
-
/* @__PURE__ */ jsx7("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-4", "data-testid": "eb-generate-purposes", children: /* @__PURE__ */ jsxs7("fieldset", { disabled: busy, className: "eb:m-0 eb:min-w-0 eb:border-0 eb:p-0", children: [
|
|
1836
|
-
/* @__PURE__ */ jsx7("legend", { className: "eb:sr-only", children: t("What is the email for?") }),
|
|
1837
|
-
groups.length === 0 && /* @__PURE__ */ jsx7("p", { className: "eb:py-6 eb:text-center eb:text-[13px] eb:text-foreground-muted", children: t("Nothing matches. Describe your email in the details below.") }),
|
|
1838
|
-
/* @__PURE__ */ jsx7("div", { className: "eb:flex eb:flex-col eb:gap-4", children: groups.map((group) => /* @__PURE__ */ jsxs7("section", { className: "eb:flex eb:flex-col eb:gap-2", "aria-label": t(group.label), children: [
|
|
1839
|
-
/* @__PURE__ */ jsx7(SectionLabel, { children: t(group.label) }),
|
|
1840
|
-
/* @__PURE__ */ jsx7("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: group.purposes.map((entry) => {
|
|
1841
|
-
const selected = purpose === entry.id;
|
|
1842
|
-
return /* @__PURE__ */ jsxs7(
|
|
1843
|
-
"button",
|
|
1844
|
-
{
|
|
1845
|
-
type: "button",
|
|
1846
|
-
"aria-pressed": selected,
|
|
1847
|
-
"data-purpose": entry.id,
|
|
1848
|
-
onClick: () => setPurpose(selected ? null : entry.id),
|
|
1849
|
-
className: chipClassName(selected),
|
|
1850
|
-
children: [
|
|
1851
|
-
selected && /* @__PURE__ */ jsx7(Check, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
1852
|
-
t(entry.label)
|
|
1853
|
-
]
|
|
1854
|
-
},
|
|
1855
|
-
entry.id
|
|
1856
|
-
);
|
|
1857
|
-
}) })
|
|
1858
|
-
] }, group.id)) })
|
|
1859
|
-
] }) }),
|
|
1860
|
-
/* @__PURE__ */ jsxs7("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
|
|
1861
|
-
/* @__PURE__ */ jsx7(
|
|
1862
|
-
Textarea,
|
|
1863
|
-
{
|
|
1864
|
-
value: details,
|
|
1865
|
-
onChange: (event) => setDetails(event.target.value.slice(0, MAX_DETAILS_LENGTH)),
|
|
1866
|
-
placeholder: t("Details (optional): product, dates, offer, tone, language\u2026"),
|
|
1867
|
-
"aria-label": t("Details (optional)"),
|
|
1868
|
-
rows: 2,
|
|
1869
|
-
className: "eb:min-h-14",
|
|
1870
|
-
disabled: busy,
|
|
1871
|
-
onKeyDown: (event) => {
|
|
1872
|
-
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) generate();
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
),
|
|
1876
|
-
error ? /* @__PURE__ */ jsxs7("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
1877
|
-
/* @__PURE__ */ jsx7(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
1878
|
-
error
|
|
1879
|
-
] }) : dirty && !busy && /* @__PURE__ */ jsx7("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("The email being edited will be replaced, unsaved changes included.") }),
|
|
1880
|
-
/* @__PURE__ */ jsxs7("div", { className: "eb:flex eb:items-center eb:justify-end eb:gap-2", children: [
|
|
1881
|
-
busy && /* @__PURE__ */ jsx7("p", { className: "eb:mr-auto eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: t("Writing your email\u2026 {seconds} s", { seconds }) }),
|
|
1882
|
-
busy && /* @__PURE__ */ jsx7(Button, { variant: "outline", size: "sm", onClick: cancel, children: t("Cancel") }),
|
|
1883
|
-
/* @__PURE__ */ jsxs7(Button, { size: "sm", onClick: generate, disabled: !canGenerate || busy, "data-testid": "eb-generate-submit", children: [
|
|
1884
|
-
busy ? /* @__PURE__ */ jsx7(Loader23, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx7(Sparkles, {}),
|
|
1885
|
-
t("Generate")
|
|
1886
|
-
] })
|
|
1887
|
-
] })
|
|
1888
|
-
] })
|
|
1889
|
-
]
|
|
1890
|
-
}
|
|
1891
|
-
) })
|
|
1892
|
-
] });
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
// src/features/generate/index.js
|
|
1896
|
-
var generateFeature = {
|
|
1897
|
-
id: "generate",
|
|
1898
|
-
label: "Generate",
|
|
1899
|
-
order: 4,
|
|
1900
|
-
toolbar: GenerateButton,
|
|
1901
|
-
isAvailable: (context) => typeof context.storage?.generate === "function"
|
|
1902
|
-
};
|
|
1903
|
-
|
|
1904
1657
|
// src/features/brand/BrandButton.jsx
|
|
1905
|
-
import { useEffect as
|
|
1906
|
-
import { Globe, ImageOff, Loader2 as
|
|
1658
|
+
import { useEffect as useEffect4, useRef as useRef2, useState as useState8 } from "react";
|
|
1659
|
+
import { Globe, ImageOff, Loader2 as Loader23, Palette, Trash2 as Trash22, TriangleAlert, Upload, WandSparkles } from "lucide-react";
|
|
1907
1660
|
|
|
1908
1661
|
// src/ui/primitives/color-picker.jsx
|
|
1909
|
-
import { useState as
|
|
1662
|
+
import { useState as useState7 } from "react";
|
|
1910
1663
|
import { HexAlphaColorPicker } from "react-colorful";
|
|
1911
|
-
import { Check
|
|
1912
|
-
import { jsx as
|
|
1664
|
+
import { Check } from "lucide-react";
|
|
1665
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1913
1666
|
var PRESET_COLORS = [
|
|
1914
1667
|
"#8B5CF6",
|
|
1915
1668
|
"#A78BFA",
|
|
@@ -1940,20 +1693,20 @@ function normalizeHex(v) {
|
|
|
1940
1693
|
}
|
|
1941
1694
|
function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
|
|
1942
1695
|
const t = useT();
|
|
1943
|
-
const [open, setOpen] =
|
|
1696
|
+
const [open, setOpen] = useState7(false);
|
|
1944
1697
|
const valid = isValidHex(value);
|
|
1945
1698
|
const normalized = valid ? normalizeHex(value) : "";
|
|
1946
1699
|
const emit = (hex) => onChange(normalizeHex(hex));
|
|
1947
|
-
return /* @__PURE__ */
|
|
1948
|
-
/* @__PURE__ */
|
|
1949
|
-
/* @__PURE__ */
|
|
1700
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("eb:flex eb:gap-2", className), children: [
|
|
1701
|
+
/* @__PURE__ */ jsxs7(Popover, { open, onOpenChange: setOpen, children: [
|
|
1702
|
+
/* @__PURE__ */ jsx7(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
|
|
1950
1703
|
"button",
|
|
1951
1704
|
{
|
|
1952
1705
|
type: "button",
|
|
1953
1706
|
"aria-label": t("Pick a colour"),
|
|
1954
1707
|
title: t("Pick a colour"),
|
|
1955
1708
|
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]",
|
|
1956
|
-
children: /* @__PURE__ */
|
|
1709
|
+
children: /* @__PURE__ */ jsx7(
|
|
1957
1710
|
"span",
|
|
1958
1711
|
{
|
|
1959
1712
|
className: "eb:absolute eb:inset-0",
|
|
@@ -1963,18 +1716,18 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
|
|
|
1963
1716
|
)
|
|
1964
1717
|
}
|
|
1965
1718
|
) }),
|
|
1966
|
-
/* @__PURE__ */
|
|
1967
|
-
/* @__PURE__ */
|
|
1968
|
-
/* @__PURE__ */
|
|
1969
|
-
/* @__PURE__ */
|
|
1719
|
+
/* @__PURE__ */ jsxs7(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
|
|
1720
|
+
/* @__PURE__ */ jsx7("div", { className: "eb-picker", children: /* @__PURE__ */ jsx7(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
|
|
1721
|
+
/* @__PURE__ */ jsxs7("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
|
|
1722
|
+
/* @__PURE__ */ jsx7(
|
|
1970
1723
|
"span",
|
|
1971
1724
|
{
|
|
1972
1725
|
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",
|
|
1973
1726
|
"aria-hidden": true,
|
|
1974
|
-
children: /* @__PURE__ */
|
|
1727
|
+
children: /* @__PURE__ */ jsx7("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
|
|
1975
1728
|
}
|
|
1976
1729
|
),
|
|
1977
|
-
/* @__PURE__ */
|
|
1730
|
+
/* @__PURE__ */ jsx7(
|
|
1978
1731
|
Input,
|
|
1979
1732
|
{
|
|
1980
1733
|
value: value || "",
|
|
@@ -1986,7 +1739,7 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
|
|
|
1986
1739
|
}
|
|
1987
1740
|
)
|
|
1988
1741
|
] }),
|
|
1989
|
-
/* @__PURE__ */
|
|
1742
|
+
/* @__PURE__ */ jsx7("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__ */ jsx7(
|
|
1990
1743
|
"button",
|
|
1991
1744
|
{
|
|
1992
1745
|
type: "button",
|
|
@@ -1995,13 +1748,13 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
|
|
|
1995
1748
|
"aria-label": c,
|
|
1996
1749
|
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",
|
|
1997
1750
|
style: { backgroundColor: c },
|
|
1998
|
-
children: normalized === c && /* @__PURE__ */
|
|
1751
|
+
children: normalized === c && /* @__PURE__ */ jsx7(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)]" })
|
|
1999
1752
|
},
|
|
2000
1753
|
c
|
|
2001
1754
|
)) })
|
|
2002
1755
|
] })
|
|
2003
1756
|
] }),
|
|
2004
|
-
/* @__PURE__ */
|
|
1757
|
+
/* @__PURE__ */ jsx7(
|
|
2005
1758
|
Input,
|
|
2006
1759
|
{
|
|
2007
1760
|
id,
|
|
@@ -2016,7 +1769,7 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
|
|
|
2016
1769
|
}
|
|
2017
1770
|
|
|
2018
1771
|
// src/features/brand/BrandButton.jsx
|
|
2019
|
-
import { Fragment as
|
|
1772
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2020
1773
|
var toForm = (brand) => ({
|
|
2021
1774
|
name: brand.name || "",
|
|
2022
1775
|
logoUrl: brand.logoUrl || "",
|
|
@@ -2030,31 +1783,31 @@ var isLogoUrl = (url) => !url || url.startsWith("/") || /^https:\/\//i.test(url)
|
|
|
2030
1783
|
function BrandButton({ context }) {
|
|
2031
1784
|
const { editor, ready, storage, brand, setBrand, hostSender } = context;
|
|
2032
1785
|
const { t, notify } = useUi();
|
|
2033
|
-
const [open, setOpen] =
|
|
2034
|
-
const [form, setForm] =
|
|
2035
|
-
const [applyNow, setApplyNow] =
|
|
2036
|
-
const [reading, setReading] =
|
|
2037
|
-
const [readError, setReadError] =
|
|
2038
|
-
const [notes, setNotes] =
|
|
1786
|
+
const [open, setOpen] = useState8(false);
|
|
1787
|
+
const [form, setForm] = useState8(() => toForm(brand || {}));
|
|
1788
|
+
const [applyNow, setApplyNow] = useState8(true);
|
|
1789
|
+
const [reading, setReading] = useState8(false);
|
|
1790
|
+
const [readError, setReadError] = useState8("");
|
|
1791
|
+
const [notes, setNotes] = useState8(
|
|
2039
1792
|
/** @type {string[]} */
|
|
2040
1793
|
[]
|
|
2041
1794
|
);
|
|
2042
|
-
const [uploading, setUploading] =
|
|
2043
|
-
const [saving, setSaving] =
|
|
2044
|
-
const [error, setError] =
|
|
2045
|
-
const abortRef =
|
|
1795
|
+
const [uploading, setUploading] = useState8(false);
|
|
1796
|
+
const [saving, setSaving] = useState8(false);
|
|
1797
|
+
const [error, setError] = useState8("");
|
|
1798
|
+
const abortRef = useRef2(
|
|
2046
1799
|
/** @type {AbortController|null} */
|
|
2047
1800
|
null
|
|
2048
1801
|
);
|
|
2049
|
-
const fileRef =
|
|
1802
|
+
const fileRef = useRef2(
|
|
2050
1803
|
/** @type {HTMLInputElement|null} */
|
|
2051
1804
|
null
|
|
2052
1805
|
);
|
|
2053
1806
|
const canRead = typeof storage.importBrand === "function";
|
|
2054
1807
|
const canUpload = typeof storage.uploadImage === "function";
|
|
2055
|
-
|
|
1808
|
+
useEffect4(() => () => abortRef.current?.abort(), []);
|
|
2056
1809
|
const logoUrl = form.logoUrl;
|
|
2057
|
-
|
|
1810
|
+
useEffect4(() => {
|
|
2058
1811
|
if (!logoUrl || !isLogoUrl(logoUrl)) return void 0;
|
|
2059
1812
|
let live = true;
|
|
2060
1813
|
const image = new Image();
|
|
@@ -2163,11 +1916,11 @@ function BrandButton({ context }) {
|
|
|
2163
1916
|
}
|
|
2164
1917
|
};
|
|
2165
1918
|
const swatch = brand?.primary;
|
|
2166
|
-
return /* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */
|
|
2168
|
-
/* @__PURE__ */
|
|
1919
|
+
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1920
|
+
/* @__PURE__ */ jsxs8(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, "data-testid": "eb-brand-button", children: [
|
|
1921
|
+
/* @__PURE__ */ jsx8(Palette, {}),
|
|
2169
1922
|
t("Brand"),
|
|
2170
|
-
swatch && /* @__PURE__ */
|
|
1923
|
+
swatch && /* @__PURE__ */ jsx8(
|
|
2171
1924
|
"span",
|
|
2172
1925
|
{
|
|
2173
1926
|
"aria-hidden": "true",
|
|
@@ -2176,18 +1929,18 @@ function BrandButton({ context }) {
|
|
|
2176
1929
|
}
|
|
2177
1930
|
)
|
|
2178
1931
|
] }),
|
|
2179
|
-
/* @__PURE__ */
|
|
1932
|
+
/* @__PURE__ */ jsx8(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs8(
|
|
2180
1933
|
DialogContent,
|
|
2181
1934
|
{
|
|
2182
1935
|
className: "eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl",
|
|
2183
1936
|
"data-testid": "eb-brand",
|
|
2184
1937
|
children: [
|
|
2185
|
-
/* @__PURE__ */
|
|
2186
|
-
/* @__PURE__ */
|
|
2187
|
-
/* @__PURE__ */
|
|
1938
|
+
/* @__PURE__ */ jsxs8(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-4", children: [
|
|
1939
|
+
/* @__PURE__ */ jsx8(DialogTitle, { children: t("Your brand") }),
|
|
1940
|
+
/* @__PURE__ */ jsx8(DialogDescription, { children: t("Set it once: new emails, templates and blocks take your logo, colors and font.") })
|
|
2188
1941
|
] }),
|
|
2189
|
-
/* @__PURE__ */
|
|
2190
|
-
canRead && /* @__PURE__ */
|
|
1942
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-5", children: [
|
|
1943
|
+
canRead && /* @__PURE__ */ jsxs8(
|
|
2191
1944
|
"form",
|
|
2192
1945
|
{
|
|
2193
1946
|
className: "eb:mb-5 eb:flex eb:flex-col eb:gap-1.5",
|
|
@@ -2196,11 +1949,11 @@ function BrandButton({ context }) {
|
|
|
2196
1949
|
readWebsite();
|
|
2197
1950
|
},
|
|
2198
1951
|
children: [
|
|
2199
|
-
/* @__PURE__ */
|
|
2200
|
-
/* @__PURE__ */
|
|
2201
|
-
/* @__PURE__ */
|
|
2202
|
-
/* @__PURE__ */
|
|
2203
|
-
/* @__PURE__ */
|
|
1952
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-website", children: t("Your website") }),
|
|
1953
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-2 eb:sm:flex-row", children: [
|
|
1954
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:relative eb:min-w-0 eb:flex-1", children: [
|
|
1955
|
+
/* @__PURE__ */ jsx8(Globe, { className: "eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted", "aria-hidden": "true" }),
|
|
1956
|
+
/* @__PURE__ */ jsx8(
|
|
2204
1957
|
Input,
|
|
2205
1958
|
{
|
|
2206
1959
|
id: "eb-brand-website",
|
|
@@ -2214,28 +1967,28 @@ function BrandButton({ context }) {
|
|
|
2214
1967
|
}
|
|
2215
1968
|
)
|
|
2216
1969
|
] }),
|
|
2217
|
-
/* @__PURE__ */
|
|
2218
|
-
reading ? /* @__PURE__ */
|
|
1970
|
+
/* @__PURE__ */ jsxs8(Button, { type: "submit", size: "sm", disabled: reading || !form.website.trim(), children: [
|
|
1971
|
+
reading ? /* @__PURE__ */ jsx8(Loader23, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx8(WandSparkles, {}),
|
|
2219
1972
|
t("Get my brand")
|
|
2220
1973
|
] })
|
|
2221
1974
|
] }),
|
|
2222
|
-
reading ? /* @__PURE__ */
|
|
2223
|
-
/* @__PURE__ */
|
|
1975
|
+
reading ? /* @__PURE__ */ jsx8("p", { role: "status", className: "eb:text-[12px] eb:text-foreground-muted", children: t("Reading the website\u2026") }) : readError ? /* @__PURE__ */ jsxs8("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
1976
|
+
/* @__PURE__ */ jsx8(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
2224
1977
|
readError
|
|
2225
|
-
] }) : notes.length ? /* @__PURE__ */
|
|
1978
|
+
] }) : notes.length ? /* @__PURE__ */ jsx8("ul", { className: "eb:flex eb:flex-col eb:gap-1 eb:text-[12px] eb:text-foreground-muted", "data-testid": "eb-brand-notes", children: notes.map((note) => /* @__PURE__ */ jsx8("li", { children: t(BRAND_NOTES[note]) }, note)) }) : /* @__PURE__ */ jsx8("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("The logo, colors and font are read from it. You can change them before saving.") })
|
|
2226
1979
|
]
|
|
2227
1980
|
}
|
|
2228
1981
|
),
|
|
2229
|
-
/* @__PURE__ */
|
|
2230
|
-
/* @__PURE__ */
|
|
2231
|
-
/* @__PURE__ */
|
|
2232
|
-
/* @__PURE__ */
|
|
2233
|
-
/* @__PURE__ */
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
/* @__PURE__ */
|
|
2236
|
-
/* @__PURE__ */
|
|
2237
|
-
canUpload && /* @__PURE__ */
|
|
2238
|
-
/* @__PURE__ */
|
|
1982
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:grid eb:gap-6 eb:sm:grid-cols-[minmax(0,1fr)_220px]", children: [
|
|
1983
|
+
/* @__PURE__ */ jsxs8("fieldset", { disabled: reading, className: "eb:m-0 eb:flex eb:min-w-0 eb:flex-col eb:gap-4 eb:border-0 eb:p-0", children: [
|
|
1984
|
+
/* @__PURE__ */ jsx8("legend", { className: "eb:sr-only", children: t("Your brand") }),
|
|
1985
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
1986
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-logo", children: t("Logo") }),
|
|
1987
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:items-center eb:gap-3", children: [
|
|
1988
|
+
/* @__PURE__ */ jsx8("div", { className: "eb-checker eb:flex eb:h-12 eb:w-24 eb:shrink-0 eb:items-center eb:justify-center eb:overflow-hidden eb:rounded-lg2 eb:ring-1 eb:ring-border eb:ring-inset", children: form.logoUrl && !logoInvalid ? /* @__PURE__ */ jsx8("img", { src: form.logoUrl, alt: t("Logo"), className: "eb:max-h-10 eb:max-w-20 eb:object-contain" }) : /* @__PURE__ */ jsx8(ImageOff, { className: "eb:size-4 eb:text-foreground-muted", "aria-hidden": "true" }) }),
|
|
1989
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-wrap eb:gap-2", children: [
|
|
1990
|
+
canUpload && /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1991
|
+
/* @__PURE__ */ jsx8(
|
|
2239
1992
|
"input",
|
|
2240
1993
|
{
|
|
2241
1994
|
ref: fileRef,
|
|
@@ -2248,18 +2001,18 @@ function BrandButton({ context }) {
|
|
|
2248
2001
|
onChange: pickLogo
|
|
2249
2002
|
}
|
|
2250
2003
|
),
|
|
2251
|
-
/* @__PURE__ */
|
|
2252
|
-
uploading ? /* @__PURE__ */
|
|
2004
|
+
/* @__PURE__ */ jsxs8(Button, { type: "button", variant: "outline", size: "sm", disabled: uploading, onClick: () => fileRef.current?.click(), children: [
|
|
2005
|
+
uploading ? /* @__PURE__ */ jsx8(Loader23, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx8(Upload, {}),
|
|
2253
2006
|
t("Upload")
|
|
2254
2007
|
] })
|
|
2255
2008
|
] }),
|
|
2256
|
-
form.logoUrl && /* @__PURE__ */
|
|
2257
|
-
/* @__PURE__ */
|
|
2009
|
+
form.logoUrl && /* @__PURE__ */ jsxs8(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => set({ logoUrl: "", logoWidth: 0 }), children: [
|
|
2010
|
+
/* @__PURE__ */ jsx8(Trash22, {}),
|
|
2258
2011
|
t("Remove")
|
|
2259
2012
|
] })
|
|
2260
2013
|
] })
|
|
2261
2014
|
] }),
|
|
2262
|
-
/* @__PURE__ */
|
|
2015
|
+
/* @__PURE__ */ jsx8(
|
|
2263
2016
|
Input,
|
|
2264
2017
|
{
|
|
2265
2018
|
id: "eb-brand-logo",
|
|
@@ -2270,11 +2023,11 @@ function BrandButton({ context }) {
|
|
|
2270
2023
|
className: "eb:text-[12px]"
|
|
2271
2024
|
}
|
|
2272
2025
|
),
|
|
2273
|
-
logoInvalid && /* @__PURE__ */
|
|
2026
|
+
logoInvalid && /* @__PURE__ */ jsx8("p", { className: "eb:text-[12px] eb:text-destructive", children: t("An https:// address is required") })
|
|
2274
2027
|
] }),
|
|
2275
|
-
/* @__PURE__ */
|
|
2276
|
-
/* @__PURE__ */
|
|
2277
|
-
/* @__PURE__ */
|
|
2028
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
2029
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-name", children: t("Company name") }),
|
|
2030
|
+
/* @__PURE__ */ jsx8(
|
|
2278
2031
|
Input,
|
|
2279
2032
|
{
|
|
2280
2033
|
id: "eb-brand-name",
|
|
@@ -2285,36 +2038,36 @@ function BrandButton({ context }) {
|
|
|
2285
2038
|
}
|
|
2286
2039
|
)
|
|
2287
2040
|
] }),
|
|
2288
|
-
/* @__PURE__ */
|
|
2289
|
-
/* @__PURE__ */
|
|
2290
|
-
/* @__PURE__ */
|
|
2291
|
-
/* @__PURE__ */
|
|
2292
|
-
/* @__PURE__ */
|
|
2041
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:grid eb:gap-4 eb:sm:grid-cols-2", children: [
|
|
2042
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
2043
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-primary", children: t("Main color") }),
|
|
2044
|
+
/* @__PURE__ */ jsx8(ColorPicker, { id: "eb-brand-primary", value: form.primary, onChange: (primary) => set({ primary }), placeholder: PALETTE.navy }),
|
|
2045
|
+
/* @__PURE__ */ jsx8("p", { className: "eb:text-[11px] eb:text-foreground-muted", children: t("Buttons, banners, links") })
|
|
2293
2046
|
] }),
|
|
2294
|
-
/* @__PURE__ */
|
|
2295
|
-
/* @__PURE__ */
|
|
2296
|
-
/* @__PURE__ */
|
|
2047
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
2048
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-heading", children: t("Headings color") }),
|
|
2049
|
+
/* @__PURE__ */ jsx8(ColorPicker, { id: "eb-brand-heading", value: form.heading, onChange: (heading) => set({ heading }), placeholder: PALETTE.navy })
|
|
2297
2050
|
] })
|
|
2298
2051
|
] }),
|
|
2299
|
-
/* @__PURE__ */
|
|
2300
|
-
/* @__PURE__ */
|
|
2301
|
-
/* @__PURE__ */
|
|
2302
|
-
/* @__PURE__ */
|
|
2303
|
-
/* @__PURE__ */
|
|
2052
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
2053
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-font", children: t("Font") }),
|
|
2054
|
+
/* @__PURE__ */ jsxs8(Select, { value: form.font || FONT_STACK, onValueChange: (font) => set({ font }), children: [
|
|
2055
|
+
/* @__PURE__ */ jsx8(SelectTrigger, { id: "eb-brand-font", children: /* @__PURE__ */ jsx8(SelectValue, {}) }),
|
|
2056
|
+
/* @__PURE__ */ jsx8(SelectContent, { children: FONT_OPTIONS.map((option) => /* @__PURE__ */ jsx8(SelectItem, { value: option.value, children: /* @__PURE__ */ jsx8("span", { style: { fontFamily: option.value }, children: option.label }) }, option.value)) })
|
|
2304
2057
|
] })
|
|
2305
2058
|
] })
|
|
2306
2059
|
] }),
|
|
2307
|
-
/* @__PURE__ */
|
|
2060
|
+
/* @__PURE__ */ jsx8(BrandPreview, { brand: draft, fallbackName: hostSender?.name || t("Your company") })
|
|
2308
2061
|
] })
|
|
2309
2062
|
] }),
|
|
2310
|
-
/* @__PURE__ */
|
|
2311
|
-
error && /* @__PURE__ */
|
|
2312
|
-
/* @__PURE__ */
|
|
2063
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
|
|
2064
|
+
error && /* @__PURE__ */ jsxs8("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
2065
|
+
/* @__PURE__ */ jsx8(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
2313
2066
|
error
|
|
2314
2067
|
] }),
|
|
2315
|
-
/* @__PURE__ */
|
|
2316
|
-
/* @__PURE__ */
|
|
2317
|
-
/* @__PURE__ */
|
|
2068
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:sm:flex-row eb:sm:items-center", children: [
|
|
2069
|
+
/* @__PURE__ */ jsxs8("label", { className: "eb:flex eb:cursor-pointer eb:items-center eb:gap-2 eb:text-[13px] eb:sm:mr-auto", children: [
|
|
2070
|
+
/* @__PURE__ */ jsx8(
|
|
2318
2071
|
"input",
|
|
2319
2072
|
{
|
|
2320
2073
|
type: "checkbox",
|
|
@@ -2326,10 +2079,10 @@ function BrandButton({ context }) {
|
|
|
2326
2079
|
),
|
|
2327
2080
|
t("Also apply to this email")
|
|
2328
2081
|
] }),
|
|
2329
|
-
/* @__PURE__ */
|
|
2330
|
-
/* @__PURE__ */
|
|
2331
|
-
/* @__PURE__ */
|
|
2332
|
-
saving && /* @__PURE__ */
|
|
2082
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
|
|
2083
|
+
/* @__PURE__ */ jsx8(Button, { variant: "outline", size: "sm", onClick: () => close(false), children: t("Cancel") }),
|
|
2084
|
+
/* @__PURE__ */ jsxs8(Button, { size: "sm", onClick: save, disabled: saving || reading || uploading || logoInvalid, "data-testid": "eb-brand-save", children: [
|
|
2085
|
+
saving && /* @__PURE__ */ jsx8(Loader23, { className: "eb:animate-spin" }),
|
|
2333
2086
|
t("Save")
|
|
2334
2087
|
] })
|
|
2335
2088
|
] })
|
|
@@ -2348,30 +2101,30 @@ function BrandPreview({ brand, fallbackName }) {
|
|
|
2348
2101
|
const link2 = contrastRatio(primary, PALETTE.white) >= 3 ? primary : heading;
|
|
2349
2102
|
const font = brand.font || FONT_STACK;
|
|
2350
2103
|
const name = brand.name || fallbackName;
|
|
2351
|
-
return /* @__PURE__ */
|
|
2352
|
-
/* @__PURE__ */
|
|
2353
|
-
/* @__PURE__ */
|
|
2104
|
+
return /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
2105
|
+
/* @__PURE__ */ jsx8("span", { className: "eb:text-[12px] eb:font-medium eb:text-foreground-muted", children: t("Preview") }),
|
|
2106
|
+
/* @__PURE__ */ jsx8(
|
|
2354
2107
|
"div",
|
|
2355
2108
|
{
|
|
2356
2109
|
className: "eb:overflow-hidden eb:rounded-lg2 eb:p-2.5 eb:ring-1 eb:ring-border",
|
|
2357
2110
|
style: { backgroundColor: PALETTE.canvas, fontFamily: font },
|
|
2358
2111
|
"data-testid": "eb-brand-preview",
|
|
2359
2112
|
"aria-hidden": "true",
|
|
2360
|
-
children: /* @__PURE__ */
|
|
2361
|
-
/* @__PURE__ */
|
|
2362
|
-
/* @__PURE__ */
|
|
2363
|
-
/* @__PURE__ */
|
|
2364
|
-
/* @__PURE__ */
|
|
2113
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "eb:overflow-hidden eb:rounded-[3px]", style: { backgroundColor: PALETTE.white }, children: [
|
|
2114
|
+
/* @__PURE__ */ jsx8("div", { className: "eb:px-3 eb:pt-3 eb:pb-2", children: brand.logoUrl ? /* @__PURE__ */ jsx8("img", { src: brand.logoUrl, alt: "", style: { width: Math.round((brand.logoWidth || 140) * 0.45), maxWidth: "100%" } }) : /* @__PURE__ */ jsx8("span", { className: "eb:text-[11px] eb:font-bold", style: { color: PALETTE.ink }, children: name }) }),
|
|
2115
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:px-3 eb:py-4 eb:text-center", style: { backgroundColor: primary, color: onPrimary }, children: [
|
|
2116
|
+
/* @__PURE__ */ jsx8("div", { className: "eb:text-[13px] eb:leading-tight eb:font-bold", children: t("Big news") }),
|
|
2117
|
+
/* @__PURE__ */ jsx8("div", { className: "eb:mt-1 eb:text-[9px] eb:opacity-90", children: t("A short line under the title.") })
|
|
2365
2118
|
] }),
|
|
2366
|
-
/* @__PURE__ */
|
|
2367
|
-
/* @__PURE__ */
|
|
2368
|
-
/* @__PURE__ */
|
|
2119
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5 eb:px-3 eb:py-3", children: [
|
|
2120
|
+
/* @__PURE__ */ jsx8("div", { className: "eb:text-[11px] eb:leading-tight eb:font-bold", style: { color: heading }, children: t("A headline") }),
|
|
2121
|
+
/* @__PURE__ */ jsxs8("div", { className: "eb:text-[9px] eb:leading-snug", style: { color: PALETTE.ink }, children: [
|
|
2369
2122
|
t("Some text, with"),
|
|
2370
2123
|
" ",
|
|
2371
|
-
/* @__PURE__ */
|
|
2124
|
+
/* @__PURE__ */ jsx8("span", { className: "eb:underline", style: { color: link2 }, children: t("a link") }),
|
|
2372
2125
|
"."
|
|
2373
2126
|
] }),
|
|
2374
|
-
/* @__PURE__ */
|
|
2127
|
+
/* @__PURE__ */ jsx8(
|
|
2375
2128
|
"span",
|
|
2376
2129
|
{
|
|
2377
2130
|
className: cn("eb:mt-1 eb:self-start eb:rounded-[3px] eb:px-2.5 eb:py-1 eb:text-[9px] eb:font-bold"),
|
|
@@ -2380,7 +2133,7 @@ function BrandPreview({ brand, fallbackName }) {
|
|
|
2380
2133
|
}
|
|
2381
2134
|
)
|
|
2382
2135
|
] }),
|
|
2383
|
-
/* @__PURE__ */
|
|
2136
|
+
/* @__PURE__ */ jsx8("div", { className: "eb:px-3 eb:pb-2.5 eb:text-center eb:text-[7px]", style: { color: "#6d7484" }, children: name })
|
|
2384
2137
|
] })
|
|
2385
2138
|
}
|
|
2386
2139
|
)
|
|
@@ -2398,7 +2151,6 @@ var brandFeature = {
|
|
|
2398
2151
|
|
|
2399
2152
|
// src/features/index.js
|
|
2400
2153
|
var FEATURES = [
|
|
2401
|
-
generateFeature,
|
|
2402
2154
|
templatesFeature,
|
|
2403
2155
|
brandFeature,
|
|
2404
2156
|
variablesFeature,
|
|
@@ -2529,93 +2281,6 @@ function betweenSections(body, point, boxOf, skip, wrap) {
|
|
|
2529
2281
|
};
|
|
2530
2282
|
}
|
|
2531
2283
|
|
|
2532
|
-
// src/engine/richText.js
|
|
2533
|
-
var DROPPED_WITH_CONTENT = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "template", "svg", "math", "head", "title", "meta", "link"]);
|
|
2534
|
-
var KEPT_ATTRIBUTES = {
|
|
2535
|
-
"*": /* @__PURE__ */ new Set(["style"]),
|
|
2536
|
-
a: /* @__PURE__ */ new Set(["href", "target", "title", "rel"]),
|
|
2537
|
-
font: /* @__PURE__ */ new Set(["color", "face", "size"])
|
|
2538
|
-
};
|
|
2539
|
-
var KEPT_STYLES = /* @__PURE__ */ new Set([
|
|
2540
|
-
"color",
|
|
2541
|
-
"background-color",
|
|
2542
|
-
"font-weight",
|
|
2543
|
-
"font-style",
|
|
2544
|
-
"font-size",
|
|
2545
|
-
"font-family",
|
|
2546
|
-
"text-decoration",
|
|
2547
|
-
"text-align",
|
|
2548
|
-
"line-height",
|
|
2549
|
-
"letter-spacing"
|
|
2550
|
-
]);
|
|
2551
|
-
function cleanStyle(style) {
|
|
2552
|
-
return style.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
2553
|
-
const colon = part.indexOf(":");
|
|
2554
|
-
if (colon === -1) return null;
|
|
2555
|
-
const name = part.slice(0, colon).trim().toLowerCase();
|
|
2556
|
-
const value = part.slice(colon + 1).trim();
|
|
2557
|
-
if (!KEPT_STYLES.has(name) || /expression|url\s*\(|javascript:/i.test(value)) return null;
|
|
2558
|
-
return `${name}:${value}`;
|
|
2559
|
-
}).filter(Boolean).join(";");
|
|
2560
|
-
}
|
|
2561
|
-
function cleanAttributes(element) {
|
|
2562
|
-
const tag = element.tagName.toLowerCase();
|
|
2563
|
-
const own = (
|
|
2564
|
-
/** @type {Record<string, Set<string>>} */
|
|
2565
|
-
KEPT_ATTRIBUTES[tag]
|
|
2566
|
-
);
|
|
2567
|
-
for (const attribute of [...element.attributes]) {
|
|
2568
|
-
const name = attribute.name.toLowerCase();
|
|
2569
|
-
if (!KEPT_ATTRIBUTES["*"].has(name) && !(own && own.has(name))) {
|
|
2570
|
-
element.removeAttribute(attribute.name);
|
|
2571
|
-
continue;
|
|
2572
|
-
}
|
|
2573
|
-
if (name === "href" && !isSafeUrl(attribute.value, "link")) element.removeAttribute(attribute.name);
|
|
2574
|
-
if (name === "style") {
|
|
2575
|
-
const style = cleanStyle(attribute.value);
|
|
2576
|
-
if (style) element.setAttribute("style", style);
|
|
2577
|
-
else element.removeAttribute("style");
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
if (tag === "a" && element.getAttribute("target") === "_blank") element.setAttribute("rel", "noopener");
|
|
2581
|
-
}
|
|
2582
|
-
function cleanChildren(node) {
|
|
2583
|
-
for (const child of [...node.childNodes]) {
|
|
2584
|
-
if (child.nodeType === 8) {
|
|
2585
|
-
child.remove();
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2588
|
-
if (child.nodeType !== 1) continue;
|
|
2589
|
-
const element = (
|
|
2590
|
-
/** @type {Element} */
|
|
2591
|
-
child
|
|
2592
|
-
);
|
|
2593
|
-
const tag = element.tagName.toLowerCase();
|
|
2594
|
-
if (DROPPED_WITH_CONTENT.has(tag)) {
|
|
2595
|
-
element.remove();
|
|
2596
|
-
continue;
|
|
2597
|
-
}
|
|
2598
|
-
cleanChildren(element);
|
|
2599
|
-
if (!INLINE_TAGS.has(tag)) {
|
|
2600
|
-
element.replaceWith(...element.childNodes);
|
|
2601
|
-
continue;
|
|
2602
|
-
}
|
|
2603
|
-
cleanAttributes(element);
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
function cleanRichText(html, document2) {
|
|
2607
|
-
const template = document2.createElement("template");
|
|
2608
|
-
template.innerHTML = String(html || "");
|
|
2609
|
-
cleanChildren(template.content);
|
|
2610
|
-
return template.innerHTML.replace(/<br>/g, "<br />").replace(/(<br \/>)+$/, "").trim();
|
|
2611
|
-
}
|
|
2612
|
-
function escapeText(text) {
|
|
2613
|
-
return String(text ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2614
|
-
}
|
|
2615
|
-
function textOfHtml(html) {
|
|
2616
|
-
return String(html || "").replace(/<br\s*\/?>/gi, " ").replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&").trim();
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
2284
|
// src/engine/canvasMjml.js
|
|
2620
2285
|
var MARKED_TAGS = /* @__PURE__ */ new Set([
|
|
2621
2286
|
"mj-body",
|
|
@@ -2737,7 +2402,7 @@ function sameKind(from, to) {
|
|
|
2737
2402
|
);
|
|
2738
2403
|
}
|
|
2739
2404
|
function morphChildren(from, to, skip = () => false) {
|
|
2740
|
-
const
|
|
2405
|
+
const document = (
|
|
2741
2406
|
/** @type {Document} */
|
|
2742
2407
|
from.ownerDocument || from
|
|
2743
2408
|
);
|
|
@@ -2746,9 +2411,9 @@ function morphChildren(from, to, skip = () => false) {
|
|
|
2746
2411
|
const target = targets[i];
|
|
2747
2412
|
const current = from.childNodes[i];
|
|
2748
2413
|
if (!current) {
|
|
2749
|
-
from.appendChild(
|
|
2414
|
+
from.appendChild(document.importNode(target, true));
|
|
2750
2415
|
} else if (!sameKind(current, target)) {
|
|
2751
|
-
from.replaceChild(
|
|
2416
|
+
from.replaceChild(document.importNode(target, true), current);
|
|
2752
2417
|
} else if (current.nodeType === 1) {
|
|
2753
2418
|
const element = (
|
|
2754
2419
|
/** @type {Element} */
|
|
@@ -4063,7 +3728,7 @@ function createNativeAdapter({ loadCompiler = defaultLoadCompiler } = {}) {
|
|
|
4063
3728
|
get root() {
|
|
4064
3729
|
return root;
|
|
4065
3730
|
},
|
|
4066
|
-
async initialize({ container: host, document
|
|
3731
|
+
async initialize({ container: host, document, onEvent, t: translate, labelOf: label }) {
|
|
4067
3732
|
if (view) throw new Error("NativeAdapter: already initialized");
|
|
4068
3733
|
const ticket = ++generation;
|
|
4069
3734
|
const loaded = await loadCompiler();
|
|
@@ -4073,7 +3738,7 @@ function createNativeAdapter({ loadCompiler = defaultLoadCompiler } = {}) {
|
|
|
4073
3738
|
if (label) labelOf = label;
|
|
4074
3739
|
compile = loaded;
|
|
4075
3740
|
container = host;
|
|
4076
|
-
root =
|
|
3741
|
+
root = document ? fromProject(document.project) : parseMjml(EMPTY_MJML);
|
|
4077
3742
|
selectedId = null;
|
|
4078
3743
|
history = { past: [], future: [], key: null, at: 0 };
|
|
4079
3744
|
view = createCanvasView({
|
|
@@ -4302,14 +3967,14 @@ function createDefaultEmailEditor(options = {}) {
|
|
|
4302
3967
|
}
|
|
4303
3968
|
|
|
4304
3969
|
// src/ui/EmailCanvas.jsx
|
|
4305
|
-
import { useEffect as
|
|
4306
|
-
import { jsx as
|
|
4307
|
-
function EmailCanvas({ editor, document
|
|
4308
|
-
const ref =
|
|
4309
|
-
const documentRef =
|
|
4310
|
-
const callbacks =
|
|
3970
|
+
import { useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
3971
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
3972
|
+
function EmailCanvas({ editor, document = null, onReady, onError, className }) {
|
|
3973
|
+
const ref = useRef3(null);
|
|
3974
|
+
const documentRef = useRef3(document);
|
|
3975
|
+
const callbacks = useRef3({ onReady, onError });
|
|
4311
3976
|
callbacks.current = { onReady, onError };
|
|
4312
|
-
|
|
3977
|
+
useEffect5(() => {
|
|
4313
3978
|
if (!editor || !ref.current) return void 0;
|
|
4314
3979
|
let alive = true;
|
|
4315
3980
|
editor.initialize({ container: ref.current, document: documentRef.current }).then(() => {
|
|
@@ -4322,12 +3987,12 @@ function EmailCanvas({ editor, document: document2 = null, onReady, onError, cla
|
|
|
4322
3987
|
editor.destroy();
|
|
4323
3988
|
};
|
|
4324
3989
|
}, [editor]);
|
|
4325
|
-
return /* @__PURE__ */
|
|
3990
|
+
return /* @__PURE__ */ jsx9("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
|
|
4326
3991
|
}
|
|
4327
3992
|
|
|
4328
3993
|
// src/ui/EmailBuilder.jsx
|
|
4329
|
-
import { useCallback as useCallback2, useEffect as
|
|
4330
|
-
import { AlertTriangle, Loader2 as
|
|
3994
|
+
import { useCallback as useCallback2, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef8, useState as useState12 } from "react";
|
|
3995
|
+
import { AlertTriangle, Loader2 as Loader25, Plus as Plus2, Save, SlidersHorizontal } from "lucide-react";
|
|
4331
3996
|
|
|
4332
3997
|
// src/features/autosave/scheduler.js
|
|
4333
3998
|
function createAutosaveScheduler({
|
|
@@ -4386,11 +4051,11 @@ function defaultStore() {
|
|
|
4386
4051
|
return null;
|
|
4387
4052
|
}
|
|
4388
4053
|
}
|
|
4389
|
-
function saveLocalDraft({ document
|
|
4054
|
+
function saveLocalDraft({ document, meta }, store = defaultStore()) {
|
|
4390
4055
|
const target = usable(store);
|
|
4391
4056
|
if (!target) return false;
|
|
4392
4057
|
try {
|
|
4393
|
-
target.setItem(KEY2, JSON.stringify({ savedAt: Date.now(), document
|
|
4058
|
+
target.setItem(KEY2, JSON.stringify({ savedAt: Date.now(), document, meta }));
|
|
4394
4059
|
return true;
|
|
4395
4060
|
} catch {
|
|
4396
4061
|
return false;
|
|
@@ -4437,8 +4102,8 @@ import {
|
|
|
4437
4102
|
Square,
|
|
4438
4103
|
Type
|
|
4439
4104
|
} from "lucide-react";
|
|
4440
|
-
import { useRef as
|
|
4441
|
-
import { jsx as
|
|
4105
|
+
import { useRef as useRef4 } from "react";
|
|
4106
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
4442
4107
|
var DRAG_THRESHOLD = 5;
|
|
4443
4108
|
var ICONS2 = {
|
|
4444
4109
|
Heading,
|
|
@@ -4463,8 +4128,8 @@ var GROUPS2 = [
|
|
|
4463
4128
|
function BlockTile({ block, disabled, onAdd, onDragStart }) {
|
|
4464
4129
|
const t = useT();
|
|
4465
4130
|
const Icon = ICONS2[block.icon] || Square;
|
|
4466
|
-
const press =
|
|
4467
|
-
return /* @__PURE__ */
|
|
4131
|
+
const press = useRef4(null);
|
|
4132
|
+
return /* @__PURE__ */ jsx10(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs9(
|
|
4468
4133
|
"button",
|
|
4469
4134
|
{
|
|
4470
4135
|
type: "button",
|
|
@@ -4504,8 +4169,8 @@ function BlockTile({ block, disabled, onAdd, onDragStart }) {
|
|
|
4504
4169
|
disabled && "eb:cursor-not-allowed eb:opacity-50"
|
|
4505
4170
|
),
|
|
4506
4171
|
children: [
|
|
4507
|
-
/* @__PURE__ */
|
|
4508
|
-
/* @__PURE__ */
|
|
4172
|
+
/* @__PURE__ */ jsx10(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
|
|
4173
|
+
/* @__PURE__ */ jsx10("span", { className: "eb:text-[12px] eb:text-foreground", children: t(block.label) })
|
|
4509
4174
|
]
|
|
4510
4175
|
}
|
|
4511
4176
|
) });
|
|
@@ -4513,12 +4178,12 @@ function BlockTile({ block, disabled, onAdd, onDragStart }) {
|
|
|
4513
4178
|
function BlockSidebar({ editor, disabled = false, onAdded }) {
|
|
4514
4179
|
const t = useT();
|
|
4515
4180
|
const blocks = editor.listBlocks();
|
|
4516
|
-
return /* @__PURE__ */
|
|
4181
|
+
return /* @__PURE__ */ jsx10("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) => {
|
|
4517
4182
|
const items = blocks.filter((b) => b.kind === group.kind && b.mjml);
|
|
4518
4183
|
if (!items.length) return null;
|
|
4519
|
-
return /* @__PURE__ */
|
|
4520
|
-
/* @__PURE__ */
|
|
4521
|
-
/* @__PURE__ */
|
|
4184
|
+
return /* @__PURE__ */ jsxs9("section", { className: "eb:space-y-2", children: [
|
|
4185
|
+
/* @__PURE__ */ jsx10(SectionLabel, { children: t(group.title) }),
|
|
4186
|
+
/* @__PURE__ */ jsx10("div", { className: "eb:grid eb:grid-cols-2 eb:gap-2", children: items.map((block) => /* @__PURE__ */ jsx10(
|
|
4522
4187
|
BlockTile,
|
|
4523
4188
|
{
|
|
4524
4189
|
block,
|
|
@@ -4536,15 +4201,15 @@ function BlockSidebar({ editor, disabled = false, onAdded }) {
|
|
|
4536
4201
|
|
|
4537
4202
|
// src/ui/ElementActions.jsx
|
|
4538
4203
|
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Copy, Trash2 as Trash23 } from "lucide-react";
|
|
4539
|
-
import { jsx as
|
|
4204
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4540
4205
|
function ElementActions({ editor, selection }) {
|
|
4541
4206
|
const t = useT();
|
|
4542
4207
|
if (!selection || !selection.canRemove && !selection.canDuplicate) return null;
|
|
4543
4208
|
const horizontal = selection.tagName === "mj-column";
|
|
4544
4209
|
const [Prev, Next] = horizontal ? [ArrowLeft, ArrowRight] : [ArrowUp, ArrowDown];
|
|
4545
4210
|
const [prevLabel, nextLabel] = horizontal ? [t("Move left"), t("Move right")] : [t("Move up"), t("Move down")];
|
|
4546
|
-
return /* @__PURE__ */
|
|
4547
|
-
/* @__PURE__ */
|
|
4211
|
+
return /* @__PURE__ */ jsxs10("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: [
|
|
4212
|
+
/* @__PURE__ */ jsx11(TooltipWrap, { label: prevLabel, children: /* @__PURE__ */ jsx11(
|
|
4548
4213
|
Button,
|
|
4549
4214
|
{
|
|
4550
4215
|
variant: "ghost",
|
|
@@ -4552,10 +4217,10 @@ function ElementActions({ editor, selection }) {
|
|
|
4552
4217
|
"aria-label": prevLabel,
|
|
4553
4218
|
disabled: !selection.canMoveUp,
|
|
4554
4219
|
onClick: () => editor.move(-1, selection.id),
|
|
4555
|
-
children: /* @__PURE__ */
|
|
4220
|
+
children: /* @__PURE__ */ jsx11(Prev, {})
|
|
4556
4221
|
}
|
|
4557
4222
|
) }),
|
|
4558
|
-
/* @__PURE__ */
|
|
4223
|
+
/* @__PURE__ */ jsx11(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx11(
|
|
4559
4224
|
Button,
|
|
4560
4225
|
{
|
|
4561
4226
|
variant: "ghost",
|
|
@@ -4563,10 +4228,10 @@ function ElementActions({ editor, selection }) {
|
|
|
4563
4228
|
"aria-label": nextLabel,
|
|
4564
4229
|
disabled: !selection.canMoveDown,
|
|
4565
4230
|
onClick: () => editor.move(1, selection.id),
|
|
4566
|
-
children: /* @__PURE__ */
|
|
4231
|
+
children: /* @__PURE__ */ jsx11(Next, {})
|
|
4567
4232
|
}
|
|
4568
4233
|
) }),
|
|
4569
|
-
/* @__PURE__ */
|
|
4234
|
+
/* @__PURE__ */ jsx11(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx11(
|
|
4570
4235
|
Button,
|
|
4571
4236
|
{
|
|
4572
4237
|
variant: "ghost",
|
|
@@ -4574,10 +4239,10 @@ function ElementActions({ editor, selection }) {
|
|
|
4574
4239
|
"aria-label": t("Duplicate"),
|
|
4575
4240
|
disabled: !selection.canDuplicate,
|
|
4576
4241
|
onClick: () => editor.duplicate(selection.id),
|
|
4577
|
-
children: /* @__PURE__ */
|
|
4242
|
+
children: /* @__PURE__ */ jsx11(Copy, {})
|
|
4578
4243
|
}
|
|
4579
4244
|
) }),
|
|
4580
|
-
/* @__PURE__ */
|
|
4245
|
+
/* @__PURE__ */ jsx11(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx11(
|
|
4581
4246
|
Button,
|
|
4582
4247
|
{
|
|
4583
4248
|
variant: "ghost",
|
|
@@ -4586,20 +4251,20 @@ function ElementActions({ editor, selection }) {
|
|
|
4586
4251
|
disabled: !selection.canRemove,
|
|
4587
4252
|
onClick: () => editor.remove(selection.id),
|
|
4588
4253
|
className: "eb:ml-auto eb:text-foreground-muted eb:hover:bg-destructive/10 eb:hover:text-destructive",
|
|
4589
|
-
children: /* @__PURE__ */
|
|
4254
|
+
children: /* @__PURE__ */ jsx11(Trash23, {})
|
|
4590
4255
|
}
|
|
4591
4256
|
) })
|
|
4592
4257
|
] });
|
|
4593
4258
|
}
|
|
4594
4259
|
|
|
4595
4260
|
// src/ui/PropertiesPanel.jsx
|
|
4596
|
-
import { useMemo as
|
|
4261
|
+
import { useMemo as useMemo4, useRef as useRef6 } from "react";
|
|
4597
4262
|
import { X } from "lucide-react";
|
|
4598
4263
|
|
|
4599
4264
|
// src/ui/fields.jsx
|
|
4600
|
-
import { useEffect as
|
|
4601
|
-
import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as
|
|
4602
|
-
import { Fragment as
|
|
4265
|
+
import { useEffect as useEffect6, useRef as useRef5, useState as useState9 } from "react";
|
|
4266
|
+
import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader24, Upload as Upload2 } from "lucide-react";
|
|
4267
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
4603
4268
|
var COLOR_DEBOUNCE_MS = 250;
|
|
4604
4269
|
function emailColor(value) {
|
|
4605
4270
|
const v = String(value || "").trim();
|
|
@@ -4608,11 +4273,11 @@ function emailColor(value) {
|
|
|
4608
4273
|
return null;
|
|
4609
4274
|
}
|
|
4610
4275
|
function useDraft(value, toDraft) {
|
|
4611
|
-
const [draft, setDraft] =
|
|
4612
|
-
const ref =
|
|
4613
|
-
const convert =
|
|
4276
|
+
const [draft, setDraft] = useState9(() => toDraft(value));
|
|
4277
|
+
const ref = useRef5(null);
|
|
4278
|
+
const convert = useRef5(toDraft);
|
|
4614
4279
|
convert.current = toDraft;
|
|
4615
|
-
|
|
4280
|
+
useEffect6(() => {
|
|
4616
4281
|
const el = ref.current;
|
|
4617
4282
|
const focused = el && el.ownerDocument?.activeElement && el.contains(el.ownerDocument.activeElement);
|
|
4618
4283
|
if (!focused) setDraft(convert.current(value));
|
|
@@ -4621,10 +4286,10 @@ function useDraft(value, toDraft) {
|
|
|
4621
4286
|
}
|
|
4622
4287
|
var asText = (v) => v || "";
|
|
4623
4288
|
function FieldLabel({ id, children }) {
|
|
4624
|
-
return /* @__PURE__ */
|
|
4289
|
+
return /* @__PURE__ */ jsx12(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
|
|
4625
4290
|
}
|
|
4626
4291
|
function FieldError({ children }) {
|
|
4627
|
-
return children ? /* @__PURE__ */
|
|
4292
|
+
return children ? /* @__PURE__ */ jsx12("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
|
|
4628
4293
|
}
|
|
4629
4294
|
function TextField({ id, property, value, fallback = "", onCommit, addon = null, error = null }) {
|
|
4630
4295
|
const t = useT();
|
|
@@ -4655,23 +4320,23 @@ function TextField({ id, property, value, fallback = "", onCommit, addon = null,
|
|
|
4655
4320
|
onKeyDown: keyDown,
|
|
4656
4321
|
"aria-invalid": invalid || void 0
|
|
4657
4322
|
};
|
|
4658
|
-
return /* @__PURE__ */
|
|
4659
|
-
/* @__PURE__ */
|
|
4660
|
-
property.type === "textarea" ? /* @__PURE__ */
|
|
4661
|
-
/* @__PURE__ */
|
|
4323
|
+
return /* @__PURE__ */ jsxs11("div", { ref, className: "eb:space-y-1.5", children: [
|
|
4324
|
+
/* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
|
|
4325
|
+
property.type === "textarea" ? /* @__PURE__ */ jsx12(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs11("div", { className: "eb:flex eb:gap-2", children: [
|
|
4326
|
+
/* @__PURE__ */ jsx12(Input, { ...shared, className: "eb:min-w-0 eb:flex-1" }),
|
|
4662
4327
|
addon
|
|
4663
|
-
] }) : /* @__PURE__ */
|
|
4664
|
-
/* @__PURE__ */
|
|
4328
|
+
] }) : /* @__PURE__ */ jsx12(Input, { ...shared }),
|
|
4329
|
+
/* @__PURE__ */ jsx12(FieldError, { children: invalid ? kind === "image" ? t("An https:// address is required") : t("Address not allowed") : error })
|
|
4665
4330
|
] });
|
|
4666
4331
|
}
|
|
4667
4332
|
function ImageField({ id, property, value, onCommit, services }) {
|
|
4668
4333
|
const t = useT();
|
|
4669
|
-
const inputRef =
|
|
4334
|
+
const inputRef = useRef5(
|
|
4670
4335
|
/** @type {HTMLInputElement|null} */
|
|
4671
4336
|
null
|
|
4672
4337
|
);
|
|
4673
|
-
const [busy, setBusy] =
|
|
4674
|
-
const [error, setError] =
|
|
4338
|
+
const [busy, setBusy] = useState9(false);
|
|
4339
|
+
const [error, setError] = useState9(
|
|
4675
4340
|
/** @type {string|null} */
|
|
4676
4341
|
null
|
|
4677
4342
|
);
|
|
@@ -4693,8 +4358,8 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
4693
4358
|
setBusy(false);
|
|
4694
4359
|
}
|
|
4695
4360
|
};
|
|
4696
|
-
const addon = upload ? /* @__PURE__ */
|
|
4697
|
-
/* @__PURE__ */
|
|
4361
|
+
const addon = upload ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
4362
|
+
/* @__PURE__ */ jsx12(
|
|
4698
4363
|
"input",
|
|
4699
4364
|
{
|
|
4700
4365
|
ref: inputRef,
|
|
@@ -4707,7 +4372,7 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
4707
4372
|
onChange: pick
|
|
4708
4373
|
}
|
|
4709
4374
|
),
|
|
4710
|
-
/* @__PURE__ */
|
|
4375
|
+
/* @__PURE__ */ jsxs11(
|
|
4711
4376
|
Button,
|
|
4712
4377
|
{
|
|
4713
4378
|
type: "button",
|
|
@@ -4716,13 +4381,13 @@ function ImageField({ id, property, value, onCommit, services }) {
|
|
|
4716
4381
|
disabled: busy,
|
|
4717
4382
|
onClick: () => inputRef.current?.click(),
|
|
4718
4383
|
children: [
|
|
4719
|
-
busy ? /* @__PURE__ */
|
|
4384
|
+
busy ? /* @__PURE__ */ jsx12(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx12(Upload2, {}),
|
|
4720
4385
|
t("Upload")
|
|
4721
4386
|
]
|
|
4722
4387
|
}
|
|
4723
4388
|
)
|
|
4724
4389
|
] }) : null;
|
|
4725
|
-
return /* @__PURE__ */
|
|
4390
|
+
return /* @__PURE__ */ jsx12(TextField, { id, property, value, onCommit, addon, error });
|
|
4726
4391
|
}
|
|
4727
4392
|
var asNumber = (v) => {
|
|
4728
4393
|
const n = Number.parseFloat(String(v || ""));
|
|
@@ -4744,10 +4409,10 @@ function SizeField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4744
4409
|
const next = `${n}${property.unit || "px"}`;
|
|
4745
4410
|
if (next !== value) onCommit(next);
|
|
4746
4411
|
};
|
|
4747
|
-
return /* @__PURE__ */
|
|
4748
|
-
/* @__PURE__ */
|
|
4749
|
-
/* @__PURE__ */
|
|
4750
|
-
/* @__PURE__ */
|
|
4412
|
+
return /* @__PURE__ */ jsxs11("div", { ref, className: "eb:space-y-1.5", children: [
|
|
4413
|
+
/* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
|
|
4414
|
+
/* @__PURE__ */ jsxs11("div", { className: "eb:relative", children: [
|
|
4415
|
+
/* @__PURE__ */ jsx12(
|
|
4751
4416
|
Input,
|
|
4752
4417
|
{
|
|
4753
4418
|
id,
|
|
@@ -4766,17 +4431,17 @@ function SizeField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4766
4431
|
className: "eb:pr-9 eb:tabular-nums"
|
|
4767
4432
|
}
|
|
4768
4433
|
),
|
|
4769
|
-
/* @__PURE__ */
|
|
4434
|
+
/* @__PURE__ */ jsx12("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" })
|
|
4770
4435
|
] })
|
|
4771
4436
|
] });
|
|
4772
4437
|
}
|
|
4773
4438
|
function ColorField({ id, property, value, fallback = "", onCommit }) {
|
|
4774
4439
|
const t = useT();
|
|
4775
4440
|
const [draft, setDraft, ref] = useDraft(value, asText);
|
|
4776
|
-
const timer =
|
|
4777
|
-
const latest =
|
|
4441
|
+
const timer = useRef5(null);
|
|
4442
|
+
const latest = useRef5({ value, onCommit });
|
|
4778
4443
|
latest.current = { value, onCommit };
|
|
4779
|
-
|
|
4444
|
+
useEffect6(() => () => clearTimeout(timer.current), []);
|
|
4780
4445
|
const change = (next) => {
|
|
4781
4446
|
setDraft(next);
|
|
4782
4447
|
clearTimeout(timer.current);
|
|
@@ -4789,9 +4454,9 @@ function ColorField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4789
4454
|
}
|
|
4790
4455
|
}, COLOR_DEBOUNCE_MS);
|
|
4791
4456
|
};
|
|
4792
|
-
return /* @__PURE__ */
|
|
4793
|
-
/* @__PURE__ */
|
|
4794
|
-
/* @__PURE__ */
|
|
4457
|
+
return /* @__PURE__ */ jsxs11("div", { ref, className: "eb:space-y-1.5", children: [
|
|
4458
|
+
/* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
|
|
4459
|
+
/* @__PURE__ */ jsx12(ColorPicker, { id, value: draft, placeholder: emailColor(fallback) || "", onChange: change })
|
|
4795
4460
|
] });
|
|
4796
4461
|
}
|
|
4797
4462
|
var DEFAULT_OPTION = "__default__";
|
|
@@ -4801,9 +4466,9 @@ function SelectField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4801
4466
|
const words = (v) => String(v || "").trim().split(/\s+/).sort().join(" ");
|
|
4802
4467
|
const fallbackOption = (property.options || []).find((o) => o.value && words(o.value) === words(fallback));
|
|
4803
4468
|
const defaultLabel = fallbackOption ? t("Default ({value})", { value: t(fallbackOption.label) }) : t("Default");
|
|
4804
|
-
return /* @__PURE__ */
|
|
4805
|
-
/* @__PURE__ */
|
|
4806
|
-
/* @__PURE__ */
|
|
4469
|
+
return /* @__PURE__ */ jsxs11("div", { className: "eb:space-y-1.5", children: [
|
|
4470
|
+
/* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
|
|
4471
|
+
/* @__PURE__ */ jsxs11(
|
|
4807
4472
|
Select,
|
|
4808
4473
|
{
|
|
4809
4474
|
value: current,
|
|
@@ -4812,10 +4477,10 @@ function SelectField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4812
4477
|
if (v !== (value || "")) onCommit(v || null);
|
|
4813
4478
|
},
|
|
4814
4479
|
children: [
|
|
4815
|
-
/* @__PURE__ */
|
|
4816
|
-
/* @__PURE__ */
|
|
4817
|
-
!(property.options || []).some((o) => !o.value) && /* @__PURE__ */
|
|
4818
|
-
(property.options || []).map((o) => /* @__PURE__ */
|
|
4480
|
+
/* @__PURE__ */ jsx12(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx12(SelectValue, { placeholder: defaultLabel }) }),
|
|
4481
|
+
/* @__PURE__ */ jsxs11(SelectContent, { children: [
|
|
4482
|
+
!(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx12(SelectItem, { value: DEFAULT_OPTION, children: defaultLabel }),
|
|
4483
|
+
(property.options || []).map((o) => /* @__PURE__ */ jsx12(SelectItem, { value: o.value || DEFAULT_OPTION, children: t(o.label) }, o.value || DEFAULT_OPTION))
|
|
4819
4484
|
] })
|
|
4820
4485
|
]
|
|
4821
4486
|
}
|
|
@@ -4825,9 +4490,9 @@ function SelectField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4825
4490
|
var ALIGN_ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify: AlignJustify };
|
|
4826
4491
|
function AlignField({ id, property, value, fallback = "", onCommit }) {
|
|
4827
4492
|
const t = useT();
|
|
4828
|
-
return /* @__PURE__ */
|
|
4829
|
-
/* @__PURE__ */
|
|
4830
|
-
/* @__PURE__ */
|
|
4493
|
+
return /* @__PURE__ */ jsxs11("div", { className: "eb:space-y-1.5", children: [
|
|
4494
|
+
/* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
|
|
4495
|
+
/* @__PURE__ */ jsx12(
|
|
4831
4496
|
"div",
|
|
4832
4497
|
{
|
|
4833
4498
|
id,
|
|
@@ -4837,7 +4502,7 @@ function AlignField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4837
4502
|
children: (property.options || []).map((option) => {
|
|
4838
4503
|
const Icon = ALIGN_ICONS[option.value] || AlignLeft;
|
|
4839
4504
|
const active = (value || fallback) === option.value;
|
|
4840
|
-
return /* @__PURE__ */
|
|
4505
|
+
return /* @__PURE__ */ jsx12(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx12(
|
|
4841
4506
|
"button",
|
|
4842
4507
|
{
|
|
4843
4508
|
type: "button",
|
|
@@ -4849,7 +4514,7 @@ function AlignField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4849
4514
|
"eb:inline-flex eb:h-7 eb:w-7 eb:items-center eb:justify-center eb:rounded-lg2 eb:transition-colors",
|
|
4850
4515
|
active ? "eb:bg-primary eb:text-primary-foreground" : "eb:text-foreground-lighter eb:hover:bg-surface-200 eb:hover:text-foreground"
|
|
4851
4516
|
),
|
|
4852
|
-
children: /* @__PURE__ */
|
|
4517
|
+
children: /* @__PURE__ */ jsx12(Icon, { className: "eb:h-3.5 eb:w-3.5" })
|
|
4853
4518
|
}
|
|
4854
4519
|
) }, option.value);
|
|
4855
4520
|
})
|
|
@@ -4878,10 +4543,10 @@ function SpacingField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4878
4543
|
);
|
|
4879
4544
|
if (next !== (value || "")) onCommit(next);
|
|
4880
4545
|
};
|
|
4881
|
-
return /* @__PURE__ */
|
|
4882
|
-
/* @__PURE__ */
|
|
4883
|
-
/* @__PURE__ */
|
|
4884
|
-
/* @__PURE__ */
|
|
4546
|
+
return /* @__PURE__ */ jsxs11("fieldset", { ref, className: "eb:space-y-1.5", children: [
|
|
4547
|
+
/* @__PURE__ */ jsx12("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
|
|
4548
|
+
/* @__PURE__ */ jsx12("div", { className: "eb:grid eb:grid-cols-4 eb:gap-1.5", children: SIDE_LABELS.map(([side, label], i) => /* @__PURE__ */ jsxs11("div", { className: "eb:space-y-1", children: [
|
|
4549
|
+
/* @__PURE__ */ jsx12(
|
|
4885
4550
|
Input,
|
|
4886
4551
|
{
|
|
4887
4552
|
id: `${id}-${side}`,
|
|
@@ -4900,7 +4565,7 @@ function SpacingField({ id, property, value, fallback = "", onCommit }) {
|
|
|
4900
4565
|
className: "eb:px-2 eb:text-center eb:tabular-nums"
|
|
4901
4566
|
}
|
|
4902
4567
|
),
|
|
4903
|
-
/* @__PURE__ */
|
|
4568
|
+
/* @__PURE__ */ jsx12("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
|
|
4904
4569
|
] }, side)) })
|
|
4905
4570
|
] });
|
|
4906
4571
|
}
|
|
@@ -4918,15 +4583,15 @@ var FIELDS = {
|
|
|
4918
4583
|
function PropertyField({ property, value, fallback = "", onCommit, idPrefix = "eb", services = null }) {
|
|
4919
4584
|
const Field = FIELDS[property.type] || TextField;
|
|
4920
4585
|
const id = `${idPrefix}-${property.key}`;
|
|
4921
|
-
return /* @__PURE__ */
|
|
4586
|
+
return /* @__PURE__ */ jsx12(Field, { id, property, value, fallback, onCommit, services });
|
|
4922
4587
|
}
|
|
4923
4588
|
|
|
4924
4589
|
// src/features/variables/VariableInsertButton.jsx
|
|
4925
|
-
import { useState as
|
|
4590
|
+
import { useState as useState10 } from "react";
|
|
4926
4591
|
import { Braces as Braces3 } from "lucide-react";
|
|
4927
|
-
import { jsx as
|
|
4592
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4928
4593
|
function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
|
|
4929
|
-
const [open, setOpen] =
|
|
4594
|
+
const [open, setOpen] = useState10(false);
|
|
4930
4595
|
const insert = (variable) => {
|
|
4931
4596
|
const input = inputRef.current;
|
|
4932
4597
|
const text = String(value || "");
|
|
@@ -4942,228 +4607,14 @@ function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
|
|
|
4942
4607
|
input.setSelectionRange(start + token.length, start + token.length);
|
|
4943
4608
|
});
|
|
4944
4609
|
};
|
|
4945
|
-
return /* @__PURE__ */
|
|
4946
|
-
/* @__PURE__ */
|
|
4947
|
-
/* @__PURE__ */
|
|
4948
|
-
] });
|
|
4949
|
-
}
|
|
4950
|
-
|
|
4951
|
-
// src/features/generate/RewriteText.jsx
|
|
4952
|
-
import { useEffect as useEffect8, useRef as useRef7, useState as useState12 } from "react";
|
|
4953
|
-
import { ChevronDown, Languages, Loader2 as Loader26, Sparkles as Sparkles2, TriangleAlert as TriangleAlert3 } from "lucide-react";
|
|
4954
|
-
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4955
|
-
function nativeName(code) {
|
|
4956
|
-
try {
|
|
4957
|
-
const name = new Intl.DisplayNames([code], { type: "language" }).of(code) || code;
|
|
4958
|
-
return name.charAt(0).toLocaleUpperCase(code) + name.slice(1);
|
|
4959
|
-
} catch {
|
|
4960
|
-
return code;
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4963
|
-
function RewriteText({ editor, selection, rewrite }) {
|
|
4964
|
-
const { t, notify } = useUi();
|
|
4965
|
-
const [running, setRunning] = useState12(
|
|
4966
|
-
/** @type {string|null} */
|
|
4967
|
-
null
|
|
4968
|
-
);
|
|
4969
|
-
const [error, setError] = useState12("");
|
|
4970
|
-
const [instruction, setInstruction] = useState12("");
|
|
4971
|
-
const abortRef = useRef7(
|
|
4972
|
-
/** @type {AbortController|null} */
|
|
4973
|
-
null
|
|
4974
|
-
);
|
|
4975
|
-
const label = selection.tagName === "mj-button";
|
|
4976
|
-
useEffect8(() => () => abortRef.current?.abort(), []);
|
|
4977
|
-
const currentText = () => {
|
|
4978
|
-
const element = editor.getElement(selection.id);
|
|
4979
|
-
return (label ? element?.content : element?.html) ?? "";
|
|
4980
|
-
};
|
|
4981
|
-
const run = async (action, extra = {}) => {
|
|
4982
|
-
if (running) return;
|
|
4983
|
-
const text = currentText();
|
|
4984
|
-
setError("");
|
|
4985
|
-
setRunning(action);
|
|
4986
|
-
const controller = new AbortController();
|
|
4987
|
-
abortRef.current = controller;
|
|
4988
|
-
try {
|
|
4989
|
-
const result = await rewrite({ action, text, format: label ? "plain" : "html", ...extra }, { signal: controller.signal });
|
|
4990
|
-
if (controller.signal.aborted) return;
|
|
4991
|
-
if (currentText() !== text) {
|
|
4992
|
-
setError(t("The text changed in the meantime, so it was left as it is."));
|
|
4993
|
-
return;
|
|
4994
|
-
}
|
|
4995
|
-
editor.setContent(label ? result.text : cleanRichText(result.text, document), selection.id);
|
|
4996
|
-
if (action === "custom") setInstruction("");
|
|
4997
|
-
notify("success", t("Text rewritten. Ctrl+Z brings back the previous one."));
|
|
4998
|
-
} catch (err) {
|
|
4999
|
-
if (!controller.signal.aborted) setError(t(err?.message || "The text could not be rewritten."));
|
|
5000
|
-
} finally {
|
|
5001
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
5002
|
-
setRunning(null);
|
|
5003
|
-
}
|
|
5004
|
-
};
|
|
5005
|
-
const cancel = () => {
|
|
5006
|
-
abortRef.current?.abort();
|
|
5007
|
-
setRunning(null);
|
|
5008
|
-
};
|
|
5009
|
-
const busy = Boolean(running);
|
|
5010
|
-
const spinner = /* @__PURE__ */ jsx15(Loader26, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
|
|
5011
|
-
return /* @__PURE__ */ jsxs14("section", { className: "eb:space-y-2.5", "aria-label": t("Rewrite with AI"), "data-testid": "eb-rewrite", children: [
|
|
5012
|
-
/* @__PURE__ */ jsxs14(SectionLabel, { className: "eb:flex eb:items-center eb:gap-1.5", children: [
|
|
5013
|
-
/* @__PURE__ */ jsx15(Sparkles2, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
5014
|
-
t("Rewrite with AI")
|
|
5015
|
-
] }),
|
|
5016
|
-
/* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: [
|
|
5017
|
-
REWRITE_ACTIONS.map((action) => /* @__PURE__ */ jsxs14("button", { type: "button", className: chipClassName(), disabled: busy, onClick: () => run(action.id), children: [
|
|
5018
|
-
running === action.id && spinner,
|
|
5019
|
-
t(action.label)
|
|
5020
|
-
] }, action.id)),
|
|
5021
|
-
/* @__PURE__ */ jsxs14(DropdownMenu, { children: [
|
|
5022
|
-
/* @__PURE__ */ jsx15(DropdownMenuTrigger, { asChild: true, disabled: busy, children: /* @__PURE__ */ jsxs14("button", { type: "button", className: chipClassName(), children: [
|
|
5023
|
-
running === "translate" ? spinner : /* @__PURE__ */ jsx15(Languages, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
5024
|
-
t("Translate"),
|
|
5025
|
-
/* @__PURE__ */ jsx15(ChevronDown, { className: "eb:size-3", "aria-hidden": "true" })
|
|
5026
|
-
] }) }),
|
|
5027
|
-
/* @__PURE__ */ jsx15(DropdownMenuContent, { align: "start", "aria-label": t("Translate into"), children: REWRITE_LANGUAGES.map((code) => /* @__PURE__ */ jsx15(DropdownMenuItem, { lang: code, onSelect: () => run("translate", { targetLanguage: code }), children: nativeName(code) }, code)) })
|
|
5028
|
-
] })
|
|
5029
|
-
] }),
|
|
5030
|
-
/* @__PURE__ */ jsxs14(
|
|
5031
|
-
"form",
|
|
5032
|
-
{
|
|
5033
|
-
className: "eb:flex eb:gap-1.5",
|
|
5034
|
-
onSubmit: (event) => {
|
|
5035
|
-
event.preventDefault();
|
|
5036
|
-
if (instruction.trim()) run("custom", { instruction: instruction.trim() });
|
|
5037
|
-
},
|
|
5038
|
-
children: [
|
|
5039
|
-
/* @__PURE__ */ jsx15(
|
|
5040
|
-
Input,
|
|
5041
|
-
{
|
|
5042
|
-
value: instruction,
|
|
5043
|
-
onChange: (event) => setInstruction(event.target.value),
|
|
5044
|
-
maxLength: MAX_REWRITE_INSTRUCTION,
|
|
5045
|
-
placeholder: t("Or say what to change\u2026"),
|
|
5046
|
-
"aria-label": t("What to change"),
|
|
5047
|
-
disabled: busy
|
|
5048
|
-
}
|
|
5049
|
-
),
|
|
5050
|
-
/* @__PURE__ */ jsx15(Button, { type: "submit", variant: "outline", size: "icon-sm", "aria-label": t("Rewrite"), disabled: busy || !instruction.trim(), children: running === "custom" ? /* @__PURE__ */ jsx15(Loader26, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx15(Sparkles2, {}) })
|
|
5051
|
-
]
|
|
5052
|
-
}
|
|
5053
|
-
),
|
|
5054
|
-
running && /* @__PURE__ */ jsxs14("p", { className: "eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
|
|
5055
|
-
t("Rewriting\u2026"),
|
|
5056
|
-
/* @__PURE__ */ jsx15(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
|
|
5057
|
-
] }),
|
|
5058
|
-
error && /* @__PURE__ */ jsxs14("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
5059
|
-
/* @__PURE__ */ jsx15(TriangleAlert3, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
5060
|
-
error
|
|
5061
|
-
] })
|
|
5062
|
-
] });
|
|
5063
|
-
}
|
|
5064
|
-
|
|
5065
|
-
// src/features/generate/SubjectIdeas.jsx
|
|
5066
|
-
import { useEffect as useEffect9, useRef as useRef8, useState as useState13 } from "react";
|
|
5067
|
-
import { Check as Check3, Loader2 as Loader27, Sparkles as Sparkles3, TriangleAlert as TriangleAlert4 } from "lucide-react";
|
|
5068
|
-
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
5069
|
-
function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
5070
|
-
const { t, locale } = useUi();
|
|
5071
|
-
const [running, setRunning] = useState13(false);
|
|
5072
|
-
const [error, setError] = useState13("");
|
|
5073
|
-
const [ideas, setIdeas] = useState13(
|
|
5074
|
-
/** @type {string[]|null} */
|
|
5075
|
-
null
|
|
5076
|
-
);
|
|
5077
|
-
const [original, setOriginal] = useState13("");
|
|
5078
|
-
const abortRef = useRef8(
|
|
5079
|
-
/** @type {AbortController|null} */
|
|
5080
|
-
null
|
|
5081
|
-
);
|
|
5082
|
-
useEffect9(() => () => abortRef.current?.abort(), []);
|
|
5083
|
-
const run = async () => {
|
|
5084
|
-
if (running) return;
|
|
5085
|
-
const yours = ideas ? original : meta.subject.trim();
|
|
5086
|
-
const controller = new AbortController();
|
|
5087
|
-
abortRef.current = controller;
|
|
5088
|
-
setError("");
|
|
5089
|
-
setRunning(true);
|
|
5090
|
-
try {
|
|
5091
|
-
const result = await suggest(
|
|
5092
|
-
{
|
|
5093
|
-
subject: yours,
|
|
5094
|
-
preheader: meta.preheader,
|
|
5095
|
-
text: emailWords(editor.exportMjml()),
|
|
5096
|
-
language: locale,
|
|
5097
|
-
variables: variables?.map(({ key, label }) => ({ key, label })),
|
|
5098
|
-
avoid: ideas || []
|
|
5099
|
-
},
|
|
5100
|
-
{ signal: controller.signal }
|
|
5101
|
-
);
|
|
5102
|
-
if (controller.signal.aborted) return;
|
|
5103
|
-
setOriginal(yours);
|
|
5104
|
-
setIdeas(result.subjects);
|
|
5105
|
-
} catch (err) {
|
|
5106
|
-
if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t("No subject could be suggested."));
|
|
5107
|
-
} finally {
|
|
5108
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
5109
|
-
setRunning(false);
|
|
5110
|
-
}
|
|
5111
|
-
};
|
|
5112
|
-
const cancel = () => {
|
|
5113
|
-
abortRef.current?.abort();
|
|
5114
|
-
setRunning(false);
|
|
5115
|
-
};
|
|
5116
|
-
const options = ideas ? [...original ? [{ text: original, yours: true }] : [], ...ideas.map((text) => ({ text, yours: false }))] : [];
|
|
5117
|
-
const spinner = /* @__PURE__ */ jsx16(Loader27, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
|
|
5118
|
-
return /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-2", "data-testid": "eb-subject-ideas", children: [
|
|
5119
|
-
!ideas && /* @__PURE__ */ jsxs15("button", { type: "button", className: chipClassName(), disabled: running, onClick: run, children: [
|
|
5120
|
-
running ? spinner : /* @__PURE__ */ jsx16(Sparkles3, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
5121
|
-
t("Suggest subjects")
|
|
5122
|
-
] }),
|
|
5123
|
-
ideas && /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-2 eb:rounded-md eb:border eb:border-border eb:p-1.5", children: [
|
|
5124
|
-
/* @__PURE__ */ jsx16("div", { role: "radiogroup", "aria-label": t("Subject ideas"), className: "eb:space-y-0.5", children: options.map((option) => {
|
|
5125
|
-
const chosen = meta.subject === option.text;
|
|
5126
|
-
return /* @__PURE__ */ jsxs15(
|
|
5127
|
-
"button",
|
|
5128
|
-
{
|
|
5129
|
-
type: "button",
|
|
5130
|
-
role: "radio",
|
|
5131
|
-
"aria-checked": chosen,
|
|
5132
|
-
className: cn(
|
|
5133
|
-
"eb:flex eb:w-full eb:items-start eb:gap-2 eb:rounded eb:px-2 eb:py-1.5 eb:text-left eb:text-[12.5px] eb:leading-snug eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50",
|
|
5134
|
-
chosen ? "eb:bg-primary/10 eb:text-foreground" : "eb:text-foreground-lighter eb:hover:bg-muted"
|
|
5135
|
-
),
|
|
5136
|
-
onClick: () => onSubject(option.text),
|
|
5137
|
-
children: [
|
|
5138
|
-
/* @__PURE__ */ jsx16(Check3, { className: cn("eb:mt-0.5 eb:size-3.5 eb:shrink-0", chosen ? "eb:text-primary" : "eb:invisible"), "aria-hidden": "true" }),
|
|
5139
|
-
/* @__PURE__ */ jsx16("span", { className: "eb:min-w-0 eb:flex-1 eb:break-words", children: option.text }),
|
|
5140
|
-
option.yours && /* @__PURE__ */ jsx16("span", { className: "eb:shrink-0 eb:text-[11px] eb:text-foreground-muted", children: t("yours") })
|
|
5141
|
-
]
|
|
5142
|
-
},
|
|
5143
|
-
`${option.yours ? "yours" : "idea"}:${option.text}`
|
|
5144
|
-
);
|
|
5145
|
-
}) }),
|
|
5146
|
-
/* @__PURE__ */ jsxs15("div", { className: "eb:flex eb:items-center eb:gap-1.5 eb:px-1 eb:pb-0.5", children: [
|
|
5147
|
-
/* @__PURE__ */ jsxs15("button", { type: "button", className: chipClassName(), disabled: running, onClick: run, children: [
|
|
5148
|
-
running ? spinner : /* @__PURE__ */ jsx16(Sparkles3, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
5149
|
-
t("More ideas")
|
|
5150
|
-
] }),
|
|
5151
|
-
/* @__PURE__ */ jsx16(Button, { type: "button", variant: "ghost", size: "xs", className: "eb:ml-auto", disabled: running, onClick: () => setIdeas(null), children: t("Done") })
|
|
5152
|
-
] })
|
|
5153
|
-
] }),
|
|
5154
|
-
running && /* @__PURE__ */ jsxs15("p", { className: "eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
|
|
5155
|
-
t("Reading the email\u2026"),
|
|
5156
|
-
/* @__PURE__ */ jsx16(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
|
|
5157
|
-
] }),
|
|
5158
|
-
error && /* @__PURE__ */ jsxs15("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
5159
|
-
/* @__PURE__ */ jsx16(TriangleAlert4, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
5160
|
-
error
|
|
5161
|
-
] })
|
|
4610
|
+
return /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
|
|
4611
|
+
/* @__PURE__ */ jsx13(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx13(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx13(Braces3, {}) }) }),
|
|
4612
|
+
/* @__PURE__ */ jsx13(PopoverContent, { align: "end", className: "eb:w-72 eb:p-0", onCloseAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx13(VariablePicker, { variables, onPick: insert, filter: (v) => v.kind !== "link" }) })
|
|
5162
4613
|
] });
|
|
5163
4614
|
}
|
|
5164
4615
|
|
|
5165
4616
|
// src/ui/PropertiesPanel.jsx
|
|
5166
|
-
import { Fragment as
|
|
4617
|
+
import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
5167
4618
|
var GROUP_ORDER = Object.values(GROUPS);
|
|
5168
4619
|
function grouped(properties) {
|
|
5169
4620
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -5177,9 +4628,9 @@ function grouped(properties) {
|
|
|
5177
4628
|
}
|
|
5178
4629
|
function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
|
|
5179
4630
|
const t = useT();
|
|
5180
|
-
return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */
|
|
5181
|
-
/* @__PURE__ */
|
|
5182
|
-
properties.map((property) => /* @__PURE__ */
|
|
4631
|
+
return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs13("section", { className: "eb:space-y-3", children: [
|
|
4632
|
+
/* @__PURE__ */ jsx14(SectionLabel, { children: t(group) }),
|
|
4633
|
+
properties.map((property) => /* @__PURE__ */ jsx14(
|
|
5183
4634
|
PropertyField,
|
|
5184
4635
|
{
|
|
5185
4636
|
idPrefix,
|
|
@@ -5197,31 +4648,31 @@ var LABEL_PROPERTY = { key: "content", label: "Label", type: "text", group: GROU
|
|
|
5197
4648
|
var REWRITABLE_TAGS = /* @__PURE__ */ new Set(["mj-text", "mj-button"]);
|
|
5198
4649
|
function PanelHeader({ title, onClose, closeLabel = "Deselect" }) {
|
|
5199
4650
|
const t = useT();
|
|
5200
|
-
return /* @__PURE__ */
|
|
5201
|
-
/* @__PURE__ */
|
|
5202
|
-
onClose && /* @__PURE__ */
|
|
4651
|
+
return /* @__PURE__ */ jsxs13("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: [
|
|
4652
|
+
/* @__PURE__ */ jsx14("h3", { className: "eb:min-w-0 eb:flex-1 eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx14(TruncatedText, { children: title }) }),
|
|
4653
|
+
onClose && /* @__PURE__ */ jsx14(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t(closeLabel), children: /* @__PURE__ */ jsx14(X, {}) })
|
|
5203
4654
|
] });
|
|
5204
4655
|
}
|
|
5205
|
-
function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null,
|
|
4656
|
+
function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, ai = null, onDismiss = null }) {
|
|
5206
4657
|
const t = useT();
|
|
5207
|
-
const subjectRef =
|
|
5208
|
-
const preheaderRef =
|
|
4658
|
+
const subjectRef = useRef6(null);
|
|
4659
|
+
const preheaderRef = useRef6(null);
|
|
5209
4660
|
const rootId = editor.getRootId();
|
|
5210
|
-
const root =
|
|
4661
|
+
const root = useMemo4(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
|
|
5211
4662
|
const bodyBlock2 = root?.blockId ? editor.getBlock(root.blockId) : null;
|
|
5212
4663
|
const field = (key) => (e) => onMetaChange({ ...meta, [key]: e.target.value });
|
|
5213
|
-
return /* @__PURE__ */
|
|
5214
|
-
/* @__PURE__ */
|
|
5215
|
-
/* @__PURE__ */
|
|
5216
|
-
/* @__PURE__ */
|
|
5217
|
-
/* @__PURE__ */
|
|
5218
|
-
/* @__PURE__ */
|
|
5219
|
-
/* @__PURE__ */
|
|
4664
|
+
return /* @__PURE__ */ jsxs13(Fragment6, { children: [
|
|
4665
|
+
/* @__PURE__ */ jsx14(PanelHeader, { title: t("Email"), onClose: onDismiss, closeLabel: "Close" }),
|
|
4666
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:space-y-5 eb:p-4", children: [
|
|
4667
|
+
/* @__PURE__ */ jsxs13("section", { className: "eb:space-y-3", children: [
|
|
4668
|
+
/* @__PURE__ */ jsx14(SectionLabel, { children: t("Template") }),
|
|
4669
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
|
|
4670
|
+
/* @__PURE__ */ jsxs13(Label, { htmlFor: "eb-meta-name", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: [
|
|
5220
4671
|
t("Name"),
|
|
5221
4672
|
" ",
|
|
5222
|
-
/* @__PURE__ */
|
|
4673
|
+
/* @__PURE__ */ jsx14("span", { className: "eb:text-destructive", children: "*" })
|
|
5223
4674
|
] }),
|
|
5224
|
-
/* @__PURE__ */
|
|
4675
|
+
/* @__PURE__ */ jsx14(
|
|
5225
4676
|
Input,
|
|
5226
4677
|
{
|
|
5227
4678
|
id: "eb-meta-name",
|
|
@@ -5231,12 +4682,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
5231
4682
|
"aria-invalid": showErrors && !meta.name.trim() || void 0
|
|
5232
4683
|
}
|
|
5233
4684
|
),
|
|
5234
|
-
showErrors && !meta.name.trim() && /* @__PURE__ */
|
|
4685
|
+
showErrors && !meta.name.trim() && /* @__PURE__ */ jsx14("p", { className: "eb:text-[12px] eb:text-destructive", children: t("Name is required") })
|
|
5235
4686
|
] }),
|
|
5236
|
-
/* @__PURE__ */
|
|
5237
|
-
/* @__PURE__ */
|
|
5238
|
-
/* @__PURE__ */
|
|
5239
|
-
variables && /* @__PURE__ */
|
|
4687
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
|
|
4688
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
|
|
4689
|
+
/* @__PURE__ */ jsx14(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
|
|
4690
|
+
variables && /* @__PURE__ */ jsx14(
|
|
5240
4691
|
VariableInsertButton,
|
|
5241
4692
|
{
|
|
5242
4693
|
variables,
|
|
@@ -5247,22 +4698,22 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
5247
4698
|
}
|
|
5248
4699
|
)
|
|
5249
4700
|
] }),
|
|
5250
|
-
/* @__PURE__ */
|
|
5251
|
-
|
|
5252
|
-
|
|
4701
|
+
/* @__PURE__ */ jsx14(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") }),
|
|
4702
|
+
ai?.suggestSubjects && /* @__PURE__ */ jsx14(
|
|
4703
|
+
ai.components.Subjects,
|
|
5253
4704
|
{
|
|
5254
4705
|
editor,
|
|
5255
4706
|
meta,
|
|
5256
4707
|
onSubject: (subject) => onMetaChange({ ...meta, subject }),
|
|
5257
|
-
suggest:
|
|
4708
|
+
suggest: ai.suggestSubjects,
|
|
5258
4709
|
variables
|
|
5259
4710
|
}
|
|
5260
4711
|
)
|
|
5261
4712
|
] }),
|
|
5262
|
-
/* @__PURE__ */
|
|
5263
|
-
/* @__PURE__ */
|
|
5264
|
-
/* @__PURE__ */
|
|
5265
|
-
variables && /* @__PURE__ */
|
|
4713
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
|
|
4714
|
+
/* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
|
|
4715
|
+
/* @__PURE__ */ jsx14(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
|
|
4716
|
+
variables && /* @__PURE__ */ jsx14(
|
|
5266
4717
|
VariableInsertButton,
|
|
5267
4718
|
{
|
|
5268
4719
|
variables,
|
|
@@ -5273,7 +4724,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
5273
4724
|
}
|
|
5274
4725
|
)
|
|
5275
4726
|
] }),
|
|
5276
|
-
/* @__PURE__ */
|
|
4727
|
+
/* @__PURE__ */ jsx14(
|
|
5277
4728
|
Textarea,
|
|
5278
4729
|
{
|
|
5279
4730
|
ref: preheaderRef,
|
|
@@ -5287,7 +4738,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
|
|
|
5287
4738
|
)
|
|
5288
4739
|
] })
|
|
5289
4740
|
] }),
|
|
5290
|
-
root && bodyBlock2 && /* @__PURE__ */
|
|
4741
|
+
root && bodyBlock2 && /* @__PURE__ */ jsx14(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
|
|
5291
4742
|
] })
|
|
5292
4743
|
] });
|
|
5293
4744
|
}
|
|
@@ -5300,12 +4751,13 @@ function PropertiesPanel({
|
|
|
5300
4751
|
actions = null,
|
|
5301
4752
|
showErrors = false,
|
|
5302
4753
|
services = null,
|
|
4754
|
+
ai = null,
|
|
5303
4755
|
variables = null,
|
|
5304
4756
|
onDismiss = null
|
|
5305
4757
|
}) {
|
|
5306
4758
|
const t = useT();
|
|
5307
4759
|
const block = selection?.blockId ? editor.getBlock(selection.blockId) : null;
|
|
5308
|
-
return /* @__PURE__ */
|
|
4760
|
+
return /* @__PURE__ */ jsx14("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__ */ jsx14(
|
|
5309
4761
|
TemplateSettings,
|
|
5310
4762
|
{
|
|
5311
4763
|
editor,
|
|
@@ -5314,11 +4766,11 @@ function PropertiesPanel({
|
|
|
5314
4766
|
changes,
|
|
5315
4767
|
showErrors,
|
|
5316
4768
|
variables,
|
|
5317
|
-
|
|
4769
|
+
ai,
|
|
5318
4770
|
onDismiss
|
|
5319
4771
|
}
|
|
5320
|
-
) : /* @__PURE__ */
|
|
5321
|
-
/* @__PURE__ */
|
|
4772
|
+
) : /* @__PURE__ */ jsxs13(Fragment6, { children: [
|
|
4773
|
+
/* @__PURE__ */ jsx14(
|
|
5322
4774
|
PanelHeader,
|
|
5323
4775
|
{
|
|
5324
4776
|
title: t(selection.label),
|
|
@@ -5327,12 +4779,12 @@ function PropertiesPanel({
|
|
|
5327
4779
|
}
|
|
5328
4780
|
),
|
|
5329
4781
|
actions,
|
|
5330
|
-
selection.multiple ? /* @__PURE__ */
|
|
4782
|
+
selection.multiple ? /* @__PURE__ */ jsx14("p", { className: "eb:p-4 eb:text-[13px] eb:leading-relaxed eb:text-foreground-lighter", "data-testid": "eb-select-all", children: t("{count} sections selected. Press Delete to remove them (Ctrl+Z brings them back).", {
|
|
5331
4783
|
count: selection.multiple
|
|
5332
|
-
}) }) : /* @__PURE__ */
|
|
5333
|
-
selection.content !== null && selection.content !== void 0 && /* @__PURE__ */
|
|
5334
|
-
/* @__PURE__ */
|
|
5335
|
-
/* @__PURE__ */
|
|
4784
|
+
}) }) : /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-5 eb:p-4", children: [
|
|
4785
|
+
selection.content !== null && selection.content !== void 0 && /* @__PURE__ */ jsxs13("section", { className: "eb:space-y-3", children: [
|
|
4786
|
+
/* @__PURE__ */ jsx14(SectionLabel, { children: t(GROUPS.content) }),
|
|
4787
|
+
/* @__PURE__ */ jsx14(
|
|
5336
4788
|
PropertyField,
|
|
5337
4789
|
{
|
|
5338
4790
|
idPrefix: "eb-prop",
|
|
@@ -5343,16 +4795,16 @@ function PropertiesPanel({
|
|
|
5343
4795
|
`${selection.id}:content`
|
|
5344
4796
|
)
|
|
5345
4797
|
] }),
|
|
5346
|
-
selection.tagName === "mj-text" && /* @__PURE__ */
|
|
5347
|
-
|
|
5348
|
-
/* @__PURE__ */
|
|
4798
|
+
selection.tagName === "mj-text" && /* @__PURE__ */ jsx14("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
|
|
4799
|
+
ai?.rewrite && REWRITABLE_TAGS.has(selection.tagName) && /* @__PURE__ */ jsx14(ai.components.Rewrite, { editor, selection, rewrite: ai.rewrite }, selection.id),
|
|
4800
|
+
/* @__PURE__ */ jsx14(PropertyGroups, { editor, element: selection, block, idPrefix: "eb-prop", services })
|
|
5349
4801
|
] })
|
|
5350
4802
|
] }) });
|
|
5351
4803
|
}
|
|
5352
4804
|
|
|
5353
4805
|
// src/ui/Toolbar.jsx
|
|
5354
4806
|
import { ArrowLeft as ArrowLeft2, Monitor, Redo2, Smartphone, Undo2 } from "lucide-react";
|
|
5355
|
-
import { Fragment as
|
|
4807
|
+
import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
5356
4808
|
function Toolbar({
|
|
5357
4809
|
title,
|
|
5358
4810
|
status,
|
|
@@ -5369,8 +4821,8 @@ function Toolbar({
|
|
|
5369
4821
|
compact = false
|
|
5370
4822
|
}) {
|
|
5371
4823
|
const t = useT();
|
|
5372
|
-
const identity2 = /* @__PURE__ */
|
|
5373
|
-
/* @__PURE__ */
|
|
4824
|
+
const identity2 = /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
4825
|
+
/* @__PURE__ */ jsxs14(
|
|
5374
4826
|
Button,
|
|
5375
4827
|
{
|
|
5376
4828
|
variant: "ghost",
|
|
@@ -5378,50 +4830,50 @@ function Toolbar({
|
|
|
5378
4830
|
onClick: onBack,
|
|
5379
4831
|
"aria-label": compact ? t("Back") : void 0,
|
|
5380
4832
|
children: [
|
|
5381
|
-
/* @__PURE__ */
|
|
4833
|
+
/* @__PURE__ */ jsx15(ArrowLeft2, {}),
|
|
5382
4834
|
!compact && t("Back")
|
|
5383
4835
|
]
|
|
5384
4836
|
}
|
|
5385
4837
|
),
|
|
5386
|
-
/* @__PURE__ */
|
|
5387
|
-
/* @__PURE__ */
|
|
5388
|
-
/* @__PURE__ */
|
|
5389
|
-
/* @__PURE__ */
|
|
4838
|
+
/* @__PURE__ */ jsx15(Separator, { orientation: "vertical", className: "eb:h-5" }),
|
|
4839
|
+
/* @__PURE__ */ jsxs14("div", { className: cn("eb:min-w-0 eb:flex-1", !compact && "eb:max-w-[320px]"), children: [
|
|
4840
|
+
/* @__PURE__ */ jsx15("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx15(TruncatedText, { children: title || t("New template") }) }),
|
|
4841
|
+
/* @__PURE__ */ jsx15("p", { className: "eb:text-[11px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: status })
|
|
5390
4842
|
] })
|
|
5391
4843
|
] });
|
|
5392
|
-
const editing = /* @__PURE__ */
|
|
5393
|
-
/* @__PURE__ */
|
|
5394
|
-
/* @__PURE__ */
|
|
5395
|
-
/* @__PURE__ */
|
|
4844
|
+
const editing = /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: [
|
|
4845
|
+
/* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
|
|
4846
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: t("Undo (Ctrl+Z)"), children: /* @__PURE__ */ jsx15(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onUndo, disabled: !ready || !canUndo, "aria-label": t("Undo"), children: /* @__PURE__ */ jsx15(Undo2, {}) }) }),
|
|
4847
|
+
/* @__PURE__ */ jsx15(TooltipWrap, { label: t("Redo (Ctrl+Y)"), children: /* @__PURE__ */ jsx15(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onRedo, disabled: !ready || !canRedo, "aria-label": t("Redo"), children: /* @__PURE__ */ jsx15(Redo2, {}) }) })
|
|
5396
4848
|
] }),
|
|
5397
|
-
/* @__PURE__ */
|
|
5398
|
-
/* @__PURE__ */
|
|
5399
|
-
/* @__PURE__ */
|
|
4849
|
+
/* @__PURE__ */ jsx15(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs14(TabsList, { "aria-label": t("Device"), children: [
|
|
4850
|
+
/* @__PURE__ */ jsxs14(TabsTrigger, { value: "desktop", disabled: !ready, "aria-label": compact ? t("Desktop") : void 0, children: [
|
|
4851
|
+
/* @__PURE__ */ jsx15(Monitor, {}),
|
|
5400
4852
|
!compact && t("Desktop")
|
|
5401
4853
|
] }),
|
|
5402
|
-
/* @__PURE__ */
|
|
5403
|
-
/* @__PURE__ */
|
|
4854
|
+
/* @__PURE__ */ jsxs14(TabsTrigger, { value: "mobile", disabled: !ready, "aria-label": compact ? t("Mobile") : void 0, children: [
|
|
4855
|
+
/* @__PURE__ */ jsx15(Smartphone, {}),
|
|
5404
4856
|
!compact && t("Mobile")
|
|
5405
4857
|
] })
|
|
5406
4858
|
] }) })
|
|
5407
4859
|
] });
|
|
5408
4860
|
if (compact) {
|
|
5409
|
-
return /* @__PURE__ */
|
|
5410
|
-
/* @__PURE__ */
|
|
4861
|
+
return /* @__PURE__ */ jsxs14("div", { className: "eb:shrink-0 eb:border-b eb:border-border", "data-layout": "compact", children: [
|
|
4862
|
+
/* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:h-12 eb:items-center eb:gap-2 eb:px-2", children: [
|
|
5411
4863
|
identity2,
|
|
5412
4864
|
primary
|
|
5413
4865
|
] }),
|
|
5414
|
-
/* @__PURE__ */
|
|
4866
|
+
/* @__PURE__ */ jsxs14("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: [
|
|
5415
4867
|
editing,
|
|
5416
|
-
/* @__PURE__ */
|
|
5417
|
-
/* @__PURE__ */
|
|
4868
|
+
/* @__PURE__ */ jsx15(Separator, { orientation: "vertical", className: "eb:h-5 eb:shrink-0" }),
|
|
4869
|
+
/* @__PURE__ */ jsx15("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: actions })
|
|
5418
4870
|
] })
|
|
5419
4871
|
] });
|
|
5420
4872
|
}
|
|
5421
|
-
return /* @__PURE__ */
|
|
4873
|
+
return /* @__PURE__ */ jsxs14("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: [
|
|
5422
4874
|
identity2,
|
|
5423
4875
|
editing,
|
|
5424
|
-
/* @__PURE__ */
|
|
4876
|
+
/* @__PURE__ */ jsxs14("div", { className: "eb:ml-auto eb:flex eb:items-center eb:gap-2", "data-testid": "eb-toolbar-actions", children: [
|
|
5425
4877
|
actions,
|
|
5426
4878
|
primary
|
|
5427
4879
|
] })
|
|
@@ -5429,12 +4881,12 @@ function Toolbar({
|
|
|
5429
4881
|
}
|
|
5430
4882
|
|
|
5431
4883
|
// src/ui/Drawer.jsx
|
|
5432
|
-
import { useEffect as
|
|
4884
|
+
import { useEffect as useEffect7 } from "react";
|
|
5433
4885
|
import { X as X2 } from "lucide-react";
|
|
5434
|
-
import { jsx as
|
|
4886
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
5435
4887
|
function Drawer({ open, side = "left", title, onClose, children, testId, header = true }) {
|
|
5436
4888
|
const t = useT();
|
|
5437
|
-
|
|
4889
|
+
useEffect7(() => {
|
|
5438
4890
|
if (!open) return void 0;
|
|
5439
4891
|
const onKey = (e) => {
|
|
5440
4892
|
if (e.key === "Escape") onClose();
|
|
@@ -5442,9 +4894,9 @@ function Drawer({ open, side = "left", title, onClose, children, testId, header
|
|
|
5442
4894
|
window.addEventListener("keydown", onKey);
|
|
5443
4895
|
return () => window.removeEventListener("keydown", onKey);
|
|
5444
4896
|
}, [open, onClose]);
|
|
5445
|
-
return /* @__PURE__ */
|
|
5446
|
-
/* @__PURE__ */
|
|
5447
|
-
/* @__PURE__ */
|
|
4897
|
+
return /* @__PURE__ */ jsxs15("div", { className: cn("eb:absolute eb:inset-0 eb:z-20", !open && "eb:hidden"), "data-testid": testId, "data-open": open ? "true" : "false", children: [
|
|
4898
|
+
/* @__PURE__ */ jsx16("div", { className: "eb:absolute eb:inset-0 eb:bg-black/30", onClick: onClose, "aria-hidden": "true" }),
|
|
4899
|
+
/* @__PURE__ */ jsxs15(
|
|
5448
4900
|
"section",
|
|
5449
4901
|
{
|
|
5450
4902
|
role: "dialog",
|
|
@@ -5454,11 +4906,11 @@ function Drawer({ open, side = "left", title, onClose, children, testId, header
|
|
|
5454
4906
|
side === "left" ? "eb:left-0 eb:border-r eb:border-border" : "eb:right-0 eb:border-l eb:border-border"
|
|
5455
4907
|
),
|
|
5456
4908
|
children: [
|
|
5457
|
-
header && /* @__PURE__ */
|
|
5458
|
-
/* @__PURE__ */
|
|
5459
|
-
/* @__PURE__ */
|
|
4909
|
+
header && /* @__PURE__ */ jsxs15("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: [
|
|
4910
|
+
/* @__PURE__ */ jsx16("h3", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: title }),
|
|
4911
|
+
/* @__PURE__ */ jsx16(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx16(X2, {}) })
|
|
5460
4912
|
] }),
|
|
5461
|
-
/* @__PURE__ */
|
|
4913
|
+
/* @__PURE__ */ jsx16("div", { className: "eb:grid eb:min-h-0 eb:flex-1", children })
|
|
5462
4914
|
]
|
|
5463
4915
|
}
|
|
5464
4916
|
)
|
|
@@ -5466,10 +4918,10 @@ function Drawer({ open, side = "left", title, onClose, children, testId, header
|
|
|
5466
4918
|
}
|
|
5467
4919
|
|
|
5468
4920
|
// src/ui/useEmailEditorState.js
|
|
5469
|
-
import { useCallback, useEffect as
|
|
4921
|
+
import { useCallback, useEffect as useEffect8, useRef as useRef7, useState as useState11 } from "react";
|
|
5470
4922
|
var DIRTY_CHECK_MS = 150;
|
|
5471
4923
|
function useEmailEditorState(editor) {
|
|
5472
|
-
const [state, setState] =
|
|
4924
|
+
const [state, setState] = useState11(() => ({
|
|
5473
4925
|
status: editor?.status || "idle",
|
|
5474
4926
|
selection: null,
|
|
5475
4927
|
canUndo: false,
|
|
@@ -5478,9 +4930,9 @@ function useEmailEditorState(editor) {
|
|
|
5478
4930
|
changes: 0,
|
|
5479
4931
|
dirty: false
|
|
5480
4932
|
}));
|
|
5481
|
-
const clean =
|
|
5482
|
-
const forced =
|
|
5483
|
-
const timer =
|
|
4933
|
+
const clean = useRef7(null);
|
|
4934
|
+
const forced = useRef7(false);
|
|
4935
|
+
const timer = useRef7(null);
|
|
5484
4936
|
const snapshot = useCallback(() => {
|
|
5485
4937
|
try {
|
|
5486
4938
|
return editor.status === "ready" ? editor.exportMjml() : null;
|
|
@@ -5503,7 +4955,7 @@ function useEmailEditorState(editor) {
|
|
|
5503
4955
|
forced.current = true;
|
|
5504
4956
|
setState((s) => ({ ...s, dirty: true }));
|
|
5505
4957
|
}, []);
|
|
5506
|
-
|
|
4958
|
+
useEffect8(() => {
|
|
5507
4959
|
if (!editor) return void 0;
|
|
5508
4960
|
const patch = (values) => setState((s) => ({ ...s, ...values }));
|
|
5509
4961
|
const measure = () => {
|
|
@@ -5550,7 +5002,7 @@ function useEmailEditorState(editor) {
|
|
|
5550
5002
|
}
|
|
5551
5003
|
|
|
5552
5004
|
// src/ui/EmailBuilder.jsx
|
|
5553
|
-
import { Fragment as
|
|
5005
|
+
import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5554
5006
|
var EMPTY_META = { name: "", subject: "", preheader: "", description: "" };
|
|
5555
5007
|
function metaOf(template) {
|
|
5556
5008
|
return {
|
|
@@ -5576,19 +5028,20 @@ function Builder({
|
|
|
5576
5028
|
onSaved,
|
|
5577
5029
|
onDirtyChange,
|
|
5578
5030
|
dirtyCheckRef,
|
|
5031
|
+
ai,
|
|
5579
5032
|
className
|
|
5580
5033
|
}) {
|
|
5581
5034
|
const { t, locale, notify, confirm } = useUi();
|
|
5582
|
-
const tRef =
|
|
5035
|
+
const tRef = useRef8(t);
|
|
5583
5036
|
tRef.current = t;
|
|
5584
|
-
const editor =
|
|
5037
|
+
const editor = useMemo5(
|
|
5585
5038
|
() => injected || createDefaultEmailEditor({ locale, t: (key, values) => tRef.current(key, values) }),
|
|
5586
5039
|
[injected, locale]
|
|
5587
5040
|
);
|
|
5588
|
-
const storage =
|
|
5589
|
-
const systemVariables =
|
|
5590
|
-
const [customVariables, setCustomVariables] =
|
|
5591
|
-
const variables =
|
|
5041
|
+
const storage = useMemo5(() => injectedStorage || createHttpStorage(), [injectedStorage]);
|
|
5042
|
+
const systemVariables = useMemo5(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
|
|
5043
|
+
const [customVariables, setCustomVariables] = useState12([]);
|
|
5044
|
+
const variables = useMemo5(() => mergeVariables(systemVariables, customVariables), [systemVariables, customVariables]);
|
|
5592
5045
|
const canManageVariables = typeof storage.saveVariable === "function" && typeof storage.listVariables === "function";
|
|
5593
5046
|
const reloadVariables = useCallback2(async () => {
|
|
5594
5047
|
if (typeof storage.listVariables !== "function") return;
|
|
@@ -5598,52 +5051,45 @@ function Builder({
|
|
|
5598
5051
|
notify("error", error.message);
|
|
5599
5052
|
}
|
|
5600
5053
|
}, [storage, notify]);
|
|
5601
|
-
|
|
5054
|
+
useEffect9(() => {
|
|
5602
5055
|
reloadVariables();
|
|
5603
5056
|
}, [reloadVariables]);
|
|
5604
5057
|
const { brand, setBrand } = useBrand(storage);
|
|
5605
|
-
const brandSender =
|
|
5606
|
-
const starter =
|
|
5058
|
+
const brandSender = useMemo5(() => brandedSender(sender, brand), [sender, brand]);
|
|
5059
|
+
const starter = useMemo5(
|
|
5607
5060
|
() => brandTemplate(starterMjml || createStarterMjml({ sender: brandSender, t }), brand, { sender, t }),
|
|
5608
5061
|
[starterMjml, brandSender, brand, sender, t]
|
|
5609
5062
|
);
|
|
5610
|
-
const templates =
|
|
5063
|
+
const templates = useMemo5(
|
|
5611
5064
|
() => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: brandSender, t }).map((template2) => ({
|
|
5612
5065
|
...template2,
|
|
5613
5066
|
mjml: brandTemplate(template2.mjml, brand, { sender, t })
|
|
5614
5067
|
})),
|
|
5615
5068
|
[galleryProp, brandSender, brand, sender, t]
|
|
5616
5069
|
);
|
|
5617
|
-
|
|
5070
|
+
useEffect9(() => {
|
|
5618
5071
|
editor.setBrand?.(hasBrand(brand) ? brand : null);
|
|
5619
5072
|
}, [editor, brand]);
|
|
5620
|
-
const services =
|
|
5621
|
-
() => ({
|
|
5622
|
-
uploadImage: (file) => storage.uploadImage(file),
|
|
5623
|
-
rewrite: typeof storage.rewrite === "function" ? (input, options) => storage.rewrite(input, options) : null,
|
|
5624
|
-
suggestSubjects: typeof storage.suggestSubjects === "function" ? (input, options) => storage.suggestSubjects(input, options) : null
|
|
5625
|
-
}),
|
|
5626
|
-
[storage]
|
|
5627
|
-
);
|
|
5073
|
+
const services = useMemo5(() => ({ uploadImage: (file) => storage.uploadImage(file) }), [storage]);
|
|
5628
5074
|
const state = useEmailEditorState(editor);
|
|
5629
5075
|
const autosaveOn = isFeatureEnabled("autosave");
|
|
5630
5076
|
const autosaveOptions = emailBuilderFeatures.get("autosave")?.options || {};
|
|
5631
|
-
const [template, setTemplate] =
|
|
5632
|
-
const [boot, setBoot] =
|
|
5633
|
-
const [meta, setMeta] =
|
|
5634
|
-
const [savedMeta, setSavedMeta] =
|
|
5635
|
-
const [saving, setSaving] =
|
|
5636
|
-
const [showErrors, setShowErrors] =
|
|
5637
|
-
const [autosaveBlocked, setAutosaveBlocked] =
|
|
5638
|
-
const [engineError, setEngineError] =
|
|
5639
|
-
const [attempt, setAttempt] =
|
|
5640
|
-
const rootRef =
|
|
5077
|
+
const [template, setTemplate] = useState12({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
|
|
5078
|
+
const [boot, setBoot] = useState12({ status: "loading", document: null, mjml: null, draft: false, message: null });
|
|
5079
|
+
const [meta, setMeta] = useState12(EMPTY_META);
|
|
5080
|
+
const [savedMeta, setSavedMeta] = useState12(EMPTY_META);
|
|
5081
|
+
const [saving, setSaving] = useState12(null);
|
|
5082
|
+
const [showErrors, setShowErrors] = useState12(false);
|
|
5083
|
+
const [autosaveBlocked, setAutosaveBlocked] = useState12(null);
|
|
5084
|
+
const [engineError, setEngineError] = useState12(null);
|
|
5085
|
+
const [attempt, setAttempt] = useState12(0);
|
|
5086
|
+
const rootRef = useRef8(null);
|
|
5641
5087
|
const layout2 = useLayoutWidth(rootRef);
|
|
5642
5088
|
const compact = layout2 !== "wide";
|
|
5643
5089
|
const blocksInDrawer = layout2 === "narrow";
|
|
5644
|
-
const [drawer, setDrawer] =
|
|
5090
|
+
const [drawer, setDrawer] = useState12(null);
|
|
5645
5091
|
const closeDrawer = useCallback2(() => setDrawer(null), []);
|
|
5646
|
-
|
|
5092
|
+
useEffect9(() => {
|
|
5647
5093
|
if (!compact) setDrawer(null);
|
|
5648
5094
|
else if (!blocksInDrawer) setDrawer((d) => d === "blocks" ? null : d);
|
|
5649
5095
|
}, [compact, blocksInDrawer]);
|
|
@@ -5654,8 +5100,8 @@ function Builder({
|
|
|
5654
5100
|
(date) => date ? new Date(date).toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" }) : "",
|
|
5655
5101
|
[locale]
|
|
5656
5102
|
);
|
|
5657
|
-
const booted =
|
|
5658
|
-
|
|
5103
|
+
const booted = useRef8(false);
|
|
5104
|
+
useEffect9(() => {
|
|
5659
5105
|
if (booted.current) return;
|
|
5660
5106
|
booted.current = true;
|
|
5661
5107
|
(async () => {
|
|
@@ -5715,26 +5161,26 @@ function Builder({
|
|
|
5715
5161
|
if (!boot.document) editor.loadMjml(boot.mjml || initialMjml || starter);
|
|
5716
5162
|
}, [editor, boot.document, boot.mjml, starter, initialMjml]);
|
|
5717
5163
|
const { markDirty, markClean, checkDirty } = state;
|
|
5718
|
-
|
|
5164
|
+
useEffect9(() => {
|
|
5719
5165
|
if (ready && boot.draft) markDirty();
|
|
5720
5166
|
}, [ready, boot.draft, markDirty]);
|
|
5721
5167
|
const isDirty = useCallback2(
|
|
5722
5168
|
() => editor.status === "ready" && checkDirty() || metaDirty,
|
|
5723
5169
|
[editor, checkDirty, metaDirty]
|
|
5724
5170
|
);
|
|
5725
|
-
const reportDirty =
|
|
5171
|
+
const reportDirty = useRef8(onDirtyChange);
|
|
5726
5172
|
reportDirty.current = onDirtyChange;
|
|
5727
|
-
|
|
5173
|
+
useEffect9(() => {
|
|
5728
5174
|
reportDirty.current?.(dirty);
|
|
5729
5175
|
}, [dirty]);
|
|
5730
|
-
|
|
5176
|
+
useEffect9(() => {
|
|
5731
5177
|
if (!dirtyCheckRef) return void 0;
|
|
5732
5178
|
dirtyCheckRef.current = isDirty;
|
|
5733
5179
|
return () => {
|
|
5734
5180
|
dirtyCheckRef.current = null;
|
|
5735
5181
|
};
|
|
5736
5182
|
}, [dirtyCheckRef, isDirty]);
|
|
5737
|
-
|
|
5183
|
+
useEffect9(() => {
|
|
5738
5184
|
if (!dirty) return void 0;
|
|
5739
5185
|
const warn = (e) => {
|
|
5740
5186
|
e.preventDefault();
|
|
@@ -5743,8 +5189,8 @@ function Builder({
|
|
|
5743
5189
|
window.addEventListener("beforeunload", warn);
|
|
5744
5190
|
return () => window.removeEventListener("beforeunload", warn);
|
|
5745
5191
|
}, [dirty]);
|
|
5746
|
-
const savingRef =
|
|
5747
|
-
const latest =
|
|
5192
|
+
const savingRef = useRef8(false);
|
|
5193
|
+
const latest = useRef8({});
|
|
5748
5194
|
latest.current = { template, meta, onSaved, compact };
|
|
5749
5195
|
const save = useCallback2(
|
|
5750
5196
|
async ({ auto = false } = {}) => {
|
|
@@ -5759,9 +5205,9 @@ function Builder({
|
|
|
5759
5205
|
}
|
|
5760
5206
|
return false;
|
|
5761
5207
|
}
|
|
5762
|
-
const
|
|
5208
|
+
const document = editor.getDocument();
|
|
5763
5209
|
const mjml6 = editor.exportMjml();
|
|
5764
|
-
const payload = { ...currentMeta, document
|
|
5210
|
+
const payload = { ...currentMeta, document, mjml: mjml6 };
|
|
5765
5211
|
savingRef.current = true;
|
|
5766
5212
|
setSaving(auto ? "auto" : "manual");
|
|
5767
5213
|
try {
|
|
@@ -5774,11 +5220,19 @@ function Builder({
|
|
|
5774
5220
|
if (!current.id) clearLocalDraft();
|
|
5775
5221
|
latest.current.onSaved?.(result.template);
|
|
5776
5222
|
const warnings = result.warnings || [];
|
|
5223
|
+
const withoutPicture = auto ? 0 : countImagesWithoutPicture(mjml6);
|
|
5777
5224
|
if (warnings.length) {
|
|
5778
5225
|
notify(
|
|
5779
5226
|
"warning",
|
|
5780
5227
|
t("Saved, with {count} MJML warning(s): {message}", { count: warnings.length, message: warnings[0].message })
|
|
5781
5228
|
);
|
|
5229
|
+
} else if (withoutPicture) {
|
|
5230
|
+
notify(
|
|
5231
|
+
"warning",
|
|
5232
|
+
t("Saved. {count} image(s) have no picture yet: they are left out of the email until you add one.", {
|
|
5233
|
+
count: withoutPicture
|
|
5234
|
+
})
|
|
5235
|
+
);
|
|
5782
5236
|
} else if (!auto) {
|
|
5783
5237
|
notify(
|
|
5784
5238
|
"success",
|
|
@@ -5827,7 +5281,7 @@ function Builder({
|
|
|
5827
5281
|
},
|
|
5828
5282
|
[editor, storage, markClean, notify, confirm, t]
|
|
5829
5283
|
);
|
|
5830
|
-
|
|
5284
|
+
useEffect9(() => {
|
|
5831
5285
|
const onKey = (e) => {
|
|
5832
5286
|
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "s") {
|
|
5833
5287
|
e.preventDefault();
|
|
@@ -5837,11 +5291,11 @@ function Builder({
|
|
|
5837
5291
|
window.addEventListener("keydown", onKey, true);
|
|
5838
5292
|
return () => window.removeEventListener("keydown", onKey, true);
|
|
5839
5293
|
}, [save]);
|
|
5840
|
-
|
|
5294
|
+
useEffect9(() => {
|
|
5841
5295
|
if (!ready) return void 0;
|
|
5842
5296
|
return editor.addShortcut("save", "mod+s", () => save());
|
|
5843
5297
|
}, [ready, editor, save]);
|
|
5844
|
-
const scheduler =
|
|
5298
|
+
const scheduler = useMemo5(
|
|
5845
5299
|
() => createAutosaveScheduler({
|
|
5846
5300
|
run: () => save({ auto: true }),
|
|
5847
5301
|
idleMs: autosaveOptions.idleMs,
|
|
@@ -5849,13 +5303,13 @@ function Builder({
|
|
|
5849
5303
|
}),
|
|
5850
5304
|
[save, autosaveOptions.idleMs, autosaveOptions.maxWaitMs]
|
|
5851
5305
|
);
|
|
5852
|
-
|
|
5853
|
-
|
|
5306
|
+
useEffect9(() => () => scheduler.dispose(), [scheduler]);
|
|
5307
|
+
useEffect9(() => {
|
|
5854
5308
|
if (!autosaveOn || !ready || !template.id || autosaveBlocked) return;
|
|
5855
5309
|
if (dirty) scheduler.notify();
|
|
5856
5310
|
else scheduler.reset();
|
|
5857
5311
|
}, [autosaveOn, ready, template.id, autosaveBlocked, dirty, state.changes, meta, scheduler]);
|
|
5858
|
-
|
|
5312
|
+
useEffect9(() => {
|
|
5859
5313
|
if (!autosaveOn || !ready || template.id || !dirty) return void 0;
|
|
5860
5314
|
const timer = setTimeout(() => {
|
|
5861
5315
|
if (editor.status === "ready") saveLocalDraft({ document: editor.getDocument(), meta });
|
|
@@ -5896,12 +5350,13 @@ function Builder({
|
|
|
5896
5350
|
sender: brandSender,
|
|
5897
5351
|
hostSender: sender,
|
|
5898
5352
|
brand,
|
|
5899
|
-
setBrand
|
|
5353
|
+
setBrand,
|
|
5354
|
+
ai
|
|
5900
5355
|
};
|
|
5901
5356
|
const toolbarFeatures = emailBuilderFeatures.list(
|
|
5902
5357
|
(f) => f.enabled !== false && Boolean(f.toolbar) && (f.isAvailable ? f.isAvailable(featureContext) : true)
|
|
5903
5358
|
);
|
|
5904
|
-
const properties = /* @__PURE__ */
|
|
5359
|
+
const properties = /* @__PURE__ */ jsx17(
|
|
5905
5360
|
PropertiesPanel,
|
|
5906
5361
|
{
|
|
5907
5362
|
onDismiss: compact ? closeDrawer : null,
|
|
@@ -5911,12 +5366,13 @@ function Builder({
|
|
|
5911
5366
|
onMetaChange: setMeta,
|
|
5912
5367
|
changes: state.changes,
|
|
5913
5368
|
showErrors,
|
|
5914
|
-
actions: /* @__PURE__ */
|
|
5369
|
+
actions: /* @__PURE__ */ jsx17(ElementActions, { editor, selection: state.selection }),
|
|
5915
5370
|
services,
|
|
5371
|
+
ai,
|
|
5916
5372
|
variables
|
|
5917
5373
|
}
|
|
5918
5374
|
);
|
|
5919
|
-
return /* @__PURE__ */
|
|
5375
|
+
return /* @__PURE__ */ jsxs16(
|
|
5920
5376
|
"div",
|
|
5921
5377
|
{
|
|
5922
5378
|
ref: rootRef,
|
|
@@ -5926,7 +5382,7 @@ function Builder({
|
|
|
5926
5382
|
"data-layout": layout2,
|
|
5927
5383
|
"data-template-id": template.id || "",
|
|
5928
5384
|
children: [
|
|
5929
|
-
/* @__PURE__ */
|
|
5385
|
+
/* @__PURE__ */ jsx17(
|
|
5930
5386
|
Toolbar,
|
|
5931
5387
|
{
|
|
5932
5388
|
title: meta.name,
|
|
@@ -5940,17 +5396,21 @@ function Builder({
|
|
|
5940
5396
|
onDeviceChange: (device) => editor.setDevice(device),
|
|
5941
5397
|
onBack: onClose ? back : void 0,
|
|
5942
5398
|
compact,
|
|
5943
|
-
actions:
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5399
|
+
actions: [
|
|
5400
|
+
// The AI module's button first, when the host gave the module.
|
|
5401
|
+
ai?.generate && /* @__PURE__ */ jsx17(ai.components.Generate, { context: featureContext, generate: ai.generate }, "ai-generate"),
|
|
5402
|
+
...toolbarFeatures.map((feature) => {
|
|
5403
|
+
const Action = feature.toolbar;
|
|
5404
|
+
return /* @__PURE__ */ jsx17(Action, { context: featureContext }, feature.id);
|
|
5405
|
+
})
|
|
5406
|
+
].filter(Boolean),
|
|
5407
|
+
primary: /* @__PURE__ */ jsxs16(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
|
|
5408
|
+
saving === "manual" ? /* @__PURE__ */ jsx17(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx17(Save, {}),
|
|
5949
5409
|
t("Save")
|
|
5950
5410
|
] })
|
|
5951
5411
|
}
|
|
5952
5412
|
),
|
|
5953
|
-
/* @__PURE__ */
|
|
5413
|
+
/* @__PURE__ */ jsxs16(
|
|
5954
5414
|
"div",
|
|
5955
5415
|
{
|
|
5956
5416
|
className: cn(
|
|
@@ -5960,12 +5420,12 @@ function Builder({
|
|
|
5960
5420
|
layout2 === "narrow" && "eb:grid-cols-1"
|
|
5961
5421
|
),
|
|
5962
5422
|
children: [
|
|
5963
|
-
!blocksInDrawer && /* @__PURE__ */
|
|
5964
|
-
/* @__PURE__ */
|
|
5965
|
-
/* @__PURE__ */
|
|
5966
|
-
/* @__PURE__ */
|
|
5967
|
-
/* @__PURE__ */
|
|
5968
|
-
engineError && /* @__PURE__ */
|
|
5423
|
+
!blocksInDrawer && /* @__PURE__ */ jsx17(BlockSidebar, { editor, disabled: !ready, onAdded: compact ? () => setDrawer("properties") : void 0 }),
|
|
5424
|
+
/* @__PURE__ */ jsx17("div", { className: "eb:relative eb:min-w-0 eb:bg-surface-75", "aria-busy": !ready, children: boot.status === "error" || engineError ? /* @__PURE__ */ jsxs16("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: [
|
|
5425
|
+
/* @__PURE__ */ jsx17(AlertTriangle, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
|
|
5426
|
+
/* @__PURE__ */ jsx17("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.") }),
|
|
5427
|
+
/* @__PURE__ */ jsx17("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
|
|
5428
|
+
engineError && /* @__PURE__ */ jsx17(
|
|
5969
5429
|
Button,
|
|
5970
5430
|
{
|
|
5971
5431
|
variant: "outline",
|
|
@@ -5977,8 +5437,8 @@ function Builder({
|
|
|
5977
5437
|
children: t("Retry")
|
|
5978
5438
|
}
|
|
5979
5439
|
)
|
|
5980
|
-
] }) : /* @__PURE__ */
|
|
5981
|
-
boot.status === "ready" && /* @__PURE__ */
|
|
5440
|
+
] }) : /* @__PURE__ */ jsxs16(Fragment8, { children: [
|
|
5441
|
+
boot.status === "ready" && /* @__PURE__ */ jsx17(
|
|
5982
5442
|
EmailCanvas,
|
|
5983
5443
|
{
|
|
5984
5444
|
editor,
|
|
@@ -5988,14 +5448,14 @@ function Builder({
|
|
|
5988
5448
|
},
|
|
5989
5449
|
attempt
|
|
5990
5450
|
),
|
|
5991
|
-
!ready && /* @__PURE__ */
|
|
5992
|
-
/* @__PURE__ */
|
|
5993
|
-
/* @__PURE__ */
|
|
5451
|
+
!ready && /* @__PURE__ */ jsxs16("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: [
|
|
5452
|
+
/* @__PURE__ */ jsx17(Loader25, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
|
|
5453
|
+
/* @__PURE__ */ jsx17("span", { className: "eb:sr-only", children: t("Loading the builder") })
|
|
5994
5454
|
] })
|
|
5995
5455
|
] }) }),
|
|
5996
5456
|
!compact && properties,
|
|
5997
|
-
blocksInDrawer && /* @__PURE__ */
|
|
5998
|
-
compact && /* @__PURE__ */
|
|
5457
|
+
blocksInDrawer && /* @__PURE__ */ jsx17(Drawer, { open: drawer === "blocks", side: "left", title: t("Add a block"), onClose: closeDrawer, testId: "eb-blocks-drawer", children: /* @__PURE__ */ jsx17(BlockSidebar, { editor, disabled: !ready, onAdded: () => setDrawer("properties") }) }),
|
|
5458
|
+
compact && /* @__PURE__ */ jsx17(
|
|
5999
5459
|
Drawer,
|
|
6000
5460
|
{
|
|
6001
5461
|
open: drawer === "properties",
|
|
@@ -6010,12 +5470,12 @@ function Builder({
|
|
|
6010
5470
|
]
|
|
6011
5471
|
}
|
|
6012
5472
|
),
|
|
6013
|
-
compact && /* @__PURE__ */
|
|
6014
|
-
blocksInDrawer && /* @__PURE__ */
|
|
6015
|
-
/* @__PURE__ */
|
|
5473
|
+
compact && /* @__PURE__ */ jsxs16("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: [
|
|
5474
|
+
blocksInDrawer && /* @__PURE__ */ jsxs16(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setDrawer("blocks"), children: [
|
|
5475
|
+
/* @__PURE__ */ jsx17(Plus2, {}),
|
|
6016
5476
|
t("Add a block")
|
|
6017
5477
|
] }),
|
|
6018
|
-
/* @__PURE__ */
|
|
5478
|
+
/* @__PURE__ */ jsxs16(
|
|
6019
5479
|
Button,
|
|
6020
5480
|
{
|
|
6021
5481
|
variant: state.selection ? "default" : "outline",
|
|
@@ -6024,25 +5484,44 @@ function Builder({
|
|
|
6024
5484
|
disabled: !ready,
|
|
6025
5485
|
onClick: () => setDrawer("properties"),
|
|
6026
5486
|
children: [
|
|
6027
|
-
/* @__PURE__ */
|
|
6028
|
-
/* @__PURE__ */
|
|
5487
|
+
/* @__PURE__ */ jsx17(SlidersHorizontal, {}),
|
|
5488
|
+
/* @__PURE__ */ jsx17("span", { className: "eb:truncate", children: ready && state.selection ? t("Edit: {element}", { element: t(state.selection.label) }) : t("Email settings") })
|
|
6029
5489
|
]
|
|
6030
5490
|
}
|
|
6031
5491
|
)
|
|
6032
5492
|
] }),
|
|
6033
|
-
/* @__PURE__ */
|
|
5493
|
+
/* @__PURE__ */ jsx17(NoticeArea, {}),
|
|
5494
|
+
ai?.key && ai.components.Connect && /* @__PURE__ */ jsx17(ai.components.Connect, { aiKey: ai.key })
|
|
6034
5495
|
]
|
|
6035
5496
|
}
|
|
6036
5497
|
);
|
|
6037
5498
|
}
|
|
6038
|
-
function EmailBuilder({ locale = "en", messages, onNotify, ...props }) {
|
|
6039
|
-
|
|
5499
|
+
function EmailBuilder({ locale = "en", messages, onNotify, ai: aiProp = null, ...props }) {
|
|
5500
|
+
const ai = useAiModule(aiProp);
|
|
5501
|
+
const allMessages = useMemo5(
|
|
5502
|
+
() => ai ? { ...messagesOf(ai.messages, locale), ...messages } : messages,
|
|
5503
|
+
[ai, locale, messages]
|
|
5504
|
+
);
|
|
5505
|
+
return /* @__PURE__ */ jsx17(UiProvider, { locale, messages: allMessages, onNotify, children: /* @__PURE__ */ jsx17(Builder, { ...props, ai, templateId: props.templateId ?? null }) });
|
|
5506
|
+
}
|
|
5507
|
+
function useAiModule(value) {
|
|
5508
|
+
const valid = Boolean(value && typeof value === "object" && /** @type {any} */
|
|
5509
|
+
value.components);
|
|
5510
|
+
useEffect9(() => {
|
|
5511
|
+
if (value && !valid) {
|
|
5512
|
+
console.warn("EmailBuilder: `ai` must be made by createAi() from best-email-builder/ai \u2014 the AI stays off.");
|
|
5513
|
+
}
|
|
5514
|
+
}, [value, valid]);
|
|
5515
|
+
return valid ? (
|
|
5516
|
+
/** @type {import('../ai/createAi.js').EmailBuilderAi} */
|
|
5517
|
+
value
|
|
5518
|
+
) : null;
|
|
6040
5519
|
}
|
|
6041
5520
|
|
|
6042
5521
|
// src/ui/EmailBuilderBoundary.jsx
|
|
6043
5522
|
import { Component } from "react";
|
|
6044
5523
|
import { AlertTriangle as AlertTriangle2, ArrowLeft as ArrowLeft3, RefreshCw } from "lucide-react";
|
|
6045
|
-
import { jsx as
|
|
5524
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
6046
5525
|
var isChunkError = (error) => error?.name === "ChunkLoadError" || /Loading (CSS )?chunk .* failed/i.test(String(error?.message || ""));
|
|
6047
5526
|
var english = (text) => text;
|
|
6048
5527
|
var EmailBuilderBoundary = class extends Component {
|
|
@@ -6067,23 +5546,23 @@ var EmailBuilderBoundary = class extends Component {
|
|
|
6067
5546
|
const { error, attempt } = this.state;
|
|
6068
5547
|
const t = this.props.t || english;
|
|
6069
5548
|
if (!error) {
|
|
6070
|
-
return /* @__PURE__ */
|
|
5549
|
+
return /* @__PURE__ */ jsx18("div", { style: { height: "100%" }, children: this.props.children }, attempt);
|
|
6071
5550
|
}
|
|
6072
|
-
return /* @__PURE__ */
|
|
5551
|
+
return /* @__PURE__ */ jsxs17(
|
|
6073
5552
|
"div",
|
|
6074
5553
|
{
|
|
6075
5554
|
role: "alert",
|
|
6076
5555
|
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",
|
|
6077
5556
|
children: [
|
|
6078
|
-
/* @__PURE__ */
|
|
6079
|
-
/* @__PURE__ */
|
|
6080
|
-
/* @__PURE__ */
|
|
6081
|
-
this.props.onClose && /* @__PURE__ */
|
|
6082
|
-
/* @__PURE__ */
|
|
5557
|
+
/* @__PURE__ */ jsx18(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
|
|
5558
|
+
/* @__PURE__ */ jsx18("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
|
|
5559
|
+
/* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:gap-2", children: [
|
|
5560
|
+
this.props.onClose && /* @__PURE__ */ jsxs17(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
|
|
5561
|
+
/* @__PURE__ */ jsx18(ArrowLeft3, {}),
|
|
6083
5562
|
t("Back")
|
|
6084
5563
|
] }),
|
|
6085
|
-
/* @__PURE__ */
|
|
6086
|
-
/* @__PURE__ */
|
|
5564
|
+
/* @__PURE__ */ jsxs17(Button, { size: "sm", onClick: this.retry, children: [
|
|
5565
|
+
/* @__PURE__ */ jsx18(RefreshCw, {}),
|
|
6087
5566
|
t("Retry")
|
|
6088
5567
|
] })
|
|
6089
5568
|
] })
|
|
@@ -6107,4 +5586,4 @@ export {
|
|
|
6107
5586
|
EmailBuilder,
|
|
6108
5587
|
EmailBuilderBoundary
|
|
6109
5588
|
};
|
|
6110
|
-
//# sourceMappingURL=chunk-
|
|
5589
|
+
//# sourceMappingURL=chunk-7QQH6MTV.js.map
|