best-email-builder 0.1.0 → 0.2.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 +43 -0
- package/README.md +11 -1
- package/dist/{chunk-2PMZMNKA.js → chunk-MTALS5GC.js} +98 -3
- package/dist/chunk-MTALS5GC.js.map +1 -0
- package/dist/index.js +977 -299
- package/dist/index.js.map +1 -1
- package/dist/server.js +13 -13
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +1 -1
- package/dist/types/adapters/grapesjs/unwrapModule.d.ts +18 -0
- package/dist/types/blocks/columns/index.d.ts +3 -0
- package/dist/types/blocks/heading/index.d.ts +3 -0
- package/dist/types/blocks/hero/index.d.ts +3 -0
- package/dist/types/blocks/navbar/index.d.ts +6 -0
- package/dist/types/blocks/social/index.d.ts +13 -0
- package/dist/types/core/EmailEditor.d.ts +12 -4
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/features/templates/TemplateGallery.d.ts +3 -0
- package/dist/types/features/templates/gallery.d.ts +41 -0
- package/dist/types/features/templates/index.d.ts +2 -0
- package/dist/types/features/templates/starter.d.ts +22 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/server/compileMjml.d.ts +3 -3
- package/dist/types/server/render.d.ts +4 -4
- package/dist/types/server/templates.d.ts +2 -2
- package/dist/types/ui/BlockSidebar.d.ts +2 -1
- package/dist/types/ui/Drawer.d.ts +19 -0
- package/dist/types/ui/EmailBuilder.d.ts +2 -0
- package/dist/types/ui/PropertiesPanel.d.ts +2 -1
- package/dist/types/ui/Toolbar.d.ts +9 -2
- package/dist/types/ui/primitives/button.d.ts +1 -1
- package/dist/types/ui/useLayoutWidth.d.ts +10 -0
- package/package.json +8 -7
- package/dist/chunk-2PMZMNKA.js.map +0 -1
- package/dist/chunk-3EOCXTI2.js +0 -652
- package/dist/chunk-3EOCXTI2.js.map +0 -1
package/dist/chunk-3EOCXTI2.js
DELETED
|
@@ -1,652 +0,0 @@
|
|
|
1
|
-
// src/core/constants.js
|
|
2
|
-
var EMAIL_BUILDER_VERSION = "1.0.0";
|
|
3
|
-
var EMAIL_DOCUMENT_SCHEMA_VERSION = 1;
|
|
4
|
-
var EMAIL_DEVICES = ["desktop", "mobile"];
|
|
5
|
-
var EMAIL_MOBILE_WIDTH = 375;
|
|
6
|
-
|
|
7
|
-
// src/core/document.js
|
|
8
|
-
var MIGRATIONS = {};
|
|
9
|
-
var EmailDocumentError = class extends Error {
|
|
10
|
-
/** @param {string} message */
|
|
11
|
-
constructor(message) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = "EmailDocumentError";
|
|
14
|
-
this.status = 422;
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
function isPlainObject(value) {
|
|
18
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19
|
-
}
|
|
20
|
-
function createDocument({ engine, engineVersion, project }) {
|
|
21
|
-
if (!engine) throw new EmailDocumentError("Document: missing engine");
|
|
22
|
-
if (!isPlainObject(project)) throw new EmailDocumentError("Document: invalid engine project");
|
|
23
|
-
return {
|
|
24
|
-
schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,
|
|
25
|
-
engine,
|
|
26
|
-
engineVersion: String(engineVersion || ""),
|
|
27
|
-
builderVersion: EMAIL_BUILDER_VERSION,
|
|
28
|
-
project: (
|
|
29
|
-
/** @type {Record<string, any>} */
|
|
30
|
-
project
|
|
31
|
-
)
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function readDocument(raw) {
|
|
35
|
-
let value = raw;
|
|
36
|
-
if (typeof value === "string") {
|
|
37
|
-
try {
|
|
38
|
-
value = JSON.parse(value);
|
|
39
|
-
} catch {
|
|
40
|
-
throw new EmailDocumentError("Unreadable document: invalid JSON");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (!isPlainObject(value)) throw new EmailDocumentError("Unreadable document: object expected");
|
|
44
|
-
let document = { .../** @type {object} */
|
|
45
|
-
value };
|
|
46
|
-
const version = Number(document.schemaVersion);
|
|
47
|
-
if (!Number.isInteger(version) || version < 1) {
|
|
48
|
-
throw new EmailDocumentError("Unreadable document: missing schema version");
|
|
49
|
-
}
|
|
50
|
-
if (version > EMAIL_DOCUMENT_SCHEMA_VERSION) {
|
|
51
|
-
throw new EmailDocumentError(
|
|
52
|
-
`Document written by a newer version of the builder (schema ${version}) \u2014 reload the page`
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
for (let v = version; v < EMAIL_DOCUMENT_SCHEMA_VERSION; v += 1) {
|
|
56
|
-
const migrate = MIGRATIONS[v];
|
|
57
|
-
if (!migrate) throw new EmailDocumentError(`Document: no migration from schema ${v}`);
|
|
58
|
-
document = migrate(document);
|
|
59
|
-
}
|
|
60
|
-
if (typeof document.engine !== "string" || !document.engine) {
|
|
61
|
-
throw new EmailDocumentError("Unreadable document: missing engine");
|
|
62
|
-
}
|
|
63
|
-
if (!isPlainObject(document.project)) {
|
|
64
|
-
throw new EmailDocumentError("Unreadable document: missing engine project");
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,
|
|
68
|
-
engine: document.engine,
|
|
69
|
-
engineVersion: String(document.engineVersion || ""),
|
|
70
|
-
builderVersion: String(document.builderVersion || ""),
|
|
71
|
-
project: document.project
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function serializeDocument(document) {
|
|
75
|
-
return JSON.stringify(readDocument(document));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/i18n/fr.js
|
|
79
|
-
var fr = {
|
|
80
|
-
"{label} \u2014 {side}": "{label} \u2014 {side}",
|
|
81
|
-
"{max} test addresses at most": "{max} adresses de test au maximum",
|
|
82
|
-
"{message} Your version can be saved as a copy.": "{message} Votre version peut \xEAtre enregistr\xE9e comme copie.",
|
|
83
|
-
"{name} (copy)": "{name} (copie)",
|
|
84
|
-
"{sent} sent, {failed} failed.": "{sent} parti(s), {failed} en \xE9chec.",
|
|
85
|
-
"{sent} test(s) sent \u2014 check your inbox.": "{sent} test(s) parti(s) \u2014 ouvrez votre messagerie avant de continuer.",
|
|
86
|
-
"1 column": "1 colonne",
|
|
87
|
-
"2 columns": "2 colonnes",
|
|
88
|
-
"2-column section": "Section en 2 colonnes",
|
|
89
|
-
"25%": "25 %",
|
|
90
|
-
"3 columns": "3 colonnes",
|
|
91
|
-
"3-column section": "Section en 3 colonnes",
|
|
92
|
-
"50%": "50 %",
|
|
93
|
-
"600 px (standard)": "600 px (standard)",
|
|
94
|
-
"640 px": "640 px",
|
|
95
|
-
"75%": "75 %",
|
|
96
|
-
"Address not allowed": "Adresse refus\xE9e",
|
|
97
|
-
"Addresses": "Adresses",
|
|
98
|
-
"Alignment": "Alignement",
|
|
99
|
-
"Alt text": "Texte alternatif",
|
|
100
|
-
"An https:// address is required": "Une adresse https:// est requise",
|
|
101
|
-
"attribute {name} not allowed": "attribut {name} interdit",
|
|
102
|
-
"Authentication required": "Authentification requise",
|
|
103
|
-
"Autosave": "Sauvegarde automatique",
|
|
104
|
-
"Autosave paused": "Sauvegarde automatique suspendue",
|
|
105
|
-
"Autosaved at {time}": "Enregistr\xE9 automatiquement \xE0 {time}",
|
|
106
|
-
"Autosaving\u2026": "Sauvegarde automatique\u2026",
|
|
107
|
-
"Available in every template.": "Disponible dans tous les mod\xE8les.",
|
|
108
|
-
"Back": "Retour",
|
|
109
|
-
"Background": "Fond",
|
|
110
|
-
"base tag not allowed": "balise base interdite",
|
|
111
|
-
"Blocks": "Blocs",
|
|
112
|
-
"Bold": "Gras",
|
|
113
|
-
"Bottom": "Bas",
|
|
114
|
-
"Button": "Bouton",
|
|
115
|
-
"Button background": "Fond du bouton",
|
|
116
|
-
"Button padding": "Marges du bouton",
|
|
117
|
-
"Button without link ({count})": "Bouton sans lien ({count})",
|
|
118
|
-
"Call to action": "Appel \xE0 l\u2019action",
|
|
119
|
-
"Campaign": "Campagne",
|
|
120
|
-
"Campaign name": "Nom de la campagne",
|
|
121
|
-
"Cancel": "Annuler",
|
|
122
|
-
"CDATA section not allowed": "section CDATA interdite",
|
|
123
|
-
"Center": "Centre",
|
|
124
|
-
"Close": "Fermer",
|
|
125
|
-
"Colors": "Couleurs",
|
|
126
|
-
"Column": "Colonne",
|
|
127
|
-
"Company": "Soci\xE9t\xE9",
|
|
128
|
-
"component script not allowed": "script de composant interdit",
|
|
129
|
-
"conditional comment not allowed": "commentaire conditionnel interdit",
|
|
130
|
-
"Confirm": "Confirmer",
|
|
131
|
-
"Content": "Contenu",
|
|
132
|
-
"Content width": "Largeur du contenu",
|
|
133
|
-
"Copy saved: \u201C{name}\u201D.": "Copie enregistr\xE9e : \xAB {name} \xBB.",
|
|
134
|
-
"Corner radius": "Arrondi",
|
|
135
|
-
"Create": "Cr\xE9er",
|
|
136
|
-
"Create \u201C{name}\u201D": "Cr\xE9er \xAB {name} \xBB",
|
|
137
|
-
"current": "actuelle",
|
|
138
|
-
"Custom": "Personnalis\xE9es",
|
|
139
|
-
"Custom variables are not configured": "Les variables personnalis\xE9es ne sont pas configur\xE9es",
|
|
140
|
-
"Dashed": "Tirets",
|
|
141
|
-
"Default": "Par d\xE9faut",
|
|
142
|
-
"Delete": "Supprimer",
|
|
143
|
-
"Delete (Del)": "Supprimer (Suppr)",
|
|
144
|
-
"Delete the variable \u201C{name}\u201D?": "Supprimer la variable \xAB {name} \xBB ?",
|
|
145
|
-
"Describes the image if it is blocked": "D\xE9crit l\u2019image si elle est bloqu\xE9e",
|
|
146
|
-
"Deselect": "D\xE9s\xE9lectionner",
|
|
147
|
-
"Desktop": "Bureau",
|
|
148
|
-
"Device": "Appareil",
|
|
149
|
-
"Dismiss": "Masquer",
|
|
150
|
-
"Divider": "S\xE9parateur",
|
|
151
|
-
"Dotted": "Pointill\xE9s",
|
|
152
|
-
"Double-click the text to edit it.": "Double-cliquez sur le texte pour le modifier.",
|
|
153
|
-
"Duplicate": "Dupliquer",
|
|
154
|
-
"Edit": "Modifier",
|
|
155
|
-
"Edit \u201C{name}\u201D": "Modifier \xAB {name} \xBB",
|
|
156
|
-
"Edit variable": "Modifier la variable",
|
|
157
|
-
"Element actions": "Actions sur l\u2019\xE9l\xE9ment",
|
|
158
|
-
"Email": "Courriel",
|
|
159
|
-
"Email address": "Adresse e-mail",
|
|
160
|
-
"Email preview": "Aper\xE7u du courriel",
|
|
161
|
-
"embed not allowed": "embed interdit",
|
|
162
|
-
"Empty file": "Fichier vide",
|
|
163
|
-
"Empty MJML": "MJML vide",
|
|
164
|
-
"Empty MJML compilation": "Compilation MJML vide",
|
|
165
|
-
"Empty subject": "Objet vide",
|
|
166
|
-
"executable content not allowed": "contenu ex\xE9cutable interdit",
|
|
167
|
-
"external resource not allowed": "ressource externe interdite",
|
|
168
|
-
"File missing": "Fichier manquant",
|
|
169
|
-
"Final rendering, variables filled with samples.": "Rendu final, variables remplies d\u2019exemples.",
|
|
170
|
-
"First name": "Pr\xE9nom",
|
|
171
|
-
"Fit to text": "Ajust\xE9e au texte",
|
|
172
|
-
"Font": "Police",
|
|
173
|
-
"forbidden element in project: {tag}": "\xE9l\xE9ment interdit dans le projet : {tag}",
|
|
174
|
-
"form field not allowed": "champ de formulaire interdit",
|
|
175
|
-
"form not allowed": "formulaire interdit",
|
|
176
|
-
"Format rejected: PNG, JPEG or GIF expected": "Format refus\xE9 : PNG, JPEG ou GIF attendu",
|
|
177
|
-
"frame not allowed": "frame interdite",
|
|
178
|
-
"Full length": "Toute la largeur",
|
|
179
|
-
"Full width": "Pleine largeur",
|
|
180
|
-
"Full-width section": "Section pleine largeur",
|
|
181
|
-
"Give the template a name before saving it.": "Donnez un libell\xE9 au mod\xE8le avant de l\u2019enregistrer.",
|
|
182
|
-
"Group": "Groupe",
|
|
183
|
-
"Height": "Hauteur",
|
|
184
|
-
"Hello {name},": "Bonjour {name},",
|
|
185
|
-
"Hex code": "Code hexad\xE9cimal",
|
|
186
|
-
"Horizontal rule": "Filet horizontal",
|
|
187
|
-
"HTML button not allowed (use the Button block)": "bouton HTML interdit (utiliser le bloc Bouton)",
|
|
188
|
-
"iframe not allowed": "iframe interdite",
|
|
189
|
-
"Ignored (invalid): {email}": "Ignor\xE9e (invalide) : {email}",
|
|
190
|
-
"Image": "Image",
|
|
191
|
-
"image rejected ({name})": "image refus\xE9e ({name})",
|
|
192
|
-
"image rejected ({name}): https only": "image refus\xE9e ({name}) : https seulement",
|
|
193
|
-
"Image too large: {max} MB maximum": "Image trop lourde : {max} Mo au maximum",
|
|
194
|
-
"Image without alt text ({count})": "Texte alternatif manquant ({count} image(s))",
|
|
195
|
-
"Image without file ({count})": "Image sans fichier ({count})",
|
|
196
|
-
"Inbox": "Bo\xEEte de r\xE9ception",
|
|
197
|
-
"Indicative preview \u2014 check it with \u201CSend test\u201D.": "Aper\xE7u indicatif \u2014 validez avec \xAB Envoyer un test \xBB.",
|
|
198
|
-
"inline style rejected": "style en ligne refus\xE9",
|
|
199
|
-
"Insecure http link ({count})": "Lien non s\xE9curis\xE9 en http ({count})",
|
|
200
|
-
"Insert a variable in the preview text": "Ins\xE9rer une variable dans le texte d\u2019aper\xE7u",
|
|
201
|
-
"Insert a variable in the subject": "Ins\xE9rer une variable dans l\u2019objet",
|
|
202
|
-
"Internal error": "Erreur interne",
|
|
203
|
-
"Invalid JSON": "JSON invalide",
|
|
204
|
-
"Justify": "Justifi\xE9",
|
|
205
|
-
"Key is required": "Balise requise",
|
|
206
|
-
"Last name": "Nom",
|
|
207
|
-
"Layout": "Mise en page",
|
|
208
|
-
"Learn more": "D\xE9couvrir",
|
|
209
|
-
"Leave": "Quitter",
|
|
210
|
-
"Leave the builder?": "Quitter le constructeur ?",
|
|
211
|
-
"Left": "Gauche",
|
|
212
|
-
"Length": "Longueur",
|
|
213
|
-
"Line color": "Couleur du filet",
|
|
214
|
-
"Line height": "Interligne",
|
|
215
|
-
"Line style": "Trait",
|
|
216
|
-
"Link": "Lien",
|
|
217
|
-
"Link on click": "Lien au clic",
|
|
218
|
-
"Link URL": "Adresse du lien",
|
|
219
|
-
"Links": "Liens",
|
|
220
|
-
"Loading the builder": "Chargement du constructeur",
|
|
221
|
-
"Loading\u2026": "Chargement\u2026",
|
|
222
|
-
'Lowercase letters, digits, "_" and "." only, starting with a letter': "Minuscules, chiffres, \xAB _ \xBB et \xAB . \xBB seulement, en commen\xE7ant par une lettre",
|
|
223
|
-
"MathML not allowed": "MathML interdit",
|
|
224
|
-
"Message is {size} KB: Gmail clips messages over {limit} KB": "Message de {size} Ko : Gmail le coupe au-del\xE0 de {limit} Ko",
|
|
225
|
-
"meta tag not allowed": "balise meta interdite",
|
|
226
|
-
"Middle": "Milieu",
|
|
227
|
-
"missing <mj-body>": "<mj-body> manquant",
|
|
228
|
-
"Missing unsubscribe link": "Lien de d\xE9sinscription absent",
|
|
229
|
-
"mj-font (external font) is outside the builder": "mj-font (police externe) est hors du constructeur",
|
|
230
|
-
"mj-include would read a file from the server": "mj-include lirait un fichier du serveur",
|
|
231
|
-
"mj-raw (free HTML) is not allowed": "mj-raw (HTML libre) est refus\xE9",
|
|
232
|
-
"mj-style (free CSS) is outside the builder": "mj-style (CSS libre) est hors du constructeur",
|
|
233
|
-
"MJML too large ({size} KB, maximum {max} KB)": "MJML trop volumineux ({size} ko, maximum {max} ko)",
|
|
234
|
-
"Mobile": "Mobile",
|
|
235
|
-
"Move down": "Descendre",
|
|
236
|
-
"Move left": "D\xE9placer \xE0 gauche",
|
|
237
|
-
"Move right": "D\xE9placer \xE0 droite",
|
|
238
|
-
"Move up": "Monter",
|
|
239
|
-
"Multipart form expected": "Formulaire multipart attendu",
|
|
240
|
-
"Name": "Libell\xE9",
|
|
241
|
-
"Name is required": "Libell\xE9 requis",
|
|
242
|
-
"Name required": "Libell\xE9 requis",
|
|
243
|
-
"New template": "Nouveau mod\xE8le",
|
|
244
|
-
"New variable": "Nouvelle variable",
|
|
245
|
-
"No changes": "Aucune modification",
|
|
246
|
-
"No preview text": "Aucun texte d\u2019aper\xE7u",
|
|
247
|
-
"No subject": "Sans objet",
|
|
248
|
-
"No usable test address": "Aucune adresse de test exploitable",
|
|
249
|
-
"No variable found.": "Aucune variable trouv\xE9e.",
|
|
250
|
-
"No versions yet.": "Aucune version.",
|
|
251
|
-
"Normal": "Normale",
|
|
252
|
-
"Nothing to fix.": "Aucun point \xE0 corriger.",
|
|
253
|
-
"Nothing to show yet.": "Rien \xE0 afficher pour l\u2019instant.",
|
|
254
|
-
"object not allowed": "object interdit",
|
|
255
|
-
"Order number": "Num\xE9ro de commande",
|
|
256
|
-
"Padding": "Marges",
|
|
257
|
-
"Page background": "Fond de page",
|
|
258
|
-
"Paragraph or heading": "Paragraphe ou titre",
|
|
259
|
-
"Photo, visual or logo": "Photo, visuel ou logo",
|
|
260
|
-
"Pick a colour": "Choisir une couleur",
|
|
261
|
-
"Points to check": "Points \xE0 v\xE9rifier",
|
|
262
|
-
"Preview": "Aper\xE7u",
|
|
263
|
-
"Preview and tests": "Aper\xE7u et tests",
|
|
264
|
-
"Preview text": "Texte d\u2019aper\xE7u",
|
|
265
|
-
"Project rejected: {issues}": "Projet refus\xE9 : {issues}",
|
|
266
|
-
"project too deep": "projet trop profond",
|
|
267
|
-
"project too large ({size} KB, maximum {max} KB)": "projet trop volumineux ({size} ko, maximum {max} ko)",
|
|
268
|
-
"Properties": "Propri\xE9t\xE9s",
|
|
269
|
-
"Recipient": "Destinataire",
|
|
270
|
-
"Redo": "R\xE9tablir",
|
|
271
|
-
"Redo (Ctrl+Y)": "R\xE9tablir (Ctrl+Y)",
|
|
272
|
-
"Refresh": "Rafra\xEEchir",
|
|
273
|
-
"Rendering\u2026": "Rendu en cours\u2026",
|
|
274
|
-
"Request too large": "Requ\xEAte trop volumineuse",
|
|
275
|
-
"Restore": "Recharger",
|
|
276
|
-
"Restore version {n}": "Recharger la version {n}",
|
|
277
|
-
"Restore version {n}?": "Recharger la version {n} ?",
|
|
278
|
-
"Results": "R\xE9sultats",
|
|
279
|
-
"Resume": "Reprendre",
|
|
280
|
-
"Resume the draft?": "Reprendre le brouillon ?",
|
|
281
|
-
"Retry": "R\xE9essayer",
|
|
282
|
-
"Right": "Droite",
|
|
283
|
-
"Route not found": "Route introuvable",
|
|
284
|
-
"Sample value": "Valeur d\u2019exemple",
|
|
285
|
-
"Save": "Enregistrer",
|
|
286
|
-
"Save a copy": "Enregistrer une copie",
|
|
287
|
-
"Save the template first": "Enregistrez d\u2019abord le mod\xE8le",
|
|
288
|
-
"Saved": "Enregistr\xE9",
|
|
289
|
-
"Saved \u2014 version {n}.": "Enregistr\xE9 \u2014 version {n}.",
|
|
290
|
-
"Saved at {time}": "Enregistr\xE9 \xE0 {time}",
|
|
291
|
-
"Saved, with {count} MJML warning(s): {message}": "Enregistr\xE9, avec {count} avertissement(s) MJML : {message}",
|
|
292
|
-
"Saving\u2026": "Enregistrement\u2026",
|
|
293
|
-
"script not allowed": "script interdit",
|
|
294
|
-
"Search variables\u2026": "Rechercher une variable\u2026",
|
|
295
|
-
"Section": "Section",
|
|
296
|
-
"Select a text or a button first.": "S\xE9lectionnez d\u2019abord un texte ou un bouton.",
|
|
297
|
-
"Send": "Envoyer",
|
|
298
|
-
"Send a test": "Envoyer un test",
|
|
299
|
-
"Shown after the subject in the inbox": "Affich\xE9 apr\xE8s l\u2019objet dans la bo\xEEte de r\xE9ception",
|
|
300
|
-
"Size": "Taille",
|
|
301
|
-
"Solid": "Plein",
|
|
302
|
-
"Spacer": "Espace",
|
|
303
|
-
"Spacing": "Espacement",
|
|
304
|
-
"Start over": "Partir de z\xE9ro",
|
|
305
|
-
"style sheet not allowed": "feuille de style interdite",
|
|
306
|
-
"Subject": "Objet",
|
|
307
|
-
"SVG not allowed": "SVG interdit",
|
|
308
|
-
"Tag": "Balise",
|
|
309
|
-
"tag not allowed: <{tag}>": "balise non admise : <{tag}>",
|
|
310
|
-
"Template": "Mod\xE8le",
|
|
311
|
-
"Template changed in the meantime": "Mod\xE8le modifi\xE9 entre-temps",
|
|
312
|
-
"Template changed in the meantime \u2014 reload it before saving": "Mod\xE8le modifi\xE9 entre-temps \u2014 rechargez-le avant d\u2019enregistrer",
|
|
313
|
-
"Template changed in the meantime by {user} \u2014 reload it before saving": "Mod\xE8le modifi\xE9 entre-temps par {user} \u2014 rechargez-le avant d\u2019enregistrer",
|
|
314
|
-
"Template not found": "Mod\xE8le introuvable",
|
|
315
|
-
"Template rejected: {issues}": "Mod\xE8le refus\xE9 : {issues}",
|
|
316
|
-
"Template rejected: {issues} (+{more})": "Mod\xE8le refus\xE9 : {issues} (+{more})",
|
|
317
|
-
"Template saved.": "Mod\xE8le enregistr\xE9.",
|
|
318
|
-
"Templates that use it will show its \u201Cvalue if empty\u201D.": "Les mod\xE8les qui l\u2019utilisent afficheront sa \xAB valeur si vide \xBB.",
|
|
319
|
-
"Test": "Tester",
|
|
320
|
-
"Test sending is not configured": "L\u2019envoi de test n\u2019est pas configur\xE9",
|
|
321
|
-
"Text": "Texte",
|
|
322
|
-
"The builder could not load.": "Le constructeur n\u2019a pas pu se charger.",
|
|
323
|
-
"The builder could not start.": "Le constructeur n\u2019a pas pu d\xE9marrer.",
|
|
324
|
-
"the document must start with <mjml>": "le document doit commencer par <mjml>",
|
|
325
|
-
"The draft will be offered again next time.": "Le brouillon reste propos\xE9 \xE0 la prochaine ouverture.",
|
|
326
|
-
"The email on screen, to reviewers\u2019 addresses.": "Le courriel \xE0 l\u2019\xE9cran, aux adresses de relecture.",
|
|
327
|
-
"The template could not be loaded.": "Le mod\xE8le n\u2019a pas pu \xEAtre charg\xE9.",
|
|
328
|
-
"Thickness": "\xC9paisseur",
|
|
329
|
-
"This key is already used": "Cette balise existe d\xE9j\xE0",
|
|
330
|
-
"To check": "\xC0 v\xE9rifier",
|
|
331
|
-
"Too many test sends \u2014 try again later": "Trop d\u2019envois de test \u2014 r\xE9essayez plus tard",
|
|
332
|
-
"Too many uploads \u2014 try again later": "Trop de d\xE9p\xF4ts d\u2019images \u2014 r\xE9essayez plus tard",
|
|
333
|
-
"Top": "Haut",
|
|
334
|
-
"Typography": "Typographie",
|
|
335
|
-
"Undo": "Annuler",
|
|
336
|
-
"Undo (Ctrl+Z)": "Annuler (Ctrl+Z)",
|
|
337
|
-
"Unknown variables (rendered empty): {tags}": "Variables inconnues (affich\xE9es vides) : {tags}",
|
|
338
|
-
"Unsaved changes": "Modifications non enregistr\xE9es",
|
|
339
|
-
"Unsaved changes will be lost.": "Les modifications non enregistr\xE9es seront perdues.",
|
|
340
|
-
"Unsaved changes will be replaced.": "Les modifications non enregistr\xE9es seront remplac\xE9es.",
|
|
341
|
-
"Unsaved template from {when}.": "Mod\xE8le non enregistr\xE9 du {when}.",
|
|
342
|
-
"Unsubscribe": "Se d\xE9sinscrire",
|
|
343
|
-
"Unsubscribe link": "Lien de d\xE9sinscription",
|
|
344
|
-
"Upload": "D\xE9poser",
|
|
345
|
-
"Upload failed": "D\xE9p\xF4t impossible",
|
|
346
|
-
"URL rejected ({name})": "adresse refus\xE9e ({name})",
|
|
347
|
-
"Value if empty": "Valeur si vide",
|
|
348
|
-
"Variable \u201C{name}\u201D created and inserted.": "Variable \xAB {name} \xBB cr\xE9\xE9e et ins\xE9r\xE9e.",
|
|
349
|
-
"Variable \u201C{name}\u201D created.": "Variable \xAB {name} \xBB cr\xE9\xE9e.",
|
|
350
|
-
"Variable \u201C{name}\u201D saved.": "Variable \xAB {name} \xBB enregistr\xE9e.",
|
|
351
|
-
"Variable deleted.": "Variable supprim\xE9e.",
|
|
352
|
-
"Variable not found": "Variable introuvable",
|
|
353
|
-
"Variables": "Variables",
|
|
354
|
-
"Version {n}": "Version {n}",
|
|
355
|
-
"Version {n} restored \u2014 save to keep it.": "Version {n} recharg\xE9e \u2014 enregistrez pour la garder.",
|
|
356
|
-
"Version not found": "Version introuvable",
|
|
357
|
-
"Versions": "Versions",
|
|
358
|
-
"Vertical alignment": "Alignement vertical",
|
|
359
|
-
"Vertical space": "Vide vertical",
|
|
360
|
-
"Weight": "Graisse",
|
|
361
|
-
"When a recipient has none": "Quand un destinataire n\u2019en a pas",
|
|
362
|
-
"Width": "Largeur",
|
|
363
|
-
"Your company": "Votre soci\xE9t\xE9",
|
|
364
|
-
"Your headline": "Titre du message",
|
|
365
|
-
"Your text.": "Votre texte."
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
// src/i18n/index.js
|
|
369
|
-
var LOCALES = (
|
|
370
|
-
/** @type {Record<string, Messages>} */
|
|
371
|
-
{ en: {}, fr }
|
|
372
|
-
);
|
|
373
|
-
function createTranslator(messages = {}) {
|
|
374
|
-
return (text, params) => {
|
|
375
|
-
const template = Object.prototype.hasOwnProperty.call(messages, text) ? messages[text] : text;
|
|
376
|
-
if (!params) return template;
|
|
377
|
-
return template.replace(
|
|
378
|
-
/\{(\w+)\}/g,
|
|
379
|
-
(match, key) => params[key] === void 0 || params[key] === null ? match : String(params[key])
|
|
380
|
-
);
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
function translatorFor(locale = "en", overrides = {}) {
|
|
384
|
-
const base = LOCALES[String(locale).toLowerCase()] || LOCALES[String(locale).slice(0, 2).toLowerCase()] || {};
|
|
385
|
-
return createTranslator({ ...base, ...overrides });
|
|
386
|
-
}
|
|
387
|
-
var identity = createTranslator();
|
|
388
|
-
|
|
389
|
-
// src/features/variables/catalog.js
|
|
390
|
-
var DEFAULT_VARIABLES = [
|
|
391
|
-
{ key: "contact.firstname", label: "First name", group: "Recipient", sample: "Jane" },
|
|
392
|
-
{ key: "contact.lastname", label: "Last name", group: "Recipient", sample: "Doe" },
|
|
393
|
-
{ key: "contact.email", label: "Email address", group: "Recipient", sample: "jane.doe@example.com" },
|
|
394
|
-
{ key: "company.name", label: "Company", group: "Recipient", sample: "Example Inc." },
|
|
395
|
-
{ key: "campaign.name", label: "Campaign name", group: "Campaign", sample: "Newsletter" },
|
|
396
|
-
{
|
|
397
|
-
key: "unsubscribe",
|
|
398
|
-
label: "Unsubscribe link",
|
|
399
|
-
group: "Links",
|
|
400
|
-
sample: "",
|
|
401
|
-
token: "{{ unsubscribe }}",
|
|
402
|
-
kind: "link"
|
|
403
|
-
}
|
|
404
|
-
];
|
|
405
|
-
var CUSTOM_GROUP = "Custom";
|
|
406
|
-
var RESERVED_KEYS = /* @__PURE__ */ new Set(["unsubscribe", "mirror"]);
|
|
407
|
-
var VARIABLE_LIMITS = { key: 60, label: 80, group: 40, sample: 200, fallback: 200, description: 200 };
|
|
408
|
-
var KEY_SHAPE = /^[a-z][a-z0-9_]*(\.[a-z0-9_]+)*$/;
|
|
409
|
-
function tokenOf(variable) {
|
|
410
|
-
return variable.token || `{{${variable.key}}}`;
|
|
411
|
-
}
|
|
412
|
-
function keyFromLabel(label) {
|
|
413
|
-
const key = String(label || "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^[^a-z]+/, "").slice(0, VARIABLE_LIMITS.key).replace(/_+$/, "");
|
|
414
|
-
return key || "variable";
|
|
415
|
-
}
|
|
416
|
-
function defineVariables(variables) {
|
|
417
|
-
const seen = /* @__PURE__ */ new Set();
|
|
418
|
-
for (const variable of variables) {
|
|
419
|
-
if (!variable || !KEY_SHAPE.test(variable.key || "")) {
|
|
420
|
-
throw new Error(`variables: invalid key "${variable?.key}"`);
|
|
421
|
-
}
|
|
422
|
-
if (seen.has(variable.key)) throw new Error(`variables: duplicate key "${variable.key}"`);
|
|
423
|
-
seen.add(variable.key);
|
|
424
|
-
}
|
|
425
|
-
return variables;
|
|
426
|
-
}
|
|
427
|
-
var clean = (value, max) => String(value ?? "").replace(/\s+/g, " ").trim().slice(0, max);
|
|
428
|
-
function validateCustomVariable(input, { existing = [], editing = null, t = identity } = {}) {
|
|
429
|
-
const errors = {};
|
|
430
|
-
const label = clean(input?.label, VARIABLE_LIMITS.label);
|
|
431
|
-
const key = editing || String(input?.key || "").trim();
|
|
432
|
-
if (!label) errors.label = t("Name is required");
|
|
433
|
-
if (!editing) {
|
|
434
|
-
if (!key) errors.key = t("Key is required");
|
|
435
|
-
else if (key.length > VARIABLE_LIMITS.key || !KEY_SHAPE.test(key)) {
|
|
436
|
-
errors.key = t('Lowercase letters, digits, "_" and "." only, starting with a letter');
|
|
437
|
-
} else if (RESERVED_KEYS.has(key) || existing.some((v) => v.key === key)) {
|
|
438
|
-
errors.key = t("This key is already used");
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (Object.keys(errors).length) return { errors };
|
|
442
|
-
return {
|
|
443
|
-
variable: {
|
|
444
|
-
key,
|
|
445
|
-
label,
|
|
446
|
-
group: clean(input?.group, VARIABLE_LIMITS.group) || CUSTOM_GROUP,
|
|
447
|
-
sample: clean(input?.sample, VARIABLE_LIMITS.sample),
|
|
448
|
-
fallback: clean(input?.fallback, VARIABLE_LIMITS.fallback),
|
|
449
|
-
description: clean(input?.description, VARIABLE_LIMITS.description),
|
|
450
|
-
custom: true
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
function mergeVariables(system, custom = []) {
|
|
455
|
-
const keys = new Set(system.map((v) => v.key));
|
|
456
|
-
return [...system, ...custom.filter((v) => !keys.has(v.key)).map((v) => ({ ...v, custom: true }))];
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// src/renderer/links.js
|
|
460
|
-
var UNSUBSCRIBE = /\{\{\s*unsubscribe\s*\}\}|\[UNSUBSCRIBE\]/gi;
|
|
461
|
-
var MIRROR = /\{\{\s*mirror\s*\}\}/gi;
|
|
462
|
-
var DEFAULT_ASSETS_PATH = "/email-assets/";
|
|
463
|
-
var ASSET_PATH_PATTERN = /^\/(?:[A-Za-z0-9._~-]+\/)*[a-f0-9]{32}\.(png|jpg|gif)$/;
|
|
464
|
-
var RELATIVE_ASSET = /(^|["'(=\s])(\/(?:[A-Za-z0-9._~-]+\/)*[a-f0-9]{32}\.(?:png|jpg|gif))(?=["')\s>]|$)/g;
|
|
465
|
-
function isAssetPath(value) {
|
|
466
|
-
const path = String(value || "").trim();
|
|
467
|
-
return ASSET_PATH_PATTERN.test(path) && !path.split("/").some((part) => part === ".." || part === ".");
|
|
468
|
-
}
|
|
469
|
-
function absoluteAssetUrls(html, baseUrl) {
|
|
470
|
-
const root = String(baseUrl || "").replace(/\/+$/, "");
|
|
471
|
-
if (!root) return String(html || "");
|
|
472
|
-
return String(html || "").replace(
|
|
473
|
-
RELATIVE_ASSET,
|
|
474
|
-
(match, before, path) => isAssetPath(path) ? `${before}${root}${path}` : match
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
function escapeAttribute(text) {
|
|
478
|
-
return String(text ?? "").replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
479
|
-
}
|
|
480
|
-
function applyServiceLinks(html, { unsubscribeUrl }) {
|
|
481
|
-
return String(html || "").replace(UNSUBSCRIBE, () => escapeAttribute(unsubscribeUrl)).replace(MIRROR, "#");
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// src/features/variables/service.js
|
|
485
|
-
function escapeHtml(text) {
|
|
486
|
-
return String(text ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
487
|
-
}
|
|
488
|
-
function decodeHtml(text) {
|
|
489
|
-
return String(text ?? "").replace(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
490
|
-
}
|
|
491
|
-
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
492
|
-
function patternOf(key) {
|
|
493
|
-
return new RegExp(`\\{\\{\\s*${escapeRegExp(key)}\\s*(?:\\|([^{}]*))?\\}\\}`, "g");
|
|
494
|
-
}
|
|
495
|
-
var ANY_TAG = /\{\{\s*([A-Za-z][\w.]*)\s*(?:\|[^{}]*)?\}\}/g;
|
|
496
|
-
var textVariables = (variables) => variables.filter((v) => v.kind !== "link");
|
|
497
|
-
var present = (value) => value !== null && value !== void 0 && String(value) !== "";
|
|
498
|
-
function replace(text, values, variables, html) {
|
|
499
|
-
let out = String(text || "");
|
|
500
|
-
for (const variable of textVariables(variables)) {
|
|
501
|
-
out = out.replace(patternOf(variable.key), (_, inline) => {
|
|
502
|
-
const value = values?.[variable.key];
|
|
503
|
-
if (present(value)) return html ? escapeHtml(value) : String(value);
|
|
504
|
-
const fallback = inline !== void 0 ? (html ? decodeHtml(inline) : inline).trim() : variable.fallback || "";
|
|
505
|
-
return html ? escapeHtml(fallback) : fallback;
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
return out;
|
|
509
|
-
}
|
|
510
|
-
function applyVariables(html, values, variables = DEFAULT_VARIABLES) {
|
|
511
|
-
return replace(html, values, variables, true);
|
|
512
|
-
}
|
|
513
|
-
function applyVariablesToText(text, values, variables = DEFAULT_VARIABLES) {
|
|
514
|
-
return replace(text, values, variables, false);
|
|
515
|
-
}
|
|
516
|
-
function mapVariables(html, mapping, variables = DEFAULT_VARIABLES) {
|
|
517
|
-
let out = String(html || "");
|
|
518
|
-
for (const variable of textVariables(variables)) {
|
|
519
|
-
if (!Object.prototype.hasOwnProperty.call(mapping, variable.key)) continue;
|
|
520
|
-
out = out.replace(patternOf(variable.key), () => String(mapping[variable.key]));
|
|
521
|
-
}
|
|
522
|
-
return out;
|
|
523
|
-
}
|
|
524
|
-
function usedVariables(text, variables = DEFAULT_VARIABLES) {
|
|
525
|
-
const source = String(text || "");
|
|
526
|
-
return variables.filter((v) => patternOf(v.key).test(source)).map((v) => v.key);
|
|
527
|
-
}
|
|
528
|
-
function unknownVariables(text, variables = DEFAULT_VARIABLES, ignore = ["unsubscribe", "mirror"]) {
|
|
529
|
-
const known = /* @__PURE__ */ new Set([...variables.map((v) => v.key), ...ignore]);
|
|
530
|
-
const found = /* @__PURE__ */ new Set();
|
|
531
|
-
for (const match of String(text || "").matchAll(ANY_TAG)) {
|
|
532
|
-
if (!known.has(match[1])) found.add(match[1]);
|
|
533
|
-
}
|
|
534
|
-
return [...found];
|
|
535
|
-
}
|
|
536
|
-
function sampleValues(variables = DEFAULT_VARIABLES, overrides = {}) {
|
|
537
|
-
const values = Object.fromEntries(textVariables(variables).map((v) => [v.key, v.sample]));
|
|
538
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
539
|
-
if (present(value)) values[key] = String(value);
|
|
540
|
-
}
|
|
541
|
-
return values;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// src/validation/images.js
|
|
545
|
-
var MAX_IMAGE_BYTES = 2 * 1024 * 1024;
|
|
546
|
-
var IMAGE_TYPES = { "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif" };
|
|
547
|
-
var ASSET_KEY_PATTERN = /^[a-f0-9]{32}\.(png|jpg|gif)$/;
|
|
548
|
-
var SIGNATURES = [
|
|
549
|
-
{ type: "image/png", bytes: [137, 80, 78, 71, 13, 10, 26, 10] },
|
|
550
|
-
{ type: "image/jpeg", bytes: [255, 216, 255] },
|
|
551
|
-
{ type: "image/gif", bytes: [71, 73, 70, 56] }
|
|
552
|
-
];
|
|
553
|
-
function detectImage(bytes) {
|
|
554
|
-
if (!bytes || bytes.length < 8) return null;
|
|
555
|
-
const found = SIGNATURES.find((s) => s.bytes.every((b, i) => bytes[i] === b));
|
|
556
|
-
return found ? { type: found.type, ext: IMAGE_TYPES[found.type] } : null;
|
|
557
|
-
}
|
|
558
|
-
function imageUploadError(file, { t = identity } = {}) {
|
|
559
|
-
if (!(String(file?.type || "") in IMAGE_TYPES)) return t("Format rejected: PNG, JPEG or GIF expected");
|
|
560
|
-
if (Number(file?.size || 0) > MAX_IMAGE_BYTES) {
|
|
561
|
-
return t("Image too large: {max} MB maximum", { max: MAX_IMAGE_BYTES / (1024 * 1024) });
|
|
562
|
-
}
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// src/validation/urls.js
|
|
567
|
-
var LINK_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "action", "formaction", "xlink:href"]);
|
|
568
|
-
var IMAGE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
569
|
-
"src",
|
|
570
|
-
"srcset",
|
|
571
|
-
"background-url",
|
|
572
|
-
"background",
|
|
573
|
-
"poster",
|
|
574
|
-
"icon-unwrapped-url",
|
|
575
|
-
"icon-wrapped-url"
|
|
576
|
-
]);
|
|
577
|
-
var NAMED_ENTITIES = {
|
|
578
|
-
colon: ":",
|
|
579
|
-
tab: " ",
|
|
580
|
-
newline: "\n",
|
|
581
|
-
lpar: "(",
|
|
582
|
-
rpar: ")",
|
|
583
|
-
amp: "&",
|
|
584
|
-
quot: '"',
|
|
585
|
-
apos: "'",
|
|
586
|
-
lt: "<",
|
|
587
|
-
gt: ">",
|
|
588
|
-
nbsp: " "
|
|
589
|
-
};
|
|
590
|
-
function decodeEntities(value) {
|
|
591
|
-
return String(value ?? "").replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);?/gi, (whole, body) => {
|
|
592
|
-
const key = String(body).toLowerCase();
|
|
593
|
-
if (key.startsWith("#x")) return String.fromCodePoint(Number.parseInt(key.slice(2), 16) || 0);
|
|
594
|
-
if (key.startsWith("#")) return String.fromCodePoint(Number.parseInt(key.slice(1), 10) || 0);
|
|
595
|
-
return NAMED_ENTITIES[key] ?? whole;
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
function isMergeTag(value) {
|
|
599
|
-
return /^\{\{\s*[a-zA-Z][\w.]*\s*\}\}$/.test(String(value || "").trim());
|
|
600
|
-
}
|
|
601
|
-
function isSafeUrl(raw, kind) {
|
|
602
|
-
const value = decodeEntities(raw).trim();
|
|
603
|
-
if (!value) return true;
|
|
604
|
-
if (isMergeTag(value)) return true;
|
|
605
|
-
const compact = value.replace(/[\s\u0000-\u001f\u007f]+/g, "").toLowerCase();
|
|
606
|
-
if (kind === "image") return compact.startsWith("https://") || isAssetPath(value);
|
|
607
|
-
if (compact.startsWith("#")) return true;
|
|
608
|
-
return /^(https?:\/\/|mailto:|tel:)/.test(compact);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
export {
|
|
612
|
-
EMAIL_BUILDER_VERSION,
|
|
613
|
-
EMAIL_DOCUMENT_SCHEMA_VERSION,
|
|
614
|
-
EMAIL_DEVICES,
|
|
615
|
-
EMAIL_MOBILE_WIDTH,
|
|
616
|
-
EmailDocumentError,
|
|
617
|
-
createDocument,
|
|
618
|
-
readDocument,
|
|
619
|
-
serializeDocument,
|
|
620
|
-
LOCALES,
|
|
621
|
-
createTranslator,
|
|
622
|
-
translatorFor,
|
|
623
|
-
identity,
|
|
624
|
-
DEFAULT_VARIABLES,
|
|
625
|
-
CUSTOM_GROUP,
|
|
626
|
-
VARIABLE_LIMITS,
|
|
627
|
-
tokenOf,
|
|
628
|
-
keyFromLabel,
|
|
629
|
-
defineVariables,
|
|
630
|
-
validateCustomVariable,
|
|
631
|
-
mergeVariables,
|
|
632
|
-
DEFAULT_ASSETS_PATH,
|
|
633
|
-
isAssetPath,
|
|
634
|
-
absoluteAssetUrls,
|
|
635
|
-
applyServiceLinks,
|
|
636
|
-
MAX_IMAGE_BYTES,
|
|
637
|
-
IMAGE_TYPES,
|
|
638
|
-
ASSET_KEY_PATTERN,
|
|
639
|
-
detectImage,
|
|
640
|
-
imageUploadError,
|
|
641
|
-
LINK_ATTRIBUTES,
|
|
642
|
-
IMAGE_ATTRIBUTES,
|
|
643
|
-
decodeEntities,
|
|
644
|
-
isSafeUrl,
|
|
645
|
-
applyVariables,
|
|
646
|
-
applyVariablesToText,
|
|
647
|
-
mapVariables,
|
|
648
|
-
usedVariables,
|
|
649
|
-
unknownVariables,
|
|
650
|
-
sampleValues
|
|
651
|
-
};
|
|
652
|
-
//# sourceMappingURL=chunk-3EOCXTI2.js.map
|