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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/constants.js","../src/core/document.js","../src/i18n/fr.js","../src/i18n/index.js","../src/engine/model.js","../src/engine/mjml.js","../src/blocks/shared.js","../src/features/brand/brand.js","../src/features/variables/catalog.js","../src/features/preview/darkMode.js","../src/renderer/links.js","../src/validation/images.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.4.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 few words about it.\": \"Quelques mots pour le présenter.\",\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 \"Add to favorites\": \"Ajouter aux favoris\",\n \"All blocks\": \"Tous les blocs\",\n \"All capitals\": \"Tout en majuscules\",\n \"All templates\": \"Tous les modèles\",\n \"All your templates are in the favorites.\": \"Tous vos modèles sont dans les favoris.\",\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 \"Apply the link\": \"Appliquer le lien\",\n \"Article {n}\": \"Article {n}\",\n \"As typed\": \"Comme saisi\",\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 \"Background width\": \"Largeur du fond\",\n \"Base\": \"Base\",\n \"Base — not editable: start from it to create your template.\": \"Base non modifiable — partez-en pour créer votre modèle.\",\n \"Bases\": \"Bases\",\n \"Blank\": \"Vierge\",\n \"Blog\": \"Blog\",\n \"Bold (Ctrl+B)\": \"Gras (Ctrl+B)\",\n \"Book my seat\": \"Je réserve ma place\",\n \"Builder\": \"Constructeur\",\n \"Buy\": \"Acheter\",\n \"by {name}\": \"par {name}\",\n \"Capitals\": \"Majuscules\",\n \"Choose a template\": \"Choisissez un modèle\",\n \"Choose an image in the panel\": \"Choisissez une image dans le panneau\",\n \"Clear formatting\": \"Effacer la mise en forme\",\n \"Clear the search\": \"Effacer la recherche\",\n \"Contact\": \"Contact\",\n \"Copy HTML\": \"Copier le HTML\",\n \"Could not load the templates.\": \"Impossible de charger les modèles.\",\n \"Created\": \"Créé\",\n \"Date, place and a button to book a seat\": \"Date, lieu et un bouton pour réserver\",\n \"Default ({value})\": \"Par défaut ({value})\",\n \"Delete “{name}”?\": \"Supprimer « {name} » ?\",\n \"Direction\": \"Disposition\",\n \"Download\": \"Télécharger\",\n \"Drag to move\": \"Glisser pour déplacer\",\n \"Drop a block here\": \"Déposez un bloc ici\",\n \"Edit: {element}\": \"Modifier : {element}\",\n \"Email canvas\": \"Aperçu modifiable de l’e-mail\",\n \"Email settings\": \"Réglages de l'e-mail\",\n \"Email template library\": \"Bibliothèque de modèles e-mail\",\n \"Equal\": \"Égales\",\n \"Event invitation\": \"Invitation\",\n \"Facebook\": \"Facebook\",\n \"Favorites\": \"Favoris\",\n \"Fill the section\": \"Remplir la section\",\n \"First letters\": \"Premières lettres\",\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 \"HTML copied.\": \"HTML copié.\",\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 \"Italic (Ctrl+I)\": \"Italique (Ctrl+I)\",\n \"Keep its width\": \"Garder sa largeur\",\n \"Label\": \"Texte affiché\",\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 \"No preview: this storage cannot render emails.\": \"Pas d'aperçu : ce stockage ne sait pas compiler les e-mails.\",\n \"No template yet. Start from a base, or create a new one.\": \"Aucun modèle pour l’instant. Partez d’une base, ou créez-en un.\",\n \"Nothing matches “{query}”.\": \"Aucun résultat pour « {query} ».\",\n \"Offer valid until Sunday midnight, while stocks last.\": \"Offre valable jusqu'à dimanche minuit, dans la limite des stocks.\",\n \"On mobile\": \"Sur mobile\",\n \"One sentence about it.\": \"Une phrase pour en parler.\",\n \"One sentence that makes people want to read on.\": \"Une phrase qui donne envie de lire la suite.\",\n \"Origin\": \"Origine\",\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 \"Ready-made templates to copy into your campaigns.\": \"Modèles prêts à copier dans vos campagnes.\",\n \"Remove from favorites\": \"Retirer des favoris\",\n \"Remove the link\": \"Retirer le lien\",\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 \"Search templates\": \"Rechercher un modèle\",\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 \"Select a template on the left.\": \"Sélectionnez un modèle à gauche.\",\n \"Select the parent\": \"Sélectionner le bloc parent\",\n \"Shop\": \"Boutique\",\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 \"Start from this base\": \"Partir de cette base\",\n \"Strikethrough\": \"Barré\",\n \"Take a tour of what you can do.\": \"Faites le tour de ce que vous pouvez faire.\",\n \"Template deleted.\": \"Modèle supprimé.\",\n \"Template duplicated.\": \"Modèle dupliqué.\",\n \"Templates\": \"Modèles\",\n \"Text formatting\": \"Mise en forme du texte\",\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 browser refused to copy.\": \"Le navigateur a refusé la copie.\",\n \"The news of the month\": \"Les nouvelles du mois\",\n \"The template and its versions will be deleted.\": \"Le modèle et ses versions seront supprimés.\",\n \"The template preview appears here.\": \"L'aperçu du modèle s'affiche ici.\",\n \"This template was saved in a format this builder cannot open.\": \"Ce modèle a été enregistré dans un format que ce constructeur ne sait pas ouvrir.\",\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 \"Title\": \"Titre\",\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 \"Underline (Ctrl+U)\": \"Souligné (Ctrl+U)\",\n \"Updated\": \"Modifié\",\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 \"Whole screen\": \"Tout l’écran\",\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 \"Your big announcement\": \"Votre grande annonce\",\n \"YouTube\": \"YouTube\",\n \"{count} sections selected. Press Delete to remove them (Ctrl+Z brings them back).\": \"{count} sections sélectionnées. Appuyez sur Suppr pour les supprimer (Ctrl+Z les rétablit).\",\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 \"Generate\": \"Générer\",\n \"Generate an email\": \"Générer un e-mail\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"Choisissez à quoi il sert. L'IA écrit un e-mail complet qui s'ouvre ici, prêt à modifier.\",\n \"Search\": \"Rechercher\",\n \"Search: sale, password, birthday…\": \"Rechercher : soldes, mot de passe, anniversaire…\",\n \"What is the email for?\": \"À quoi sert l'e-mail ?\",\n \"Nothing matches. Describe your email in the details below.\": \"Aucun résultat. Décrivez votre e-mail dans les précisions ci-dessous.\",\n \"Details (optional)\": \"Précisions (facultatif)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"Précisions (facultatif) : produit, dates, offre, ton, langue…\",\n \"The email being edited will be replaced, unsaved changes included.\": \"L'e-mail en cours sera remplacé, modifications non enregistrées comprises.\",\n \"Writing your email… {seconds} s\": \"Rédaction de votre e-mail… {seconds} s\",\n \"Your email is ready. Edit anything, then save.\": \"Votre e-mail est prêt. Modifiez ce que vous voulez, puis enregistrez.\",\n \"Cancelled\": \"Annulé\",\n \"Email generation is not configured\": \"La génération d'e-mails n'est pas configurée\",\n \"Too many generated emails — try again later\": \"Trop d'e-mails générés — réessayez plus tard\",\n \"The AI service did not answer — try again in a moment.\": \"Le service d'IA n'a pas répondu — réessayez dans un instant.\",\n \"Pick what the email is for, or describe it.\": \"Choisissez à quoi sert l'e-mail, ou décrivez-le.\",\n \"The AI declined to write this email. Try other details.\": \"L'IA a refusé d'écrire cet e-mail. Essayez d'autres précisions.\",\n \"The email was too long to finish. Ask for a shorter one.\": \"L'e-mail était trop long pour être terminé. Demandez-en un plus court.\",\n \"The AI answer could not be read. Try again.\": \"La réponse de l'IA est illisible. Réessayez.\",\n \"The generated email could not be used. Try again.\": \"L'e-mail généré est inutilisable. Réessayez.\",\n \"Sales and promotions\": \"Ventes et promotions\",\n \"Holidays and occasions\": \"Fêtes et occasions\",\n \"Events and appointments\": \"Événements et rendez-vous\",\n \"News and content\": \"Actualités et contenus\",\n \"Account and security\": \"Compte et sécurité\",\n \"Orders and payments\": \"Commandes et paiements\",\n \"Customer relationship\": \"Relation client\",\n \"Business and organization\": \"Entreprise et organisation\",\n \"Store sale\": \"Soldes en boutique\",\n \"Flash sale\": \"Vente flash\",\n \"Discount code\": \"Code promo\",\n \"New product\": \"Nouveau produit\",\n \"Back in stock\": \"De retour en stock\",\n \"Price drop\": \"Baisse de prix\",\n \"Abandoned cart\": \"Panier abandonné\",\n \"Black Friday\": \"Black Friday\",\n \"Loyalty reward\": \"Récompense fidélité\",\n \"Refer a friend\": \"Parrainage\",\n \"Christmas\": \"Noël\",\n \"New Year\": \"Nouvel An\",\n \"Season's greetings\": \"Vœux de fin d'année\",\n \"Valentine's Day\": \"Saint-Valentin\",\n \"Easter\": \"Pâques\",\n \"Mother's Day\": \"Fête des mères\",\n \"Father's Day\": \"Fête des pères\",\n \"Halloween\": \"Halloween\",\n \"Back to school\": \"Rentrée\",\n \"Summer holidays\": \"Vacances d'été\",\n \"Birthday wishes\": \"Joyeux anniversaire\",\n \"Customer anniversary\": \"Anniversaire client\",\n \"Women's Day\": \"Journée des femmes\",\n \"Thanksgiving\": \"Thanksgiving\",\n \"Lunar New Year\": \"Nouvel An lunaire\",\n \"Ramadan and Eid\": \"Ramadan et Aïd\",\n \"Diwali\": \"Diwali\",\n \"Meeting reminder\": \"Rappel de réunion\",\n \"Appointment confirmation\": \"Confirmation de rendez-vous\",\n \"Booking confirmation\": \"Confirmation de réservation\",\n \"Webinar invitation\": \"Invitation à un webinaire\",\n \"Save the date\": \"Save the date\",\n \"Registration confirmed\": \"Inscription confirmée\",\n \"Cancellation or new date\": \"Annulation ou report\",\n \"Thanks for attending\": \"Merci de votre participation\",\n \"New article\": \"Nouvel article\",\n \"What's new\": \"Nouveautés\",\n \"Announcement\": \"Annonce\",\n \"Tips and how-to\": \"Conseils et tutoriels\",\n \"Email verification\": \"Vérification de l'e-mail\",\n \"Password reset\": \"Réinitialisation du mot de passe\",\n \"Login code\": \"Code de connexion\",\n \"Security alert\": \"Alerte de sécurité\",\n \"Getting started\": \"Premiers pas\",\n \"Trial ending\": \"Fin de l'essai\",\n \"Order confirmation\": \"Confirmation de commande\",\n \"Order shipped\": \"Commande expédiée\",\n \"Delivery delay\": \"Retard de livraison\",\n \"Receipt or invoice\": \"Reçu ou facture\",\n \"Payment failed\": \"Échec du paiement\",\n \"Subscription renewal\": \"Renouvellement d'abonnement\",\n \"Refund issued\": \"Remboursement effectué\",\n \"Thank you\": \"Remerciements\",\n \"Review request\": \"Demande d'avis\",\n \"Survey\": \"Enquête de satisfaction\",\n \"We miss you\": \"Vous nous manquez\",\n \"Follow-up\": \"Relance\",\n \"Apology\": \"Excuses\",\n \"We're hiring\": \"On recrute\",\n \"Policy update\": \"Mise à jour des conditions\",\n \"Price change\": \"Changement de prix\",\n \"Scheduled maintenance\": \"Maintenance planifiée\",\n \"Closure or opening hours\": \"Fermeture ou horaires\",\n \"Donation appeal\": \"Appel aux dons\",\n \"Internal update\": \"Communication interne\",\n \"Rewrite with AI\": \"Réécrire avec l'IA\",\n \"Shorter\": \"Plus court\",\n \"Longer\": \"Plus long\",\n \"Fix mistakes\": \"Corriger les fautes\",\n \"Friendlier\": \"Plus chaleureux\",\n \"More formal\": \"Plus formel\",\n \"Translate\": \"Traduire\",\n \"Translate into\": \"Traduire en\",\n \"Or say what to change…\": \"Ou dites ce qu'il faut changer…\",\n \"What to change\": \"Ce qu'il faut changer\",\n \"Rewrite\": \"Réécrire\",\n \"Rewriting…\": \"Réécriture…\",\n \"Color scheme\": \"Thème de couleurs\",\n \"Light mode\": \"Mode clair\",\n \"Dark mode\": \"Mode sombre\",\n \"Dark mode as Gmail and Outlook apps show it: light backgrounds turn dark, dark text turns light, images stay as they are.\": \"Mode sombre tel que l'affichent les applis Gmail et Outlook : les fonds clairs deviennent sombres, le texte foncé devient clair, les images restent telles quelles.\",\n \"Suggest subjects\": \"Suggérer des objets\",\n \"Subject ideas\": \"Idées d'objet\",\n \"More ideas\": \"Autres idées\",\n \"Done\": \"Terminé\",\n \"yours\": \"le vôtre\",\n \"Reading the email…\": \"Lecture de l'e-mail…\",\n \"No subject could be suggested.\": \"Aucun objet n'a pu être suggéré.\",\n \"Subject ideas are not configured\": \"Les idées d'objet ne sont pas configurées\",\n \"Too many subject ideas — try again later\": \"Trop de demandes d'idées d'objet — réessayez plus tard\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"Rédigez d'abord l'e-mail ou un objet : les idées en sont tirées.\",\n \"The AI declined to suggest subjects for this email.\": \"L'IA a refusé de suggérer des objets pour cet e-mail.\",\n \"No usable subject came back. Try again.\": \"Aucun objet utilisable n'a été proposé. Réessayez.\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"Texte réécrit. Ctrl+Z rétablit le précédent.\",\n \"The text changed in the meantime, so it was left as it is.\": \"Le texte a changé entre-temps : il a été laissé tel quel.\",\n \"The text could not be rewritten.\": \"Le texte n'a pas pu être réécrit.\",\n \"There is no text to rewrite.\": \"Il n'y a pas de texte à réécrire.\",\n \"The AI declined to rewrite this text.\": \"L'IA a refusé de réécrire ce texte.\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"Le texte réécrit avait perdu un lien ou une variable : rien n'a été modifié. Réessayez.\",\n \"Text rewriting is not configured\": \"La réécriture de texte n'est pas configurée\",\n \"Too many rewrites — try again later\": \"Trop de réécritures — réessayez plus tard\",\n \"Brand\": \"Marque\",\n \"Your brand\": \"Votre marque\",\n \"Set it once: new emails, templates and blocks take your logo, colors and font.\": \"À définir une fois : les nouveaux e-mails, modèles et blocs prennent votre logo, vos couleurs et votre police.\",\n \"Your website\": \"Votre site web\",\n \"Get my brand\": \"Récupérer ma marque\",\n \"Reading the website…\": \"Lecture du site…\",\n \"The logo, colors and font are read from it. You can change them before saving.\": \"Le logo, les couleurs et la police y sont lus. Vous pourrez les modifier avant d'enregistrer.\",\n \"The website could not be read.\": \"Le site n'a pas pu être lu.\",\n \"Logo\": \"Logo\",\n \"Remove\": \"Retirer\",\n \"or an image address: https://…\": \"ou l'adresse d'une image : https://…\",\n \"Company name\": \"Nom de l'entreprise\",\n \"Main color\": \"Couleur principale\",\n \"Buttons, banners, links\": \"Boutons, bandeaux, liens\",\n \"Headings color\": \"Couleur des titres\",\n \"Also apply to this email\": \"Appliquer aussi à cet e-mail\",\n \"Brand applied. Ctrl+Z brings back the previous look.\": \"Marque appliquée. Ctrl+Z rétablit l'apparence précédente.\",\n \"Brand saved. New emails and blocks use it.\": \"Marque enregistrée. Les nouveaux e-mails et blocs l'utilisent.\",\n \"The brand could not be saved.\": \"La marque n'a pas pu être enregistrée.\",\n \"Big news\": \"Grande nouvelle\",\n \"A short line under the title.\": \"Une courte ligne sous le titre.\",\n \"A headline\": \"Un titre\",\n \"Some text, with\": \"Un peu de texte, avec\",\n \"a link\": \"un lien\",\n \"The brand is not configured\": \"La marque n'est pas configurée\",\n \"Reading a brand from a website is not configured\": \"La lecture d'une marque depuis un site n'est pas configurée\",\n \"Too many websites read — try again later\": \"Trop de sites lus — réessayez plus tard\",\n \"Enter the address of a website, like acme.com\": \"Saisissez l'adresse d'un site, par exemple acme.com\",\n \"This address is not a public website\": \"Cette adresse n'est pas un site public\",\n \"The website could not be reached. Check the address.\": \"Le site est injoignable. Vérifiez l'adresse.\",\n \"The website took too long to answer\": \"Le site a mis trop de temps à répondre\",\n \"The website does not let the builder read it. Fill in your brand below.\": \"Le site ne se laisse pas lire par l'éditeur. Renseignez votre marque ci-dessous.\",\n \"This address is not a web page\": \"Cette adresse n'est pas une page web\",\n \"The website redirects too many times\": \"Le site redirige trop de fois\",\n \"The website sent too much data\": \"Le site a envoyé trop de données\",\n \"No logo was found on the website. Upload it below.\": \"Aucun logo trouvé sur le site. Importez-le ci-dessous.\",\n \"The logo on the website is an SVG or WebP image, which most email apps do not show. Upload a PNG or JPEG version.\": \"Le logo du site est une image SVG ou WebP, que la plupart des messageries n'affichent pas. Importez une version PNG ou JPEG.\",\n \"The website icon is used as the logo. Upload your full logo if you have one.\": \"L'icône du site sert de logo. Importez votre logo complet si vous en avez un.\",\n \"No main color was found on the website. Pick one below.\": \"Aucune couleur principale trouvée sur le site. Choisissez-en une ci-dessous.\",\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 * THE EMAIL MODEL — the builder's own document, an MJML tree with ids.\n *\n * { id, tag: 'mj-text', attributes: { color: '#2b3038' }, children: [], content: 'Hello' }\n *\n * One node per MJML element. \"Ending\" elements (`mj-text`, `mj-button`…)\n * keep their inner HTML in `content`; the others hold `children`. Ids are\n * opaque, unique within a tree, made of `[a-z0-9]` so they can live in a CSS\n * class (the canvas finds an element by them, see `canvasMjml.js`).\n *\n * Every operation is PURE: it returns a new tree and never changes the one it\n * got, sharing the untouched branches. The undo history is then just a list\n * of trees.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\n/**\n * @typedef {{\n * id: string,\n * tag: string,\n * attributes: Record<string, string>,\n * children: EmailNode[],\n * content: string|null,\n * }} EmailNode\n */\n\n/** MJML elements whose inner content is HTML (MJML calls them \"ending tags\"). */\nexport const ENDING_TAGS = new Set([\n 'mj-text',\n 'mj-button',\n 'mj-navbar-link',\n 'mj-social-element',\n 'mj-table',\n 'mj-raw',\n 'mj-title',\n 'mj-preview',\n 'mj-style',\n 'mj-accordion-title',\n 'mj-accordion-text',\n]);\n\n/** Elements that hold a whole section's place in the body. */\nexport const SECTION_TAGS = new Set(['mj-section', 'mj-wrapper', 'mj-hero']);\n\n/** Elements that are never removed, moved or duplicated. */\nexport const LOCKED_TAGS = new Set(['mjml', 'mj-head', 'mj-body']);\n\nlet counter = 0;\n\n/** A fresh id: short, unique for the page's lifetime, `[a-z0-9]` only. */\nexport function createId() {\n counter = (counter + 1) % 1679616; // 36^4\n return `n${Date.now().toString(36).slice(-5)}${counter.toString(36).padStart(4, '0')}${Math.floor(Math.random() * 1296)\n .toString(36)\n .padStart(2, '0')}`;\n}\n\n/**\n * @param {string} tag\n * @param {{attributes?: Record<string, string>, children?: EmailNode[], content?: string|null, id?: string}} [parts]\n * @returns {EmailNode}\n */\nexport function createNode(tag, { attributes = {}, children = [], content = null, id = createId() } = {}) {\n return { id, tag, attributes: { ...attributes }, children: [...children], content: ENDING_TAGS.has(tag) ? String(content ?? '') : null };\n}\n\n/**\n * Deep copy with NEW ids everywhere (duplicate, paste, insert a block twice).\n * @param {EmailNode} node\n * @returns {EmailNode}\n */\nexport function cloneWithNewIds(node) {\n return {\n id: createId(),\n tag: node.tag,\n attributes: { ...node.attributes },\n children: node.children.map(cloneWithNewIds),\n content: node.content,\n };\n}\n\n/* ── Reading ─────────────────────────────────────────────────────────────── */\n\n/**\n * The node with this id and its ancestors (root first), or null.\n * @param {EmailNode} root @param {string} id\n * @returns {EmailNode[]|null}\n */\nexport function pathTo(root, id) {\n if (root.id === id) return [root];\n for (const child of root.children) {\n const path = pathTo(child, id);\n if (path) return [root, ...path];\n }\n return null;\n}\n\n/** @param {EmailNode} root @param {string|null|undefined} id */\nexport function findNode(root, id) {\n if (!id) return null;\n const path = pathTo(root, id);\n return path ? path[path.length - 1] : null;\n}\n\n/** @param {EmailNode} root @param {string} id */\nexport function parentOf(root, id) {\n const path = pathTo(root, id);\n return path && path.length > 1 ? path[path.length - 2] : null;\n}\n\n/**\n * Closest node (itself included) whose tag is one of `tags`.\n * @param {EmailNode} root @param {string} id @param {Set<string>|string[]} tags\n */\nexport function closestOfTags(root, id, tags) {\n const set = tags instanceof Set ? tags : new Set(tags);\n const path = pathTo(root, id);\n if (!path) return null;\n for (let i = path.length - 1; i >= 0; i -= 1) if (set.has(path[i].tag)) return path[i];\n return null;\n}\n\n/** First node of a tag, depth first. @param {EmailNode} root @param {string} tag @returns {EmailNode|null} */\nexport function findFirst(root, tag) {\n if (root.tag === tag) return root;\n for (const child of root.children) {\n const found = findFirst(child, tag);\n if (found) return found;\n }\n return null;\n}\n\n/** Every node, depth first. @param {EmailNode} root @param {(node: EmailNode) => void} visit */\nexport function walk(root, visit) {\n visit(root);\n for (const child of root.children) walk(child, visit);\n}\n\n/* ── Writing (pure) ──────────────────────────────────────────────────────── */\n\n/**\n * The tree with the node `id` replaced by `update(node)`.\n * @param {EmailNode} root @param {string} id @param {(node: EmailNode) => EmailNode} update\n * @returns {EmailNode}\n */\nexport function updateNode(root, id, update) {\n if (root.id === id) return update(root);\n let changed = false;\n const children = root.children.map((child) => {\n const next = updateNode(child, id, update);\n if (next !== child) changed = true;\n return next;\n });\n return changed ? { ...root, children } : root;\n}\n\n/**\n * Sets (string) or removes (null, '') attributes.\n * @param {EmailNode} root @param {string} id @param {Record<string, string|null|undefined>} attributes\n */\nexport function setAttributes(root, id, attributes) {\n return updateNode(root, id, (node) => {\n const next = { ...node.attributes };\n for (const [key, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === '') delete next[key];\n else next[key] = String(value);\n }\n return { ...node, attributes: next };\n });\n}\n\n/** @param {EmailNode} root @param {string} id @param {string} content */\nexport function setContent(root, id, content) {\n return updateNode(root, id, (node) => (node.content === null ? node : { ...node, content: String(content ?? '') }));\n}\n\n/**\n * Inserts nodes into `parentId` at `index` (clamped).\n * @param {EmailNode} root @param {string} parentId @param {number} index @param {EmailNode[]} nodes\n */\nexport function insertNodes(root, parentId, index, nodes) {\n return updateNode(root, parentId, (parent) => {\n const at = Math.max(0, Math.min(index, parent.children.length));\n const children = [...parent.children];\n children.splice(at, 0, ...nodes);\n return { ...parent, children };\n });\n}\n\n/** @param {EmailNode} root @param {string} id */\nexport function removeNode(root, id) {\n const parent = parentOf(root, id);\n if (!parent) return root;\n return updateNode(root, parent.id, (p) => ({ ...p, children: p.children.filter((c) => c.id !== id) }));\n}\n\n/**\n * Moves a node under `parentId` at `index`, where `index` counts the target's\n * children BEFORE the node leaves its place (what a drop indicator shows).\n * @param {EmailNode} root @param {string} id @param {string} parentId @param {number} index\n */\nexport function moveNode(root, id, parentId, index) {\n const node = findNode(root, id);\n const from = parentOf(root, id);\n if (!node || !from) return root;\n // A node cannot go inside itself.\n if (pathTo(node, parentId)) return root;\n let at = index;\n if (from.id === parentId) {\n const current = from.children.findIndex((c) => c.id === id);\n if (current < at) at -= 1;\n if (current === at) return root;\n }\n return insertNodes(removeNode(root, id), parentId, at, [node]);\n}\n\n/**\n * Index of a node among its siblings, and their count.\n * @param {EmailNode} root @param {string} id\n */\nexport function positionOf(root, id) {\n const parent = parentOf(root, id);\n if (!parent) return { parent: null, index: -1, count: 0 };\n return { parent, index: parent.children.findIndex((c) => c.id === id), count: parent.children.length };\n}\n\n/* ── Snapshot (the stored project) ───────────────────────────────────────── */\n\n/** Shape version of the stored project (`{ format, root }`). */\nexport const PROJECT_FORMAT = 1;\n\n/** @param {EmailNode} root */\nexport function toProject(root) {\n return { format: PROJECT_FORMAT, root: strip(root) };\n}\n\n/** @param {EmailNode} node @returns {any} */\nfunction strip(node) {\n /** @type {any} */\n const out = { id: node.id, tag: node.tag };\n if (Object.keys(node.attributes).length) out.attributes = { ...node.attributes };\n if (node.content !== null) out.content = node.content;\n if (node.children.length) out.children = node.children.map(strip);\n return out;\n}\n\n/**\n * Reads a stored project back into a tree. Ids are kept when they are valid\n * and unique, replaced otherwise. Throws on anything that is not a project of\n * this format.\n * @param {unknown} project\n * @returns {EmailNode}\n */\nexport function fromProject(project) {\n const value = /** @type {any} */ (project);\n if (!value || typeof value !== 'object' || value.format !== PROJECT_FORMAT || !value.root) {\n throw new Error('Not a project of this builder');\n }\n const seen = new Set();\n /** @param {any} raw @returns {EmailNode} */\n const read = (raw) => {\n if (!raw || typeof raw !== 'object' || typeof raw.tag !== 'string' || !/^(mjml|mj-[a-z-]+)$/.test(raw.tag)) {\n throw new Error('Invalid element in the project');\n }\n let id = typeof raw.id === 'string' && /^[a-z0-9]{1,40}$/.test(raw.id) ? raw.id : createId();\n if (seen.has(id)) id = createId();\n seen.add(id);\n /** @type {Record<string, string>} */\n const attributes = {};\n for (const [k, v] of Object.entries(raw.attributes || {})) {\n if (/^[a-z][a-z0-9-]*$/i.test(k) && v !== null && v !== undefined) attributes[k] = String(v);\n }\n return createNode(raw.tag, {\n id,\n attributes,\n content: ENDING_TAGS.has(raw.tag) ? String(raw.content ?? '') : null,\n children: ENDING_TAGS.has(raw.tag) ? [] : (Array.isArray(raw.children) ? raw.children : []).map(read),\n });\n };\n const root = read(value.root);\n if (root.tag !== 'mjml' || !findFirst(root, 'mj-body')) throw new Error('The project has no email body');\n return root;\n}\n","// @ts-check\n/**\n * MJML ⇄ MODEL — reading MJML into the builder's tree, writing it back.\n *\n * MJML is close enough to XML to be read by a small scanner, and only two\n * kinds of element exist:\n * - containers (`mj-section`, `mj-column`…), made of other MJML elements;\n * - \"ending\" elements (`mj-text`, `mj-button`…), whose inside is HTML kept\n * as is, up to the matching closing tag.\n * Anything else between containers (stray text, comments, HTML outside an\n * ending element) is not MJML and is dropped.\n *\n * Attribute values are DECODED in the model (`&amp;` → `&`) and encoded again\n * on write: the model holds what the attribute means, the MJML how to write\n * it.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { ENDING_TAGS, createNode, findFirst } from './model.js';\n\n/** @typedef {import('./model.js').EmailNode} EmailNode */\n\nconst NAMED_ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: ' ' };\n\n/** @param {string} value */\nexport function decodeAttribute(value) {\n return value.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (match, entity) => {\n if (entity[0] === '#') {\n const code = entity[1] === 'x' || entity[1] === 'X' ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);\n return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : match;\n }\n const named = /** @type {Record<string, string>} */ (NAMED_ENTITIES)[entity.toLowerCase()];\n return named ?? match;\n });\n}\n\n/** @param {string} value */\nexport function encodeAttribute(value) {\n return String(value).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\n/**\n * Reads the attributes of an opening tag, from `start` (just after the name)\n * to its `>`. Quotes may contain `>`.\n * @param {string} source @param {number} start\n * @returns {{attributes: Record<string, string>, end: number, selfClosing: boolean}}\n */\nfunction readAttributes(source, start) {\n /** @type {Record<string, string>} */\n const attributes = {};\n let i = start;\n const n = source.length;\n while (i < n) {\n while (i < n && /\\s/.test(source[i])) i += 1;\n if (source[i] === '>') return { attributes, end: i + 1, selfClosing: false };\n if (source[i] === '/' && source[i + 1] === '>') return { attributes, end: i + 2, selfClosing: true };\n if (i >= n) break;\n const nameMatch = /^[^\\s=/>\"']+/.exec(source.slice(i, i + 200));\n if (!nameMatch) {\n i += 1;\n continue;\n }\n const name = nameMatch[0].toLowerCase();\n i += nameMatch[0].length;\n while (i < n && /\\s/.test(source[i])) i += 1;\n let value = '';\n if (source[i] === '=') {\n i += 1;\n while (i < n && /\\s/.test(source[i])) i += 1;\n const quote = source[i];\n if (quote === '\"' || quote === \"'\") {\n const close = source.indexOf(quote, i + 1);\n const stop = close === -1 ? n : close;\n value = source.slice(i + 1, stop);\n i = stop + 1;\n } else {\n let bare = /^[^\\s>]*/.exec(source.slice(i))?.[0] || '';\n // `width=100/>`: the slash closes the tag, it is not in the value.\n if (bare.endsWith('/') && source[i + bare.length] === '>') bare = bare.slice(0, -1);\n value = bare;\n i += bare.length;\n }\n }\n attributes[name] = decodeAttribute(value);\n }\n return { attributes, end: n, selfClosing: false };\n}\n\n/**\n * MJML elements of a fragment (no `<mjml>` needed): `<mj-section>…</mj-section><mj-section>…`.\n * @param {string} source\n * @returns {EmailNode[]}\n */\nexport function parseMjmlFragment(source) {\n const text = String(source || '').replace(/^\\uFEFF/, '');\n /** @type {EmailNode} */\n const top = createNode('mj-fragment');\n /** @type {EmailNode[]} */\n const stack = [top];\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const lt = text.indexOf('<', i);\n if (lt === -1) break;\n i = lt;\n if (text.startsWith('<!--', i)) {\n const close = text.indexOf('-->', i + 4);\n i = close === -1 ? n : close + 3;\n continue;\n }\n if (text.startsWith('<?', i) || text.startsWith('<!', i)) {\n const close = text.indexOf('>', i);\n i = close === -1 ? n : close + 1;\n continue;\n }\n const closing = text[i + 1] === '/';\n const nameMatch = /^[a-z][a-z0-9-]*/i.exec(text.slice(i + (closing ? 2 : 1), i + 60));\n if (!nameMatch) {\n i += 1;\n continue;\n }\n const tag = nameMatch[0].toLowerCase();\n const afterName = i + (closing ? 2 : 1) + nameMatch[0].length;\n const isMjml = tag === 'mjml' || tag.startsWith('mj-');\n\n if (closing) {\n const close = text.indexOf('>', afterName);\n i = close === -1 ? n : close + 1;\n if (!isMjml) continue;\n // Close up to the matching element (tolerates a forgotten closing tag).\n for (let depth = stack.length - 1; depth > 0; depth -= 1) {\n if (stack[depth].tag === tag) {\n stack.length = depth;\n break;\n }\n }\n continue;\n }\n\n const { attributes, end, selfClosing } = readAttributes(text, afterName);\n i = end;\n if (!isMjml) continue;\n\n const parent = stack[stack.length - 1];\n if (ENDING_TAGS.has(tag)) {\n let content = '';\n if (!selfClosing) {\n const closeRe = new RegExp(`</${tag}\\\\s*>`, 'ig');\n closeRe.lastIndex = i;\n const close = closeRe.exec(text);\n const stop = close ? close.index : n;\n content = text.slice(i, stop).trim();\n i = close ? close.index + close[0].length : n;\n }\n parent.children.push(createNode(tag, { attributes, content }));\n continue;\n }\n const node = createNode(tag, { attributes });\n parent.children.push(node);\n if (!selfClosing) stack.push(node);\n }\n return top.children;\n}\n\n/**\n * A whole email. Always returns an `mjml` root holding an `mj-body`: a bare\n * fragment is wrapped, a missing body is added.\n * @param {string} source\n * @returns {EmailNode}\n */\nexport function parseMjml(source) {\n const nodes = parseMjmlFragment(source);\n let root = nodes.find((node) => node.tag === 'mjml') || null;\n if (!root) {\n const body = nodes.find((node) => node.tag === 'mj-body');\n const head = nodes.find((node) => node.tag === 'mj-head');\n root = createNode('mjml', {\n children: [\n ...(head ? [head] : []),\n body || createNode('mj-body', { children: nodes.filter((node) => node.tag !== 'mj-head') }),\n ],\n });\n }\n if (!findFirst(root, 'mj-body')) root.children.push(createNode('mj-body'));\n return root;\n}\n\n/**\n * The MJML of a tree. `pretty`: one element per line, indented (what is\n * stored and downloaded); otherwise on one line.\n * @param {EmailNode} node\n * @param {{pretty?: boolean, attributes?: (node: EmailNode) => Record<string, string>, extraChildren?: (node: EmailNode) => string}} [options]\n * `attributes`: replaces a node's attributes on write (the canvas adds its\n * markers there); `extraChildren`: MJML appended inside a container.\n */\nexport function serializeMjml(node, options = {}) {\n const { pretty = true } = options;\n /** @param {EmailNode} current @param {number} depth @returns {string} */\n const write = (current, depth) => {\n const indent = pretty ? ' '.repeat(depth) : '';\n const attributes = options.attributes ? options.attributes(current) : current.attributes;\n const attrs = Object.entries(attributes)\n .map(([key, value]) => ` ${key}=\"${encodeAttribute(value)}\"`)\n .join('');\n if (current.content !== null) {\n return `${indent}<${current.tag}${attrs}>${current.content}</${current.tag}>`;\n }\n const inner = current.children.map((child) => write(child, depth + 1));\n const extra = options.extraChildren ? options.extraChildren(current) : '';\n if (extra) inner.push(pretty ? `${' '.repeat(depth + 1)}${extra}` : extra);\n if (!inner.length) return `${indent}<${current.tag}${attrs} />`;\n return pretty\n ? `${indent}<${current.tag}${attrs}>\\n${inner.join('\\n')}\\n${indent}</${current.tag}>`\n : `<${current.tag}${attrs}>${inner.join('')}</${current.tag}>`;\n };\n return write(node, 0);\n}\n","// @ts-check\n/**\n * Building blocks shared by all blocks: palette, fonts, recurring properties.\n *\n * -- DEFAULT PALETTE ---------------------------------------------------------\n * Plain hex values: email clients load no stylesheet and understand neither\n * `var()` nor modern color functions, so every value ends up inline in a\n * `style=\"\"` attribute.\n *\n * -- EVERY BLOCK SETS ITS OWN TEXT ATTRIBUTES --------------------------------\n * Font, size, line height and color are written on the element itself, never\n * left to MJML defaults (the \"Ubuntu\" font loaded from Google, line height 1):\n * the canvas and the sent email then render the same thing, and Outlook gets\n * explicit values.\n *\n * Fonts: only web-safe stacks, installed everywhere and understood by\n * Outlook - a web font only renders in a minority of email clients.\n */\n\n/** @typedef {import('../core/EmailEditor.js').BlockProperty} BlockProperty */\n\n/** Text for an MJML attribute or element. @param {string} text */\nexport function escapeXml(text) {\n return String(text ?? '')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\n/** The default palette. */\nexport const PALETTE = {\n ink: '#2b3038',\n inkSoft: '#41506b',\n navy: '#212e5a',\n brand: '#2977bc',\n border: '#dfe6f0',\n canvas: '#eceff4',\n surface: '#ffffff',\n white: '#ffffff',\n};\n\n/** Default link of a new button. */\nexport const DEFAULT_BUTTON_URL = 'https://example.com';\n\nexport const FONT_STACK = 'Arial, Helvetica, sans-serif';\n\n/** @type {{value: string, label: string}[]} */\nexport const FONT_OPTIONS = [\n { value: 'Arial, Helvetica, sans-serif', label: 'Arial' },\n { value: 'Verdana, Geneva, sans-serif', label: 'Verdana' },\n { value: 'Tahoma, Geneva, sans-serif', label: 'Tahoma' },\n { value: \"'Trebuchet MS', Helvetica, sans-serif\", label: 'Trebuchet' },\n { value: 'Georgia, Times, serif', label: 'Georgia' },\n { value: \"'Times New Roman', Times, serif\", label: 'Times' },\n { value: \"'Courier New', Courier, monospace\", label: 'Courier' },\n];\n\n/** @type {{value: string, label: string}[]} */\nexport const ALIGN_OPTIONS = [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n];\n\n/** Properties panel groups, in display order. */\nexport const GROUPS = {\n content: 'Content',\n link: 'Link',\n typography: 'Typography',\n colors: 'Colors',\n layout: 'Layout',\n spacing: 'Spacing',\n};\n\n/** Recurring properties. */\nexport const prop = {\n /** @returns {BlockProperty} */\n fontFamily: () => ({ key: 'font-family', label: 'Font', type: 'select', group: GROUPS.typography, options: FONT_OPTIONS }),\n /** @returns {BlockProperty} */\n fontSize: (min = 10, max = 48) => ({ key: 'font-size', label: 'Size', type: 'size', group: GROUPS.typography, min, max, unit: 'px' }),\n /** @returns {BlockProperty} */\n lineHeight: () => ({ key: 'line-height', label: 'Line height', type: 'size', group: GROUPS.typography, min: 10, max: 80, unit: 'px' }),\n /** @returns {BlockProperty} */\n color: (label = 'Text') => ({ key: 'color', label, type: 'color', group: GROUPS.colors }),\n /** @returns {BlockProperty} */\n background: (key = 'background-color', label = 'Background') => ({ key, label, type: 'color', group: GROUPS.colors }),\n /** @returns {BlockProperty} */\n align: (options = ALIGN_OPTIONS) => ({ key: 'align', label: 'Alignment', type: 'align', group: GROUPS.layout, options }),\n /** @returns {BlockProperty} */\n padding: (key = 'padding', label = 'Padding') => ({ key, label, type: 'spacing', group: GROUPS.spacing }),\n /** @returns {BlockProperty} */\n href: (label = 'Link URL') => ({ key: 'href', label, type: 'url', group: GROUPS.link, placeholder: 'https://' }),\n /** @returns {BlockProperty} */\n radius: (max = 40) => ({ key: 'border-radius', label: 'Corner radius', type: 'size', group: GROUPS.layout, min: 0, max, unit: 'px' }),\n};\n\n/**\n * White one-column section: the wrapper of a content block dropped outside\n * any column (MJML does not accept text directly inside the body).\n *\n * Warning: padding in LONGHAND form, never `padding=\"...\"`. The canvas starts\n * from the longhand defaults (20 px for a section), which override the\n * shorthand, while the export omits them - the screen and the email would\n * diverge (see core/mjmlAttributes).\n *\n * @param {string} inner\n */\nexport function inWhiteSection(inner) {\n return (\n `<mj-section background-color=\"${PALETTE.surface}\" padding-top=\"0px\" padding-bottom=\"0px\">` +\n `<mj-column>${inner}</mj-column></mj-section>`\n );\n}\n","// @ts-check\n/**\n * BRAND — the look of a company's emails: its name, its logo, a main color,\n * a color for headings and a font. Set once (by hand, or read from the\n * company's website), it is used by every new email, template, block and\n * generated email, and can be applied to the email being edited.\n *\n * `applyBrand` repaints an email by ROLE, not by exact color, so it works on\n * any email — a template, a generated one, one the user built:\n *\n * buttons filled with the main color, text in the color that\n * reads on it (white or ink);\n * colored sections (banners) take the main color; their texts and\n * buttons switch to what reads on it;\n * headings (bold, 18 px and more, on white) take the headings\n * color; body text and gray footers keep theirs;\n * colored links take the main color, or the headings color when the\n * main one is too light to read on white;\n * fonts every text, button and menu link;\n * header (optional) the logo — or the name — at the top, and\n * the name in the footer.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { FONT_OPTIONS, PALETTE, escapeXml } from '../../blocks/shared.js';\nimport { createNode } from '../../engine/model.js';\nimport { parseMjml, parseMjmlFragment, serializeMjml } from '../../engine/mjml.js';\n\n/**\n * @typedef {{\n * name?: string,\n * logoUrl?: string,\n * logoWidth?: number,\n * primary?: string,\n * heading?: string,\n * font?: string,\n * website?: string,\n * }} Brand\n * `primary`: buttons, banners, links; `heading`: headings; colors as\n * `#rrggbb`. `font`: one of the builder's web-safe stacks (FONT_OPTIONS).\n * `website`: where it was read from, to read it again.\n *\n * @typedef {import('../../engine/model.js').EmailNode} EmailNode\n */\n\nconst HEX = /^#[0-9a-f]{6}$/;\nconst FONTS = new Set(FONT_OPTIONS.map((option) => option.value));\nexport const MAX_LOGO_WIDTH = 240;\n\n/* ── Colors ─────────────────────────────────────────────────────────────── */\n\n/**\n * A CSS color as RGB(A), or null: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(),\n * rgba(), hsl(), hsla(), and the keywords white and black.\n * @param {unknown} value\n * @returns {{r: number, g: number, b: number, a: number}|null}\n */\nexport function parseColor(value) {\n const source = String(value ?? '').trim().toLowerCase();\n if (source === 'white') return { r: 255, g: 255, b: 255, a: 1 };\n if (source === 'black') return { r: 0, g: 0, b: 0, a: 1 };\n let match = source.match(/^#([0-9a-f]{3,8})$/);\n if (match) {\n const hex = match[1];\n if (hex.length === 3 || hex.length === 4) {\n const [r, g, b, a = 'ff'] = hex.split('').map((c) => c + c);\n return { r: parseInt(r, 16), g: parseInt(g, 16), b: parseInt(b, 16), a: parseInt(a, 16) / 255 };\n }\n if (hex.length === 6 || hex.length === 8) {\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,\n };\n }\n return null;\n }\n match = source.match(/^(rgba?|hsla?)\\(([^)]*)\\)$/);\n if (!match) return null;\n const parts = match[2].split(/[\\s,/]+/).filter(Boolean);\n if (parts.length < 3) return null;\n const alpha = parts[3] === undefined ? 1 : parts[3].endsWith('%') ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);\n if (match[1].startsWith('rgb')) {\n const channel = (/** @type {string} */ part) => (part.endsWith('%') ? (parseFloat(part) * 255) / 100 : parseFloat(part));\n const [r, g, b] = parts.slice(0, 3).map(channel);\n if ([r, g, b, alpha].some((n) => Number.isNaN(n))) return null;\n const clamp = (/** @type {number} */ n) => Math.max(0, Math.min(255, Math.round(n)));\n return { r: clamp(r), g: clamp(g), b: clamp(b), a: alpha };\n }\n const h = parseFloat(parts[0]);\n const s = parseFloat(parts[1]) / 100;\n const l = parseFloat(parts[2]) / 100;\n if ([h, s, l, alpha].some((n) => Number.isNaN(n))) return null;\n return { ...fromHsl(((h % 360) + 360) % 360, Math.max(0, Math.min(1, s)), Math.max(0, Math.min(1, l))), a: alpha };\n}\n\n/** @param {number} h @param {number} s @param {number} l */\nfunction fromHsl(h, s, l) {\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n const [r, g, b] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255) };\n}\n\n/** @param {{r: number, g: number, b: number}} color */\nexport const toHex = ({ r, g, b }) => `#${[r, g, b].map((n) => n.toString(16).padStart(2, '0')).join('')}`;\n\n/** Hue (0-360), saturation and lightness (0-1). @param {{r: number, g: number, b: number}} color */\nexport function toHsl({ r, g, b }) {\n const [R, G, B] = [r / 255, g / 255, b / 255];\n const max = Math.max(R, G, B);\n const min = Math.min(R, G, B);\n const l = (max + min) / 2;\n if (max === min) return { h: 0, s: 0, l };\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n const h = max === R ? ((G - B) / d + (G < B ? 6 : 0)) * 60 : max === G ? ((B - R) / d + 2) * 60 : ((R - G) / d + 4) * 60;\n return { h, s, l };\n}\n\n/** WCAG relative luminance. @param {{r: number, g: number, b: number}} color */\nfunction luminance({ r, g, b }) {\n const linear = (/** @type {number} */ n) => {\n const c = n / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). @param {string} a @param {string} b */\nexport function contrastRatio(a, b) {\n const ca = parseColor(a);\n const cb = parseColor(b);\n if (!ca || !cb) return 1;\n const [hi, lo] = [luminance(ca), luminance(cb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** The text color that reads on `background`: white or ink. @param {string} background */\nexport function readableOn(background) {\n return contrastRatio(background, PALETTE.white) >= contrastRatio(background, PALETTE.ink) ? PALETTE.white : PALETTE.ink;\n}\n\n/**\n * A color meant to be noticed (a banner, a filled button), as opposed to\n * white, light tints, grays and near-black.\n * @param {unknown} value\n */\nexport function isColored(value) {\n const color = parseColor(value);\n if (!color || color.a < 0.5) return false;\n const { s, l } = toHsl(color);\n return s >= 0.2 && l >= 0.08 && l <= 0.88;\n}\n\n/** Dark enough to carry white text (navy, charcoal…). @param {unknown} value */\nfunction isDark(value) {\n const color = parseColor(value);\n return Boolean(color && color.a >= 0.5 && luminance(color) < 0.12);\n}\n\n/** A light gray (footers, captions): left as it is. @param {unknown} value */\nfunction isMuted(value) {\n const color = parseColor(value);\n if (!color) return false;\n const { s, l } = toHsl(color);\n return s < 0.2 && l > 0.4;\n}\n\n/* ── The brand itself ───────────────────────────────────────────────────── */\n\n/** @param {unknown} value */\nconst cleanColor = (value) => {\n const color = parseColor(value);\n return color && color.a >= 0.5 ? toHex(color) : '';\n};\n\n/**\n * A brand as stored and used: unknown fields dropped, colors as #rrggbb,\n * the font one of the builder's stacks, the logo an https or site-relative\n * image. Whatever is invalid is left out (the builder's default is used).\n * @param {unknown} input\n * @returns {Brand}\n */\nexport function cleanBrand(input) {\n const source = /** @type {Record<string, unknown>} */ (input && typeof input === 'object' ? input : {});\n /** @type {Brand} */\n const brand = {};\n const name = typeof source.name === 'string' ? source.name.replace(/\\s+/g, ' ').trim().slice(0, 80) : '';\n if (name) brand.name = name;\n const logo = typeof source.logoUrl === 'string' ? source.logoUrl.trim() : '';\n if (logo && logo.length <= 2000 && (/^https:\\/\\/[^\\s\"'<>]+$/i.test(logo) || /^\\/(?!\\/)[^\\s\"'<>]*$/.test(logo))) {\n brand.logoUrl = logo;\n const width = Math.round(Number(source.logoWidth));\n if (width >= 24 && width <= MAX_LOGO_WIDTH) brand.logoWidth = width;\n }\n const primary = cleanColor(source.primary);\n if (primary && HEX.test(primary)) brand.primary = primary;\n const heading = cleanColor(source.heading);\n if (heading && HEX.test(heading)) brand.heading = heading;\n if (typeof source.font === 'string' && FONTS.has(source.font)) brand.font = source.font;\n const website = typeof source.website === 'string' ? source.website.trim() : '';\n if (/^https?:\\/\\/[^\\s\"'<>]+$/i.test(website) && website.length <= 500) brand.website = website;\n return brand;\n}\n\n/** Does the brand change anything? @param {Brand|null|undefined} brand */\nexport const hasBrand = (brand) => Boolean(brand && Object.keys(cleanBrand(brand)).some((key) => key !== 'website'));\n\n/**\n * The sender the templates use: the brand's name and logo over the host's\n * sender (whose postal address stays).\n * @param {import('../templates/starter.js').StarterSender|null|undefined} sender\n * @param {Brand|null|undefined} brand\n */\nexport function brandedSender(sender, brand) {\n const base = { ...(sender || {}) };\n if (!brand) return base;\n if (brand.name) base.name = brand.name;\n if (brand.logoUrl) {\n base.logoUrl = brand.logoUrl;\n base.logoWidth = brand.logoWidth || base.logoWidth;\n }\n return base;\n}\n\n/**\n * The colors applyBrand paints with.\n * @param {Brand} brand\n */\nfunction paletteOf(brand) {\n const primary = brand.primary || null;\n return {\n primary,\n onPrimary: primary ? readableOn(primary) : null,\n heading: brand.heading || null,\n // A link must read on white: a too-light main color gives way.\n link: primary ? (contrastRatio(primary, PALETTE.white) >= 3 ? primary : brand.heading || PALETTE.ink) : null,\n font: brand.font || null,\n };\n}\n\n/* ── Painting ───────────────────────────────────────────────────────────── */\n\nconst SECTION_LIKE = new Set(['mj-section', 'mj-wrapper', 'mj-hero', 'mj-column', 'mj-group']);\nconst FONT_TAGS = new Set(['mj-text', 'mj-button', 'mj-navbar-link', 'mj-social-element', 'mj-table']);\n\n/** @param {string|undefined} size */\nconst px = (size) => {\n const n = parseFloat(String(size ?? ''));\n return Number.isNaN(n) ? 0 : n;\n};\n\n/** @param {string|undefined} weight */\nconst isBold = (weight) => /^(bold|bolder|[6-9]00)$/.test(String(weight ?? '').trim());\n\n/**\n * The links of a text in a new color, when theirs is a color (a gray or a\n * black link is a choice, and stays).\n * @param {string} html @param {string} color\n */\nfunction recolorLinks(html, color) {\n return html.replace(/<a\\b[^>]*>/gi, (tag) =>\n tag.replace(/(style\\s*=\\s*\")([^\"]*)(\")/i, (_, open, style, close) =>\n `${open}${style.replace(/(^|;)(\\s*color\\s*:\\s*)([^;]+)/i, (whole, sep, prop, value) =>\n isColored(value) ? `${sep}${prop}${color}` : whole\n )}${close}`\n )\n );\n}\n\n/**\n * One node and what it holds, in the brand's colors.\n * @param {EmailNode} node\n * @param {ReturnType<typeof paletteOf>} palette\n * @param {boolean} onColor the node sits on a colored background\n * @returns {EmailNode}\n */\nfunction paint(node, palette, onColor) {\n const attributes = { ...node.attributes };\n let content = node.content;\n let childrenOnColor = onColor;\n const set = (/** @type {string} */ key, /** @type {string|null} */ value) => {\n if (value) attributes[key] = value;\n };\n\n if (SECTION_LIKE.has(node.tag)) {\n const background = attributes['background-color'];\n if (background && palette.primary && (isColored(background) || (isDark(background) && node.tag !== 'mj-column'))) {\n attributes['background-color'] = palette.primary;\n childrenOnColor = true;\n } else if (background && parseColor(background)) {\n childrenOnColor = false;\n }\n }\n\n if (FONT_TAGS.has(node.tag) && palette.font) set('font-family', palette.font);\n\n if (node.tag === 'mj-text' && content !== null) {\n if (onColor) {\n set('color', palette.onPrimary);\n } else {\n const heading = isBold(attributes['font-weight']) && px(attributes['font-size']) >= 18;\n if (heading && palette.heading && !isMuted(attributes.color)) attributes.color = palette.heading;\n if (palette.link) content = recolorLinks(content, palette.link);\n }\n }\n\n if (node.tag === 'mj-button') {\n const background = attributes['background-color'];\n // MJML's own default is a dark gray: a button without color is filled.\n const filled = !background || isColored(background) || isDark(background);\n if (palette.primary && filled) {\n set('background-color', onColor ? palette.onPrimary : palette.primary);\n set('color', onColor ? palette.primary : palette.onPrimary);\n } else if (palette.primary && onColor) {\n // A light button on a banner: kept light, its text in the main color.\n set('color', readableOn(background) === PALETTE.ink && contrastRatio(palette.primary, background) < 3 ? PALETTE.ink : palette.primary);\n } else if (palette.link && isColored(attributes.color)) {\n // An outlined button on white.\n attributes.color = palette.link;\n if (isColored(attributes['border-color'])) attributes['border-color'] = palette.link;\n }\n }\n\n if (node.tag === 'mj-navbar-link' && isColored(attributes.color)) {\n set('color', onColor ? palette.onPrimary : palette.link);\n }\n\n const children = node.children.map((child) => paint(child, palette, childrenOnColor));\n return { ...node, attributes, content, children };\n}\n\n/** @param {string} html */\nconst plainText = (html) =>\n html\n .replace(/<[^>]*>/g, ' ')\n .replace(/&nbsp;/g, ' ')\n .replace(/&amp;/g, '&')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/** @param {EmailNode} body */\nconst sectionsOf = (body) =>\n body.children.flatMap((child) => (child.tag === 'mj-wrapper' ? child.children.filter((c) => c.tag === 'mj-section') : [child]));\n\n/**\n * The logo, or the name, at the top; the name in the footer.\n * @param {EmailNode} root\n * @param {Brand} brand\n * @param {{names: string[], logos: string[]}} previous what the email may still show\n * @returns {EmailNode}\n */\nfunction rebrandHeader(root, brand, previous) {\n const body = root.children.find((child) => child.tag === 'mj-body');\n if (!body) return root;\n const names = previous.names.map((name) => String(name || '').trim()).filter((name) => name.length >= 2);\n const known = new Set(names.map((name) => name.toLowerCase()));\n const sections = sectionsOf(body);\n /** @type {Map<string, EmailNode>} */\n const replaced = new Map();\n\n const top = sections[0];\n const column = top?.children.find((child) => child.tag === 'mj-column');\n const first = column?.children[0];\n if (first?.tag === 'mj-image') {\n const small = px(first.attributes.width) > 0 && px(first.attributes.width) <= MAX_LOGO_WIDTH;\n const wasLogo = small || previous.logos.includes(first.attributes.src) || known.has(String(first.attributes.alt || '').toLowerCase());\n if (wasLogo && brand.logoUrl) {\n replaced.set(first.id, {\n ...first,\n attributes: {\n ...first.attributes,\n src: brand.logoUrl,\n alt: brand.name || first.attributes.alt || '',\n width: `${brand.logoWidth || Math.min(px(first.attributes.width) || 140, MAX_LOGO_WIDTH)}px`,\n },\n });\n }\n } else if (first?.tag === 'mj-text' && first.content !== null && known.has(plainText(first.content).toLowerCase())) {\n if (brand.logoUrl) {\n const keep = Object.fromEntries(Object.entries(first.attributes).filter(([key]) => key.startsWith('padding') || key === 'align'));\n replaced.set(\n first.id,\n createNode('mj-image', {\n id: first.id,\n attributes: { src: brand.logoUrl, alt: brand.name || '', width: `${brand.logoWidth || 140}px`, align: 'left', ...keep },\n })\n );\n } else if (brand.name) {\n replaced.set(first.id, { ...first, content: escapeXml(brand.name) });\n }\n }\n\n // The footer: the old name in its line becomes the new one.\n const bottom = sections.length > 1 ? sections[sections.length - 1] : null;\n const newName = brand.name;\n if (bottom && newName) {\n const swap = (/** @type {EmailNode} */ node) => {\n if (node.tag === 'mj-text' && node.content !== null) {\n let html = node.content;\n for (const name of names) {\n if (name !== newName) html = html.split(escapeXml(name)).join(escapeXml(newName));\n }\n if (html !== node.content) replaced.set(node.id, { ...node, content: html });\n }\n node.children.forEach(swap);\n };\n swap(bottom);\n }\n\n if (!replaced.size) return root;\n const rebuild = (/** @type {EmailNode} */ node) => replaced.get(node.id) || { ...node, children: node.children.map(rebuild) };\n return rebuild(root);\n}\n\n/**\n * An email in the brand's look.\n * @param {string} mjml\n * @param {Brand|null|undefined} brand\n * @param {{header?: boolean, previous?: {names?: (string|undefined)[], logos?: (string|undefined)[]}}} [options]\n * `header`: also put the logo (or name) at the top and the name in the\n * footer, replacing what `previous` lists (the names and logos the email\n * may still show: the host's sender, the brand before, \"Your company\").\n * @returns {string}\n */\nexport function applyBrand(mjml, brand, { header = false, previous = {} } = {}) {\n const clean = cleanBrand(brand);\n if (!hasBrand(clean)) return mjml;\n let root = parseMjml(mjml);\n root = paint(root, paletteOf(clean), false);\n if (header) {\n root = rebrandHeader(root, clean, {\n names: /** @type {string[]} */ ((previous.names || []).filter(Boolean)),\n logos: /** @type {string[]} */ ((previous.logos || []).filter(Boolean)),\n });\n }\n return serializeMjml(root);\n}\n\n/**\n * A fragment (a block about to be inserted) in the brand's look.\n * @param {string} mjml @param {Brand|null|undefined} brand\n */\nexport function applyBrandToFragment(mjml, brand) {\n const clean = cleanBrand(brand);\n if (!mjml || !hasBrand(clean)) return mjml;\n const palette = paletteOf(clean);\n return parseMjmlFragment(mjml)\n .map((node) => serializeMjml(paint(node, palette, false), { pretty: false }))\n .join('');\n}\n\n/**\n * A ready-made email (the starter, a gallery template) in the brand's look,\n * its header and footer naming the brand rather than the host's sender or\n * the \"Your company\" placeholder.\n * @param {string} mjml\n * @param {Brand|null|undefined} brand\n * @param {{sender?: import('../templates/starter.js').StarterSender|null, t?: (key: string) => string}} [context]\n */\nexport function brandTemplate(mjml, brand, { sender = null, t = (key) => key } = {}) {\n return applyBrand(mjml, brand, {\n header: true,\n previous: { names: [sender?.name, t('Your company')], logos: [sender?.logoUrl] },\n });\n}\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 * DARK MODE, simulated — an email as the mail apps that repaint it in dark\n * mode show it (Gmail and Outlook apps, Outlook.com): light backgrounds\n * turned dark, dark text turned light, everything else left as it is.\n *\n * surfaces background and border colors (`background`, `bgcolor`,\n * `border-color`…): a LIGHT one becomes dark, same hue; a dark\n * or saturated one (a brand button) stays.\n * text `color`: a DARK one becomes light, same hue; a light one\n * (white on a button) stays.\n * images untouched: a dark logo on a transparent background disappears\n * here as it does in those apps — what the preview is for.\n *\n * Works on the compiled HTML as a string: the colors of `style` attributes,\n * of `<style>` sheets and of `bgcolor`. No script, no DOM: the preview frame\n * stays sandboxed.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\n/** The page behind the email, and its default text, in dark apps. */\nexport const DARK_PAGE = '#121212';\nconst DARK_TEXT = '#e8e8e8';\n\n/** A few named colors emails use. */\nconst NAMED = {\n white: '#ffffff',\n black: '#000000',\n snow: '#fffafa',\n ivory: '#fffff0',\n whitesmoke: '#f5f5f5',\n ghostwhite: '#f8f8ff',\n gainsboro: '#dcdcdc',\n lightgray: '#d3d3d3',\n lightgrey: '#d3d3d3',\n silver: '#c0c0c0',\n gray: '#808080',\n grey: '#808080',\n navy: '#000080',\n};\n\n/** @typedef {{r: number, g: number, b: number, a: number}} Rgba channels 0–255, alpha 0–1 */\n\n/** @param {string} token @returns {Rgba|null} */\nexport function parseCssColor(token) {\n const value = token.trim().toLowerCase();\n const hex = NAMED[/** @type {keyof typeof NAMED} */ (value)] || value;\n let match = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.exec(hex);\n if (match) {\n let digits = match[1];\n if (digits.length <= 4) digits = [...digits].map((d) => d + d).join('');\n const n = (/** @type {number} */ i) => parseInt(digits.slice(i, i + 2), 16);\n return { r: n(0), g: n(2), b: n(4), a: digits.length === 8 ? n(6) / 255 : 1 };\n }\n match = /^rgba?\\(\\s*([\\d.]+%?)[\\s,]+([\\d.]+%?)[\\s,]+([\\d.]+%?)(?:[\\s,/]+([\\d.]+%?))?\\s*\\)$/.exec(value);\n if (match) {\n const channel = (/** @type {string} */ v) => Math.min(255, v.endsWith('%') ? (parseFloat(v) * 255) / 100 : parseFloat(v));\n const alpha = match[4] === undefined ? 1 : match[4].endsWith('%') ? parseFloat(match[4]) / 100 : parseFloat(match[4]);\n return { r: channel(match[1]), g: channel(match[2]), b: channel(match[3]), a: Math.min(1, alpha) };\n }\n return null;\n}\n\n/** WCAG relative luminance, 0 (black) to 1 (white). @param {Rgba} color */\nexport function luminance({ r, g, b }) {\n const linear = (/** @type {number} */ c) => {\n const v = c / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n}\n\n/** @param {Rgba} color @returns {[number, number, number]} hue 0–360, saturation and lightness 0–1 */\nfunction toHsl({ r, g, b }) {\n const [R, G, B] = [r / 255, g / 255, b / 255];\n const max = Math.max(R, G, B);\n const min = Math.min(R, G, B);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n const h = max === R ? (G - B) / d + (G < B ? 6 : 0) : max === G ? (B - R) / d + 2 : (R - G) / d + 4;\n return [h * 60, s, l];\n}\n\n/** @param {number} h @param {number} s @param {number} l @param {number} a */\nfunction fromHsl(h, s, l, a) {\n const k = (/** @type {number} */ n) => (n + h / 30) % 12;\n const q = s * Math.min(l, 1 - l);\n const f = (/** @type {number} */ n) => Math.round(255 * (l - q * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1))));\n const [r, g, b] = [f(0), f(8), f(4)];\n if (a < 1) return `rgba(${r}, ${g}, ${b}, ${Math.round(a * 100) / 100})`;\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;\n}\n\n/**\n * One color in dark mode. `surface`: a background or a border; otherwise a\n * text color. Unknown values (`transparent`, `inherit`…) come back as given.\n * @param {string} token @param {'surface'|'text'} role\n */\nexport function darkColor(token, role) {\n const color = parseCssColor(token);\n if (!color) return token;\n const lum = luminance(color);\n const [h, s] = toHsl(color);\n if (role === 'surface') {\n // White becomes the dark apps' page; a pale tint a dark one of its hue.\n return lum > 0.45 ? fromHsl(h, s * 0.5, 0.07 + (1 - lum) * 0.35, color.a) : token;\n }\n return lum < 0.3 ? fromHsl(h, s * 0.7, 0.91 - lum * 0.5, color.a) : token;\n}\n\nconst COLOR_TOKEN = new RegExp(`url\\\\([^)]*\\\\)|(#[0-9a-f]{3,8}\\\\b|rgba?\\\\([^)]*\\\\)|\\\\b(?:${Object.keys(NAMED).join('|')})\\\\b)`, 'gi');\nconst COLOR_PROPERTY =\n /(^|[;{\\s])(color|background(?:-color)?|border(?:-(?:top|right|bottom|left))?(?:-color)?|outline(?:-color)?)(\\s*:\\s*)([^;}]*)/gi;\n\n/** The colors of a CSS text (a sheet, or a `style` attribute) in dark mode. @param {string} css */\nexport function darkCss(css) {\n return css.replace(COLOR_PROPERTY, (_, before, property, colon, value) => {\n const role = property.toLowerCase() === 'color' ? 'text' : 'surface';\n const changed = value.replace(COLOR_TOKEN, (/** @type {string} */ whole, /** @type {string} */ color) =>\n color ? darkColor(color, role) : whole\n );\n return `${before}${property}${colon}${changed}`;\n });\n}\n\nconst DARK_SHEET = `<style data-eb-dark>:root{color-scheme:dark}html,body{background-color:${DARK_PAGE};color:${DARK_TEXT}}</style>`;\n\n/**\n * A compiled email as a dark-mode app would show it.\n * @param {string} html\n */\nexport function simulateDarkMode(html) {\n if (!html) return '';\n const recolored = String(html)\n .replace(/(<style\\b[^>]*>)([\\s\\S]*?)(<\\/style>)/gi, (_, open, css, close) => open + darkCss(css) + close)\n .replace(/(\\sstyle\\s*=\\s*)([\"'])([\\s\\S]*?)\\2/gi, (_, attr, quote, css) => attr + quote + darkCss(css) + quote)\n .replace(/(\\sbgcolor\\s*=\\s*)([\"']?)([#\\w]+)\\2/gi, (_, attr, quote, color) => attr + quote + darkColor(color, 'surface') + quote);\n // The apps' own page and text, under whatever the email sets itself.\n return /<head\\b[^>]*>/i.test(recolored) ? recolored.replace(/<head\\b[^>]*>/i, (head) => head + DARK_SHEET) : DARK_SHEET + recolored;\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 * 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"],"mappings":"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","names":["prop","clean","luminance","toHsl","fromHsl"]}