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,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words of an email, as a reader sees them: the head, the styles and the
|
|
3
|
+
* markup left out.
|
|
4
|
+
* @param {string} mjml
|
|
5
|
+
*/
|
|
6
|
+
export function emailWords(mjml: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* The request as the model sees it, or an error when there is nothing to
|
|
9
|
+
* draw subjects from.
|
|
10
|
+
* @param {SubjectsInput} input
|
|
11
|
+
*/
|
|
12
|
+
export function cleanSubjectsInput(input: SubjectsInput): {
|
|
13
|
+
subject: string;
|
|
14
|
+
preheader: string;
|
|
15
|
+
text: string;
|
|
16
|
+
language: string;
|
|
17
|
+
variables: {
|
|
18
|
+
key: string;
|
|
19
|
+
label: string;
|
|
20
|
+
}[];
|
|
21
|
+
avoid: string[];
|
|
22
|
+
};
|
|
23
|
+
/** @param {ReturnType<typeof cleanSubjectsInput>} request */
|
|
24
|
+
export function buildSubjectsPrompt({ subject, preheader, text, language, variables, avoid }: ReturnType<typeof cleanSubjectsInput>): string;
|
|
25
|
+
/**
|
|
26
|
+
* The subjects in a model's answer: the strings of its JSON field
|
|
27
|
+
* `subjects`; an answer that is no JSON at all, one per line (list marks,
|
|
28
|
+
* quotes and introductions left out). JSON without that field gives none —
|
|
29
|
+
* the model is asked again.
|
|
30
|
+
* @param {string} answer @param {string} [language] the email's, for the capital letter
|
|
31
|
+
*/
|
|
32
|
+
export function parseSubjects(answer: string, language?: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* The usable subjects, and what is wrong with the others (for the model).
|
|
35
|
+
* Left out: a repeat (of the current subject, the preview text, an idea
|
|
36
|
+
* already shown or another one), a merge tag the list does not have or one
|
|
37
|
+
* written for another tool, a word in capitals the email does not have, more
|
|
38
|
+
* than one "!", a subject longer than an inbox shows.
|
|
39
|
+
* @param {string[]} subjects @param {ReturnType<typeof cleanSubjectsInput>} request
|
|
40
|
+
*/
|
|
41
|
+
export function reviewSubjects(subjects: string[], request: ReturnType<typeof cleanSubjectsInput>): {
|
|
42
|
+
usable: string[];
|
|
43
|
+
problems: string[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options
|
|
47
|
+
* @returns {(input: SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[], attempts: number}>}
|
|
48
|
+
*/
|
|
49
|
+
export function createSubjectSuggester({ model, maxAttempts }: {
|
|
50
|
+
model: import("./generator.js").EmailModel;
|
|
51
|
+
maxAttempts?: number;
|
|
52
|
+
}): (input: SubjectsInput, options?: {
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}) => Promise<{
|
|
55
|
+
subjects: string[];
|
|
56
|
+
attempts: number;
|
|
57
|
+
}>;
|
|
58
|
+
export const SUBJECT_COUNT: 3;
|
|
59
|
+
export const MAX_SUBJECT_LENGTH: 150;
|
|
60
|
+
export const MAX_EMAIL_WORDS: 4000;
|
|
61
|
+
export namespace SUBJECTS_SCHEMA {
|
|
62
|
+
let type: string;
|
|
63
|
+
namespace properties {
|
|
64
|
+
namespace subjects {
|
|
65
|
+
let type_1: string;
|
|
66
|
+
export { type_1 as type };
|
|
67
|
+
export let description: string;
|
|
68
|
+
export namespace items {
|
|
69
|
+
let type_2: string;
|
|
70
|
+
export { type_2 as type };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let required: string[];
|
|
75
|
+
let additionalProperties: boolean;
|
|
76
|
+
}
|
|
77
|
+
export const SUBJECTS_SYSTEM_PROMPT: "You write email subject lines. You get an email (its current subject, its preview text and its words) and answer three NEW subject lines for it, in the JSON field \"subjects\".\n\nEach subject:\n- tells what the email brings, specific to its content: under 50 characters when possible, 70 at most;\n- takes its own angle: one clear and direct, one that makes the reader curious, one personal or timely;\n- is written in the language of the email;\n- invents no facts: no price, discount, date, number, name or promise the email does not contain;\n- is plain text: no HTML, no quotation marks around it, no line break;\n- has no word in capitals, at most one \"!\", no spam wording (\"free!!!\", \"act now\", \"100% guaranteed\");\n- has no emoji, unless the current subject has one;\n- differs from the current subject and from the two others, and does not repeat the preview text.\n\nMerge tags: you may personalise ONE of the three with a merge tag from the list you are given, written exactly as listed, e.g. {{contact.firstname}}. No other merge tag.";
|
|
78
|
+
/** Longer than any inbox shows. */
|
|
79
|
+
export const MAX_SUBJECT_SHOWN: 90;
|
|
80
|
+
/**
|
|
81
|
+
* `text`: the words of the email (`emailWords`); `language`: the one of the
|
|
82
|
+
* interface, used when the email has no words yet and its subject does not
|
|
83
|
+
* tell; `variables`: a link variable (`kind: 'link'`) is never offered;
|
|
84
|
+
* `avoid`: ideas already shown ("More ideas" asks for new ones).
|
|
85
|
+
*/
|
|
86
|
+
export type SubjectsInput = {
|
|
87
|
+
subject?: string;
|
|
88
|
+
preheader?: string;
|
|
89
|
+
text?: string;
|
|
90
|
+
language?: string;
|
|
91
|
+
variables?: {
|
|
92
|
+
key: string;
|
|
93
|
+
label?: string;
|
|
94
|
+
kind?: string;
|
|
95
|
+
}[];
|
|
96
|
+
avoid?: string[];
|
|
97
|
+
};
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building blocks shared by all blocks: palette, fonts, recurring properties.
|
|
3
|
+
*
|
|
4
|
+
* -- DEFAULT PALETTE ---------------------------------------------------------
|
|
5
|
+
* Plain hex values: email clients load no stylesheet and understand neither
|
|
6
|
+
* `var()` nor modern color functions, so every value ends up inline in a
|
|
7
|
+
* `style=""` attribute.
|
|
8
|
+
*
|
|
9
|
+
* -- EVERY BLOCK SETS ITS OWN TEXT ATTRIBUTES --------------------------------
|
|
10
|
+
* Font, size, line height and color are written on the element itself, never
|
|
11
|
+
* left to MJML defaults (the "Ubuntu" font loaded from Google, line height 1):
|
|
12
|
+
* the canvas and the sent email then render the same thing, and Outlook gets
|
|
13
|
+
* explicit values.
|
|
14
|
+
*
|
|
15
|
+
* Fonts: only web-safe stacks, installed everywhere and understood by
|
|
16
|
+
* Outlook - a web font only renders in a minority of email clients.
|
|
17
|
+
*/
|
|
18
|
+
/** @typedef {import('../core/EmailEditor.js').BlockProperty} BlockProperty */
|
|
19
|
+
/** Text for an MJML attribute or element. @param {string} text */
|
|
20
|
+
export function escapeXml(text: string): string;
|
|
1
21
|
/**
|
|
2
22
|
* White one-column section: the wrapper of a content block dropped outside
|
|
3
23
|
* any column (MJML does not accept text directly inside the body).
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
* `structure` : body, section, column — selectable, not addable.
|
|
28
28
|
*
|
|
29
29
|
* `mjml` is the inserted fragment; `sectionMjml` the same wrapped in a
|
|
30
|
-
* section, for when it must go outside any column
|
|
30
|
+
* section, for when it must go outside any column. Either may be a function
|
|
31
|
+
* of the translator, so the texts a block brings arrive in the interface's
|
|
32
|
+
* language; `tagName` identifies the
|
|
31
33
|
* block of a selected element (`mj-text` → text).
|
|
32
34
|
* `editableContent`: the content is editable (canvas for text, panel for a
|
|
33
35
|
* button label).
|
|
@@ -43,8 +45,8 @@
|
|
|
43
45
|
* kind: 'content'|'layout'|'structure',
|
|
44
46
|
* tagName: string,
|
|
45
47
|
* order?: number,
|
|
46
|
-
* mjml?: string,
|
|
47
|
-
* sectionMjml?: string,
|
|
48
|
+
* mjml?: string | ((t: import('../i18n/index.js').Translate) => string),
|
|
49
|
+
* sectionMjml?: string | ((t: import('../i18n/index.js').Translate) => string),
|
|
48
50
|
* properties: BlockProperty[],
|
|
49
51
|
* editableContent?: boolean,
|
|
50
52
|
* insertOnly?: boolean,
|
|
@@ -59,20 +61,30 @@
|
|
|
59
61
|
* tagName: string,
|
|
60
62
|
* attributes: Record<string, string>,
|
|
61
63
|
* content: string|null,
|
|
64
|
+
* html?: string|null,
|
|
62
65
|
* canRemove: boolean,
|
|
63
66
|
* canDuplicate: boolean,
|
|
64
67
|
* canMoveUp: boolean,
|
|
65
68
|
* canMoveDown: boolean,
|
|
69
|
+
* columnCount?: number,
|
|
70
|
+
* multiple?: number,
|
|
66
71
|
* }} EngineSelection
|
|
72
|
+
* `content`: the label of a button or a menu link, as plain text.
|
|
73
|
+
* `html`: the HTML of a text (`mj-text`), as stored.
|
|
74
|
+
* `columnCount`: for a section, how many columns it holds (its name).
|
|
75
|
+
* `multiple`: the whole email is selected (Ctrl+A) — how many sections;
|
|
76
|
+
* the selection is then described as the body, and only removal applies.
|
|
67
77
|
*/
|
|
68
78
|
/**
|
|
69
|
-
* Selected element, in the builder's vocabulary: its block
|
|
70
|
-
* values (padding recombined, see mjmlAttributes)
|
|
79
|
+
* Selected element, in the builder's vocabulary: its block, its property
|
|
80
|
+
* values (padding recombined, see mjmlAttributes) and, for the properties left
|
|
81
|
+
* empty, what MJML renders instead (`defaults`, see mjmlDefaults).
|
|
71
82
|
*
|
|
72
83
|
* @typedef {EngineSelection & {
|
|
73
84
|
* blockId: string|null,
|
|
74
85
|
* label: string,
|
|
75
86
|
* values: Record<string, string>,
|
|
87
|
+
* defaults: Record<string, string>,
|
|
76
88
|
* }} EmailSelection
|
|
77
89
|
*/
|
|
78
90
|
/**
|
|
@@ -98,6 +110,8 @@
|
|
|
98
110
|
* blocks: EmailBlock[],
|
|
99
111
|
* document: EmailDocument|null,
|
|
100
112
|
* onEvent: (type: string, payload?: any) => void,
|
|
113
|
+
* t?: import('../i18n/index.js').Translate,
|
|
114
|
+
* labelOf?: (selection: EngineSelection) => string,
|
|
101
115
|
* }) => Promise<void>,
|
|
102
116
|
* destroy: () => void,
|
|
103
117
|
* addBlock: (block: EmailBlock, options?: {targetId?: string|null}) => string|null,
|
|
@@ -120,13 +134,15 @@
|
|
|
120
134
|
* getProject: () => object,
|
|
121
135
|
* loadProject: (project: object) => void,
|
|
122
136
|
* loadMjml: (mjml: string) => void,
|
|
137
|
+
* setMjml?: (mjml: string) => void,
|
|
123
138
|
* exportMjml: () => string,
|
|
124
|
-
* exportHtml: (mjml?: string|null) => HtmlExport
|
|
139
|
+
* exportHtml: (mjml?: string|null) => Promise<HtmlExport>,
|
|
125
140
|
* setDevice: (device: EmailDevice) => void,
|
|
126
141
|
* getDevice: () => EmailDevice,
|
|
127
142
|
* startBlockDrag: (block: EmailBlock, event: Event) => void,
|
|
128
143
|
* endBlockDrag: (cancel?: boolean) => void,
|
|
129
144
|
* addShortcut: (id: string, keys: string, handler: () => void) => (() => void),
|
|
145
|
+
* selectAll?: () => void,
|
|
130
146
|
* }} EmailEditorAdapter
|
|
131
147
|
*/
|
|
132
148
|
/**
|
|
@@ -163,6 +179,7 @@
|
|
|
163
179
|
* duplicate: (id?: string|null) => string|null,
|
|
164
180
|
* move: (direction: -1|1, id?: string|null) => boolean,
|
|
165
181
|
* select: (id: string|null) => void,
|
|
182
|
+
* selectAll: () => void,
|
|
166
183
|
* getSelection: () => EmailSelection|null,
|
|
167
184
|
* getElement: (id: string) => EmailSelection|null,
|
|
168
185
|
* getRootId: () => string|null,
|
|
@@ -175,10 +192,12 @@
|
|
|
175
192
|
* canUndo: () => boolean,
|
|
176
193
|
* canRedo: () => boolean,
|
|
177
194
|
* getDocument: () => EmailDocument,
|
|
178
|
-
* loadDocument: (document: unknown) => void,
|
|
195
|
+
* loadDocument: (document: unknown, options?: {mjml?: string|null}) => void,
|
|
179
196
|
* loadMjml: (mjml: string) => void,
|
|
197
|
+
* setMjml: (mjml: string) => void,
|
|
180
198
|
* exportMjml: () => string,
|
|
181
|
-
* exportHtml: (mjml?: string|null) => HtmlExport
|
|
199
|
+
* exportHtml: (mjml?: string|null) => Promise<HtmlExport>,
|
|
200
|
+
* setBrand: (brand: import('../features/brand/brand.js').Brand|null) => void,
|
|
182
201
|
* setDevice: (device: EmailDevice) => void,
|
|
183
202
|
* getDevice: () => EmailDevice,
|
|
184
203
|
* startBlockDrag: (blockId: string, event: Event) => void,
|
|
@@ -187,18 +206,33 @@
|
|
|
187
206
|
* on: (event: string, listener: (payload?: any) => void) => () => void,
|
|
188
207
|
* }} EmailEditor
|
|
189
208
|
*/
|
|
209
|
+
/**
|
|
210
|
+
* A block with its MJML (and section MJML) as strings, its texts in the
|
|
211
|
+
* translator's language.
|
|
212
|
+
* @param {EmailBlock} block
|
|
213
|
+
* @param {import('../i18n/index.js').Translate} [t]
|
|
214
|
+
* @returns {EmailBlock & {mjml?: string, sectionMjml?: string}}
|
|
215
|
+
*/
|
|
216
|
+
export function resolveBlock(block: EmailBlock, t?: import("../i18n/index.js").Translate): EmailBlock & {
|
|
217
|
+
mjml?: string;
|
|
218
|
+
sectionMjml?: string;
|
|
219
|
+
};
|
|
190
220
|
/**
|
|
191
221
|
* @param {{
|
|
192
222
|
* adapter: EmailEditorAdapter,
|
|
193
223
|
* blocks: import('./registry.js').Registry<EmailBlock>,
|
|
194
224
|
* features?: import('./registry.js').Registry<EmailBuilderFeature> | null,
|
|
225
|
+
* t?: import('../i18n/index.js').Translate,
|
|
195
226
|
* }} options
|
|
227
|
+
* `t`: the interface's translator — texts of inserted blocks, names shown
|
|
228
|
+
* in the canvas.
|
|
196
229
|
* @returns {EmailEditor}
|
|
197
230
|
*/
|
|
198
|
-
export function createEmailEditor({ adapter, blocks, features }: {
|
|
231
|
+
export function createEmailEditor({ adapter, blocks, features, t }: {
|
|
199
232
|
adapter: EmailEditorAdapter;
|
|
200
233
|
blocks: import("./registry.js").Registry<EmailBlock>;
|
|
201
234
|
features?: import("./registry.js").Registry<EmailBuilderFeature> | null;
|
|
235
|
+
t?: import("../i18n/index.js").Translate;
|
|
202
236
|
}): EmailEditor;
|
|
203
237
|
export type EmailDevice = import("./constants.js").EmailDevice;
|
|
204
238
|
export type EmailDocument = import("./document.js").EmailDocument;
|
|
@@ -229,7 +263,9 @@ export type BlockProperty = {
|
|
|
229
263
|
* `structure` : body, section, column — selectable, not addable.
|
|
230
264
|
*
|
|
231
265
|
* `mjml` is the inserted fragment; `sectionMjml` the same wrapped in a
|
|
232
|
-
* section, for when it must go outside any column
|
|
266
|
+
* section, for when it must go outside any column. Either may be a function
|
|
267
|
+
* of the translator, so the texts a block brings arrive in the interface's
|
|
268
|
+
* language; `tagName` identifies the
|
|
233
269
|
* block of a selected element (`mj-text` → text).
|
|
234
270
|
* `editableContent`: the content is editable (canvas for text, panel for a
|
|
235
271
|
* button label).
|
|
@@ -245,34 +281,42 @@ export type EmailBlock = {
|
|
|
245
281
|
kind: "content" | "layout" | "structure";
|
|
246
282
|
tagName: string;
|
|
247
283
|
order?: number;
|
|
248
|
-
mjml?: string;
|
|
249
|
-
sectionMjml?: string;
|
|
284
|
+
mjml?: string | ((t: import("../i18n/index.js").Translate) => string);
|
|
285
|
+
sectionMjml?: string | ((t: import("../i18n/index.js").Translate) => string);
|
|
250
286
|
properties: BlockProperty[];
|
|
251
287
|
editableContent?: boolean;
|
|
252
288
|
insertOnly?: boolean;
|
|
253
289
|
};
|
|
254
290
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
291
|
+
* `content`: the label of a button or a menu link, as plain text.
|
|
292
|
+
* `html`: the HTML of a text (`mj-text`), as stored.
|
|
293
|
+
* `columnCount`: for a section, how many columns it holds (its name).
|
|
294
|
+
* `multiple`: the whole email is selected (Ctrl+A) — how many sections;
|
|
295
|
+
* the selection is then described as the body, and only removal applies.
|
|
257
296
|
*/
|
|
258
297
|
export type EngineSelection = {
|
|
259
298
|
id: string;
|
|
260
299
|
tagName: string;
|
|
261
300
|
attributes: Record<string, string>;
|
|
262
301
|
content: string | null;
|
|
302
|
+
html?: string | null;
|
|
263
303
|
canRemove: boolean;
|
|
264
304
|
canDuplicate: boolean;
|
|
265
305
|
canMoveUp: boolean;
|
|
266
306
|
canMoveDown: boolean;
|
|
307
|
+
columnCount?: number;
|
|
308
|
+
multiple?: number;
|
|
267
309
|
};
|
|
268
310
|
/**
|
|
269
|
-
* Selected element, in the builder's vocabulary: its block
|
|
270
|
-
* values (padding recombined, see mjmlAttributes)
|
|
311
|
+
* Selected element, in the builder's vocabulary: its block, its property
|
|
312
|
+
* values (padding recombined, see mjmlAttributes) and, for the properties left
|
|
313
|
+
* empty, what MJML renders instead (`defaults`, see mjmlDefaults).
|
|
271
314
|
*/
|
|
272
315
|
export type EmailSelection = EngineSelection & {
|
|
273
316
|
blockId: string | null;
|
|
274
317
|
label: string;
|
|
275
318
|
values: Record<string, string>;
|
|
319
|
+
defaults: Record<string, string>;
|
|
276
320
|
};
|
|
277
321
|
export type HtmlExport = {
|
|
278
322
|
html: string;
|
|
@@ -302,6 +346,8 @@ export type EmailEditorAdapter = {
|
|
|
302
346
|
blocks: EmailBlock[];
|
|
303
347
|
document: EmailDocument | null;
|
|
304
348
|
onEvent: (type: string, payload?: any) => void;
|
|
349
|
+
t?: import("../i18n/index.js").Translate;
|
|
350
|
+
labelOf?: (selection: EngineSelection) => string;
|
|
305
351
|
}) => Promise<void>;
|
|
306
352
|
destroy: () => void;
|
|
307
353
|
addBlock: (block: EmailBlock, options?: {
|
|
@@ -326,13 +372,15 @@ export type EmailEditorAdapter = {
|
|
|
326
372
|
getProject: () => object;
|
|
327
373
|
loadProject: (project: object) => void;
|
|
328
374
|
loadMjml: (mjml: string) => void;
|
|
375
|
+
setMjml?: (mjml: string) => void;
|
|
329
376
|
exportMjml: () => string;
|
|
330
|
-
exportHtml: (mjml?: string | null) => HtmlExport
|
|
377
|
+
exportHtml: (mjml?: string | null) => Promise<HtmlExport>;
|
|
331
378
|
setDevice: (device: EmailDevice) => void;
|
|
332
379
|
getDevice: () => EmailDevice;
|
|
333
380
|
startBlockDrag: (block: EmailBlock, event: Event) => void;
|
|
334
381
|
endBlockDrag: (cancel?: boolean) => void;
|
|
335
382
|
addShortcut: (id: string, keys: string, handler: () => void) => (() => void);
|
|
383
|
+
selectAll?: () => void;
|
|
336
384
|
};
|
|
337
385
|
/**
|
|
338
386
|
* Pluggable feature. `setup` receives the ready editor and may return a
|
|
@@ -369,6 +417,7 @@ export type EmailEditor = {
|
|
|
369
417
|
duplicate: (id?: string | null) => string | null;
|
|
370
418
|
move: (direction: -1 | 1, id?: string | null) => boolean;
|
|
371
419
|
select: (id: string | null) => void;
|
|
420
|
+
selectAll: () => void;
|
|
372
421
|
getSelection: () => EmailSelection | null;
|
|
373
422
|
getElement: (id: string) => EmailSelection | null;
|
|
374
423
|
getRootId: () => string | null;
|
|
@@ -381,10 +430,14 @@ export type EmailEditor = {
|
|
|
381
430
|
canUndo: () => boolean;
|
|
382
431
|
canRedo: () => boolean;
|
|
383
432
|
getDocument: () => EmailDocument;
|
|
384
|
-
loadDocument: (document: unknown
|
|
433
|
+
loadDocument: (document: unknown, options?: {
|
|
434
|
+
mjml?: string | null;
|
|
435
|
+
}) => void;
|
|
385
436
|
loadMjml: (mjml: string) => void;
|
|
437
|
+
setMjml: (mjml: string) => void;
|
|
386
438
|
exportMjml: () => string;
|
|
387
|
-
exportHtml: (mjml?: string | null) => HtmlExport
|
|
439
|
+
exportHtml: (mjml?: string | null) => Promise<HtmlExport>;
|
|
440
|
+
setBrand: (brand: import("../features/brand/brand.js").Brand | null) => void;
|
|
388
441
|
setDevice: (device: EmailDevice) => void;
|
|
389
442
|
getDevice: () => EmailDevice;
|
|
390
443
|
startBlockDrag: (blockId: string, event: Event) => void;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* project JSON). Both are written with every template, so a document read by
|
|
7
7
|
* a newer version knows whether it must be migrated (see `core/document.js`).
|
|
8
8
|
*/
|
|
9
|
-
export const EMAIL_BUILDER_VERSION: "0.
|
|
9
|
+
export const EMAIL_BUILDER_VERSION: "0.5.0";
|
|
10
10
|
export const EMAIL_DOCUMENT_SCHEMA_VERSION: 1;
|
|
11
11
|
/** @typedef {'desktop'|'mobile'} EmailDevice */
|
|
12
12
|
/** @type {EmailDevice[]} */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MJML DEFAULTS — what MJML 5 renders when an attribute is not written.
|
|
3
|
+
*
|
|
4
|
+
* The properties panel shows them, greyed, in empty fields: an empty
|
|
5
|
+
* "Padding" of a section is not "0" — MJML gives it 20 px above and below,
|
|
6
|
+
* and the panel must say so (and start from there when one side is edited).
|
|
7
|
+
*
|
|
8
|
+
* Copied from the `defaultAttributes` of the mjml 5 components (the same
|
|
9
|
+
* version the server and the canvas compile with); the unit tests check them
|
|
10
|
+
* against the installed components.
|
|
11
|
+
*/
|
|
12
|
+
/** @type {Record<string, Record<string, string>>} */
|
|
13
|
+
export const MJML_DEFAULTS: Record<string, Record<string, string>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scrollbars inside the builder's frames (the
|
|
2
|
+
* Scrollbars inside the builder's frames (the canvas, the preview),
|
|
3
3
|
* the same as in the rest of the builder: a 5.6 px gutter, a 2.8 px thumb,
|
|
4
4
|
* no arrows, shown only while the pointer is over the scrolling block. A frame is a
|
|
5
5
|
* document of its own, so `styles.css` does not reach it — the same rules
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An editor wired to the default engine and the built-in registries.
|
|
3
3
|
*
|
|
4
|
-
* @param {{locale?: string}} [options]
|
|
4
|
+
* @param {{locale?: string, t?: import('./i18n/index.js').Translate}} [options]
|
|
5
|
+
* `t`: the interface's translator (texts of inserted blocks, names in the
|
|
6
|
+
* canvas); by default the built-in one of `locale` (`'en'`).
|
|
5
7
|
*/
|
|
6
8
|
export function createDefaultEmailEditor(options?: {
|
|
7
9
|
locale?: string;
|
|
10
|
+
t?: import("./i18n/index.js").Translate;
|
|
8
11
|
}): import("./core/EmailEditor.js").EmailEditor;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{
|
|
3
|
+
* loadCompiler?: () => Promise<(mjml: string) => Promise<{html: string, errors: any[]}>>,
|
|
4
|
+
* }} [options]
|
|
5
|
+
* @returns {EmailEditorAdapter & {readonly view: any, readonly root: EmailNode|null}}
|
|
6
|
+
*/
|
|
7
|
+
export function createNativeAdapter({ loadCompiler }?: {
|
|
8
|
+
loadCompiler?: () => Promise<(mjml: string) => Promise<{
|
|
9
|
+
html: string;
|
|
10
|
+
errors: any[];
|
|
11
|
+
}>>;
|
|
12
|
+
}): EmailEditorAdapter & {
|
|
13
|
+
readonly view: any;
|
|
14
|
+
readonly root: EmailNode | null;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {import('../core/EmailEditor.js').EmailEditorAdapter} EmailEditorAdapter
|
|
18
|
+
* @typedef {import('../core/EmailEditor.js').EmailBlock} EmailBlock
|
|
19
|
+
* @typedef {import('../core/EmailEditor.js').EngineSelection} EngineSelection
|
|
20
|
+
* @typedef {import('./model.js').EmailNode} EmailNode
|
|
21
|
+
*/
|
|
22
|
+
export const ENGINE_NAME: "beb-mjml";
|
|
23
|
+
export const ENGINE_VERSION: "0.5.0";
|
|
24
|
+
export const EMPTY_MJML: "<mjml><mj-body background-color=\"#eceff4\" width=\"600px\"></mj-body></mjml>";
|
|
25
|
+
export type EmailEditorAdapter = import("../core/EmailEditor.js").EmailEditorAdapter;
|
|
26
|
+
export type EmailBlock = import("../core/EmailEditor.js").EmailBlock;
|
|
27
|
+
export type EngineSelection = import("../core/EmailEditor.js").EngineSelection;
|
|
28
|
+
export type EmailNode = import("./model.js").EmailNode;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{
|
|
3
|
+
* container: HTMLElement,
|
|
4
|
+
* compile: (mjml: string) => Promise<{html: string, errors: any[]}>,
|
|
5
|
+
* getRoot: () => EmailNode,
|
|
6
|
+
* t: (key: string, values?: Record<string, any>) => string,
|
|
7
|
+
* info: (id: string) => {label: string, tag: string, canRemove: boolean, canDuplicate: boolean, canMove: boolean, hasParent: boolean, parentLabel: string}|null,
|
|
8
|
+
* onSelect: (id: string|null) => void,
|
|
9
|
+
* onAction: (action: 'parent'|'duplicate'|'remove', id: string) => void,
|
|
10
|
+
* onDrop: (payload: DragPayload, target: DropTarget) => void,
|
|
11
|
+
* onEditStart: (id: string) => void,
|
|
12
|
+
* onEditInput: (id: string, html: string) => void,
|
|
13
|
+
* onEditEnd: (id: string, html: string) => void,
|
|
14
|
+
* onKeyDown: (event: KeyboardEvent) => void,
|
|
15
|
+
* }} options
|
|
16
|
+
*/
|
|
17
|
+
export function createCanvasView(options: {
|
|
18
|
+
container: HTMLElement;
|
|
19
|
+
compile: (mjml: string) => Promise<{
|
|
20
|
+
html: string;
|
|
21
|
+
errors: any[];
|
|
22
|
+
}>;
|
|
23
|
+
getRoot: () => EmailNode;
|
|
24
|
+
t: (key: string, values?: Record<string, any>) => string;
|
|
25
|
+
info: (id: string) => {
|
|
26
|
+
label: string;
|
|
27
|
+
tag: string;
|
|
28
|
+
canRemove: boolean;
|
|
29
|
+
canDuplicate: boolean;
|
|
30
|
+
canMove: boolean;
|
|
31
|
+
hasParent: boolean;
|
|
32
|
+
parentLabel: string;
|
|
33
|
+
} | null;
|
|
34
|
+
onSelect: (id: string | null) => void;
|
|
35
|
+
onAction: (action: "parent" | "duplicate" | "remove", id: string) => void;
|
|
36
|
+
onDrop: (payload: DragPayload, target: DropTarget) => void;
|
|
37
|
+
onEditStart: (id: string) => void;
|
|
38
|
+
onEditInput: (id: string, html: string) => void;
|
|
39
|
+
onEditEnd: (id: string, html: string) => void;
|
|
40
|
+
onKeyDown: (event: KeyboardEvent) => void;
|
|
41
|
+
}): {
|
|
42
|
+
frame: HTMLIFrameElement;
|
|
43
|
+
document: Document;
|
|
44
|
+
render: () => Promise<void>;
|
|
45
|
+
refresh: () => void;
|
|
46
|
+
/** @param {string|null} id */
|
|
47
|
+
setSelection(id: string | null): void;
|
|
48
|
+
/** Ctrl+A: boxes on every given element (empty array: none). @param {string[]} ids */
|
|
49
|
+
setMultiSelection(ids: string[]): void;
|
|
50
|
+
/** @param {'desktop'|'mobile'} next */
|
|
51
|
+
setDevice(next: "desktop" | "mobile"): void;
|
|
52
|
+
getDevice: () => "desktop" | "mobile";
|
|
53
|
+
startDrag: (start: {
|
|
54
|
+
payload: DragPayload;
|
|
55
|
+
label: string;
|
|
56
|
+
source: "host" | "frame";
|
|
57
|
+
pointerId: number;
|
|
58
|
+
captureTarget: Element;
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
}) => void;
|
|
62
|
+
cancelDrag: () => void;
|
|
63
|
+
isDragging: () => boolean;
|
|
64
|
+
/** @param {string} id @param {{x: number, y: number}|null} [at] */
|
|
65
|
+
startEditing: (id: string, at?: {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
} | null) => void;
|
|
69
|
+
finishEditing: () => void;
|
|
70
|
+
editingId: () => string | null;
|
|
71
|
+
/** @param {string} text */
|
|
72
|
+
insertText(text: string): boolean;
|
|
73
|
+
/** @param {string} html */
|
|
74
|
+
insertHtml(html: string): boolean;
|
|
75
|
+
destroy(): void;
|
|
76
|
+
};
|
|
77
|
+
/** @typedef {import('../model.js').EmailNode} EmailNode */
|
|
78
|
+
/** @typedef {import('../placement.js').DropTarget} DropTarget */
|
|
79
|
+
/** @typedef {{kind: 'content'|'layout', nodeId?: string|null, block?: any}} DragPayload */
|
|
80
|
+
/** Elements edited as rich text in the canvas. */
|
|
81
|
+
export const EDITABLE_TAGS: Set<string>;
|
|
82
|
+
export type EmailNode = import("../model.js").EmailNode;
|
|
83
|
+
export type DropTarget = import("../placement.js").DropTarget;
|
|
84
|
+
export type DragPayload = {
|
|
85
|
+
kind: "content" | "layout";
|
|
86
|
+
nodeId?: string | null;
|
|
87
|
+
block?: any;
|
|
88
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const CANVAS_CSS: "\nhtml, body { min-height: 100%; }\nbody { margin: 0; }\n.eb-node { cursor: default; }\n.eb-node a { cursor: pointer; }\n[contenteditable=\"true\"] { outline: none; cursor: text; caret-color: #5b4ff0; }\ntd.eb-placeholder { outline: 1.5px dashed #c3cad6; outline-offset: -8px; }\n.eb-placeholder { user-select: none; }\n\n#eb-ui { position: absolute; top: 0; left: 0; width: 0; height: 0; z-index: 2147483000;\n font: 500 11px/1.2 system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; }\n#eb-ui * { box-sizing: border-box; }\n#eb-ui [hidden] { display: none !important; }\n.eb-ui-box { position: absolute; pointer-events: none; }\n.eb-ui-hover { box-shadow: inset 0 0 0 1px rgba(91, 79, 240, 0.55); }\n.eb-ui-select { box-shadow: inset 0 0 0 2px #5b4ff0; }\n.eb-ui-chip { position: absolute; pointer-events: none; white-space: nowrap; color: #fff; background: #5b4ff0;\n padding: 4px 7px; border-radius: 5px; font-weight: 600; letter-spacing: 0.01em; }\n.eb-ui-hover-chip { background: rgba(91, 79, 240, 0.85); }\n.eb-ui-tools { position: absolute; display: flex; gap: 1px; padding: 2px; border-radius: 6px; background: #5b4ff0;\n box-shadow: 0 4px 12px -4px rgba(40, 30, 120, 0.5); pointer-events: auto; }\n.eb-ui-tools button { all: unset; box-sizing: border-box; width: 26px; height: 24px; display: grid; place-items: center;\n color: #fff; border-radius: 4px; cursor: pointer; }\n.eb-ui-tools button:hover, .eb-ui-tools button:focus-visible { background: rgba(255, 255, 255, 0.2); }\n.eb-ui-tools button[data-action=\"move\"] { cursor: grab; touch-action: none; }\n.eb-ui-tools button.eb-ui-parent { width: auto; gap: 4px; padding: 0 7px 0 5px; display: inline-flex; align-items: center;\n font-weight: 600; white-space: nowrap; }\n.eb-ui-tools button.eb-ui-parent + button { margin-left: 2px; box-shadow: -1px 0 0 rgba(255, 255, 255, 0.25); }\n.eb-ui-drop { position: absolute; height: 4px; margin-top: -2px; border-radius: 2px; background: #5b4ff0;\n box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85); pointer-events: none; }\n.eb-ui-drop::before, .eb-ui-drop::after { content: ''; position: absolute; top: -3px; width: 10px; height: 10px;\n border-radius: 50%; background: #5b4ff0; box-shadow: 0 0 0 2px #fff; }\n.eb-ui-drop::before { left: -5px; }\n.eb-ui-drop::after { right: -5px; }\n.eb-ui-text { position: absolute; display: flex; align-items: center; gap: 1px; padding: 3px; border-radius: 8px;\n background: #fff; border: 1px solid #dfe3ea; box-shadow: 0 8px 22px -10px rgba(16, 24, 40, 0.35); pointer-events: auto; }\n.eb-ui-text button { all: unset; box-sizing: border-box; width: 28px; height: 26px; display: grid; place-items: center;\n color: #2b3038; border-radius: 5px; cursor: pointer; }\n.eb-ui-text button:hover, .eb-ui-text button:focus-visible { background: #eef0f6; }\n.eb-ui-text button[aria-pressed=\"true\"] { background: #e8e6fd; color: #5b4ff0; }\n.eb-ui-text .eb-ui-sep { width: 1px; height: 18px; margin: 0 3px; background: #e3e6ec; }\n.eb-ui-text input { all: unset; box-sizing: border-box; width: 220px; height: 26px; padding: 0 8px; border-radius: 5px;\n border: 1px solid #dfe3ea; font: 400 12px/1 system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; color: #2b3038; }\n.eb-ui-text input:focus { border-color: #5b4ff0; }\n\n::-webkit-scrollbar{width:5.6px;height:5.6px;background:transparent}\n::-webkit-scrollbar-button{display:none;width:0;height:0}\n::-webkit-scrollbar-track,::-webkit-scrollbar-track-piece,::-webkit-scrollbar-corner{background:transparent;border:0;box-shadow:none}\n::-webkit-scrollbar-thumb{background-color:rgba(156,163,175,.45);border:1.4px solid transparent;background-clip:content-box;border-radius:9999px;min-height:2rem;min-width:2rem}\n::-webkit-scrollbar-thumb:hover{background-color:rgba(107,114,128,.7)}\n@supports not selector(::-webkit-scrollbar){*{scrollbar-width:thin;scrollbar-color:rgba(156,163,175,.45) transparent}}\n@media (hover:hover) and (pointer:fine){\n::-webkit-scrollbar-thumb{background-color:transparent}\n*:hover::-webkit-scrollbar-thumb{background-color:rgba(156,163,175,.45)}\n*:hover::-webkit-scrollbar-thumb:hover{background-color:rgba(107,114,128,.7)}\n@supports not selector(::-webkit-scrollbar){*{scrollbar-color:transparent transparent}*:hover{scrollbar-color:rgba(156,163,175,.45) transparent}}\n}\n";
|
|
2
|
+
/** The drag label, in the host page (outside the frame): inline styles only. */
|
|
3
|
+
export const GHOST_STYLE: string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export namespace ICONS {
|
|
2
|
+
let parent: string;
|
|
3
|
+
let move: string;
|
|
4
|
+
let duplicate: string;
|
|
5
|
+
let remove: string;
|
|
6
|
+
let bold: string;
|
|
7
|
+
let italic: string;
|
|
8
|
+
let underline: string;
|
|
9
|
+
let strike: string;
|
|
10
|
+
let link: string;
|
|
11
|
+
let unlink: string;
|
|
12
|
+
let clear: string;
|
|
13
|
+
let check: string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MORPH — updates the canvas DOM to a newly compiled email in place.
|
|
3
|
+
*
|
|
4
|
+
* Replacing the whole page at every change would reload the images (the
|
|
5
|
+
* email jumps while they come back), lose the scroll position and the caret
|
|
6
|
+
* of a text being typed. Nodes are compared position by position instead:
|
|
7
|
+
* same kind of node → its attributes and children are updated, otherwise it
|
|
8
|
+
* is replaced. An unchanged image keeps its element, so it never reloads.
|
|
9
|
+
*
|
|
10
|
+
* `skip(element)`: an element left exactly as it is, children included (the
|
|
11
|
+
* text being edited: the browser owns it until the edit ends).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @param {Element} from @param {Element} to
|
|
15
|
+
*/
|
|
16
|
+
export function syncAttributes(from: Element, to: Element): void;
|
|
17
|
+
/**
|
|
18
|
+
* Makes `from`'s children match `to`'s.
|
|
19
|
+
* @param {Node} from @param {Node} to @param {(element: Element) => boolean} [skip]
|
|
20
|
+
*/
|
|
21
|
+
export function morphChildren(from: Node, to: Node, skip?: (element: Element) => boolean): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grey frame of an image without a file (an SVG data URL, canvas only),
|
|
3
|
+
* drawn at the width it will be shown so its text stays readable in a narrow
|
|
4
|
+
* column (it says where to choose the image only when there is room).
|
|
5
|
+
* @param {string} label @param {number} [width]
|
|
6
|
+
*/
|
|
7
|
+
export function imagePlaceholder(label: string, width?: number): string;
|
|
8
|
+
/**
|
|
9
|
+
* @param {EmailNode} root
|
|
10
|
+
* @param {{t?: (key: string) => string}} [options]
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function canvasMjml(root: EmailNode, { t }?: {
|
|
14
|
+
t?: (key: string) => string;
|
|
15
|
+
}): string;
|
|
16
|
+
/**
|
|
17
|
+
* Id of the model element an HTML element belongs to (its own marker or its
|
|
18
|
+
* closest marked ancestor's), or null.
|
|
19
|
+
* @param {Element|null} element
|
|
20
|
+
* @returns {{id: string, element: Element}|null}
|
|
21
|
+
*/
|
|
22
|
+
export function markedAncestor(element: Element | null): {
|
|
23
|
+
id: string;
|
|
24
|
+
element: Element;
|
|
25
|
+
} | null;
|
|
26
|
+
export const NODE_CLASS: "eb-node";
|
|
27
|
+
export const ID_CLASS_PREFIX: "eb-id-";
|
|
28
|
+
export const TAG_CLASS_PREFIX: "eb-tag-";
|
|
29
|
+
export const PLACEHOLDER_CLASS: "eb-placeholder";
|
|
30
|
+
export type EmailNode = import("./model.js").EmailNode;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** @param {string} value */
|
|
2
|
+
export function decodeAttribute(value: string): string;
|
|
3
|
+
/** @param {string} value */
|
|
4
|
+
export function encodeAttribute(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* MJML elements of a fragment (no `<mjml>` needed): `<mj-section>…</mj-section><mj-section>…`.
|
|
7
|
+
* @param {string} source
|
|
8
|
+
* @returns {EmailNode[]}
|
|
9
|
+
*/
|
|
10
|
+
export function parseMjmlFragment(source: string): EmailNode[];
|
|
11
|
+
/**
|
|
12
|
+
* A whole email. Always returns an `mjml` root holding an `mj-body`: a bare
|
|
13
|
+
* fragment is wrapped, a missing body is added.
|
|
14
|
+
* @param {string} source
|
|
15
|
+
* @returns {EmailNode}
|
|
16
|
+
*/
|
|
17
|
+
export function parseMjml(source: string): EmailNode;
|
|
18
|
+
/**
|
|
19
|
+
* The MJML of a tree. `pretty`: one element per line, indented (what is
|
|
20
|
+
* stored and downloaded); otherwise on one line.
|
|
21
|
+
* @param {EmailNode} node
|
|
22
|
+
* @param {{pretty?: boolean, attributes?: (node: EmailNode) => Record<string, string>, extraChildren?: (node: EmailNode) => string}} [options]
|
|
23
|
+
* `attributes`: replaces a node's attributes on write (the canvas adds its
|
|
24
|
+
* markers there); `extraChildren`: MJML appended inside a container.
|
|
25
|
+
*/
|
|
26
|
+
export function serializeMjml(node: EmailNode, options?: {
|
|
27
|
+
pretty?: boolean;
|
|
28
|
+
attributes?: (node: EmailNode) => Record<string, string>;
|
|
29
|
+
extraChildren?: (node: EmailNode) => string;
|
|
30
|
+
}): string;
|
|
31
|
+
export type EmailNode = import("./model.js").EmailNode;
|