best-email-builder 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +190 -0
- package/README.md +290 -20
- package/dist/ai-server.js +68 -0
- package/dist/ai-server.js.map +1 -0
- package/dist/ai.js +852 -0
- package/dist/ai.js.map +1 -0
- package/dist/builder.js +16 -8
- package/dist/chunk-2RZHGAH5.js +64 -0
- package/dist/chunk-2RZHGAH5.js.map +1 -0
- package/dist/chunk-3LKF4QBL.js +595 -0
- package/dist/chunk-3LKF4QBL.js.map +1 -0
- package/dist/chunk-5SASKCUC.js +251 -0
- package/dist/chunk-5SASKCUC.js.map +1 -0
- package/dist/{chunk-JOC752I3.js → chunk-6D3SLT5W.js} +27 -29
- package/dist/chunk-6D3SLT5W.js.map +1 -0
- package/dist/chunk-7U6BAKM7.js +119 -0
- package/dist/chunk-7U6BAKM7.js.map +1 -0
- package/dist/chunk-DFNHICTF.js +1359 -0
- package/dist/chunk-DFNHICTF.js.map +1 -0
- package/dist/chunk-ECHGJZ2W.js +327 -0
- package/dist/chunk-ECHGJZ2W.js.map +1 -0
- package/dist/chunk-KXJJSVN5.js +98 -0
- package/dist/chunk-KXJJSVN5.js.map +1 -0
- package/dist/{chunk-ZSUHOCCE.js → chunk-LXR4QTGP.js} +123 -579
- package/dist/chunk-LXR4QTGP.js.map +1 -0
- package/dist/{chunk-BVOVAEEJ.js → chunk-LYMJ2TBX.js} +2 -2
- package/dist/chunk-MPX5CWTZ.js +1687 -0
- package/dist/chunk-MPX5CWTZ.js.map +1 -0
- package/dist/chunk-PB7YZ3WP.js +5635 -0
- package/dist/chunk-PB7YZ3WP.js.map +1 -0
- package/dist/chunk-UFKCLU2D.js +255 -0
- package/dist/chunk-UFKCLU2D.js.map +1 -0
- package/dist/index.js +51 -20
- package/dist/index.js.map +1 -1
- package/dist/library.js +12 -7
- package/dist/server.js +513 -214
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +12 -0
- package/dist/types/ai/GenerateButton.d.ts +4 -0
- package/dist/types/ai/RewriteText.d.ts +5 -0
- package/dist/types/ai/SubjectIdeas.d.ts +25 -0
- package/dist/types/ai/chip.d.ts +1 -0
- package/dist/types/ai/claude.d.ts +21 -0
- package/dist/types/ai/createAi.d.ts +66 -0
- package/dist/types/ai/draft.d.ts +163 -0
- package/dist/types/ai/generator.d.ts +123 -0
- package/dist/types/ai/i18n/fr.d.ts +2 -0
- package/dist/types/ai/i18n/index.d.ts +2 -0
- package/dist/types/ai/index.d.ts +9 -0
- package/dist/types/ai/keys.d.ts +46 -0
- package/dist/types/ai/prompt.d.ts +76 -0
- package/dist/types/ai/providers.d.ts +67 -0
- package/dist/types/ai/purposes.d.ts +48 -0
- package/dist/types/ai/rewrite.d.ts +83 -0
- package/dist/types/ai/server.d.ts +9 -0
- package/dist/types/ai/subjects.d.ts +97 -0
- package/dist/types/blocks/shared.d.ts +20 -0
- package/dist/types/core/EmailEditor.d.ts +72 -19
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/core/mjmlDefaults.d.ts +13 -0
- package/dist/types/core/scrollbars.d.ts +1 -1
- package/dist/types/defaultEditor.d.ts +4 -1
- package/dist/types/engine/NativeAdapter.d.ts +28 -0
- package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
- package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
- package/dist/types/engine/canvas/icons.d.ts +14 -0
- package/dist/types/engine/canvas/morph.d.ts +21 -0
- package/dist/types/engine/canvasMjml.d.ts +30 -0
- package/dist/types/engine/mjml.d.ts +31 -0
- package/dist/types/engine/model.d.ts +130 -0
- package/dist/types/engine/placement.d.ts +69 -0
- package/dist/types/engine/richText.d.ts +15 -0
- package/dist/types/engine/unwrapModule.d.ts +29 -0
- package/dist/types/features/brand/BrandButton.d.ts +4 -0
- package/dist/types/features/brand/brand.d.ts +108 -0
- package/dist/types/features/brand/extract.d.ts +61 -0
- package/dist/types/features/brand/index.d.ts +2 -0
- package/dist/types/features/brand/messages.d.ts +13 -0
- package/dist/types/features/brand/useBrand.d.ts +22 -0
- package/dist/types/features/preview/PreviewButton.d.ts +1 -1
- package/dist/types/features/preview/darkMode.d.ts +65 -0
- package/dist/types/features/variables/VariablePicker.d.ts +3 -2
- package/dist/types/i18n/index.d.ts +8 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/server/brand.d.ts +32 -0
- package/dist/types/server/brandImport.d.ts +93 -0
- package/dist/types/server/compileMjml.d.ts +2 -0
- package/dist/types/server/handler.d.ts +26 -0
- package/dist/types/server/index.d.ts +5 -1
- package/dist/types/storage/client.d.ts +57 -3
- package/dist/types/ui/EmailBuilder.d.ts +5 -1
- package/dist/types/ui/EmailStudio.d.ts +6 -1
- package/dist/types/ui/PropertiesPanel.d.ts +2 -1
- package/dist/types/ui/fields.d.ts +2 -1
- package/dist/types/ui/primitives/button.d.ts +1 -1
- package/dist/types/ui/primitives/color-picker.d.ts +2 -1
- package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
- package/dist/types/ui/useLayoutWidth.d.ts +1 -1
- package/package.json +27 -7
- package/dist/chunk-3M36VVXS.js +0 -665
- package/dist/chunk-3M36VVXS.js.map +0 -1
- package/dist/chunk-57CTQNTK.js +0 -56
- package/dist/chunk-57CTQNTK.js.map +0 -1
- package/dist/chunk-ILRAQTHS.js +0 -3677
- package/dist/chunk-ILRAQTHS.js.map +0 -1
- package/dist/chunk-JOC752I3.js.map +0 -1
- package/dist/chunk-ZSUHOCCE.js.map +0 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
- package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
- package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
- /package/dist/{chunk-BVOVAEEJ.js.map → chunk-LYMJ2TBX.js.map} +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** A fresh id: short, unique for the page's lifetime, `[a-z0-9]` only. */
|
|
2
|
+
export function createId(): string;
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} tag
|
|
5
|
+
* @param {{attributes?: Record<string, string>, children?: EmailNode[], content?: string|null, id?: string}} [parts]
|
|
6
|
+
* @returns {EmailNode}
|
|
7
|
+
*/
|
|
8
|
+
export function createNode(tag: string, { attributes, children, content, id }?: {
|
|
9
|
+
attributes?: Record<string, string>;
|
|
10
|
+
children?: EmailNode[];
|
|
11
|
+
content?: string | null;
|
|
12
|
+
id?: string;
|
|
13
|
+
}): EmailNode;
|
|
14
|
+
/**
|
|
15
|
+
* Deep copy with NEW ids everywhere (duplicate, paste, insert a block twice).
|
|
16
|
+
* @param {EmailNode} node
|
|
17
|
+
* @returns {EmailNode}
|
|
18
|
+
*/
|
|
19
|
+
export function cloneWithNewIds(node: EmailNode): EmailNode;
|
|
20
|
+
/**
|
|
21
|
+
* The node with this id and its ancestors (root first), or null.
|
|
22
|
+
* @param {EmailNode} root @param {string} id
|
|
23
|
+
* @returns {EmailNode[]|null}
|
|
24
|
+
*/
|
|
25
|
+
export function pathTo(root: EmailNode, id: string): EmailNode[] | null;
|
|
26
|
+
/** @param {EmailNode} root @param {string|null|undefined} id */
|
|
27
|
+
export function findNode(root: EmailNode, id: string | null | undefined): EmailNode | null;
|
|
28
|
+
/** @param {EmailNode} root @param {string} id */
|
|
29
|
+
export function parentOf(root: EmailNode, id: string): EmailNode | null;
|
|
30
|
+
/**
|
|
31
|
+
* Closest node (itself included) whose tag is one of `tags`.
|
|
32
|
+
* @param {EmailNode} root @param {string} id @param {Set<string>|string[]} tags
|
|
33
|
+
*/
|
|
34
|
+
export function closestOfTags(root: EmailNode, id: string, tags: Set<string> | string[]): EmailNode | null;
|
|
35
|
+
/** First node of a tag, depth first. @param {EmailNode} root @param {string} tag @returns {EmailNode|null} */
|
|
36
|
+
export function findFirst(root: EmailNode, tag: string): EmailNode | null;
|
|
37
|
+
/** Every node, depth first. @param {EmailNode} root @param {(node: EmailNode) => void} visit */
|
|
38
|
+
export function walk(root: EmailNode, visit: (node: EmailNode) => void): void;
|
|
39
|
+
/**
|
|
40
|
+
* The tree with the node `id` replaced by `update(node)`.
|
|
41
|
+
* @param {EmailNode} root @param {string} id @param {(node: EmailNode) => EmailNode} update
|
|
42
|
+
* @returns {EmailNode}
|
|
43
|
+
*/
|
|
44
|
+
export function updateNode(root: EmailNode, id: string, update: (node: EmailNode) => EmailNode): EmailNode;
|
|
45
|
+
/**
|
|
46
|
+
* Sets (string) or removes (null, '') attributes.
|
|
47
|
+
* @param {EmailNode} root @param {string} id @param {Record<string, string|null|undefined>} attributes
|
|
48
|
+
*/
|
|
49
|
+
export function setAttributes(root: EmailNode, id: string, attributes: Record<string, string | null | undefined>): EmailNode;
|
|
50
|
+
/** @param {EmailNode} root @param {string} id @param {string} content */
|
|
51
|
+
export function setContent(root: EmailNode, id: string, content: string): EmailNode;
|
|
52
|
+
/**
|
|
53
|
+
* Inserts nodes into `parentId` at `index` (clamped).
|
|
54
|
+
* @param {EmailNode} root @param {string} parentId @param {number} index @param {EmailNode[]} nodes
|
|
55
|
+
*/
|
|
56
|
+
export function insertNodes(root: EmailNode, parentId: string, index: number, nodes: EmailNode[]): EmailNode;
|
|
57
|
+
/** @param {EmailNode} root @param {string} id */
|
|
58
|
+
export function removeNode(root: EmailNode, id: string): EmailNode;
|
|
59
|
+
/**
|
|
60
|
+
* Moves a node under `parentId` at `index`, where `index` counts the target's
|
|
61
|
+
* children BEFORE the node leaves its place (what a drop indicator shows).
|
|
62
|
+
* @param {EmailNode} root @param {string} id @param {string} parentId @param {number} index
|
|
63
|
+
*/
|
|
64
|
+
export function moveNode(root: EmailNode, id: string, parentId: string, index: number): EmailNode;
|
|
65
|
+
/**
|
|
66
|
+
* Index of a node among its siblings, and their count.
|
|
67
|
+
* @param {EmailNode} root @param {string} id
|
|
68
|
+
*/
|
|
69
|
+
export function positionOf(root: EmailNode, id: string): {
|
|
70
|
+
parent: null;
|
|
71
|
+
index: number;
|
|
72
|
+
count: number;
|
|
73
|
+
} | {
|
|
74
|
+
parent: EmailNode;
|
|
75
|
+
index: number;
|
|
76
|
+
count: number;
|
|
77
|
+
};
|
|
78
|
+
/** @param {EmailNode} root */
|
|
79
|
+
export function toProject(root: EmailNode): {
|
|
80
|
+
format: number;
|
|
81
|
+
root: any;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Reads a stored project back into a tree. Ids are kept when they are valid
|
|
85
|
+
* and unique, replaced otherwise. Throws on anything that is not a project of
|
|
86
|
+
* this format.
|
|
87
|
+
* @param {unknown} project
|
|
88
|
+
* @returns {EmailNode}
|
|
89
|
+
*/
|
|
90
|
+
export function fromProject(project: unknown): EmailNode;
|
|
91
|
+
/**
|
|
92
|
+
* THE EMAIL MODEL — the builder's own document, an MJML tree with ids.
|
|
93
|
+
*
|
|
94
|
+
* { id, tag: 'mj-text', attributes: { color: '#2b3038' }, children: [], content: 'Hello' }
|
|
95
|
+
*
|
|
96
|
+
* One node per MJML element. "Ending" elements (`mj-text`, `mj-button`…)
|
|
97
|
+
* keep their inner HTML in `content`; the others hold `children`. Ids are
|
|
98
|
+
* opaque, unique within a tree, made of `[a-z0-9]` so they can live in a CSS
|
|
99
|
+
* class (the canvas finds an element by them, see `canvasMjml.js`).
|
|
100
|
+
*
|
|
101
|
+
* Every operation is PURE: it returns a new tree and never changes the one it
|
|
102
|
+
* got, sharing the untouched branches. The undo history is then just a list
|
|
103
|
+
* of trees.
|
|
104
|
+
*
|
|
105
|
+
* Dependency-free: runs in the browser, on the server and in unit tests.
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* @typedef {{
|
|
109
|
+
* id: string,
|
|
110
|
+
* tag: string,
|
|
111
|
+
* attributes: Record<string, string>,
|
|
112
|
+
* children: EmailNode[],
|
|
113
|
+
* content: string|null,
|
|
114
|
+
* }} EmailNode
|
|
115
|
+
*/
|
|
116
|
+
/** MJML elements whose inner content is HTML (MJML calls them "ending tags"). */
|
|
117
|
+
export const ENDING_TAGS: Set<string>;
|
|
118
|
+
/** Elements that hold a whole section's place in the body. */
|
|
119
|
+
export const SECTION_TAGS: Set<string>;
|
|
120
|
+
/** Elements that are never removed, moved or duplicated. */
|
|
121
|
+
export const LOCKED_TAGS: Set<string>;
|
|
122
|
+
/** Shape version of the stored project (`{ format, root }`). */
|
|
123
|
+
export const PROJECT_FORMAT: 1;
|
|
124
|
+
export type EmailNode = {
|
|
125
|
+
id: string;
|
|
126
|
+
tag: string;
|
|
127
|
+
attributes: Record<string, string>;
|
|
128
|
+
children: EmailNode[];
|
|
129
|
+
content: string | null;
|
|
130
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{parentId: string, index: number, wrap: boolean}} Placement
|
|
3
|
+
* `wrap`: the content must be wrapped in a new section first.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Click placement.
|
|
7
|
+
* @param {EmailNode} root
|
|
8
|
+
* @param {'content'|'layout'} kind
|
|
9
|
+
* @param {string|null} targetId selected element, if any
|
|
10
|
+
* @returns {Placement}
|
|
11
|
+
*/
|
|
12
|
+
export function placementFor(root: EmailNode, kind: "content" | "layout", targetId: string | null): Placement;
|
|
13
|
+
/**
|
|
14
|
+
* The body-level section holding a node (itself if it is one).
|
|
15
|
+
* @param {EmailNode} root @param {string} id @param {EmailNode} body
|
|
16
|
+
*/
|
|
17
|
+
export function sectionOf(root: EmailNode, id: string, body: EmailNode): import("./model.js").EmailNode | null;
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{top: number, bottom: number, left: number, right: number}} Box
|
|
20
|
+
* @typedef {Placement & {line: {x: number, y: number, width: number}}} DropTarget
|
|
21
|
+
* `line`: where the canvas draws the drop indicator (frame coordinates).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Drop placement for a pointer at (x, y), in canvas frame coordinates.
|
|
25
|
+
*
|
|
26
|
+
* @param {EmailNode} root
|
|
27
|
+
* @param {{kind: 'content'|'layout', nodeId?: string|null}} dragged
|
|
28
|
+
* `nodeId`: the element being moved (it is skipped when measuring, and
|
|
29
|
+
* cannot be dropped into itself)
|
|
30
|
+
* @param {{x: number, y: number}} point
|
|
31
|
+
* @param {string|null} hitId the marked element under the pointer
|
|
32
|
+
* @param {(id: string) => Box|null} boxOf its box in the frame
|
|
33
|
+
* @returns {DropTarget|null}
|
|
34
|
+
*/
|
|
35
|
+
export function dropTargetFor(root: EmailNode, dragged: {
|
|
36
|
+
kind: "content" | "layout";
|
|
37
|
+
nodeId?: string | null;
|
|
38
|
+
}, point: {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
}, hitId: string | null, boxOf: (id: string) => Box | null): DropTarget | null;
|
|
42
|
+
/** @typedef {import('./model.js').EmailNode} EmailNode */
|
|
43
|
+
/** Containers whose children are content blocks. */
|
|
44
|
+
export const CONTENT_CONTAINERS: Set<string>;
|
|
45
|
+
/**
|
|
46
|
+
* `wrap`: the content must be wrapped in a new section first.
|
|
47
|
+
*/
|
|
48
|
+
export type Placement = {
|
|
49
|
+
parentId: string;
|
|
50
|
+
index: number;
|
|
51
|
+
wrap: boolean;
|
|
52
|
+
};
|
|
53
|
+
export type Box = {
|
|
54
|
+
top: number;
|
|
55
|
+
bottom: number;
|
|
56
|
+
left: number;
|
|
57
|
+
right: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* `line`: where the canvas draws the drop indicator (frame coordinates).
|
|
61
|
+
*/
|
|
62
|
+
export type DropTarget = Placement & {
|
|
63
|
+
line: {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
width: number;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
export type EmailNode = import("./model.js").EmailNode;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} html
|
|
3
|
+
* @param {Document} document any document (to parse without running anything)
|
|
4
|
+
*/
|
|
5
|
+
export function cleanRichText(html: string, document: Document): string;
|
|
6
|
+
/**
|
|
7
|
+
* Plain text, escaped, for a label (button, menu link).
|
|
8
|
+
* @param {string} text
|
|
9
|
+
*/
|
|
10
|
+
export function escapeText(text: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* The text of an HTML fragment (`<br>` as a space), entities decoded.
|
|
13
|
+
* @param {string} html
|
|
14
|
+
*/
|
|
15
|
+
export function textOfHtml(html: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dynamically imported module → the value it exports by default.
|
|
3
|
+
*
|
|
4
|
+
* `mjml-browser` (the canvas compiler) is a CommonJS/UMD module. How
|
|
5
|
+
* `import()` hands such a module over depends on the bundler:
|
|
6
|
+
*
|
|
7
|
+
* Vite, esbuild { default: value }
|
|
8
|
+
* webpack (Next.js), from an { default: { __esModule: true, default: value } }
|
|
9
|
+
* ES module package
|
|
10
|
+
*
|
|
11
|
+
* so a single `.default` once returned an object instead of the function under
|
|
12
|
+
* Next.js (0.1.1: "mjmlPlugin is not a function"). Every interop layer is peeled —
|
|
13
|
+
* a module namespace or an `__esModule` object — never the value itself.
|
|
14
|
+
*
|
|
15
|
+
* @param {any} mod
|
|
16
|
+
* @returns {any}
|
|
17
|
+
*/
|
|
18
|
+
export function unwrapModule(mod: any): any;
|
|
19
|
+
/**
|
|
20
|
+
* A module whose export is a FUNCTION (`mjml-browser`) → that function, under
|
|
21
|
+
* any bundler. On top of `unwrapModule`, Vite's dynamic import of a CommonJS
|
|
22
|
+
* module hands over a plain object `{ ...props, default: fn }` (no module
|
|
23
|
+
* flag): since a function is expected, `default` is followed while the value
|
|
24
|
+
* is not one.
|
|
25
|
+
*
|
|
26
|
+
* @param {any} mod
|
|
27
|
+
* @returns {Function|null}
|
|
28
|
+
*/
|
|
29
|
+
export function functionExport(mod: any): Function | null;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CSS color as RGB(A), or null: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(),
|
|
3
|
+
* rgba(), hsl(), hsla(), and the keywords white and black.
|
|
4
|
+
* @param {unknown} value
|
|
5
|
+
* @returns {{r: number, g: number, b: number, a: number}|null}
|
|
6
|
+
*/
|
|
7
|
+
export function parseColor(value: unknown): {
|
|
8
|
+
r: number;
|
|
9
|
+
g: number;
|
|
10
|
+
b: number;
|
|
11
|
+
a: number;
|
|
12
|
+
} | null;
|
|
13
|
+
/** Hue (0-360), saturation and lightness (0-1). @param {{r: number, g: number, b: number}} color */
|
|
14
|
+
export function toHsl({ r, g, b }: {
|
|
15
|
+
r: number;
|
|
16
|
+
g: number;
|
|
17
|
+
b: number;
|
|
18
|
+
}): {
|
|
19
|
+
h: number;
|
|
20
|
+
s: number;
|
|
21
|
+
l: number;
|
|
22
|
+
};
|
|
23
|
+
/** WCAG contrast ratio of two colors (1 to 21). @param {string} a @param {string} b */
|
|
24
|
+
export function contrastRatio(a: string, b: string): number;
|
|
25
|
+
/** The text color that reads on `background`: white or ink. @param {string} background */
|
|
26
|
+
export function readableOn(background: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* A color meant to be noticed (a banner, a filled button), as opposed to
|
|
29
|
+
* white, light tints, grays and near-black.
|
|
30
|
+
* @param {unknown} value
|
|
31
|
+
*/
|
|
32
|
+
export function isColored(value: unknown): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* A brand as stored and used: unknown fields dropped, colors as #rrggbb,
|
|
35
|
+
* the font one of the builder's stacks, the logo an https or site-relative
|
|
36
|
+
* image. Whatever is invalid is left out (the builder's default is used).
|
|
37
|
+
* @param {unknown} input
|
|
38
|
+
* @returns {Brand}
|
|
39
|
+
*/
|
|
40
|
+
export function cleanBrand(input: unknown): Brand;
|
|
41
|
+
/**
|
|
42
|
+
* The sender the templates use: the brand's name and logo over the host's
|
|
43
|
+
* sender (whose postal address stays).
|
|
44
|
+
* @param {import('../templates/starter.js').StarterSender|null|undefined} sender
|
|
45
|
+
* @param {Brand|null|undefined} brand
|
|
46
|
+
*/
|
|
47
|
+
export function brandedSender(sender: import("../templates/starter.js").StarterSender | null | undefined, brand: Brand | null | undefined): {
|
|
48
|
+
name?: string;
|
|
49
|
+
address?: string;
|
|
50
|
+
logoUrl?: string;
|
|
51
|
+
logoWidth?: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* An email in the brand's look.
|
|
55
|
+
* @param {string} mjml
|
|
56
|
+
* @param {Brand|null|undefined} brand
|
|
57
|
+
* @param {{header?: boolean, previous?: {names?: (string|undefined)[], logos?: (string|undefined)[]}}} [options]
|
|
58
|
+
* `header`: also put the logo (or name) at the top and the name in the
|
|
59
|
+
* footer, replacing what `previous` lists (the names and logos the email
|
|
60
|
+
* may still show: the host's sender, the brand before, "Your company").
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
export function applyBrand(mjml: string, brand: Brand | null | undefined, { header, previous }?: {
|
|
64
|
+
header?: boolean;
|
|
65
|
+
previous?: {
|
|
66
|
+
names?: (string | undefined)[];
|
|
67
|
+
logos?: (string | undefined)[];
|
|
68
|
+
};
|
|
69
|
+
}): string;
|
|
70
|
+
/**
|
|
71
|
+
* A fragment (a block about to be inserted) in the brand's look.
|
|
72
|
+
* @param {string} mjml @param {Brand|null|undefined} brand
|
|
73
|
+
*/
|
|
74
|
+
export function applyBrandToFragment(mjml: string, brand: Brand | null | undefined): string;
|
|
75
|
+
/**
|
|
76
|
+
* A ready-made email (the starter, a gallery template) in the brand's look,
|
|
77
|
+
* its header and footer naming the brand rather than the host's sender or
|
|
78
|
+
* the "Your company" placeholder.
|
|
79
|
+
* @param {string} mjml
|
|
80
|
+
* @param {Brand|null|undefined} brand
|
|
81
|
+
* @param {{sender?: import('../templates/starter.js').StarterSender|null, t?: (key: string) => string}} [context]
|
|
82
|
+
*/
|
|
83
|
+
export function brandTemplate(mjml: string, brand: Brand | null | undefined, { sender, t }?: {
|
|
84
|
+
sender?: import("../templates/starter.js").StarterSender | null;
|
|
85
|
+
t?: (key: string) => string;
|
|
86
|
+
}): string;
|
|
87
|
+
export const MAX_LOGO_WIDTH: 240;
|
|
88
|
+
export function toHex({ r, g, b }: {
|
|
89
|
+
r: number;
|
|
90
|
+
g: number;
|
|
91
|
+
b: number;
|
|
92
|
+
}): string;
|
|
93
|
+
export function hasBrand(brand: Brand | null | undefined): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* `primary`: buttons, banners, links; `heading`: headings; colors as
|
|
96
|
+
* `#rrggbb`. `font`: one of the builder's web-safe stacks (FONT_OPTIONS).
|
|
97
|
+
* `website`: where it was read from, to read it again.
|
|
98
|
+
*/
|
|
99
|
+
export type Brand = {
|
|
100
|
+
name?: string;
|
|
101
|
+
logoUrl?: string;
|
|
102
|
+
logoWidth?: number;
|
|
103
|
+
primary?: string;
|
|
104
|
+
heading?: string;
|
|
105
|
+
font?: string;
|
|
106
|
+
website?: string;
|
|
107
|
+
};
|
|
108
|
+
export type EmailNode = import("../../engine/model.js").EmailNode;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The attributes of one start tag, names in lower case.
|
|
3
|
+
* @param {string} tag `<img src="…" class=logo>`
|
|
4
|
+
* @returns {Record<string, string>}
|
|
5
|
+
*/
|
|
6
|
+
export function tagAttributes(tag: string): Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* What a home page says about its brand.
|
|
9
|
+
* @param {string} html
|
|
10
|
+
* @param {string} pageUrl the address it was read from (after redirects)
|
|
11
|
+
* @returns {PageReading}
|
|
12
|
+
*/
|
|
13
|
+
export function readPage(html: string, pageUrl: string): PageReading;
|
|
14
|
+
/**
|
|
15
|
+
* The web-safe stack nearest to a CSS font-family.
|
|
16
|
+
* @param {string} family
|
|
17
|
+
* @returns {string|undefined}
|
|
18
|
+
*/
|
|
19
|
+
export function nearestFont(family: string): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Main color, headings color and font of a site, from its CSS.
|
|
22
|
+
* @param {{css: string, themeColor?: string}} input
|
|
23
|
+
* @returns {{primary?: string, heading?: string, font?: string}}
|
|
24
|
+
*/
|
|
25
|
+
export function brandFromCss({ css, themeColor }: {
|
|
26
|
+
css: string;
|
|
27
|
+
themeColor?: string;
|
|
28
|
+
}): {
|
|
29
|
+
primary?: string;
|
|
30
|
+
heading?: string;
|
|
31
|
+
font?: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Width and height of a PNG, GIF or JPEG, from its header; null otherwise.
|
|
35
|
+
* @param {Uint8Array} bytes
|
|
36
|
+
* @returns {{width: number, height: number}|null}
|
|
37
|
+
*/
|
|
38
|
+
export function imageSize(bytes: Uint8Array): {
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
} | null;
|
|
42
|
+
/**
|
|
43
|
+
* The width a logo is shown at in an email: a wordmark wider, a square mark
|
|
44
|
+
* smaller; never wider than the image itself (a blurry logo is worse than a
|
|
45
|
+
* small one). Retina images (twice the size shown) are common: halved first.
|
|
46
|
+
* @param {{width: number, height: number}} size
|
|
47
|
+
*/
|
|
48
|
+
export function logoWidthFor({ width, height }: {
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
}): number;
|
|
52
|
+
export type PageReading = {
|
|
53
|
+
name: string;
|
|
54
|
+
logos: {
|
|
55
|
+
url: string;
|
|
56
|
+
from: "schema" | "img" | "icon";
|
|
57
|
+
}[];
|
|
58
|
+
stylesheets: string[];
|
|
59
|
+
css: string;
|
|
60
|
+
themeColor: string;
|
|
61
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the user is told about reading a brand from a website: why it failed
|
|
3
|
+
* (the server's answer) and what it could not find (notes under the field).
|
|
4
|
+
* English source strings, translated where they are shown.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Why a website could not be read: [HTTP status, message], by
|
|
8
|
+
* BrandImportError code (src/server/brandImport.js).
|
|
9
|
+
* @type {Record<string, [number, string]>}
|
|
10
|
+
*/
|
|
11
|
+
export const BRAND_IMPORT_ERRORS: Record<string, [number, string]>;
|
|
12
|
+
/** What could not be read, by note code. @type {Record<string, string>} */
|
|
13
|
+
export const BRAND_NOTES: Record<string, string>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brand of a storage: `loaded` resolves once it is known ({} when the
|
|
3
|
+
* storage keeps none, or could not be read — the builder then uses its own look).
|
|
4
|
+
* @param {any} storage
|
|
5
|
+
* @returns {BrandEntry}
|
|
6
|
+
*/
|
|
7
|
+
export function brandOf(storage: any): BrandEntry;
|
|
8
|
+
/**
|
|
9
|
+
* @param {any} storage
|
|
10
|
+
* @returns {{brand: Brand, setBrand: (brand: Brand) => void}}
|
|
11
|
+
* `setBrand`: tells every screen of this storage about a brand just saved.
|
|
12
|
+
*/
|
|
13
|
+
export function useBrand(storage: any): {
|
|
14
|
+
brand: Brand;
|
|
15
|
+
setBrand: (brand: Brand) => void;
|
|
16
|
+
};
|
|
17
|
+
export type Brand = import("./brand.js").Brand;
|
|
18
|
+
export type BrandEntry = {
|
|
19
|
+
value: Brand;
|
|
20
|
+
loaded: Promise<Brand>;
|
|
21
|
+
listeners: Set<(brand: Brand) => void>;
|
|
22
|
+
};
|
|
@@ -2,7 +2,7 @@ export function QualityList({ quality, warnings }: {
|
|
|
2
2
|
quality?: never[] | undefined;
|
|
3
3
|
warnings?: never[] | undefined;
|
|
4
4
|
}): import("react").JSX.Element;
|
|
5
|
-
/** Sandboxed preview frame, desktop or mobile width. */
|
|
5
|
+
/** Sandboxed preview frame, desktop or mobile width, light or dark. */
|
|
6
6
|
export function EmailPreview({ html, loading, error, device, onDeviceChange, onRefresh, hint }: {
|
|
7
7
|
html: any;
|
|
8
8
|
loading?: boolean | undefined;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** @param {string} token @returns {Rgba|null} */
|
|
2
|
+
export function parseCssColor(token: string): Rgba | null;
|
|
3
|
+
/** WCAG relative luminance, 0 (black) to 1 (white). @param {Rgba} color */
|
|
4
|
+
export function luminance({ r, g, b }: Rgba): number;
|
|
5
|
+
/** WCAG contrast ratio, 1 to 21. @param {Rgba} a @param {Rgba} b */
|
|
6
|
+
export function contrast(a: Rgba, b: Rgba): number;
|
|
7
|
+
/**
|
|
8
|
+
* One color in dark mode. `surface`: a background; `text`: a text color;
|
|
9
|
+
* `line`: a border. Text and lines are decided against `background` (the
|
|
10
|
+
* repainted one behind them; the dark page by default). Unknown values
|
|
11
|
+
* (`transparent`, `inherit`…) come back as given.
|
|
12
|
+
* @param {string} token @param {'surface'|'text'|'line'} role @param {string|Rgba} [background]
|
|
13
|
+
*/
|
|
14
|
+
export function darkColor(token: string, role: "surface" | "text" | "line", background?: string | Rgba): string;
|
|
15
|
+
/**
|
|
16
|
+
* The colors of a CSS text in dark mode: a `style` attribute's declarations,
|
|
17
|
+
* or a sheet (each rule on its own; behind it, the dark page).
|
|
18
|
+
* @param {string} css @param {string|Rgba} [background]
|
|
19
|
+
*/
|
|
20
|
+
export function darkCss(css: string, background?: string | Rgba): string;
|
|
21
|
+
/**
|
|
22
|
+
* A compiled email as a dark-mode app would show it.
|
|
23
|
+
* @param {string} html
|
|
24
|
+
*/
|
|
25
|
+
export function simulateDarkMode(html: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* DARK MODE, simulated — an email as the mail apps that repaint it in dark
|
|
28
|
+
* mode show it (Gmail and Outlook apps, Outlook.com): light backgrounds
|
|
29
|
+
* turned dark, dark text turned light where it would no longer read,
|
|
30
|
+
* everything else left as it is.
|
|
31
|
+
*
|
|
32
|
+
* surfaces background colors (`background`, `bgcolor`…): a LIGHT one
|
|
33
|
+
* becomes dark, same hue; a darker one (a brand button, an
|
|
34
|
+
* orange banner) stays.
|
|
35
|
+
* text `color`, `<font color>`, `<body text>`: decided against the
|
|
36
|
+
* background it sits on, once that one is repainted. Dark text
|
|
37
|
+
* on what became dark turns light, same hue; dark text on a
|
|
38
|
+
* background that stayed light enough (black on an orange
|
|
39
|
+
* button) stays; light text stays.
|
|
40
|
+
* lines border and outline colors: a light one darkens like a
|
|
41
|
+
* surface, a dark one is decided like text (a black divider on
|
|
42
|
+
* the dark page turns light).
|
|
43
|
+
* images untouched: a dark logo on a transparent background disappears
|
|
44
|
+
* here as it does in those apps — what the preview is for.
|
|
45
|
+
*
|
|
46
|
+
* Works on the compiled HTML as a string, tag by tag, keeping which
|
|
47
|
+
* background each element sits on: the colors of `style` attributes, of
|
|
48
|
+
* `bgcolor`/`color`/`text` attributes and of `<style>` sheets (where the
|
|
49
|
+
* background is not known: the dark page). Comments are left alone (Outlook's
|
|
50
|
+
* conditional blocks). No script, no DOM: the preview frame stays sandboxed.
|
|
51
|
+
* Only color values change; tags and attribute names stay as they are.
|
|
52
|
+
*
|
|
53
|
+
* Dependency-free: runs in the browser, on the server and in unit tests.
|
|
54
|
+
*/
|
|
55
|
+
/** The page behind the email, and its default text, in dark apps. */
|
|
56
|
+
export const DARK_PAGE: "#121212";
|
|
57
|
+
/**
|
|
58
|
+
* channels 0–255, alpha 0–1
|
|
59
|
+
*/
|
|
60
|
+
export type Rgba = {
|
|
61
|
+
r: number;
|
|
62
|
+
g: number;
|
|
63
|
+
b: number;
|
|
64
|
+
a: number;
|
|
65
|
+
};
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* List of variables with a search field — shared by the toolbar menu and the
|
|
3
3
|
* buttons next to the subject and preview text fields.
|
|
4
4
|
*
|
|
5
|
-
* Keyboard: type to filter, ↑/↓ to move, Enter to insert
|
|
6
|
-
* result offers to create the variable under
|
|
5
|
+
* Keyboard: type to filter, ↑/↓ to move, Enter to insert, Ctrl+K (⌘K) back
|
|
6
|
+
* to the search. A search with no result offers to create the variable under
|
|
7
|
+
* that name.
|
|
7
8
|
*
|
|
8
9
|
* @param {{
|
|
9
10
|
* variables: import('./catalog.js').EmailVariable[],
|
|
@@ -12,6 +12,14 @@ export function createTranslator(messages?: Messages): Translate;
|
|
|
12
12
|
* @returns {Translate}
|
|
13
13
|
*/
|
|
14
14
|
export function translatorFor(locale?: string, overrides?: Messages): Translate;
|
|
15
|
+
/**
|
|
16
|
+
* The dictionary of a locale among several (`'fr-FR'` finds `fr`); an empty
|
|
17
|
+
* one when there is none.
|
|
18
|
+
* @param {Record<string, Messages>|null|undefined} dictionaries
|
|
19
|
+
* @param {string} [locale]
|
|
20
|
+
* @returns {Messages}
|
|
21
|
+
*/
|
|
22
|
+
export function messagesOf(dictionaries: Record<string, Messages> | null | undefined, locale?: string): Messages;
|
|
15
23
|
/** @typedef {Record<string, string>} Messages */
|
|
16
24
|
/** @typedef {(text: string, params?: Record<string, string|number|null|undefined>) => string} Translate */
|
|
17
25
|
/** Built-in locales. English is the source language: an empty dictionary. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,11 +6,13 @@ export { default as TemplateLibrary } from "./ui/library/TemplateLibrary.jsx";
|
|
|
6
6
|
export { default as EmailStudio } from "./ui/EmailStudio.jsx";
|
|
7
7
|
export { default as EmailBuilderBoundary } from "./ui/EmailBuilderBoundary.jsx";
|
|
8
8
|
export { default as EmailCanvas } from "./ui/EmailCanvas.jsx";
|
|
9
|
-
export { createGrapesJsAdapter } from "./adapters/grapesjs/GrapesJsAdapter.js";
|
|
10
9
|
export { createRegistry } from "./core/registry.js";
|
|
10
|
+
export { simulateDarkMode } from "./features/preview/darkMode.js";
|
|
11
11
|
export { emailBuilderFeatures, isFeatureEnabled } from "./features/index.js";
|
|
12
12
|
export { UiProvider, useUi, useT } from "./ui/context.jsx";
|
|
13
13
|
export { createHttpStorage, createMemoryStorage, StorageError } from "./storage/client.js";
|
|
14
|
+
export { createNativeAdapter, ENGINE_NAME, EMPTY_MJML } from "./engine/NativeAdapter.js";
|
|
15
|
+
export { parseMjml, parseMjmlFragment, serializeMjml } from "./engine/mjml.js";
|
|
14
16
|
export { readDocument, createDocument, serializeDocument, EmailDocumentError } from "./core/document.js";
|
|
15
17
|
export { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION, EMAIL_DEVICES } from "./core/constants.js";
|
|
16
18
|
export { DEFAULT_VARIABLES, CUSTOM_GROUP, defineVariables, mergeVariables, keyFromLabel, validateCustomVariable, tokenOf } from "./features/variables/catalog.js";
|
|
@@ -18,4 +20,5 @@ export { applyVariables, applyVariablesToText, usedVariables, unknownVariables }
|
|
|
18
20
|
export { createStarterMjml, STARTER_MJML } from "./features/templates/starter.js";
|
|
19
21
|
export { TEMPLATE_GALLERY, buildGallery, withPlaceholderImages } from "./features/templates/gallery.js";
|
|
20
22
|
export { isAssetPath, absoluteAssetUrls, DEFAULT_ASSETS_PATH } from "./renderer/links.js";
|
|
21
|
-
export {
|
|
23
|
+
export { applyBrand, cleanBrand, brandedSender } from "./features/brand/brand.js";
|
|
24
|
+
export { createTranslator, translatorFor, messagesOf, LOCALES } from "./i18n/index.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../features/brand/brand.js').Brand} Brand
|
|
3
|
+
*
|
|
4
|
+
* @typedef {{
|
|
5
|
+
* get: () => Promise<Brand|null>,
|
|
6
|
+
* save: (brand: Brand & {updatedBy: string|null}) => Promise<void>,
|
|
7
|
+
* }} BrandRepository
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @param {{repository?: BrandRepository|null, t?: import('../i18n/index.js').Translate}} options
|
|
11
|
+
*/
|
|
12
|
+
export function createBrandService({ repository, t }: {
|
|
13
|
+
repository?: BrandRepository | null;
|
|
14
|
+
t?: import("../i18n/index.js").Translate;
|
|
15
|
+
}): {
|
|
16
|
+
/** The brand, `{}` when none was set (or no repository). */
|
|
17
|
+
get(): Promise<import("../features/brand/brand.js").Brand>;
|
|
18
|
+
/** @param {unknown} input @param {{id?: string|number, name?: string}|null} [user] */
|
|
19
|
+
save(input: unknown, user?: {
|
|
20
|
+
id?: string | number;
|
|
21
|
+
name?: string;
|
|
22
|
+
} | null): Promise<import("../features/brand/brand.js").Brand>;
|
|
23
|
+
};
|
|
24
|
+
/** @returns {BrandRepository} */
|
|
25
|
+
export function createMemoryBrandRepository(): BrandRepository;
|
|
26
|
+
export type Brand = import("../features/brand/brand.js").Brand;
|
|
27
|
+
export type BrandRepository = {
|
|
28
|
+
get: () => Promise<Brand | null>;
|
|
29
|
+
save: (brand: Brand & {
|
|
30
|
+
updatedBy: string | null;
|
|
31
|
+
}) => Promise<void>;
|
|
32
|
+
};
|