best-email-builder 0.5.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 +31 -0
- package/README.md +14 -3
- package/dist/ai-server.js +4 -4
- package/dist/ai.js +7 -7
- package/dist/builder.js +9 -9
- package/dist/{chunk-3LKF4QBL.js → chunk-2RKVJQSD.js} +4 -4
- package/dist/{chunk-6D3SLT5W.js → chunk-2Y7CDXZM.js} +4 -4
- package/dist/{chunk-ECHGJZ2W.js → chunk-4U7L2NYQ.js} +32 -3
- package/dist/chunk-4U7L2NYQ.js.map +1 -0
- package/dist/{chunk-PB7YZ3WP.js → chunk-7QQH6MTV.js} +19 -65
- package/dist/chunk-7QQH6MTV.js.map +1 -0
- package/dist/{chunk-MPX5CWTZ.js → chunk-BAA5XMWY.js} +100 -12
- package/dist/chunk-BAA5XMWY.js.map +1 -0
- package/dist/{chunk-LYMJ2TBX.js → chunk-CNJZLZRH.js} +2 -2
- package/dist/{chunk-7U6BAKM7.js → chunk-H4W65GBX.js} +3 -3
- package/dist/{chunk-7U6BAKM7.js.map → chunk-H4W65GBX.js.map} +1 -1
- package/dist/{chunk-DFNHICTF.js → chunk-JI36FNWE.js} +3 -1
- package/dist/chunk-JI36FNWE.js.map +1 -0
- package/dist/{chunk-LXR4QTGP.js → chunk-N7DEVAIP.js} +3 -3
- package/dist/{chunk-2RZHGAH5.js → chunk-OWVXKMLI.js} +59 -2
- package/dist/chunk-OWVXKMLI.js.map +1 -0
- package/dist/{chunk-KXJJSVN5.js → chunk-SAIW46IC.js} +2 -2
- package/dist/{chunk-5SASKCUC.js → chunk-W424KPCR.js} +2 -2
- package/dist/index.js +11 -11
- package/dist/library.js +5 -5
- package/dist/server.js +12 -29
- package/dist/server.js.map +1 -1
- package/dist/types/ai/draft.d.ts +12 -0
- package/dist/types/ai/providers.d.ts +10 -2
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/renderer/prepareMjml.d.ts +8 -2
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/server/render.d.ts +5 -2
- package/package.json +1 -1
- package/dist/chunk-2RZHGAH5.js.map +0 -1
- package/dist/chunk-DFNHICTF.js.map +0 -1
- package/dist/chunk-ECHGJZ2W.js.map +0 -1
- package/dist/chunk-MPX5CWTZ.js.map +0 -1
- package/dist/chunk-PB7YZ3WP.js.map +0 -1
- /package/dist/{chunk-3LKF4QBL.js.map → chunk-2RKVJQSD.js.map} +0 -0
- /package/dist/{chunk-6D3SLT5W.js.map → chunk-2Y7CDXZM.js.map} +0 -0
- /package/dist/{chunk-LYMJ2TBX.js.map → chunk-CNJZLZRH.js.map} +0 -0
- /package/dist/{chunk-LXR4QTGP.js.map → chunk-N7DEVAIP.js.map} +0 -0
- /package/dist/{chunk-KXJJSVN5.js.map → chunk-SAIW46IC.js.map} +0 -0
- /package/dist/{chunk-5SASKCUC.js.map → chunk-W424KPCR.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,37 @@ All notable changes are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.1] — 2026-10-15
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **An image with no picture yet is left out of the email.** `<mj-image>`
|
|
13
|
+
without `src` (AI drafts leave them for the user, so do the gallery's
|
|
14
|
+
newsletter and promotion) compiled to `<img src="">` — a broken image in
|
|
15
|
+
every inbox, a red cross in Outlook. Saving, exporting and sending now leave
|
|
16
|
+
it out; the canvas and the preview (`/render`) still draw the frame to fill.
|
|
17
|
+
Saving says how many are left out. `prepareMjml(…, { emptyImages: 'keep' })`
|
|
18
|
+
keeps them; `countImagesWithoutPicture(mjml)` counts them.
|
|
19
|
+
- **Drafts through `openrouter/free` were cut short.** Most free models
|
|
20
|
+
reason first — 5 000 to 12 000 tokens before the email — and hit the limit
|
|
21
|
+
of 16 000 (`TRUNCATED`). OpenRouter now asks for less reasoning
|
|
22
|
+
(`reasoning: { effort: 'low' }`) and allows 32 000 tokens.
|
|
23
|
+
- **AI drafts, what free models get wrong, fixed without asking again:** an
|
|
24
|
+
`<mj-image>` put inside a text (dead HTML there) becomes an image block in
|
|
25
|
+
its place; attributes MJML reports as illegal (`align` on a column) are
|
|
26
|
+
removed after compiling; a button whose words do not read on its fill (a
|
|
27
|
+
text color given, the fill forgotten) gets white or ink words. The prompt
|
|
28
|
+
shows the greeting's default in three languages: a French email no longer
|
|
29
|
+
starts with "Hello … there".
|
|
30
|
+
- **A 403 was always the key.** One about the model (only for some apps, not
|
|
31
|
+
in this region), the content (moderation) or the money now says so
|
|
32
|
+
(`MODEL_ERROR`, `REFUSED`, `MODEL_CREDITS`), and `askKey` keeps the user's
|
|
33
|
+
key. Gemini's wrong key (a 400) is `KEY_REFUSED`.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- `providerEmailModel({ body })` and `provider: { body, maxTokens }`: fields
|
|
37
|
+
of your own in each request (OpenRouter's `models` fallback list, `reasoning`)
|
|
38
|
+
and a provider's own output limit.
|
|
39
|
+
|
|
9
40
|
## [0.5.0] — 2026-10-15
|
|
10
41
|
|
|
11
42
|
### Changed — the AI is a module (breaking)
|
package/README.md
CHANGED
|
@@ -373,8 +373,8 @@ only, the key is in the page's code: `createAi({ provider: 'groq', apiKey })`.
|
|
|
373
373
|
#### Providers
|
|
374
374
|
|
|
375
375
|
Every provider here speaks the OpenAI *chat completions* API; nothing to
|
|
376
|
-
install. `provider` is an id below, or `{ id, model, baseUrl, headers }`
|
|
377
|
-
change the model or reach an endpoint of your own. `AI_PROVIDERS` lists them
|
|
376
|
+
install. `provider` is an id below, or `{ id, model, baseUrl, headers, maxTokens, body }`
|
|
377
|
+
to change the model or reach an endpoint of your own. `AI_PROVIDERS` lists them
|
|
378
378
|
(name, address, default model, where to get a key, what is free).
|
|
379
379
|
|
|
380
380
|
| `provider` | Default model | Free to start | Key |
|
|
@@ -396,6 +396,15 @@ providerEmailModel({ provider: { baseUrl: 'http://localhost:1234/v1', model: 'qw
|
|
|
396
396
|
|
|
397
397
|
- Model names change often: the defaults were current at this release —
|
|
398
398
|
pass `model` to be sure of yours.
|
|
399
|
+
- `openrouter/free` costs nothing and is slow: most free models reason before
|
|
400
|
+
they write, so an email takes two to six minutes, and some days a free
|
|
401
|
+
model is busy upstream. OpenRouter is asked for less reasoning
|
|
402
|
+
(`reasoning: { effort: 'low' }`) and given room for it (`maxTokens: 32000`;
|
|
403
|
+
16 000 elsewhere). For drafts in seconds, use a free tier with a key of its
|
|
404
|
+
own (Gemini, Groq, Cerebras) or a paid model.
|
|
405
|
+
- `body` adds fields of your own to each request — OpenRouter's fallback list,
|
|
406
|
+
for one: `providerEmailModel({ provider: 'openrouter', apiKey, body: { models: [...] } })`.
|
|
407
|
+
It cannot change the model, the messages or the output limit.
|
|
399
408
|
- The answer is asked as structured output (a JSON schema); a model that
|
|
400
409
|
refuses it is asked for a JSON object, then for plain JSON — once, then
|
|
401
410
|
remembered. A model whose structured output breaks the JSON (some do on the
|
|
@@ -403,7 +412,9 @@ providerEmailModel({ provider: { baseUrl: 'http://localhost:1234/v1', model: 'qw
|
|
|
403
412
|
- The provider's failures become errors the interface explains, in its
|
|
404
413
|
language: `KEY_MISSING`, `KEY_REFUSED`, `MODEL_CREDITS`, `MODEL_BUSY`,
|
|
405
414
|
`MODEL_ERROR` (with the provider's own text in `detail`). None is a 401:
|
|
406
|
-
the key is the AI's, not the user's session.
|
|
415
|
+
the key is the AI's, not the user's session. A 403 about the model (not
|
|
416
|
+
available here, in this region) or the content is not `KEY_REFUSED`: with
|
|
417
|
+
`askKey`, the user's key is not thrown away for it.
|
|
407
418
|
- **Claude**: `claudeEmailModel({ client: new Anthropic() })` with the
|
|
408
419
|
Anthropic SDK (`npm install @anthropic-ai/sdk`) — `claude-opus-5-5` by
|
|
409
420
|
default, `effort: 'medium'`, structured output, streamed so a long email
|
package/dist/ai-server.js
CHANGED
|
@@ -29,10 +29,10 @@ import {
|
|
|
29
29
|
reviewDraft,
|
|
30
30
|
reviewRewrite,
|
|
31
31
|
reviewSubjects
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import "./chunk-
|
|
34
|
-
import "./chunk-
|
|
35
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-BAA5XMWY.js";
|
|
33
|
+
import "./chunk-OWVXKMLI.js";
|
|
34
|
+
import "./chunk-W424KPCR.js";
|
|
35
|
+
import "./chunk-JI36FNWE.js";
|
|
36
36
|
export {
|
|
37
37
|
AI_MESSAGES,
|
|
38
38
|
AI_PROVIDERS,
|
package/dist/ai.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cleanRichText
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SAIW46IC.js";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Dialog,
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
createHttpCaller,
|
|
24
24
|
useSearchShortcut,
|
|
25
25
|
useUi
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import "./chunk-
|
|
26
|
+
} from "./chunk-N7DEVAIP.js";
|
|
27
|
+
import "./chunk-H4W65GBX.js";
|
|
28
28
|
import {
|
|
29
29
|
AI_MESSAGES,
|
|
30
30
|
AI_PROVIDERS,
|
|
@@ -43,13 +43,13 @@ import {
|
|
|
43
43
|
providerEmailModel,
|
|
44
44
|
purposesByCategory,
|
|
45
45
|
resolveProvider
|
|
46
|
-
} from "./chunk-
|
|
47
|
-
import "./chunk-
|
|
48
|
-
import "./chunk-
|
|
46
|
+
} from "./chunk-BAA5XMWY.js";
|
|
47
|
+
import "./chunk-OWVXKMLI.js";
|
|
48
|
+
import "./chunk-W424KPCR.js";
|
|
49
49
|
import {
|
|
50
50
|
brandTemplate,
|
|
51
51
|
hasBrand
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-JI36FNWE.js";
|
|
53
53
|
|
|
54
54
|
// src/ai/keys.js
|
|
55
55
|
var STORAGE_PREFIX = "best-email-builder:ai-key:";
|
package/dist/builder.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EmailBuilder,
|
|
3
3
|
EmailBuilderBoundary
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7QQH6MTV.js";
|
|
5
5
|
import {
|
|
6
6
|
STARTER_MJML,
|
|
7
7
|
TEMPLATE_GALLERY,
|
|
8
8
|
buildGallery,
|
|
9
9
|
createStarterMjml
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-2RKVJQSD.js";
|
|
11
|
+
import "./chunk-4U7L2NYQ.js";
|
|
12
12
|
import "./chunk-UFKCLU2D.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-SAIW46IC.js";
|
|
14
14
|
import {
|
|
15
15
|
StorageError,
|
|
16
16
|
createHttpStorage,
|
|
17
17
|
createMemoryStorage
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-N7DEVAIP.js";
|
|
19
19
|
import {
|
|
20
20
|
EMAIL_BUILDER_VERSION
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-H4W65GBX.js";
|
|
22
|
+
import "./chunk-OWVXKMLI.js";
|
|
23
|
+
import "./chunk-W424KPCR.js";
|
|
24
24
|
import {
|
|
25
25
|
CUSTOM_GROUP,
|
|
26
26
|
DEFAULT_VARIABLES,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
defineVariables,
|
|
30
30
|
mergeVariables,
|
|
31
31
|
translatorFor
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-JI36FNWE.js";
|
|
33
33
|
export {
|
|
34
34
|
CUSTOM_GROUP,
|
|
35
35
|
DEFAULT_VARIABLES,
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
TabsTrigger,
|
|
16
16
|
cn,
|
|
17
17
|
useUi
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-N7DEVAIP.js";
|
|
19
19
|
import {
|
|
20
20
|
EMAIL_MOBILE_WIDTH
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-H4W65GBX.js";
|
|
22
22
|
import {
|
|
23
23
|
DEFAULT_BUTTON_URL,
|
|
24
24
|
FONT_STACK,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
identity,
|
|
29
29
|
inWhiteSection,
|
|
30
30
|
prop
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JI36FNWE.js";
|
|
32
32
|
|
|
33
33
|
// src/features/templates/starter.js
|
|
34
34
|
function escapeXml(text) {
|
|
@@ -592,4 +592,4 @@ export {
|
|
|
592
592
|
useBrand,
|
|
593
593
|
useLayoutWidth
|
|
594
594
|
};
|
|
595
|
-
//# sourceMappingURL=chunk-
|
|
595
|
+
//# sourceMappingURL=chunk-2RKVJQSD.js.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useBrand,
|
|
6
6
|
useLayoutWidth,
|
|
7
7
|
withPlaceholderImages
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2RKVJQSD.js";
|
|
9
9
|
import {
|
|
10
10
|
Button,
|
|
11
11
|
Input,
|
|
@@ -18,11 +18,11 @@ import {
|
|
|
18
18
|
createHttpStorage,
|
|
19
19
|
useSearchShortcut,
|
|
20
20
|
useUi
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-N7DEVAIP.js";
|
|
22
22
|
import {
|
|
23
23
|
brandTemplate,
|
|
24
24
|
brandedSender
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-JI36FNWE.js";
|
|
26
26
|
|
|
27
27
|
// src/ui/library/TemplateLibrary.jsx
|
|
28
28
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -524,4 +524,4 @@ function TemplateLibrary({ locale = "en", messages, onNotify, ...props }) {
|
|
|
524
524
|
export {
|
|
525
525
|
TemplateLibrary
|
|
526
526
|
};
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
527
|
+
//# sourceMappingURL=chunk-2Y7CDXZM.js.map
|
|
@@ -5,7 +5,34 @@ import {
|
|
|
5
5
|
parseColor,
|
|
6
6
|
toHex,
|
|
7
7
|
toHsl
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-JI36FNWE.js";
|
|
9
|
+
|
|
10
|
+
// src/renderer/prepareMjml.js
|
|
11
|
+
var MJ_IMAGE = /<mj-image\b((?:[^>"']|"[^"]*"|'[^']*')*?)\s*(?:\/>|>\s*<\/mj-image\s*>)/gi;
|
|
12
|
+
var HAS_SRC = /\ssrc\s*=\s*(?:"\s*[^"\s][^"]*"|'\s*[^'\s][^']*')/i;
|
|
13
|
+
function countImagesWithoutPicture(mjml) {
|
|
14
|
+
return [...String(mjml || "").matchAll(MJ_IMAGE)].filter(([, attrs]) => !HAS_SRC.test(attrs)).length;
|
|
15
|
+
}
|
|
16
|
+
function escapeXml(text) {
|
|
17
|
+
return String(text ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
18
|
+
}
|
|
19
|
+
function prepareMjml(mjml, { title = null, preheader = null, lang = "en", emptyImages = "drop" } = {}) {
|
|
20
|
+
let out = String(mjml || "").replace(/<mj-title(?:\s[^>]*)?>[\s\S]*?<\/mj-title>/gi, "").replace(/<mj-preview(?:\s[^>]*)?>[\s\S]*?<\/mj-preview>/gi, "");
|
|
21
|
+
if (emptyImages !== "keep") out = out.replace(MJ_IMAGE, (element, attrs) => HAS_SRC.test(attrs) ? element : "");
|
|
22
|
+
out = out.replace(
|
|
23
|
+
/<mjml(\s[^>]*)?>/i,
|
|
24
|
+
(tag, attrs = "") => /\slang\s*=/.test(attrs) ? tag : `<mjml lang="${escapeXml(lang)}"${attrs}>`
|
|
25
|
+
);
|
|
26
|
+
const head = [
|
|
27
|
+
title && String(title).trim() ? `<mj-title>${escapeXml(String(title).trim())}</mj-title>` : "",
|
|
28
|
+
preheader && String(preheader).trim() ? `<mj-preview>${escapeXml(String(preheader).trim())}</mj-preview>` : ""
|
|
29
|
+
].join("");
|
|
30
|
+
if (!head) return out;
|
|
31
|
+
if (/<mj-head(\s[^>]*)?>/i.test(out)) {
|
|
32
|
+
return out.replace(/<mj-head(\s[^>]*)?>/i, (tag) => `${tag}${head}`);
|
|
33
|
+
}
|
|
34
|
+
return out.replace(/<mjml(\s[^>]*)?>/i, (tag) => `${tag}<mj-head>${head}</mj-head>`);
|
|
35
|
+
}
|
|
9
36
|
|
|
10
37
|
// src/features/brand/extract.js
|
|
11
38
|
function decodeEntities(text) {
|
|
@@ -322,6 +349,8 @@ export {
|
|
|
322
349
|
imageSize,
|
|
323
350
|
logoWidthFor,
|
|
324
351
|
BRAND_IMPORT_ERRORS,
|
|
325
|
-
BRAND_NOTES
|
|
352
|
+
BRAND_NOTES,
|
|
353
|
+
countImagesWithoutPicture,
|
|
354
|
+
prepareMjml
|
|
326
355
|
};
|
|
327
|
-
//# sourceMappingURL=chunk-
|
|
356
|
+
//# sourceMappingURL=chunk-4U7L2NYQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderer/prepareMjml.js","../src/features/brand/extract.js","../src/features/brand/messages.js"],"sourcesContent":["// @ts-check\n/**\n * Prepares MJML before compilation — shared by the server preview, the test\n * send and saving.\n *\n * The subject and the preview text (preheader) are METADATA of the template,\n * typed outside the canvas: they do not live in the engine project. They are\n * written here, into `<mj-head>`, at compile time:\n *\n * `<mj-title>` document title (browser tab, some webmails);\n * `<mj-preview>` the grey text that follows the subject in the inbox.\n *\n * `lang` is set when missing: otherwise screen readers read the message with\n * the voice of the system language.\n *\n * An image with no picture yet (`src` empty or missing — the canvas shows a\n * grey frame, AI drafts leave them for the user) is left out: sent, it would\n * be a broken image in every inbox (a red cross in Outlook). A preview keeps\n * it (`emptyImages: 'keep'`) to draw the frame that is still to fill.\n */\n\n/** An `<mj-image>` element, self-closing or not; its attributes in group 1. */\nconst MJ_IMAGE = /<mj-image\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*?)\\s*(?:\\/>|>\\s*<\\/mj-image\\s*>)/gi;\nconst HAS_SRC = /\\ssrc\\s*=\\s*(?:\"\\s*[^\"\\s][^\"]*\"|'\\s*[^'\\s][^']*')/i;\n\n/**\n * How many images have no picture yet.\n * @param {string} mjml\n */\nexport function countImagesWithoutPicture(mjml) {\n return [...String(mjml || '').matchAll(MJ_IMAGE)].filter(([, attrs]) => !HAS_SRC.test(attrs)).length;\n}\n\nfunction escapeXml(text) {\n return String(text ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n/**\n * @param {string} mjml\n * @param {{title?: string|null, preheader?: string|null, lang?: string, emptyImages?: 'drop'|'keep'}} [meta]\n * @returns {string}\n */\nexport function prepareMjml(mjml, { title = null, preheader = null, lang = 'en', emptyImages = 'drop' } = {}) {\n let out = String(mjml || '')\n .replace(/<mj-title(?:\\s[^>]*)?>[\\s\\S]*?<\\/mj-title>/gi, '')\n .replace(/<mj-preview(?:\\s[^>]*)?>[\\s\\S]*?<\\/mj-preview>/gi, '');\n if (emptyImages !== 'keep') out = out.replace(MJ_IMAGE, (element, attrs) => (HAS_SRC.test(attrs) ? element : ''));\n\n out = out.replace(/<mjml(\\s[^>]*)?>/i, (tag, attrs = '') =>\n /\\slang\\s*=/.test(attrs) ? tag : `<mjml lang=\"${escapeXml(lang)}\"${attrs}>`\n );\n\n const head = [\n title && String(title).trim() ? `<mj-title>${escapeXml(String(title).trim())}</mj-title>` : '',\n preheader && String(preheader).trim() ? `<mj-preview>${escapeXml(String(preheader).trim())}</mj-preview>` : '',\n ].join('');\n if (!head) return out;\n\n if (/<mj-head(\\s[^>]*)?>/i.test(out)) {\n return out.replace(/<mj-head(\\s[^>]*)?>/i, (tag) => `${tag}${head}`);\n }\n return out.replace(/<mjml(\\s[^>]*)?>/i, (tag) => `${tag}<mj-head>${head}</mj-head>`);\n}\n","// @ts-check\n/**\n * BRAND FROM A WEBSITE — what a company's home page says about its look:\n * its name, where its logo is, its main color, the color of its headings\n * and its font.\n *\n * Pure text work on what the server downloaded (src/server/brandImport.js\n * does the downloading, with its safety rules): no network, no DOM, so it\n * runs anywhere and is tested on fixtures.\n *\n * name og:site_name → JSON-LD Organization → application-name → the\n * part of <title> that matches the domain → the domain;\n * logo JSON-LD Organization logo → an <img> called \"logo\" (header\n * first) → apple-touch-icon; SVG and WebP are skipped later\n * (most email apps do not show them);\n * colors counted in the CSS, weighted by where they are used (buttons,\n * links, custom properties named primary/brand/accent, the\n * theme-color meta); grays, alert and social colors set aside;\n * font the body's, mapped to the nearest web-safe stack.\n */\n\nimport { FONT_OPTIONS, PALETTE } from '../../blocks/shared.js';\nimport { contrastRatio, parseColor, toHex, toHsl } from './brand.js';\n\n/**\n * @typedef {{\n * name: string,\n * logos: {url: string, from: 'schema'|'img'|'icon'}[],\n * stylesheets: string[],\n * css: string,\n * themeColor: string,\n * }} PageReading\n */\n\n/* ── HTML ───────────────────────────────────────────────────────────────── */\n\n/** @param {string} text */\nfunction decodeEntities(text) {\n return String(text ?? '')\n .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))\n .replace(/&#(\\d+);/g, (_, dec) => String.fromCodePoint(Number(dec)))\n .replace(/"/g, '\"')\n .replace(/'|'/g, \"'\")\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/ /g, ' ')\n .replace(/&/g, '&');\n}\n\n/**\n * The attributes of one start tag, names in lower case.\n * @param {string} tag `<img src=\"…\" class=logo>`\n * @returns {Record<string, string>}\n */\nexport function tagAttributes(tag) {\n /** @type {Record<string, string>} */\n const attributes = {};\n const inner = tag.replace(/^<[^\\s>/]+/, '').replace(/\\/?>$/, '');\n const pattern = /([^\\s\"'=<>/]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n let match;\n while ((match = pattern.exec(inner))) {\n const name = match[1].toLowerCase();\n if (!(name in attributes)) attributes[name] = decodeEntities(match[2] ?? match[3] ?? match[4] ?? '');\n }\n return attributes;\n}\n\n/** @param {string} html @param {string} name */\nconst startTags = (html, name) => html.match(new RegExp(`<${name}\\\\b[^>]*>`, 'gi')) || [];\n\n/** An http(s) address, resolved against the page; '' otherwise. @param {string|undefined} href @param {string} base */\nfunction resolve(href, base) {\n const value = String(href ?? '').trim();\n if (!value || /^(data|javascript|blob):/i.test(value)) return '';\n try {\n const url = new URL(value, base);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '';\n } catch {\n return '';\n }\n}\n\n/** The largest image of a srcset (\"a.png 1x, a@2x.png 2x\"). @param {string} srcset */\nfunction largestOf(srcset) {\n let best = { url: '', size: 0 };\n for (const part of srcset.split(/,\\s+/)) {\n const [url, descriptor = '1x'] = part.trim().split(/\\s+/);\n const size = parseFloat(descriptor) * (descriptor.endsWith('w') ? 1 : 1000);\n if (url && size > best.size) best = { url, size };\n }\n return best.url;\n}\n\n/** Objects of a JSON-LD script, flattened (@graph, arrays). @param {unknown} value @returns {any[]} */\nfunction jsonLdNodes(value) {\n if (Array.isArray(value)) return value.flatMap(jsonLdNodes);\n if (!value || typeof value !== 'object') return [];\n const node = /** @type {any} */ (value);\n return [node, ...jsonLdNodes(node['@graph']), ...jsonLdNodes(node.publisher), ...jsonLdNodes(node.brand)];\n}\n\nconst ORGANIZATION = /organization|corporation|business|brand|store|restaurant|company|ngo|school|airline|bank|agency/i;\n\n/** The words of a host name that name the company: \"www.acme-corp.co.uk\" → \"acme corp\". @param {string} host */\nfunction hostWords(host) {\n const labels = host.toLowerCase().replace(/^www\\d?\\./, '').split('.');\n const main = labels.length > 2 && labels[labels.length - 2].length <= 3 ? labels[labels.length - 3] : labels[0];\n return main.replace(/[-_]+/g, ' ');\n}\n\n/** @param {string} text */\nconst squash = (text) => text.toLowerCase().replace(/[^a-z0-9À-]+/g, '');\n\n/** @param {unknown} value */\nconst cleanName = (value) =>\n decodeEntities(String(value ?? ''))\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 80);\n\n/**\n * What a home page says about its brand.\n * @param {string} html\n * @param {string} pageUrl the address it was read from (after redirects)\n * @returns {PageReading}\n */\nexport function readPage(html, pageUrl) {\n const source = String(html ?? '');\n const head = source.slice(0, 400_000);\n const baseTag = startTags(head, 'base').map(tagAttributes).find((a) => a.href);\n const base = resolve(baseTag?.href, pageUrl) || pageUrl;\n const host = (() => {\n try {\n return new URL(pageUrl).hostname;\n } catch {\n return '';\n }\n })();\n\n /** @type {Record<string, string>} */\n const meta = {};\n for (const attributes of startTags(head, 'meta').map(tagAttributes)) {\n const key = (attributes.property || attributes.name || '').toLowerCase();\n if (key && attributes.content && !(key in meta)) meta[key] = attributes.content;\n }\n\n // JSON-LD: the organization's own name and logo, when the site states them.\n let schemaName = '';\n let schemaLogo = '';\n for (const [, body] of source.matchAll(/<script\\b[^>]*type\\s*=\\s*[\"']?application\\/ld\\+json[^>]*>([\\s\\S]*?)<\\/script>/gi)) {\n let data;\n try {\n data = JSON.parse(body.trim());\n } catch {\n continue;\n }\n for (const node of jsonLdNodes(data)) {\n const types = [].concat(node['@type'] || []).join(' ');\n if (!ORGANIZATION.test(types)) continue;\n if (!schemaName && typeof node.name === 'string') schemaName = node.name;\n const logo = typeof node.logo === 'string' ? node.logo : node.logo?.url || node.logo?.contentUrl || '';\n if (!schemaLogo && typeof logo === 'string') schemaLogo = resolve(logo, base);\n }\n }\n\n /* Name */\n const title = cleanName(head.match(/<title\\b[^>]*>([\\s\\S]*?)<\\/title>/i)?.[1] || '');\n const words = hostWords(host);\n const fromTitle =\n title\n .split(/\\s+[|–—:·•»-]\\s+/)\n .map((part) => part.trim())\n .find((part) => part && squash(part).length <= 40 && (squash(part).includes(squash(words)) || squash(words).includes(squash(part)))) || '';\n const fromHost = words.replace(/\\b\\p{L}/gu, (letter) => letter.toUpperCase());\n const name = cleanName(meta['og:site_name'] || schemaName || meta['application-name'] || fromTitle || fromHost);\n\n /* Logo candidates, best first */\n /** @type {PageReading['logos']} */\n const logos = [];\n const add = (/** @type {string} */ url, /** @type {PageReading['logos'][number]['from']} */ from) => {\n if (url && !logos.some((logo) => logo.url === url)) logos.push({ url, from });\n };\n add(schemaLogo, 'schema');\n /** @type {{url: string, score: number}[]} */\n const images = [];\n const headerEnd = (() => {\n const at = source.search(/<\\/header>/i);\n return at > 0 ? at : Math.min(source.length, 20_000);\n })();\n for (const match of source.matchAll(/<img\\b[^>]*>/gi)) {\n const attributes = tagAttributes(match[0]);\n const own = [attributes.class, attributes.id, attributes.alt, attributes.src, attributes['data-src']].join(' ').toLowerCase();\n const before = source.slice(Math.max(0, (match.index ?? 0) - 400), match.index).toLowerCase();\n const parent = /<(a|div|span|figure|picture)\\b[^>]*(class|id)\\s*=\\s*[\"'][^\"']*logo[^\"']*[\"'][^>]*>[^<]*(<source\\b[^>]*>\\s*)*$/i.test(before);\n if (!own.includes('logo') && !parent) continue;\n // Partner, client and payment logos are someone else's.\n if (/partner|client|customer|sponsor|award|payment|badge|trust|press|app-?store|google-?play/.test(own)) continue;\n const url = resolve(attributes.srcset ? largestOf(attributes.srcset) : attributes.src || attributes['data-src'] || '', base);\n if (!url) continue;\n let score = own.includes('logo') ? 3 : 2;\n if ((match.index ?? 0) < headerEnd) score += 3;\n if (name && squash(attributes.alt || '').includes(squash(name))) score += 2;\n if (/footer/.test(own)) score -= 2;\n images.push({ url, score: score - images.length * 0.01 });\n }\n images.sort((a, b) => b.score - a.score).forEach((image) => add(image.url, 'img'));\n /** @type {{url: string, size: number}[]} */\n const icons = [];\n for (const attributes of startTags(head, 'link').map(tagAttributes)) {\n const rel = (attributes.rel || '').toLowerCase();\n if (!/apple-touch-icon|icon/.test(rel)) continue;\n const size = parseInt(attributes.sizes || (rel.includes('apple') ? '180' : '0'), 10) || 0;\n if (size >= 96) icons.push({ url: resolve(attributes.href, base), size });\n }\n icons.sort((a, b) => b.size - a.size).forEach((icon) => add(icon.url, 'icon'));\n\n /* Styles */\n const stylesheets = startTags(head, 'link')\n .map(tagAttributes)\n .filter((a) => /(^|\\s)stylesheet(\\s|$)/i.test(a.rel || '') && !/^print$/i.test((a.media || '').trim()))\n .map((a) => resolve(a.href, base))\n .filter(Boolean)\n // Fonts and third-party widgets say nothing about the brand's colors.\n .filter((href) => !/fonts\\.googleapis|use\\.typekit|fontawesome|cookie|consent|onetrust/i.test(href));\n const css = [\n ...[...source.matchAll(/<style\\b[^>]*>([\\s\\S]*?)<\\/style>/gi)].map((m) => m[1]),\n // Inline styles of buttons and links count too: `<a class=\"btn\" style=\"…\">`.\n ...[...source.matchAll(/<(a|button)\\b[^>]*>/gi)]\n .map((m) => tagAttributes(m[0]))\n .filter((a) => a.style)\n .map((a) => `${/btn|button|cta/i.test(a.class || '') ? '.btn' : 'a'}{${a.style}}`),\n ].join('\\n');\n\n return {\n name,\n logos,\n stylesheets: [...new Set(stylesheets)],\n css,\n themeColor: meta['theme-color'] || meta['msapplication-tilecolor'] || '',\n };\n}\n\n/* ── CSS ────────────────────────────────────────────────────────────────── */\n\nconst PRIMARY_NAME = /primary|brand|accent|main|theme|highlight|cta/i;\nconst NOT_A_BRAND_NAME = /text|foreground|fg|border|muted|gr[ae]y|neutral|surface|shadow|overlay|success|error|danger|warning|info|disabled|placeholder/i;\nconst NOISE = /error|danger|success|warning|alert|invalid|valid\\b|notice|toast|badge|disabled|facebook|twitter|linkedin|instagram|youtube|whatsapp|pinterest|tiktok|social|cookie|consent|onetrust|ot-|cmp|gdpr|wp-block|has-[\\w-]+-(background-)?color|syntax|hljs|code/i;\nconst COLOR_IN_VALUE = /#[0-9a-f]{3,8}\\b|rgba?\\([^)]*\\)|hsla?\\([^)]*\\)/gi;\nconst COLOR_PROPERTY = /^(color|background|background-color|border|border-color|border-(top|bottom|left|right)(-color)?|fill|stroke|outline(-color)?|text-decoration-color|accent-color|caret-color)$/;\n\n/** @param {string} css */\nconst stripComments = (css) => css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n/** Rules as [selector, declarations], @media flattened. @param {string} css */\nfunction rulesOf(css) {\n /** @type {[string, [string, string][]][]} */\n const rules = [];\n for (const [, selector, body] of stripComments(css).matchAll(/([^{}]+)\\{([^{}]*)\\}/g)) {\n const declarations = body\n .split(';')\n .map((part) => {\n const colon = part.indexOf(':');\n return /** @type {[string, string]} */ ([part.slice(0, colon).trim().toLowerCase(), part.slice(colon + 1).trim()]);\n })\n .filter(([property, value]) => property && value);\n if (declarations.length) rules.push([selector.trim(), declarations]);\n }\n return rules;\n}\n\n/**\n * A custom property's value as a color: `#2977bc`, `rgb(…)`, and the bare\n * channels design systems keep there (`41 119 188`, `210 64% 45%`).\n * @param {string} value\n */\nfunction customColor(value) {\n const direct = parseColor(value.replace(/\\s*!important$/, ''));\n if (direct) return direct;\n if (/^\\d{1,3}(\\.\\d+)?\\s+\\d{1,3}(\\.\\d+)?\\s+\\d{1,3}(\\.\\d+)?$/.test(value)) return parseColor(`rgb(${value})`);\n if (/^\\d{1,3}(\\.\\d+)?(deg)?\\s+\\d{1,3}(\\.\\d+)?%\\s+\\d{1,3}(\\.\\d+)?%$/.test(value)) return parseColor(`hsl(${value.replace('deg', '')})`);\n return null;\n}\n\n/**\n * `var(--x, fallback)` replaced by what --x holds, a few levels deep.\n * @param {string} value @param {Map<string, string>} custom\n */\nfunction resolveVars(value, custom) {\n let out = value;\n for (let depth = 0; depth < 4 && out.includes('var('); depth += 1) {\n out = out.replace(/var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*([^()]*(?:\\([^()]*\\))?[^()]*))?\\)/g, (_, name, fallback) => custom.get(name) ?? fallback ?? '');\n }\n return out;\n}\n\n/** @param {{r: number, g: number, b: number}} a @param {{r: number, g: number, b: number}} b */\nconst distance = (a, b) => Math.hypot(a.r - b.r, a.g - b.g, a.b - b.b);\n\n/** Colors of one value, as found (not resolved through var()). @param {string} value */\nconst colorsIn = (value) => (value.match(COLOR_IN_VALUE) || []).map((text) => parseColor(text)).filter((c) => c && c.a >= 0.6);\n\n/**\n * The web-safe stack nearest to a CSS font-family.\n * @param {string} family\n * @returns {string|undefined}\n */\nexport function nearestFont(family) {\n const stack = String(family ?? '')\n .toLowerCase()\n .replace(/[\"']/g, '');\n if (!stack.trim() || /^(inherit|initial|unset|revert)$/.test(stack.trim())) return undefined;\n const first = stack.split(',')[0].trim();\n const known = FONT_OPTIONS.find((option) => first.startsWith(option.label.toLowerCase()));\n if (known) return known.value;\n if (/^helvetica/.test(first)) return FONT_OPTIONS[0].value;\n if (/^times/.test(first)) return FONT_OPTIONS.find((o) => o.label === 'Times')?.value;\n if (/monospace|mono\\b|courier|consolas/.test(stack)) return FONT_OPTIONS.find((o) => o.label === 'Courier')?.value;\n // Web fonts: their family decides (serif, else sans-serif).\n if (/(^|,)\\s*serif\\s*(,|$)|garamond|playfair|merriweather|lora|baskerville|caslon|didot|bodoni|crimson|libre baskerville|source serif|noto serif|pt serif/.test(stack)) {\n return FONT_OPTIONS.find((o) => o.label === 'Georgia')?.value;\n }\n return FONT_OPTIONS[0].value;\n}\n\n/**\n * Main color, headings color and font of a site, from its CSS.\n * @param {{css: string, themeColor?: string}} input\n * @returns {{primary?: string, heading?: string, font?: string}}\n */\nexport function brandFromCss({ css, themeColor = '' }) {\n const rules = rulesOf(String(css ?? ''));\n\n /** @type {Map<string, string>} */\n const custom = new Map();\n for (const [selector, declarations] of rules) {\n // :root first: a theme's dark variant must not replace it.\n const root = /^(:root|html|body|\\*)(\\s*,|$)/i.test(selector);\n for (const [property, value] of declarations) {\n if (property.startsWith('--') && (root || !custom.has(property))) custom.set(property, value.replace(/\\s*!important$/, ''));\n }\n }\n\n /** @type {{color: {r: number, g: number, b: number}, weight: number}[]} */\n const found = [];\n /** @type {{color: {r: number, g: number, b: number}, weight: number}[]} */\n const darkText = [];\n /** @type {{font: string, weight: number}[]} */\n const fonts = [];\n const count = (/** @type {{r: number, g: number, b: number}} */ color, /** @type {number} */ weight) => found.push({ color, weight });\n\n for (const [selector, declarations] of rules) {\n if (selector.startsWith('@')) continue;\n const noise = NOISE.test(selector);\n const emphasis = /btn|button|cta|primary|brand|accent|hero|highlight/i.test(selector) ? 4 : /(^|[\\s,>+~])a([:.[\\s,]|$)|link/i.test(selector) ? 2 : 1;\n const isHeading = /(^|[\\s,>])h[1-3]\\b|title|heading|headline/i.test(selector);\n const isBody = /^(html|body|:root)(\\s*,|$)|(^|,)\\s*(html|body)\\s*(,|$)/i.test(selector);\n for (const [property, raw] of declarations) {\n if (property.startsWith('--')) {\n const color = customColor(resolveVars(raw, custom));\n if (color && color.a >= 0.6 && PRIMARY_NAME.test(property) && !NOT_A_BRAND_NAME.test(property)) count(color, 10);\n continue;\n }\n const value = resolveVars(raw, custom);\n if (property === 'font-family' || property === 'font') {\n const family = property === 'font' ? value.replace(/^.*?\\d[\\w%.]*(\\/[\\w%.]+)?\\s+/, '') : value;\n if (isBody || isHeading) fonts.push({ font: family, weight: isBody ? 2 : 1 });\n continue;\n }\n if (!COLOR_PROPERTY.test(property) || noise) continue;\n for (const color of colorsIn(value)) {\n if (!color) continue;\n count(color, emphasis * (property.startsWith('background') ? 1.5 : 1));\n if (property === 'color' && (isHeading || isBody)) darkText.push({ color, weight: isHeading ? 2 : 1 });\n }\n }\n }\n const theme = parseColor(themeColor);\n if (theme && theme.a >= 0.6) count(theme, 15);\n\n // Near colors are one color (hover shades, rounding).\n /** @type {{color: {r: number, g: number, b: number}, weight: number}[]} */\n const clusters = [];\n for (const entry of found) {\n const near = clusters.find((cluster) => distance(cluster.color, entry.color) < 24);\n if (near) near.weight += entry.weight;\n else clusters.push({ ...entry });\n }\n clusters.sort((a, b) => b.weight - a.weight);\n const primary = clusters.find(({ color }) => {\n const { s, l } = toHsl(color);\n return s >= 0.25 && l >= 0.15 && l <= 0.85;\n });\n\n /** @type {{primary?: string, heading?: string, font?: string}} */\n const result = {};\n if (primary) result.primary = toHex(primary.color);\n\n const dark = darkText\n .filter(({ color }) => toHsl(color).l < 0.3)\n .reduce((/** @type {Map<string, number>} */ map, { color, weight }) => map.set(toHex(color), (map.get(toHex(color)) || 0) + weight), new Map());\n const heading = [...dark.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];\n if (heading) result.heading = heading;\n else if (result.primary && contrastRatio(result.primary, PALETTE.white) >= 7) result.heading = result.primary;\n else result.heading = PALETTE.ink;\n\n const font = fonts.sort((a, b) => b.weight - a.weight).map(({ font }) => nearestFont(font)).find(Boolean);\n if (font) result.font = font;\n return result;\n}\n\n/* ── Images ─────────────────────────────────────────────────────────────── */\n\n/**\n * Width and height of a PNG, GIF or JPEG, from its header; null otherwise.\n * @param {Uint8Array} bytes\n * @returns {{width: number, height: number}|null}\n */\nexport function imageSize(bytes) {\n if (!bytes || bytes.length < 24) return null;\n const at = (/** @type {number} */ i) => bytes[i];\n // PNG: IHDR right after the signature.\n if (at(0) === 0x89 && at(1) === 0x50 && at(2) === 0x4e && at(3) === 0x47) {\n const read32 = (/** @type {number} */ i) => ((at(i) << 24) | (at(i + 1) << 16) | (at(i + 2) << 8) | at(i + 3)) >>> 0;\n return { width: read32(16), height: read32(20) };\n }\n // GIF: logical screen size.\n if (at(0) === 0x47 && at(1) === 0x49 && at(2) === 0x46) {\n return { width: at(6) | (at(7) << 8), height: at(8) | (at(9) << 8) };\n }\n // JPEG: the first start-of-frame marker.\n if (at(0) === 0xff && at(1) === 0xd8) {\n let i = 2;\n while (i + 9 < bytes.length) {\n if (at(i) !== 0xff) {\n i += 1;\n continue;\n }\n const marker = at(i + 1);\n if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0xff) {\n i += marker === 0xff ? 1 : 2;\n continue;\n }\n const length = (at(i + 2) << 8) | at(i + 3);\n if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {\n return { height: (at(i + 5) << 8) | at(i + 6), width: (at(i + 7) << 8) | at(i + 8) };\n }\n i += 2 + length;\n }\n }\n return null;\n}\n\n/**\n * The width a logo is shown at in an email: a wordmark wider, a square mark\n * smaller; never wider than the image itself (a blurry logo is worse than a\n * small one). Retina images (twice the size shown) are common: halved first.\n * @param {{width: number, height: number}} size\n */\nexport function logoWidthFor({ width, height }) {\n if (!width || !height) return 140;\n const ratio = width / height;\n const shown = width >= 240 ? width / 2 : width;\n const target = ratio >= 2.5 ? 180 : ratio >= 1.3 ? 140 : 72;\n return Math.max(24, Math.round(Math.min(target, shown)));\n}\n","// @ts-check\n/**\n * What the user is told about reading a brand from a website: why it failed\n * (the server's answer) and what it could not find (notes under the field).\n * English source strings, translated where they are shown.\n */\n\n/**\n * Why a website could not be read: [HTTP status, message], by\n * BrandImportError code (src/server/brandImport.js).\n * @type {Record<string, [number, string]>}\n */\nexport const BRAND_IMPORT_ERRORS = {\n BAD_URL: [400, 'Enter the address of a website, like acme.com'],\n PRIVATE_ADDRESS: [400, 'This address is not a public website'],\n UNREACHABLE: [422, 'The website could not be reached. Check the address.'],\n TIMEOUT: [422, 'The website took too long to answer'],\n HTTP_ERROR: [422, 'The website does not let the builder read it. Fill in your brand below.'],\n NOT_HTML: [422, 'This address is not a web page'],\n TOO_MANY_REDIRECTS: [422, 'The website redirects too many times'],\n TOO_LARGE: [422, 'The website sent too much data'],\n};\n\n/** What could not be read, by note code. @type {Record<string, string>} */\nexport const BRAND_NOTES = {\n LOGO_NOT_FOUND: 'No logo was found on the website. Upload it below.',\n LOGO_UNSUPPORTED: 'The logo on the website is an SVG or WebP image, which most email apps do not show. Upload a PNG or JPEG version.',\n LOGO_ICON: 'The website icon is used as the logo. Upload your full logo if you have one.',\n COLORS_NOT_FOUND: 'No main color was found on the website. Pick one below.',\n};\n"],"mappings":"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","names":["font"]}
|
|
@@ -10,16 +10,17 @@ import {
|
|
|
10
10
|
useBrand,
|
|
11
11
|
useLayoutWidth,
|
|
12
12
|
withThumbnailImages
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2RKVJQSD.js";
|
|
14
14
|
import {
|
|
15
15
|
BRAND_NOTES,
|
|
16
|
+
countImagesWithoutPicture,
|
|
16
17
|
logoWidthFor
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-4U7L2NYQ.js";
|
|
18
19
|
import {
|
|
19
20
|
cleanRichText,
|
|
20
21
|
escapeText,
|
|
21
22
|
textOfHtml
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SAIW46IC.js";
|
|
23
24
|
import {
|
|
24
25
|
Button,
|
|
25
26
|
Dialog,
|
|
@@ -55,24 +56,25 @@ import {
|
|
|
55
56
|
useSearchShortcut,
|
|
56
57
|
useT,
|
|
57
58
|
useUi
|
|
58
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-N7DEVAIP.js";
|
|
59
60
|
import {
|
|
60
61
|
EMAIL_BUILDER_VERSION,
|
|
61
62
|
EMAIL_DEVICES,
|
|
62
63
|
createDocument,
|
|
63
64
|
imageUploadError,
|
|
64
65
|
readDocument
|
|
65
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-H4W65GBX.js";
|
|
66
67
|
import {
|
|
67
68
|
LONGHAND_SPACING,
|
|
69
|
+
MJML_DEFAULTS,
|
|
68
70
|
formatSpacing,
|
|
69
71
|
parseSpacing,
|
|
70
72
|
toAttributes,
|
|
71
73
|
toValues
|
|
72
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-OWVXKMLI.js";
|
|
73
75
|
import {
|
|
74
76
|
isSafeUrl
|
|
75
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-W424KPCR.js";
|
|
76
78
|
import {
|
|
77
79
|
CUSTOM_GROUP,
|
|
78
80
|
DEFAULT_BUTTON_URL,
|
|
@@ -121,7 +123,7 @@ import {
|
|
|
121
123
|
tokenOf,
|
|
122
124
|
translatorFor,
|
|
123
125
|
validateCustomVariable
|
|
124
|
-
} from "./chunk-
|
|
126
|
+
} from "./chunk-JI36FNWE.js";
|
|
125
127
|
|
|
126
128
|
// src/core/events.js
|
|
127
129
|
function createEmitter({ onError } = {}) {
|
|
@@ -154,62 +156,6 @@ function createEmitter({ onError } = {}) {
|
|
|
154
156
|
};
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
// src/core/mjmlDefaults.js
|
|
158
|
-
var MJML_DEFAULTS = {
|
|
159
|
-
"mj-body": { width: "600px" },
|
|
160
|
-
"mj-section": {
|
|
161
|
-
"background-repeat": "repeat",
|
|
162
|
-
"background-size": "auto",
|
|
163
|
-
"background-position": "top center",
|
|
164
|
-
padding: "20px 0",
|
|
165
|
-
"text-align": "center"
|
|
166
|
-
},
|
|
167
|
-
"mj-wrapper": { "background-repeat": "repeat", "background-size": "auto", "background-position": "top center", padding: "20px 0" },
|
|
168
|
-
"mj-column": { "vertical-align": "top" },
|
|
169
|
-
"mj-text": {
|
|
170
|
-
align: "left",
|
|
171
|
-
color: "#000000",
|
|
172
|
-
"font-family": "Ubuntu, Helvetica, Arial, sans-serif",
|
|
173
|
-
"font-size": "13px",
|
|
174
|
-
"line-height": "1",
|
|
175
|
-
padding: "10px 25px"
|
|
176
|
-
},
|
|
177
|
-
"mj-image": { align: "center", height: "auto", padding: "10px 25px" },
|
|
178
|
-
"mj-button": {
|
|
179
|
-
align: "center",
|
|
180
|
-
"background-color": "#414141",
|
|
181
|
-
"border-radius": "3px",
|
|
182
|
-
color: "#ffffff",
|
|
183
|
-
"font-family": "Ubuntu, Helvetica, Arial, sans-serif",
|
|
184
|
-
"font-size": "13px",
|
|
185
|
-
"font-weight": "normal",
|
|
186
|
-
"inner-padding": "10px 25px",
|
|
187
|
-
"line-height": "120%",
|
|
188
|
-
padding: "10px 25px"
|
|
189
|
-
},
|
|
190
|
-
"mj-divider": { "border-color": "#000000", "border-style": "solid", "border-width": "4px", padding: "10px 25px", width: "100%", align: "center" },
|
|
191
|
-
"mj-spacer": { height: "20px" },
|
|
192
|
-
"mj-social": {
|
|
193
|
-
align: "center",
|
|
194
|
-
"border-radius": "3px",
|
|
195
|
-
"icon-size": "20px",
|
|
196
|
-
mode: "horizontal",
|
|
197
|
-
padding: "10px 25px"
|
|
198
|
-
},
|
|
199
|
-
"mj-social-element": { "border-radius": "3px", padding: "4px" },
|
|
200
|
-
"mj-navbar": { align: "center" },
|
|
201
|
-
"mj-navbar-link": {
|
|
202
|
-
color: "#000000",
|
|
203
|
-
"font-family": "Ubuntu, Helvetica, Arial, sans-serif",
|
|
204
|
-
"font-size": "13px",
|
|
205
|
-
"font-weight": "normal",
|
|
206
|
-
"line-height": "22px",
|
|
207
|
-
padding: "15px 10px",
|
|
208
|
-
"text-transform": "uppercase"
|
|
209
|
-
},
|
|
210
|
-
"mj-hero": { "background-color": "#ffffff", padding: "0px" }
|
|
211
|
-
};
|
|
212
|
-
|
|
213
159
|
// src/core/EmailEditor.js
|
|
214
160
|
function resolveBlock(block, t = identity) {
|
|
215
161
|
const text = (value) => typeof value === "function" ? String(value(t)) : value;
|
|
@@ -5274,11 +5220,19 @@ function Builder({
|
|
|
5274
5220
|
if (!current.id) clearLocalDraft();
|
|
5275
5221
|
latest.current.onSaved?.(result.template);
|
|
5276
5222
|
const warnings = result.warnings || [];
|
|
5223
|
+
const withoutPicture = auto ? 0 : countImagesWithoutPicture(mjml6);
|
|
5277
5224
|
if (warnings.length) {
|
|
5278
5225
|
notify(
|
|
5279
5226
|
"warning",
|
|
5280
5227
|
t("Saved, with {count} MJML warning(s): {message}", { count: warnings.length, message: warnings[0].message })
|
|
5281
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
|
+
);
|
|
5282
5236
|
} else if (!auto) {
|
|
5283
5237
|
notify(
|
|
5284
5238
|
"success",
|
|
@@ -5632,4 +5586,4 @@ export {
|
|
|
5632
5586
|
EmailBuilder,
|
|
5633
5587
|
EmailBuilderBoundary
|
|
5634
5588
|
};
|
|
5635
|
-
//# sourceMappingURL=chunk-
|
|
5589
|
+
//# sourceMappingURL=chunk-7QQH6MTV.js.map
|