best-email-builder 0.2.0 → 0.4.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +186 -0
  2. package/README.md +309 -12
  3. package/dist/builder.js +44 -0
  4. package/dist/builder.js.map +1 -0
  5. package/dist/chunk-AT73Y7JA.js +1427 -0
  6. package/dist/chunk-AT73Y7JA.js.map +1 -0
  7. package/dist/chunk-HHLOE6OZ.js +1682 -0
  8. package/dist/chunk-HHLOE6OZ.js.map +1 -0
  9. package/dist/chunk-JNHFZXDL.js +1776 -0
  10. package/dist/chunk-JNHFZXDL.js.map +1 -0
  11. package/dist/chunk-MY6WPDT4.js +6110 -0
  12. package/dist/chunk-MY6WPDT4.js.map +1 -0
  13. package/dist/chunk-NAEGU6PR.js +538 -0
  14. package/dist/chunk-NAEGU6PR.js.map +1 -0
  15. package/dist/chunk-TGDZ4NFI.js +114 -0
  16. package/dist/chunk-TGDZ4NFI.js.map +1 -0
  17. package/dist/index.js +124 -4866
  18. package/dist/index.js.map +1 -1
  19. package/dist/library.js +31 -0
  20. package/dist/library.js.map +1 -0
  21. package/dist/server.js +554 -210
  22. package/dist/server.js.map +1 -1
  23. package/dist/styles.css +1 -1
  24. package/dist/types/blocks/shared.d.ts +20 -0
  25. package/dist/types/builder.d.ts +8 -0
  26. package/dist/types/core/EmailEditor.d.ts +72 -19
  27. package/dist/types/core/constants.d.ts +1 -1
  28. package/dist/types/core/mjmlDefaults.d.ts +13 -0
  29. package/dist/types/core/scrollbars.d.ts +13 -0
  30. package/dist/types/defaultEditor.d.ts +11 -0
  31. package/dist/types/engine/NativeAdapter.d.ts +28 -0
  32. package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
  33. package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
  34. package/dist/types/engine/canvas/icons.d.ts +14 -0
  35. package/dist/types/engine/canvas/morph.d.ts +21 -0
  36. package/dist/types/engine/canvasMjml.d.ts +30 -0
  37. package/dist/types/engine/mjml.d.ts +31 -0
  38. package/dist/types/engine/model.d.ts +130 -0
  39. package/dist/types/engine/placement.d.ts +69 -0
  40. package/dist/types/engine/richText.d.ts +15 -0
  41. package/dist/types/engine/unwrapModule.d.ts +29 -0
  42. package/dist/types/features/brand/BrandButton.d.ts +4 -0
  43. package/dist/types/features/brand/brand.d.ts +108 -0
  44. package/dist/types/features/brand/extract.d.ts +61 -0
  45. package/dist/types/features/brand/index.d.ts +2 -0
  46. package/dist/types/features/brand/messages.d.ts +13 -0
  47. package/dist/types/features/brand/useBrand.d.ts +22 -0
  48. package/dist/types/features/generate/GenerateButton.d.ts +3 -0
  49. package/dist/types/features/generate/RewriteText.d.ts +5 -0
  50. package/dist/types/features/generate/SubjectIdeas.d.ts +16 -0
  51. package/dist/types/features/generate/chip.d.ts +1 -0
  52. package/dist/types/features/generate/claude.d.ts +21 -0
  53. package/dist/types/features/generate/draft.d.ts +122 -0
  54. package/dist/types/features/generate/generator.d.ts +118 -0
  55. package/dist/types/features/generate/index.d.ts +2 -0
  56. package/dist/types/features/generate/prompt.d.ts +76 -0
  57. package/dist/types/features/generate/purposes.d.ts +48 -0
  58. package/dist/types/features/generate/rewrite.d.ts +83 -0
  59. package/dist/types/features/generate/subjects.d.ts +83 -0
  60. package/dist/types/features/preview/PreviewButton.d.ts +1 -1
  61. package/dist/types/features/preview/darkMode.d.ts +48 -0
  62. package/dist/types/features/templates/gallery.d.ts +7 -0
  63. package/dist/types/index.d.ts +14 -10
  64. package/dist/types/library.d.ts +5 -0
  65. package/dist/types/server/brand.d.ts +32 -0
  66. package/dist/types/server/brandImport.d.ts +93 -0
  67. package/dist/types/server/compileMjml.d.ts +2 -0
  68. package/dist/types/server/handler.d.ts +25 -0
  69. package/dist/types/server/index.d.ts +11 -0
  70. package/dist/types/storage/client.d.ts +79 -3
  71. package/dist/types/ui/EmailStudio.d.ts +42 -0
  72. package/dist/types/ui/fields.d.ts +2 -1
  73. package/dist/types/ui/library/TemplateLibrary.d.ts +41 -0
  74. package/dist/types/ui/primitives/button.d.ts +2 -2
  75. package/dist/types/ui/primitives/color-picker.d.ts +2 -1
  76. package/dist/types/ui/useLayoutWidth.d.ts +1 -1
  77. package/package.json +27 -4
  78. package/dist/chunk-MTALS5GC.js +0 -747
  79. package/dist/chunk-MTALS5GC.js.map +0 -1
  80. package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
  81. package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -162
  82. package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/constants.js","../src/core/document.js","../src/i18n/fr.js","../src/i18n/index.js","../src/features/variables/catalog.js","../src/renderer/links.js","../src/features/variables/service.js","../src/validation/images.js","../src/validation/urls.js"],"sourcesContent":["// @ts-check\n/**\n * Email builder constants.\n *\n * `EMAIL_BUILDER_VERSION` versions THIS package (UI, blocks, rendering);\n * `EMAIL_DOCUMENT_SCHEMA_VERSION` versions the stored JSON envelope (the\n * project JSON). Both are written with every template, so a document read by\n * a newer version knows whether it must be migrated (see `core/document.js`).\n */\n\nexport const EMAIL_BUILDER_VERSION = '0.2.0';\n\nexport const EMAIL_DOCUMENT_SCHEMA_VERSION = 1;\n\n/** @typedef {'desktop'|'mobile'} EmailDevice */\n\n/** @type {EmailDevice[]} */\nexport const EMAIL_DEVICES = ['desktop', 'mobile'];\n\n/** Content column width — the email client standard. */\nexport const EMAIL_CONTENT_WIDTH = 600;\n\n/** Mobile preview frame width (standard iPhone, the common worst case). */\nexport const EMAIL_MOBILE_WIDTH = 375;\n","// @ts-check\n/**\n * DOCUMENT envelope — what gets stored (the project JSON).\n *\n * HTML is only a compilation output; what gets edited is the engine's\n * project. It is not stored bare but wrapped with:\n *\n * schemaVersion the shape of THIS envelope (migrations, see MIGRATIONS);\n * engine the engine that can read `project` back;\n * engineVersion its version at write time;\n * builderVersion the builder version that wrote it;\n * project the engine data, opaque to the rest of the code.\n *\n * A document from another engine, a future schema or a malformed one is\n * REJECTED on read rather than half-loaded: a template silently emptied and\n * then saved again would be lost.\n *\n * Dependency-free module: testable outside the browser.\n */\n\nimport { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from './constants.js';\n\n/**\n * @typedef {{\n * schemaVersion: number,\n * engine: string,\n * engineVersion: string,\n * builderVersion: string,\n * project: Record<string, any>,\n * }} EmailDocument\n */\n\n/**\n * Envelope migrations, from N to N+1. Empty while only one schema exists; the\n * first entry will be `1: (doc) => ({ ...doc, schemaVersion: 2 })`.\n *\n * @type {Record<number, (document: any) => any>}\n */\nconst MIGRATIONS = {};\n\nexport class EmailDocumentError extends Error {\n /** @param {string} message */\n constructor(message) {\n super(message);\n this.name = 'EmailDocumentError';\n this.status = 422;\n }\n}\n\nfunction isPlainObject(value) {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * @param {{engine: string, engineVersion: string, project: object}} parts\n * @returns {EmailDocument}\n */\nexport function createDocument({ engine, engineVersion, project }) {\n if (!engine) throw new EmailDocumentError('Document: missing engine');\n if (!isPlainObject(project)) throw new EmailDocumentError('Document: invalid engine project');\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine,\n engineVersion: String(engineVersion || ''),\n builderVersion: EMAIL_BUILDER_VERSION,\n project: /** @type {Record<string, any>} */ (project),\n };\n}\n\n/**\n * Reads a document (object or JSON string), migrates it to the current schema\n * and validates it.\n *\n * @param {unknown} raw\n * @returns {EmailDocument}\n * @throws {EmailDocumentError}\n */\nexport function readDocument(raw) {\n let value = raw;\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value);\n } catch {\n throw new EmailDocumentError('Unreadable document: invalid JSON');\n }\n }\n if (!isPlainObject(value)) throw new EmailDocumentError('Unreadable document: object expected');\n\n /** @type {any} */\n let document = { ...(/** @type {object} */ (value)) };\n const version = Number(document.schemaVersion);\n if (!Number.isInteger(version) || version < 1) {\n throw new EmailDocumentError('Unreadable document: missing schema version');\n }\n if (version > EMAIL_DOCUMENT_SCHEMA_VERSION) {\n throw new EmailDocumentError(\n `Document written by a newer version of the builder (schema ${version}) — ` +\n 'reload the page'\n );\n }\n for (let v = version; v < EMAIL_DOCUMENT_SCHEMA_VERSION; v += 1) {\n const migrate = MIGRATIONS[v];\n if (!migrate) throw new EmailDocumentError(`Document: no migration from schema ${v}`);\n document = migrate(document);\n }\n\n if (typeof document.engine !== 'string' || !document.engine) {\n throw new EmailDocumentError('Unreadable document: missing engine');\n }\n if (!isPlainObject(document.project)) {\n throw new EmailDocumentError('Unreadable document: missing engine project');\n }\n\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine: document.engine,\n engineVersion: String(document.engineVersion || ''),\n builderVersion: String(document.builderVersion || ''),\n project: document.project,\n };\n}\n\n/** @param {EmailDocument} document */\nexport function serializeDocument(document) {\n return JSON.stringify(readDocument(document));\n}\n","// French locale. Keys are the English source strings (see src/i18n/index.js).\n// Missing keys: `node scripts/i18n-keys.mjs fr`.\n\n/** @type {Record<string, string>} */\nexport const fr = {\n \"1. Complete your profile\": \"1. Complétez votre profil\",\n \"2. Explore\": \"2. Explorez\",\n \"3. Ask us anything\": \"3. Posez-nous vos questions\",\n \"30% off everything\": \"-30 % sur tout\",\n \"30% off this weekend only\": \"-30 % ce week-end seulement\",\n \"4 columns\": \"4 colonnes\",\n \"4-column section\": \"Section en 4 colonnes\",\n \"A new feature\": \"Une nouveauté\",\n \"A sale with a discount code and two products\": \"Une vente avec un code promo et deux produits\",\n \"A tip\": \"Une astuce\",\n \"Add a block\": \"Ajouter un bloc\",\n \"An empty page to build from scratch\": \"Une page vide pour partir de zéro\",\n \"An evening of talks and drinks with the team and our customers.\": \"Une soirée de rencontres autour d'un verre avec l'équipe et nos clients.\",\n \"An event\": \"Un événement\",\n \"Article {n}\": \"Article {n}\",\n \"Background image\": \"Image de fond\",\n \"Background image with a title and a button\": \"Image de fond avec un titre et un bouton\",\n \"Blank\": \"Vierge\",\n \"Book my seat\": \"Je réserve ma place\",\n \"Buy\": \"Acheter\",\n \"Date, place and a button to book a seat\": \"Date, lieu et un bouton pour réserver\",\n \"Direction\": \"Disposition\",\n \"Edit: {element}\": \"Modifier : {element}\",\n \"Email settings\": \"Réglages de l'e-mail\",\n \"Event invitation\": \"Invitation\",\n \"Facebook\": \"Facebook\",\n \"Fill the section\": \"Remplir la section\",\n \"Get started\": \"Commencer\",\n \"GitHub\": \"GitHub\",\n \"Greets a new customer and lists the first steps\": \"Accueille un nouveau client et liste les premières étapes\",\n \"Heading\": \"Titre\",\n \"Hero banner\": \"Bannière\",\n \"Horizontal\": \"Horizontale\",\n \"Icon background\": \"Fond de l'icône\",\n \"Icon size\": \"Taille des icônes\",\n \"Icons linking to your profiles\": \"Icônes vers vos profils\",\n \"Image + text\": \"Image + texte\",\n \"Image beside a title, text and button\": \"Image à côté d'un titre, d'un texte et d'un bouton\",\n \"Image position\": \"Position de l'image\",\n \"Image size\": \"Taille de l'image\",\n \"In brief\": \"En bref\",\n \"Instagram\": \"Instagram\",\n \"Large bold title\": \"Grand titre en gras\",\n \"LinkedIn\": \"LinkedIn\",\n \"Logo, headline, text and a button\": \"Logo, titre, texte et un bouton\",\n \"Menu\": \"Menu\",\n \"Menu link\": \"Lien du menu\",\n \"Monthly news: banner, two articles, highlights\": \"Les nouvelles du mois : bannière, deux articles, brèves\",\n \"Network\": \"Réseau\",\n \"Newsletter\": \"Newsletter\",\n \"Offer valid until Sunday midnight, while stocks last.\": \"Offre valable jusqu'à dimanche minuit, dans la limite des stocks.\",\n \"One sentence about it.\": \"Une phrase pour en parler.\",\n \"Original size\": \"Taille d'origine\",\n \"Our offices, 1 Main Street\": \"Nos bureaux, 1 rue Principale\",\n \"Picking a template replaces the email being edited.\": \"Choisir un modèle remplace l'e-mail en cours de modification.\",\n \"Product one\": \"Produit un\",\n \"Product two\": \"Produit deux\",\n \"Profile URL\": \"Adresse du profil\",\n \"Promotion\": \"Promotion\",\n \"Read more\": \"Lire la suite\",\n \"Replace\": \"Remplacer\",\n \"Replace the current email?\": \"Remplacer l'e-mail actuel ?\",\n \"Reply to this email: a person reads it.\": \"Répondez à cet e-mail : une personne le lit.\",\n \"Row of links to your site\": \"Rangée de liens vers votre site\",\n \"Save the date — seats are limited.\": \"Notez la date — les places sont limitées.\",\n \"Seats are limited: book yours now, it takes ten seconds.\": \"Les places sont limitées : réservez la vôtre, cela prend dix secondes.\",\n \"Shop the sale\": \"Voir les offres\",\n \"Simple\": \"Simple\",\n \"So we can tailor what we send you.\": \"Pour adapter ce que nous vous envoyons.\",\n \"Social link\": \"Réseau social\",\n \"Social links\": \"Réseaux sociaux\",\n \"Space around each icon\": \"Espace autour de chaque icône\",\n \"Start from a template\": \"Partir d'un modèle\",\n \"Take a tour of what you can do.\": \"Faites le tour de ce que vous pouvez faire.\",\n \"Templates\": \"Modèles\",\n \"Thank you for joining us. Here is how to get the most out of your account.\": \"Merci de nous avoir rejoints. Voici comment tirer le meilleur de votre compte.\",\n \"The news of the month\": \"Les nouvelles du mois\",\n \"This weekend only, with the code SPRING30.\": \"Ce week-end seulement, avec le code SPRING30.\",\n \"Three steps to get started.\": \"Trois étapes pour bien commencer.\",\n \"Thursday, 7 pm\": \"Jeudi, 19 h\",\n \"Two or three sentences that sum up the story and make people want the rest.\": \"Deux ou trois phrases qui résument l'article et donnent envie de lire la suite.\",\n \"Vertical\": \"Verticale\",\n \"Welcome\": \"Bienvenue\",\n \"Welcome aboard!\": \"Bienvenue à bord !\",\n \"Welcome, {name}!\": \"Bienvenue, {name} !\",\n \"What changed, what is coming, and two stories worth your time.\": \"Ce qui a changé, ce qui arrive, et deux articles qui valent le détour.\",\n \"When\": \"Quand\",\n \"Where\": \"Où\",\n \"Whole image\": \"Image entière\",\n \"With the code SPRING30, until Sunday midnight.\": \"Avec le code SPRING30, jusqu'à dimanche minuit.\",\n \"X\": \"X\",\n \"You're invited\": \"On vous invite !\",\n \"YouTube\": \"YouTube\",\n \"{label} — {side}\": \"{label} — {side}\",\n \"{max} test addresses at most\": \"{max} adresses de test au maximum\",\n \"{message} Your version can be saved as a copy.\": \"{message} Votre version peut être enregistrée comme copie.\",\n \"{name} (copy)\": \"{name} (copie)\",\n \"{sent} sent, {failed} failed.\": \"{sent} parti(s), {failed} en échec.\",\n \"{sent} test(s) sent — check your inbox.\": \"{sent} test(s) parti(s) — ouvrez votre messagerie avant de continuer.\",\n \"1 column\": \"1 colonne\",\n \"2 columns\": \"2 colonnes\",\n \"2-column section\": \"Section en 2 colonnes\",\n \"25%\": \"25 %\",\n \"3 columns\": \"3 colonnes\",\n \"3-column section\": \"Section en 3 colonnes\",\n \"50%\": \"50 %\",\n \"600 px (standard)\": \"600 px (standard)\",\n \"640 px\": \"640 px\",\n \"75%\": \"75 %\",\n \"Address not allowed\": \"Adresse refusée\",\n \"Addresses\": \"Adresses\",\n \"Alignment\": \"Alignement\",\n \"Alt text\": \"Texte alternatif\",\n \"An https:// address is required\": \"Une adresse https:// est requise\",\n \"attribute {name} not allowed\": \"attribut {name} interdit\",\n \"Authentication required\": \"Authentification requise\",\n \"Autosave\": \"Sauvegarde automatique\",\n \"Autosave paused\": \"Sauvegarde automatique suspendue\",\n \"Autosaved at {time}\": \"Enregistré automatiquement à {time}\",\n \"Autosaving…\": \"Sauvegarde automatique…\",\n \"Available in every template.\": \"Disponible dans tous les modèles.\",\n \"Back\": \"Retour\",\n \"Background\": \"Fond\",\n \"base tag not allowed\": \"balise base interdite\",\n \"Blocks\": \"Blocs\",\n \"Bold\": \"Gras\",\n \"Bottom\": \"Bas\",\n \"Button\": \"Bouton\",\n \"Button background\": \"Fond du bouton\",\n \"Button padding\": \"Marges du bouton\",\n \"Button without link ({count})\": \"Bouton sans lien ({count})\",\n \"Call to action\": \"Appel à l’action\",\n \"Campaign\": \"Campagne\",\n \"Campaign name\": \"Nom de la campagne\",\n \"Cancel\": \"Annuler\",\n \"CDATA section not allowed\": \"section CDATA interdite\",\n \"Center\": \"Centre\",\n \"Close\": \"Fermer\",\n \"Colors\": \"Couleurs\",\n \"Column\": \"Colonne\",\n \"Company\": \"Société\",\n \"component script not allowed\": \"script de composant interdit\",\n \"conditional comment not allowed\": \"commentaire conditionnel interdit\",\n \"Confirm\": \"Confirmer\",\n \"Content\": \"Contenu\",\n \"Content width\": \"Largeur du contenu\",\n \"Copy saved: “{name}”.\": \"Copie enregistrée : « {name} ».\",\n \"Corner radius\": \"Arrondi\",\n \"Create\": \"Créer\",\n \"Create “{name}”\": \"Créer « {name} »\",\n \"current\": \"actuelle\",\n \"Custom\": \"Personnalisées\",\n \"Custom variables are not configured\": \"Les variables personnalisées ne sont pas configurées\",\n \"Dashed\": \"Tirets\",\n \"Default\": \"Par défaut\",\n \"Delete\": \"Supprimer\",\n \"Delete (Del)\": \"Supprimer (Suppr)\",\n \"Delete the variable “{name}”?\": \"Supprimer la variable « {name} » ?\",\n \"Describes the image if it is blocked\": \"Décrit l’image si elle est bloquée\",\n \"Deselect\": \"Désélectionner\",\n \"Desktop\": \"Bureau\",\n \"Device\": \"Appareil\",\n \"Dismiss\": \"Masquer\",\n \"Divider\": \"Séparateur\",\n \"Dotted\": \"Pointillés\",\n \"Double-click the text to edit it.\": \"Double-cliquez sur le texte pour le modifier.\",\n \"Duplicate\": \"Dupliquer\",\n \"Edit\": \"Modifier\",\n \"Edit “{name}”\": \"Modifier « {name} »\",\n \"Edit variable\": \"Modifier la variable\",\n \"Element actions\": \"Actions sur l’élément\",\n \"Email\": \"Courriel\",\n \"Email address\": \"Adresse e-mail\",\n \"Email preview\": \"Aperçu du courriel\",\n \"embed not allowed\": \"embed interdit\",\n \"Empty file\": \"Fichier vide\",\n \"Empty MJML\": \"MJML vide\",\n \"Empty MJML compilation\": \"Compilation MJML vide\",\n \"Empty subject\": \"Objet vide\",\n \"executable content not allowed\": \"contenu exécutable interdit\",\n \"external resource not allowed\": \"ressource externe interdite\",\n \"File missing\": \"Fichier manquant\",\n \"Final rendering, variables filled with samples.\": \"Rendu final, variables remplies d’exemples.\",\n \"First name\": \"Prénom\",\n \"Fit to text\": \"Ajustée au texte\",\n \"Font\": \"Police\",\n \"forbidden element in project: {tag}\": \"élément interdit dans le projet : {tag}\",\n \"form field not allowed\": \"champ de formulaire interdit\",\n \"form not allowed\": \"formulaire interdit\",\n \"Format rejected: PNG, JPEG or GIF expected\": \"Format refusé : PNG, JPEG ou GIF attendu\",\n \"frame not allowed\": \"frame interdite\",\n \"Full length\": \"Toute la largeur\",\n \"Full width\": \"Pleine largeur\",\n \"Full-width section\": \"Section pleine largeur\",\n \"Give the template a name before saving it.\": \"Donnez un libellé au modèle avant de l’enregistrer.\",\n \"Group\": \"Groupe\",\n \"Height\": \"Hauteur\",\n \"Hello {name},\": \"Bonjour {name},\",\n \"Hex code\": \"Code hexadécimal\",\n \"Horizontal rule\": \"Filet horizontal\",\n \"HTML button not allowed (use the Button block)\": \"bouton HTML interdit (utiliser le bloc Bouton)\",\n \"iframe not allowed\": \"iframe interdite\",\n \"Ignored (invalid): {email}\": \"Ignorée (invalide) : {email}\",\n \"Image\": \"Image\",\n \"image rejected ({name})\": \"image refusée ({name})\",\n \"image rejected ({name}): https only\": \"image refusée ({name}) : https seulement\",\n \"Image too large: {max} MB maximum\": \"Image trop lourde : {max} Mo au maximum\",\n \"Image without alt text ({count})\": \"Texte alternatif manquant ({count} image(s))\",\n \"Image without file ({count})\": \"Image sans fichier ({count})\",\n \"Inbox\": \"Boîte de réception\",\n \"Indicative preview — check it with “Send test”.\": \"Aperçu indicatif — validez avec « Envoyer un test ».\",\n \"inline style rejected\": \"style en ligne refusé\",\n \"Insecure http link ({count})\": \"Lien non sécurisé en http ({count})\",\n \"Insert a variable in the preview text\": \"Insérer une variable dans le texte d’aperçu\",\n \"Insert a variable in the subject\": \"Insérer une variable dans l’objet\",\n \"Internal error\": \"Erreur interne\",\n \"Invalid JSON\": \"JSON invalide\",\n \"Justify\": \"Justifié\",\n \"Key is required\": \"Balise requise\",\n \"Last name\": \"Nom\",\n \"Layout\": \"Mise en page\",\n \"Learn more\": \"Découvrir\",\n \"Leave\": \"Quitter\",\n \"Leave the builder?\": \"Quitter le constructeur ?\",\n \"Left\": \"Gauche\",\n \"Length\": \"Longueur\",\n \"Line color\": \"Couleur du filet\",\n \"Line height\": \"Interligne\",\n \"Line style\": \"Trait\",\n \"Link\": \"Lien\",\n \"Link on click\": \"Lien au clic\",\n \"Link URL\": \"Adresse du lien\",\n \"Links\": \"Liens\",\n \"Loading the builder\": \"Chargement du constructeur\",\n \"Loading…\": \"Chargement…\",\n \"Lowercase letters, digits, \\\"_\\\" and \\\".\\\" only, starting with a letter\": \"Minuscules, chiffres, « _ » et « . » seulement, en commençant par une lettre\",\n \"MathML not allowed\": \"MathML interdit\",\n \"Message is {size} KB: Gmail clips messages over {limit} KB\": \"Message de {size} Ko : Gmail le coupe au-delà de {limit} Ko\",\n \"meta tag not allowed\": \"balise meta interdite\",\n \"Middle\": \"Milieu\",\n \"missing <mj-body>\": \"<mj-body> manquant\",\n \"Missing unsubscribe link\": \"Lien de désinscription absent\",\n \"mj-font (external font) is outside the builder\": \"mj-font (police externe) est hors du constructeur\",\n \"mj-include would read a file from the server\": \"mj-include lirait un fichier du serveur\",\n \"mj-raw (free HTML) is not allowed\": \"mj-raw (HTML libre) est refusé\",\n \"mj-style (free CSS) is outside the builder\": \"mj-style (CSS libre) est hors du constructeur\",\n \"MJML too large ({size} KB, maximum {max} KB)\": \"MJML trop volumineux ({size} ko, maximum {max} ko)\",\n \"Mobile\": \"Mobile\",\n \"Move down\": \"Descendre\",\n \"Move left\": \"Déplacer à gauche\",\n \"Move right\": \"Déplacer à droite\",\n \"Move up\": \"Monter\",\n \"Multipart form expected\": \"Formulaire multipart attendu\",\n \"Name\": \"Libellé\",\n \"Name is required\": \"Libellé requis\",\n \"Name required\": \"Libellé requis\",\n \"New template\": \"Nouveau modèle\",\n \"New variable\": \"Nouvelle variable\",\n \"No changes\": \"Aucune modification\",\n \"No preview text\": \"Aucun texte d’aperçu\",\n \"No subject\": \"Sans objet\",\n \"No usable test address\": \"Aucune adresse de test exploitable\",\n \"No variable found.\": \"Aucune variable trouvée.\",\n \"No versions yet.\": \"Aucune version.\",\n \"Normal\": \"Normale\",\n \"Nothing to fix.\": \"Aucun point à corriger.\",\n \"Nothing to show yet.\": \"Rien à afficher pour l’instant.\",\n \"object not allowed\": \"object interdit\",\n \"Order number\": \"Numéro de commande\",\n \"Padding\": \"Marges\",\n \"Page background\": \"Fond de page\",\n \"Paragraph or heading\": \"Paragraphe ou titre\",\n \"Photo, visual or logo\": \"Photo, visuel ou logo\",\n \"Pick a colour\": \"Choisir une couleur\",\n \"Points to check\": \"Points à vérifier\",\n \"Preview\": \"Aperçu\",\n \"Preview and tests\": \"Aperçu et tests\",\n \"Preview text\": \"Texte d’aperçu\",\n \"Project rejected: {issues}\": \"Projet refusé : {issues}\",\n \"project too deep\": \"projet trop profond\",\n \"project too large ({size} KB, maximum {max} KB)\": \"projet trop volumineux ({size} ko, maximum {max} ko)\",\n \"Properties\": \"Propriétés\",\n \"Recipient\": \"Destinataire\",\n \"Redo\": \"Rétablir\",\n \"Redo (Ctrl+Y)\": \"Rétablir (Ctrl+Y)\",\n \"Refresh\": \"Rafraîchir\",\n \"Rendering…\": \"Rendu en cours…\",\n \"Request too large\": \"Requête trop volumineuse\",\n \"Restore\": \"Recharger\",\n \"Restore version {n}\": \"Recharger la version {n}\",\n \"Restore version {n}?\": \"Recharger la version {n} ?\",\n \"Results\": \"Résultats\",\n \"Resume\": \"Reprendre\",\n \"Resume the draft?\": \"Reprendre le brouillon ?\",\n \"Retry\": \"Réessayer\",\n \"Right\": \"Droite\",\n \"Route not found\": \"Route introuvable\",\n \"Sample value\": \"Valeur d’exemple\",\n \"Save\": \"Enregistrer\",\n \"Save a copy\": \"Enregistrer une copie\",\n \"Save the template first\": \"Enregistrez d’abord le modèle\",\n \"Saved\": \"Enregistré\",\n \"Saved — version {n}.\": \"Enregistré — version {n}.\",\n \"Saved at {time}\": \"Enregistré à {time}\",\n \"Saved, with {count} MJML warning(s): {message}\": \"Enregistré, avec {count} avertissement(s) MJML : {message}\",\n \"Saving…\": \"Enregistrement…\",\n \"script not allowed\": \"script interdit\",\n \"Search variables…\": \"Rechercher une variable…\",\n \"Section\": \"Section\",\n \"Select a text or a button first.\": \"Sélectionnez d’abord un texte ou un bouton.\",\n \"Send\": \"Envoyer\",\n \"Send a test\": \"Envoyer un test\",\n \"Shown after the subject in the inbox\": \"Affiché après l’objet dans la boîte de réception\",\n \"Size\": \"Taille\",\n \"Solid\": \"Plein\",\n \"Spacer\": \"Espace\",\n \"Spacing\": \"Espacement\",\n \"Start over\": \"Partir de zéro\",\n \"style sheet not allowed\": \"feuille de style interdite\",\n \"Subject\": \"Objet\",\n \"SVG not allowed\": \"SVG interdit\",\n \"Tag\": \"Balise\",\n \"tag not allowed: <{tag}>\": \"balise non admise : <{tag}>\",\n \"Template\": \"Modèle\",\n \"Template changed in the meantime\": \"Modèle modifié entre-temps\",\n \"Template changed in the meantime — reload it before saving\": \"Modèle modifié entre-temps — rechargez-le avant d’enregistrer\",\n \"Template changed in the meantime by {user} — reload it before saving\": \"Modèle modifié entre-temps par {user} — rechargez-le avant d’enregistrer\",\n \"Template not found\": \"Modèle introuvable\",\n \"Template rejected: {issues}\": \"Modèle refusé : {issues}\",\n \"Template rejected: {issues} (+{more})\": \"Modèle refusé : {issues} (+{more})\",\n \"Template saved.\": \"Modèle enregistré.\",\n \"Templates that use it will show its “value if empty”.\": \"Les modèles qui l’utilisent afficheront sa « valeur si vide ».\",\n \"Test\": \"Tester\",\n \"Test sending is not configured\": \"L’envoi de test n’est pas configuré\",\n \"Text\": \"Texte\",\n \"The builder could not load.\": \"Le constructeur n’a pas pu se charger.\",\n \"The builder could not start.\": \"Le constructeur n’a pas pu démarrer.\",\n \"the document must start with <mjml>\": \"le document doit commencer par <mjml>\",\n \"The draft will be offered again next time.\": \"Le brouillon reste proposé à la prochaine ouverture.\",\n \"The email on screen, to reviewers’ addresses.\": \"Le courriel à l’écran, aux adresses de relecture.\",\n \"The template could not be loaded.\": \"Le modèle n’a pas pu être chargé.\",\n \"Thickness\": \"Épaisseur\",\n \"This key is already used\": \"Cette balise existe déjà\",\n \"To check\": \"À vérifier\",\n \"Too many test sends — try again later\": \"Trop d’envois de test — réessayez plus tard\",\n \"Too many uploads — try again later\": \"Trop de dépôts d’images — réessayez plus tard\",\n \"Top\": \"Haut\",\n \"Typography\": \"Typographie\",\n \"Undo\": \"Annuler\",\n \"Undo (Ctrl+Z)\": \"Annuler (Ctrl+Z)\",\n \"Unknown variables (rendered empty): {tags}\": \"Variables inconnues (affichées vides) : {tags}\",\n \"Unsaved changes\": \"Modifications non enregistrées\",\n \"Unsaved changes will be lost.\": \"Les modifications non enregistrées seront perdues.\",\n \"Unsaved changes will be replaced.\": \"Les modifications non enregistrées seront remplacées.\",\n \"Unsaved template from {when}.\": \"Modèle non enregistré du {when}.\",\n \"Unsubscribe\": \"Se désinscrire\",\n \"Unsubscribe link\": \"Lien de désinscription\",\n \"Upload\": \"Déposer\",\n \"Upload failed\": \"Dépôt impossible\",\n \"URL rejected ({name})\": \"adresse refusée ({name})\",\n \"Value if empty\": \"Valeur si vide\",\n \"Variable “{name}” created and inserted.\": \"Variable « {name} » créée et insérée.\",\n \"Variable “{name}” created.\": \"Variable « {name} » créée.\",\n \"Variable “{name}” saved.\": \"Variable « {name} » enregistrée.\",\n \"Variable deleted.\": \"Variable supprimée.\",\n \"Variable not found\": \"Variable introuvable\",\n \"Variables\": \"Variables\",\n \"Version {n}\": \"Version {n}\",\n \"Version {n} restored — save to keep it.\": \"Version {n} rechargée — enregistrez pour la garder.\",\n \"Version not found\": \"Version introuvable\",\n \"Versions\": \"Versions\",\n \"Vertical alignment\": \"Alignement vertical\",\n \"Vertical space\": \"Vide vertical\",\n \"Weight\": \"Graisse\",\n \"When a recipient has none\": \"Quand un destinataire n’en a pas\",\n \"Width\": \"Largeur\",\n \"Your company\": \"Votre société\",\n \"Your headline\": \"Titre du message\",\n \"Your text.\": \"Votre texte.\",\n \"“{name}” template applied.\": \"Modèle « {name} » appliqué.\",\n \"€49 instead of €70\": \"49 € au lieu de 70 €\",\n};\n","// @ts-check\n/**\n * Translations.\n *\n * Source strings are written in English directly in the code and double as\n * translation keys (`t('Save')`). A locale is a plain object mapping those\n * English strings to translated ones; a missing entry falls back to English.\n * Placeholders use `{name}`: `t('Saved — version {n}.', { n: 3 })`.\n *\n * The same translator serves the browser (builder interface) and the server\n * (validation and API error messages).\n */\n\nimport { fr } from './fr.js';\n\n/** @typedef {Record<string, string>} Messages */\n/** @typedef {(text: string, params?: Record<string, string|number|null|undefined>) => string} Translate */\n\n/** Built-in locales. English is the source language: an empty dictionary. */\nexport const LOCALES = /** @type {Record<string, Messages>} */ ({ en: {}, fr });\n\n/**\n * @param {Messages} [messages]\n * @returns {Translate}\n */\nexport function createTranslator(messages = {}) {\n return (text, params) => {\n const template = Object.prototype.hasOwnProperty.call(messages, text) ? messages[text] : text;\n if (!params) return template;\n return template.replace(/\\{(\\w+)\\}/g, (match, key) =>\n params[key] === undefined || params[key] === null ? match : String(params[key])\n );\n };\n}\n\n/**\n * Translator for a locale code (`'fr'`, `'fr-FR'`, `'en'`…), optionally\n * extended with application-specific strings.\n *\n * @param {string} [locale]\n * @param {Messages} [overrides]\n * @returns {Translate}\n */\nexport function translatorFor(locale = 'en', overrides = {}) {\n const base = LOCALES[String(locale).toLowerCase()] || LOCALES[String(locale).slice(0, 2).toLowerCase()] || {};\n return createTranslator({ ...base, ...overrides });\n}\n\n/** English, untouched: the default everywhere a translator is optional. */\nexport const identity = createTranslator();\n","// @ts-check\n/**\n * Variables catalog — the merge tags a template can contain.\n *\n * The editor only inserts TAGS (`{{contact.firstname}}`); it never knows a\n * value. Rendering (server side) replaces them:\n *\n * preview, test → sample values (`sample`), the real address for a test;\n * sending → each recipient's values (`applyVariables`), or the merge\n * tags of an email provider (`mapVariables`).\n *\n * A missing value falls back to the tag's inline default\n * (`{{contact.firstname|there}}`), then to the variable's `fallback`, then to\n * an empty string — never to the raw tag.\n *\n * Two kinds of variables:\n * - SYSTEM variables, given by the application (`variables` prop / option),\n * read-only in the interface — this list is the default;\n * - CUSTOM variables, created by users in the builder and kept by the\n * storage (`listVariables` / `saveVariable` / `deleteVariable`), shared by\n * every template.\n *\n * The unsubscribe link is `{{ unsubscribe }}`: the sending pipeline replaces it\n * with the right link (see `applyServiceLinks`).\n *\n * Dependency-free: shared by the browser and the server.\n */\n\nimport { identity } from '../../i18n/index.js';\n\n/**\n * @typedef {{\n * key: string,\n * label: string,\n * group: string,\n * sample: string,\n * fallback?: string,\n * description?: string,\n * token?: string,\n * kind?: 'text'|'link',\n * custom?: boolean,\n * }} EmailVariable\n */\n\n/** @type {EmailVariable[]} */\nexport const DEFAULT_VARIABLES = [\n { key: 'contact.firstname', label: 'First name', group: 'Recipient', sample: 'Jane' },\n { key: 'contact.lastname', label: 'Last name', group: 'Recipient', sample: 'Doe' },\n { key: 'contact.email', label: 'Email address', group: 'Recipient', sample: 'jane.doe@example.com' },\n { key: 'company.name', label: 'Company', group: 'Recipient', sample: 'Example Inc.' },\n { key: 'campaign.name', label: 'Campaign name', group: 'Campaign', sample: 'Newsletter' },\n {\n key: 'unsubscribe',\n label: 'Unsubscribe link',\n group: 'Links',\n sample: '',\n token: '{{ unsubscribe }}',\n kind: 'link',\n },\n];\n\n/** Group of custom variables created without a group. */\nexport const CUSTOM_GROUP = 'Custom';\n\n/** Keys reserved by the sending pipeline. */\nconst RESERVED_KEYS = new Set(['unsubscribe', 'mirror']);\n\nexport const VARIABLE_LIMITS = { key: 60, label: 80, group: 40, sample: 200, fallback: 200, description: 200 };\n\nconst KEY_SHAPE = /^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$/;\n\n/** Tag inserted by the editor. @param {EmailVariable} variable */\nexport function tokenOf(variable) {\n return variable.token || `{{${variable.key}}}`;\n}\n\n/**\n * @param {string} key\n * @param {EmailVariable[]} [variables]\n */\nexport function findVariable(key, variables = DEFAULT_VARIABLES) {\n return variables.find((v) => v.key === key) || null;\n}\n\n/**\n * A key proposed from a label: \"Order number\" → `order_number`,\n * \"Numéro de commande\" → `numero_de_commande`.\n *\n * @param {string} label\n */\nexport function keyFromLabel(label) {\n const key = String(label || '')\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .replace(/^[^a-z]+/, '')\n .slice(0, VARIABLE_LIMITS.key)\n .replace(/_+$/, '');\n return key || 'variable';\n}\n\n/**\n * Checks an application-provided catalog (keys must be unique and safe to\n * embed in a tag).\n *\n * @param {EmailVariable[]} variables\n * @returns {EmailVariable[]}\n */\nexport function defineVariables(variables) {\n const seen = new Set();\n for (const variable of variables) {\n if (!variable || !KEY_SHAPE.test(variable.key || '')) {\n throw new Error(`variables: invalid key \"${variable?.key}\"`);\n }\n if (seen.has(variable.key)) throw new Error(`variables: duplicate key \"${variable.key}\"`);\n seen.add(variable.key);\n }\n return variables;\n}\n\nconst clean = (value, max) => String(value ?? '').replace(/\\s+/g, ' ').trim().slice(0, max);\n\n/**\n * Validates and normalizes a CUSTOM variable typed by a user.\n * Returns `{ variable }` or `{ errors: { field: message } }`.\n *\n * @param {Partial<EmailVariable>} input\n * @param {{\n * existing?: EmailVariable[],\n * editing?: string|null,\n * t?: import('../../i18n/index.js').Translate,\n * }} [options] `editing`: key of the variable being edited (its key cannot change)\n */\nexport function validateCustomVariable(input, { existing = [], editing = null, t = identity } = {}) {\n /** @type {Record<string, string>} */\n const errors = {};\n const label = clean(input?.label, VARIABLE_LIMITS.label);\n const key = editing || String(input?.key || '').trim();\n if (!label) errors.label = t('Name is required');\n if (!editing) {\n if (!key) errors.key = t('Key is required');\n else if (key.length > VARIABLE_LIMITS.key || !KEY_SHAPE.test(key)) {\n errors.key = t('Lowercase letters, digits, \"_\" and \".\" only, starting with a letter');\n } else if (RESERVED_KEYS.has(key) || existing.some((v) => v.key === key)) {\n errors.key = t('This key is already used');\n }\n }\n if (Object.keys(errors).length) return { errors };\n return {\n variable: {\n key,\n label,\n group: clean(input?.group, VARIABLE_LIMITS.group) || CUSTOM_GROUP,\n sample: clean(input?.sample, VARIABLE_LIMITS.sample),\n fallback: clean(input?.fallback, VARIABLE_LIMITS.fallback),\n description: clean(input?.description, VARIABLE_LIMITS.description),\n custom: true,\n },\n };\n}\n\n/**\n * System variables first, then custom ones (a custom variable never replaces\n * a system one).\n *\n * @param {EmailVariable[]} system\n * @param {EmailVariable[]} custom\n */\nexport function mergeVariables(system, custom = []) {\n const keys = new Set(system.map((v) => v.key));\n return [...system, ...custom.filter((v) => !keys.has(v.key)).map((v) => ({ ...v, custom: true }))];\n}\n","// @ts-check\n/**\n * Service links and uploaded images in a rendered email.\n *\n * `{{ unsubscribe }}` → the unsubscribe link. For a preview or a test it is\n * an INERT link: it can be displayed and clicked but\n * unsubscribes nobody — least of all the person\n * proofreading their test;\n * `{{ mirror }}` → \"view online\" link of some providers, `#` here.\n *\n * UPLOADED IMAGES: a template keeps a RELATIVE path (`/email-assets/<key>`),\n * made absolute at render time with the public URL of the environment that\n * sends. An absolute URL frozen at save time would point to whichever\n * environment saved the template.\n *\n * An uploaded image path is any root-relative path ending with an asset key:\n * 32 hex characters of the content hash and a known extension. The prefix is\n * the application's choice (`assetsPath` option of the server handler).\n */\n\nconst UNSUBSCRIBE = /\\{\\{\\s*unsubscribe\\s*\\}\\}|\\[UNSUBSCRIBE\\]/gi;\nconst MIRROR = /\\{\\{\\s*mirror\\s*\\}\\}/gi;\n\n/** Default public path of uploaded images. */\nexport const DEFAULT_ASSETS_PATH = '/email-assets/';\n\nconst ASSET_PATH_PATTERN = /^\\/(?:[A-Za-z0-9._~-]+\\/)*[a-f0-9]{32}\\.(png|jpg|gif)$/;\nconst RELATIVE_ASSET = /(^|[\"'(=\\s])(\\/(?:[A-Za-z0-9._~-]+\\/)*[a-f0-9]{32}\\.(?:png|jpg|gif))(?=[\"')\\s>]|$)/g;\n\n/** Is this the path of an uploaded image? @param {string} value */\nexport function isAssetPath(value) {\n const path = String(value || '').trim();\n return ASSET_PATH_PATTERN.test(path) && !path.split('/').some((part) => part === '..' || part === '.');\n}\n\n/**\n * Makes uploaded image paths absolute. Without a base URL the HTML is\n * returned as is (preview inside the application, same origin).\n *\n * @param {string} html\n * @param {string|null|undefined} baseUrl\n */\nexport function absoluteAssetUrls(html, baseUrl) {\n const root = String(baseUrl || '').replace(/\\/+$/, '');\n if (!root) return String(html || '');\n return String(html || '').replace(RELATIVE_ASSET, (match, before, path) =>\n isAssetPath(path) ? `${before}${root}${path}` : match\n );\n}\n\nfunction escapeAttribute(text) {\n return String(text ?? '')\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * @param {string} html\n * @param {{unsubscribeUrl: string}} links\n */\nexport function applyServiceLinks(html, { unsubscribeUrl }) {\n return String(html || '')\n .replace(UNSUBSCRIBE, () => escapeAttribute(unsubscribeUrl))\n .replace(MIRROR, '#');\n}\n","// @ts-check\n/**\n * Variables service — replaces merge tags in compiled HTML (or in a subject).\n *\n * Only the tags of the catalog are touched, in their `{{key}}` or\n * `{{key|fallback}}` form (spaces allowed): other tags (an email provider's own\n * merge tags, `{{ mirror }}`) are left intact.\n *\n * Values are escaped in HTML: a first name \"<b>Jo</b>\" is displayed as typed,\n * it makes nothing bold — and cannot inject anything.\n *\n * Missing value → the tag's inline fallback → the variable's `fallback` → ''.\n * A raw tag never reaches a recipient.\n *\n * The unsubscribe link is NOT replaced here: only the sending pipeline knows\n * the right link (see `applyServiceLinks`).\n */\n\nimport { DEFAULT_VARIABLES } from './catalog.js';\n\n/** @typedef {import('./catalog.js').EmailVariable} EmailVariable */\n/** @typedef {Record<string, string|number|null|undefined>} VariableValues */\n\nfunction escapeHtml(text) {\n return String(text ?? '')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\n/** Undoes the escaping the editor applied to an inline fallback. */\nfunction decodeHtml(text) {\n return String(text ?? '')\n .replace(/&nbsp;/g, ' ')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&amp;/g, '&');\n}\n\nconst escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** `{{ key }}` or `{{ key | fallback }}`. @param {string} key */\nfunction patternOf(key) {\n return new RegExp(`\\\\{\\\\{\\\\s*${escapeRegExp(key)}\\\\s*(?:\\\\|([^{}]*))?\\\\}\\\\}`, 'g');\n}\n\n/** Any `{{ … }}` tag that looks like a variable. */\nconst ANY_TAG = /\\{\\{\\s*([A-Za-z][\\w.]*)\\s*(?:\\|[^{}]*)?\\}\\}/g;\n\n/** @param {EmailVariable[]} variables */\nconst textVariables = (variables) => variables.filter((v) => v.kind !== 'link');\n\nconst present = (value) => value !== null && value !== undefined && String(value) !== '';\n\n/**\n * @param {string} text\n * @param {VariableValues} values\n * @param {EmailVariable[]} variables\n * @param {boolean} html\n */\nfunction replace(text, values, variables, html) {\n let out = String(text || '');\n for (const variable of textVariables(variables)) {\n out = out.replace(patternOf(variable.key), (_, inline) => {\n const value = values?.[variable.key];\n if (present(value)) return html ? escapeHtml(value) : String(value);\n const fallback = inline !== undefined ? (html ? decodeHtml(inline) : inline).trim() : variable.fallback || '';\n return html ? escapeHtml(fallback) : fallback;\n });\n }\n return out;\n}\n\n/**\n * Replaces the known tags with values (escaped) in HTML.\n *\n * @param {string} html\n * @param {VariableValues} values\n * @param {EmailVariable[]} [variables]\n */\nexport function applyVariables(html, values, variables = DEFAULT_VARIABLES) {\n return replace(html, values, variables, true);\n}\n\n/**\n * Same as `applyVariables` for PLAIN TEXT (subject line, preview text): no\n * HTML escaping.\n *\n * @param {string} text\n * @param {VariableValues} values\n * @param {EmailVariable[]} [variables]\n */\nexport function applyVariablesToText(text, values, variables = DEFAULT_VARIABLES) {\n return replace(text, values, variables, false);\n}\n\n/**\n * Replaces the known tags with RAW replacements — typically the merge tags of\n * an email provider (`{ 'contact.firstname': '{{ contact.FIRSTNAME }}' }`).\n * A tag without replacement is left untouched.\n *\n * @param {string} html\n * @param {Record<string, string>} mapping\n * @param {EmailVariable[]} [variables]\n */\nexport function mapVariables(html, mapping, variables = DEFAULT_VARIABLES) {\n let out = String(html || '');\n for (const variable of textVariables(variables)) {\n if (!Object.prototype.hasOwnProperty.call(mapping, variable.key)) continue;\n out = out.replace(patternOf(variable.key), () => String(mapping[variable.key]));\n }\n return out;\n}\n\n/**\n * Catalog keys present in a text.\n * @param {string} text\n * @param {EmailVariable[]} [variables]\n */\nexport function usedVariables(text, variables = DEFAULT_VARIABLES) {\n const source = String(text || '');\n return variables.filter((v) => patternOf(v.key).test(source)).map((v) => v.key);\n}\n\n/**\n * Tags that look like variables but are not in the catalog (typos such as\n * `{{contact.fistname}}`). `ignore` lists tags handled elsewhere.\n *\n * @param {string} text\n * @param {EmailVariable[]} [variables]\n * @param {string[]} [ignore]\n */\nexport function unknownVariables(text, variables = DEFAULT_VARIABLES, ignore = ['unsubscribe', 'mirror']) {\n const known = new Set([...variables.map((v) => v.key), ...ignore]);\n const found = new Set();\n for (const match of String(text || '').matchAll(ANY_TAG)) {\n if (!known.has(match[1])) found.add(match[1]);\n }\n return [...found];\n}\n\n/**\n * Sample values (preview, test), with optional overrides\n * (e.g. `{ 'contact.email': 'reviewer@example.com' }`).\n *\n * @param {EmailVariable[]} [variables]\n * @param {VariableValues} [overrides]\n */\nexport function sampleValues(variables = DEFAULT_VARIABLES, overrides = {}) {\n /** @type {Record<string, string>} */\n const values = Object.fromEntries(textVariables(variables).map((v) => [v.key, v.sample]));\n for (const [key, value] of Object.entries(overrides)) {\n if (present(value)) values[key] = String(value);\n }\n return values;\n}\n","// @ts-check\n/**\n * Images UPLOADED in the builder — one rule shared by the browser (immediate\n * rejection, storage/client.js) and the server (storage/server/assetFile.js).\n *\n * PNG, JPEG, GIF: the three formats every email client displays. WebP and SVG\n * do not display in Outlook, and an SVG can carry script.\n * The server reads the type from the file BYTES; the browser, from `file.type`.\n */\n\nimport { identity } from '../i18n/index.js';\n\n/** Maximum image size: beyond it, the email becomes slow to open. */\nexport const MAX_IMAGE_BYTES = 2 * 1024 * 1024;\n\n/** Accepted types → extension of the public key. */\nexport const IMAGE_TYPES = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif' };\n\n/** Public key: 32 hex characters of the hash + extension. */\nexport const ASSET_KEY_PATTERN = /^[a-f0-9]{32}\\.(png|jpg|gif)$/;\n\nconst SIGNATURES = [\n { type: 'image/png', bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] },\n { type: 'image/jpeg', bytes: [0xff, 0xd8, 0xff] },\n { type: 'image/gif', bytes: [0x47, 0x49, 0x46, 0x38] },\n];\n\n/**\n * Image type detected from the file signature, or `null`.\n * @param {Uint8Array} bytes\n * @returns {{type: string, ext: string}|null}\n */\nexport function detectImage(bytes) {\n if (!bytes || bytes.length < 8) return null;\n const found = SIGNATURES.find((s) => s.bytes.every((b, i) => bytes[i] === b));\n return found ? { type: found.type, ext: IMAGE_TYPES[found.type] } : null;\n}\n\n/**\n * Immediate rejection of a file the server would refuse, or `null`.\n * @param {{type?: string, size?: number}} file\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function imageUploadError(file, { t = identity } = {}) {\n if (!(String(file?.type || '') in IMAGE_TYPES)) return t('Format rejected: PNG, JPEG or GIF expected');\n if (Number(file?.size || 0) > MAX_IMAGE_BYTES) {\n return t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) });\n }\n return null;\n}\n","// @ts-check\n/**\n * URLs accepted in an email template.\n *\n * link (`href`) https, http, mailto:, tel:, `#…` anchor,\n * or a `{{ … }}` merge tag (unsubscribe,\n * variable) replaced at render time;\n * image (`src`, `background-url`…) https only: an http image is blocked or\n * flagged by most email clients, and\n * `data:` bloats the message until Gmail\n * clips it. Or an UPLOADED image (asset\n * path, see `isAssetPath`), made absolute\n * at render time (renderer/links.js).\n *\n * Everything else (`javascript:`, `data:`, `vbscript:`, `file:`…) is rejected.\n * The scheme is read AFTER decoding entities and stripping whitespace and\n * control characters: `java&#x09;script:` and `&#106;avascript:` are classics.\n */\nimport { isAssetPath } from '../renderer/links.js';\n\n/** Attributes carrying a LINK URL. */\nexport const LINK_ATTRIBUTES = new Set(['href', 'action', 'formaction', 'xlink:href']);\n\n/** Attributes carrying an IMAGE URL (MJML and HTML). */\nexport const IMAGE_ATTRIBUTES = new Set([\n 'src',\n 'srcset',\n 'background-url',\n 'background',\n 'poster',\n 'icon-unwrapped-url',\n 'icon-wrapped-url',\n]);\n\nconst NAMED_ENTITIES = {\n colon: ':',\n tab: '\\t',\n newline: '\\n',\n lpar: '(',\n rpar: ')',\n amp: '&',\n quot: '\"',\n apos: \"'\",\n lt: '<',\n gt: '>',\n nbsp: ' ',\n};\n\n/** Decodes the HTML entities of an attribute value. @param {string} value */\nexport function decodeEntities(value) {\n return String(value ?? '').replace(/&(#x[0-9a-f]+|#\\d+|[a-z]+);?/gi, (whole, body) => {\n const key = String(body).toLowerCase();\n if (key.startsWith('#x')) return String.fromCodePoint(Number.parseInt(key.slice(2), 16) || 0);\n if (key.startsWith('#')) return String.fromCodePoint(Number.parseInt(key.slice(1), 10) || 0);\n return NAMED_ENTITIES[key] ?? whole;\n });\n}\n\n/** A `{{ … }}` merge tag (variable, unsubscribe, web version). @param {string} value */\nexport function isMergeTag(value) {\n return /^\\{\\{\\s*[a-zA-Z][\\w.]*\\s*\\}\\}$/.test(String(value || '').trim());\n}\n\n/**\n * @param {string} raw\n * @param {'link'|'image'} kind\n * @returns {boolean}\n */\nexport function isSafeUrl(raw, kind) {\n const value = decodeEntities(raw).trim();\n if (!value) return true;\n if (isMergeTag(value)) return true;\n // Scheme read without whitespace or control characters (\"java\\tscript:\").\n // eslint-disable-next-line no-control-regex -- control characters are exactly what is stripped\n const compact = value.replace(/[\\s\\u0000-\\u001f\\u007f]+/g, '').toLowerCase();\n if (kind === 'image') return compact.startsWith('https://') || isAssetPath(value);\n if (compact.startsWith('#')) return true;\n return /^(https?:\\/\\/|mailto:|tel:)/.test(compact);\n}\n"],"mappings":"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","names":[]}
@@ -1,33 +0,0 @@
1
- /**
2
- * @param {{
3
- * locale?: string,
4
- * loadEngine?: (locale: string) => Promise<{grapesjs: any, mjmlPlugin: any, locales?: {engine?: any, mjml?: any}}>,
5
- * }} [options]
6
- * @returns {EmailEditorAdapter & {readonly engine: any}}
7
- */
8
- export function createGrapesJsAdapter({ locale, loadEngine }?: {
9
- locale?: string;
10
- loadEngine?: (locale: string) => Promise<{
11
- grapesjs: any;
12
- mjmlPlugin: any;
13
- locales?: {
14
- engine?: any;
15
- mjml?: any;
16
- };
17
- }>;
18
- }): EmailEditorAdapter & {
19
- readonly engine: any;
20
- };
21
- /**
22
- * @typedef {import('../../core/EmailEditor.js').EmailEditorAdapter} EmailEditorAdapter
23
- * @typedef {import('../../core/EmailEditor.js').EmailBlock} EmailBlock
24
- * @typedef {import('../../core/EmailEditor.js').EngineSelection} EngineSelection
25
- * @typedef {import('../../core/constants.js').EmailDevice} EmailDevice
26
- */
27
- export const ENGINE_NAME: "grapesjs-mjml";
28
- /** Engine version, copied into every document (exact package.json version). */
29
- export const ENGINE_VERSION: "0.23.6";
30
- export type EmailEditorAdapter = import("../../core/EmailEditor.js").EmailEditorAdapter;
31
- export type EmailBlock = import("../../core/EmailEditor.js").EmailBlock;
32
- export type EngineSelection = import("../../core/EmailEditor.js").EngineSelection;
33
- export type EmailDevice = import("../../core/constants.js").EmailDevice;
@@ -1,162 +0,0 @@
1
- /**
2
- * @param {{
3
- * container: HTMLElement,
4
- * mjmlPlugin: any,
5
- * locales?: {engine?: any, mjml?: any},
6
- * locale?: string,
7
- * }} options
8
- */
9
- export function buildEngineConfig({ container, mjmlPlugin, locales, locale }: {
10
- container: HTMLElement;
11
- mjmlPlugin: any;
12
- locales?: {
13
- engine?: any;
14
- mjml?: any;
15
- };
16
- locale?: string;
17
- }): {
18
- container: HTMLElement;
19
- height: string;
20
- width: string;
21
- telemetry: boolean;
22
- customUI: boolean;
23
- cssIcons: string;
24
- noticeOnUnload: boolean;
25
- storageManager: false;
26
- fromElement: boolean;
27
- showDevices: boolean;
28
- showOffsets: boolean;
29
- multipleSelection: boolean;
30
- avoidInlineStyle: boolean;
31
- log: ("warning" | "error")[];
32
- canvasCss: string;
33
- i18n: {
34
- locale: string;
35
- localeFallback: string;
36
- detectLocale: boolean;
37
- messages: {
38
- [locale]: any;
39
- };
40
- };
41
- panels: {
42
- defaults: never[];
43
- };
44
- blockManager: {
45
- custom: boolean;
46
- };
47
- layerManager: {
48
- custom: boolean;
49
- };
50
- traitManager: {
51
- custom: boolean;
52
- };
53
- selectorManager: {
54
- custom: boolean;
55
- componentFirst: boolean;
56
- };
57
- styleManager: {
58
- custom: boolean;
59
- };
60
- assetManager: {
61
- custom: boolean;
62
- upload: boolean;
63
- };
64
- deviceManager: {
65
- devices: ({
66
- id: string;
67
- name: string;
68
- width: string;
69
- widthMedia?: undefined;
70
- } | {
71
- id: string;
72
- name: string;
73
- width: string;
74
- widthMedia: string;
75
- })[];
76
- default: string;
77
- };
78
- undoManager: {
79
- trackSelection: boolean;
80
- maximumStackLength: number;
81
- };
82
- keymaps: {
83
- defaults: {
84
- 'core:undo': {
85
- keys: string;
86
- handler: string;
87
- opts: {
88
- prevent: boolean;
89
- };
90
- };
91
- 'core:redo': {
92
- keys: string;
93
- handler: string;
94
- opts: {
95
- prevent: boolean;
96
- };
97
- };
98
- 'core:copy': {
99
- keys: string;
100
- handler: string;
101
- };
102
- 'core:paste': {
103
- keys: string;
104
- handler: string;
105
- };
106
- 'core:component-delete': {
107
- keys: string;
108
- handler: string;
109
- opts: {
110
- prevent: boolean;
111
- };
112
- };
113
- };
114
- };
115
- parser: {
116
- optionsHtml: {
117
- allowScripts: boolean;
118
- allowUnsafeAttr: boolean;
119
- allowUnsafeAttrValue: boolean;
120
- };
121
- };
122
- plugins: ((editor: any) => any)[];
123
- };
124
- /**
125
- * GrapesJS engine configuration — without any of its UI.
126
- *
127
- * The builder has ITS OWN React UI: GrapesJS only provides the canvas (the
128
- * iframe where you click and drag) and its data model. Hence:
129
- * - `customUI`, `panels.defaults: []` and `custom` managers: no panel, no
130
- * toolbar, no style or layer manager;
131
- * - `telemetry: false` and `cssIcons: ''`: no request to a third-party
132
- * service (GrapesJS stats, Font Awesome from a CDN);
133
- * - `storageManager: false`: saving is ours (`storage/`), never the
134
- * engine's `localStorage`;
135
- * - an HTML parser that rejects scripts and `on*` attributes: a reloaded
136
- * template must not be able to execute anything in the host page.
137
- */
138
- /** Canvas devices: ids = `EmailDevice`. */
139
- export const DEVICES: ({
140
- id: string;
141
- name: string;
142
- width: string;
143
- widthMedia?: undefined;
144
- } | {
145
- id: string;
146
- name: string;
147
- width: string;
148
- widthMedia: string;
149
- })[];
150
- /** Empty email: just the body, at the standard width. */
151
- export const EMPTY_MJML: "<mjml><mj-body background-color=\"#eceff4\" width=\"600px\"></mj-body></mjml>";
152
- /**
153
- * Image WITHOUT A FILE, as shown in the canvas: a light grey 600 × 220 banner
154
- * with an icon. Replaces GrapesJS's default `src`, raw SVG markup whose quotes
155
- * and ";" broke the attribute and the style string the MJML plugin reads back
156
- * ("illegal transform attribute"). This one is a `data:` URL with NO ";" and
157
- * no double quote.
158
- *
159
- * It never leaves the adapter: `GrapesJsAdapter` turns it into "no image" in
160
- * the export, the project and the selection.
161
- */
162
- export const IMAGE_PLACEHOLDER: string;
@@ -1,18 +0,0 @@
1
- /**
2
- * A dynamically imported module → the value it exports by default.
3
- *
4
- * `grapesjs-mjml` and its locales are CommonJS/UMD modules flagged
5
- * `__esModule`. How `import()` hands them 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` returned an object instead of the plugin under
12
- * Next.js ("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;