[
  {
    "path": ".editorconfig",
    "content": "# https://editorconfig.org\n\n# A special property that should be specified at the top of the file outside of\n# any sections. Set to true to stop .editor config file search on current file\nroot = true\n\n# Indentation style\n# Possible values - tab, space\nindent_style = space\n\n# Indentation size in single-spaced characters\n# Possible values - an integer, tab\nindent_size = 2\n\n# Line ending file format\n# Possible values - lf, crlf, cr\nend_of_line = lf\n\n# File character encoding\n# Possible values - latin1, utf-8, utf-16be, utf-16le\ncharset = utf-8\n\n# Denotes whether to trim whitespace at the end of lines\n# Possible values - true, false\ntrim_trailing_whitespace = true\n\n# Denotes whether file should end with a newline\n# Possible values - true, false\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules\nlib\npackages/mjml-core/src/types*\ntype.js\ntest-html-attributes.js\ntest.js\nbabel.config.js\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": [\"airbnb-base\", \"prettier\"],\n  \"parser\": \"babel-eslint\",\n  \"rules\": {\n    \"comma-dangle\": [2, \"always-multiline\"],\n    \"semi\": [2, \"never\"],\n    \"no-mixed-operators\": 0,\n    \"no-shadow\": 0,\n    \"no-param-reassign\": 0,\n    \"no-restricted-syntax\": 0,\n  },\n  \"env\": {\n    \"node\": true,\n    \"mocha\": true,\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"packages/mjml/test/*.test.js\"],\n      \"rules\": {\n        \"prefer-arrow-callback\": \"off\",\n        \"func-names\": \"off\",\n      },\n    },\n  ],\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Create a file with this MJML code: `<mjml>...</mjml>`\n2. Render it to HTML by doing '...'\n3. Send the HTML to an email address with '...'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**MJML environment (please complete the following information):**\n - OS: [e.g. MacOS]\n - MJML Version [e.g. 4.2.0]\n - MJML tool used [e.g MJML App]\n\n**Email sending environment(for rendering issues)**:\n - Platform used to send the email [e.g [Putsmail](https://putsmail.com/)]  \n\n**Affected email clients (for rendering issues):**\n - Email Client [e.g Gmail] \n - OS: [e.g. Windows]\n - Browser [e.g. Google Chrome]\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/build-documentation.yml",
    "content": "name: Build Documentation\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  buildDoc:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Trigger Documentation Build\n        uses: peter-evans/repository-dispatch@v1\n        with:\n          token: ${{ secrets.documentation_token }}\n          repository: mjmlio/slate\n          event-type: build-doc\n"
  },
  {
    "path": ".github/workflows/mjml-workflow.yml",
    "content": "name: Mjml CI\non: [push, pull_request]\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [16.x, 18.x, 20.x]\n    steps:\n      - uses: actions/checkout@v2\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Run linting & tests\n        run: |\n          yarn install\n          yarn build\n          yarn lint\n          yarn test\n"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "content": "name: Publish Package to NPM\non:\n  workflow_dispatch:\n    inputs:\n      release_type:\n        description: 'Version bump type'\n        required: true\n        default: 'patch'\n        type: choice\n        options:\n          - patch\n          - minor\n          - major\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20.x'\n          registry-url: 'https://registry.npmjs.org'\n      - run: yarn install\n      - run: yarn build\n      - run: yarn build-browser\n      - name: Configure git user\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n      - name: Bump version\n        id: bump\n        run: |\n          yarn lerna version ${{ github.event.inputs.release_type }} --no-push --no-git-tag-version --yes --no-private --force-publish\n          NEW_VERSION=$(jq -r '.version' lerna.json)\n          echo \"version=$NEW_VERSION\" >> $GITHUB_OUTPUT\n      - name: Commit & tag release\n        run: |\n          git add lerna.json packages/*/package.json\n          git commit -m \"chore(release): v${{ steps.bump.outputs.version }}\"\n          git push origin HEAD\n      - name: Create GitHub Release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: v${{ steps.bump.outputs.version }}\n          generate_release_notes: true\n      - name: Publish packages to NPM\n        run: yarn lerna publish from-package --yes\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*.log\n.idea/\nlib\nnode_modules\ntest.html\n/**/npmignore\n/testing-manual\n"
  },
  {
    "path": ".prettierignore",
    "content": "package.json\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 80,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# What should I know before I get started?\n\n## Code of Conduct\n\nThis project adheres to the Contributor Covenant [code of conduct](https://contributor-covenant.org/version/1/4/). By participating, you are expected to uphold this code. Please report unacceptable behavior to [support@mjml.io](mailto:support@mjml.io).\n\n## Packages\n\nMJML is made up of different [packages](https://github.com/mjmlio/mjml/tree/master/packages), which make it very modular but might also make it hard for you to know how it is organized.\n\nThere are 3 types of packages:\n\n* `mjml-core`: the engine that renders mjml components\n\n* `mjml-cli`: the client, base on the mjml-core interface\n\n* `mjml`: a standalone client including the standard library of components\n\n* one standalone package for each component\n\n# How Can I Contribute?\n\n## Reporting Bugs\n\nHere are the guidelines to help maintainers and the community better understand and solve your issue.\n\n## Before Submitting a Bug Report or Enhancement\n\n* **Check the [FAQ](https://mjml.io/faq)** for a list of common questions and problems\n\n* **Check the [documentation](https://documentation.mjml.io/)** for more details on how to use MJML, MJML components, how to create a custom component and more\n\n* **Search [issues](https://github.com/mjmlio/mjml/issues?utf8=%E2%9C%93&q=is%3Aissue+)** and **[pull requests](https://github.com/mjmlio/mjml/pulls?utf8=%E2%9C%93&q=is%3Apr+)** to see if a similar one might have been already asked before\n\n## How To Submit A Good Bug Report or Enhancement?\n\nExplain the problem you’re facing and include as many details as you can to help maintainers reproduce the problem:\n\n* **Use a clear and self-explanatory title**\n\n* **Provide all the specific information that might be needed to reproduce the problem, such as:**\n\n    * **How you’re using MJML** (whether you’re using the [try it live](https://mjml.io/try-it-live), [running it locally](https://github.com/mjmlio/mjml/releases), [using the app](https://github.com/mjmlio/mjml-app), or any other way)\n\n    * The **version of MJML** you’re using\n\n    * The **MJML code** you used to encounter this bug, as copy/pasteable snippets, using [Markdown Code Blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/)\n\n    * The **name and version of the email client(s)** on which a bug is encountered\n\n    * **Screenshots** of the issue / behaviour before enhancement on the **given email clients**\n\n    * Explain why **what you encountered is a bug** / how your enhancement would **improve MJML**: what did you expect to see and why?\n\n* If you want MJML to support a new styling attribute, **add screenshots **from Litmus or Email On Acid showing that this attribute is **supported for [email clients supported by MJML](https://mjml.io/faq#email-clients)**\n\n## Template For Submitting Bug Reports\n\n      [Short description of problem here]\n\n      **Reproduction Steps:**\n\n      1. [First Step]\n      2. [Second Step]\n      3. [Other Steps...]\n\n      **Expected behavior:**\n\n      [Describe expected behavior here]\n\n      **Observed behavior:**\n\n      [Describe observed behavior here]\n\n      **Screenshots and GIFs**\n\n      ![Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url)\n\n      **MJML version:**\n\n      [Enter MJML version here]\n\n      **Email clients the bug is seen on:**\n\n      [Enter email clients names and versions here]\n\n## Your First Code Contribution\n\nIf you’re not sure how you can contribute to MJML, start looking for the [beginner](https://github.com/mjmlio/mjml/labels/Beginner) and [help-wanted](https://github.com/mjmlio/mjml/labels/Community%20help%20wanted) labels.\n\n## How to Submit A Good Pull Request\n\n* Document your code\n* Update the documentation (example: table of a component’s supported attributes if you add an attribute to this component)\n* Test your pull request locally\n* Include screenshots from [Litmus](https://litmus.com/) or [Email On Acid](https://www.emailonacid.com/) showing that your feature is supported for [email clients supported by MJML](https://mjml.io/faq#email-clients)\n* Provide the MJML code you used to test locally and on the screenshots\n* We suggest following the [React Styleguide](https://github.com/airbnb/javascript/tree/master/react) by Airbnb\n\n# Additional Notes\n\n## Discussions vs Bugs & Enhancements\n\n## Tags categories\n\nType of issue and issue state\n\n#### Type of Issue and Issue State\n\n| Label name | Description |\n| --- | --- |\n| `Feature request` | Feature requests or improvements |\n| `Bug` | Confirmed bugs or reports likely to be bugs |\n| `Community-help-wanted` | The MJML team would appreciate help from the community in implementing these issues |\n| `Beginner` | Less complex issues that would be good first issues to work on for users who want to contribute to MJML |\n| `More information needed` | We need more information to solve this issue (see [How to submit a good bug report or enhancement]( https://github.com/mjmlio/mjml/blob/master/CONTRIBUTING.md#how-to-submit-a-good-bug-report-or-enhancement)) |\n| `Needs reproduction` | Likely bugs we couldn’t reproduce |\n| `Duplicate` | Issues that are duplicates of other issues |\n| `Invalid` | Issues which aren’t valid (e.g user errors) |\n| `Tooling idea` | Feature requests that might be good candidates for tools around MJML instead of extending MJML |\n\n#### Topic categories\n\n| Label name | Description |\n| --- | --- |\n| `Not rendering` | the engine won’t render a template without a valid reason |\n| `General rendering issue` | the HTML rendered is not responsive while respecting MJML’s best practices |\n| `Email client name` | The HTML rendered is not responsive for a specific email client |\n| `CLI`| issues related to the MJML Command Line Interface |\n| `Documentation` | issues related to the MJML documentation |\n\n#### Pull Requests labels\n\n| Label name | Description |\n| --- | --- |\n| `Work in progress` | PR which are still being worked on, more changes will follow |\n| `Needs review `| Pull requests which need code review and approval |\n| `Under review` | PR being reviewed |\n| `Requires changes` | PR which need to be updated based on review comments and then reviewed again\n| `Needs testing` | PRs which need testing on [Litmus](https://litmus.com/) or [Email On Acid](https://www.emailonacid.com/) |\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Mailjet SAS, https://mjml.io\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# MJML 4\n\nIf you're looking for MJML 3.3.X check [this branch](https://github.com/mjmlio/mjml/tree/3.3.x)\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://mjml.io\" target=\"_blank\">\n    <img width=\"250\"src=\"https://mjml.io/assets/img/litmus/mjmlbymailjet.png\">\n\n  </a>\n</p>\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://github.com/mjmlio/mjml/actions\">\n    <img src=\"https://github.com/mjmlio/mjml/workflows/Mjml%20CI/badge.svg?branch=master\" alt=\"github actions\">\n  </a>\n  <a href=\"https://www.codacy.com/app/gbadi/mjml\">\n    <img src=\"https://api.codacy.com/project/badge/grade/575339cb861f4ff4b0dbb3f9e1759c35\"/>\n  </a>\n</p>\n\n\n<p style=\"text-align: center;\" >\n  | <b><a href=\"#translated-documentation\">Translated documentation</a></b>\n  | <b><a href=\"#introduction\">Introduction</a></b>\n  | <b><a href=\"#installation\">Installation</a></b>\n  | <b><a href=\"#usage\">Usage</a></b> |\n</p>\n\n---\n# Translated documentation\n\n| Language | Link for documentation |\n| :-: | :-: |\n| 日本語 | [日本語ドキュメント](https://github.com/mjmlio/mjml/blob/master/readme-ja.md) |\n\n# Introduction\n\n`MJML` is a markup language created by [Mailjet](https://www.mailjet.com/) and designed to reduce the pain of coding a responsive email. Its semantic syntax makes the language easy and straightforward while its rich standard components library shortens your development time and lightens your email codebase. MJML’s open-source engine takes care of translating the `MJML` you wrote into responsive HTML.\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://mjml.io\" target=\"_blank\">\n    <img width=\"75%\" src=\"https://cloud.githubusercontent.com/assets/6558790/12450760/ee034178-bf85-11e5-9dda-98d0c8f9f8d6.png\">\n  </a>\n</p>\n\n\n# Installation\n\nYou can install `MJML` with `NPM` to use it with NodeJS or the Command Line Interface. If you're not sure what those are, head over to <a href=\"#usage\">Usage</a> for other ways to use MJML.\n\n```bash\nnpm install mjml\n```\n\n# Development\n\nTo work on MJML, make changes and create merge requests, download and install [yarn](https://yarnpkg.com/lang/en/docs/install/) for easy development.\n\n```bash\ngit clone https://github.com/mjmlio/mjml.git && cd mjml\nyarn\nyarn build\n```\n\nYou can also run `yarn build:watch` to rebuild the package as you code.\n\n# Usage\n\n## Online\n\nDon't want to install anything? Use the free online editor!\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://mjml.io/try-it-live\" target=\"_blank\"><img src=\"https://cloud.githubusercontent.com/assets/6558790/12195421/58a40618-b5f7-11e5-9ed3-80463874ab14.png\" alt=\"try it live\" width=\"75%\"></a>\n</p>\n<br>\n\n## Applications and plugins\n\nMJML comes with an ecosystem of tools and plugins, check out:\n- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJML is included)\n- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJML is included)\n- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJML needs to be installed separately)\n\nFor more tools, check the [Community](https://mjml.io/community) page.\n\n## Command line interface\n\n> Compiles the file and outputs the HTML generated in `output.html`\n\n```bash\nmjml input.mjml -o output.html\n```\n\nYou can pass optional `arguments` to the CLI and combine them.\n\nargument | description | default value\n---------|--------|--------------\n`mjml -m [input]` | Migrates a v3 MJML file to the v4 syntax | NA\n`mjml [input] -o [output]` | Writes the output to [output] | NA\n`mjml [input] -s` | Writes the output to `stdout` | NA\n`mjml -w [input]` | Watches the changes made to `[input]` (file or folder) | NA\n`mjml [input] --config.beautify` | Beautifies the output (`true` or `false`) | true\n`mjml [input] --config.minify` | Minifies the output (`true` or `false`) | false\n\nSee [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more information about config options.\n\n## Inside Node.js\n\n```javascript\nimport mjml2html from 'mjml'\n\n/*\n  Compile an mjml string\n*/\nconst htmlOutput = mjml2html(`\n  <mjml>\n    <mj-body>\n      <mj-section>\n        <mj-column>\n          <mj-text>\n            Hello World!\n          </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-body>\n  </mjml>\n`, options)\n\n\n/*\n  Print the responsive HTML generated and MJML errors if any\n*/\nconsole.log(htmlOutput)\n```\n\nYou can pass optional `options` as an object to the `mjml2html` function:\n\noption   | unit   | description  | default value\n-------------|--------|--------------|---------------\nfonts  | object | Default fonts imported in the HTML rendered by MJML | See in [index.js](https://github.com/mjmlio/mjml/blob/master/packages/mjml-core/src/index.js#L100-L108)\nkeepComments | boolean | Option to keep comments in the HTML output | true\nignoreIncludes | boolean | Option to ignore mj-includes | false\nbeautify | boolean | Option to beautify the HTML output | false\nminify | boolean | Option to minify the HTML output | false\nvalidationLevel | string | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): 'strict', 'soft', 'skip'  | 'soft'\nfilePath | string | Path of file, used for relative paths in mj-includes | '.'\npreprocessors | array of functions | Preprocessors applied to the xml before parsing. Input must be xml, not json. Functions must be (xml: string) => string | []\njuicePreserveTags | Preserve some tags when inlining css, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info | NA\nminifyOptions | Options for html minifier, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info | NA\nmjmlConfigPath | string | The path or directory of the `.mjmlconfig` file (for custom components use) | `process.cwd()`\nuseMjmlConfigOptions | Allows to use the `options` attribute from `.mjmlconfig` file | false\n\n## Client-side (in browser)\n\n```javascript\nvar mjml2html = require('mjml-browser')\n\n/*\n  Compile a mjml string\n*/\nvar htmlOutput = mjml2html(`\n  <mjml>\n    <mj-body>\n      <mj-section>\n        <mj-column>\n          <mj-text>\n            Hello World!\n          </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-body>\n  </mjml>\n`, options)\n\n\n/*\n  Print the responsive HTML generated and MJML errors if any\n*/\nconsole.log(htmlOutput)\n```\n\n## API\n\nA free-to-use MJML API is available to make it easy to integrate MJML in your application. Head over [here](https://mjml.io/api) to learn more about the API.\n\n# MJML Slack\n\nMJML wouldn't be as cool without its amazing community. Head over the [Community Slack](https://join.slack.com/t/mjml/shared_invite/zt-gqmwfwmr-kPBnfuuB7wof5httaTcXxg) to meet fellow MJML'ers.\n\n# Contributors\n\n- [Maxime](https://github.com/iRyusa)\n- [Nicolas](https://github.com/ngarnier)\n- [Cedric](https://github.com/kmcb777)\n- [Loeck](https://github.com/lohek)\n- [Robin](https://github.com/robink)\n- [Guillaume](https://github.com/GuillaumeBadi)\n- [Meriadec](https://github.com/meriadec)\n- [Arnaud](https://github.com/arnaudbreton)\n- [HTeuMeuLeu](https://github.com/hteumeuleu)\n- [Emmanuel Payet](https://github.com/epayet)\n- [Matthieu](https://github.com/swibge)\n- [Rogier](https://github.com/rogierslag)\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  presets: [['@babel/env', {\n    targets: { node: '10' },\n    include: ['transform-classes'],\n  }]],\n  plugins: [\n    '@babel/proposal-class-properties',\n    [\n      '@babel/transform-runtime',\n      {\n        // by default the plugin assumes we have 7.0.0-beta.0 version of runtime\n        // and inline all missing helpers instead of requiring them\n        version: require('@babel/plugin-transform-runtime/package.json')\n          .version,\n      },\n    ],\n    'add-module-exports',\n    'lodash',\n  ],\n}\n"
  },
  {
    "path": "doc/basic.md",
    "content": "\n## Basic layout example\n\nIn this section, you're going to learn how to code a basic email template using MJML.\n\nHere‘s what we’re building:\n\n<figure>\n  <img width=\"350px\" src=\"https://static.mailjet.com/mjml-website/documentation/basic-layout-example.png\" alt=\"Basic email layout\" />\n</figure>\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/templates/basic\">Try it live</a></p>\n\n### Sections\n\n```html\n<mjml>\n  <mj-body>\n    <!-- Company Header -->\n    <mj-section background-color=\"#f0f0f0\"></mj-section>\n\n    <!-- Image Header -->\n    <mj-section background-color=\"#f0f0f0\"></mj-section>\n\n    <!-- Introduction Text -->\n    <mj-section background-color=\"#fafafa\"></mj-section>\n\n    <!-- 2 columns section -->\n    <mj-section background-color=\"white\"></mj-section>\n\n    <!-- Icons -->\n    <mj-section background-color=\"#fbfbfb\"></mj-section>\n\n    <!-- Social icons -->\n    <mj-section background-color=\"#f0f0f0\"></mj-section>\n  </mj-body>\n</mjml>\n```\n\nFirst, we’ll create the basic structure, dividing the email into six sections.\n\n#### Company Header\n\n```html\n<!-- Company Header -->\n<mj-section background-color=\"#f0f0f0\">\n  <mj-column>\n    <mj-text\n      align=\"center\"\n      font-style=\"italic\"\n      font-size=\"20px\"\n      color=\"#626262\"\n    >\n      My Company\n    </mj-text>\n  </mj-column>\n</mj-section>\n```\n\nThe first section of the email consists in a centered banner, containing only the company name. The following markup is the MJML representation of the layout we want to obtain.\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>Remember everything has to be contained within the column.</p>\n</div>\n\n#### Image Header\n\n```html\n<!-- Image Header -->\n<mj-section\n  background-url=\"https://1.bp.blogspot.com/-TPrfhxbYpDY/Uh3Refzk02I/AAAAAAAALw8/5sUJ0UUGYuw/s1600/New+York+in+The+1960's+-+70's+(2).jpg\"\n  background-size=\"cover\"\n  background-repeat=\"no-repeat\"\n>\n  <mj-column width=\"600px\">\n    <mj-text\n      align=\"center\"\n      color=\"#fff\"\n      font-size=\"40px\"\n      font-family=\"Helvetica Neue\"\n    >\n      Slogan here\n    </mj-text>\n\n    <mj-button background-color=\"#F63A4D\" href=\"#\"> Promotion </mj-button>\n  </mj-column>\n</mj-section>\n```\n\nNext comes a section with a background image and a block of text (representing the company slogan) and a button pointing to a page listing all the company promotions.\n\nTo add the image header, you will have to replace the section's `background-color` with a `background-url`.\n\nSimilarly to the first company header, you will have to center the text.\n\nThe button `href` sets where the button links to.\n\nIn order to have the background rendered full-width in the column, set the column width to 600px with `width=\"600px\"`.\n\n#### Introduction Text\n\n```html\n<!-- Intro text -->\n<mj-section background-color=\"#fafafa\">\n  <mj-column width=\"400px\">\n    <mj-text\n      font-style=\"italic\"\n      font-size=\"20px\"\n      font-family=\"Helvetica Neue\"\n      color=\"#626262\"\n    >\n      My Awesome Text\n    </mj-text>\n\n    <mj-text color=\"#525252\">\n      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin rutrum enim\n      eget magna efficitur, eu semper augue semper. Aliquam erat volutpat. Cras\n      id dui lectus. Vestibulum sed finibus lectus, sit amet suscipit nibh.\n      Proin nec commodo purus. Sed eget nulla elit. Nulla aliquet mollis\n      faucibus.\n    </mj-text>\n\n    <mj-button background-color=\"#F45E43\" href=\"#\">Learn more</mj-button>\n  </mj-column>\n</mj-section>\n```\n\nThe introduction text will consist of a heading, the main text and a button.\nThe title is a regular `mj-text` tag that can be styled as a heading.\n\n#### 2 Columns Section\n\n```html\n<!-- Side image -->\n<mj-section background-color=\"white\">\n  <!-- Left image -->\n  <mj-column>\n    <mj-image\n      width=\"200px\"\n      src=\"https://designspell.files.wordpress.com/2012/01/sciolino-paris-bw.jpg\"\n    />\n  </mj-column>\n\n  <!-- right paragraph -->\n  <mj-column>\n    <mj-text\n      font-style=\"italic\"\n      font-size=\"20px\"\n      font-family=\"Helvetica Neue\"\n      color=\"#626262\"\n    >\n      Find amazing places\n    </mj-text>\n\n    <mj-text color=\"#525252\">\n      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin rutrum enim\n      eget magna efficitur, eu semper augue semper. Aliquam erat volutpat. Cras\n      id dui lectus. Vestibulum sed finibus lectus.</mj-text\n    >\n  </mj-column>\n</mj-section>\n```\n\nThis section is made up of two columns. One containing an image, the other containing text.\n\nFor the image, note that when a tag does not have any children, you can use the XML self-closing tag syntax: `<mj-image />`\n\nFor the text, you are going to use two `mj-text` tags like, as previously; one with a heading style, and the other one styled as regular text.\n\n#### Icons\n\n```html\n<!-- Icons -->\n<mj-section background-color=\"#fbfbfb\">\n  <mj-column>\n    <mj-image\n      padding=\"10px\"\n      width=\"100px\"\n      src=\"https://191n.mj.am/img/191n/3s/x0l.png\"\n    />\n  </mj-column>\n  <mj-column>\n    <mj-image\n      padding=\"10px\"\n      width=\"100px\"\n      src=\"https://191n.mj.am/img/191n/3s/x01.png\"\n    />\n  </mj-column>\n  <mj-column>\n    <mj-image\n      padding=\"10px\"\n      width=\"100px\"\n      src=\"https://191n.mj.am/img/191n/3s/x0s.png\"\n    />\n  </mj-column>\n</mj-section>\n```\nThis section uses a 3-column layout to display the 3 icons horizontally across the email.\n\n\n#### Social Icons\n\n```html\n<mj-section background-color=\"#e7e7e7\">\n  <mj-column>\n    <mj-social>\n      <mj-social-element name=\"facebook\">Share</mj-social-element>\n    </mj-social>\n  </mj-column>\n</mj-section>\n```\n\nMJML has an `mj-social` component as standard. Here, we're going to use `facebook` only, but there are several default social media sites to choose from, or you can add your own bespoke.\n"
  },
  {
    "path": "doc/body_components.md",
    "content": "## Standard Body components\n\nBody components ease your development process by providing ready made responsive layouts that you can use to create your email template.\n"
  },
  {
    "path": "doc/community-components.md",
    "content": "## Community components\n\nIn addition to the standard components available in MJML, our awesome community is contributing by creating their own components.\n\nTo use a community component, proceed as follows:\n\n- Install MJML locally with `npm install mjml` in a folder\n- Install the community component with `npm install {component-name}` in the same folder\n- Create a `.mjmlconfig` file in the same folder with this code:\n\n```json\n{\n  \"packages\": [\"component-name/path-to-js-file\"]\n}\n```\n\nYou can now use the component in an MJML file, for example `index.mjml`, and run MJML locally in your terminal. Ensure that you’re in the folder where you installed MJML and the community component, e.g.: `./node_modules/.bin/mjml index.mjml`.\n"
  },
  {
    "path": "doc/community-contributions.md",
    "content": "## Community Contributions\n\nThe MJML ecosystem has a dedicated community that we count to help make it grow and provide it with even more awesome tools, always aiming to make development with MJML an efficient and fun process!\n\nGetting involved is really easy. If you want to contribute, feel free to [open an issue](https://github.com/mjmlio/mjml/issues) or [submit a pull-request](https://github.com/mjmlio/mjml/pulls)!\n\nHere are some tools that utilise MJML:\n\n### Mailjet\n\n[Mailjet](https://www.mailjet.com/demo/) offers an integrated MJML workspace designed for creating, previewing, and managing email templates. Its MJML editor supports syntax highlighting, live preview, and validation, helping you move quickly while keeping your markup in good shape.\n\nThe drag-and-drop editor in Mailjet is also built on MJML, so visually created templates share the same responsive structure as those coded by hand.\n\nWhen your template is ready, you can export it in MJML or HTML, or send emails directly through Mailjet.\n\n### Parcel\n\n[Parcel](https://parcel.io) is the code editor built for email. This feature packed tool includes syntax highlighting, Emmet, inline documentation, autocomplete, live preview, screenshots, and full MJML, CSS, and HTML validation.\n\nUse Focus Mode to keep the preview aligned with the code you're working on, or Inspect Element to easily find the code that produces specific elements in the preview.\n\nExport MJML to HTML with a click.\n\n### IntelliJ IDEA Plugin - MJML Support\n\n[IntelliJ IDEA](https://www.jetbrains.com/idea/) is an IDE developed by JetBrains. The plugin provides you with a (near) realtime preview, auto complete, inline documentation and code analysis.\n\nIt is available on the [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/16418-mjml-support).\n\n### Gradle Plugin - MJML Compilation\n\n[Gradle](https://gradle.org/) is a build tool for a various set of languages and environments. The plugin provides an easy way to embed your MJML templates to your Java/Kotlin application in its resources in precompiled form (HTML).\n\nIt is available through the gradle plugin system [io.freefair.mjml.java](https://plugins.gradle.org/plugin/io.freefair.mjml.java) and documentation is available here [FreeFair User Guide](https://docs.freefair.io/gradle-plugins/current/reference/).\n\n### Neos CMS\n\n[Neos CMS](https://www.neos.io/) is a content management system that combines structured content with application. This package adds the helper for compiling MJML markup as well as some prototypes which allow you to use TailwindCSS like classes in your MJML markup.\n\nIt is available on [packagist](https://packagist.org/packages/garagist/mjml).\n\n### Easy-email\n\n[Easy-email](https://github.com/zalify/easy-email) drag-and-drop email editor based on MJML. Transform structured JSON data into HTML that’s compatible with major email clients.\n\n### Email Love\n\nThe [Email Love Figma plugin](https://www.figma.com/community/plugin/1387891288648822744/email-love-html-email-builder) takes the headache out of the email development process by enabling you to export responsive, production-ready email HTML or MJML directly from Figma.\n"
  },
  {
    "path": "doc/components_1.md",
    "content": "## Components\n\nComponents are the core of MJML. A component is an abstraction of a more complex responsive HTML layout. It exposes attributes, enabling you to create bespoke styling.\n\nMJML comes out of the box with a set of standard components to help you easily build your first templates without having to reinvent the wheel.\n\nFor instance, the `mj-button` component is, on the inside, a complex HTML layout:\n\n```html\n<!-- MJML -->\n<mj-button href=\"#\"> Hello There! </mj-button>\n\n<!-- HTML -->\n<table\n  cellpadding=\"0\"\n  cellspacing=\"0\"\n  style=\"border:none;border-radius:3px;\"\n  align=\"center\"\n>\n  <tbody>\n    <tr>\n      <td\n        style=\"background-color:#414141;border-radius:3px;color:#ffffff;cursor:auto;\"\n        align=\"center\"\n        valign=\"middle\"\n        bgcolor=\"#414141\"\n      >\n        <a\n          class=\"mj-content\"\n          href=\"#\"\n          style=\"display:inline-block;text-decoration:none;background-color:#414141;border:1px solid #414141;border-radius:3px;color:#ffffff;font-size:13px;font-weight:bold;padding:15px 30px;\"\n          target=\"_blank\"\n        >\n          Hello There!\n        </a>\n      </td>\n    </tr>\n  </tbody>\n</table>\n```\n\n### Which email clients/versions are supported?\n\nFor full details of component support, [please visit our support matrix](https://mjml.io/compatibility).\n\n### mjml\n\nAn MJML document starts with an `mjml` tag. It can contain only `mj-head` and `mj-body` tags. Both have the same purpose of `head` and `body` in a HTML document.\n\n#### Attributes\n\n| attribute | accepts | description                                                                                                                                                                                              | default value |\n| --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |\n| owa       | string  | if set to `desktop`, this will force the desktop version for older (self-hosted) versions of Outlook.com that don't support media queries (cf. [this issue](https://github.com/mjmlio/mjml/issues/2241)) | `none`        |\n| lang      | string  | adds a `lang` attribute in the `html` and `body > div` tags                                                                                                                                              | `und`         |\n| dir       | string  | adds a `dir` attribute in the `html` and `body > div` tags                                                                                                                                               | `auto`        |\n\n### mj-head\n\nContains components related to the document head such as style and meta elements (see [head components](#standard-head-components)).\n"
  },
  {
    "path": "doc/components_2.md",
    "content": "### mj-include\n\nThe `mjml-core` package allows you to include external `.mjml` files\nto build your email template.\n\n```xml\n<!-- header.mjml -->\n<mj-section>\n  <mj-column>\n    <mj-text>This is a header</mj-text>\n  </mj-column>\n</mj-section>\n```\n\nYou can wrap your external `.mjml` files inside the default `mjml > mj-body`\ntags to make it easier to preview outside the main template.\n\n```xml\n<!-- main.mjml -->\n<mjml>\n  <mj-body>\n    <mj-include path=\"./header.mjml\" />\n  </mj-body>\n</mjml>\n```\n\nThe MJML engine will then replace your included files before starting the rendering process.\n\n#### Other file types\n\nYou can include external `.css` files which will be inserted in the same way as using an `mj-style` tag. You need to specify that you're including a CSS file using the attribute `type=\"css\"` attribute.\n\nIf you want the CSS to be inlined, you can use the `css-inline=\"inline\"` attribute.\n\n```xml\n<!-- main.mjml -->\n<mj-include path=\"./styles.css\" type=\"css\" />\n<mj-include path=\"./inline-styles.css\" type=\"css\" css-inline=\"inline\" />\n```\n\nYou can also include external `html` files. They will be inserted the same way as when using an `mj-raw` tag. You need to specify that you're including an HTML file using the attribute `type=\"html\"`.\n\n```xml\n<!-- main.mjml -->\n<mj-include path=\"./partial.html\" type=\"html\" />\n```\n"
  },
  {
    "path": "doc/config.json",
    "content": "[\n  \"mjml/doc/guide.md\",\n  \"mjml/doc/install.md\",\n  \"mjml/doc/getting_started.md\",\n  \"mjml/doc/basic.md\",\n  \"mjml/doc/components_1.md\",\n  \"mjml/packages/mjml-body/README.md\",\n  \"mjml/doc/components_2.md\",\n  \"mjml/doc/head_components.md\",\n  \"mjml/packages/mjml-head-attributes/README.md\",\n  \"mjml/packages/mjml-head-breakpoint/README.md\",\n  \"mjml/packages/mjml-head-font/README.md\",\n  \"mjml/packages/mjml-head-html-attributes/README.md\",\n  \"mjml/packages/mjml-head-preview/README.md\",\n  \"mjml/packages/mjml-head-style/README.md\",\n  \"mjml/packages/mjml-head-title/README.md\",\n  \"mjml/doc/body_components.md\",\n  \"mjml/packages/mjml-accordion/README.md\",\n  \"mjml/packages/mjml-button/README.md\",\n  \"mjml/packages/mjml-carousel/README.md\",\n  \"mjml/packages/mjml-column/README.md\",\n  \"mjml/packages/mjml-divider/README.md\",\n  \"mjml/packages/mjml-group/README.md\",\n  \"mjml/packages/mjml-hero/README.md\",\n  \"mjml/packages/mjml-image/README.md\",\n  \"mjml/packages/mjml-navbar/README.md\",\n  \"mjml/packages/mjml-raw/README.md\",\n  \"mjml/packages/mjml-section/README.md\",\n  \"mjml/packages/mjml-social/README.md\",\n  \"mjml/packages/mjml-spacer/README.md\",\n  \"mjml/packages/mjml-table/README.md\",\n  \"mjml/packages/mjml-text/README.md\",\n  \"mjml/packages/mjml-wrapper/README.md\",\n  \"mjml/doc/ending-tags.md\",\n  \"mjml/doc/community-components.md\",\n  \"mjml/doc/ports.md\",\n  \"mjml/doc/mjml-bar-chart.md\",\n  \"mjml/doc/mjml-chart.md\",\n  \"mjml/doc/mjml-chartjs.md\",\n  \"mjml/doc/mjml-qr-code.md\",\n  \"mjml/doc/mjml-mso-button.md\",\n  \"mjml/packages/mjml-validator/README.md\",\n  \"mjml/doc/create.md\",\n  \"mjml/doc/using_mjml_in_json.md\",\n  \"mjml/doc/tooling.md\",\n  \"mjml/doc/community-contributions.md\"\n]\n"
  },
  {
    "path": "doc/create.md",
    "content": "## Creating a Component\n\nOne of the great advantages of MJML is that it's component-based. Components abstract complex patterns and can easily be reused. In addition to the standard library of components, it is also possible to create your own components!\n\nWe have published [a step-by-step guide](https://medium.com/mjml-making-responsive-email-easy/tutorial-creating-your-own-component-with-mjml-4-1c0e84e97b36) that explains how to create a custom components with MJML. It will introduce you to the [boilerplate repo](https://github.com/mjmlio/mjml-component-boilerplate) hosted on Github, which provides a fast way of getting started with developing your own components.\n"
  },
  {
    "path": "doc/ending-tags.md",
    "content": "### Ending tags\n\nSome MJML components are \"ending tags\". These are mostly the components that will contain text content, like `mj-text` or `mj-button`.\n\nThese components can contain both text and HTML content, which will remain unprocessed by the MJML engine. You cannot use other MJML components.\n\nSince the content is not processed, this means that any text won't be escaped, so if you use characters that are used to define html tags in your text, like `<` or `>`, you should use the encoded characters `&lt;` and `&lt;`.\n\nThere can also be issues if you use the `minify` option, `mj-html-attributes` or an inline `mj-style`, because these require the HTML to be re-parsed internally.\n\nIf you're just using the `minify` option, and need to use the `< >` characters, e.g for a templating language, you can also avoid this problem by wrapping the troublesome content between two `<!-- htmlmin:ignore -->` tags.\n\nHere is the list of all ending tags :\n\n- `mj-accordion-text`\n- `mj-accordion-title`\n- `mj-button`\n- `mj-navbar-link`\n- `mj-raw`\n- `mj-social-element`\n- `mj-text`\n- `mj-table`\n"
  },
  {
    "path": "doc/getting_started.md",
    "content": "## Getting Started\n\nThis is a responsive email:\n\n<figure>\n  <img width=\"300px\" src=\"https://static.mailjet.com/mjml-website/documentation/getting-started-1.png\" alt=\"layout\">\n</figure>\n\nLike a regular HTML template, we can split this one into different parts to fit in a grid.\n\nThe body of your email, represented by the `mj-body` tag contains the entire content of your document:\n\n<figure>\n  <img width=\"300px\" src=\"https://static.mailjet.com/mjml-website/documentation/getting-started-2.png\" alt=\"the body is overlaid with a semi opaque orange color\">\n</figure>\n\nFrom here, you can first define your sections:\n\n<figure>\n  <img width=\"300px\" src=\"https://static.mailjet.com/mjml-website/documentation/getting-started-3.png\" alt=\"each section is overlaid with various semi opaque colors\">\n</figure>\n\nInside any section, there should be columns (even if you need only one column). Columns are what makes MJML responsive.\n\n<figure>\n  <img width=\"300px\" src=\"https://static.mailjet.com/mjml-website/documentation/getting-started-4.png\" alt=\"each column is overlaid with various semi opaque colors\">\n</figure>\n\nBelow, you'll find some basic rules of MJML to keep in mind for later. We'll remind them when useful but better start learning them early on.\n\n### Column sizing\n\n#### Auto sizing\n\nThe default behavior of the MJML translation engine is to divide the section space (600px by default, but it can be changed with the `width` attribute on `mj-body`) in as many columns as you declare.\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>Any MJML component included in a column will have a width equivalent to 100% of this column's width.</p>\n</div>\n\nLet's take the following layout to illustrate this:\n\n```html\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <!-- First column content -->\n      </mj-column>\n      <mj-column>\n        <!-- Second column content -->\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\nSince the first section defines only 2 columns, the engine will translate that in a layout where each column takes 50% of the total space (300px each). If we add a third one, it goes down to 33%, and with a fourth one to 25%.\n\n#### Manual sizing\n\nYou can also manually set the size of your columns, in pixels or percentage, by using the `width` attribute on `mj-column`.\n\nLet's take the following layout to illustrate this:\n\n```html\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column width=\"200px\">\n        <!-- First column content -->\n      </mj-column>\n      <mj-column width=\"400px\">\n        <!-- Second column content -->\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n"
  },
  {
    "path": "doc/guide.md",
    "content": "---\ntitle: API Reference\n\nlanguage_tabs:\n  - html: MJML\n\ntoc_footers:\n  - <a href='https://github.com/mjmlio/mjml'>Fork me on Github</a>\n  - <a href='https://github.com/mjmlio/mjml/issues'>Submit an Issue</a>\n\nsearch: true\n---\n\n# MJML Guide\n\nMJML (Mailjet Markup Language) is a markup language designed to reduce the pain of coding a responsive email.\n\nIts semantic syntax makes it easy and straightforward whilst its rich standard components library speeds up your development time and lightens your email codebase.\n\nMJML’s open-source engine generates high quality responsive HTML compliant with best practices.\n\n## Overview\n\nMJML rolls up all of what Mailjet has learned about HTML email design and abstracts the whole layer of complexity related to responsive email design.\n\nGet your speed and productivity boosted with MJML’s semantic syntax. Say goodbye to endless HTML table nesting or email client specific CSS. Building a responsive email is super easy with tags such as `<mj-section>` and `<mj-column>`.\n\nMJML has been designed with responsiveness in mind. The abstraction it offers guarantee that you will always be up-to-date with the industry practices.\n\nEmail clients update their specs and requirements regularly, but we geek about that stuff - we’ll stay on top of it so you can spend less time reading up on latest email client updates and more time designing beautiful emails.\n\n```html\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-image\n          width=\"100px\"\n          src=\"https://mjml.io/assets/img/logo-small.png\"\n        ></mj-image>\n        <mj-divider border-color=\"#F45E43\"></mj-divider>\n        <mj-text font-size=\"20px\" color=\"#F45E43\" font-family=\"helvetica\"\n          >Hello World</mj-text\n        >\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/intro\">Try it live</a></p>\n"
  },
  {
    "path": "doc/head_components.md",
    "content": "## Standard Head components\n\nHead components ease your development process, for example, enabling you to import fonts, define default styles or create classes for MJML components.\n"
  },
  {
    "path": "doc/install.md",
    "content": "## Installation\n\nYou can [install MJML](https://www.npmjs.com/package/mjml) with NPM to use it with NodeJS or the Command Line Interface. If you're not sure what those are,\n[head over to Usage](#usage) for other ways to use MJML.\n\n```bash\nnpm install mjml\n```\n\n## Development\n\nTo work on MJML, make changes and create merge requests, [download and\ninstall yarn](https://yarnpkg.com/lang/en/docs/install/) for easy development.\n\n```bash\ngit clone https://github.com/mjmlio/mjml.git && cd mjml\nyarn\nyarn build\n```\n\nYou can also run `yarn build:watch` to rebuild the package as you code.\n\n## Usage\n\n### Online\n\nDon't want to install anything? [Use the free online editor](https://mjml.io/try-it-live)!\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/usage-online.png\" alt=\"try it live\" width=\"75%\">\n</figure>\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live\">Try it live</a></p>\n\n### Applications and plugins\n\nMJML comes with an ecosystem of tools and plugins, check out:\n\n- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJML is included)\n- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJML is included)\n- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJML needs to be installed separately)\n\nFor more information, [check the Tooling section](#tooling).  \nFor more tools, [check the Community page](https://mjml.io/community).\n\n### Command line interface\n\n> Compiles the file and outputs the HTML generated in `output.html`\n\n```bash\nmjml input.mjml -o output.html\n```\n\nYou can pass optional `arguments` to the CLI and combine them.\n\n| argument                                                | description                                                                                                                                              | default value                                                                      |\n| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| `mjml -m [input]`                                       | Migrates a v3 MJML file to the v4 syntax                                                                                                                 |                                                                                    |\n| `mjml [input] -o [output]`                              | Writes the output to [output]                                                                                                                            |                                                                                    |\n| `mjml [input] -s`                                       | Writes the output to `stdout`                                                                                                                            |                                                                                    |\n| `mjml [input] -s --noStdoutFileComment`                 | Writes the output to `stdout` without an comment containing the source file in the first line                                                            | the outputs first line contains the file in the format `<!-- FILE: {filename} -->` |\n| `mjml -w [input]`                                       | Watches the changes made to `[input]` (file or folder)                                                                                                   |                                                                                    |\n| `mjml [input] --config.beautify`                        | Beautifies the output (`true` or `false`)                                                                                                                | `true`                                                                             |\n| `mjml [input] --config.minify`                          | Minifies the output (`true` or `false`)                                                                                                                  | `false`                                                                            |\n| `mjml [input] --config.juicePreserveTags`               | Preserve some tags when inlining css, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info |                                                                                    |\n| `mjml [input] --config.minifyOptions`                   | Options for html minifier, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info            |                                                                                    |\n| `mjml [input] --config.mjmlConfigPath [mjmlconfigPath]` | Uses the `.mjmlconfig` file in the specified path or directory to include custom components                                                              | _The `.mjmlconfig` file in the current working directory, if any_                  |\n| `mjml [input] --config.useMjmlConfigOptions`            | Allows to use the `options` attribute from `.mjmlconfig` file                                                                                            | `false`                                                                            |\n| `mjml [input] --config.validationLevel`                 | [Validation level](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): `strict`, `soft` or `skip`                       | `soft`                                                                             |\n\n### Inside Node.js\n\n```javascript\nimport mjml2html from 'mjml'\n\n/*\n  Compile an mjml string\n*/\nconst htmlOutput = mjml2html(\n  `\n  <mjml>\n    <mj-body>\n      <mj-section>\n        <mj-column>\n          <mj-text>\n            Hello World!\n          </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-body>\n  </mjml>\n`,\n  options,\n)\n\n/*\n  Print the responsive HTML generated and MJML errors if any\n*/\nconsole.log(htmlOutput)\n```\n\nYou can pass optional `options` as an object to the `mjml2html` function:\n\n| option               | accepts | description                                                                                                                                            | default value                                                                                                                                                                                                                                                                                                                                                                     |\n| -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| fonts                | object  | Default fonts imported in the HTML rendered by MJML                                                                                                    | See in [index.js](https://github.com/mjmlio/mjml/blob/master/packages/mjml-core/src/index.js#L100-L108)                                                                                                                                                                                                                                                                           |\n| keepComments         | boolean | Option to keep comments in the HTML output                                                                                                             | `true`                                                                                                                                                                                                                                                                                                                                                                            |\n| beautify             | boolean | Option to beautify the HTML output                                                                                                                     | `false`                                                                                                                                                                                                                                                                                                                                                                           |\n| minify               | boolean | Option to minify the HTML output                                                                                                                       | `false`                                                                                                                                                                                                                                                                                                                                                                           |\n| validationLevel      | string  | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): `strict` `soft` `skip`       | `soft`                                                                                                                                                                                                                                                                                                                                                                            |\n| filePath             | string  | Full path of the specified file to use when resolving paths from [`mj-include` components](#mj-include)                                                | `.`                                                                                                                                                                                                                                                                                                                                                                               |\n| mjmlConfigPath       | string  | The path or directory of the [`.mjmlconfig` file](#community-components)                                                                               | `process.cwd()`                                                                                                                                                                                                                                                                                                                                                                   |\n| useMjmlConfigOptions | boolean | Allows to use the `options` attribute from `.mjmlconfig` file                                                                                          | `false`                                                                                                                                                                                                                                                                                                                                                                           |\n| minifyOptions        | object  | Options for HTML minifier, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info          | `{\"collapseWhitespace\": true, \"minifyCss\": false, \"removeEmptyAttributes\": true}` <br><br> `minifyCss` can take a value of `false` or one of the two preset options `lite` or `default`. Within either preset, you can specify specific options from cssnano, for example `minifyCss: { options: { preset: [ 'default', { minifyFontValues: { removeQuotes: false }, }, ], }, },` |\n| juicePreserveTags    | boolean | Optional setting when inlining CSS, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info |                                                                                                                                                                                                                                                                                                                                                                                   |\n\n### API\n\nA free-to-use MJML API is available to make it easy to integrate MJML in your application. Head\nover here to [learn more about the API](https://mjml.io/api).\n"
  },
  {
    "path": "doc/mjml-bar-chart.md",
    "content": "### mj-bar-chart\n\nAn open-source component that allows you to create fully embedded static bar charts in your MJML templates.\n\nThese bar charts are easily customizable, 100% built with HTML tables, and do not require any external dependencies.\n\nThe package is available on [GitHub](https://github.com/Freezystem/mjml-bar-chart) and [NPM](https://www.npmjs.com/package/@freezystem/mjml-bar-chart).\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/bar-chart-example-1.png\" alt=\"Basic mjml-bar-chart rendering\" />\n</figure>\n\nYou can also render as stacked bars, add a link to your sources, vertically align labels, and much more:\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/bar-chart-example-2.png\" alt=\"Stacked rendering with vertically aligned legends\" />\n</figure>\n\nThis component is lightweight, written with TypeScript, and is thoroughly tested.\n\nIt is available as UMD (CJS + AMD), ESM and TS module format."
  },
  {
    "path": "doc/mjml-chart.md",
    "content": "### mj-chart\n\nDisplays charts as images in your email.\n\nThanks to [image-charts](https://image-charts.com/) for their contribution with this component. It's available on [Github](https://github.com/image-charts/mjml-charts) and [NPM](https://www.npmjs.com/package/mjml-chart).\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/chart-example.png\" alt=\"mj-chart demo\" />\n</figure>\n"
  },
  {
    "path": "doc/mjml-chartjs.md",
    "content": "### mj-chartjs\n\nDisplays [Chart.js](https://www.chartjs.org/) charts as images in your email. Chart.js is an open-source Javascript charting library.\n\nIt’s available on [Github](https://github.com/typpo/mjml-chartjs) and [NPM](https://www.npmjs.com/package/mjml-chartjs). By default, it uses the open-source [QuickChart](https://quickchart.io/) API for chart rendering.\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/chartjs-example.webp\" alt=\"mj-chartjs demo\" />\n</figure>\n"
  },
  {
    "path": "doc/mjml-mso-button.md",
    "content": "### mjml-msobutton\n\nA button that uses the [VML](https://docs.microsoft.com/en-us/windows/win32/vml/shape-element--vml) solution for radius, which is supported in Outlook desktop\n\nIt uses the same attributes as the standard `mj-button` but includes three additional ones:\n\n| attribute  | accepts | description                    | default value |\n| ---------- | ------- | ------------------------------ | ------------- |\n| mso-proof  | boolean | Active the bulletproof mode    | `false`       |\n| mso-width  | `px`    | The width of the VML solution  | `200px`       |\n| mso-height | `px`    | The height of the VML solution | `40px`        |\n\nThese new attributes allow MJML to generate a “bulletproof button“ which incorporates radius, stroke and alignment, [using this method](https://buttons.cm/),\n\nIt's available on [Github](https://github.com/adrien-zinger/mjml-mso-button) and [NPM](https://www.npmjs.com/package/mjml-msobutton).\n\n**Usage**\n\nUse it like a standard `mj-button`:\n\n```html\n<mj-msobutton mso-proof=\"true\">Click !</mj-msobutton>\n```\n\n**Problems that you should know**\n\n1. This cannot be used with an image in background\n2. It creates a duplication of code in the HTML\n3. The width and the height cannot be used with the auto value.\n\n> Sample project on github [here](https://github.com/adrien-zinger/mjml-msobutton-sample)\n"
  },
  {
    "path": "doc/mjml-qr-code.md",
    "content": "### mj-qr-code\n\nDisplays QR codes in your email. It's available on [Github](https://github.com/typpo/mjml-qr-code) and [NPM](https://www.npmjs.com/package/mjml-qr-code).\n\nBy default, it uses the open-source QuickChart [QR code API](https://quickchart.io/).\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/qr-code-example.webp\" alt=\"mj-qr-code demo\" />\n</figure>\n"
  },
  {
    "path": "doc/ports.md",
    "content": "## Ports and Language Bindings\n\nMJML is also available for other platforms to use. The community has created ports to these and wrappers for the official Node implementation.\n\nNote: These contributions are not directly supported by the MJML team.\n\n### Rust: MRML\n\nThis project is a reimplementation of the nice MJML markup language in Rust.\n\n[https://github.com/jdrouet/mrml](https://github.com/jdrouet/mrml)\n\n#### Missing implementations / components:\n\n- `mj-style set to inline`: not yet implemented. It requires parsing the generated HTML to apply the inline styles afterward (that's how it's done in MJML) which would kill the performance. Applying it at render time would improve the performance but it would still require it to parse the CSS.\n\n### .NET: MJML.NET\n\nA blazingly-fast unofficial port of MJML 4 to .NET 6.\n\n[https://github.com/SebastianStehle/mjml-net](https://github.com/SebastianStehle/mjml-net)\n\n### Elixir: MJML (Rust NIFs for Elixir)\n\nNative Implemented Function (NIF) bindings for the MJML Rust implementation (mrml).\n\n[https://github.com/adoptoposs/mjml_nif](https://github.com/adoptoposs/mjml_nif)\n\n### Ruby: MRML Ruby\n\nRuby wrapper for MRML, the MJML markup language implementation in Rust.\n\n[https://github.com/hardpixel/mrml-ruby](https://github.com/hardpixel/mrml-ruby)\n\n### React: mjml-react\n\nReact components for MJML components.\n\n[https://github.com/faire/mjml-react#readme](https://github.com/faire/mjml-react#readme)\n\n### Python: mjml-python\n\nPython wrapper for MRML, the MJML markup language implementation in Rust.\n\n[https://github.com/mgd020/mjml-python](https://github.com/mgd020/mjml-python)\n\n### Python: mjml-python\n\nPython implementation for MJML.\n\n[https://github.com/FelixSchwarz/mjml-python](https://github.com/FelixSchwarz/mjml-python)\n\n### Python / Django: django-mjml\n\nThe simplest way to use MJML in Django templates.\n\n[https://github.com/liminspace/django-mjml](https://github.com/liminspace/django-mjml)\n\n### PHP / Laravel: Laravel MJML\n\nBuild responsive e-mails easily using MJML and Laravel Mailables.\n\n- [https://github.com/EvanSchleret/lara-mjml](https://github.com/EvanSchleret/lara-mjml)\n- [https://github.com/asahasrabuddhe/laravel-mjml](https://github.com/asahasrabuddhe/laravel-mjml) (not maintained)\n"
  },
  {
    "path": "doc/tooling.md",
    "content": "## Tooling\n\nIn order to provide you with the best and most efficient experience using MJML, we've developed some tools to integrate it seamlessly into your development workflow:\n\n### Visual Studio Code\n\n[Visual Studio Code](https://code.visualstudio.com/) is a free code editor made by [Microsoft](https://www.microsoft.com/). We recommend this package as it is among the most feature-rich MJML plugins for code editors; with live previews, syntax highlighting and linting, as well as export features including HTML and screenshots.\n\nIt is available [on Github](https://github.com/mjmlio/vscode-mjml) and through the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=mjmlio.vscode-mjml).\n\n### Sublime Text\n\n[Sublime Text](https://www.sublimetext.com/) is a powerful text editor. We’ve provided you with a package to color MJML tags.\n\nIt is available [on Github](https://github.com/mjmlio/mjml-syntax) and through the [Sublime Package Control](https://packagecontrol.io/packages/MJML-syntax).\n\n### Gulp\n\n[Gulp](https://gulpjs.com/) is a tool designed to help you automate and enhance your workflow. Our plugin enables you to plug the MJML translation engine into your workflow, helping you to streamline your development workflow.\n\nIt is available here on [Github](https://github.com/mjmlio/gulp-mjml).\n"
  },
  {
    "path": "doc/using_mjml_in_json.md",
    "content": "## Using MJML in JSON\n\nMJML can not only be used as a markup, but also as a JSON object, very useful for\nprogrammatic manipulation or with the MJML API.\n\nWith the JSON format, a MJML component is defined as an `object` with the following properties:\n\n- a `tagName` as a `string`\n- a list of attributes as an `object`\n- either a `content` as a `string` or a list of `children` tags as an `array`.\n\nExactly like using MJML as a markup, the JSON definition can be passed as an object to the `mjml2html` function.\nHere is working example:\n\n```javascript\nvar mjml2html = require('mjml')\n\nconsole.log(\n  mjml2html({\n    tagName: 'mjml',\n    attributes: {},\n    children: [\n      {\n        tagName: 'mj-body',\n        attributes: {},\n        children: [\n          {\n            tagName: 'mj-section',\n            attributes: {},\n            children: [\n              {\n                tagName: 'mj-column',\n                attributes: {},\n                children: [\n                  {\n                    tagName: 'mj-image',\n                    attributes: {\n                      width: '100px',\n                      src: '/assets/img/logo-small.png',\n                    },\n                  },\n                  {\n                    tagName: 'mj-divider',\n                    attributes: {\n                      'border-color': '#F46E43',\n                    },\n                  },\n                  {\n                    tagName: 'mj-text',\n                    attributes: {\n                      'font-size': '20px',\n                      color: '#F45E43',\n                      'font-family': 'Helvetica',\n                    },\n                    content: 'Hello World',\n                  },\n                ],\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  }),\n)\n```\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"packages\": [\n    \"packages/*\"\n  ],\n  \"command\": {\n    \"publish\": {\n      \"exact\": true\n    }\n  },\n  \"npmClient\": \"yarn\",\n  \"useWorkspaces\": true,\n  \"version\": \"4.18.0\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mjml-master\",\n  \"private\": true,\n  \"scripts\": {\n    \"build:watch\": \"lerna run build --parallel -- -- -w\",\n    \"build\": \"lerna run build --parallel --ignore mjml-browser\",\n    \"build-browser\": \"cd packages/mjml-browser && yarn build\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"postinstall\": \"lerna bootstrap\",\n    \"prettier\": \"prettier --write \\\"packages/**/{src,bin}/**/*.?(js|json)\\\"\",\n    \"test\": \"lerna run test\"\n  },\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.28.4\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n    \"@babel/plugin-transform-runtime\": \"^7.28.3\",\n    \"@babel/preset-env\": \"^7.28.3\",\n    \"@babel/register\": \"^7.28.3\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"babel-plugin-add-module-exports\": \"^1.0.2\",\n    \"babel-plugin-lodash\": \"^3.3.4\",\n    \"eslint\": \"^6.8.0\",\n    \"eslint-config-airbnb-base\": \"^14.1.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-import\": \"^2.21.1\",\n    \"lerna\": \"^3.22.1\",\n    \"mocha\": \"10\",\n    \"open\": \"^7.3.0\",\n    \"prettier\": \"^3.2.4\",\n    \"rimraf\": \"^3.0.2\"\n  },\n  \"resolutions\": {\n    \"@babel/runtime\": \"7.28.4\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml/README.md",
    "content": "# MJML 4\n\n<p style=\"text-align: center;\" >\n  <a href=\"http://mjml.io\" target=\"_blank\">\n    <img width=\"250\"src=\"https://mjml.io/assets/img/litmus/mjmlbymailjet.png\">\n\n  </a>\n</p>\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://github.com/mjmlio/mjml/actions\">\n    <img src=\"https://github.com/mjmlio/mjml/workflows/Mjml%20CI/badge.svg?branch=master\" alt=\"github actions\">\n  </a>\n  <a href=\"https://www.codacy.com/app/gbadi/mjml\">\n    <img src=\"https://api.codacy.com/project/badge/grade/575339cb861f4ff4b0dbb3f9e1759c35\"/>\n  </a>\n</p>\n\n\n<p style=\"text-align: center;\" >\n  | <b><a href=\"#introduction\">Introduction</a></b>\n  | <b><a href=\"#installation\">Installation</a></b>\n  | <b><a href=\"#usage\">Usage</a></b>\n  | <b><a href=\"#contribute\">Contribute</a></b> |\n</p>\n\n---\n\n# Introduction\n\n`MJML` is a markup language created by [Mailjet](https://www.mailjet.com/) and designed to reduce the pain of coding a responsive email. Its semantic syntax makes it easy and straightforward while its rich standard components library fastens your development time and lightens your email codebase. MJML’s open-source engine takes care of translating the `MJML` you wrote into responsive HTML.\n\n<p style=\"text-align: center;\" >\n  <a href=\"http://mjml.io\" target=\"_blank\">\n    <img width=\"75%\" src=\"https://cloud.githubusercontent.com/assets/6558790/12450760/ee034178-bf85-11e5-9dda-98d0c8f9f8d6.png\">\n  </a>\n</p>\n\n\n# Installation\n\nYou can install `MJML` with `NPM` to use it with NodeJS or the Command Line Interface. If you're not sure what those are, head over to <a href=\"#usage\">Usage</a> for other ways to use MJML.\n\n```bash\nnpm install mjml\n```\n\n# Usage\n\n## Online\n\nDon't want to install anything? Use the free online editor!\n\n<p style=\"text-align: center;\" >\n  <a href=\"http://mjml.io/try-it-live\" target=\"_blank\"><img src=\"https://cloud.githubusercontent.com/assets/6558790/12195421/58a40618-b5f7-11e5-9ed3-80463874ab14.png\" alt=\"try it live\" width=\"75%\"></a>\n</p>\n<br>\n\n## Applications and plugins\n\nMJML comes with an ecosystem of tools and plugins, check out:\n- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJML is included)\n- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJML is included)\n- [Atom plugin](https://atom.io/users/mjmlio) (MJML needs to be installed separately)\n- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJML needs to be installed separately)\n\nFor more tools, check the [Community](https://mjml.io/community) page.\n\n## Command line interface\n\n> Compiles the file and outputs the HTML generated in `output.html`\n\n```bash\nmjml input.mjml -o output.html\n```\n\nYou can pass optional `arguments` to the CLI and combine them.\n\nargument | description | default value\n---------|--------|--------------\n`mjml -m [input]` | Migrates a v3 MJML file to the v4 syntax | NA\n`mjml [input] -o [output]` | Writes the output to [output] | NA\n`mjml [input] -s` | Writes the output to `stdout` | NA\n`mjml -w [input]` | Watches the changes made to `[input]` (file or folder) | NA\n`mjml [input] --config.beautify` | Beautifies the output (`true` or `false`) | true\n`mjml [input] --config.minify` | Minifies the output (`true` or `false`) | false\n\nSee [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more information about config options.\n\n## Inside Node.js\n\n```javascript\nimport mjml2html from 'mjml'\n\n/*\n  Compile an mjml string\n*/\nconst htmlOutput = mjml2html(`\n  <mjml>\n    <mj-body>\n      <mj-section>\n        <mj-column>\n          <mj-text>\n            Hello World!\n          </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-body>\n  </mjml>\n`, options)\n\n\n/*\n  Print the responsive HTML generated and MJML errors if any\n*/\nconsole.log(htmlOutput)\n```\n\nYou can pass optional `options` as an object to the `mjml2html` function:\n\noption   | unit   | description  | default value\n-------------|--------|--------------|---------------\nfonts  | object | Default fonts imported in the HTML rendered by MJML | See in [index.js](https://github.com/mjmlio/mjml/blob/master/packages/mjml-core/src/index.js#L100-L108)\nkeepComments | boolean | Option to keep comments in the HTML output | true\nignoreIncludes | boolean | Option to ignore mj-includes | false\nbeautify | boolean | Option to beautify the HTML output | false\nminify | boolean | Option to minify the HTML output | false\nvalidationLevel | string | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): 'strict', 'soft', 'skip'  | 'soft'\nfilePath | string | Path of file, used for relative paths in mj-includes | '.'\npreprocessors | array of functions | Preprocessors applied to the xml before parsing. Input must be xml, not json. Functions must be (xml: string) => string | []\njuicePreserveTags | Preserve some tags when inlining css, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info | NA\nminifyOptions | Options for html minifier, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info | NA\nmjmlConfigPath | string | The path or directory of the `.mjmlconfig` file (for custom components use) | `process.cwd()`\nuseMjmlConfigOptions | Allows to use the `options` attribute from `.mjmlconfig` file | false\n\nNote that it's also possible to define preprocessors in your mjmlconfig file. For this, you need to use a `.mjmlconfig.js` file. This js file needs to export an Object with the same structure as a standard JSON .mjmlconfig file.  \n\n## API\n\nA free-to-use MJML API is available to make it easy to integrate MJML in your application. Head over [here](https://mjml.io/api) to learn more about the API.\n\n# MJML Slack\n\nMJML wouldn't be as cool without its amazing community. Head over the [Community Slack](https://join.slack.com/t/mjml/shared_invite/zt-gqmwfwmr-kPBnfuuB7wof5httaTcXxg) to meet fellow MJML'ers.\n"
  },
  {
    "path": "packages/mjml/bin/mjml",
    "content": "#!/usr/bin/env node\n\nrequire('../lib/index')\nrequire('mjml-cli')\n"
  },
  {
    "path": "packages/mjml/package.json",
    "content": "{\n  \"name\": \"mjml\",\n  \"description\": \"MJML: the only framework that makes responsive-email easy\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"bin\": {\n    \"mjml\": \"bin/mjml\"\n  },\n  \"files\": [\n    \"bin\",\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\",\n    \"test\": \"mocha ./test/*.test.js\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"mjml-cli\": \"4.18.0\",\n    \"mjml-core\": \"4.18.0\",\n    \"mjml-migrate\": \"4.18.0\",\n    \"mjml-preset-core\": \"4.18.0\",\n    \"mjml-validator\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"chai\": \"^4.1.1\",\n    \"chai-spies\": \"^1.0.0\",\n    \"cheerio\": \"1.0.0-rc.12\",\n    \"lodash\": \"^4.17.21\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml/src/index.js",
    "content": "import mjml2html, { components, assignComponents } from 'mjml-core'\nimport { dependencies, assignDependencies } from 'mjml-validator'\nimport presetCore from 'mjml-preset-core'\n\nassignComponents(components, presetCore.components)\nassignDependencies(dependencies, presetCore.dependencies)\n\nexport default mjml2html\n"
  },
  {
    "path": "packages/mjml/test/accordion-fontFamily.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-accordion font-family inheritance', function () {\n  it('should render correct font-family in CSS style values on accordion-title and accordion-text', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-accordion css-class=\"my-accordion-1\" font-family=\"serif\">\n              <mj-accordion-element>\n                <mj-accordion-title>Why use an accordion?</mj-accordion-title>\n                <mj-accordion-text>\n                    Because emails with a lot of content are most of the time a very bad experience on mobile, mj-accordion comes handy when you want to deliver a lot of information in a concise way.\n                </mj-accordion-text>\n              </mj-accordion-element>\n            </mj-accordion>\n          </mj-column>\n        </mj-section>\n        <mj-section>\n          <mj-column>\n            <mj-accordion css-class=\"my-accordion-2\" font-family=\"serif\">\n              <mj-accordion-element font-family=\"sans-serif\">\n                <mj-accordion-title font-family=\"monospace\">Why use an accordion?</mj-accordion-title>\n                <mj-accordion-text font-family=\"monospace\">\n                    Because emails with a lot of content are most of the time a very bad experience on mobile, mj-accordion comes handy when you want to deliver a lot of information in a concise way.\n                </mj-accordion-text>\n              </mj-accordion-element>\n            </mj-accordion>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // style values should be correct\n    chai\n      .expect(\n        $(\n          '.my-accordion-1 .mj-accordion-title td:first-child, .my-accordion-1 .mj-accordion-content td:first-child',\n          '.my-accordion-2 .mj-accordion-title td:first-child, .my-accordion-2 .mj-accordion-content td:first-child, ',\n        )\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('font-family:') + 12\n            const end = $(this).attr('style').indexOf(';', start)\n            const result = $(this).attr('style').substring(start, end)\n            return result\n          })\n          .get(),\n        'Font-family in CSS style values on accordion-title',\n      )\n      .to.eql(['serif', 'serif', 'monospace', 'monospace'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/accordion-padding.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-accordion padding-X', function () {\n  it('should render correct padding in CSS style values on accordion-title and accordion-text', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-accordion>\n              <mj-accordion-element>\n                <mj-accordion-title padding=\"20px\" padding-bottom=\"40px\" padding-left=\"40px\" padding-right=\"40px\" padding-top=\"40px\">Why use an accordion?</mj-accordion-title>\n                <mj-accordion-text padding=\"20px\" padding-bottom=\"40px\" padding-left=\"40px\" padding-right=\"40px\" padding-top=\"40px\">\n                    Because emails with a lot of content are most of the time a very bad experience on mobile, mj-accordion comes handy when you want to deliver a lot of information in a concise way.\n                </mj-accordion-text>\n              </mj-accordion-element>\n            </mj-accordion>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n    const $ = load(html)\n\n    function extractPadding(style, prop) {\n      const start = style.indexOf(`${prop}:`) + prop.length + 1\n      const end = style.indexOf(';', start)\n      return style.substring(start, end).trim()\n    }\n\n    const paddings = [\n      'padding-left',\n      'padding-right',\n      'padding-top',\n      'padding-bottom',\n    ]\n    const results = paddings.map((padding) =>\n      $(\n        '.mj-accordion-title td:first-child, .mj-accordion-content td:first-child',\n      )\n        .map(function () {\n          const style = $(this).attr('style')\n          return extractPadding(style, padding)\n        })\n        .get(),\n    )\n\n    // Each padding should be ['40px', '40px']\n    paddings.forEach((padding, idx) => {\n      chai\n        .expect(\n          results[idx],\n          `${padding} in CSS style values on accordion-title and accordion-text`,\n        )\n        .to.eql(['40px', '40px'])\n    })\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/accordionTitle-fontWeight.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-accordion-title font-weight', function () {\n  it('should render correct font-weight in CSS style values on accordion-title', function () {\n    const input = `\n    <mjml>\n      <mj-head>\n        <mj-attributes>\n          <mj-accordion border=\"none\" padding=\"1px\" />\n          <mj-accordion-element icon-wrapped-url=\"https://i.imgur.com/Xvw0vjq.png\" icon-unwrapped-url=\"https://i.imgur.com/KKHenWa.png\" icon-height=\"24px\" icon-width=\"24px\" />\n          <mj-accordion-title font-family=\"Roboto, Open Sans, Helvetica, Arial, sans-serif\" background-color=\"#fff\" color=\"#031017\" padding=\"15px\" font-size=\"18px\" />\n          <mj-accordion-text font-family=\"Open Sans, Helvetica, Arial, sans-serif\" background-color=\"#fafafa\" padding=\"15px\" color=\"#505050\" font-size=\"14px\" />\n        </mj-attributes>\n      </mj-head>\n      <mj-body>\n        <mj-section padding=\"20px\" background-color=\"#ffffff\">\n          <mj-column background-color=\"#dededd\">\n            <mj-accordion>\n              <mj-accordion-element>\n                <mj-accordion-title font-weight=\"bold\" css-class=\"accordion-title\">Why use an accordion?</mj-accordion-title>\n                <mj-accordion-text font-weight=\"bold\">\n                  <span style=\"line-height:20px\">\n                    Because emails with a lot of content are most of the time a very bad experience on mobile, mj-accordion comes handy when you want to deliver a lot of information in a concise way.\n                  </span>\n                </mj-accordion-text>\n              </mj-accordion-element>\n              <mj-accordion-element>\n                <mj-accordion-title font-weight=\"700\" css-class=\"accordion-title\">How it works</mj-accordion-title>\n                <mj-accordion-text font-weight=\"700\">\n                  <span style=\"line-height:20px\">\n                    Content is stacked into tabs and users can expand them at will. If responsive styles are not supported (mostly on desktop clients), tabs are then expanded and your content is readable at once.\n                  </span>\n                </mj-accordion-text>\n              </mj-accordion-element>\n            </mj-accordion>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // style values should be correct\n    chai\n      .expect(\n        $('.accordion-title')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('font-weight:') + 12\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Font-weight in CSS style values on accordion-title',\n      )\n      .to.eql(['bold', '700'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/carousel-hoverSupported.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-carousel-thumbnail thumbnails supported', function () {\n  it('should render correct display in CSS style values on mj-carousel-thumbnail', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-carousel thumbnails=\"supported\">\n              <mj-carousel-image src=\"https://placehold.co/450x300/333/ccc/png\" />\n              <mj-carousel-image src=\"https://placehold.co/450x300/ccc/000/png\" />\n              <mj-carousel-image src=\"https://placehold.co/450x300/f45e43/fff/png\" />\n            </mj-carousel>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // style values should be correct\n    chai\n      .expect(\n        $('.mj-carousel-thumbnail')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('display:') + 8\n            const end = $(this).attr('style').indexOf(';', start)\n            const result = $(this).attr('style').substring(start, end)\n            return result\n          })\n          .get(),\n        'Display CSS style values on mj-carousel-thumbnail',\n      )\n      .to.eql(['none', 'none', 'none'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/column-border-radius.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-column border-radius', function () {\n  it('should render correct border-radius / inner-border-radius (and border-collapse) in CSS style values on mj-column', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column border-radius=\"50px\" inner-border-radius=\"40px\" padding=\"50px\" border=\"5px solid #000\" inner-border=\"5px solid #666\">\n            <mj-text>Hello World</mj-text>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // border radius values should be correct\n    chai\n      .expect(\n        $(\n          '.mj-column-per-100 > table > tbody > tr > td, .mj-column-per-100 > table > tbody > tr > td > table',\n        )\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('border-radius:') + 14\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Border-radius / inner-border-radius in CSS style values on mj-column',\n      )\n      .to.eql(['50px', '40px'])\n\n    // border collapse values should be correct\n    chai\n      .expect(\n        $(\n          '.mj-column-per-100 > table > tbody > tr > td, .mj-column-per-100 > table > tbody > tr > td > table',\n        )\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('border-collapse:') + 16\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Border-collapse in CSS style values on mj-column',\n      )\n      .to.eql(['separate', 'separate'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/html-attributes.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst { sortBy } = require('lodash')\nconst mjml = require('../lib')\n\ndescribe('html-attributes', function () {\n  it('should put the attributes at the right place', function () {\n    const input = `\n<mjml>\n  <mj-head>\n    <mj-html-attributes>\n      <mj-selector path=\".text div\">\n        <mj-html-attribute name=\"data-id\">42</mj-html-attribute>\n      </mj-selector>\n      <mj-selector path=\".image td\">\n        <mj-html-attribute name=\"data-name\">43</mj-html-attribute>\n      </mj-selector>\n    </mj-html-attributes>\n  </mj-head>\n  <mj-body>\n    <mj-raw>{ if item < 5 }</mj-raw>\n    <mj-section css-class=\"section\">\n      <mj-column>\n        <mj-raw>{ if item > 10 }</mj-raw>\n        <mj-text css-class=\"text\">\n          Hello World! { item }\n        </mj-text>\n        <mj-raw>{ end if }</mj-raw>\n        <mj-text css-class=\"text\">\n          Hello World! { item + 1 }\n        </mj-text>\n        <mj-image css-class=\"image\" src=\"https://via.placeholder.com/150x30\"/>\n      </mj-column>\n    </mj-section>\n    <mj-raw>{ end if }</mj-raw>\n  </mj-body>\n</mjml>\n`\n\n    const { html } = mjml(input)\n    const $ = load(html)\n\n    // should put the attributes at the right place\n    chai\n      .expect(\n        $('.text div')\n          .map(function getAttr() {\n            return $(this).attr('data-id')\n          })\n          .get(),\n        'Custom attributes added on texts',\n      )\n      .to.eql(['42', '42'])\n\n    chai\n      .expect(\n        $('.image td')\n          .map(function getAttr() {\n            return $(this).attr('data-name')\n          })\n          .get(),\n        'Custom attributes added on image',\n      )\n      .to.eql(['43'])\n\n    // should not alter templating syntax, or move the content that is outside any tag (mj-raws)\n    const expected = [\n      '{ if item < 5 }',\n      'class=\"section\"',\n      '{ if item > 10 }',\n      'class=\"text\"',\n      '{ item }',\n      '{ end if }',\n      '{ item + 1 }',\n    ]\n    const indexes = expected.map((str) => html.indexOf(str))\n\n    chai.expect(indexes, 'Templating syntax unaltered').to.not.include(-1)\n\n    chai\n      .expect(sortBy(indexes), 'Mj-raws kept same positions')\n      .to.deep.eql(indexes)\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/html-comments.test.js",
    "content": "const chai = require('chai')\nconst mjml = require('../lib')\n\ndescribe('HTML comments', function () {\n  it('should not alter the whitespace between the opening/closing comment tags and the comment content', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-text>\n            <p>View source to see comments below</p>\n            <!-- comment with standard spaces -->\n            <br>\n            <!--comment without spaces-->\n            <br>\n            <!--     comment with 5 spaces     -->\n            </mj-text>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    // should not alter templating syntax, or move the content that is outside any tag (mj-raws)\n    const expected = [\n      '<!-- comment with standard spaces -->',\n      '<!--comment without spaces-->',\n      '<!--     comment with 5 spaces     -->',\n    ]\n    const indexes = expected.map((str) => html.indexOf(str))\n\n    chai.expect(indexes, 'Cmment syntax unaltered').to.not.include(-1)\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/lazy-head-style.test.js",
    "content": "const chai = require('chai')\nconst spies = require('chai-spies')\nconst mjml = require('../lib')\nconst {\n  HeadComponent,\n  registerComponent,\n} = require('../../mjml-core/lib/index')\n\nchai.use(spies)\n\ndescribe('lazy-head-style', function () {\n  it('should call style with correct breakpoint', function () {\n    const addStyle = chai.spy(\n      (breakpoint) => `\n        @media only screen and (max-width:${breakpoint}) {\n          h1 {\n            font-size: 20px;\n          }\n        }\n      `,\n    )\n\n    class HeadComponentWithFunctionStyle extends HeadComponent {\n      handler() {\n        const { add } = this.context\n        add('style', addStyle)\n      }\n    }\n    HeadComponentWithFunctionStyle.componentName =\n      'mj-head-component-with-function-style'\n    HeadComponentWithFunctionStyle.endingTag = true\n    HeadComponentWithFunctionStyle.allowedAttributes = {}\n\n    registerComponent(HeadComponentWithFunctionStyle)\n\n    mjml(`\n    <mjml>\n      <mj-head>\n        <mj-head-component-with-function-style />\n        <mj-breakpoint width=\"300px\" />\n      </mj-head>\n      <mj-body>\n      </mj-body>\n    </mjml>\n    `)\n\n    chai.expect(addStyle).to.have.been.called.with('300px')\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/navbar-ico-padding.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-navbar ico-padding-X', function () {\n  it('should render correct padding in CSS style values on navbar hamburger icon', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-navbar hamburger=\"hamburger\" ico-padding=\"20px\" ico-padding-bottom=\"20px\" ico-padding-left=\"30px\" ico-padding-right=\"40px\"  ico-padding-top=\"50px\" >\n                <mj-navbar-link href=\"/gettings-started-onboard\" color=\"#ffffff\">Getting started</mj-navbar-link>\n                <mj-navbar-link href=\"/try-it-live\" color=\"#ffffff\">Try it live</mj-navbar-link>\n            </mj-navbar>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n    const $ = load(html)\n\n    function extractPadding(style, prop) {\n      const start = style.indexOf(`${prop}:`) + prop.length + 1\n      const end = style.indexOf(';', start)\n      return style.substring(start, end).trim()\n    }\n\n    const paddings = [\n      'padding-bottom',\n      'padding-left',\n      'padding-right',\n      'padding-top',\n    ]\n\n    const results = paddings.map((padding) =>\n      $('.mj-menu-label')\n        .map(function () {\n          const style = $(this).attr('style')\n          return extractPadding(style, padding)\n        })\n        .get(),\n    )\n\n    // Padding should be ['20px', '30px', '40px', '50px']\n    const expected = {\n      'padding-bottom': ['20px'],\n      'padding-left': ['30px'],\n      'padding-right': ['40px'],\n      'padding-top': ['50px'],\n    }\n\n    paddings.forEach((padding, idx) => {\n      chai\n        .expect(results[idx], `${padding} in CSS style values on navbar icon`)\n        .to.eql(expected[padding])\n    })\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/social-align.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-social-element align', function () {\n  it('should render correct align in CSS style values on mj-social-element', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-social mode=\"vertical\">\n              <mj-social-element name=\"facebook\" href=\"https://mjml.io/\" icon-position=\"right\" align=\"right\"css-class=\"my-social-element\">\n                Facebook\n              </mj-social-element>\n            </mj-social>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // align values should be correct\n    chai\n      .expect(\n        $('.my-social-element > td:first-child')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('text-align:') + 11\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'align values on social elements',\n      )\n      .to.eql(['right'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/social-icon-height.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-social icon-height', function () {\n  it('should render correct icon-height align in CSS style values on mj-social', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column css-class=\"my-social-element\">\n            <mj-social icon-height=\"40px\">\n              <mj-social-element name=\"facebook\" href=\"https://mjml.io/\" css-class=\"my-social-element\">\n                Facebook\n              </mj-social-element>\n            </mj-social>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // height values should be correct\n    chai\n      .expect(\n        $('.my-social-element > td > table > tbody > tr > td')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('height:') + 7\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'icon-height values on social elements',\n      )\n      .to.eql(['40px'])\n\n    chai\n      .expect(\n        $('.my-social-element > td > table > tbody > tr > td img')\n          .map(function getAttr() {\n            return $(this).attr('height')\n          })\n          .get(),\n      )\n      .to.satisfy((arr) => arr.every((val) => !val))\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/table-cellspacing.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-table cellspacing', function () {\n  it('should render correct cellspacing (and border-collapse) in HTML tag / CSS style values on mj-table', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-section>\n          <mj-column>\n            <mj-table border=\"1px solid #000\" width=\"auto\" cellpadding=\"20\" cellspacing=\"10\" css-class=\"my-table\">\n              <tr style=\"border-bottom:1px solid #000;text-align:left;\">\n                <th style=\"background:#ddd;\">Year</th>\n                <th style=\"background:#ddd;\">Language</th>\n                <th style=\"background:#ddd;\">Inspired from</th>\n              </tr>\n              <tr>\n                <td style=\"background:#ddd;\">1995</td>\n                <td style=\"background:#ddd;\">PHP</td>\n                <td style=\"background:#ddd;\">C, Shell Unix</td>\n              </tr>\n            </mj-table>\n          </mj-column>\n        </mj-section>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // border radius values should be correct\n    chai\n      .expect(\n        $('.my-table > table')\n          .map(function getAttr() {\n            return $(this).attr('cellspacing')\n          })\n          .get(),\n        'cellspacing values on table elements',\n      )\n      .to.eql(['10'])\n\n    // border collapse values should be correct\n    chai\n      .expect(\n        $('.my-table > table')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('border-collapse:') + 16\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Border-collapse in CSS style values on mj-table',\n      )\n      .to.eql(['separate'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/tableWidth.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-table width', function () {\n  it('should render correct width in CSS style values on mj-table', function () {\n    const input = `\n    <mjml>\n        <mj-body>\n            <mj-wrapper>\n                <mj-section>\n                    <mj-column>\n                        <mj-table css-class=\"table\">\n                            <tr>\n                                <th style=\"border: 1px solid black;text-align: left;\">\n                                    Default Width\n                                </th>\n                                <td style=\"border: 1px solid black;\">\n                                    100%\n                                </td>\n                            </tr>\n                        </mj-table>\n                    </mj-column>\n                </mj-section>\n                <mj-section>\n                    <mj-column>\n                        <mj-table width=\"500px\" css-class=\"table\">\n                            <tr>\n                                <th style=\"border: 1px solid black;text-align: left;\">\n                                    Pixel Width\n                                </th>\n                                <td style=\"border: 1px solid black;\">\n                                    500px\n                                </td>\n                            </tr>\n                        </mj-table>\n                    </mj-column>\n                </mj-section>\n                <mj-section>\n                    <mj-column>\n                        <mj-table width=\"80%\" css-class=\"table\">\n                            <tr>\n                                <th style=\"border: 1px solid black;text-align: left;\">\n                                    Percentage Width\n                                </th>\n                                <td style=\"border: 1px solid black;\">\n                                    80%\n                                </td>\n                            </tr>\n                        </mj-table>\n                    </mj-column>\n                </mj-section>\n                <mj-section css-class=\"section\">\n                    <mj-column>\n                        <mj-table width=\"auto\" css-class=\"table\">\n                            <tr>\n                                <th style=\"border: 1px solid black;text-align: left;\">\n                                    Auto Width\n                                </th>\n                                <td style=\"border: 1px solid black;\">\n                                    Auto\n                                </td>\n                            </tr>\n                        </mj-table>\n                    </mj-column>\n                </mj-section>\n            </mj-wrapper>\n        </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // width values should be correct\n    chai\n      .expect(\n        $('.table table')\n          .map(function getAttr() {\n            return $(this).attr('width')\n          })\n          .get(),\n        'Width values on tables',\n      )\n      .to.eql(['100%', '500', '80%', 'auto'])\n\n    // style values should be correct\n    chai\n      .expect(\n        $('.table table')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('width:') + 6\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Width in CSS style values on tables',\n      )\n      .to.eql(['100%', '500px', '80%', 'auto'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/wrapper-border-radius.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-wrapper and mj-section border-radius', function () {\n  it('should render correct border-radius (and border-collapse) in CSS style values on mj-wrapper and mj-section', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-wrapper border=\"1px solid red\" border-radius=\"10px\">\n          <mj-section>\n            <mj-column>\n              <mj-text font-size=\"20px\" color=\"#F45E43\" font-family=\"helvetica\">Hello World</mj-text>\n            </mj-column>\n          </mj-section>\n        </mj-wrapper>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // border radius values should be correct\n    chai\n      .expect(\n        $(\n          'body > div > div > table:first-child > tbody > tr > td, body > div > div',\n        )\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('border-radius:') + 14\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Border-radius in CSS style values on mj-wrapper',\n      )\n      .to.eql(['10px', '10px'])\n\n    // overflow value should be correct\n    chai\n      .expect(\n        $('body > div > div')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('overflow:') + 9\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Overflow in CSS style values on mj-wrapper',\n      )\n      .to.eql(['hidden'])\n\n    // border collapse values should be correct\n    chai\n      .expect(\n        $('body > div > div > table:first-child')\n          .map(function getAttr() {\n            const start = $(this).attr('style').indexOf('border-collapse:') + 16\n            const end = $(this).attr('style').indexOf(';', start)\n            return $(this).attr('style').substring(start, end)\n          })\n          .get(),\n        'Border-collapse in CSS style values on mj-wrapper',\n      )\n      .to.eql(['separate'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml/test/wrapper-gap.test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-wrapper gap', function () {\n  it('should render correct gap values in CSS style values on children mj-section', function () {\n    const input = `\n    <mjml>\n      <mj-body>\n        <mj-wrapper gap=\"20px\" css-class=\"my-wrapper\" background-color=\"#000\"> \n          <mj-section css-class=\"my-section\" background-color=\"#f45e43\" padding=\"10px\">\n            <mj-column>\n              <mj-text>Section 1</mj-text>\n            </mj-column>\n          </mj-section>\n          <mj-section css-class=\"my-section\" background-color=\"#ccc\" padding=\"10px\">\n            <mj-column>\n              <mj-text>Section 2</mj-text>\n            </mj-column>\n          </mj-section>\n          <mj-section css-class=\"my-section\" background-color=\"#333\" padding=\"10px\">\n            <mj-column>\n              <mj-text color=\"#fff\">Section 3</mj-text>\n            </mj-column>\n          </mj-section>\n        </mj-wrapper>\n      </mj-body>\n    </mjml>\n    `\n\n    const { html } = mjml(input)\n\n    const $ = load(html)\n\n    // gap values should be correct\n    chai\n      .expect(\n        $('.my-section')\n          .map(function getAttr() {\n            const str = $(this).attr('style')\n            const substr = 'margin-top:'\n\n            if (str.includes(substr)) {\n              const start = $(this).attr('style').indexOf(substr) + 11\n              const end = $(this).attr('style').indexOf(';', start)\n              return $(this).attr('style').substring(start, end)\n            }\n            return undefined\n          })\n          .get(),\n        'Gap in CSS style values on mj-wrapper',\n      )\n      .to.eql(['20px', '20px'])\n  })\n})\n"
  },
  {
    "path": "packages/mjml-accordion/README.md",
    "content": "### mj-accordion\n\nAn interactive MJML component that stacks content in tabs, so the information is collapsed and only the titles are visible.\n\nReaders can interact by clicking on the tabs to reveal the content, providing a better experience for mobile users by reducing the amount of scrolling.\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/accordion-example.gif\" alt=\"accordion\" />\n</figure>\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-accordion-text</code> and <code>mj-accordion-title</code> are \"ending tags\", which means that they can contain HTML code but they cannot contain other MJML components.</p>\n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n```xml\n<mjml>\n  <mj-head>\n    <mj-attributes>\n      <mj-accordion border=\"none\" padding=\"1px\" />\n      <mj-accordion-element icon-wrapped-url=\"https://static.mailjet.com/mjml-website/documentation/accordion-arrow-down.png\" icon-unwrapped-url=\"https://static.mailjet.com/mjml-website/documentation/accordion-arrow-up.png\" icon-height=\"24px\" icon-width=\"24px\" />\n      <mj-accordion-title font-family=\"Roboto, Open Sans, Helvetica, Arial, sans-serif\" background-color=\"#fff\" color=\"#031017\" padding=\"15px\" font-size=\"18px\" />\n      <mj-accordion-text font-family=\"Open Sans, Helvetica, Arial, sans-serif\" background-color=\"#fafafa\" padding=\"15px\" color=\"#505050\" font-size=\"14px\" />\n    </mj-attributes>\n  </mj-head>\n\n  <mj-body>\n    <mj-section padding=\"20px\" background-color=\"#ffffff\">\n      <mj-column background-color=\"#dededd\">\n        <mj-accordion>\n          <mj-accordion-element>\n            <mj-accordion-title>Why use an accordion?</mj-accordion-title>\n            <mj-accordion-text>\n              <span style=\"line-height:20px\">\n                Because emails with a lot of content are most of the time a very bad experience on mobile, mj-accordion comes handy when you want to deliver a lot of information in a concise way.\n              </span>\n            </mj-accordion-text>\n          </mj-accordion-element>\n          <mj-accordion-element>\n            <mj-accordion-title>How it works</mj-accordion-title>\n            <mj-accordion-text>\n              <span style=\"line-height:20px\">\n                Content is stacked into tabs and users can expand them at will. If responsive styles are not supported (mostly on desktop clients), tabs are then expanded and your content is readable at once.\n              </span>\n            </mj-accordion-text>\n          </mj-accordion-element>\n        </mj-accordion>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute                  | accepts                 | description                                        | default value                          |\n| -------------------------- | ----------------------- | -------------------------------------------------- | -------------------------------------- |\n| border                     | string                  | CSS border format                                  | `2px solid black`                      |\n| container-background-color | CSS color formats       | background-color of the cell                       |                                        |\n| css-class                  | string                  | class name, added to the root HTML element created |                                        |\n| font-family                | string                  | font                                               | `Ubuntu, Helvetica, Arial, sans-serif` |\n| icon-align                 | `top` `middle` `bottom` | icon alignment                                     | `middle`                               |\n| icon-height                | `px` `%`                | icon height                                        | `32px`                                 |\n| icon-position              | left,<br>right          | display icon left or right                         | `right`                                |\n| icon-unwrapped-alt         | string                  | alt text when accordion is unwrapped               | `-`                                    |\n| icon-unwrapped-url         | string                  | icon when accordion is unwrapped                   | `https://i.imgur.com/w4uTygT.png`      |\n| icon-width                 | `px` `%`                | icon width                                         | `32px`                                 |\n| icon-wrapped-alt           | string                  | alt text when accordion is wrapped                 | `+`                                    |\n| icon-wrapped-url           | string                  | icon when accordion is wrapped                     | `https://i.imgur.com/bIXv1bk.png`      |\n| padding                    | `px` `%`                | accordion padding, supports up to 4 parameters     | `10px 25px`                            |\n| padding-bottom             | `px` `%`                | accordion bottom padding                           |                                        |\n| padding-left               | `px` `%`                | accordion left padding                             |                                        |\n| padding-right              | `px` `%`                | accordion right padding                            |                                        |\n| padding-top                | `px` `%`                | accordion top padding                              |                                        |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/accordion\">Try it live</a></p>\n\n#### mj-accordion-element\n\nCreates an accordion title/text pair. An accordion can have any number of these pairs.\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>Inheritance applies for attributes supported in both <code>mj-accordion</code> and\n<code>mj-accordion-element</code> except where the latter overrides.</p>\n</div>\n\n##### Attributes\n\n| attribute          | accepts                 | description                                                                               | default value |\n| ------------------ | ----------------------- | ----------------------------------------------------------------------------------------- | ------------- |\n| background-color   | CSS color formats       | background color                                                                          |               |\n| border             | string                  | CSS border format. <br>affects each horizontal border in the accordion except the top one |               |\n| css-class          | string                  | class name, added to the root HTML element created                                        |               |\n| font-family        | string                  | font                                                                                      |               |\n| icon-align         | `top` `middle` `bottom` | icon alignment                                                                            |               |\n| icon-height        | `px` `%`                | icon width                                                                                | `32px`        |\n| icon-position      | `left` `right`          | postion of icon                                                                           |               |\n| icon-unwrapped-alt | string                  | alt text when accordion is unwrapped                                                      |               |\n| icon-unwrapped-url | string                  | icon when accordion is unwrapped                                                          |               |\n| icon-width         | `px` `%`                | icon height                                                                               | `32px`        |\n| icon-wrapped-alt   | string                  | alt text when accordion is wrapped                                                        |               |\n| icon-wrapped-url   | string                  | icon when accordion is wrapped                                                            |               |\n\n#### mj-accordion-title\n\nDisplays the title in a title/text pair.\n\n##### Attributes\n\n| attribute        | accepts           | description                                          | default value |\n| ---------------- | ----------------- | ---------------------------------------------------- | ------------- |\n| background-color | CSS color formats | background color                                     |               |\n| color            | CSS color formats | text color                                           |               |\n| css-class        | string            | class name, added to the root HTML element created   |               |\n| font-family      | string            | font family                                          |               |\n| font-size        | `px`              | font size                                            | `13px`        |\n| font-weight      | string            | text thickness                                       |               |\n| padding          | `px` `%`          | accordion title padding, supports up to 4 parameters | `16px`        |\n| padding-bottom   | `px` `%`          | accordion title bottom padding                       |               |\n| padding-left     | `px` `%`          | accordion title left padding                         |               |\n| padding-right    | `px` `%`          | accordion title right padding                        |               |\n| padding-top      | `px` `%`          | accordion title top padding                          |               |\n\n#### mj-accordion-text\n\nDisplays the text in a title/text pair.\n\n##### Attributes\n\n| attribute        | accepts           | description                                         | default value |\n| ---------------- | ----------------- | --------------------------------------------------- | ------------- |\n| background-color | CSS color formats | background color                                    |               |\n| color            | CSS color formats | text color                                          |               |\n| css-class        | string            | class name, added to the root HTML element created  |               |\n| font-family      | string            | font family                                         |               |\n| font-size        | `px`              | font size                                           | `13px`        |\n| font-weight      | string            | text thickness                                      |               |\n| letter-spacing   | `px` `em`         | letter spacing                                      |               |\n| line-height      | `px` `%`          | space between the lines                             | `1`           |\n| padding          | `px` `%`          | accordion text padding, supports up to 4 parameters | `16px`        |\n| padding-bottom   | `px` `%`          | accordion text bottom padding                       |               |\n| padding-left     | `px` `%`          | accordion text left padding                         |               |\n| padding-right    | `px` `%`          | accordion text right padding                        |               |\n| padding-top      | `px` `%`          | accordion text top padding                          |               |\n"
  },
  {
    "path": "packages/mjml-accordion/package.json",
    "content": "{\n  \"name\": \"mjml-accordion\",\n  \"description\": \"mjml-accordion\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-accordion\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-accordion/src/Accordion.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjAccordion extends BodyComponent {\n  static componentName = 'mj-accordion'\n\n  static allowedAttributes = {\n    'container-background-color': 'color',\n    border: 'string',\n    'font-family': 'string',\n    'icon-align': 'enum(top,middle,bottom)',\n    'icon-width': 'unit(px,%)',\n    'icon-height': 'unit(px,%)',\n    'icon-wrapped-url': 'string',\n    'icon-wrapped-alt': 'string',\n    'icon-unwrapped-url': 'string',\n    'icon-unwrapped-alt': 'string',\n    'icon-position': 'enum(left,right)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n  }\n\n  static defaultAttributes = {\n    border: '2px solid black',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'icon-align': 'middle',\n    'icon-wrapped-url': 'https://i.imgur.com/bIXv1bk.png',\n    'icon-wrapped-alt': '+',\n    'icon-unwrapped-url': 'https://i.imgur.com/w4uTygT.png',\n    'icon-unwrapped-alt': '-',\n    'icon-position': 'right',\n    'icon-height': '32px',\n    'icon-width': '32px',\n    padding: '10px 25px',\n  }\n\n  headStyle = () =>\n    `\n      noinput.mj-accordion-checkbox { display:block!important; }\n\n      @media yahoo, only screen and (min-width:0) {\n        .mj-accordion-element { display:block; }\n        input.mj-accordion-checkbox, .mj-accordion-less { display:none!important; }\n        input.mj-accordion-checkbox + * .mj-accordion-title { cursor:pointer; touch-action:manipulation; -webkit-user-select:none; -moz-user-select:none; user-select:none; }\n        input.mj-accordion-checkbox + * .mj-accordion-content { overflow:hidden; display:none; }\n        input.mj-accordion-checkbox + * .mj-accordion-more { display:block!important; }\n        input.mj-accordion-checkbox:checked + * .mj-accordion-content { display:block; }\n        input.mj-accordion-checkbox:checked + * .mj-accordion-more { display:none!important; }\n        input.mj-accordion-checkbox:checked + * .mj-accordion-less { display:block!important; }\n      }\n\n      .moz-text-html input.mj-accordion-checkbox + * .mj-accordion-title { cursor: auto; touch-action: auto; -webkit-user-select: auto; -moz-user-select: auto; user-select: auto; }\n      .moz-text-html input.mj-accordion-checkbox + * .mj-accordion-content { overflow: hidden; display: block; }\n      .moz-text-html input.mj-accordion-checkbox + * .mj-accordion-ico { display: none; }\n\n      @goodbye { @gmail }\n    `\n\n  getStyles() {\n    return {\n      table: {\n        width: '100%',\n        'border-collapse': 'collapse',\n        border: this.getAttribute('border'),\n        'border-bottom': 'none',\n        'font-family': this.getAttribute('font-family'),\n      },\n    }\n  }\n\n  getChildContext() {\n    return {\n      ...this.context,\n      accordionFontFamily: this.getAttribute('font-family'),\n    }\n  }\n\n  render() {\n    const childrenAttr = [\n      'border',\n      'icon-align',\n      'icon-width',\n      'icon-height',\n      'icon-position',\n      'icon-wrapped-url',\n      'icon-wrapped-alt',\n      'icon-unwrapped-url',\n      'icon-unwrapped-alt',\n    ].reduce(\n      (res, val) => ({\n        ...res,\n        [val]: this.getAttribute(val),\n      }),\n      {},\n    )\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          cellspacing: '0',\n          cellpadding: '0',\n          class: 'mj-accordion',\n          style: 'table',\n        })}\n      >\n        <tbody>\n          ${this.renderChildren(this.props.children, {\n            attributes: childrenAttr,\n          })}\n        </tbody>\n      </table>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-accordion/src/AccordionElement.js",
    "content": "import { BodyComponent } from 'mjml-core'\nimport { find } from 'lodash'\nimport conditionalTag from 'mjml-core/lib/helpers/conditionalTag'\nimport AccordionText from './AccordionText'\nimport AccordionTitle from './AccordionTitle'\n\nexport default class MjAccordionElement extends BodyComponent {\n  static componentName = 'mj-accordion-element'\n\n  static allowedAttributes = {\n    'background-color': 'color',\n    border: 'string',\n    'font-family': 'string',\n    'icon-align': 'enum(top,middle,bottom)',\n    'icon-width': 'unit(px,%)',\n    'icon-height': 'unit(px,%)',\n    'icon-wrapped-url': 'string',\n    'icon-wrapped-alt': 'string',\n    'icon-unwrapped-url': 'string',\n    'icon-unwrapped-alt': 'string',\n    'icon-position': 'enum(left,right)',\n  }\n\n  static defaultAttributes = {\n    title: {\n      img: {\n        width: '32px',\n        height: '32px',\n      },\n    },\n  }\n\n  getStyles() {\n    return {\n      td: {\n        padding: '0px',\n        'background-color': this.getAttribute('background-color'),\n      },\n      label: {\n        'font-size': '13px',\n        'font-family': this.getAttribute('font-family'),\n      },\n      input: {\n        display: 'none',\n      },\n    }\n  }\n\n  handleMissingChildren() {\n    const { children } = this.props\n    const childrenAttr = [\n      'border',\n      'icon-align',\n      'icon-width',\n      'icon-height',\n      'icon-position',\n      'icon-wrapped-url',\n      'icon-wrapped-alt',\n      'icon-unwrapped-url',\n      'icon-unwrapped-alt',\n    ].reduce(\n      (res, val) => ({\n        ...res,\n        [val]: this.getAttribute(val),\n      }),\n      {},\n    )\n\n    const result = []\n\n    if (!find(children, { tagName: 'mj-accordion-title' })) {\n      result.push(\n        new AccordionTitle({\n          attributes: childrenAttr,\n          context: this.getChildContext(),\n        }).render(),\n      )\n    }\n\n    result.push(this.renderChildren(children, { attributes: childrenAttr }))\n\n    if (!find(children, { tagName: 'mj-accordion-text' })) {\n      result.push(\n        new AccordionText({\n          attributes: childrenAttr,\n          context: this.getChildContext(),\n        }).render(),\n      )\n    }\n\n    return result.join('\\n')\n  }\n\n  getChildContext() {\n    return {\n      ...this.context,\n      elementFontFamily: this.getAttribute('font-family'),\n    }\n  }\n\n  render() {\n    return `\n      <tr\n        ${this.htmlAttributes({\n          class: this.getAttribute('css-class'),\n        })}\n      >\n        <td ${this.htmlAttributes({ style: 'td' })}>\n          <label\n            ${this.htmlAttributes({\n              class: 'mj-accordion-element',\n              style: 'label',\n            })}\n          >\n            ${conditionalTag(\n              `\n              <input\n                ${this.htmlAttributes({\n                  class: 'mj-accordion-checkbox',\n                  type: 'checkbox',\n                  style: 'input',\n                })}\n              />\n            `,\n              true,\n            )}\n            <div>\n              ${this.handleMissingChildren()}\n            </div>\n          </label>\n        </td>\n      </tr>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-accordion/src/AccordionText.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjAccordionText extends BodyComponent {\n  static componentName = 'mj-accordion-text'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    'background-color': 'color',\n    'font-size': 'unit(px)',\n    'font-family': 'string',\n    'font-weight': 'string',\n    'letter-spacing': 'unitWithNegative(px,em)',\n    'line-height': 'unit(px,%,)',\n    color: 'color',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n  }\n\n  static defaultAttributes = {\n    'font-size': '13px',\n    'line-height': '1',\n    padding: '16px',\n  }\n\n  getStyles() {\n    return {\n      td: {\n        background: this.getAttribute('background-color'),\n        'font-size': this.getAttribute('font-size'),\n        'font-family': this.resolveFontFamily(),\n        'font-weight': this.getAttribute('font-weight'),\n        'letter-spacing': this.getAttribute('letter-spacing'),\n        'line-height': this.getAttribute('line-height'),\n        color: this.getAttribute('color'),\n        padding: this.getAttribute('padding'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n        'padding-left': this.getAttribute('padding-left'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-top': this.getAttribute('padding-top'),\n      },\n      table: {\n        width: '100%',\n        'border-bottom': this.getAttribute('border'),\n      },\n    }\n  }\n\n  renderContent() {\n    return `\n      <td\n        ${this.htmlAttributes({\n          class: this.getAttribute('css-class'),\n          style: 'td',\n        })}\n      >\n        ${this.getContent()}\n      </td>\n    `\n  }\n\n  resolveFontFamily() {\n    if (\n      this.props &&\n      this.props.rawAttrs &&\n      Object.prototype.hasOwnProperty.call(this.props.rawAttrs, 'font-family')\n    ) {\n      return this.getAttribute('font-family')\n    }\n    if (this.context && this.context.elementFontFamily) {\n      return this.context.elementFontFamily\n    }\n    if (this.context && this.context.accordionFontFamily) {\n      return this.context.accordionFontFamily\n    }\n    return MjAccordionText.defaultAttributes.fontFamily\n  }\n\n  render() {\n    return `\n      <div\n        ${this.htmlAttributes({\n          class: 'mj-accordion-content',\n        })}\n      >\n        <table\n          ${this.htmlAttributes({\n            cellspacing: '0',\n            cellpadding: '0',\n            style: 'table',\n          })}\n        >\n          <tbody>\n            <tr>\n              ${this.renderContent()}\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-accordion/src/AccordionTitle.js",
    "content": "import { BodyComponent } from 'mjml-core'\nimport conditionalTag from 'mjml-core/lib/helpers/conditionalTag'\n\nexport default class MjAccordionTitle extends BodyComponent {\n  static componentName = 'mj-accordion-title'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    'background-color': 'color',\n    color: 'color',\n    'font-size': 'unit(px)',\n    'font-family': 'string',\n    'font-weight': 'string',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n  }\n\n  static defaultAttributes = {\n    'font-size': '13px',\n    padding: '16px',\n  }\n\n  getStyles() {\n    return {\n      td: {\n        width: '100%',\n        'background-color': this.getAttribute('background-color'),\n        color: this.getAttribute('color'),\n        'font-size': this.getAttribute('font-size'),\n        'font-family': this.resolveFontFamily(),\n        'font-weight': this.getAttribute('font-weight'),\n        padding: this.getAttribute('padding'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n        'padding-left': this.getAttribute('padding-left'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-top': this.getAttribute('padding-top'),\n      },\n      table: {\n        width: '100%',\n        'border-bottom': this.getAttribute('border'),\n      },\n      td2: {\n        padding: '16px',\n        background: this.getAttribute('background-color'),\n        'vertical-align': this.getAttribute('icon-align'),\n      },\n      img: {\n        display: 'none',\n        width: this.getAttribute('icon-width'),\n        height: this.getAttribute('icon-height'),\n      },\n    }\n  }\n\n  resolveFontFamily() {\n    if (\n      this.props &&\n      this.props.rawAttrs &&\n      Object.prototype.hasOwnProperty.call(this.props.rawAttrs, 'font-family')\n    ) {\n      return this.getAttribute('font-family')\n    }\n    if (this.context && this.context.elementFontFamily) {\n      return this.context.elementFontFamily\n    }\n    if (this.context && this.context.accordionFontFamily) {\n      return this.context.accordionFontFamily\n    }\n    return MjAccordionTitle.defaultAttributes.fontFamily\n  }\n\n  renderTitle() {\n    return `\n      <td\n        ${this.htmlAttributes({\n          class: this.getAttribute('css-class'),\n          style: 'td',\n        })}\n      >\n        ${this.getContent()}\n      </td>\n    `\n  }\n\n  renderIcons() {\n    return conditionalTag(\n      `\n      <td\n        ${this.htmlAttributes({\n          class: 'mj-accordion-ico',\n          style: 'td2',\n        })}\n      >\n        <img\n          ${this.htmlAttributes({\n            src: this.getAttribute('icon-wrapped-url'),\n            alt: this.getAttribute('icon-wrapped-alt'),\n            class: 'mj-accordion-more',\n            style: 'img',\n          })}\n        />\n        <img\n          ${this.htmlAttributes({\n            src: this.getAttribute('icon-unwrapped-url'),\n            alt: this.getAttribute('icon-unwrapped-alt'),\n            class: 'mj-accordion-less',\n            style: 'img',\n          })}\n        />\n      </td>\n    `,\n      true,\n    )\n  }\n\n  render() {\n    const contentElements = [this.renderTitle(), this.renderIcons()]\n    const content = (\n      this.getAttribute('icon-position') === 'right'\n        ? contentElements\n        : contentElements.reverse()\n    ).join('\\n')\n\n    return `\n      <div ${this.htmlAttributes({ class: 'mj-accordion-title' })}>\n        <table\n          ${this.htmlAttributes({\n            cellspacing: '0',\n            cellpadding: '0',\n            style: 'table',\n          })}\n        >\n          <tbody>\n            <tr>\n              ${content}\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-accordion/src/index.js",
    "content": "export { default as Accordion } from './Accordion'\nexport { default as AccordionElement } from './AccordionElement'\nexport { default as AccordionText } from './AccordionText'\nexport { default as AccordionTitle } from './AccordionTitle'\n"
  },
  {
    "path": "packages/mjml-body/README.md",
    "content": "### mj-body\n\nThis is the starting point of your email. To aid accessibility, MJML automatically adds a `div` tag as the child of the body, with the following ARIA attributes `role=\"article\"`, `aria-roledescription=\"email\"` and `aria-label=\"EMAIL NAME\"`, where 'EMAIL NAME' is taken from the content of the `mj-title` tag. The `lang` and `dir` attributes are also added here, with values taken from the `mjml` tag.\n\n```xml\n<mjml>\n  <mj-body>\n    <!-- Your email goes here -->\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute           | accepts           | description                                        | default value |\n| ------------------- | ----------------- | -------------------------------------------------- | ------------- |\n| background-color    | CSS color formats | the general background color                       |               |\n| css-class           | string            | class name, added to the root HTML element created |               |\n| width               | `px`              | email width                                        | `600px`       |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/body\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-body/package.json",
    "content": "{\n  \"name\": \"mjml-body\",\n  \"description\": \"mjml-body\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-body\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-body/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjBody extends BodyComponent {\n  static componentName = 'mj-body'\n\n  static allowedAttributes = {\n    width: 'unit(px)',\n    'background-color': 'color',\n  }\n\n  static defaultAttributes = {\n    width: '600px',\n  }\n\n  getChildContext() {\n    return {\n      ...this.context,\n      containerWidth: this.getAttribute('width'),\n    }\n  }\n\n  getStyles() {\n    return {\n      div: {\n        'background-color': this.getAttribute('background-color'),\n      },\n    }\n  }\n\n  render() {\n    const {\n      setBackgroundColor,\n      globalData: { lang, dir, title },\n    } = this.context\n    setBackgroundColor(this.getAttribute('background-color'))\n\n    return `\n      <div\n        ${this.htmlAttributes({\n          ...(title && { 'aria-label': title }),\n          'aria-roledescription': 'email',\n          class: this.getAttribute('css-class'),\n          style: 'div',\n          role: 'article',\n          lang,\n          dir,\n        })}\n      >\n        ${this.renderChildren()}\n      </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-browser/README.md",
    "content": "## MJML Browser build\n\nThis package allows MJML to be used client-side.\n\n### Usage\n\nIt can be used as the regular mjml package :  \n\n```javascript\nvar mjml2html = require('mjml-browser')\n\nvar result = mjml2html(mjml, options)\n```\n\n### Unavailable features  \n\n- `mj-include` tags are unavailable and will be ignored.\n- features involving the `.mjmlconfig` file are unavailable, which means no custom components.\n"
  },
  {
    "path": "packages/mjml-browser/browser-mocks/fs.js",
    "content": "module.exports = {\n  readFileSync: () => {\n    console.warn('fs should not be used in browser build') // eslint-disable-line no-console\n    return null\n  },\n}\n"
  },
  {
    "path": "packages/mjml-browser/browser-mocks/path.js",
    "content": "const mockFn = () => {\n  console.warn('fs should not be used in browser build') // eslint-disable-line no-console\n  return null\n}\n\nmodule.exports = {\n  parse: mockFn,\n  resolve: mockFn,\n  join: mockFn,\n  dirname: mockFn,\n  isAbsolute: mockFn,\n}\n"
  },
  {
    "path": "packages/mjml-browser/browser-mocks/uglify-js.js",
    "content": "module.exports = {}\n"
  },
  {
    "path": "packages/mjml-browser/package.json",
    "content": "{\n  \"name\": \"mjml-browser\",\n  \"description\": \"MJML: the only framework that makes responsive-email easy\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-browser\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"webpack\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"@babel/core\": \"^7.8.4\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.8.3\",\n    \"@babel/plugin-proposal-decorators\": \"^7.8.3\",\n    \"@babel/plugin-proposal-export-default-from\": \"^7.8.3\",\n    \"@babel/plugin-proposal-function-bind\": \"^7.8.3\",\n    \"@babel/preset-env\": \"^7.8.4\",\n    \"babel-loader\": \"^8.0.6\",\n    \"rimraf\": \"^3.0.2\",\n    \"uglifyjs-webpack-plugin\": \"^2.1.3\",\n    \"webpack\": \"^4.36.1\",\n    \"webpack-cli\": \"^3.3.6\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-browser/webpack.config.js",
    "content": "const path = require('path')\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')\n\nmodule.exports = {\n  mode: 'production',\n  entry: {\n    \"mjml\": ['../mjml/lib/index'],\n  },\n  optimization: {\n    minimizer: [\n      new UglifyJsPlugin({\n        uglifyOptions: {\n          ecma: 5,\n          keep_classnames: true,\n          keep_fnames: true,\n          compress: {\n            passes: 2,\n            keep_fargs: false,\n          },\n          output: {\n            beautify: false,\n          },\n          mangle: true,\n        },\n      }),\n    ],\n  },\n  output: {\n    library: 'mjml',\n    filename: 'index.js',\n    path: path.resolve(__dirname, './lib'),\n    libraryTarget: 'umd',\n    umdNamedDefine: true,\n  },\n  resolve: {\n    alias: {\n      'path': path.resolve(__dirname, 'browser-mocks/path'),\n      'fs': path.resolve(__dirname, 'browser-mocks/fs'),\n      'uglify-js': path.resolve(__dirname, 'browser-mocks/uglify-js'),\n    },\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        exclude: path.join(__dirname, 'node_modules'),\n        use: [\n          {\n            loader: 'babel-loader',\n            options: {\n              presets: [\n                '@babel/preset-env',\n              ],\n              plugins: [\n                [\"@babel/plugin-proposal-decorators\", { \"legacy\": true }],\n                [\"@babel/plugin-proposal-class-properties\", { \"loose\" : true }],\n                \"@babel/plugin-proposal-function-bind\",\n                \"@babel/plugin-proposal-export-default-from\",\n              ],\n              babelrc: false,\n            },\n          },\n        ],\n      },\n  \t],\n  },\n}\n"
  },
  {
    "path": "packages/mjml-button/README.md",
    "content": "### mj-button\n\nDisplays a customizable button.\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/button-example.png\"\n       alt=\"desktop\" width=\"150px\" />\n</figure>\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>The <code>mj-button</code> won't be fully clickable because of client support.\n  See discussion at\n    <a href=\"https://github.com/mjmlio/mjml/issues/359\">Issue #359</a>.</p>\n</div>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-button font-family=\"Helvetica\" background-color=\"#f45e43\" color=\"white\">\n          Don't click me!\n         </mj-button>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-button</code> is an \"ending tag\", which means that it can contain HTML code but it cannot contain other MJML components.</p> \n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n#### Attributes\n\n| attribute                  | accepts                            | description                                           | default value                          |\n| -------------------------- | ---------------------------------- | ----------------------------------------------------- | -------------------------------------- |\n| align                      | `left` `center` `right`            | horizontal alignment                                  | `center`                               |\n| background-color           | CSS color formats                  | button background-color                               | `#414141`                              |\n| border                     | string                             | CSS border format                                     | `none`                                 |\n| border-bottom              | string                             | CSS border format                                     |                                        |\n| border-left                | string                             | CSS border format                                     |                                        |\n| border-radius              | string                             | border radius                                         | `3px`                                  |\n| border-right               | string                             | CSS border format                                     |                                        |\n| border-top                 | string                             | CSS border format                                     |                                        |\n| color                      | CSS color formats                  | text color                                            | `#ffffff`                              |\n| container-background-color | CSS color formats                  | button container background color                     |                                        |\n| css-class                  | string                             | class name, added to the root HTML element created    |                                        |\n| font-family                | string                             | font name                                             | `Ubuntu, Helvetica, Arial, sans-serif` |\n| font-size                  | `px`                               | text size                                             | `13px`                                 |\n| font-style                 | string                             | CSS values, e.g. `normal` `italic` `oblique`          |                                        |\n| font-weight                | string                             | text thickness                                        | `normal`                               |\n| height                     | `px` `%`                           | button height                                         |                                        |\n| href                       | string                             | URL format                                            |                                        |\n| inner-padding              | `px` `%`                           | inner button padding, <br>supports up to 4 parameters | `10px 25px`                            |\n| letter-spacing             | `px` `em`                          | letter-spacing                                        |                                        |\n| line-height                | `px` `%`                           | line-height on link                                   | `120%`                                 |\n| name                       | string                             | specify the name attribute for the button link        |\n| padding                    | `px` `%`                           | button container padding, supports up to 4 parameters | `10px 25px`                            |\n| padding-bottom             | `px` `%`                           | button container bottom padding                       |                                        |\n| padding-left               | `px` `%`                           | button container left padding                         |                                        |\n| padding-right              | `px` `%`                           | button container right padding                        |                                        |\n| padding-top                | `px` `%`                           | button container top padding                          |                                        |\n| rel                        | string                             | specify the rel attribute for the button link         |                                        |\n| target                     | string                             | specify the target attribute for the button link      | `_blank`                               |\n| text-align                 | `left` `center` `right`            | text-align button content                             |                                        |\n| text-decoration            | string                             | underline/overline/none                               | `none`                                 |\n| text-transform             | string                             | capitalize/uppercase/lowercase                        | `none`                                 |\n| title                      | string                             | tooltip & accessibility                               |                                        |\n| vertical-align             | `top` `bottom` `middle` `vertical` | vertical alignment                                    | `middle`                               |\n| width                      | `px` `%`                           | button width                                          |                                        |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/button\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-button/package.json",
    "content": "{\n  \"name\": \"mjml-button\",\n  \"description\": \"mjml-button\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-button\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-button/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default class MjButton extends BodyComponent {\n  static componentName = 'mj-button'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    align: 'enum(left,center,right)',\n    'background-color': 'color',\n    'border-bottom': 'string',\n    'border-left': 'string',\n    'border-radius': 'string',\n    'border-right': 'string',\n    'border-top': 'string',\n    border: 'string',\n    color: 'color',\n    'container-background-color': 'color',\n    'font-family': 'string',\n    'font-size': 'unit(px)',\n    'font-style': 'string',\n    'font-weight': 'string',\n    height: 'unit(px,%)',\n    href: 'string',\n    name: 'string',\n    title: 'string',\n    'inner-padding': 'unit(px,%){1,4}',\n    'letter-spacing': 'unitWithNegative(px,em)',\n    'line-height': 'unit(px,%,)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    rel: 'string',\n    target: 'string',\n    'text-decoration': 'string',\n    'text-transform': 'string',\n    'vertical-align': 'enum(top,bottom,middle)',\n    'text-align': 'enum(left,right,center)',\n    width: 'unit(px,%)',\n  }\n\n  static defaultAttributes = {\n    align: 'center',\n    'background-color': '#414141',\n    border: 'none',\n    'border-radius': '3px',\n    color: '#ffffff',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'font-size': '13px',\n    'font-weight': 'normal',\n    'inner-padding': '10px 25px',\n    'line-height': '120%',\n    padding: '10px 25px',\n    target: '_blank',\n    'text-decoration': 'none',\n    'text-transform': 'none',\n    'vertical-align': 'middle',\n  }\n\n  getStyles() {\n    return {\n      table: {\n        'border-collapse': 'separate',\n        width: this.getAttribute('width'),\n        'line-height': '100%',\n      },\n      td: {\n        border: this.getAttribute('border'),\n        'border-bottom': this.getAttribute('border-bottom'),\n        'border-left': this.getAttribute('border-left'),\n        'border-radius': this.getAttribute('border-radius'),\n        'border-right': this.getAttribute('border-right'),\n        'border-top': this.getAttribute('border-top'),\n        cursor: 'auto',\n        'font-style': this.getAttribute('font-style'),\n        height: this.getAttribute('height'),\n        'mso-padding-alt': this.getAttribute('inner-padding'),\n        'text-align': this.getAttribute('text-align'),\n        background: this.getAttribute('background-color'),\n      },\n      content: {\n        display: 'inline-block',\n        width: this.calculateAWidth(this.getAttribute('width')),\n        background: this.getAttribute('background-color'),\n        color: this.getAttribute('color'),\n        'font-family': this.getAttribute('font-family'),\n        'font-size': this.getAttribute('font-size'),\n        'font-style': this.getAttribute('font-style'),\n        'font-weight': this.getAttribute('font-weight'),\n        'line-height': this.getAttribute('line-height'),\n        'letter-spacing': this.getAttribute('letter-spacing'),\n        margin: '0',\n        'text-decoration': this.getAttribute('text-decoration'),\n        'text-transform': this.getAttribute('text-transform'),\n        padding: this.getAttribute('inner-padding'),\n        'mso-padding-alt': '0px',\n        'border-radius': this.getAttribute('border-radius'),\n      },\n    }\n  }\n\n  calculateAWidth(width) {\n    if (!width) return null\n\n    const { parsedWidth, unit } = widthParser(width)\n\n    // impossible to handle percents because it depends on padding and text width\n    if (unit !== 'px') return null\n\n    const { borders } = this.getBoxWidths()\n\n    const innerPaddings =\n      this.getShorthandAttrValue('inner-padding', 'left') +\n      this.getShorthandAttrValue('inner-padding', 'right')\n\n    return `${parsedWidth - innerPaddings - borders}px`\n  }\n\n  render() {\n    const tag = this.getAttribute('href') ? 'a' : 'p'\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n          style: 'table',\n        })}\n      >\n        <tbody>\n          <tr>\n            <td\n              ${this.htmlAttributes({\n                align: 'center',\n                bgcolor:\n                  this.getAttribute('background-color') === 'none'\n                    ? undefined\n                    : this.getAttribute('background-color'),\n                role: 'presentation',\n                style: 'td',\n                valign: this.getAttribute('vertical-align'),\n              })}\n            >\n              <${tag}\n                ${this.htmlAttributes({\n                  href: this.getAttribute('href'),\n                  name: this.getAttribute('name'),\n                  rel: this.getAttribute('rel'),\n                  title: this.getAttribute('title'),\n                  style: 'content',\n                  target: tag === 'a' ? this.getAttribute('target') : undefined,\n                })}\n              >\n                ${this.getContent()}\n              </${tag}>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-carousel/README.md",
    "content": "### mj-carousel\n\nDisplays a gallery of images or \"carousel\". Readers can interact by hovering and clicking on thumbnails depending on the email client they use.\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/carousel-example.gif\" alt=\"desktop\" />\n</figure>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-carousel>\n          <mj-carousel-image src=\"https://static.mailjet.com/mjml-website/documentation/carousel-1.jpg\" />\n          <mj-carousel-image src=\"https://static.mailjet.com/mjml-website/documentation/carousel-2.jpg\" />\n          <mj-carousel-image src=\"https://static.mailjet.com/mjml-website/documentation/carousel-3.jpg\" />\n        </mj-carousel>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute                  | accepts                        | description                                            | default value                     |\n| -------------------------- | ------------------------------ | ------------------------------------------------------ | --------------------------------- |\n| align                      | `left` `center` `right`        | horizontal alignment                                   | `center`                          |\n| border-radius              | `px` `%`                       | border radius                                          | `6px`                             |\n| container-background-color | CSS color formats              | column background color                                |                                   |\n| css-class                  | string                         | class name, added to the root HTML element created     |                                   |\n| icon-width                 | `px` `%`                       | width of the icons on left and right of the main image | `44px`                            |\n| left-icon                  | string                         | icon on the left of the main image                     | `https://i.imgur.com/xTh3hln.png` |\n| padding                    | `px` `%`                       | carousel padding, supports up to 4 parameters          |                                   |\n| padding-bottom             | `px` `%`                       | carousel bottom padding                                |                                   |\n| padding-left               | `px` `%`                       | carousel left padding                                  |                                   |\n| padding-right              | `px` `%`                       | carousel right padding                                 |                                   |\n| padding-top                | `px` `%`                       | carousel top padding                                   |                                   |\n| right-icon                 | string                         | icon on the right of the main image                    | `https://i.imgur.com/os7o9kz.png` |\n| tb-border                  | string                         | border of the thumbnails in CSS border format          | `2px solid transparent`           |\n| tb-border-radius           | `px` `%`                       | border-radius of the thumbnails                        | `6px`                             |\n| tb-hover-border-color      | CSS color formats              | border color of the hovered thumbnail                  | `#fead0d`                         |\n| tb-selected-border-color   | CSS color formats              | border color of the selected thumbnail                 | `#ccc`                            |\n| tb-width                   | `px` `%`                       | thumbnail width                                        |                                   |\n| thumbnails                 | `visible` `hidden` `supported` | display the thumbnails                                 | `hidden`                          |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/carousel\">Try it live</a></p>\n\n#### mj-carousel-image\n\nEnables you to add and style the images in the carousel.\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-carousel-image</code> is an \"ending tag\", which means that it can contain HTML code but it cannot contain other MJML components.</p>\n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n#### Attributes\n\n| attribute        | accepts  | description                                        | default value |\n| ---------------- | -------- | -------------------------------------------------- | ------------- |\n| alt              | string   | image description                                  | `''`          |\n| border-radius    | `px` `%` | border radius of the main image                    |               |\n| css-class        | string   | class name, added to the root HTML element created |               |\n| href             | string   | link to redirect to on click, <br>URL format       |               |\n| rel              | string   | specify the rel attribute                          |               |\n| src              | string   | URL format                                         |               |\n| target           | string   | link target on click                               | `_blank`      |\n| tb-border        | string   | CSS border format                                  |               |\n| tb-border-radius | `px` `%` | border radius of the thumbnail                     |               |\n| thumbnails-src   | string   | specify a different thumbnail image in URL format  |               |\n| title            | string   | tooltip & accessibility                            |               |\n"
  },
  {
    "path": "packages/mjml-carousel/package.json",
    "content": "{\n  \"name\": \"mjml-carousel\",\n  \"description\": \"mjml-carousel\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-carousel\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-carousel/src/Carousel.js",
    "content": "import { BodyComponent } from 'mjml-core'\nimport { range, repeat, min, map } from 'lodash'\n\nimport { msoConditionalTag } from 'mjml-core/lib/helpers/conditionalTag'\nimport genRandomHexString from 'mjml-core/lib/helpers/genRandomHexString'\n\nexport default class MjCarousel extends BodyComponent {\n  static componentName = 'mj-carousel'\n\n  static allowedAttributes = {\n    align: 'enum(left,center,right)',\n    'border-radius': 'unit(px,%){1,4}',\n    'container-background-color': 'color',\n    'icon-width': 'unit(px,%)',\n    'left-icon': 'string',\n    padding: 'unit(px,%){1,4}',\n    'padding-top': 'unit(px,%)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'right-icon': 'string',\n    thumbnails: 'enum(visible,hidden,supported)',\n    'tb-border': 'string',\n    'tb-border-radius': 'unit(px,%)',\n    'tb-hover-border-color': 'color',\n    'tb-selected-border-color': 'color',\n    'tb-width': 'unit(px,%)',\n  }\n\n  static defaultAttributes = {\n    align: 'center',\n    'border-radius': '6px',\n    'icon-width': '44px',\n    'left-icon': 'https://i.imgur.com/xTh3hln.png',\n    'right-icon': 'https://i.imgur.com/os7o9kz.png',\n    thumbnails: 'visible',\n    'tb-border': '2px solid transparent',\n    'tb-border-radius': '6px',\n    'tb-hover-border-color': '#fead0d',\n    'tb-selected-border-color': '#ccc',\n  }\n\n  constructor(initialDatas = {}) {\n    super(initialDatas)\n    this.carouselId = genRandomHexString(16)\n  }\n\n  componentHeadStyle = () => {\n    const { length } = this.props.children\n    const { carouselId } = this\n\n    if (!length) return ''\n\n    const carouselCss = `\n    .mj-carousel {\n      -webkit-user-select: none;\n      -moz-user-select: none;\n      user-select: none;\n    }\n\n    .mj-carousel-${carouselId}-icons-cell {\n      display: table-cell !important;\n      width: ${this.getAttribute('icon-width')} !important;\n    }\n\n    .mj-carousel-radio,\n    .mj-carousel-next,\n    .mj-carousel-previous {\n      display: none !important;\n    }\n\n    .mj-carousel-thumbnail,\n    .mj-carousel-next,\n    .mj-carousel-previous {\n      touch-action: manipulation;\n    }\n\n    ${range(0, length)\n      .map(\n        (i) =>\n          `.mj-carousel-${carouselId}-radio:checked ${repeat(\n            '+ * ',\n            i,\n          )}+ .mj-carousel-content .mj-carousel-image`,\n      )\n      .join(',')} {\n      display: none !important;\n    }\n\n    ${range(0, length)\n      .map(\n        (i) =>\n          `.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(\n            '+ * ',\n            length - i - 1,\n          )}+ .mj-carousel-content .mj-carousel-image-${i + 1}`,\n      )\n      .join(',')} {\n      display: block !important;\n    }\n\n    .mj-carousel-previous-icons,\n    .mj-carousel-next-icons,\n    ${range(0, length).map(\n      (i) =>\n        `.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(\n          '+ * ',\n          length - i - 1,\n        )}+ .mj-carousel-content .mj-carousel-next-${\n          ((i + (1 % length) + length) % length) + 1\n        }`,\n    )},\n    ${range(0, length).map(\n      (i) =>\n        `.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(\n          '+ * ',\n          length - i - 1,\n        )}+ .mj-carousel-content .mj-carousel-previous-${\n          ((i - (1 % length) + length) % length) + 1\n        }`,\n    )} {\n      display: block !important;\n    }\n\n    ${range(0, length)\n      .map(\n        (i) =>\n          `.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(\n            '+ * ',\n            length - i - 1,\n          )}+ .mj-carousel-content .mj-carousel-${carouselId}-thumbnail-${\n            i + 1\n          }`,\n      )\n      .join(',')} {\n      border-color: ${this.getAttribute('tb-selected-border-color')} !important;\n    }\n\n    ${range(0, length)\n      .map(\n        (i) =>\n          `.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(\n            '+ * ',\n            length - i - 1,\n          )}+ .mj-carousel-content .mj-carousel-${carouselId}-thumbnail\n          `,\n      )\n      .join(',')} {\n      display: inline-block !important;\n    }\n\n    .mj-carousel-image img + div,\n    .mj-carousel-thumbnail img + div {\n      display: none !important;\n    }\n\n    ${range(0, length)\n      .map(\n        (i) =>\n          `.mj-carousel-${carouselId}-thumbnail:hover ${repeat(\n            '+ * ',\n            length - i - 1,\n          )}+ .mj-carousel-main .mj-carousel-image`,\n      )\n      .join(',')} {\n      display: none !important;\n    }\n\n    .mj-carousel-thumbnail:hover {\n      border-color: ${this.getAttribute('tb-hover-border-color')} !important;\n    }\n\n    ${range(0, length)\n      .map(\n        (i) =>\n          `.mj-carousel-${carouselId}-thumbnail-${i + 1}:hover ${repeat(\n            '+ * ',\n            length - i - 1,\n          )}+ .mj-carousel-main .mj-carousel-image-${i + 1}`,\n      )\n      .join(',')} {\n      display: block !important;\n    }\n    `\n\n    const fallback = `\n      .mj-carousel noinput { display:block !important; }\n      .mj-carousel noinput .mj-carousel-image-1 { display: block !important;  }\n      .mj-carousel noinput .mj-carousel-arrows,\n      .mj-carousel noinput .mj-carousel-thumbnails { display: none !important; }\n\n      [owa] .mj-carousel-thumbnail { display: none !important; }\n      \n      @media screen yahoo {\n          .mj-carousel-${this.carouselId}-icons-cell,\n          .mj-carousel-previous-icons,\n          .mj-carousel-next-icons {\n              display: none !important;\n          }\n\n          .mj-carousel-${carouselId}-radio-1:checked ${repeat(\n            '+ *',\n            length - 1,\n          )}+ .mj-carousel-content .mj-carousel-${carouselId}-thumbnail-1 {\n              border-color: transparent;\n          }\n      }\n    `\n\n    return `${carouselCss}\\n${fallback}`\n  }\n\n  getStyles() {\n    return {\n      carousel: {\n        div: {\n          display: 'table',\n          width: '100%',\n          'table-layout': 'fixed',\n          'text-align': 'center',\n          'font-size': '0px',\n        },\n        table: {\n          'caption-side': 'top',\n          display: 'table-caption',\n          'table-layout': 'fixed',\n          width: '100%',\n        },\n      },\n      images: {\n        td: {\n          padding: '0px',\n        },\n      },\n      controls: {\n        div: {\n          display: 'none',\n          'mso-hide': 'all',\n        },\n        img: {\n          display: 'block',\n          width: this.getAttribute('icon-width'),\n          height: 'auto',\n        },\n        td: {\n          'font-size': '0px',\n          display: 'none',\n          'mso-hide': 'all',\n          padding: '0px',\n        },\n      },\n    }\n  }\n\n  thumbnailsWidth() {\n    if (!this.props.children.length) return 0\n    return (\n      this.getAttribute('tb-width') ||\n      `${min([this.context.parentWidth / this.props.children.length, 110])}px`\n    )\n  }\n\n  imagesAttributes() {\n    return map(this.children, 'attributes')\n  }\n\n  generateRadios() {\n    return this.renderChildren(this.props.children, {\n      renderer: (component) => component.renderRadio(),\n      attributes: {\n        carouselId: this.carouselId,\n      },\n    })\n  }\n\n  generateThumbnails() {\n    if (!['visible', 'supported'].includes(this.getAttribute('thumbnails')))\n      return ''\n\n    return this.renderChildren(this.props.children, {\n      attributes: {\n        'tb-border': this.getAttribute('tb-border'),\n        'tb-border-radius': this.getAttribute('tb-border-radius'),\n        'tb-width': this.thumbnailsWidth(),\n        carouselId: this.carouselId,\n      },\n      renderer: (component) => component.renderThumbnail(),\n    })\n  }\n\n  generateControls(direction, icon) {\n    const iconWidth = parseInt(this.getAttribute('icon-width'), 10)\n\n    return `\n      <td\n        ${this.htmlAttributes({\n          class: `mj-carousel-${this.carouselId}-icons-cell`,\n          style: 'controls.td',\n        })}\n      >\n        <div\n          ${this.htmlAttributes({\n            class: `mj-carousel-${direction}-icons`,\n            style: 'controls.div',\n          })}\n        >\n          ${range(1, this.props.children.length + 1)\n            .map(\n              (i) => `\n              <label\n                ${this.htmlAttributes({\n                  for: `mj-carousel-${this.carouselId}-radio-${i}`,\n                  class: `mj-carousel-${direction} mj-carousel-${direction}-${i}`,\n                })}\n              >\n                <img\n                  ${this.htmlAttributes({\n                    src: icon,\n                    alt: direction,\n                    style: 'controls.img',\n                    width: iconWidth,\n                  })}\n                />\n              </label>\n            `,\n            )\n            .join('')}\n        </div>\n      </td>\n    `\n  }\n\n  generateImages() {\n    return `\n      <td\n        ${this.htmlAttributes({\n          style: 'images.td',\n        })}\n      >\n        <div\n          ${this.htmlAttributes({\n            class: 'mj-carousel-images',\n          })}\n        >\n          ${this.renderChildren(this.props.children, {\n            attributes: {\n              'border-radius': this.getAttribute('border-radius'),\n            },\n          })}\n        </div>\n      </td>\n    `\n  }\n\n  generateCarousel() {\n    return `\n      <table\n        ${this.htmlAttributes({\n          style: 'carousel.table',\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          width: '100%',\n          role: 'presentation',\n          class: 'mj-carousel-main',\n        })}\n      >\n        <tbody>\n          <tr>\n            ${this.generateControls('previous', this.getAttribute('left-icon'))}\n            ${this.generateImages()}\n            ${this.generateControls('next', this.getAttribute('right-icon'))}\n          </tr>\n        </tbody>\n      </table>\n    `\n  }\n\n  renderFallback() {\n    const { children } = this.props\n    if (children.length === 0) return ''\n\n    return msoConditionalTag(\n      this.renderChildren([children[0]], {\n        attributes: {\n          'border-radius': this.getAttribute('border-radius'),\n        },\n      }),\n    )\n  }\n\n  getChildContext() {\n    return {\n      ...this.context,\n      thumbnails: this.getAttribute('thumbnails'),\n    }\n  }\n\n  render() {\n    return `\n      ${msoConditionalTag(\n        `\n        <div\n          ${this.htmlAttributes({\n            class: 'mj-carousel',\n          })}\n        >\n          ${this.generateRadios()}\n          <div\n            ${this.htmlAttributes({\n              class: `mj-carousel-content mj-carousel-${this.carouselId}-content`,\n              style: 'carousel.div',\n            })}\n          >\n            ${this.generateThumbnails()}\n            ${this.generateCarousel()}\n          </div>\n        </div>\n      `,\n        true,\n      )}\n      ${this.renderFallback()}\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-carousel/src/CarouselImage.js",
    "content": "import { BodyComponent, suffixCssClasses } from 'mjml-core'\n\nexport default class MjCarouselImage extends BodyComponent {\n  static componentName = 'mj-carousel-image'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    alt: 'string',\n    href: 'string',\n    rel: 'string',\n    target: 'string',\n    title: 'string',\n    src: 'string',\n    'thumbnails-src': 'string',\n    'border-radius': 'unit(px,%){1,4}',\n    'tb-border': 'string',\n    'tb-border-radius': 'unit(px,%){1,4}',\n  }\n\n  static defaultAttributes = {\n    alt: '',\n    target: '_blank',\n  }\n\n  getStyles() {\n    const hasThumbnailsSupported = this.hasThumbnailsSupported()\n    return {\n      images: {\n        img: {\n          'border-radius': this.getAttribute('border-radius'),\n          display: 'block',\n          width: this.context.containerWidth,\n          'max-width': '100%',\n          height: 'auto',\n        },\n        firstImageDiv: {},\n        otherImageDiv: {\n          display: 'none',\n          'mso-hide': 'all',\n        },\n      },\n      radio: {\n        input: {\n          display: 'none',\n          'mso-hide': 'all',\n        },\n      },\n      thumbnails: {\n        a: {\n          border: this.getAttribute('tb-border'),\n          'border-radius': this.getAttribute('tb-border-radius'),\n          display: hasThumbnailsSupported ? 'none' : 'inline-block',\n          overflow: 'hidden',\n          width: this.getAttribute('tb-width'),\n        },\n        img: {\n          display: 'block',\n          width: '100%',\n          height: 'auto',\n        },\n      },\n    }\n  }\n\n  hasThumbnailsSupported() {\n    const thumbnails =\n      this.getAttribute('thumbnails') || this.context.thumbnails\n    return thumbnails === 'supported'\n  }\n\n  renderThumbnail() {\n    const { carouselId, src, alt, 'tb-width': width, target } = this.attributes\n    const imgIndex = this.props.index + 1\n    const cssClass = suffixCssClasses(\n      this.getAttribute('css-class'),\n      'thumbnail',\n    )\n\n    return `\n      <a\n        ${this.htmlAttributes({\n          style: 'thumbnails.a',\n          href: `#${imgIndex}`,\n          target,\n          class: `mj-carousel-thumbnail mj-carousel-${carouselId}-thumbnail mj-carousel-${carouselId}-thumbnail-${imgIndex} ${cssClass}`,\n        })}\n      >\n        <label ${this.htmlAttributes({\n          for: `mj-carousel-${carouselId}-radio-${imgIndex}`,\n        })}>\n          <img\n            ${this.htmlAttributes({\n              style: 'thumbnails.img',\n              src: this.getAttribute('thumbnails-src') || src,\n              alt,\n              width: parseInt(width, 10),\n            })}\n          />\n        </label>\n      </a>\n    `\n  }\n\n  renderRadio() {\n    const { index } = this.props\n    const carouselId = this.getAttribute('carouselId')\n\n    return `\n      <input\n        ${this.htmlAttributes({\n          class: `mj-carousel-radio mj-carousel-${carouselId}-radio mj-carousel-${carouselId}-radio-${\n            index + 1\n          }`,\n          checked: index === 0 ? 'checked' : null,\n          type: 'radio',\n          name: `mj-carousel-radio-${carouselId}`,\n          id: `mj-carousel-${carouselId}-radio-${index + 1}`,\n          style: 'radio.input',\n        })}\n      />\n    `\n  }\n\n  render() {\n    const { src, alt, href, rel, title } = this.attributes\n    const { index } = this.props\n\n    const image = `\n      <img\n        ${this.htmlAttributes({\n          title,\n          src,\n          alt,\n          style: 'images.img',\n          width: parseInt(this.context.containerWidth, 10),\n          border: '0',\n        })} />\n    `\n\n    const cssClass = this.getAttribute('css-class') || ''\n\n    return `\n      <div\n        ${this.htmlAttributes({\n          class: `mj-carousel-image mj-carousel-image-${index + 1} ${cssClass}`,\n          style: index === 0 ? 'images.firstImageDiv' : 'images.otherImageDiv',\n        })}\n      >\n        ${\n          href\n            ? `<a ${this.htmlAttributes({ href, rel, target: '_blank' })}>${image}</a>`\n            : image\n        }\n      </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-carousel/src/index.js",
    "content": "export { default as Carousel } from './Carousel'\nexport { default as CarouselImage } from './CarouselImage'\n"
  },
  {
    "path": "packages/mjml-cli/README.md",
    "content": "## mjml-cli\n\n# Installation\n\nWe recommend installing and using MJML locally, in a project folder where you'll use MJML:\n```bash\nnpm install mjml\n```\nIn the folder where you installed MJML you can now run:\n```bash\n./node_modules/.bin/mjml input.mjml\n```\nTo avoid typing `./node_modules/.bin/`, add it to your PATH:\n```bash\nexport PATH=\"$PATH:./node_modules/.bin\"\n```\nYou can now run MJML directly, in that folder:\n```bash\nmjml input.mjml\n```\n\nMJML is written with [NodeJS](https://nodejs.org/en/)\nYou can download and install the MJML engine from [NPM](https://www.npmjs.com).\n\n# Command Line Interface\n\nIn addition to the translation engine, which converts MJML to email HTML, we've bundled a Command Line Interface (CLI) helping you to achieve the basic features it offers and integrate it seamlessly in your development flow.\n\n### Render MJML to HTML\n\n```bash\nmjml input.mjml\n```\n\nIt will output a HTML file called `input.html`.\nInput can also be a directory.\n\n### Migrate MJML3 to MJML4\n\n```bash\n$> mjml -m input.mjml -o result.mjml\n```\n\nIt will output a MJML file called `result.mjml`.\n\n### Validate MJML\n\n```bash\n$> mjml -v input.mjml\n```\n\nIt will log validation errors. If there are errors, exits with code 1. Otherwise, exits with code 0.\n\n### Render and redirect the result to stdout\n\n```bash\nmjml -s input.mjml\n\n# or\n\nmjml --stdout input.mjml\n```\n\n### Render and redirect the result to a file\n\n```bash\nmjml input.mjml -o my-email.html\n\n# or\n\nmjml input.mjml --output my-email.html\n```\n\nYou can output the resulting email responsive HTML in a file.\nIf the output file does not exist it will be created, but output directories must already exist.\nIf output is a directory, output file(s) will be `output/input-file-name.html`\n\n### Set the validation mode\n\n```bash\nmjml -l skip -r input.mjml\n```\n\nAccepted values are\n- 'normal' : *(default)* will display validation messages but compile anyway\n- 'skip' : the file is rendered without being validated\n- 'strict' : will throw an error if validation fails\n\n### Watch changes on a file\n\n```bash\nmjml -w input.mjml\n\n# or\n\nmjml --watch input.mjml\n```\n\nIf you like live-coding, you might want to use the `-w` option that enables you to re-render your file every time you save it.\nIt can be time-saving when you can just split you screen and see the HTML output modified when you modify your MJML.\n\nOf course, the `-w` option can be used with an `--output` option too.\n\n### Available options\n\n```bash\nmjml input.mjml --config.optionName value\n\n# or\n\nmjml input.mjml -c.optionName value\n```\n\nAll the options that can be passed to mjml2html (see general documentation) can be provided. The most common ones are detailed below.\n\n### Minify and beautify the output HTML\n\n```bash\n$> mjml input.mjml --config.beautify true --config.minify false\n```\n\nThese are the default options.  \n\n### Change minify options\n\n```bash\n$> mjml input.mjml --config.minifyOptions='{\"minifyCSS\": true, \"removeEmptyAttributes\": false}'\n```\n\nThe defaults are \"collapseWhitespace\": true, \"minifyCSS\": false, \"removeEmptyAttributes\": true  \nSee html-minifier documentation for more available options\n\n### Change juice options (library used for inlining mj-style css)\n\n```bash\n$> mjml input.mjml --config.juiceOptions='{\"preserveImportant\": true}'\n```\n\nThe defaults are \"applyStyleTags\": false, \"insertPreservedExtraCss\": false, \"removeStyleTags\": false  \nSee juice documentation for more available options  \n\n### Preserve specific tags when using inline mj-style\n\n```bash\n$> mjml input.mjml --config.juicePreserveTags='{\"myTag\": { \"start\": \"<#\", \"end\": \"</#\" }}'\n```\n\nWhen using `<mj-style inline=\"inline\">` the css will be inlined using the juice library. As a side effect, juice will convert all tags' attributes into lower case. If you need to preserve some cases (i.e. for a templating lib) you can specify the tags to preserve. With the example above, all tags of the form `<# myVar=\"\" >` or `</# myVar=\"\" >` will be left untouched. By default juice already ignores `<% EJS %>` and `{{ HBS }}` tags.\n\n### Override base path for mj-include relative paths\n\n```bash\n$> mjml ./my-project/input.mjml --config.filePath ./my-partials/\n```\n\nIf you like to keep your partials together and you want to be able to mj-include them without having to change the relative path of the includes depending on the compiled file path, you can use this option. In this exemple, `<mj-include path=\"./header.mjml\" />` will include `./my-partials/header.mjml`, ignoring the actual path of `input.mjml`.\n\n### Log error stack\n\n```bash\n$> mjml input.mjml --config.stack true\n```\n"
  },
  {
    "path": "packages/mjml-cli/bin/mjml",
    "content": "#!/usr/bin/env node\n\nrequire('../lib/client.js')()\n"
  },
  {
    "path": "packages/mjml-cli/package.json",
    "content": "{\n  \"name\": \"mjml-cli\",\n  \"description\": \"MJML: the only framework that makes responsive-email easy\",\n  \"version\": \"4.18.0\",\n  \"main\": \"bin/mjml\",\n  \"bin\": {\n    \"mjml-cli\": \"bin/mjml\"\n  },\n  \"files\": [\n    \"bin\",\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-cli\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"chokidar\": \"^3.0.0\",\n    \"glob\": \"^10.3.10\",\n    \"html-minifier\": \"^4.0.0\",\n    \"js-beautify\": \"^1.6.14\",\n    \"lodash\": \"^4.17.21\",\n    \"minimatch\": \"^9.0.3\",\n    \"mjml-core\": \"4.18.0\",\n    \"mjml-migrate\": \"4.18.0\",\n    \"mjml-parser-xml\": \"4.18.0\",\n    \"mjml-validator\": \"4.18.0\",\n    \"yargs\": \"^17.7.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-cli/src/client.js",
    "content": "import path from 'path'\nimport yargs from 'yargs'\nimport { flow, pick, isNil, negate, pickBy } from 'lodash/fp'\nimport { isArray, isEmpty, map, get, omit } from 'lodash'\nimport { html as htmlBeautify } from 'js-beautify'\nimport { minify as htmlMinify } from 'html-minifier'\n\nimport mjml2html, { components, initializeType } from 'mjml-core'\nimport migrate from 'mjml-migrate'\nimport validate, { dependencies } from 'mjml-validator'\nimport MJMLParser from 'mjml-parser-xml'\n\nimport { version as coreVersion } from 'mjml-core/package.json'\nimport readFile, { flatMapPaths } from './commands/readFile'\nimport watchFiles from './commands/watchFiles'\nimport readStream from './commands/readStream'\nimport outputToFile, { isDirectory } from './commands/outputToFile'\nimport outputToConsole from './commands/outputToConsole'\n\nimport { version as cliVersion } from '../package.json'\nimport DEFAULT_OPTIONS from './helpers/defaultOptions'\n\nconst beautifyConfig = {\n  indent_size: 2,\n  wrap_attributes_indent_size: 2,\n  max_preserve_newline: 0,\n  preserve_newlines: false,\n  end_with_newline: true,\n}\n\nconst minifyConfig = {\n  collapseWhitespace: true,\n  minifyCSS: false,\n  caseSensitive: true,\n  removeEmptyAttributes: true,\n}\n\nexport default async () => {\n  let EXIT_CODE = 0\n  let KEEP_OPEN = false\n\n  const error = (msg) => {\n    console.error('\\nCommand line error:') // eslint-disable-line no-console\n    console.error(msg) // eslint-disable-line no-console\n\n    process.exit(1)\n  }\n\n  const pickArgs = (args) =>\n    flow(\n      pick(args),\n      pickBy((e) => negate(isNil)(e) && !(isArray(e) && isEmpty(e))),\n    )\n\n  const { argv } = yargs\n    .version(false) // cf. https://github.com/yargs/yargs/issues/961\n    .options({\n      r: {\n        alias: 'read',\n        describe: 'Compile MJML File(s)',\n        type: 'array',\n      },\n      m: {\n        alias: 'migrate',\n        describe: 'Migrate MJML3 File(s) (deprecated)',\n        type: 'array',\n      },\n      v: {\n        alias: 'validate',\n        describe: 'Run validator on File(s)',\n        type: 'array',\n      },\n      w: {\n        alias: 'watch',\n        type: 'array',\n        describe: 'Watch and compile MJML File(s) when modified',\n      },\n      i: {\n        alias: 'stdin',\n        describe: 'Compiles MJML from input stream',\n      },\n      s: {\n        alias: 'stdout',\n        describe: 'Output HTML to stdout',\n      },\n      o: {\n        alias: 'output',\n        type: 'string',\n        describe: 'Filename/Directory to output compiled files',\n      },\n      c: {\n        alias: 'config',\n        type: 'object',\n        describe: 'Option to pass to mjml-core',\n      },\n      version: {\n        alias: 'V',\n      },\n      noStdoutFileComment: {\n        type: 'boolean',\n        describe: 'Add no file comment to stdout',\n      },\n    })\n    .help()\n    .version(`mjml-core: ${coreVersion}\\nmjml-cli: ${cliVersion}`)\n\n  let juiceOptions\n  let minifyOptions\n  let juicePreserveTags\n  let fonts\n\n  try {\n    juiceOptions =\n      argv.c && argv.c.juiceOptions && JSON.parse(argv.c.juiceOptions)\n  } catch (e) {\n    error(`Failed to decode JSON for config.juiceOptions argument`)\n  }\n\n  try {\n    minifyOptions =\n      argv.c && argv.c.minifyOptions && JSON.parse(argv.c.minifyOptions)\n  } catch (e) {\n    error(`Failed to decode JSON for config.minifyOptions argument`)\n  }\n\n  try {\n    juicePreserveTags =\n      argv.c && argv.c.juicePreserveTags && JSON.parse(argv.c.juicePreserveTags)\n  } catch (e) {\n    error(`Failed to decode JSON for config.juicePreserveTags argument`)\n  }\n\n  try {\n    fonts = argv.c && argv.c.fonts && JSON.parse(argv.c.fonts)\n  } catch (e) {\n    error(`Failed to decode JSON for config.fonts argument`)\n  }\n\n  const filePath = argv.c && argv.c.filePath\n\n  const config = Object.assign(\n    DEFAULT_OPTIONS,\n    argv.c,\n    fonts && { fonts },\n    minifyOptions && { minifyOptions },\n    juiceOptions && { juiceOptions },\n    juicePreserveTags && { juicePreserveTags },\n    argv.c && argv.c.keepComments === 'false' && { keepComments: false },\n  )\n\n  const inputArgs = pickArgs(['r', 'w', 'i', '_', 'm', 'v'])(argv)\n  const outputArgs = pickArgs(['o', 's'])(argv)\n\n  // implies (until yargs pr is accepted)\n  ;[\n    [Object.keys(inputArgs).length === 0, 'No input argument received'],\n    [Object.keys(inputArgs).length > 1, 'Too many input arguments received'],\n    [Object.keys(outputArgs).length > 1, 'Too many output arguments received'],\n    [\n      argv.w && argv.w.length > 1 && !argv.o,\n      'Need an output option when watching files',\n    ],\n    [\n      argv.w &&\n        argv.w.length > 1 &&\n        argv.o &&\n        !isDirectory(argv.o) &&\n        argv.o !== '',\n      'Need an output option when watching files',\n    ],\n  ].forEach((v) => (v[0] ? error(v[1]) : null))\n\n  const inputOpt = Object.keys(inputArgs)[0]\n  const outputOpt = Object.keys(outputArgs)[0] || 's'\n\n  const inputFiles = isArray(inputArgs[inputOpt])\n    ? inputArgs[inputOpt]\n    : [inputArgs[inputOpt]]\n  const inputs = []\n\n  switch (inputOpt) {\n    case 'r':\n    case 'v':\n    case 'm':\n    case '_': {\n      flatMapPaths(inputFiles).forEach((file) => {\n        inputs.push(readFile(file))\n      })\n\n      if (!inputs.length) {\n        error('No input files found')\n        return\n      }\n      break\n    }\n    case 'w':\n      watchFiles(inputFiles, {\n        ...argv,\n        config,\n        minifyConfig,\n        beautifyConfig,\n      })\n      KEEP_OPEN = true\n      break\n    case 'i':\n      inputs.push(await readStream())\n      break\n    default:\n      error('Command line error: Incorrect input options')\n  }\n\n  const convertedStream = []\n  const failedStream = []\n\n  inputs.forEach((i) => {\n    try {\n      let compiled\n      switch (inputOpt) {\n        case 'm':\n          compiled = { html: migrate(i.mjml, { beautify: true }) }\n          break\n        case 'v': // eslint-disable-next-line no-case-declarations\n          const mjmlJson = MJMLParser(i.mjml, {\n            components,\n            filePath: filePath || i.file,\n            actualPath: i.file,\n          })\n          compiled = {\n            errors: validate(mjmlJson, {\n              dependencies,\n              components,\n              initializeType,\n            }),\n          }\n          break\n\n        default: {\n          const beautify = config.beautify && config.beautify !== 'false'\n          const minify = config.minify && config.minify !== 'false'\n\n          compiled = mjml2html(i.mjml, {\n            ...omit(config, ['minify', 'beautify']),\n            filePath: filePath || i.file,\n            actualPath: i.file,\n          })\n          if (beautify) {\n            compiled.html = htmlBeautify(compiled.html, beautifyConfig)\n          }\n          if (minify) {\n            compiled.html = htmlMinify(compiled.html, {\n              ...minifyConfig,\n              ...config.minifyOptions,\n            })\n          }\n        }\n      }\n\n      convertedStream.push({ ...i, compiled })\n    } catch (e) {\n      EXIT_CODE = 2\n      failedStream.push({ file: i.file, error: e })\n    }\n  })\n\n  convertedStream.forEach((s) => {\n    if (get(s, 'compiled.errors.length')) {\n      console.error(map(s.compiled.errors, 'formattedMessage').join('\\n')) // eslint-disable-line no-console\n    }\n  })\n\n  failedStream.forEach(({ error, file }) => {\n    console.error(`${file ? `File: ${file}\\n` : null}${error}`) // eslint-disable-line no-console\n\n    if (config.stack) {\n      console.error(error.stack) // eslint-disable-line no-console\n    }\n  })\n\n  if (inputOpt === 'v') {\n    const isInvalid =\n      failedStream.length ||\n      convertedStream.some((s) => !!get(s, 'compiled.errors.length'))\n\n    if (isInvalid) {\n      error('Validation failed')\n      return\n    }\n    process.exitCode = 0\n    return\n  }\n\n  if (!KEEP_OPEN && convertedStream.length === 0) {\n    error('Input file(s) failed to render')\n  }\n\n  switch (outputOpt) {\n    case 'o': {\n      if (inputs.length > 1 && !isDirectory(argv.o) && argv.o !== '') {\n        error(\n          `Multiple input files, but output option should be either an existing directory or an empty string: ${argv.o} given`,\n        )\n      }\n\n      const fullOutputPath = path.parse(path.resolve(process.cwd(), argv.o))\n\n      if (inputs.length === 1 && !isDirectory(fullOutputPath.dir)) {\n        error(`Output directory doesn’t exist for path : ${argv.o}`)\n      }\n\n      Promise.all(convertedStream.map(outputToFile(argv.o)))\n        .then(() => {\n          if (!KEEP_OPEN) {\n            process.exitCode = EXIT_CODE\n          }\n        })\n        .catch(({ outputName, err }) => {\n          if (!KEEP_OPEN) {\n            error(`Error writing file - ${outputName} : ${err}`)\n          }\n        })\n      break\n    }\n    case 's': {\n      const addFileHeaderComment = !argv.noStdoutFileComment\n      Promise.all(\n        convertedStream.map((converted) =>\n          outputToConsole(converted, addFileHeaderComment),\n        ),\n      )\n        .then(() => (process.exitCode = EXIT_CODE)) // eslint-disable-line no-return-assign\n        .catch(() => (process.exitCode = 1)) // eslint-disable-line no-return-assign\n      break\n    }\n    default:\n      error('Command line error: No output option available')\n  }\n}\n"
  },
  {
    "path": "packages/mjml-cli/src/commands/outputToConsole.js",
    "content": "export default ({ compiled: { html }, file }, addFileHeaderComment) =>\n  new Promise((resolve) => {\n    let output = ''\n    if (addFileHeaderComment) {\n      output = `<!-- FILE: ${file} -->\\n`\n    }\n    output += `${html}\\n`\n\n    process.stdout.write(output, resolve)\n  })\n"
  },
  {
    "path": "packages/mjml-cli/src/commands/outputToFile.js",
    "content": "import fs from 'fs'\nimport path from 'path'\n\nexport const isDirectory = (file) => {\n  try {\n    const outputPath = path.resolve(process.cwd(), file)\n\n    return fs.statSync(outputPath).isDirectory()\n  } catch (e) {\n    return false\n  }\n}\n\nconst replaceExtension = (input) =>\n  input.replace(\n    '.mjml',\n    input.replace('.mjml', '').match(/(.)*\\.(.)+$/g) ? '' : '.html',\n  )\n\nconst stripPath = (input) => input.match(/[^/\\\\]+$/g)[0]\n\nconst makeGuessOutputName = (outputPath) => {\n  if (isDirectory(outputPath)) {\n    return (input) => path.join(outputPath, replaceExtension(stripPath(input)))\n  }\n\n  return (input) => {\n    if (!outputPath) {\n      return replaceExtension(stripPath(input))\n    }\n\n    return outputPath\n  }\n}\n\nexport default (outputPath) => {\n  const guessOutputName = makeGuessOutputName(outputPath)\n\n  return ({ file, compiled: { html } }) =>\n    new Promise((resolve, reject) => {\n      const outputName = guessOutputName(file)\n\n      fs.writeFile(outputName, html, (err) => {\n        if (err) {\n          // eslint-disable-next-line prefer-promise-reject-errors\n          return reject({ outputName, err })\n        }\n\n        return resolve(outputName)\n      })\n    })\n}\n"
  },
  {
    "path": "packages/mjml-cli/src/commands/readFile.js",
    "content": "import fs from 'fs'\nimport { sync } from 'glob'\nimport { flatMap } from 'lodash'\n\nexport const flatMapPaths = (paths) =>\n  flatMap(paths, (p) => sync(p, { nodir: true }))\n\nexport default (path) => {\n  try {\n    return { file: path, mjml: fs.readFileSync(path).toString() }\n  } catch (e) {\n    // eslint-disable-next-line\n    console.warn(`Cannot read file: ${path} doesn't exist or no access`, e)\n    return {}\n  }\n}\n"
  },
  {
    "path": "packages/mjml-cli/src/commands/readStream.js",
    "content": "const stdinSync = () =>\n  new Promise((res) => {\n    let buffer = ''\n\n    const stream = process.stdin\n\n    stream.on('data', (chunck) => {\n      buffer += chunck\n    })\n\n    stream.on('end', () => res(buffer))\n  })\n\nexport default async () => {\n  const mjml = await stdinSync()\n  return { mjml }\n}\n"
  },
  {
    "path": "packages/mjml-cli/src/commands/watchFiles.js",
    "content": "/* eslint-disable no-console */\nimport chokidar from 'chokidar'\nimport { sync } from 'glob'\nimport { match } from 'minimatch'\nimport path from 'path'\nimport mjml2html from 'mjml-core'\nimport { flow, pickBy, flatMap, uniq, difference, remove } from 'lodash/fp'\nimport { omit } from 'lodash'\nimport { html as htmlBeautify } from 'js-beautify'\nimport { minify as htmlMinify } from 'html-minifier'\n\nimport readFile from './readFile'\nimport makeOutputToFile from './outputToFile'\nimport fileContext from '../helpers/fileContext'\n\nlet dirty = []\n\nconst _flatMap = flatMap.convert({ cap: false }) // eslint-disable-line no-underscore-dangle\nconst flatMapAndJoin = _flatMap((v, k) => v.map((p) => path.join(k, p)))\nconst flatMapKeyAndValues = flow(\n  _flatMap((v, k) => [k, ...v]),\n  uniq,\n)\n\nexport default (input, options) => {\n  const dependencies = {}\n  const outputToFile = makeOutputToFile(options.o)\n  const getRelatedFiles = (file) =>\n    flow(\n      pickBy((v, k) => k === file || v.indexOf(file) !== -1),\n      Object.keys,\n    )(dependencies)\n  const synchronyzeWatcher = (filePath) => {\n    getRelatedFiles(filePath).forEach((f) => {\n      dependencies[f] = fileContext(f, options.config.filePath)\n\n      if (dirty.indexOf(f) === -1) {\n        dirty.push(f)\n      }\n    })\n\n    /* eslint-disable no-use-before-define */\n    const files = {\n      toWatch: flatMapKeyAndValues(dependencies),\n      watched: flatMapAndJoin(watcher.getWatched()),\n    }\n\n    watcher.add(difference(files.toWatch, files.watched))\n    watcher.unwatch(difference(files.watched, files.toWatch))\n    /* eslint-enable no-use-before-define */\n  }\n  const readAndCompile = flow(\n    (file) => ({ file, content: readFile(file).mjml }),\n    (args) => {\n      const { config, beautifyConfig, minifyConfig } = options\n      const beautify = config.beautify && config.beautify !== 'false'\n      const minify = config.minify && config.minify !== 'false'\n\n      const compiled = mjml2html(args.content, {\n        filePath: args.file,\n        actualPath: args.file,\n        ...omit(config, ['minify', 'beautify']),\n      })\n      if (beautify) {\n        compiled.html = htmlBeautify(compiled.html, beautifyConfig)\n      }\n      if (minify) {\n        compiled.html = htmlMinify(compiled.html, {\n          ...minifyConfig,\n          ...config.minifyOptions,\n        })\n      }\n\n      return {\n        ...args,\n        compiled,\n      }\n    },\n    (args) => {\n      const {\n        compiled: { errors },\n      } = args\n\n      errors.forEach((e) => console.warn(e.formattedMessage))\n\n      return args\n    },\n    (args) =>\n      outputToFile(args)\n        .then(() => console.log(`${args.file} - Successfully compiled`))\n        .catch(() => console.log(`${args.file} - Error while compiling file`)),\n  )\n\n  const watcher = chokidar\n    .watch(input.map((i) => i.replace(/\\\\/g, '/')))\n    .on('change', (file) => synchronyzeWatcher(path.resolve(file)))\n    .on('add', (file) => {\n      const filePath = path.resolve(file)\n      console.log(`Now watching file: ${filePath}`)\n\n      const matchInputOption = input.reduce(\n        (found, file) =>\n          found || match(sync(path.resolve(file)), filePath)?.length > 0,\n        false,\n      )\n\n      if (matchInputOption) {\n        dependencies[filePath] = getRelatedFiles(filePath)\n      }\n\n      synchronyzeWatcher(filePath)\n    })\n    .on('unlink', (file) => {\n      const filePath = path.resolve(file)\n\n      delete dependencies[path.resolve(filePath)]\n\n      remove(dirty, (f) => f === filePath)\n\n      synchronyzeWatcher(filePath)\n    })\n\n  setInterval(() => {\n    dirty.forEach((f) => {\n      console.log(`${f} - Change detected`)\n      try {\n        readAndCompile(f)\n      } catch (e) {\n        console.log(`${f} - Error while rendering the file : `, e)\n      }\n    })\n    dirty = []\n  }, 500)\n\n  return []\n}\n/* eslint-enable no-console */\n"
  },
  {
    "path": "packages/mjml-cli/src/helpers/defaultOptions.js",
    "content": "export default {\n  beautify: true,\n  minify: false,\n}\n"
  },
  {
    "path": "packages/mjml-cli/src/helpers/fileContext.js",
    "content": "import fs from 'fs'\nimport path from 'path'\n\nconst includeRegexp =\n  /<mj-include[^<>]+path=['\"](.*(?:\\.mjml|\\.css|\\.html))['\"]\\s*[^<>]*(\\/>|>\\s*<\\/mj-include>)/gi\n\nconst ensureIncludeIsSupportedFile = (file) =>\n  path.extname(file).match(/\\.mjml|\\.css|\\.html/) ? file : `${file}.mjml`\n\nconst error = (e) => console.error(e.stack || e) // eslint-disable-line no-console\n\nexport default (baseFile, filePath) => {\n  const filesIncluded = []\n\n  let filePathDirectory = ''\n  if (filePath) {\n    try {\n      const isFilePathDir = fs.lstatSync(filePath).isDirectory()\n\n      filePathDirectory = isFilePathDir ? filePath : path.dirname(filePath)\n    } catch (e) {\n      if (e.code === 'ENOENT') {\n        throw new Error('Specified filePath does not exist')\n      } else {\n        throw e\n      }\n    }\n  }\n\n  const readIncludes = (dir, file, base) => {\n    const currentFile = path.resolve(\n      dir\n        ? path.join(dir, ensureIncludeIsSupportedFile(file))\n        : ensureIncludeIsSupportedFile(file),\n    )\n\n    const currentDirectory = path.dirname(currentFile)\n\n    const includes = new RegExp(includeRegexp)\n\n    let content\n    try {\n      content = fs.readFileSync(currentFile, 'utf8')\n    } catch (e) {\n      error(`File not found ${currentFile} from ${base}`)\n      return\n    }\n\n    let matchgroup = includes.exec(content)\n    while (matchgroup != null) {\n      const includedFile = ensureIncludeIsSupportedFile(matchgroup[1])\n\n      // when reading first level of includes we must join the path specified in filePath\n      // when reading further nested includes, just take parent dir as base\n      const targetDir =\n        filePath && file === baseFile ? filePathDirectory : currentDirectory\n\n      const includedFilePath = path.resolve(path.join(targetDir, includedFile))\n\n      filesIncluded.push(includedFilePath)\n\n      readIncludes(targetDir, includedFile, currentFile)\n      matchgroup = includes.exec(content)\n    }\n  }\n\n  readIncludes(null, baseFile, baseFile)\n\n  return filesIncluded\n}\n"
  },
  {
    "path": "packages/mjml-column/README.md",
    "content": "### mj-column\n\nColumns enable you to organize the content of your sections into distinct columns which stack when viewed on a mobile device.\n\nThey must be located within `mj-section` tags in order to be considered by the engine.\n\n<div class=\"alert alert-caution\" role=\"alert\">\n  <p>Caution</p>\n  <p>The sum of columns in a section cannot be greater than\n      the width of the parent <code>mj-section</code> (or 100%).</p>\n</div>\n\nEvery single column has to contain something because they are responsive containers, and will be vertically stacked on a mobile view. Any standard component, or component that you have defined and registered, can be placed within a column – except `mj-column` or `mj-section` elements.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <!-- Your first column -->\n      </mj-column>\n      <mj-column>\n        <!-- Your second column -->\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-caution\" role=\"alert\">\n  <p>Caution</p>\n  <p>Columns are used as a container for your content and should not be used to offset. Any MJML component included in a column will have a width equivalent to 100% of this column's width.</p>\n</div>\n\n<div class=\"alert alert-caution\" role=\"alert\">\n  <p>Caution</p>\n  <p>Neither the <code>mj-column</code> or <code>mj-section</code> tags can be nested in an <code>mj-column</code> tag</p>\n</div>\n\n#### Attributes\n\n| attribute              | accepts                 | description                                                                              | default attributes                             |\n| ---------------------- | ----------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------- |\n| background-color       | CSS color formats       | background color for a column                                                            |                                                |\n| border                 | string                  | CSS border format                                                                        |                                                |\n| border-bottom          | string                  | CSS border format                                                                        |                                                |\n| border-left            | string                  | CSS border format                                                                        |                                                |\n| border-radius          | `px` `%`                | border radius                                                                            |                                                |\n| border-right           | string                  | CSS border format                                                                        |                                                |\n| border-top             | string                  | CSS border format                                                                        |                                                |\n| css-class              | string                  | class name, added to the root HTML element created                                       |                                                |\n| direction              | `ltr` `rtl`             | set the display order of direct children                                                 | `ltr`                                          |\n| inner-background-color | CSS color formats       | inner background color for column; requires a padding                                    |                                                |\n| inner-border           | string                  | CSS border; requires a padding format                                                    |                                                |\n| inner-border-bottom    | string                  | CSS border format; requires a padding                                                    |                                                |\n| inner-border-left      | string                  | CSS border format; requires a padding                                                    |                                                |\n| inner-border-radius    | `px` `%`                | border radius ; requires a padding                                                       |                                                |\n| inner-border-right     | string                  | CSS border format; requires a padding                                                    |                                                |\n| inner-border-top       | string                  | CSS border format; requires a padding                                                    |                                                |\n| padding                | `px` `%`                | column padding, supports up to 4 parameters                                              |                                                |\n| padding-bottom         | `px` `%`                | column bottom padding                                                                    |                                                |\n| padding-left           | `px` `%`                | column left padding                                                                      |                                                |\n| padding-right          | `px` `%`                | column right padding                                                                     |                                                |\n| padding-top            | `px` `%`                | column top padding                                                                       |                                                |\n| width                  | `px` `%`                | column width                                                                             | (100 / number of non-raw elements in section)% |\n| vertical-align         | `top` `middle` `bottom` | vertical alignment.<br>Note: `middle` only applies when all `mj-column` instances use it | `top`                                          |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/column\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-column/package.json",
    "content": "{\n  \"name\": \"mjml-column\",\n  \"description\": \"mjml-column\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-column\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-column/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default class MjColumn extends BodyComponent {\n  static componentName = 'mj-column'\n\n  static allowedAttributes = {\n    'background-color': 'color',\n    border: 'string',\n    'border-bottom': 'string',\n    'border-left': 'string',\n    'border-radius': 'unit(px,%){1,4}',\n    'border-right': 'string',\n    'border-top': 'string',\n    direction: 'enum(ltr,rtl)',\n    'inner-background-color': 'color',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    'inner-border': 'string',\n    'inner-border-bottom': 'string',\n    'inner-border-left': 'string',\n    'inner-border-radius': 'unit(px,%){1,4}',\n    'inner-border-right': 'string',\n    'inner-border-top': 'string',\n    padding: 'unit(px,%){1,4}',\n    'vertical-align': 'enum(top,bottom,middle)',\n    width: 'unit(px,%)',\n  }\n\n  static defaultAttributes = {\n    direction: 'ltr',\n    'vertical-align': 'top',\n  }\n\n  getChildContext() {\n    const { containerWidth: parentWidth } = this.context\n    const { nonRawSiblings } = this.props\n    const { borders, paddings } = this.getBoxWidths()\n    const innerBorders =\n      this.getShorthandBorderValue('left', 'inner-border') +\n      this.getShorthandBorderValue('right', 'inner-border')\n\n    const allPaddings = paddings + borders + innerBorders\n\n    let containerWidth =\n      this.getAttribute('width') ||\n      `${parseFloat(parentWidth) / nonRawSiblings}px`\n\n    const { unit, parsedWidth } = widthParser(containerWidth, {\n      parseFloatToInt: false,\n    })\n\n    if (unit === '%') {\n      containerWidth = `${\n        (parseFloat(parentWidth) * parsedWidth) / 100 - allPaddings\n      }px`\n    } else {\n      containerWidth = `${parsedWidth - allPaddings}px`\n    }\n\n    return {\n      ...this.context,\n      containerWidth,\n    }\n  }\n\n  getStyles() {\n    const hasBorderRadius = this.hasBorderRadius()\n    const hasInnerBorderRadius = this.hasInnerBorderRadius()\n\n    const tableStyle = {\n      'background-color': this.getAttribute('background-color'),\n      border: this.getAttribute('border'),\n      'border-bottom': this.getAttribute('border-bottom'),\n      'border-left': this.getAttribute('border-left'),\n      'border-radius': this.getAttribute('border-radius'),\n      'border-right': this.getAttribute('border-right'),\n      'border-top': this.getAttribute('border-top'),\n      'vertical-align': this.getAttribute('vertical-align'),\n      ...(hasBorderRadius && { 'border-collapse': 'separate' }),\n    }\n\n    return {\n      div: {\n        'font-size': '0px',\n        'text-align': 'left',\n        direction: this.getAttribute('direction'),\n        display: 'inline-block',\n        'vertical-align': this.getAttribute('vertical-align'),\n        width: this.getMobileWidth(),\n      },\n      table: {\n        ...(this.hasGutter()\n          ? {\n              'background-color': this.getAttribute('inner-background-color'),\n              border: this.getAttribute('inner-border'),\n              'border-bottom': this.getAttribute('inner-border-bottom'),\n              'border-left': this.getAttribute('inner-border-left'),\n              'border-radius': this.getAttribute('inner-border-radius'),\n              'border-right': this.getAttribute('inner-border-right'),\n              'border-top': this.getAttribute('inner-border-top'),\n            }\n          : tableStyle),\n        ...(hasInnerBorderRadius && { 'border-collapse': 'separate' }),\n      },\n      tdOutlook: {\n        'vertical-align': this.getAttribute('vertical-align'),\n        width: this.getWidthAsPixel(),\n      },\n      gutter: {\n        ...tableStyle,\n        padding: this.getAttribute('padding'),\n        'padding-top': this.getAttribute('padding-top'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n        'padding-left': this.getAttribute('padding-left'),\n      },\n    }\n  }\n\n  getMobileWidth() {\n    const { containerWidth } = this.context\n    const { nonRawSiblings } = this.props\n    const width = this.getAttribute('width')\n    const mobileWidth = this.getAttribute('mobileWidth')\n\n    if (mobileWidth !== 'mobileWidth') {\n      return '100%'\n    }\n    if (width === undefined) {\n      return `${parseInt(100 / nonRawSiblings, 10)}%`\n    }\n\n    const { unit, parsedWidth } = widthParser(width, {\n      parseFloatToInt: false,\n    })\n\n    switch (unit) {\n      case '%':\n        return width\n      case 'px':\n      default:\n        return `${(parsedWidth / parseInt(containerWidth, 10)) * 100}%`\n    }\n  }\n\n  getWidthAsPixel() {\n    const { containerWidth } = this.context\n\n    const { unit, parsedWidth } = widthParser(this.getParsedWidth(true), {\n      parseFloatToInt: false,\n    })\n\n    if (unit === '%') {\n      return `${(parseFloat(containerWidth) * parsedWidth) / 100}px`\n    }\n    return `${parsedWidth}px`\n  }\n\n  getParsedWidth(toString) {\n    const { nonRawSiblings } = this.props\n\n    const width = this.getAttribute('width') || `${100 / nonRawSiblings}%`\n\n    const { unit, parsedWidth } = widthParser(width, {\n      parseFloatToInt: false,\n    })\n\n    if (toString) {\n      return `${parsedWidth}${unit}`\n    }\n\n    return {\n      unit,\n      parsedWidth,\n    }\n  }\n\n  getColumnClass() {\n    const { addMediaQuery } = this.context\n\n    let className = ''\n\n    const { parsedWidth, unit } = this.getParsedWidth()\n    const formattedClassNb = parsedWidth.toString().replace('.', '-')\n\n    switch (unit) {\n      case '%':\n        className = `mj-column-per-${formattedClassNb}`\n        break\n\n      case 'px':\n      default:\n        className = `mj-column-px-${formattedClassNb}`\n        break\n    }\n\n    // Add className to media queries\n    addMediaQuery(className, {\n      parsedWidth,\n      unit,\n    })\n\n    return className\n  }\n\n  hasBorderRadius() {\n    const borderRadius = this.getAttribute('border-radius')\n    return borderRadius !== '' && typeof borderRadius !== 'undefined'\n  }\n\n  hasInnerBorderRadius() {\n    const innerBorderRadius = this.getAttribute('inner-border-radius')\n    return innerBorderRadius !== '' && typeof innerBorderRadius !== 'undefined'\n  }\n\n  hasGutter() {\n    return [\n      'padding',\n      'padding-bottom',\n      'padding-left',\n      'padding-right',\n      'padding-top',\n    ].some((attr) => this.getAttribute(attr) != null)\n  }\n\n  renderGutter() {\n    const hasBorderRadius = this.hasBorderRadius()\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n          width: '100%',\n          ...(hasBorderRadius && {\n            style: { 'border-collapse': 'separate' },\n          }),\n        })}\n      >\n        <tbody>\n          <tr>\n            <td ${this.htmlAttributes({ style: 'gutter' })}>\n              ${this.renderColumn()}\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    `\n  }\n\n  renderColumn() {\n    const { children } = this.props\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n          style: 'table',\n          width: '100%',\n        })}\n      >\n        <tbody>\n          ${this.renderChildren(children, {\n            renderer: (component) =>\n              component.constructor.isRawElement()\n                ? component.render()\n                : `\n              <tr>\n                <td\n                  ${component.htmlAttributes({\n                    align: component.getAttribute('align'),\n                    class: component.getAttribute('css-class'),\n                    style: {\n                      background: component.getAttribute(\n                        'container-background-color',\n                      ),\n                      'font-size': '0px',\n                      padding: component.getAttribute('padding'),\n                      'padding-top': component.getAttribute('padding-top'),\n                      'padding-right': component.getAttribute('padding-right'),\n                      'padding-bottom':\n                        component.getAttribute('padding-bottom'),\n                      'padding-left': component.getAttribute('padding-left'),\n                      'word-break': 'break-word',\n                    },\n                  })}\n                >\n                  ${component.render()}\n                </td>\n              </tr>\n            `,\n          })}\n        </tbody>\n      </table>\n    `\n  }\n\n  render() {\n    let classesName = `${this.getColumnClass()} mj-outlook-group-fix`\n\n    if (this.getAttribute('css-class')) {\n      classesName += ` ${this.getAttribute('css-class')}`\n    }\n\n    return `\n      <div\n        ${this.htmlAttributes({\n          class: classesName,\n          style: 'div',\n        })}\n      >\n        ${this.hasGutter() ? this.renderGutter() : this.renderColumn()}\n      </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/README.md",
    "content": "## mjml-core\n\n### Installation\n\n```bash\nnpm install --save mjml-core\n```\n\nThis is the core mjml library, composed by a set of functions for both parsing, and rendering mjml\n\n### Usage\n\n```javascript\nimport mjml2html from 'mjml'\n\nconsole.log(mjml2html(`code`))\n```\n"
  },
  {
    "path": "packages/mjml-core/package.json",
    "content": "{\n  \"name\": \"mjml-core\",\n  \"description\": \"mjml-core\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-core\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\",\n    \"test\": \"node ./tests/index.js\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"cheerio\": \"1.0.0-rc.12\",\n    \"detect-node\": \"^2.0.4\",\n    \"html-minifier\": \"^4.0.0\",\n    \"js-beautify\": \"^1.6.14\",\n    \"juice\": \"^10.0.0\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-migrate\": \"4.18.0\",\n    \"mjml-parser-xml\": \"4.18.0\",\n    \"mjml-validator\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"chai\": \"^4.1.1\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/src/components.js",
    "content": "import { kebabCase } from 'lodash'\nimport { registerDependencies } from 'mjml-validator'\n\nconst components = {}\n\nexport function assignComponents(target, source) {\n  for (const component of source) {\n    target[component.componentName || kebabCase(component.name)] = component\n  }\n}\n\nexport function registerComponent(Component, options = {}) {\n  assignComponents(components, [Component])\n\n  if (Component.dependencies && options.registerDependencies) {\n    registerDependencies(Component.dependencies)\n  }\n}\n\nexport default components\n"
  },
  {
    "path": "packages/mjml-core/src/createComponent.js",
    "content": "// eslint-disable-next-line max-classes-per-file\nimport {\n  get,\n  forEach,\n  identity,\n  reduce,\n  kebabCase,\n  find,\n  filter,\n  isNil,\n  omitBy,\n} from 'lodash'\n\nimport MJMLParser from 'mjml-parser-xml'\n\nimport shorthandParser, { borderParser } from './helpers/shorthandParser'\nimport formatAttributes from './helpers/formatAttributes'\nimport jsonToXML from './helpers/jsonToXML'\n\nexport function initComponent({ initialDatas, name }) {\n  const Component = initialDatas.context.components[name]\n\n  if (Component) {\n    const component = new Component(initialDatas)\n\n    if (component.headStyle) {\n      component.context.addHeadStyle(name, component.headStyle)\n    }\n    if (component.componentHeadStyle) {\n      component.context.addComponentHeadSyle(component.componentHeadStyle)\n    }\n\n    return component\n  }\n\n  return null\n}\n\nclass Component {\n  static getTagName() {\n    return this.componentName || kebabCase(this.name)\n  }\n\n  static isRawElement() {\n    return !!this.rawElement\n  }\n\n  static defaultAttributes = {}\n\n  constructor(initialDatas = {}) {\n    const {\n      attributes = {},\n      children = [],\n      content = '',\n      context = {},\n      props = {},\n      globalAttributes = {},\n      absoluteFilePath = null,\n      rawAttrs = {},\n    } = initialDatas\n\n    this.props = {\n      absoluteFilePath,\n      ...props,\n      children,\n      content,\n      rawAttrs,\n    }\n\n    this.attributes = formatAttributes(\n      {\n        ...this.constructor.defaultAttributes,\n        ...globalAttributes,\n        ...attributes,\n      },\n      this.constructor.allowedAttributes,\n    )\n    this.context = context\n\n    return this\n  }\n\n  getChildContext() {\n    return this.context\n  }\n\n  getAttribute(name) {\n    return this.attributes[name]\n  }\n\n  getContent() {\n    return this.props.content.trim()\n  }\n\n  renderMJML(mjml, options = {}) {\n    if (typeof mjml === 'string') {\n      // supports returning siblings elements from a custom component\n      const partialMjml = MJMLParser(`<fragment>${mjml}</fragment>`, {\n        ...options,\n        components: this.context.components,\n        ignoreIncludes: true,\n      })\n      return partialMjml.children\n        .map((child) => this.context.processing(child, this.context))\n        .join('')\n    }\n\n    return this.context.processing(mjml, this.context)\n  }\n}\n\nexport class BodyComponent extends Component {\n  // eslint-disable-next-line class-methods-use-this\n  getStyles() {\n    return {}\n  }\n\n  getShorthandAttrValue(attribute, direction) {\n    const mjAttributeDirection = this.getAttribute(`${attribute}-${direction}`)\n    const mjAttribute = this.getAttribute(attribute)\n\n    if (mjAttributeDirection) {\n      return parseInt(mjAttributeDirection, 10)\n    }\n\n    if (!mjAttribute) {\n      return 0\n    }\n\n    return shorthandParser(mjAttribute, direction)\n  }\n\n  getShorthandBorderValue(direction, attribute = 'border') {\n    const borderDirection =\n      direction && this.getAttribute(`${attribute}-${direction}`)\n    const border = this.getAttribute(attribute)\n\n    return borderParser(borderDirection || border || '0')\n  }\n\n  getBoxWidths() {\n    const { containerWidth } = this.context\n    const parsedWidth = parseInt(containerWidth, 10)\n\n    const paddings =\n      this.getShorthandAttrValue('padding', 'right') +\n      this.getShorthandAttrValue('padding', 'left')\n\n    const borders =\n      this.getShorthandBorderValue('right') +\n      this.getShorthandBorderValue('left')\n\n    return {\n      totalWidth: parsedWidth,\n      borders,\n      paddings,\n      box: parsedWidth - paddings - borders,\n    }\n  }\n\n  htmlAttributes(attributes) {\n    const specialAttributes = {\n      style: (v) => this.styles(v),\n      default: identity,\n    }\n\n    return reduce(\n      omitBy(attributes, isNil),\n      (output, v, name) => {\n        const value = (specialAttributes[name] || specialAttributes.default)(v)\n\n        return `${output} ${name}=\"${value}\"`\n      },\n      '',\n    )\n  }\n\n  styles(styles) {\n    let stylesObject\n\n    if (styles) {\n      if (typeof styles === 'string') {\n        stylesObject = get(this.getStyles(), styles)\n      } else {\n        stylesObject = styles\n      }\n    }\n\n    return reduce(\n      stylesObject,\n      (output, value, name) => {\n        if (!isNil(value)) {\n          return `${output}${name}:${value};`\n        }\n        return output\n      },\n      '',\n    )\n  }\n\n  renderChildren(children, options = {}) {\n    const {\n      props = {},\n      renderer = (component) => component.render(),\n      attributes = {},\n      rawXML = false,\n    } = options\n\n    children = children || this.props.children\n\n    if (rawXML) {\n      return children\n        .map((child) => {\n          child.attributes = { ...attributes, ...child.attributes }\n          return jsonToXML(child)\n        })\n        .join('\\n')\n    }\n\n    const sibling = children.length\n\n    const rawComponents = filter(this.context.components, (c) =>\n      c.isRawElement(),\n    )\n    const nonRawSiblings = children.filter(\n      (child) => !find(rawComponents, (c) => c.getTagName() === child.tagName),\n    ).length\n\n    let output = ''\n    let index = 0\n\n    forEach(children, (children) => {\n      const component = initComponent({\n        name: children.tagName,\n        initialDatas: {\n          ...children,\n          attributes: {\n            ...attributes,\n            ...children.attributes,\n          },\n          context: this.getChildContext(),\n          props: {\n            ...props,\n            first: index === 0,\n            index,\n            last: index + 1 === sibling,\n            sibling,\n            nonRawSiblings,\n          },\n        },\n      })\n\n      if (component !== null) {\n        output += renderer(component)\n      }\n\n      index++ // eslint-disable-line no-plusplus\n    })\n\n    return output\n  }\n}\n\nexport class HeadComponent extends Component {\n  static getTagName() {\n    return this.componentName || kebabCase(this.name)\n  }\n\n  handlerChildren() {\n    const { children } = this.props\n\n    return children.map((children) => {\n      const component = initComponent({\n        name: children.tagName,\n        initialDatas: {\n          ...children,\n          context: this.getChildContext(),\n        },\n      })\n\n      if (!component) {\n        // eslint-disable-next-line no-console\n        console.error(`No matching component for tag : ${children.tagName}`)\n        return null\n      }\n\n      if (component.handler) {\n        component.handler()\n      }\n\n      if (component.render) {\n        return component.render()\n      }\n      return null\n    })\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/conditionalTag.js",
    "content": "export const startConditionalTag = '<!--[if mso | IE]>'\nexport const startMsoConditionalTag = '<!--[if mso]>'\nexport const endConditionalTag = '<![endif]-->'\nexport const startNegationConditionalTag = '<!--[if !mso | IE]><!-->'\nexport const startMsoNegationConditionalTag = '<!--[if !mso]><!-->'\nexport const endNegationConditionalTag = '<!--<![endif]-->'\n\nexport default function conditionalTag(content, negation = false) {\n  return `\n    ${negation ? startNegationConditionalTag : startConditionalTag}\n    ${content}\n    ${negation ? endNegationConditionalTag : endConditionalTag}\n  `\n}\n\nexport function msoConditionalTag(content, negation = false) {\n  return `\n    ${negation ? startMsoNegationConditionalTag : startMsoConditionalTag}\n    ${content}\n    ${negation ? endNegationConditionalTag : endConditionalTag}\n  `\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/fonts.js",
    "content": "import { forEach, map } from 'lodash'\n\n// eslint-disable-next-line import/prefer-default-export\nexport function buildFontsTags(content, inlineStyle, fonts = {}) {\n  const toImport = []\n\n  forEach(fonts, (url, name) => {\n    const regex = new RegExp(`\"[^\"]*font-family:[^\"]*${name}[^\"]*\"`, 'gmi')\n    const inlineRegex = new RegExp(`font-family:[^;}]*${name}`, 'gmi')\n\n    if (content.match(regex) || inlineStyle.some((s) => s.match(inlineRegex))) {\n      toImport.push(url)\n    }\n  })\n\n  if (toImport.length > 0) {\n    return `\n      <!--[if !mso]><!-->\n        ${map(\n          toImport,\n          (url) => `<link href=\"${url}\" rel=\"stylesheet\" type=\"text/css\">`,\n        ).join('\\n')}\n        <style type=\"text/css\">\n          ${map(toImport, (url) => `@import url(${url});`).join('\\n')}\n        </style>\n      <!--<![endif]-->\\n\n    `\n  }\n\n  return ''\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/formatAttributes.js",
    "content": "import { reduce } from 'lodash'\nimport { initializeType } from '../types/type'\n\nexport default (attributes, allowedAttributes) =>\n  reduce(\n    attributes,\n    (acc, val, attrName) => {\n      if (allowedAttributes && allowedAttributes[attrName]) {\n        const TypeConstructor = initializeType(allowedAttributes[attrName])\n\n        if (TypeConstructor) {\n          const type = new TypeConstructor(val)\n\n          return {\n            ...acc,\n            [attrName]: type.getValue(),\n          }\n        }\n      }\n\n      return {\n        ...acc,\n        [attrName]: val,\n      }\n    },\n    {},\n  )\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/genRandomHexString.js",
    "content": "export default function genRandomHexString(length) {\n  let str = ''\n  for (let i = 0; i < length; i += 1) {\n    str += Math.floor(Math.random() * 16).toString(16)\n  }\n  return str\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/jsonToXML.js",
    "content": "const jsonToXML = ({ tagName, attributes, children, content }) => {\n  const subNode =\n    children && children.length > 0\n      ? children.map(jsonToXML).join('\\n')\n      : content || ''\n\n  const stringAttrs = Object.keys(attributes)\n    .map((attr) => `${attr}=\"${attributes[attr]}\"`)\n    .join(' ')\n\n  return `<${tagName}${\n    stringAttrs === '' ? '>' : ` ${stringAttrs}>`\n  }${subNode}</${tagName}>`\n}\n\nexport default jsonToXML\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/makeLowerBreakpoint.js",
    "content": "export default function makeLowerBreakpoint(breakpoint) {\n  try {\n    const pixels = Number.parseInt(breakpoint.match('[0-9]+')[0], 10)\n    return `${pixels - 1}px`\n  } catch (e) {\n    return breakpoint\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/mediaQueries.js",
    "content": "import { map, isEmpty } from 'lodash'\n\n// eslint-disable-next-line import/prefer-default-export\nexport default function buildMediaQueriesTags(\n  breakpoint,\n  mediaQueries = {},\n  options = {},\n) {\n  if (isEmpty(mediaQueries)) {\n    return ''\n  }\n\n  const { forceOWADesktop = false, printerSupport = false } = options\n\n  const baseMediaQueries = map(\n    mediaQueries,\n    (mediaQuery, className) => `.${className} ${mediaQuery}`,\n  )\n  const thunderbirdMediaQueries = map(\n    mediaQueries,\n    (mediaQuery, className) => `.moz-text-html .${className} ${mediaQuery}`,\n  )\n  const owaQueries = map(baseMediaQueries, (mq) => `[owa] ${mq}`)\n\n  return `\n    <style type=\"text/css\">\n      @media only screen and (min-width:${breakpoint}) {\n        ${baseMediaQueries.join('\\n')}\n      }\n    </style>\n    <style media=\"screen and (min-width:${breakpoint})\">\n      ${thunderbirdMediaQueries.join('\\n')}\n    </style>\n    ${\n      printerSupport\n        ? `<style type=\"text/css\">\n            @media only print {\n              ${baseMediaQueries.join('\\n')}\n            }\n          </style>`\n        : ``\n    }\n    ${\n      forceOWADesktop\n        ? `<style type=\"text/css\">\\n${owaQueries.join('\\n')}\\n</style>`\n        : ``\n    }\n  `\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/mergeOutlookConditionnals.js",
    "content": "// # OPTIMIZE ME: — check if previous conditionnal is `<!--[if mso | I`]>` too\nexport default (content) =>\n  content.replace(/(<!\\[endif]-->\\s*?<!--\\[if mso \\| IE]>)/gm, '')\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/minifyOutlookConditionnals.js",
    "content": "export default (content) =>\n  // find conditionnal comment blocks\n  content.replace(\n    /(<!--\\[if\\s[^\\]]+]>)([\\s\\S]*?)(<!\\[endif]-->)/gm,\n    (match, prefix, content, suffix) => {\n      // find spaces between tags\n      const processedContent = content\n        .replace(\n          /(^|>)(\\s+)(<|$)/gm,\n          (match, prefix, content, suffix) => `${prefix}${suffix}`,\n        )\n        .replace(/\\s{2,}/gm, ' ')\n      return `${prefix}${processedContent}${suffix}`\n    },\n  )\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/mjmlconfig.js",
    "content": "import path from 'path'\nimport fs from 'fs'\nimport { registerDependencies } from 'mjml-validator'\n\nimport { registerComponent } from '../components'\n\nexport function readMjmlConfig(configPathOrDir = process.cwd()) {\n  let componentRootPath = process.cwd()\n  let mjmlConfigPath = configPathOrDir\n  try {\n    mjmlConfigPath = path\n      .basename(configPathOrDir)\n      .match(/^\\.mjmlconfig(\\.js)?$/)\n      ? path.resolve(configPathOrDir)\n      : path.resolve(configPathOrDir, '.mjmlconfig')\n    componentRootPath = path.dirname(mjmlConfigPath)\n\n    const fullPath = path.resolve(mjmlConfigPath)\n\n    let mjmlConfig\n    if (path.extname(mjmlConfigPath) === '.js') {\n      delete require.cache[fullPath]\n      mjmlConfig = require(fullPath) // eslint-disable-line global-require, import/no-dynamic-require\n    } else {\n      mjmlConfig = JSON.parse(fs.readFileSync(fullPath, 'utf8'))\n    }\n\n    return { mjmlConfig, componentRootPath }\n  } catch (e) {\n    if (e.code !== 'ENOENT') {\n      console.error('Error reading mjmlconfig : ', e) // eslint-disable-line no-console\n    }\n    return {\n      mjmlConfig: { packages: [], options: {} },\n      mjmlConfigPath,\n      componentRootPath,\n      error: e,\n    }\n  }\n}\n\nexport function resolveComponentPath(compPath, componentRootPath) {\n  if (!compPath) {\n    return null\n  }\n  if (!compPath.startsWith('.') && !path.isAbsolute(compPath)) {\n    try {\n      return require.resolve(compPath)\n    } catch (e) {\n      if (e.code !== 'MODULE_NOT_FOUND') {\n        console.error('Error resolving custom component path : ', e) // eslint-disable-line no-console\n        return null\n      }\n      // we got a 'MODULE_NOT_FOUND' error\n      try {\n        // try again as relative path to node_modules: (this may be necessary if mjml is installed globally or by npm link)\n        return resolveComponentPath(\n          `./node_modules/${compPath}`,\n          componentRootPath,\n        )\n      } catch (e) {\n        //  try again as a plain local path:\n        return resolveComponentPath(`./${compPath}`, componentRootPath)\n      }\n    }\n  }\n  return require.resolve(path.resolve(componentRootPath, compPath))\n}\n\nexport function registerCustomComponent(\n  comp,\n  registerCompFn = registerComponent,\n) {\n  if (comp instanceof Function) {\n    registerCompFn(comp)\n  } else {\n    const compNames = Object.keys(comp) // this approach handles both an array and an object (like the mjml-accordion default export)\n    compNames.forEach((compName) => {\n      registerCustomComponent(comp[compName], registerCompFn)\n    })\n  }\n}\n\nexport function handleMjmlConfigComponents(\n  packages,\n  componentRootPath,\n  registerCompFn,\n) {\n  const result = {\n    success: [],\n    failures: [],\n  }\n\n  packages.forEach((compPath) => {\n    let resolvedPath = compPath\n    try {\n      resolvedPath = resolveComponentPath(compPath, componentRootPath)\n      if (resolvedPath) {\n        const requiredComp = require(resolvedPath) // eslint-disable-line global-require, import/no-dynamic-require\n        registerCustomComponent(\n          requiredComp.default || requiredComp,\n          registerCompFn,\n        )\n        registerDependencies(\n          (requiredComp.default || requiredComp).dependencies || {},\n        )\n        result.success.push(compPath)\n      }\n    } catch (e) {\n      result.failures.push({ error: e, compPath })\n      if (e.code === 'ENOENT' || e.code === 'MODULE_NOT_FOUND') {\n        console.error('Missing or unreadable custom component : ', resolvedPath) // eslint-disable-line no-console\n      } else {\n        // eslint-disable-next-line no-console\n        console.error(\n          'Error when registering custom component : ',\n          resolvedPath,\n          e,\n        )\n      }\n    }\n  })\n\n  return result\n}\n\nexport default function handleMjmlConfig(\n  configPathOrDir = process.cwd(),\n  registerCompFn = registerComponent,\n) {\n  const {\n    mjmlConfig: { packages },\n    componentRootPath,\n    error,\n  } = readMjmlConfig(configPathOrDir)\n  if (error) return { error }\n\n  return handleMjmlConfigComponents(packages, componentRootPath, registerCompFn)\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/preview.js",
    "content": "export default function (content) {\n  if (content === '') {\n    return ''\n  }\n\n  return `\n    <div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">${content}</div>\n  `\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/shorthandParser.js",
    "content": "import { get } from 'lodash'\n\nexport default function (cssValue, direction) {\n  const splittedCssValue = cssValue.trim().replace(/\\s+/g, ' ').split(' ', 4)\n  let directions = {}\n\n  switch (splittedCssValue.length) {\n    case 2:\n      directions = { top: 0, bottom: 0, left: 1, right: 1 }\n      break\n\n    case 3:\n      directions = { top: 0, left: 1, right: 1, bottom: 2 }\n      break\n\n    case 4:\n      directions = { top: 0, right: 1, bottom: 2, left: 3 }\n      break\n    case 1:\n    default:\n      return parseInt(cssValue, 10)\n  }\n\n  return parseInt(splittedCssValue[directions[direction]] || 0, 10)\n}\n\nexport function borderParser(border) {\n  return parseInt(get(border.match(/(?:(?:^| )(\\d+))/), 1), 10) || 0\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/skeleton.js",
    "content": "import { negate, isNil } from 'lodash'\nimport buildPreview from './preview'\nimport { buildFontsTags } from './fonts'\nimport buildMediaQueriesTags from './mediaQueries'\nimport { buildStyleFromComponents, buildStyleFromTags } from './styles'\n\nexport default function skeleton(options) {\n  const {\n    backgroundColor = '',\n    beforeDoctype = '',\n    breakpoint = '480px',\n    content = '',\n    fonts = {},\n    mediaQueries = {},\n    headStyle = {},\n    componentsHeadStyle = [],\n    headRaw = [],\n    preview,\n    title = '',\n    style = [],\n    forceOWADesktop,\n    printerSupport,\n    inlineStyle,\n    lang,\n    dir,\n  } = options\n\n  return `${beforeDoctype ? `${beforeDoctype}\\n` : ''}<!doctype html>\n<html lang=\"${lang}\" dir=\"${dir}\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n  <head>\n    <title>${title}</title>\n    <!--[if !mso]><!-->\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <!--<![endif]-->\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <style type=\"text/css\">\n      #outlook a { padding:0; }\n      body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }\n      table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }\n      img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }\n      p { display:block;margin:13px 0; }\n    </style>\n    <!--[if mso]>\n    <noscript>\n    <xml>\n    <o:OfficeDocumentSettings>\n      <o:AllowPNG/>\n      <o:PixelsPerInch>96</o:PixelsPerInch>\n    </o:OfficeDocumentSettings>\n    </xml>\n    </noscript>\n    <![endif]-->\n    <!--[if lte mso 11]>\n    <style type=\"text/css\">\n      .mj-outlook-group-fix { width:100% !important; }\n    </style>\n    <![endif]-->\n    ${buildFontsTags(content, inlineStyle, fonts)}\n    ${buildMediaQueriesTags(breakpoint, mediaQueries, {\n      forceOWADesktop,\n      printerSupport,\n    })}\n    ${buildStyleFromComponents(breakpoint, componentsHeadStyle, headStyle)}\n    ${buildStyleFromTags(breakpoint, style)}\n    ${headRaw.filter(negate(isNil)).join('\\n')}\n  </head>\n  <body style=\"word-spacing:normal;${\n    backgroundColor ? `background-color:${backgroundColor};` : ''\n  }\">\n    ${buildPreview(preview)}\n    ${content}\n  </body>\n</html>\n  `\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/styles.js",
    "content": "import { isFunction } from 'lodash'\n\nexport function buildStyleFromComponents(\n  breakpoint,\n  componentsHeadStyles,\n  headStylesObject,\n) {\n  const headStyles = Object.values(headStylesObject)\n\n  if (componentsHeadStyles.length === 0 && headStyles.length === 0) {\n    return ''\n  }\n\n  return `\n    <style type=\"text/css\">${[...componentsHeadStyles, ...headStyles].reduce(\n      (result, styleFunction) => `${result}\\n${styleFunction(breakpoint)}`,\n      '',\n    )}\n    </style>`\n}\n\nexport function buildStyleFromTags(breakpoint, styles) {\n  if (styles.length === 0) {\n    return ''\n  }\n\n  return ` \n    <style type=\"text/css\">${styles.reduce(\n      (result, style) =>\n        `${result}\\n${isFunction(style) ? style(breakpoint) : style}`,\n      '',\n    )}\n    </style>`\n}\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/suffixCssClasses.js",
    "content": "export default (classes, suffix) =>\n  classes\n    ? classes\n        .split(' ')\n        .map((c) => `${c}-${suffix}`)\n        .join(' ')\n    : ''\n"
  },
  {
    "path": "packages/mjml-core/src/helpers/widthParser.js",
    "content": "const unitRegex = /[\\d.,]*(\\D*)$/\n\nexport default function widthParser(width, options = {}) {\n  const { parseFloatToInt = true } = options\n\n  const widthUnit = unitRegex.exec(width.toString())[1]\n  const unitParsers = {\n    default: parseInt,\n    px: parseInt,\n    '%': parseFloatToInt ? parseInt : parseFloat,\n  }\n  const parser = unitParsers[widthUnit] || unitParsers.default\n\n  return {\n    parsedWidth: parser(width),\n    unit: widthUnit || 'px',\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/src/index.js",
    "content": "import {\n  find,\n  filter,\n  get,\n  identity,\n  map,\n  omit,\n  reduce,\n  isObject,\n  each,\n  isEmpty,\n} from 'lodash'\nimport path from 'path'\nimport juice from 'juice'\nimport { html as htmlBeautify } from 'js-beautify'\nimport { minify as htmlMinify } from 'html-minifier'\nimport { load } from 'cheerio'\n\nimport MJMLParser from 'mjml-parser-xml'\nimport MJMLValidator, {\n  dependencies as globalDependencies,\n  assignDependencies,\n} from 'mjml-validator'\nimport { handleMjml3 } from 'mjml-migrate'\n\nimport { initComponent } from './createComponent'\nimport globalComponents, {\n  registerComponent,\n  assignComponents,\n} from './components'\n\nimport makeLowerBreakpoint from './helpers/makeLowerBreakpoint'\nimport suffixCssClasses from './helpers/suffixCssClasses'\nimport mergeOutlookConditionnals from './helpers/mergeOutlookConditionnals'\nimport minifyOutlookConditionnals from './helpers/minifyOutlookConditionnals'\nimport defaultSkeleton from './helpers/skeleton'\nimport { initializeType } from './types/type'\n\nimport handleMjmlConfig, {\n  readMjmlConfig,\n  handleMjmlConfigComponents,\n} from './helpers/mjmlconfig'\n\nconst isNode = require('detect-node')\n\nclass ValidationError extends Error {\n  constructor(message, errors) {\n    super(message)\n\n    this.errors = errors\n  }\n}\n\nexport default function mjml2html(mjml, options = {}) {\n  let content = ''\n  let errors = []\n\n  if (isNode && typeof options.skeleton === 'string') {\n    /* eslint-disable global-require */\n    /* eslint-disable import/no-dynamic-require */\n    options.skeleton = require(\n      options.skeleton.charAt(0) === '.'\n        ? path.resolve(process.cwd(), options.skeleton)\n        : options.skeleton,\n    )\n    /* eslint-enable global-require */\n    /* eslint-enable import/no-dynamic-require */\n  }\n\n  let packages = {}\n  let confOptions = {}\n  let mjmlConfigOptions = {}\n  let confPreprocessors = []\n  let error = null\n  let componentRootPath = null\n\n  if ((isNode && options.useMjmlConfigOptions) || options.mjmlConfigPath) {\n    const mjmlConfigContent = readMjmlConfig(options.mjmlConfigPath)\n\n    ;({\n      mjmlConfig: {\n        packages,\n        options: confOptions,\n        preprocessors: confPreprocessors,\n      },\n      componentRootPath,\n      error,\n    } = mjmlConfigContent)\n\n    if (options.useMjmlConfigOptions) {\n      mjmlConfigOptions = confOptions\n    }\n  }\n\n  // if mjmlConfigPath is specified then we need to register components it on each call\n  if (isNode && !error && options.mjmlConfigPath) {\n    handleMjmlConfigComponents(packages, componentRootPath, registerComponent)\n  }\n\n  const {\n    beautify = false,\n    fonts = {\n      'Open Sans':\n        'https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700',\n      'Droid Sans':\n        'https://fonts.googleapis.com/css?family=Droid+Sans:300,400,500,700',\n      Lato: 'https://fonts.googleapis.com/css?family=Lato:300,400,500,700',\n      Roboto: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700',\n      Ubuntu: 'https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700',\n    },\n    keepComments,\n    minify = false,\n    minifyOptions = {},\n    ignoreIncludes = false,\n    juiceOptions = {},\n    juicePreserveTags = null,\n    skeleton = defaultSkeleton,\n    validationLevel = 'soft',\n    filePath = '.',\n    actualPath = '.',\n    noMigrateWarn = false,\n    preprocessors,\n    presets = [],\n    printerSupport = false,\n  } = {\n    ...mjmlConfigOptions,\n    ...options,\n    preprocessors: options.preprocessors\n      ? [...confPreprocessors, ...options.preprocessors]\n      : confPreprocessors,\n  }\n\n  const components = { ...globalComponents }\n  const dependencies = assignDependencies({}, globalDependencies)\n  for (const preset of presets) {\n    assignComponents(components, preset.components)\n    assignDependencies(dependencies, preset.dependencies)\n  }\n\n  if (typeof mjml === 'string') {\n    mjml = MJMLParser(mjml, {\n      keepComments,\n      components,\n      filePath,\n      actualPath,\n      preprocessors,\n      ignoreIncludes,\n    })\n  }\n\n  mjml = handleMjml3(mjml, { noMigrateWarn })\n\n  const globalData = {\n    backgroundColor: '',\n    beforeDoctype: '',\n    breakpoint: '480px',\n    classes: {},\n    classesDefault: {},\n    defaultAttributes: {},\n    htmlAttributes: {},\n    fonts,\n    inlineStyle: [],\n    headStyle: {},\n    componentsHeadStyle: [],\n    headRaw: [],\n    mediaQueries: {},\n    preview: '',\n    style: [],\n    title: '',\n    forceOWADesktop: get(mjml, 'attributes.owa', 'mobile') === 'desktop',\n    lang: get(mjml, 'attributes.lang') || 'und',\n    dir: get(mjml, 'attributes.dir') || 'auto',\n  }\n\n  const validatorOptions = {\n    components,\n    dependencies,\n    initializeType,\n  }\n\n  switch (validationLevel) {\n    case 'skip':\n      break\n\n    case 'strict':\n      errors = MJMLValidator(mjml, validatorOptions)\n\n      if (errors.length > 0) {\n        throw new ValidationError(\n          `ValidationError: \\n ${errors\n            .map((e) => e.formattedMessage)\n            .join('\\n')}`,\n          errors,\n        )\n      }\n      break\n\n    case 'soft':\n    default:\n      errors = MJMLValidator(mjml, validatorOptions)\n      break\n  }\n\n  const mjBody = find(mjml.children, { tagName: 'mj-body' })\n  const mjHead = find(mjml.children, { tagName: 'mj-head' })\n  const mjOutsideRaws = filter(mjml.children, { tagName: 'mj-raw' })\n\n  const processing = (node, context, parseMJML = identity) => {\n    if (!node) {\n      return\n    }\n\n    const component = initComponent({\n      name: node.tagName,\n      initialDatas: {\n        ...parseMJML(node),\n        context,\n      },\n    })\n\n    if (component !== null) {\n      if ('handler' in component) {\n        return component.handler() // eslint-disable-line consistent-return\n      }\n\n      if ('render' in component) {\n        return component.render() // eslint-disable-line consistent-return\n      }\n    }\n  }\n\n  const applyAttributes = (mjml) => {\n    const parse = (mjml, parentMjClass = '') => {\n      const { attributes, tagName, children } = mjml\n      const classes = get(mjml.attributes, 'mj-class', '').split(' ')\n      const attributesClasses = reduce(\n        classes,\n        (acc, value) => {\n          const mjClassValues = globalData.classes[value]\n          let multipleClasses = {}\n          if (acc['css-class'] && get(mjClassValues, 'css-class')) {\n            multipleClasses = {\n              'css-class': `${acc['css-class']} ${mjClassValues['css-class']}`,\n            }\n          }\n\n          return {\n            ...acc,\n            ...mjClassValues,\n            ...multipleClasses,\n          }\n        },\n        {},\n      )\n\n      const defaultAttributesForClasses = reduce(\n        parentMjClass.split(' '),\n        (acc, value) => ({\n          ...acc,\n          ...get(globalData.classesDefault, `${value}.${tagName}`),\n        }),\n        {},\n      )\n      const nextParentMjClass = get(attributes, 'mj-class', parentMjClass)\n\n      return {\n        ...mjml,\n        attributes: {\n          ...globalData.defaultAttributes[tagName],\n          ...attributesClasses,\n          ...defaultAttributesForClasses,\n          ...omit(attributes, ['mj-class']),\n        },\n        rawAttrs: { ...omit(attributes, ['mj-class']) },\n        globalAttributes: {\n          ...globalData.defaultAttributes['mj-all'],\n        },\n        children: map(children, (mjml) => parse(mjml, nextParentMjClass)),\n      }\n    }\n\n    return parse(mjml)\n  }\n\n  const bodyHelpers = {\n    components,\n    globalData,\n    addMediaQuery(className, { parsedWidth, unit }) {\n      globalData.mediaQueries[className] =\n        `{ width:${parsedWidth}${unit} !important; max-width: ${parsedWidth}${unit}; }`\n    },\n    addHeadStyle(identifier, headStyle) {\n      globalData.headStyle[identifier] = headStyle\n    },\n    addComponentHeadSyle(headStyle) {\n      globalData.componentsHeadStyle.push(headStyle)\n    },\n    setBackgroundColor: (color) => {\n      globalData.backgroundColor = color\n    },\n    processing: (node, context) => processing(node, context, applyAttributes),\n  }\n\n  const headHelpers = {\n    components,\n    globalData,\n    add(attr, ...params) {\n      if (Array.isArray(globalData[attr])) {\n        globalData[attr].push(...params)\n      } else if (Object.prototype.hasOwnProperty.call(globalData, attr)) {\n        if (params.length > 1) {\n          if (isObject(globalData[attr][params[0]])) {\n            globalData[attr][params[0]] = {\n              ...globalData[attr][params[0]],\n              ...params[1],\n            }\n          } else {\n            // eslint-disable-next-line prefer-destructuring\n            globalData[attr][params[0]] = params[1]\n          }\n        } else {\n          // eslint-disable-next-line prefer-destructuring\n          globalData[attr] = params[0]\n        }\n      } else {\n        throw Error(\n          `An mj-head element add an unkown head attribute : ${attr} with params ${\n            Array.isArray(params) ? params.join('') : params\n          }`,\n        )\n      }\n    },\n  }\n\n  globalData.headRaw = processing(mjHead, headHelpers)\n\n  content = processing(mjBody, bodyHelpers, applyAttributes)\n\n  if (!content) {\n    throw new Error(\n      'Malformed MJML. Check that your structure is correct and enclosed in <mjml> tags.',\n    )\n  }\n\n  content = minifyOutlookConditionnals(content)\n\n  if (mjOutsideRaws.length) {\n    const toAddBeforeDoctype = mjOutsideRaws.filter(\n      (elt) =>\n        elt.attributes.position && elt.attributes.position === 'file-start',\n    )\n    if (toAddBeforeDoctype.length) {\n      globalData.beforeDoctype = toAddBeforeDoctype\n        .map((elt) => elt.content)\n        .join('\\n')\n    }\n  }\n\n  if (!isEmpty(globalData.htmlAttributes)) {\n    const $ = load(content, {\n      xmlMode: true, // otherwise it may move contents that aren't in any tag\n      decodeEntities: false, // won't escape special characters\n    })\n\n    each(globalData.htmlAttributes, (data, selector) => {\n      each(data, (value, attrName) => {\n        $(selector).each(function getAttr() {\n          $(this).attr(attrName, value || '')\n        })\n      })\n    })\n\n    content = $.root().html()\n  }\n\n  content = skeleton({\n    content,\n    ...globalData,\n    printerSupport,\n  })\n\n  if (globalData.inlineStyle.length > 0) {\n    if (juicePreserveTags) {\n      each(juicePreserveTags, (val, key) => {\n        juice.codeBlocks[key] = val\n      })\n    }\n\n    content = juice(content, {\n      applyStyleTags: false,\n      extraCss: globalData.inlineStyle.join(''),\n      insertPreservedExtraCss: false,\n      removeStyleTags: false,\n      ...juiceOptions,\n    })\n  }\n\n  content = mergeOutlookConditionnals(content)\n\n  if (beautify) {\n    // eslint-disable-next-line no-console\n    console.warn(\n      '\"beautify\" option is deprecated in mjml-core and only available in mjml cli.',\n    )\n    content = htmlBeautify(content, {\n      indent_size: 2,\n      wrap_attributes_indent_size: 2,\n      max_preserve_newline: 0,\n      preserve_newlines: false,\n    })\n  }\n\n  if (minify) {\n    // eslint-disable-next-line no-console\n    console.warn(\n      '\"minify\" option is deprecated in mjml-core and only available in mjml cli.',\n    )\n\n    content = htmlMinify(content, {\n      collapseWhitespace: true,\n      minifyCSS: false,\n      caseSensitive: true,\n      removeEmptyAttributes: true,\n      ...minifyOptions,\n    })\n  }\n\n  return {\n    html: content,\n    json: mjml,\n    errors,\n  }\n}\n\nif (isNode) {\n  handleMjmlConfig(process.cwd(), registerComponent)\n}\n\nexport {\n  globalComponents as components,\n  initComponent,\n  registerComponent,\n  assignComponents,\n  makeLowerBreakpoint,\n  suffixCssClasses,\n  handleMjmlConfig,\n  initializeType,\n}\n\nexport { BodyComponent, HeadComponent } from './createComponent'\n"
  },
  {
    "path": "packages/mjml-core/src/types/boolean.js",
    "content": "import Type from './type'\n\nexport const matcher = /^boolean/gim\n\nexport default () =>\n  class Boolean extends Type {\n    constructor(boolean) {\n      super(boolean)\n\n      this.matchers = [/^true$/i, /^false$/i]\n    }\n\n    isValid() {\n      return this.value === true || this.value === false\n    }\n  }\n"
  },
  {
    "path": "packages/mjml-core/src/types/color.js",
    "content": "import Type from './type'\nimport colors from './helpers/colors'\n\nexport const matcher = /^color/gim\n\nconst shorthandRegex = /^#\\w{3}$/\nconst replaceInputRegex = /^#(\\w)(\\w)(\\w)$/\nconst replaceOutput = '#$1$1$2$2$3$3'\n\nexport default () =>\n  class Color extends Type {\n    constructor(color) {\n      super(color)\n\n      this.matchers = [\n        /rgba\\(\\d{1,3},\\s?\\d{1,3},\\s?\\d{1,3},\\s?\\d(\\.\\d{1,3})?\\)/gi,\n        /rgb\\(\\d{1,3},\\s?\\d{1,3},\\s?\\d{1,3}\\)/gi,\n        /^#([0-9a-f]{3}){1,2}$/gi,\n        new RegExp(`^(${colors.join('|')})$`),\n      ]\n    }\n\n    getValue() {\n      if (typeof this.value === 'string' && this.value.match(shorthandRegex)) {\n        return this.value.replace(replaceInputRegex, replaceOutput)\n      }\n\n      return this.value\n    }\n  }\n"
  },
  {
    "path": "packages/mjml-core/src/types/enum.js",
    "content": "import { escapeRegExp } from 'lodash'\nimport Type from './type'\n\nexport const matcher = /^enum/gim\n\nexport default (params) => {\n  const matchers = params.match(/\\(([^)]+)\\)/)[1].split(',')\n\n  return class Enum extends Type {\n    static errorMessage = `has invalid value: $value for type Enum, only accepts ${matchers.join(\n      ', ',\n    )}`\n\n    constructor(value) {\n      super(value)\n\n      this.matchers = matchers.map((m) => new RegExp(`^${escapeRegExp(m)}$`))\n    }\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/src/types/helpers/colors.js",
    "content": "export default [\n  'aliceblue',\n  'antiquewhite',\n  'aqua',\n  'aquamarine',\n  'azure',\n  'beige',\n  'bisque',\n  'black',\n  'blanchedalmond',\n  'blue',\n  'blueviolet',\n  'brown',\n  'burlywood',\n  'cadetblue',\n  'chartreuse',\n  'chocolate',\n  'coral',\n  'cornflowerblue',\n  'cornsilk',\n  'crimson',\n  'cyan',\n  'darkblue',\n  'darkcyan',\n  'darkgoldenrod',\n  'darkgray',\n  'darkgreen',\n  'darkgrey',\n  'darkkhaki',\n  'darkmagenta',\n  'darkolivegreen',\n  'darkorange',\n  'darkorchid',\n  'darkred',\n  'darksalmon',\n  'darkseagreen',\n  'darkslateblue',\n  'darkslategray',\n  'darkslategrey',\n  'darkturquoise',\n  'darkviolet',\n  'deeppink',\n  'deepskyblue',\n  'dimgray',\n  'dimgrey',\n  'dodgerblue',\n  'firebrick',\n  'floralwhite',\n  'forestgreen',\n  'fuchsia',\n  'gainsboro',\n  'ghostwhite',\n  'gold',\n  'goldenrod',\n  'gray',\n  'green',\n  'greenyellow',\n  'grey',\n  'honeydew',\n  'hotpink',\n  'indianred',\n  'indigo',\n  'inherit',\n  'ivory',\n  'khaki',\n  'lavender',\n  'lavenderblush',\n  'lawngreen',\n  'lemonchiffon',\n  'lightblue',\n  'lightcoral',\n  'lightcyan',\n  'lightgoldenrodyellow',\n  'lightgray',\n  'lightgreen',\n  'lightgrey',\n  'lightpink',\n  'lightsalmon',\n  'lightseagreen',\n  'lightskyblue',\n  'lightslategray',\n  'lightslategrey',\n  'lightsteelblue',\n  'lightyellow',\n  'lime',\n  'limegreen',\n  'linen',\n  'magenta',\n  'maroon',\n  'mediumaquamarine',\n  'mediumblue',\n  'mediumorchid',\n  'mediumpurple',\n  'mediumseagreen',\n  'mediumslateblue',\n  'mediumspringgreen',\n  'mediumturquoise',\n  'mediumvioletred',\n  'midnightblue',\n  'mintcream',\n  'mistyrose',\n  'moccasin',\n  'navajowhite',\n  'navy',\n  'oldlace',\n  'olive',\n  'olivedrab',\n  'orange',\n  'orangered',\n  'orchid',\n  'palegoldenrod',\n  'palegreen',\n  'paleturquoise',\n  'palevioletred',\n  'papayawhip',\n  'peachpuff',\n  'peru',\n  'pink',\n  'plum',\n  'powderblue',\n  'purple',\n  'rebeccapurple',\n  'red',\n  'rosybrown',\n  'royalblue',\n  'saddlebrown',\n  'salmon',\n  'sandybrown',\n  'seagreen',\n  'seashell',\n  'sienna',\n  'silver',\n  'skyblue',\n  'slateblue',\n  'slategray',\n  'slategrey',\n  'snow',\n  'springgreen',\n  'steelblue',\n  'tan',\n  'teal',\n  'thistle',\n  'tomato',\n  'transparent',\n  'turquoise',\n  'violet',\n  'wheat',\n  'white',\n  'whitesmoke',\n  'yellow',\n  'yellowgreen',\n]\n"
  },
  {
    "path": "packages/mjml-core/src/types/index.js",
    "content": "import NBoolean, { matcher as booleanMatcher } from './boolean'\nimport Color, { matcher as colorMatcher } from './color'\nimport Enum, { matcher as enumMatcher } from './enum'\nimport Unit, { matcher as unitMatcher } from './unit'\nimport NString, { matcher as stringMatcher } from './string'\nimport NInteger, { matcher as intMatcher } from './integer'\n\nexport default {\n  boolean: { matcher: booleanMatcher, typeConstructor: NBoolean },\n  enum: { matcher: enumMatcher, typeConstructor: Enum },\n  color: { matcher: colorMatcher, typeConstructor: Color },\n  unit: { matcher: unitMatcher, typeConstructor: Unit },\n  string: { matcher: stringMatcher, typeConstructor: NString },\n  integer: { matcher: intMatcher, typeConstructor: NInteger },\n}\n"
  },
  {
    "path": "packages/mjml-core/src/types/integer.js",
    "content": "import Type from './type'\n\nexport const matcher = /^integer/gim\n\nexport default () =>\n  class NInteger extends Type {\n    constructor(value) {\n      super(value)\n\n      this.matchers = [/\\d+/]\n    }\n  }\n"
  },
  {
    "path": "packages/mjml-core/src/types/string.js",
    "content": "import Type from './type'\n\nexport const matcher = /^string/gim\n\nexport default () =>\n  class NString extends Type {\n    constructor(value) {\n      super(value)\n\n      this.matchers = [/.*/]\n    }\n  }\n"
  },
  {
    "path": "packages/mjml-core/src/types/type.js",
    "content": "import { some, find } from 'lodash'\nimport typesConstructors from './index'\n\n// Avoid recreate existing types\nexport const types = {}\n\nexport const initializeType = (typeConfig) => {\n  if (types[typeConfig]) {\n    return types[typeConfig]\n  }\n\n  const { typeConstructor } =\n    find(typesConstructors, (type) => !!typeConfig.match(type.matcher)) || {}\n\n  if (!typeConstructor) {\n    throw new Error(`No type found for ${typeConfig}`)\n  }\n\n  types[typeConfig] = typeConstructor(typeConfig)\n\n  return types[typeConfig]\n}\n\nexport default class Type {\n  constructor(value) {\n    this.value = value\n  }\n\n  isValid() {\n    return some(this.matchers, (matcher) => `${this.value}`.match(matcher))\n  }\n\n  getErrorMessage() {\n    if (this.isValid()) {\n      return\n    }\n\n    const errorMessage =\n      this.constructor.errorMessage ||\n      `has invalid value: ${this.value} for type ${this.constructor.name} `\n\n    return errorMessage.replace(/\\$value/g, this.value)\n  }\n\n  static check(type) {\n    return !!type.match(this.constructor.typeChecker)\n  }\n\n  getValue() {\n    return this.value\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/src/types/unit.js",
    "content": "import { escapeRegExp } from 'lodash'\nimport Type from './type'\n\nexport const matcher = /^(unit|unitWithNegative)\\(.*\\)/gim\n\nexport default (params) => {\n  const allowNeg = params.match(/^unitWithNegative/) ? '-|' : ''\n\n  const units = params.match(/\\(([^)]+)\\)/)[1].split(',')\n  const argsMatch = params.match(/\\{([^}]+)\\}/)\n  const args = (argsMatch && argsMatch[1] && argsMatch[1].split(',')) || ['1'] // defaults to 1\n\n  const allowAuto = units.includes('auto') ? '|auto' : ''\n  const filteredUnits = units.filter((u) => u !== 'auto')\n\n  return class Unit extends Type {\n    static errorMessage = `has invalid value: $value for type Unit, only accepts (${units.join(\n      ', ',\n    )}) units and ${args.join(' to ')} value(s)`\n\n    constructor(value) {\n      super(value)\n\n      this.matchers = [\n        new RegExp(\n          `^(((${allowNeg}\\\\d|,|\\\\.){1,}(${filteredUnits\n            .map(escapeRegExp)\n            .join('|')})|0${allowAuto})( )?){${args.join(',')}}$`,\n        ),\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/tests/.eslintrc",
    "content": "{\n  \"extends\": \"../../../.eslintrc\",\n  \"rules\": {\n    \"import/no-extraneous-dependencies\": [\"error\", {\"devDependencies\": true}]\n  }\n}\n"
  },
  {
    "path": "packages/mjml-core/tests/index.js",
    "content": "require('./jsonToXml-test')\nrequire('./mergeOutlookConditionnals-test')\nrequire('./minifyOutlookConditionnals-test')\nrequire('./shorthandParser-test')\nrequire('./skeleton-test')\nrequire('./widthParser-test')\n"
  },
  {
    "path": "packages/mjml-core/tests/jsonToXml-test.js",
    "content": "const chai = require('chai')\nconst jsonToXml = require('../lib/helpers/jsonToXML')\n\nconst json = {\n  line: 1,\n  includedIn: [],\n  tagName: 'mjml',\n  children:\n   [ {\n       line: 2,\n       includedIn: [],\n       tagName: 'mj-body',\n       children:\n        [ {\n            line: 3,\n            includedIn: [],\n            tagName: 'mj-section',\n            children:\n             [ {\n                 line: 4,\n                 includedIn: [],\n                 tagName: 'mj-column',\n                 children:\n                  [ {\n                      line: 5,\n                      includedIn: [],\n                      tagName: 'mj-text',\n                      attributes:\n                       { 'font-size': '20px',\n                         color: '#F45E43',\n                         'font-family': 'helvetica' },\n                      content: 'Hello World' } ],\n                 attributes: {} } ],\n            attributes: {} } ],\n       attributes: {} } ],\n  attributes: {} }\n\nconst xml = jsonToXml(json)\n\nconst validXml = '<mjml><mj-body><mj-section><mj-column><mj-text font-size=\"20px\" color=\"#F45E43\" font-family=\"helvetica\">Hello World</mj-text></mj-column></mj-section></mj-body></mjml>'\n\nchai.expect(xml, 'jsonToXML test failed')\n    .to.equal(validXml)\n"
  },
  {
    "path": "packages/mjml-core/tests/mergeOutlookConditionnals-test.js",
    "content": "const chai = require('chai')\nconst mergeOutlookConditionnals = require('../lib/helpers/mergeOutlookConditionnals')\n\nconst testValues = [\n  {\n    input: '<![endif]--><!--[if mso | IE]>',\n    output: '',\n  },\n  {\n    input: `\n    </tr>\n    <![endif]-->\n    <!--[if mso | IE]>\n    </td>`,\n    output: `\\n    </tr>\\n    \\n    </td>`,\n  },\n  {\n    input: `</div>\\n              <!--[if mso | IE]>\\n            </td>\\n          <![endif]-->\\n              <!--[if mso | IE]>\\n        </tr>\\n      <![endif]-->\\n              <!--[if mso | IE]>\\n                  </table>\\n                <![endif]-->\\n            </td>\\n          </tr>\\n        </tbody>\\n      </table>\\n    </div>\\n    <!--[if mso | IE]>\\n          </td>`,\n    output: `</div>\\n              <!--[if mso | IE]>\\n            </td>\\n          \\n        </tr>\\n      \\n                  </table>\\n                <![endif]-->\\n            </td>\\n          </tr>\\n        </tbody>\\n      </table>\\n    </div>\\n    <!--[if mso | IE]>\\n          </td>`,\n  },\n]\n\ntestValues.forEach(testUnit => {\n  const { input, output } = testUnit\n\n  chai.expect(mergeOutlookConditionnals(input), `mergeOutlookConditionnals test failed`)\n      .to.deep.equal(output)\n})\n"
  },
  {
    "path": "packages/mjml-core/tests/minifyOutlookConditionnals-test.js",
    "content": "const chai = require('chai')\nconst minifyOutlookConditionnals = require('../lib/helpers/minifyOutlookConditionnals')\n\nconst testValues = [\n  {\n    input: `\\n      <div\\n         style=\"\"\\n      >\\n        \\n      \\n      <!--[if mso | IE]>\\n      <table\\n         align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"\" style=\"width:600px;\" width=\"600\"\\n      >\\n        <tr>\\n          <td style=\"line-height:0px;font-size:0px;mso-line-height-rule:exactly;\">\\n      <![endif]-->\\n    \\n      \\n      <div  style=\"Margin:0px auto;max-width:600px;\">\\n        \\n        <table\\n           align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"width:100%;\"\\n        >\\n          <tbody>\\n            <tr>\\n              <td\\n                 style=\"direction:ltr;font-size:0px;padding:20px 0;text-align:center;vertical-align:top;\"\\n              >\\n                <!--[if mso | IE]>\\n                  <table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\\n                <![endif]-->\\n                  \\n      <!--[if mso | IE]>\\n        <tr>\\n      <![endif]-->\\n      \\n          <!--[if mso | IE]>\\n            <td\\n               class=\"\" style=\"vertical-align:top;width:600px;\"\\n            >\\n          <![endif]-->\\n            \\n      <div\\n         class=\"mj-column-per-100 mj-outlook-group-fix\" style=\"font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;\"\\n      >\\n        \\n      <table\\n         border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"vertical-align:top;\" width=\"100%\"\\n      >\\n        \\n            <tr>\\n              <td\\n                 align=\"left\" style=\"font-size:0px;padding:10px 25px;word-break:break-word;\"\\n              >\\n                \\n      <div\\n         style=\"font-family:helvetica;font-size:20px;line-height:1;text-align:left;color:#F45E43;\"\\n      >\\n        Hello World\\n      </div>\\n    \\n              </td>\\n            </tr>\\n          \\n      </table>\\n    \\n      </div>\\n    \\n          <!--[if mso | IE]>\\n            </td>\\n          <![endif]-->\\n    \\n\\n      <!--[if mso | IE]>\\n        </tr>\\n      <![endif]-->\\n    \\n                <!--[if mso | IE]>\\n                  </table>\\n                <![endif]-->\\n              </td>\\n            </tr>\\n          </tbody>\\n        </table>\\n        \\n      </div>\\n    \\n      \\n      <!--[if mso | IE]>\\n          </td>\\n        </tr>\\n      </table>\\n      <![endif]-->\\n    \\n    \\n      </div>\\n`,\n    output: `\\n      <div\\n         style=\"\"\\n      >\\n        \\n      \\n      <!--[if mso | IE]><table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"\" style=\"width:600px;\" width=\"600\" ><tr><td style=\"line-height:0px;font-size:0px;mso-line-height-rule:exactly;\"><![endif]-->\\n    \\n      \\n      <div  style=\"Margin:0px auto;max-width:600px;\">\\n        \\n        <table\\n           align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"width:100%;\"\\n        >\\n          <tbody>\\n            <tr>\\n              <td\\n                 style=\"direction:ltr;font-size:0px;padding:20px 0;text-align:center;vertical-align:top;\"\\n              >\\n                <!--[if mso | IE]><table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><![endif]-->\\n                  \\n      <!--[if mso | IE]><tr><![endif]-->\\n      \\n          <!--[if mso | IE]><td class=\"\" style=\"vertical-align:top;width:600px;\" ><![endif]-->\\n            \\n      <div\\n         class=\"mj-column-per-100 mj-outlook-group-fix\" style=\"font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;\"\\n      >\\n        \\n      <table\\n         border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"vertical-align:top;\" width=\"100%\"\\n      >\\n        \\n            <tr>\\n              <td\\n                 align=\"left\" style=\"font-size:0px;padding:10px 25px;word-break:break-word;\"\\n              >\\n                \\n      <div\\n         style=\"font-family:helvetica;font-size:20px;line-height:1;text-align:left;color:#F45E43;\"\\n      >\\n        Hello World\\n      </div>\\n    \\n              </td>\\n            </tr>\\n          \\n      </table>\\n    \\n      </div>\\n    \\n          <!--[if mso | IE]></td><![endif]-->\\n    \\n\\n      <!--[if mso | IE]></tr><![endif]-->\\n    \\n                <!--[if mso | IE]></table><![endif]-->\\n              </td>\\n            </tr>\\n          </tbody>\\n        </table>\\n        \\n      </div>\\n    \\n      \\n      <!--[if mso | IE]></td></tr></table><![endif]-->\\n    \\n    \\n      </div>\\n`,\n  },\n]\n\ntestValues.forEach(testUnit => {\n  const { input, output } = testUnit\n\n  chai.expect(minifyOutlookConditionnals(input), `minifyOutlookConditionnals test failed`)\n      .to.deep.equal(output)\n})\n"
  },
  {
    "path": "packages/mjml-core/tests/shorthandParser-test.js",
    "content": "const chai = require('chai')\nconst helper = require('../lib/helpers/shorthandParser')\n\nconst shorthandParser = helper && helper.default\n\nconst testValues = [\n  {\n    input: '1px',\n    output: { top: 1, right: 1, bottom: 1, left: 1 },\n  },\n  {\n    input: '1px 0',\n    output: { top: 1, right: 0, bottom: 1, left: 0 },\n  },\n  {\n    input: '1px 2px 3px',\n    output: { top: 1, right: 2, bottom: 3, left: 2 },\n  },\n  {\n    input: '1px 2px 3px 4px',\n    output: { top: 1, right: 2, bottom: 3, left: 4 },\n  },\n  {\n    input: ' 1px 2px  3px 4px ',\n    output: { top: 1, right: 2, bottom: 3, left: 4 },\n  },\n]\n\ntestValues.forEach(testUnit => {\n  const { input, output } = testUnit\n  const directions = ['top', 'right', 'bottom', 'left']\n  directions.forEach(dir => {\n    chai.expect(shorthandParser(input, dir), `shorthandParser test failed`)\n        .to.deep.equal(output[dir])\n  })\n})\n"
  },
  {
    "path": "packages/mjml-core/tests/skeleton-test.js",
    "content": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst skeleton = require('../lib/helpers/skeleton')\n\n// The conditional style tag for Outlook does not get parsed by cheerio,\n// so each outputStyleCount excludes it\nconst testValues = [\n  {\n    options: {},\n    outputStyleCount: 1,\n  },\n  {\n    options: {\n      componentsHeadStyle: [\n        () => '.custom-component-1 .custom-child { background: red; }',\n      ],\n    },\n    outputStyleCount: 2,\n  },\n  {\n    options: {\n      headStyle: {\n        'custom-component': () =>\n          '.custom-component .custom-child { background: orange; }',\n      },\n    },\n    outputStyleCount: 2,\n  },\n  {\n    options: {\n      componentsHeadStyle: [\n        () => '.custom-component-1 .custom-child { background: yellow; }',\n      ],\n      headStyle: {\n        'custom-component': () =>\n          '.custom-component .custom-child { background: green; }',\n      },\n    },\n    outputStyleCount: 2,\n  },\n  {\n    options: {\n      style: ['#title { background: blue; }'],\n    },\n    outputStyleCount: 2,\n  },\n  {\n    options: {\n      componentsHeadStyle: [\n        () => '.custom-component-1 .custom-child { background: purple; }',\n      ],\n      headStyle: {\n        'custom-component': () =>\n          '.custom-component .custom-child { background: black; }',\n      },\n      style: [() => '#title { background: white; }'],\n    },\n    outputStyleCount: 3,\n  },\n]\n\ntestValues.forEach((testUnit) => {\n  const { options, outputStyleCount } = testUnit\n\n  const $ = load(skeleton(options))\n\n  chai\n    .expect($('head style').get().length, 'Unexpected number of style tags')\n    .to.equal(outputStyleCount)\n})\n"
  },
  {
    "path": "packages/mjml-core/tests/widthParser-test.js",
    "content": "const chai = require('chai')\nconst widthParser = require('../lib/helpers/widthParser')\n\nconst testValues = [\n  {\n    input: '1px',\n    options: {},\n    output: { parsedWidth: 1, unit: 'px'},\n  },\n  {\n    input: '33.3px',\n    options: {},\n    output: { parsedWidth: 33, unit: 'px'},\n  },\n  {\n    input: '33.3%',\n    options: {},\n    output: { parsedWidth: 33, unit: '%'},\n  },\n  {\n    input: '33.3%',\n    options: { parseFloatToInt: false },\n    output: { parsedWidth: 33.3, unit: '%'},\n  },\n]\n\ntestValues.forEach(testUnit => {\n  const { input, options, output } = testUnit\n\n  chai.expect(widthParser(input, options), `widthParser test failed`)\n      .to.deep.equal(output)\n})\n"
  },
  {
    "path": "packages/mjml-divider/README.md",
    "content": "### mj-divider\n\nDisplays a horizontal divider that can be customized like a HTML border.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-divider border-width=\"1px\" border-style=\"dashed\" border-color=\"lightgrey\" />\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute                  | accepts                 | description                                        | default value |\n| -------------------------- | ----------------------- | -------------------------------------------------- | ------------- |\n| align                      | `left` `center` `right` | horizontal alignment                               | `center`      |\n| border-color               | CSS color formats       | divider color                                      | `#000000`     |\n| border-style               | string                  | CSS values, e.g. `dashed` `dotted` `solid`         | `solid`       |\n| border-width               | `px`                    | divider's border width                             | `4px`         |\n| container-background-color | CSS color formats       | inner element background color                     |               |\n| css-class                  | string                  | class name, added to the root HTML element created |               |\n| padding                    | `px` `%`                | divider padding, supports up to 4 parameters       | `10px 25px`   |\n| padding-bottom             | `px` `%`                | divider bottom padding                             |               |\n| padding-left               | `px` `%`                | divider left padding                               |               |\n| padding-right              | `px` `%`                | divider right padding                              |               |\n| padding-top                | `px` `%`                | divider top padding                                |               |\n| width                      | `px` `%`                | divider width                                      | `100%`        |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/divider\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-divider/package.json",
    "content": "{\n  \"name\": \"mjml-divider\",\n  \"description\": \"mjml-divider\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-divider\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-divider/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default class MjDivider extends BodyComponent {\n  static componentName = 'mj-divider'\n\n  static allowedAttributes = {\n    'border-color': 'color',\n    'border-style': 'string',\n    'border-width': 'unit(px)',\n    'container-background-color': 'color',\n    padding: 'unit(px,%){1,4}',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    width: 'unit(px,%)',\n    align: 'enum(left,center,right)',\n  }\n\n  static defaultAttributes = {\n    'border-color': '#000000',\n    'border-style': 'solid',\n    'border-width': '4px',\n    padding: '10px 25px',\n    width: '100%',\n    align: 'center',\n  }\n\n  getStyles() {\n    let computeAlign = '0px auto'\n    if (this.getAttribute('align') === 'left') {\n      computeAlign = '0px'\n    } else if (this.getAttribute('align') === 'right') {\n      computeAlign = '0px 0px 0px auto'\n    }\n    const p = {\n      'border-top': ['style', 'width', 'color']\n        .map((attr) => this.getAttribute(`border-${attr}`))\n        .join(' '),\n      'font-size': '1px',\n      margin: computeAlign,\n      width: this.getAttribute('width'),\n    }\n\n    return {\n      p,\n      outlook: {\n        ...p,\n        width: this.getOutlookWidth(),\n      },\n    }\n  }\n\n  getOutlookWidth() {\n    const { containerWidth } = this.context\n    const paddingSize =\n      this.getShorthandAttrValue('padding', 'left') +\n      this.getShorthandAttrValue('padding', 'right')\n\n    const width = this.getAttribute('width')\n\n    const { parsedWidth, unit } = widthParser(width)\n\n    switch (unit) {\n      case '%': {\n        const effectiveWidth = parseInt(containerWidth, 10) - paddingSize\n        const percentMultiplier = parseInt(parsedWidth, 10) / 100\n        return `${effectiveWidth * percentMultiplier}px`\n      }\n      case 'px':\n        return width\n      default:\n        return `${parseInt(containerWidth, 10) - paddingSize}px`\n    }\n  }\n\n  renderAfter() {\n    return `\n      <!--[if mso | IE]>\n        <table\n          ${this.htmlAttributes({\n            align: this.getAttribute('align'),\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            style: 'outlook',\n            role: 'presentation',\n            width: this.getOutlookWidth(),\n          })}\n        >\n          <tr>\n            <td style=\"height:0;line-height:0;\">\n              &nbsp;\n            </td>\n          </tr>\n        </table>\n      <![endif]-->\n    `\n  }\n\n  render() {\n    return `\n      <p\n        ${this.htmlAttributes({\n          style: 'p',\n        })}\n      >\n      </p>\n      ${this.renderAfter()}\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-group/README.md",
    "content": "### mj-group\n\nPrevent adjacent `mj-column` instances from stacking on mobile by wrapping them inside an `mj-group` tag, keeping them side by side on mobile.\n\n<figure>\n  <figcaption>Desktop</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/group-example-1.png\"\n       alt=\"easy and quick; responsive\" />\n</figure>\n\n<figure>\n  <figcaption>Mobile</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/group-example-2.png\"\n      alt=\"easy and quick; responsive\" />\n</figure>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-group>\n        <mj-column>\n          <mj-image width=\"137px\" height=\"185px\" padding=\"0\" src=\"https://static.mailjet.com/mjml-website/documentation/group-1.png\" />\n          <mj-text align=\"center\">\n            <h2>Easy and quick</h2>\n            <p>Write less code, save time and code more efficiently with MJML’s semantic syntax.</p>\n          </mj-text>\n        </mj-column>\n        <mj-column>\n          <mj-image width=\"166px\" height=\"185px\" padding=\"0\" src=\"https://static.mailjet.com/mjml-website/documentation/group-2.png\" />\n          <mj-text align=\"center\">\n            <h2>Responsive</h2>\n            <p>MJML is responsive by design on most-popular email clients, even Outlook.</p>\n          </mj-text>\n        </mj-column>\n      </mj-group>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>Column inside a group must have a width in percentage, not in pixel.</p>\n</div>\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>You can nest both <code>mj-column</code> and <code>mj-group</code> inside a <code>mj-section</code>.</p>\n</div>\n\n\n#### Attributes\n\n| attribute        | accepts           | description                                        | default attributes                             |\n| ---------------- | ----------------- | -------------------------------------------------- | ---------------------------------------------- |\n| background-color | CSS color formats | background color for a group                       |                                                |\n| css-class        | string            | class name, added to the root HTML element created |                                                |\n| direction        | `ltr` `rtl`       | set the display order of direct children           | `ltr`                                          |\n| vertical-align   | string            | CSS values, e.g. `middle` `top` `bottom`           |                                                |\n| width            | `px` `%`          | group width                                        | (100 / number of non-raw elements in section)% |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/group\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-group/package.json",
    "content": "{\n  \"name\": \"mjml-group\",\n  \"description\": \"mjml-group\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-group\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-group/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default class MjGroup extends BodyComponent {\n  static componentName = 'mj-group'\n\n  static allowedAttributes = {\n    'background-color': 'color',\n    direction: 'enum(ltr,rtl)',\n    'vertical-align': 'enum(top,bottom,middle)',\n    width: 'unit(px,%)',\n  }\n\n  static defaultAttributes = {\n    direction: 'ltr',\n  }\n\n  getChildContext() {\n    const { containerWidth: parentWidth } = this.context\n    const { nonRawSiblings, children } = this.props\n    const paddingSize =\n      this.getShorthandAttrValue('padding', 'left') +\n      this.getShorthandAttrValue('padding', 'right')\n\n    let containerWidth =\n      this.getAttribute('width') ||\n      `${parseFloat(parentWidth) / nonRawSiblings}px`\n\n    const { unit, parsedWidth } = widthParser(containerWidth, {\n      parseFloatToInt: false,\n    })\n\n    if (unit === '%') {\n      containerWidth = `${\n        (parseFloat(parentWidth) * parsedWidth) / 100 - paddingSize\n      }px`\n    } else {\n      containerWidth = `${parsedWidth - paddingSize}px`\n    }\n\n    return {\n      ...this.context,\n      containerWidth,\n      nonRawSiblings: children.length,\n    }\n  }\n\n  getStyles() {\n    return {\n      div: {\n        'font-size': '0',\n        'line-height': '0',\n        'text-align': 'left',\n        display: 'inline-block',\n        width: '100%',\n        direction: this.getAttribute('direction'),\n        'vertical-align': this.getAttribute('vertical-align'),\n        'background-color': this.getAttribute('background-color'),\n      },\n      tdOutlook: {\n        'vertical-align': this.getAttribute('vertical-align'),\n        width: this.getWidthAsPixel(),\n      },\n    }\n  }\n\n  getParsedWidth(toString) {\n    const { nonRawSiblings } = this.props\n\n    const width = this.getAttribute('width') || `${100 / nonRawSiblings}%`\n\n    const { unit, parsedWidth } = widthParser(width, {\n      parseFloatToInt: false,\n    })\n\n    if (toString) {\n      return `${parsedWidth}${unit}`\n    }\n\n    return {\n      unit,\n      parsedWidth,\n    }\n  }\n\n  getWidthAsPixel() {\n    const { containerWidth } = this.context\n\n    const { unit, parsedWidth } = widthParser(this.getParsedWidth(true), {\n      parseFloatToInt: false,\n    })\n\n    if (unit === '%') {\n      return `${(parseFloat(containerWidth) * parsedWidth) / 100}px`\n    }\n    return `${parsedWidth}px`\n  }\n\n  getColumnClass() {\n    const { addMediaQuery } = this.context\n\n    let className = ''\n\n    const { parsedWidth, unit } = this.getParsedWidth()\n\n    switch (unit) {\n      case '%':\n        className = `mj-column-per-${parseInt(parsedWidth, 10)}`\n        break\n\n      case 'px':\n      default:\n        className = `mj-column-px-${parseInt(parsedWidth, 10)}`\n        break\n    }\n\n    // Add className to media queries\n    addMediaQuery(className, {\n      parsedWidth,\n      unit,\n    })\n\n    return className\n  }\n\n  render() {\n    const { children, nonRawSiblings } = this.props\n\n    const { containerWidth: groupWidth } = this.getChildContext()\n\n    const { containerWidth } = this.context\n\n    const getElementWidth = (width) => {\n      if (!width) {\n        return `${\n          parseInt(containerWidth, 10) / parseInt(nonRawSiblings, 10)\n        }px`\n      }\n\n      const { unit, parsedWidth } = widthParser(width, {\n        parseFloatToInt: false,\n      })\n\n      if (unit === '%') {\n        return `${(100 * parsedWidth) / groupWidth}px`\n      }\n      return `${parsedWidth}${unit}`\n    }\n\n    let classesName = `${this.getColumnClass()} mj-outlook-group-fix`\n\n    if (this.getAttribute('css-class')) {\n      classesName += ` ${this.getAttribute('css-class')}`\n    }\n\n    return `\n      <div\n        ${this.htmlAttributes({\n          class: classesName,\n          style: 'div',\n        })}\n      >\n        <!--[if mso | IE]>\n        <table\n          ${this.htmlAttributes({\n            bgcolor:\n              this.getAttribute('background-color') === 'none'\n                ? undefined\n                : this.getAttribute('background-color'),\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            role: 'presentation',\n          })}\n        >\n          <tr>\n        <![endif]-->\n          ${this.renderChildren(children, {\n            attributes: { mobileWidth: 'mobileWidth' },\n            renderer: (component) =>\n              component.constructor.isRawElement()\n                ? component.render()\n                : `\n              <!--[if mso | IE]>\n              <td\n                ${component.htmlAttributes({\n                  style: {\n                    align: component.getAttribute('align'),\n                    'vertical-align': component.getAttribute('vertical-align'),\n                    width: getElementWidth(\n                      component.getWidthAsPixel\n                        ? component.getWidthAsPixel()\n                        : component.getAttribute('width'),\n                    ),\n                  },\n                })}\n              >\n              <![endif]-->\n                ${component.render()}\n              <!--[if mso | IE]>\n              </td>\n              <![endif]-->\n          `,\n          })}\n        <!--[if mso | IE]>\n          </tr>\n          </table>\n        <![endif]-->\n      </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head/package.json",
    "content": "{\n  \"name\": \"mjml-head\",\n  \"description\": \"mjml-head\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head/src/index.js",
    "content": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjHead extends HeadComponent {\n  static componentName = 'mj-head'\n\n  handler() {\n    return this.handlerChildren()\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-attributes/README.md",
    "content": "### mj-attributes\n\nInside the `mj-attributes` tag, you can cite other MJML components, like `mj-text` for example, to override the default settings for that component.\n\nAn `mj-all` tag is like the above, but affects all MJML components.\n\nAn `mj-class` tag creates a named group of MJML attributes you can apply to MJML\ncomponents using `mj-class=\"<name>\"`.\n\n```xml\n<mjml>\n <mj-head>\n   <mj-attributes>\n     <mj-text padding=\"0\" />\n     <mj-class name=\"blue\" color=\"blue\" />\n     <mj-class name=\"big\" font-size=\"20px\" />\n     <mj-all font-family=\"Arial\" />\n   </mj-attributes>\n </mj-head>\n <mj-body>\n   <mj-section>\n     <mj-column>\n       <mj-text mj-class=\"blue big\">\n         Hello World!\n       </mj-text>\n     </mj-column>\n   </mj-section>\n </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>MJML will apply found attributes in the following order:</p>\n  <ul>\n    <li>inline attributes within tags,</li>\n    <li>attributes found in tags within the <code>mj-attributes</code> tag, e.g. <code>mj-text</code>,</li>\n    <li>the <code>mj-all</code> tag wuthin <code>mj-attributes</code>, and</li>\n    <li>the default MJML values.</li>\n  </ul>\n</div>\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-attributes\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-attributes/package.json",
    "content": "{\n  \"name\": \"mjml-head-attributes\",\n  \"description\": \"mjml-head-attributes\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-attributes\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-attributes/src/index.js",
    "content": "import { forEach, omit, reduce } from 'lodash'\n\nimport { HeadComponent } from 'mjml-core'\n\nexport default class MjAttributes extends HeadComponent {\n  static componentName = 'mj-attributes'\n\n  handler() {\n    const { add } = this.context\n\n    const { children } = this.props\n\n    forEach(children, (child) => {\n      const { tagName, attributes, children } = child\n\n      if (tagName === 'mj-class') {\n        add('classes', attributes.name, omit(attributes, ['name']))\n\n        add(\n          'classesDefault',\n          attributes.name,\n          reduce(\n            children,\n            (acc, { tagName, attributes }) => ({\n              ...acc,\n              [tagName]: attributes,\n            }),\n            {},\n          ),\n        )\n      } else {\n        add('defaultAttributes', tagName, attributes)\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-breakpoint/README.md",
    "content": "### mj-breakpoint\n\nAllows you to control at what width the layout should change from the desktop/mobile view to the desktop one.\n\n```xml\n<mjml>\n  <mj-head>\n    <mj-breakpoint width=\"320px\" />\n  </mj-head>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-text>\n          Hello World!\n        </mj-text>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute | accepts | description        | default value |\n| --------- | ------- | ------------------ | ------------- |\n| width     | `px`    | breakpoint's value |               |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-breakpoint\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-breakpoint/package.json",
    "content": "{\n  \"name\": \"mjml-head-breakpoint\",\n  \"description\": \"mjml-head-breakpoint\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-breakpoint\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-breakpoint/src/index.js",
    "content": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjBreakpoint extends HeadComponent {\n  static componentName = 'mj-breakpoint'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    width: 'unit(px)',\n  }\n\n  handler() {\n    const { add } = this.context\n\n    add('breakpoint', this.getAttribute('width'))\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-font/README.md",
    "content": "### mj-font\n\nImports external fonts and is only applied if the template uses the font.\n\nThe `href` attribute should point to a hosted CSS file that contains a `@font-face` declaration, for example: [https://fonts\n.googleapis.com/css?family=Raleway](https://fonts.googleapis.com/css?family=Raleway)\n\n```xml\n<mjml>\n  <mj-head>\n    <mj-font name=\"Raleway\"\n      href=\"https://fonts.googleapis.com/css?family=Raleway\" />\n  </mj-head>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-text font-family=\"Raleway, Arial\">\n          Hello World!\n        </mj-text>\n      </mj-column>\n     </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute | accepts | description              | default value |\n| --------- | ------- | ------------------------ | ------------- |\n| href      | string  | URL of a hosted CSS file |               |\n| name      | string  | name of the font         |               |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-font\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-font/package.json",
    "content": "{\n  \"name\": \"mjml-head-font\",\n  \"description\": \"mjml-head-font\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-font\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-font/src/index.js",
    "content": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjFont extends HeadComponent {\n  static componentName = 'mj-font'\n\n  static allowedAttributes = {\n    name: 'string',\n    href: 'string',\n  }\n\n  handler() {\n    const { add } = this.context\n\n    add('fonts', this.getAttribute('name'), this.getAttribute('href'))\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-html-attributes/README.md",
    "content": "### mj-html-attributes\n\nAllows you to add custom attributes on any HTML tag within the generated HTML, using CSS selectors.\n\nIt's not needed for most email creations, but can be useful in some cases, e.g. editable templates.\n\n```xml\n<mjml>\n <mj-head>\n   <mj-html-attributes>\n     <mj-selector path=\".custom div\">\n       <mj-html-attribute name=\"data-id\">42</mj-html-attribute>\n     </mj-selector>\n   </mj-html-attributes>\n </mj-head>\n <mj-body>\n   <mj-section>\n     <mj-column>\n       <mj-text css-class=\"custom\">\n         Hello World!\n       </mj-text>\n     </mj-column>\n   </mj-section>\n </mj-body>\n</mjml>\n```\n\nIn the generated HTML, an `mj-text` tag becomes a `td` tag with a child `div` tag.\n\nIn this example, the `td` tag will have the `class=\"custom\"` attribute. Using the css selector `path=\".custom div\"`, the `div` inside the `td` will get the attribute `data-id=\"42\"`.\n\nTo use this component, you will likely have to look at the generated HTML to see exactly where the `css-class` is applied, to know which CSS selector you need to add your custom attribute to.\n\nYou can use multiple `mj-selector` tags inside a `mj-html-attributes` tag, and multiple `mj-html-attribute` tags inside a `mj-selector` tag.\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-html-attributes\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-html-attributes/package.json",
    "content": "{\n  \"name\": \"mjml-head-html-attributes\",\n  \"description\": \"mjml-head-html-attributes\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-html-attributes\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-html-attributes/src/index.js",
    "content": "import { get } from 'lodash'\nimport { HeadComponent } from 'mjml-core'\n\nexport default class MjHtmlAttributes extends HeadComponent {\n  static componentName = 'mj-html-attributes'\n\n  handler() {\n    const { add } = this.context\n    const { children } = this.props\n\n    children\n      .filter((c) => c.tagName === 'mj-selector')\n      .forEach((selector) => {\n        const { attributes, children } = selector\n        const { path } = attributes\n\n        const custom = children\n          .filter(\n            (c) =>\n              c.tagName === 'mj-html-attribute' && !!get(c, 'attributes.name'),\n          )\n          .reduce(\n            (acc, c) => ({\n              ...acc,\n              [c.attributes.name]: c.content,\n            }),\n            {},\n          )\n\n        add('htmlAttributes', path, custom)\n      })\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-preview/README.md",
    "content": "### mj-preview\n\nThis tag allows you to set the preview text that will be displayed in the inbox of the recipient.\n\n```xml\n<mjml>\n <mj-head>\n   <mj-preview>Hello MJML</mj-preview>\n </mj-head>\n <mj-body>\n   <mj-section>\n     <mj-column>\n       <mj-text>\n         Hello World!\n       </mj-text>\n     </mj-column>\n   </mj-section>\n </mj-body>\n</mjml>\n```\n\n`mj-preview` doesn't support any attributes.\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-preview\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-preview/package.json",
    "content": "{\n  \"name\": \"mjml-head-preview\",\n  \"description\": \"mjml-head-preview\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-preview\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-preview/src/index.js",
    "content": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjPreview extends HeadComponent {\n  static componentName = 'mj-preview'\n\n  static endingTag = true\n\n  handler() {\n    const { add } = this.context\n\n    add('preview', this.getContent())\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-style/README.md",
    "content": "### mj-style\n\nAllows you to set CSS styles that will be applied to your MJML document as well as the outputted HTML.\n\nThe CSS styles will be added to the `head` tag of the rendered HTML by default, but can also be inlined by using the `inline=\"inline\"` attribute.\n\nHere is an example showing its use in combination with the `css-class` attribute, which is supported by all body components.\n\n```xml\n<mjml>\n  <mj-head>\n    <mj-attributes>\n      <mj-class name=\"mjclass\" color=\"green\" font-size=\"30px\" />\n    </mj-attributes>\n    <mj-style inline=\"inline\">\n      .blue-text div {\n        color: blue !important;\n      }\n    </mj-style>\n    <mj-style>\n      .red-text div {\n        color: red !important;\n        text-decoration: underline !important;\n      }\n    </mj-style>\n  </mj-head>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-text css-class=\"red-text\">I'm red and underlined</mj-text>\n        <mj-text css-class=\"blue-text\">I'm blue because of inline</mj-text>\n        <mj-text mj-class=\"mjclass\">I'm green</mj-text>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-tip\" role=\"alert\">\n  <p>Tip</p>\n  <p>MJML generates multiple HTML tags from a single MJML tag. For optimal flexibility, the <code>css-class</code> will be applied to the most outer HTML tag, therefore if you want to target a specific child tag with a CSS selector, you may need to look at the generated HTML to determine the exact selector you need.</p>\n</div>\n\n#### Attributes\n\n| attribute | accepts | description                      | default value |\n| --------- | ------- | -------------------------------- | ------------- |\n| inline    | string  | set to `inline` to inline styles |               |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-style\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-style/package.json",
    "content": "{\n  \"name\": \"mjml-head-style\",\n  \"description\": \"mjml-head-style\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-style\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-style/src/index.js",
    "content": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjStyle extends HeadComponent {\n  static componentName = 'mj-style'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    inline: 'string',\n  }\n\n  handler() {\n    const { add } = this.context\n\n    add(\n      this.getAttribute('inline') === 'inline' ? 'inlineStyle' : 'style',\n      this.getContent(),\n    )\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-title/README.md",
    "content": "### mj-title\n\nDefines the document's title by populating the title tag. This can be shown in the browsers title bar in some cases.\n\nIts content is also used to populate the value of the `aria-label` attribute located in the immediate child `div` of the `body` tag, which is used to aid accessibility.\n\n```xml\n<mjml>\n <mj-head>\n   <mj-title>Hello MJML</mj-title>\n </mj-head>\n <mj-body>\n   <mj-section>\n     <mj-column>\n       <mj-text>\n         Hello World!\n       </mj-text>\n     </mj-column>\n   </mj-section>\n </mj-body>\n</mjml>\n```\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/head-title\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-head-title/package.json",
    "content": "{\n  \"name\": \"mjml-head-title\",\n  \"description\": \"mjml-head-title\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-head-title\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-head-title/src/index.js",
    "content": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjTitle extends HeadComponent {\n  static componentName = 'mj-title'\n\n  static endingTag = true\n\n  handler() {\n    const { add } = this.context\n\n    add('title', this.getContent())\n  }\n}\n"
  },
  {
    "path": "packages/mjml-hero/README.md",
    "content": "### mj-hero\n\nDisplays a hero image and behaves like an `mj-section` tag with a single `mj-column` tag.\n\nThe `background-height` and `background-width` attributes are mandatory and it's best to use an image with width the same as the `mj-body` (`width=\"600px\"` by default) and height the same or larger than the `height` of `mj-hero`.\n\nUse `background-color` to provide a fallback color in case an email client doesn't support `background-url`.\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>The <code>height</code> attribute is only required for <code>mode=\"fixed-height\"</code>.</p>\n</div>\n\n<figure>\n  <figcaption>Fixed height</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/hero-example-1.png\"\n     alt=\"demo background picture with fixed height\" />\n</figure>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-hero\n      mode=\"fixed-height\"\n      height=\"469px\"\n      background-width=\"600px\"\n      background-height=\"469px\"\n      background-url=\n          \"https://static.mailjet.com/mjml-website/documentation/hero.jpg\"\n      background-color=\"#2a3448\"\n      padding=\"100px 0px\">\n      <mj-text\n        padding=\"20px\"\n        color=\"#ffffff\"\n        font-family=\"Helvetica\"\n        align=\"center\"\n        font-size=\"45px\"\n        line-height=\"45px\"\n        font-weight=\"900\">\n        GO TO SPACE\n      </mj-text>\n      <mj-button href=\"https://mjml.io/\" align=\"center\">\n        ORDER YOUR TICKET NOW\n      </mj-button>\n    </mj-hero>\n  </mj-body>\n</mjml>\n```\n\n<figure>\n  <figcaption>Fluid height</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/hero-example-1.png\"\n     alt=\"demo background picture with fixed height\" />\n</figure>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-hero\n      mode=\"fluid-height\"\n      background-width=\"600px\"\n      background-height=\"469px\"\n      background-url=\n          \"https://static.mailjet.com/mjml-website/documentation/hero.jpg\"\n      background-color=\"#2a3448\"\n      padding=\"100px 0px\">\n      <mj-text\n        padding=\"20px\"\n        color=\"#ffffff\"\n        font-family=\"Helvetica\"\n        align=\"center\"\n        font-size=\"45px\"\n        line-height=\"45px\"\n        font-weight=\"900\">\n        GO TO SPACE\n      </mj-text>\n      <mj-button href=\"https://mjml.io/\" align=\"center\">\n        ORDER YOUR TICKET NOW\n      </mj-button>\n    </mj-hero>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute              | accepts                 | description                                                        | default value                 |\n| ---------------------- | ----------------------- | ------------------------------------------------------------------ | ----------------------------- |\n| background-color       | CSS color formats       | hero background color                                              | #ffffff                       |\n| background-height      | `px` `%`                | height of the image used, mandatory                                |                               |\n| background-position    | string                  | CSS values, i.e. `left` `center` `right` + `top` `center` `bottom` | `center center`               |\n| background-url         | string                  | absolute background in URL format                                  | `null`                        |\n| background-width       | `px` `%`                | width of the image used, mandatory                                 | inherits parent element width |\n| border-radius          | string                  | border radius                                                      |                               |\n| css-class              | string                  | class name, added to the root HTML element created                 |                               |\n| height                 | `px` `%`                | hero section height, (required for `fixed-height` mode)            | `0px`                         |\n| inner-background-color | CSS color formats       | content background color                                           |                               |\n| mode                   | string                  | `fluid-height` or `fixed-height`                                   | `fluid-height`                |\n| padding                | `px` `%`                | hero padding, supports up to 4 parameters                          | `0px`                         |\n| padding-bottom         | `px` `%`                | hero bottom padding                                                | `null`                        |\n| padding-left           | `px` `%`                | hero left padding                                                  | `null`                        |\n| padding-right          | `px` `%`                | hero right padding                                                 | `null`                        |\n| padding-top            | `px` `%`                | hero top padding                                                   | `null`                        |\n| vertical-align         | `top` `middle` `bottom` | content vertical alignment                                         | `top`                         |\n\n<ul class=\"cta-container\">\n  <li>Fixed height: <br><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/hero\">Try it live</a></li>\n  <li>Fluid height: <br><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/hero/1\">Try it live</a></li>\n</ul>\n"
  },
  {
    "path": "packages/mjml-hero/package.json",
    "content": "{\n  \"name\": \"mjml-hero\",\n  \"description\": \"mjml-hero\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-hero\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-hero/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\nimport { flow, identity, join, filter } from 'lodash/fp'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nconst makeBackgroundString = flow(filter(identity), join(' '))\n\nexport default class MjHero extends BodyComponent {\n  static componentName = 'mj-hero'\n\n  static allowedAttributes = {\n    mode: 'string',\n    height: 'unit(px,%)',\n    'background-url': 'string',\n    'background-width': 'unit(px,%)',\n    'background-height': 'unit(px,%)',\n    'background-position': 'string',\n    'border-radius': 'string',\n    'container-background-color': 'color',\n    'inner-background-color': 'color',\n    'inner-padding': 'unit(px,%){1,4}',\n    'inner-padding-top': 'unit(px,%)',\n    'inner-padding-left': 'unit(px,%)',\n    'inner-padding-right': 'unit(px,%)',\n    'inner-padding-bottom': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    'background-color': 'color',\n    'vertical-align': 'enum(top,bottom,middle)',\n  }\n\n  static defaultAttributes = {\n    mode: 'fixed-height',\n    height: '0px',\n    'background-url': null,\n    'background-position': 'center center',\n    padding: '0px',\n    'padding-bottom': null,\n    'padding-left': null,\n    'padding-right': null,\n    'padding-top': null,\n    'background-color': '#ffffff',\n    'vertical-align': 'top',\n  }\n\n  getChildContext() {\n    // Refactor -- removePaddingFor(width, ['padding', 'inner-padding'])\n    const { containerWidth } = this.context\n    const paddingSize =\n      this.getShorthandAttrValue('padding', 'left') +\n      this.getShorthandAttrValue('padding', 'right')\n\n    let currentContainerWidth = `${parseFloat(containerWidth)}px`\n\n    const { unit, parsedWidth } = widthParser(currentContainerWidth, {\n      parseFloatToInt: false,\n    })\n\n    if (unit === '%') {\n      currentContainerWidth = `${\n        (parseFloat(containerWidth) * parsedWidth) / 100 - paddingSize\n      }px`\n    } else {\n      currentContainerWidth = `${parsedWidth - paddingSize}px`\n    }\n\n    return {\n      ...this.context,\n      containerWidth: currentContainerWidth,\n    }\n  }\n\n  getStyles() {\n    const { containerWidth } = this.context\n    const backgroundRatio = Math.round(\n      (parseInt(this.getAttribute('background-height'), 10) /\n        parseInt(this.getAttribute('background-width'), 10)) *\n        100,\n    )\n\n    const width = this.getAttribute('background-width') || containerWidth\n\n    return {\n      div: {\n        margin: '0 auto',\n        'max-width': containerWidth,\n      },\n      table: {\n        width: '100%',\n      },\n      tr: {\n        'vertical-align': 'top',\n      },\n      'td-fluid': {\n        width: `0.01%`,\n        'padding-bottom': `${backgroundRatio}%`,\n        'mso-padding-bottom-alt': '0',\n      },\n      'outlook-table': {\n        width: containerWidth,\n      },\n      'outlook-td': {\n        'line-height': 0,\n        'font-size': 0,\n        'mso-line-height-rule': 'exactly',\n      },\n      'outlook-inner-table': {\n        width: containerWidth,\n      },\n      'outlook-image': {\n        border: '0',\n        height: this.getAttribute('background-height'),\n        'mso-position-horizontal': 'center',\n        position: 'absolute',\n        top: 0,\n        width,\n        'z-index': '-3',\n      },\n      'outlook-inner-td': {\n        'background-color': this.getAttribute('inner-background-color'),\n        padding: this.getAttribute('inner-padding'),\n        'padding-top': this.getAttribute('inner-padding-top'),\n        'padding-left': this.getAttribute('inner-padding-left'),\n        'padding-right': this.getAttribute('inner-padding-right'),\n        'padding-bottom': this.getAttribute('inner-padding-bottom'),\n      },\n      'inner-table': {\n        width: '100%',\n        margin: '0px',\n      },\n      'inner-div': {\n        'background-color': this.getAttribute('inner-background-color'),\n        float: this.getAttribute('align'),\n        margin: '0px auto',\n        width: this.getAttribute('width'),\n      },\n    }\n  }\n\n  getBackground = () =>\n    makeBackgroundString([\n      this.getAttribute('background-color'),\n      ...(this.getAttribute('background-url')\n        ? [\n            `url('${this.getAttribute('background-url')}')`,\n            'no-repeat',\n            `${this.getAttribute('background-position')} / cover`,\n          ]\n        : []),\n    ])\n\n  renderContent() {\n    const { containerWidth } = this.context\n    const { children } = this.props\n\n    return `\n      <!--[if mso | IE]>\n        <table\n          ${this.htmlAttributes({\n            align: this.getAttribute('align'),\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            style: 'outlook-inner-table',\n            width: containerWidth.replace('px', ''),\n          })}\n        >\n          <tr>\n            <td ${this.htmlAttributes({ style: 'outlook-inner-td' })}>\n      <![endif]-->\n      <div\n        ${this.htmlAttributes({\n          align: this.getAttribute('align'),\n          class: 'mj-hero-content',\n          style: 'inner-div',\n        })}\n      >\n        <table\n          ${this.htmlAttributes({\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            role: 'presentation',\n            style: 'inner-table',\n          })}\n        >\n          <tbody>\n            <tr>\n              <td ${this.htmlAttributes({ style: 'inner-td' })} >\n                <table\n                  ${this.htmlAttributes({\n                    border: '0',\n                    cellpadding: '0',\n                    cellspacing: '0',\n                    role: 'presentation',\n                    style: 'inner-table',\n                  })}\n                >\n                  <tbody>\n                    ${this.renderChildren(children, {\n                      renderer: (component) =>\n                        component.constructor.isRawElement()\n                          ? component.render()\n                          : `\n                        <tr>\n                          <td\n                            ${component.htmlAttributes({\n                              align: component.getAttribute('align'),\n                              background: component.getAttribute(\n                                'container-background-color',\n                              ),\n                              class: component.getAttribute('css-class'),\n                              style: {\n                                background: component.getAttribute(\n                                  'container-background-color',\n                                ),\n                                'font-size': '0px',\n                                padding: component.getAttribute('padding'),\n                                'padding-top':\n                                  component.getAttribute('padding-top'),\n                                'padding-right':\n                                  component.getAttribute('padding-right'),\n                                'padding-bottom':\n                                  component.getAttribute('padding-bottom'),\n                                'padding-left':\n                                  component.getAttribute('padding-left'),\n                                'word-break': 'break-word',\n                              },\n                            })}\n                          >\n                            ${component.render()}\n                          </td>\n                        </tr>\n                      `,\n                    })}\n                  </tbody>\n                </table>\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <!--[if mso | IE]>\n            </td>\n          </tr>\n        </table>\n      <![endif]-->\n    `\n  }\n\n  renderMode() {\n    const commonAttributes = {\n      background: this.getAttribute('background-url'),\n      style: {\n        background: this.getBackground(),\n        'background-position': this.getAttribute('background-position'),\n        'background-repeat': 'no-repeat',\n        'border-radius': this.getAttribute('border-radius'),\n        padding: this.getAttribute('padding'),\n        'padding-top': this.getAttribute('padding-top'),\n        'padding-left': this.getAttribute('padding-left'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n        'vertical-align': this.getAttribute('vertical-align'),\n      },\n    }\n\n    /* eslint-disable no-alert, no-case-declarations */\n    switch (this.getAttribute('mode')) {\n      case 'fluid-height':\n        const magicTd = this.htmlAttributes({ style: `td-fluid` })\n\n        return `\n          <td ${magicTd} />\n          <td ${this.htmlAttributes({ ...commonAttributes })}>\n            ${this.renderContent()}\n          </td>\n          <td ${magicTd} />\n        `\n      case 'fixed-height':\n      default:\n        const height =\n          parseInt(this.getAttribute('height'), 10) -\n          this.getShorthandAttrValue('padding', 'top') -\n          this.getShorthandAttrValue('padding', 'bottom')\n\n        return `\n          <td\n            ${this.htmlAttributes({\n              ...commonAttributes,\n              height,\n              style: {\n                ...commonAttributes.style,\n                height: `${height}px`,\n              },\n            })}\n          >\n            ${this.renderContent()}\n          </td>\n        `\n    }\n    /* eslint-enable no-alert, no-case-declarations */\n  }\n\n  render() {\n    const { containerWidth } = this.context\n\n    return `\n      <!--[if mso | IE]>\n        <table\n          ${this.htmlAttributes({\n            align: 'center',\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            role: 'presentation',\n            style: 'outlook-table',\n            width: parseInt(containerWidth, 10),\n          })}\n        >\n          <tr>\n            <td ${this.htmlAttributes({ style: 'outlook-td' })}>\n              <v:image\n                ${this.htmlAttributes({\n                  style: 'outlook-image',\n                  src: this.getAttribute('background-url'),\n                  'xmlns:v': 'urn:schemas-microsoft-com:vml',\n                })}\n              />\n      <![endif]-->\n      <div\n        ${this.htmlAttributes({\n          align: this.getAttribute('align'),\n          class: this.getAttribute('css-class'),\n          style: 'div',\n        })}\n      >\n        <table\n          ${this.htmlAttributes({\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            role: 'presentation',\n            style: 'table',\n          })}\n        >\n          <tbody>\n            <tr\n              ${this.htmlAttributes({\n                style: 'tr',\n              })}\n            >\n              ${this.renderMode()}\n            </tr>\n          </tbody>\n      </table>\n    </div>\n    <!--[if mso | IE]>\n          </td>\n        </tr>\n      </table>\n    <![endif]-->\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-image/README.md",
    "content": "### mj-image\n\nDisplays a responsive image in your email. It is similar to the HTML `<img />` tag.\n\nNote that if no width is provided, the image will use the parent column width.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-image width=\"300px\" src=\"https://static.mailjet.com/mjml-website/documentation/image.png\" />\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute                  | accepts                 | description                                                                     | default value         |\n| -------------------------- | ----------------------- | ------------------------------------------------------------------------------- | --------------------- |\n| align                      | `left` `center` `right` | image alignment                                                                 | `center`              |\n| alt                        | string                  | image description                                                               | `''`                  |\n| border                     | string                  | CSS border format                                                               | `0`                   |\n| border-bottom              | string                  | CSS border format                                                               |                       |\n| border-left                | string                  | CSS border format                                                               |                       |\n| border-radius              | `px` `%`                | border radius                                                                   |                       |\n| border-right               | string                  | CSS border format                                                               |                       |\n| border-top                 | string                  | CSS border format                                                               |                       |\n| container-background-color | CSS color formats       | inner element background color                                                  |                       |\n| css-class                  | string                  | class name, added to the root HTML element created                              |                       |\n| fluid-on-mobile            | boolean                 | if `true`, will be full width on mobile even if `width` is set                  |                       |\n| font-size                  | `px`                    | size of the alt text when image is not rendered                                 | `13px`                |\n| height                     | `px`                    | image height                                                                    | `auto`                |\n| href                       | string                  | link to redirect to on click, in URL format                                     |                       |\n| max-height                 | `px` `%`                | specify the maximum height of an image                                          |                       |\n| name                       | string                  | specify the link name attribute                                                 |                       |\n| padding                    | `px` `%`                | hero padding, supports up to 4 parameters                                       | `10px 25px`           |\n| padding-bottom             | `px` `%`                | hero bottom padding                                                             |                       |\n| padding-left               | `px` `%`                | hero left padding                                                               |                       |\n| padding-right              | `px` `%`                | hero right padding                                                              |                       |\n| padding-top                | `px` `%`                | hero top padding                                                                |                       |\n| rel                        | string                  | specify the rel attribute                                                       |                       |\n| sizes                      | string                  | set width based on query                                                        |                       |\n| src                        | string                  | image source in URL format                                                      |                       |\n| srcset                     | string                  | enables to set a different image source based on the viewport, using CSS syntax |                       |\n| target                     | string                  | link target on click                                                            | `_blank`              |\n| title                      | string                  | tooltip & accessibility                                                         |                       |\n| usemap                     | string                  | reference to image map, be careful, it isn't supported everywhere               |                       |\n| width                      | `px`                    | image width                                                                     | inherits parent width |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/image\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-image/package.json",
    "content": "{\n  \"name\": \"mjml-image\",\n  \"description\": \"mjml-image\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-image\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-image/src/index.js",
    "content": "import { min } from 'lodash'\n\nimport { BodyComponent, makeLowerBreakpoint } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default class MjImage extends BodyComponent {\n  static componentName = 'mj-image'\n\n  static allowedAttributes = {\n    alt: 'string',\n    href: 'string',\n    name: 'string',\n    src: 'string',\n    srcset: 'string',\n    sizes: 'string',\n    title: 'string',\n    rel: 'string',\n    align: 'enum(left,center,right)',\n    border: 'string',\n    'border-bottom': 'string',\n    'border-left': 'string',\n    'border-right': 'string',\n    'border-top': 'string',\n    'border-radius': 'unit(px,%){1,4}',\n    'container-background-color': 'color',\n    'fluid-on-mobile': 'boolean',\n    padding: 'unit(px,%){1,4}',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    target: 'string',\n    width: 'unit(px)',\n    height: 'unit(px,auto)',\n    'max-height': 'unit(px,%)',\n    'font-size': 'unit(px)',\n    usemap: 'string',\n  }\n\n  static defaultAttributes = {\n    alt: '',\n    align: 'center',\n    border: '0',\n    height: 'auto',\n    padding: '10px 25px',\n    target: '_blank',\n    'font-size': '13px',\n  }\n\n  getStyles() {\n    const width = this.getContentWidth()\n    const fullWidth = this.getAttribute('full-width') === 'full-width'\n\n    const { parsedWidth, unit } = widthParser(width)\n\n    return {\n      img: {\n        border: this.getAttribute('border'),\n        'border-left': this.getAttribute('border-left'),\n        'border-right': this.getAttribute('border-right'),\n        'border-top': this.getAttribute('border-top'),\n        'border-bottom': this.getAttribute('border-bottom'),\n        'border-radius': this.getAttribute('border-radius'),\n        display: 'block',\n        outline: 'none',\n        'text-decoration': 'none',\n        height: this.getAttribute('height'),\n        'max-height': this.getAttribute('max-height'),\n        'min-width': fullWidth ? '100%' : null,\n        width: '100%',\n        'max-width': fullWidth ? '100%' : null,\n        'font-size': this.getAttribute('font-size'),\n      },\n      td: {\n        width: fullWidth ? null : `${parsedWidth}${unit}`,\n      },\n      table: {\n        'min-width': fullWidth ? '100%' : null,\n        'max-width': fullWidth ? '100%' : null,\n        width: fullWidth ? `${parsedWidth}${unit}` : null,\n        'border-collapse': 'collapse',\n        'border-spacing': '0px',\n      },\n    }\n  }\n\n  getContentWidth() {\n    const width = this.getAttribute('width')\n      ? parseInt(this.getAttribute('width'), 10)\n      : Infinity\n\n    const { box } = this.getBoxWidths()\n\n    return min([box, width])\n  }\n\n  renderImage() {\n    const height = this.getAttribute('height')\n\n    const img = `\n      <img\n        ${this.htmlAttributes({\n          alt: this.getAttribute('alt'),\n          src: this.getAttribute('src'),\n          srcset: this.getAttribute('srcset'),\n          sizes: this.getAttribute('sizes'),\n          style: 'img',\n          title: this.getAttribute('title'),\n          width: this.getContentWidth(),\n          usemap: this.getAttribute('usemap'),\n          ...(height\n            ? { height: height === 'auto' ? height : parseInt(height, 10) }\n            : {}),\n        })}\n      />\n    `\n\n    if (this.getAttribute('href')) {\n      return `\n        <a\n          ${this.htmlAttributes({\n            href: this.getAttribute('href'),\n            target: this.getAttribute('target'),\n            rel: this.getAttribute('rel'),\n            name: this.getAttribute('name'),\n            title: this.getAttribute('title'),\n          })}\n        >\n          ${img}\n        </a>\n      `\n    }\n\n    return img\n  }\n\n  headStyle = (breakpoint) => `\n    @media only screen and (max-width:${makeLowerBreakpoint(breakpoint)}) {\n      table.mj-full-width-mobile { width: 100% !important; }\n      td.mj-full-width-mobile { width: auto !important; }\n    }\n  `\n\n  render() {\n    return `\n      <table\n        ${this.htmlAttributes({\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n          style: 'table',\n          class: this.getAttribute('fluid-on-mobile')\n            ? 'mj-full-width-mobile'\n            : null,\n        })}\n      >\n        <tbody>\n          <tr>\n            <td ${this.htmlAttributes({\n              style: 'td',\n              class: this.getAttribute('fluid-on-mobile')\n                ? 'mj-full-width-mobile'\n                : null,\n            })}>\n              ${this.renderImage()}\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-migrate/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Nicolas Garnier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/mjml-migrate/README.md",
    "content": "# mjml-migrate\n\n## Purpose\n\nMakes a template following the MJML 3 syntax compatible with MJML 4.\n\n## Installation\n\nClone the repo & `npm install` or install via NPM: `npm install mjml-migrate`\n\n## Usage\n\n`migrate <input> <output>`\n\n## What happens\n\n* `mj-container` is removed and its attributes are passed to `mj-body`\n* Unitless values are converted to `px`\n* `mj-social`'s syntax is replaced with the v4 syntax\n* Unsupported tags (defined in `unavailableTags` in `config.js`) are removed \n\n"
  },
  {
    "path": "packages/mjml-migrate/package.json",
    "content": "{\n  \"name\": \"mjml-migrate\",\n  \"version\": \"4.18.0\",\n  \"description\": \"A tool to migrate a template from MJML 3 to MJML 4\",\n  \"main\": \"lib/migrate.js\",\n  \"bin\": {\n    \"migrate\": \"lib/cli.js\"\n  },\n  \"files\": [\n    \"lib\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-migrate\"\n  },\n  \"author\": \"Nicolas Garnier\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"js-beautify\": \"^1.6.14\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\",\n    \"mjml-parser-xml\": \"4.18.0\",\n    \"yargs\": \"^17.7.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-migrate/src/cli.js",
    "content": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport yargs from 'yargs'\nimport migrate from './migrate'\nimport { version } from '../package.json'\n\nconst program = yargs\n  .usage('$0 [options] <input-file> <output-file>')\n  .version(version)\n  .help()\n\nif (program.argv._.length !== 2) {\n  program.showHelp()\n  process.exit(1)\n}\n\nconst [inputFilename, outputFilename] = program.argv._\n\nconst input = fs.readFileSync(inputFilename, 'utf8')\nconst output = migrate(input)\n\nfs.writeFileSync(outputFilename, output)\n\n// eslint-disable-next-line no-console\nconsole.log(\n  `${inputFilename} was converted to the MJML 4 syntax in ${outputFilename}`,\n)\n"
  },
  {
    "path": "packages/mjml-migrate/src/config.js",
    "content": "const unavailableTags = ['mj-html', 'mj-invoice', 'mj-list', 'mj-location']\n\nconst attributesWithUnit = [\n  'background-size',\n  'border-radius',\n  'border-width',\n  'cellpadding',\n  'cellspacing',\n  'font-size',\n  'height',\n  'icon-height',\n  'ico-padding',\n  'ico-padding-bottom',\n  'ico-font-size',\n  'ico-line-height',\n  'ico-padding-left',\n  'ico-padding-right',\n  'ico-padding-top',\n  'icon-size',\n  'icon-width',\n  'inner-padding',\n  'letter-spacing',\n  'padding',\n  'padding-bottom',\n  'padding-left',\n  'padding-right',\n  'padding-left',\n  'tb-border-radius',\n  'tb-width',\n  'width',\n]\n\nmodule.exports = {\n  unavailableTags,\n  attributesWithUnit,\n}\n"
  },
  {
    "path": "packages/mjml-migrate/src/migrate.js",
    "content": "/* eslint-disable no-console */\n\nimport { keys, find, isNil } from 'lodash'\nimport MJMLParser from 'mjml-parser-xml'\nimport { components } from 'mjml-core'\nimport { html as htmlBeautify } from 'js-beautify'\n\nimport { unavailableTags, attributesWithUnit } from './config'\n\nconst beautifyOptions = {\n  indent_size: 2,\n  wrap_attributes_indent_size: 2,\n  max_preserve_newline: 0,\n  preserve_newlines: false,\n}\n\nfunction removeContainerTag(bodyTag) {\n  if (bodyTag.children[0].tagName === 'mj-container') {\n    bodyTag.attributes = bodyTag.children[0].attributes\n    bodyTag.children = bodyTag.children[0].children\n  }\n  return bodyTag\n}\n\nconst listAttributes = (tag) => tag.attributes\n\nfunction addPx(value) {\n  // eslint-disable-next-line no-restricted-globals\n  if (!isNaN(value) && !isNil(value)) {\n    return `${value}px`\n  }\n  return value\n}\n\nfunction fixUnits(attribute, value) {\n  const { length } = attributesWithUnit\n  for (let i = 0; i < length; i += 1) {\n    if (attributesWithUnit[i] === attribute) {\n      return addPx(value)\n    }\n  }\n  return value\n}\n\nfunction cleanAttributes(attributes) {\n  keys(attributes).forEach((key) => {\n    attributes[key] = fixUnits(key, attributes[key])\n  })\n  return attributes\n}\n\nconst DEFAULT_SOCIAL_DISPLAY = 'facebook twitter google'\n\nfunction migrateSocialSyntax(socialTag) {\n  const listAllNetworks = (tag) => {\n    const attributes = (tag.attributes.display || DEFAULT_SOCIAL_DISPLAY).split(\n      ' ',\n    )\n    delete tag.attributes.display\n    return attributes\n  }\n\n  const attributes = listAttributes(socialTag)\n  const networks = listAllNetworks(socialTag)\n\n  socialTag.children = []\n\n  // migrate all attributes to their child attributes\n  keys(networks).forEach((network) => {\n    const nameMigrated = networks[network]\n      .replace(':url', '-noshare')\n      .replace(':share', '')\n    const nameWithoutOpts = nameMigrated.replace('-noshare', '')\n\n    socialTag.children.push({\n      tagName: `mj-social-element`,\n      attributes: { name: nameMigrated },\n      content: attributes[`${nameWithoutOpts}-content`] || '',\n    })\n\n    keys(attributes).forEach((attribute) => {\n      if (attribute.match(nameWithoutOpts) && !attribute.match('content')) {\n        socialTag.children[network].attributes[\n          attribute.replace(`${nameWithoutOpts}-`, '')\n        ] = socialTag.attributes[attribute]\n        delete socialTag.attributes[attribute]\n      }\n    })\n  })\n\n  // delete all content attributes from the root tag after they've been migrated\n  keys(attributes).forEach((attribute) => {\n    if (attribute.match('content')) {\n      delete attributes[attribute]\n    }\n  })\n\n  return socialTag\n}\n\nfunction migrateNavbarSyntax(navbarTag) {\n  navbarTag.tagName = 'mj-section'\n  navbarTag.attributes['full-width'] = 'full-width'\n  return navbarTag\n}\n\nfunction migrateHeroSyntax(heroTag) {\n  const child = find(heroTag.children, { tagName: 'mj-hero-content' })\n\n  return {\n    ...heroTag,\n    children: child.children,\n    attributes: {\n      ...heroTag.attributes,\n      ...child.attributes,\n    },\n  }\n}\n\nfunction isSupportedTag(tag) {\n  return unavailableTags.indexOf(tag) === -1\n}\n\nfunction loopThrough(tree) {\n  keys(tree).forEach((key) => {\n    if (key === 'children') {\n      for (let i = 0; i < tree.children.length; i += 1) {\n        if (isSupportedTag(tree.children[i].tagName)) {\n          switch (tree.children[i].tagName) {\n            case 'mj-body':\n              tree.children[i] = removeContainerTag(tree.children[i])\n              break\n            case 'mj-social':\n              tree.children[i] = migrateSocialSyntax(tree.children[i])\n              break\n            case 'mj-navbar':\n              tree.children[i] = migrateNavbarSyntax(tree.children[i])\n              break\n            case 'mj-inline-links':\n              tree.children[i].tagName = 'mj-navbar'\n              break\n            case 'mj-link':\n              tree.children[i].tagName = 'mj-navbar-link'\n              break\n            case 'mj-hero':\n              tree.children[i] = migrateHeroSyntax(tree.children[i])\n              break\n            // no default\n          }\n\n          tree.children[i].attributes = cleanAttributes(\n            tree.children[i].attributes,\n          )\n          loopThrough(tree.children[i])\n        } else {\n          console.error(\n            `Ignoring unsupported tag : ${tree.children[i].tagName} on line ${tree.children[i].line}`,\n          )\n          delete tree.children[i]\n        }\n      }\n    }\n  })\n  return tree\n}\n\nfunction checkV3Through(node) {\n  if (node.tagName === 'mj-container') return true\n  if (!node.children || !node.children.length) return false\n\n  return node.children.some(checkV3Through)\n}\n\nconst jsonToXML = ({ tagName, attributes, children, content }) => {\n  const subNode =\n    children && children.length > 0\n      ? children.map(jsonToXML).join('\\n')\n      : content || ''\n\n  const stringAttrs = Object.keys(attributes)\n    .map((attr) => `${attr}=\"${attributes[attr]}\"`)\n    .join(' ')\n\n  return `<${tagName}${\n    stringAttrs === '' ? '>' : ` ${stringAttrs}>`\n  }${subNode}</${tagName}>`\n}\n\nexport default function migrate(input, options = {}) {\n  console.warn('mjml-migrate is deprecated and will be removed in mjml 5')\n  const { beautify } = options\n  if (typeof input === 'object') return loopThrough(input)\n\n  const mjmlJson = MJMLParser(input, { components, ignoreIncludes: true })\n  loopThrough(mjmlJson)\n\n  return beautify\n    ? htmlBeautify(jsonToXML(mjmlJson), beautifyOptions)\n    : jsonToXML(mjmlJson)\n}\n\nexport function handleMjml3(mjml, options = {}) {\n  const isV3Synthax = checkV3Through(mjml)\n  if (!isV3Synthax) return mjml\n\n  if (!options.noMigrateWarn)\n    console.log(\n      'MJML v3 syntax detected, migrating to MJML v4 syntax. Use mjml -m to get the migrated MJML.',\n    )\n  return migrate(mjml)\n}\n\n/* eslint-enable no-console */\n"
  },
  {
    "path": "packages/mjml-navbar/README.md",
    "content": "### mj-navbar\n\nDisplays a navigation menu with an optional `hamburger` mode for mobile devices.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section background-color=\"#ef6451\">\n      <mj-column>\n        <mj-navbar base-url=\"https://mjml.io\" hamburger=\"hamburger\" ico-color=\"#ffffff\">\n            <mj-navbar-link href=\"/gettings-started-onboard\" color=\"#ffffff\">Getting started</mj-navbar-link>\n            <mj-navbar-link href=\"/try-it-live\" color=\"#ffffff\">Try it live</mj-navbar-link>\n            <mj-navbar-link href=\"/templates\" color=\"#ffffff\">Templates</mj-navbar-link>\n            <mj-navbar-link href=\"/components\" color=\"#ffffff\">Components</mj-navbar-link>\n        </mj-navbar>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<figure>\n  <figcaption>Standard Desktop</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/navbar-example.png\"\n      alt=\"example desktop width navbar\" width=\"800px\" />\n</figure>\n\n<figure>\n  <figcaption>Standard Mobile</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/navbar-mobile.png\"\n      alt=\"example mobile width navbar\" width=\"318px\" />\n</figure>\n\n<figure>\n  <figcaption>Mode hamburger enabled:</figcaption>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/navbar-hamburger.gif\"\n      alt=\"hamburger mode animation shows menu expansion after clicking hamburger icon\"\n      width=\"309px\" />\n</figure>\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>The hamburger feature works only on Apple Mail clients, when the width is below the specified (or default) breakpoint. For other email clients, the links are displayed inline and the hamburger icon is not visible.</p>\n</div>\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>All attributes prefixed with <code>ico-</code> help to customize the hamburger icon, hence they only work with the hamburger mode enabled.</p>\n</div>\n\n#### Attributes\n\n| attribute           | accepts                       | description                                                                                                             | default value                          |\n| ------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |\n| align               | `left`<br>`center`<br>`right` | align content                                                                                                           | `center`                               |\n| base-url            | string                        | base URL for child components                                                                                           | `null`                                 |\n| css-class           | string                        | class name, added to the root HTML element created                                                                      |                                        |\n| hamburger           | string                        | activate the hamburger navigation on mobile if the value is hamburger                                                   | `null`                                 |\n| ico-align           | `left`<br>`center`<br>`right` | hamburger icon alignment<br> (`hamburger=\"hamburger\"` required)                                                         | `center`                               |\n| ico-close           | string                        | char code for a custom close icon, e.g. ASCII code decimal<br> (`hamburger=\"hamburger\"` required)                       | `&#8855;`                              |\n| ico-color           | CSS color formats             | hamburger icon color<br> (`hamburger=\"hamburger\"` required)                                                             | `#000000`                              |\n| ico-font-family     | string                        | hamburger icon font<br> (`hamburger=\"hamburger\"` required)                                                              | `Ubuntu, Helvetica, Arial, sans-serif` |\n| ico-font-size       | `px` `%`                      | hamburger icon size<br> (`hamburger=\"hamburger\"` required)                                                              | `30px`                                 |\n| ico-line-height     | `px` `%`                      | hamburger icon line height<br> (`hamburger=\"hamburger\"` required)                                                       | `30px`                                 |\n| ico-open            | string                        | char code for a custom open icon, e.g. ASCII code decimal<br> (`hamburger=\"hamburger\"` required)                        | `&#9776;`                              |\n| ico-padding         | `px` `%`                      | hamburger icon padding, supports up to 4 parameters<br> (`hamburger=\"hamburger\"` required)                              | `10px`                                 |\n| ico-padding-bottom  | `px` `%`                      | hamburger icon bottom padding<br> (`hamburger=\"hamburger\"` required)                                                    |                                        |\n| ico-padding-left    | `px` `%`                      | hamburger icon left padding<br> (`hamburger=\"hamburger\"` required)                                                      |                                        |\n| ico-padding-right   | `px` `%`                      | hamburger icon right padding<br> (`hamburger=\"hamburger\"` required)                                                     |                                        |\n| ico-padding-top     | `px` `%`                      | hamburger icon top padding<br> (`hamburger=\"hamburger\"` required)                                                       |                                        |\n| ico-text-decoration | string                        | hamburger icon text decoration e.g. `none` `underline` `overline` `line-through`<br> (`hamburger=\"hamburger\"` required) | `none`                                 |\n| ico-text-transform  | string                        | hamburger icon text transformation `none` `capitalize` `uppercase` `lowercase`<br> (`hamburger=\"hamburger\"` required)   | `uppercase`                            |\n| padding             | `px` `%`                      | navbar padding, supports up to 4 parameters                                                                             |                                        |\n| padding-bottom      | `px` `%`                      | navbar bottom padding                                                                                                   |                                        |\n| padding-left        | `px` `%`                      | navbar left padding                                                                                                     |                                        |\n| padding-right       | `px` `%`                      | navbar right padding                                                                                                    |                                        |\n| padding-top         | `px` `%`                      | navbar top padding                                                                                                      |                                        |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/navbar\">Try it live</a></p>\n\n#### mj-navbar-link\n\nUsed to display an individual link in the navbar. Individual links of the menu should be wrapped inside `mj-navbar`.\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>The <code>mj-navbar-link</code> component must be used inside an <code>mj-navbar</code> component only.</p>\n</div>\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-navbar-link</code> is an \"ending tag\", which means that it can contain HTML code but it cannot contain other MJML components.</p> \n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n#### Attributes\n\n| attribute       | accepts           | description                                                  | default value                          |\n| --------------- | ----------------- | ------------------------------------------------------------ | -------------------------------------- |\n| color           | CSS color formats | text color                                                   | `#000000`                              |\n| css-class       | string            | class name, added to the root HTML element created           |                                        |\n| font-family     | string            | font                                                         | `Ubuntu, Helvetica, Arial, sans-serif` |\n| font-size       | `px`              | text size                                                    | `13px`                                 |\n| font-style      | string            | CSS values, i.e. `normal` `italic` `oblique`                 |                                        |\n| font-weight     | string            | text thickness                                               |                                        |\n| href            | string            | link to redirect to on click, in URL format                  |                                        |\n| letter-spacing  | `px` `em`         | letter-spacing                                               |                                        |\n| line-height     | `px` `%`          | space between the lines                                      | `22px`                                 |\n| name            | string            | specify the link name attribute                              |                                        |\n| padding         | `px` `%`          | navbar link padding, supports up to 4 parameters             | `15px 10px`                            |\n| padding-bottom  | `px` `%`          | bottom padding                                               |                                        |\n| padding-left    | `px` `%`          | left padding                                                 |                                        |\n| padding-right   | `px` `%`          | right padding                                                |                                        |\n| padding-top     | `px` `%`          | top padding                                                  |                                        |\n| rel             | string            | specify the rel attribute                                    |                                        |\n| target          | string            | link target on click                                         |                                        |\n| text-decoration | string            | CSS values, i.e. `underline` `overline` `none`               | `none`                                 |\n| text-transform  | string            | CSS values, i.e. `capitalize` `uppercase` `lowercase` `none` | `uppercase`                            |\n"
  },
  {
    "path": "packages/mjml-navbar/package.json",
    "content": "{\n  \"name\": \"mjml-navbar\",\n  \"description\": \"mjml-navbar\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-navbar\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-navbar/src/Navbar.js",
    "content": "import { BodyComponent, makeLowerBreakpoint } from 'mjml-core'\n\nimport conditionalTag, {\n  msoConditionalTag,\n} from 'mjml-core/lib/helpers/conditionalTag'\nimport genRandomHexString from 'mjml-core/lib/helpers/genRandomHexString'\n\nexport default class MjNavbar extends BodyComponent {\n  static componentName = 'mj-navbar'\n\n  static allowedAttributes = {\n    align: 'enum(left,center,right)',\n    'base-url': 'string',\n    hamburger: 'string',\n    'ico-align': 'enum(left,center,right)',\n    'ico-open': 'string',\n    'ico-close': 'string',\n    'ico-color': 'color',\n    'ico-font-size': 'unit(px,%)',\n    'ico-font-family': 'string',\n    'ico-text-transform': 'string',\n    'ico-padding': 'unit(px,%){1,4}',\n    'ico-padding-left': 'unit(px,%)',\n    'ico-padding-top': 'unit(px,%)',\n    'ico-padding-right': 'unit(px,%)',\n    'ico-padding-bottom': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    'padding-left': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-bottom': 'unit(px,%)',\n    'ico-text-decoration': 'string',\n    'ico-line-height': 'unit(px,%,)',\n  }\n\n  static defaultAttributes = {\n    align: 'center',\n    'base-url': null,\n    hamburger: null,\n    'ico-align': 'center',\n    'ico-open': '&#9776;',\n    'ico-close': '&#8855;',\n    'ico-color': '#000000',\n    'ico-font-size': '30px',\n    'ico-font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'ico-text-transform': 'uppercase',\n    'ico-padding': '10px',\n    'ico-text-decoration': 'none',\n    'ico-line-height': '30px',\n  }\n\n  headStyle = (breakpoint) =>\n    `\n      noinput.mj-menu-checkbox { display:block!important; max-height:none!important; visibility:visible!important; }\n\n      @media only screen and (max-width:${makeLowerBreakpoint(breakpoint)}) {\n        .mj-menu-checkbox[type=\"checkbox\"] ~ .mj-inline-links { display:none!important; }\n        .mj-menu-checkbox[type=\"checkbox\"]:checked ~ .mj-inline-links,\n        .mj-menu-checkbox[type=\"checkbox\"] ~ .mj-menu-trigger { display:block!important; max-width:none!important; max-height:none!important; font-size:inherit!important; }\n        .mj-menu-checkbox[type=\"checkbox\"] ~ .mj-inline-links > a { display:block!important; }\n        .mj-menu-checkbox[type=\"checkbox\"]:checked ~ .mj-menu-trigger .mj-menu-icon-close { display:block!important; }\n        .mj-menu-checkbox[type=\"checkbox\"]:checked ~ .mj-menu-trigger .mj-menu-icon-open { display:none!important; }\n      }\n    `\n\n  getStyles() {\n    return {\n      div: {\n        align: this.getAttribute('align'),\n        width: '100%',\n      },\n      label: {\n        display: 'block',\n        cursor: 'pointer',\n        'mso-hide': 'all',\n        '-moz-user-select': 'none',\n        'user-select': 'none',\n        color: this.getAttribute('ico-color'),\n        'font-size': this.getAttribute('ico-font-size'),\n        'font-family': this.getAttribute('ico-font-family'),\n        'text-transform': this.getAttribute('ico-text-transform'),\n        'text-decoration': this.getAttribute('ico-text-decoration'),\n        'line-height': this.getAttribute('ico-line-height'),\n        padding: this.getAttribute('ico-padding'),\n        'padding-top': this.getAttribute('ico-padding-top'),\n        'padding-right': this.getAttribute('ico-padding-right'),\n        'padding-bottom': this.getAttribute('ico-padding-bottom'),\n        'padding-left': this.getAttribute('ico-padding-left'),\n      },\n      trigger: {\n        display: 'none',\n        'max-height': '0px',\n        'max-width': '0px',\n        'font-size': '0px',\n        overflow: 'hidden',\n      },\n      icoOpen: {\n        'mso-hide': 'all',\n      },\n      icoClose: {\n        display: 'none',\n        'mso-hide': 'all',\n      },\n    }\n  }\n\n  renderHamburger() {\n    const labelKey = genRandomHexString(16)\n\n    return `\n      ${msoConditionalTag(\n        `\n        <input type=\"checkbox\" id=\"${labelKey}\" class=\"mj-menu-checkbox\" style=\"display:none !important; max-height:0; visibility:hidden;\" />\n      `,\n        true,\n      )}\n      <div\n        ${this.htmlAttributes({\n          class: 'mj-menu-trigger',\n          style: 'trigger',\n        })}\n      >\n        <label\n          ${this.htmlAttributes({\n            for: labelKey,\n            class: 'mj-menu-label',\n            style: 'label',\n            align: this.getAttribute('ico-align'),\n          })}\n        >\n          <span\n            ${this.htmlAttributes({\n              class: 'mj-menu-icon-open',\n              style: 'icoOpen',\n            })}\n          >\n            ${this.getAttribute('ico-open')}\n          </span>\n          <span\n            ${this.htmlAttributes({\n              class: 'mj-menu-icon-close',\n              style: 'icoClose',\n            })}\n          >\n            ${this.getAttribute('ico-close')}\n          </span>\n        </label>\n      </div>\n    `\n  }\n\n  render() {\n    return `\n        ${\n          this.getAttribute('hamburger') === 'hamburger'\n            ? this.renderHamburger()\n            : ''\n        }\n        <div\n          ${this.htmlAttributes({\n            class: 'mj-inline-links',\n            style: this.htmlAttributes('div'),\n          })}\n        >\n        ${conditionalTag(`\n          <table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"${this.getAttribute(\n            'align',\n          )}\">\n            <tr>\n        `)}\n          ${this.renderChildren(this.props.children, {\n            attributes: {\n              navbarBaseUrl: this.getAttribute('base-url'),\n            },\n          })}\n          ${conditionalTag(`\n            </tr></table>\n          `)}\n        </div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-navbar/src/NavbarLink.js",
    "content": "import { BodyComponent, suffixCssClasses } from 'mjml-core'\n\nimport conditionalTag from 'mjml-core/lib/helpers/conditionalTag'\n\nexport default class MjNavbarLink extends BodyComponent {\n  static componentName = 'mj-navbar-link'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    color: 'color',\n    'font-family': 'string',\n    'font-size': 'unit(px)',\n    'font-style': 'string',\n    'font-weight': 'string',\n    href: 'string',\n    name: 'string',\n    target: 'string',\n    rel: 'string',\n    'letter-spacing': 'unitWithNegative(px,em)',\n    'line-height': 'unit(px,%,)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    'text-decoration': 'string',\n    'text-transform': 'string',\n  }\n\n  static defaultAttributes = {\n    color: '#000000',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'font-size': '13px',\n    'font-weight': 'normal',\n    'line-height': '22px',\n    padding: '15px 10px',\n    target: '_blank',\n    'text-decoration': 'none',\n    'text-transform': 'uppercase',\n  }\n\n  getStyles() {\n    return {\n      a: {\n        display: 'inline-block',\n        color: this.getAttribute('color'),\n        'font-family': this.getAttribute('font-family'),\n        'font-size': this.getAttribute('font-size'),\n        'font-style': this.getAttribute('font-style'),\n        'font-weight': this.getAttribute('font-weight'),\n        'letter-spacing': this.getAttribute('letter-spacing'),\n        'line-height': this.getAttribute('line-height'),\n        'text-decoration': this.getAttribute('text-decoration'),\n        'text-transform': this.getAttribute('text-transform'),\n        padding: this.getAttribute('padding'),\n        'padding-top': this.getAttribute('padding-top'),\n        'padding-left': this.getAttribute('padding-left'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n      },\n      td: {\n        padding: this.getAttribute('padding'),\n        'padding-top': this.getAttribute('padding-top'),\n        'padding-left': this.getAttribute('padding-left'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n      },\n    }\n  }\n\n  renderContent() {\n    const href = this.getAttribute('href')\n    const navbarBaseUrl = this.getAttribute('navbarBaseUrl')\n    const link = navbarBaseUrl ? `${navbarBaseUrl}${href}` : href\n\n    const cssClass = this.getAttribute('css-class')\n      ? ` ${this.getAttribute('css-class')}`\n      : ''\n\n    return `\n      <a\n        ${this.htmlAttributes({\n          class: `mj-link${cssClass}`,\n          href: link,\n          rel: this.getAttribute('rel'),\n          target: this.getAttribute('target'),\n          name: this.getAttribute('name'),\n          style: 'a',\n        })}\n      >\n        ${this.getContent()}\n      </a>\n    `\n  }\n\n  render() {\n    return `\n        ${conditionalTag(`\n          <td\n            ${this.htmlAttributes({\n              style: 'td',\n              class: suffixCssClasses(\n                this.getAttribute('css-class'),\n                'outlook',\n              ),\n            })}\n          >\n        `)}\n        ${this.renderContent()}\n        ${conditionalTag(`\n          </td>\n        `)}\n      `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-navbar/src/index.js",
    "content": "export { default as Navbar } from './Navbar'\nexport { default as NavbarLink } from './NavbarLink'\n"
  },
  {
    "path": "packages/mjml-parser-xml/package.json",
    "content": "{\n  \"name\": \"mjml-parser-xml\",\n  \"description\": \"mjml-parser-xml\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-parser-xml\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\",\n    \"test\": \"node ./test/test.js\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"detect-node\": \"2.1.0\",\n    \"htmlparser2\": \"^9.1.0\",\n    \"lodash\": \"^4.17.21\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"chai\": \"^4.1.1\",\n    \"mjml\": \"4.18.0\",\n    \"mjml-core\": \"4.18.0\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-parser-xml/src/helpers/cleanNode.js",
    "content": "import _ from 'lodash'\n\nexport default function cleanNode(node) {\n  delete node.parent\n\n  // Delete children if needed\n  if (node.children && node.children.length) {\n    _.forEach(node.children, cleanNode)\n  } else {\n    delete node.children\n  }\n\n  // Delete attributes if needed\n  if (node.attributes && Object.keys(node.attributes).length === 0) {\n    delete node.attributes\n  }\n}\n"
  },
  {
    "path": "packages/mjml-parser-xml/src/helpers/convertBooleansOnAttrs.js",
    "content": "import { mapValues } from 'lodash'\n\n/**\n * Convert \"true\" and \"false\" string attributes values\n * to corresponding Booleans\n */\n\nexport default function convertBooleansOnAttrs(attrs) {\n  return mapValues(attrs, (val) => {\n    if (val === 'true') {\n      return true\n    }\n    if (val === 'false') {\n      return false\n    }\n\n    return val\n  })\n}\n"
  },
  {
    "path": "packages/mjml-parser-xml/src/helpers/setEmptyAttributes.js",
    "content": "import { forEach } from 'lodash'\n\nexport default function setEmptyAttributes(node) {\n  if (!node.attributes) {\n    node.attributes = {}\n  }\n  if (node.children) {\n    forEach(node.children, setEmptyAttributes)\n  }\n}\n"
  },
  {
    "path": "packages/mjml-parser-xml/src/index.js",
    "content": "import { Parser } from 'htmlparser2'\n\nimport { isObject, findLastIndex, find } from 'lodash'\nimport { filter, map, flow } from 'lodash/fp'\nimport path from 'path'\nimport fs from 'fs'\n\nimport cleanNode from './helpers/cleanNode'\nimport convertBooleansOnAttrs from './helpers/convertBooleansOnAttrs'\nimport setEmptyAttributes from './helpers/setEmptyAttributes'\n\nconst isNode = require('detect-node')\n\nconst indexesForNewLine = (xml) => {\n  const regex = /\\n/gi\n  const indexes = [0]\n\n  while (regex.exec(xml)) {\n    indexes.push(regex.lastIndex)\n  }\n\n  return indexes\n}\n\nconst isSelfClosing = (indexes, parser) =>\n  indexes.startIndex === parser.startIndex &&\n  indexes.endIndex === parser.endIndex\n\nexport default function MJMLParser(xml, options = {}, includedIn = []) {\n  const {\n    addEmptyAttributes = true,\n    components = {},\n    convertBooleans = true,\n    keepComments = true,\n    filePath = '.',\n    actualPath = '.',\n    ignoreIncludes = false,\n    preprocessors = [],\n  } = options\n\n  const endingTags = flow(\n    filter((component) => component.endingTag),\n    map((component) => component.getTagName()),\n  )({ ...components })\n\n  let cwd = process.cwd()\n\n  if (isNode && filePath) {\n    try {\n      const isDir = fs.lstatSync(filePath).isDirectory()\n      cwd = isDir ? filePath : path.dirname(filePath)\n    } catch (e) {\n      throw new Error('Specified filePath does not exist')\n    }\n  }\n\n  let mjml = null\n  let cur = null\n  let inInclude = !!includedIn.length\n  let inEndingTag = 0\n  const cssIncludes = []\n  const currentEndingTagIndexes = { startIndex: 0, endIndex: 0 }\n\n  const findTag = (tagName, tree) => find(tree.children, { tagName })\n  const lineIndexes = indexesForNewLine(xml)\n\n  const handleCssHtmlInclude = (file, attrs, line) => {\n    const partialPath = path.resolve(cwd, file)\n    let content\n    try {\n      content = fs.readFileSync(partialPath, 'utf8')\n    } catch (e) {\n      const newNode = {\n        line,\n        file,\n        absoluteFilePath: path.resolve(cwd, actualPath),\n        parent: cur,\n        tagName: 'mj-raw',\n        content: `<!-- mj-include fails to read file : ${file} at ${partialPath} -->`,\n        children: [],\n        errors: [\n          {\n            type: 'include',\n            params: { file, partialPath },\n          },\n        ],\n      }\n      cur.children.push(newNode)\n\n      return\n    }\n\n    if (attrs.type === 'html') {\n      const newNode = {\n        line,\n        file,\n        absoluteFilePath: path.resolve(cwd, actualPath),\n        parent: cur,\n        tagName: 'mj-raw',\n        content,\n      }\n      cur.children.push(newNode)\n\n      return\n    }\n\n    const attributes =\n      attrs['css-inline'] === 'inline' ? { inline: 'inline' } : {}\n\n    const newNode = {\n      line,\n      file,\n      absoluteFilePath: path.resolve(cwd, actualPath),\n      tagName: 'mj-style',\n      content,\n      children: [],\n      attributes,\n    }\n    cssIncludes.push(newNode)\n  }\n\n  const handleInclude = (file, line) => {\n    const partialPath = path.resolve(cwd, file)\n    const curBeforeInclude = cur\n\n    if (find(cur.includedIn, { file: partialPath }))\n      throw new Error(`Circular inclusion detected on file : ${partialPath}`)\n\n    let content\n\n    try {\n      content = fs.readFileSync(partialPath, 'utf8')\n    } catch (e) {\n      const newNode = {\n        line,\n        file,\n        absoluteFilePath: path.resolve(cwd, actualPath),\n        parent: cur,\n        tagName: 'mj-raw',\n        content: `<!-- mj-include fails to read file : ${file} at ${partialPath} -->`,\n        children: [],\n        errors: [\n          {\n            type: 'include',\n            params: { file, partialPath },\n          },\n        ],\n      }\n      cur.children.push(newNode)\n\n      return\n    }\n\n    content =\n      content.indexOf('<mjml>') === -1\n        ? `<mjml><mj-body>${content}</mj-body></mjml>`\n        : content\n\n    const partialMjml = MJMLParser(\n      content,\n      {\n        ...options,\n        filePath: partialPath,\n        actualPath: partialPath,\n      },\n      [\n        ...cur.includedIn,\n        {\n          file: cur.absoluteFilePath,\n          line,\n        },\n      ],\n    )\n\n    const bindToTree = (children, tree = cur) =>\n      children.map((c) => ({ ...c, parent: tree }))\n\n    if (partialMjml.tagName !== 'mjml') {\n      return\n    }\n\n    const body = findTag('mj-body', partialMjml)\n    const head = findTag('mj-head', partialMjml)\n\n    if (body) {\n      const boundChildren = bindToTree(body.children)\n      cur.children = [...cur.children, ...boundChildren]\n    }\n\n    if (head) {\n      let curHead = findTag('mj-head', mjml)\n\n      if (!curHead) {\n        mjml.children.push({\n          file: actualPath,\n          absoluteFilePath: path.resolve(cwd, actualPath),\n          parent: mjml,\n          tagName: 'mj-head',\n          children: [],\n          includedIn: [],\n        })\n\n        curHead = findTag('mj-head', mjml)\n      }\n\n      const boundChildren = bindToTree(head.children, curHead)\n      curHead.children = [...curHead.children, ...boundChildren]\n    }\n\n    // must restore cur to the cur before include started\n    cur = curBeforeInclude\n  }\n\n  const parser = new Parser(\n    {\n      onopentag: (name, attrs) => {\n        const isAnEndingTag = endingTags.indexOf(name) !== -1\n\n        if (inEndingTag > 0) {\n          if (isAnEndingTag) inEndingTag += 1\n          return\n        }\n\n        if (isAnEndingTag) {\n          inEndingTag += 1\n\n          if (inEndingTag === 1) {\n            // we're entering endingTag\n            currentEndingTagIndexes.startIndex = parser.startIndex\n            currentEndingTagIndexes.endIndex = parser.endIndex\n          }\n        }\n\n        const line =\n          findLastIndex(lineIndexes, (i) => i <= parser.startIndex) + 1\n\n        if (name === 'mj-include') {\n          if (ignoreIncludes || !isNode) return\n\n          if (attrs.type === 'css' || attrs.type === 'html') {\n            handleCssHtmlInclude(decodeURIComponent(attrs.path), attrs, line)\n            return\n          }\n\n          inInclude = true\n          handleInclude(decodeURIComponent(attrs.path), line)\n          return\n        }\n\n        if (convertBooleans) {\n          // \"true\" and \"false\" will be converted to bools\n          attrs = convertBooleansOnAttrs(attrs)\n        }\n\n        const newNode = {\n          file: actualPath,\n          absoluteFilePath: isNode ? path.resolve(cwd, actualPath) : actualPath,\n          line,\n          includedIn,\n          parent: cur,\n          tagName: name,\n          attributes: attrs,\n          children: [],\n        }\n\n        if (cur) {\n          cur.children.push(newNode)\n        } else {\n          mjml = newNode\n        }\n\n        cur = newNode\n      },\n      onclosetag: (name) => {\n        if (endingTags.indexOf(name) !== -1) {\n          inEndingTag -= 1\n\n          if (!inEndingTag) {\n            // we're getting out of endingTag\n            // if self-closing tag we don't get the content\n            if (!isSelfClosing(currentEndingTagIndexes, parser)) {\n              const partialVal = xml\n                .substring(\n                  currentEndingTagIndexes.endIndex + 1,\n                  parser.endIndex,\n                )\n                .trim()\n              const val = partialVal.substring(\n                0,\n                partialVal.lastIndexOf(`</${name}`),\n              )\n\n              if (val) cur.content = val.trim()\n            }\n          }\n        }\n\n        if (inEndingTag > 0) return\n\n        if (inInclude) {\n          inInclude = false\n        }\n\n        // for includes, setting cur is handled in handleInclude because when there is\n        // only mj-head in include it doesn't create any elements, so setting back to parent is wrong\n        if (name !== 'mj-include') cur = (cur && cur.parent) || null\n      },\n      ontext: (text) => {\n        if (inEndingTag > 0) return\n\n        if (text && text.trim() && cur) {\n          cur.content = `${(cur && cur.content) || ''}${text.trim()}`.trim()\n        }\n      },\n      oncomment: (data) => {\n        if (inEndingTag > 0) return\n\n        if (cur && keepComments) {\n          cur.children.push({\n            line: findLastIndex(lineIndexes, (i) => i <= parser.startIndex) + 1,\n            tagName: 'mj-raw',\n            content: `<!--${data}-->`,\n            includedIn,\n          })\n        }\n      },\n    },\n    {\n      recognizeCDATA: true,\n      decodeEntities: false,\n      recognizeSelfClosing: true,\n      lowerCaseAttributeNames: false,\n    },\n  )\n\n  // Apply preprocessors to raw xml\n  xml = flow(preprocessors)(xml)\n\n  parser.write(xml)\n  parser.end()\n\n  if (!isObject(mjml)) {\n    throw new Error('Parsing failed. Check your mjml.')\n  }\n\n  cleanNode(mjml)\n\n  // Assign \"attributes\" property if not set\n  if (addEmptyAttributes) {\n    setEmptyAttributes(mjml)\n  }\n\n  if (cssIncludes.length) {\n    const head = find(mjml.children, { tagName: 'mj-head' })\n\n    if (head) {\n      if (head.children) {\n        head.children = [...head.children, ...cssIncludes]\n      } else {\n        head.children = cssIncludes\n      }\n    } else {\n      mjml.children.push({\n        file: filePath,\n        line: 0,\n        tagName: 'mj-head',\n        children: cssIncludes,\n      })\n    }\n  }\n\n  return mjml\n}\n"
  },
  {
    "path": "packages/mjml-parser-xml/test/incl.mjml",
    "content": "<mj-column>\n  <mj-text font-size=\"22px\">\n    COIN\n    <a src=\"test\">aze</a>\n  </mj-text>\n  <mj-text font-size=\"22px\">\n    COIN2\n    <a src=\"test\">aze2</a>\n  </mj-text>\n</mj-column>\n"
  },
  {
    "path": "packages/mjml-parser-xml/test/test-preprocessors.js",
    "content": "const { template } = require('lodash')\nconst MJMLParser = require('../lib')\nconst mjml2html = require('../../mjml/lib')\nconst { components } = require('../../mjml-core/lib')\n\nconst parse = mjml =>\n  MJMLParser(mjml, {\n    keepComments: true,\n    components,\n    preprocessors: [\n      data =>\n        template(data, {\n          evaluate: /{{([\\s\\S]+?)}}/g,\n          interpolate: /{{=([\\s\\S]+?)}}/g,\n          escape: /{{-([\\s\\S]+?)}}/g,\n        })({\n          buttons: [{ title: 'Title' }, { title: 'Title2' }],\n        }),\n    ],\n  })\n\nconst xml = `<mjml>\n<mj-body>\n  <mj-section mj-class=\"content\">\n    {{ buttons.forEach(function(button) { }}\n    <mj-text>{{=button.title}}</mj-text>\n    {{ }); }}\n  </mj-section>\n</mj-body>\n</mjml>\n`\n\nconst json = parse(xml)\nconst { html } = mjml2html(json)\n\nconsole.log(html) // eslint-disable-line no-console\n"
  },
  {
    "path": "packages/mjml-parser-xml/test/test-utils.js",
    "content": "const _ = require('lodash')\n\nfunction omitDeepLodash(input, props) {\n  function omitDeepOnOwnProps(obj) {\n    if (!_.isArray(obj) && !_.isObject(obj)) {\n      return obj\n    }\n\n    if (_.isArray(obj)) {\n      return omitDeepLodash(obj, props)\n    }\n\n    const o = {}\n    _.forOwn(obj, (value, key) => {\n      o[key] = omitDeepLodash(value, props)\n    })\n\n    return _.omit(o, props)\n  }\n\n  if (typeof input === \"undefined\") {\n    return undefined\n  }\n\n  if (_.isArray(input)) {\n    return input.map(omitDeepOnOwnProps)\n  }\n\n  return omitDeepOnOwnProps(input)\n}\n\nfunction deepDiff(object, base) {\n\tfunction changes(object, base) {\n\t\treturn _.transform(object, (result, value, key) => {\n\t\t\tif (!_.isEqual(value, base[key])) {\n\t\t\t\tresult[key] = (_.isObject(value) && _.isObject(base[key])) ? changes(value, base[key]) : value\n\t\t\t}\n\t\t})\n\t}\n\treturn changes(object, base)\n}\n\nfunction displayDiff(obj1, obj2) {\n  const diffs = deepDiff(obj1, obj2)\n  if (_.isEqual(diffs, {})) {\n    console.log('\\x1b[32m', 'Parsing test successful') // eslint-disable-line no-console\n    console.log('\\x1b[0m', '') // eslint-disable-line no-console\n  } else {\n    console.log('\\x1b[31m', 'Parsing test failed. Differences found :') // eslint-disable-line no-console\n    console.log('\\x1b[0m', JSON.stringify(diffs, null, 2)) // eslint-disable-line no-console\n  }\n}\n\nmodule.exports = {\n  omitDeepLodash,\n  displayDiff,\n}\n"
  },
  {
    "path": "packages/mjml-parser-xml/test/test-values.js",
    "content": "/* eslint-disable comma-dangle */\nmodule.exports = [\n  {\n    test: 'Special characters',\n    mjml: `\n<mjml>\n  <mj-body>\n    <mj-section background-color=\"#CCCCCC\" full-width=\"full-width\">\n      <mj-button href=\"<% dynamic %>\" pouf=\"$2\">\n        &end<br />\n        Blu & end $1\n        &amp;\n        lorem\n      </mj-button>\n      <mj-button href=\"https://mjml.io?encodedUrl=https%3A%2F%2Fmjml.io&coin=coi\">\n        Blu\n      </mj-button>\n      <mj-button href=\"&é(§&è!çà)\">https%3A%2F%2Fmjml.io</mj-button>\n      <mj-raw>\n        <coin color=\"#CCCCCC\">bla</coin>\n      </mj-raw>\n    </mj-section>\n  </mj-body>\n</mjml>\n    `,\n    validJson: {\n      \"file\": \".\",\n      \"line\": 2,\n      \"includedIn\": [],\n      \"tagName\": \"mjml\",\n      \"children\": [\n        {\n          \"file\": \".\",\n          \"line\": 3,\n          \"includedIn\": [],\n          \"tagName\": \"mj-body\",\n          \"children\": [\n            {\n              \"file\": \".\",\n              \"line\": 4,\n              \"includedIn\": [],\n              \"tagName\": \"mj-section\",\n              \"attributes\": {\n                \"background-color\": \"#CCCCCC\",\n                \"full-width\": \"full-width\"\n              },\n              \"children\": [\n                {\n                  \"file\": \".\",\n                  \"line\": 5,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-button\",\n                  \"attributes\": {\n                    \"href\": \"<% dynamic %>\",\n                    \"pouf\": \"$2\"\n                  },\n                  \"content\": \"&end<br />\\n        Blu & end $1\\n        &amp;\\n        lorem\"\n                },\n                {\n                  \"file\": \".\",\n                  \"line\": 11,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-button\",\n                  \"attributes\": {\n                    \"href\": \"https://mjml.io?encodedUrl=https%3A%2F%2Fmjml.io&coin=coi\"\n                  },\n                  \"content\": \"Blu\"\n                },\n                {\n                  \"file\": \".\",\n                  \"line\": 14,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-button\",\n                  \"attributes\": {\n                    \"href\": \"&é(§&è!çà)\"\n                  },\n                  \"content\": \"https%3A%2F%2Fmjml.io\"\n                },\n                {\n                  \"file\": \".\",\n                  \"line\": 15,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-raw\",\n                  \"content\": \"<coin color=\\\"#CCCCCC\\\">bla</coin>\",\n                  \"attributes\": {}\n                }\n              ]\n            }\n          ],\n          \"attributes\": {}\n        }\n      ],\n      \"attributes\": {}\n    }\n  },\n  {\n    test: 'Similar tags',\n    mjml: `\n<mjml>\n  <mj-body>\n    <mj-text-test-wrapper>\n      <mj-text>MJML</mj-text>\n      <mj-text attr=\"val\">FTW</mj-text>\n    </mj-text-test-wrapper>\n    <mj-text-test-wrapper>\n      <mj-text attr=\"val\">FTW</mj-text>\n      <mj-text>MJML</mj-text>\n    </mj-text-test-wrapper>\n  </mj-body>\n</mjml>\n    `,\n    validJson: {\n      \"file\": \".\",\n      \"line\": 2,\n      \"includedIn\": [],\n      \"tagName\": \"mjml\",\n      \"children\": [\n        {\n          \"file\": \".\",\n          \"line\": 3,\n          \"includedIn\": [],\n          \"tagName\": \"mj-body\",\n          \"children\": [\n            {\n              \"file\": \".\",\n              \"line\": 4,\n              \"includedIn\": [],\n              \"tagName\": \"mj-text-test-wrapper\",\n              \"children\": [\n                {\n                  \"file\": \".\",\n                  \"line\": 5,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-text\",\n                  \"content\": \"MJML\",\n                  \"attributes\": {}\n                },\n                {\n                  \"file\": \".\",\n                  \"line\": 6,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-text\",\n                  \"attributes\": {\n                    \"attr\": \"val\"\n                  },\n                  \"content\": \"FTW\"\n                }\n              ],\n              \"attributes\": {}\n            },\n            {\n              \"file\": \".\",\n              \"line\": 8,\n              \"includedIn\": [],\n              \"tagName\": \"mj-text-test-wrapper\",\n              \"children\": [\n                {\n                  \"file\": \".\",\n                  \"line\": 9,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-text\",\n                  \"attributes\": {\n                    \"attr\": \"val\"\n                  },\n                  \"content\": \"FTW\"\n                },\n                {\n                  \"file\": \".\",\n                  \"line\": 10,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-text\",\n                  \"content\": \"MJML\",\n                  \"attributes\": {}\n                }\n              ],\n              \"attributes\": {}\n            }\n          ],\n          \"attributes\": {}\n        }\n      ],\n      \"attributes\": {}\n    }\n  },\n  {\n    test: 'Self closing tags',\n    mjml: `\n<mjml>\n  <mj-head>\n    <mj-attributes>\n      <mj-text color=\"blue\" />\n      <mj-text font-size=\"40px\" />\n    </mj-attributes>\n  </mj-head>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-text>\n          Hello !\n        </mj-text>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n    `,\n    validJson: {\n      \"file\": \".\",\n      \"line\": 2,\n      \"includedIn\": [],\n      \"tagName\": \"mjml\",\n      \"children\": [\n        {\n          \"file\": \".\",\n          \"line\": 3,\n          \"includedIn\": [],\n          \"tagName\": \"mj-head\",\n          \"children\": [\n            {\n              \"file\": \".\",\n              \"line\": 4,\n              \"includedIn\": [],\n              \"tagName\": \"mj-attributes\",\n              \"children\": [\n                {\n                  \"file\": \".\",\n                  \"line\": 5,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-text\",\n                  \"attributes\": {\n                    \"color\": \"blue\"\n                  }\n                },\n                {\n                  \"file\": \".\",\n                  \"line\": 6,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-text\",\n                  \"attributes\": {\n                    \"font-size\": \"40px\"\n                  }\n                }\n              ],\n              \"attributes\": {}\n            }\n          ],\n          \"attributes\": {}\n        },\n        {\n          \"file\": \".\",\n          \"line\": 9,\n          \"includedIn\": [],\n          \"tagName\": \"mj-body\",\n          \"children\": [\n            {\n              \"file\": \".\",\n              \"line\": 10,\n              \"includedIn\": [],\n              \"tagName\": \"mj-section\",\n              \"children\": [\n                {\n                  \"file\": \".\",\n                  \"line\": 11,\n                  \"includedIn\": [],\n                  \"tagName\": \"mj-column\",\n                  \"children\": [\n                    {\n                      \"file\": \".\",\n                      \"line\": 12,\n                      \"includedIn\": [],\n                      \"tagName\": \"mj-text\",\n                      \"content\": \"Hello !\",\n                      \"attributes\": {}\n                    }\n                  ],\n                  \"attributes\": {}\n                }\n              ],\n              \"attributes\": {}\n            }\n          ],\n          \"attributes\": {}\n        }\n      ],\n      \"attributes\": {}\n    }\n  },\n  // Input that matches most of the CDATAs regex but not all, potentially resulting in regex timeout\n  {\n    test: 'Regex timeout',\n    mjml: `\n<mj-section>\n  <mj-text font-family=\"Arial\" />\n  <mj-column background-color=\"#ffffff\" css-class=\"column1\"></mj-column>\n</mj-section>\n    `,\n    validJson: {\n      \"file\": \".\",\n      \"line\": 2,\n      \"includedIn\": [],\n      \"tagName\": \"mj-section\",\n      \"children\": [\n        {\n          \"file\": \".\",\n          \"line\": 3,\n          \"includedIn\": [],\n          \"tagName\": \"mj-text\",\n          \"attributes\": {\n            \"font-family\": \"Arial\"\n          }\n        },\n        {\n          \"file\": \".\",\n          \"line\": 4,\n          \"includedIn\": [],\n          \"tagName\": \"mj-column\",\n          \"attributes\": {\n            \"background-color\": \"#ffffff\",\n            \"css-class\": \"column1\"\n          }\n        }\n      ],\n      \"attributes\": {}\n    }\n  },\n  {\n    test: 'Multiline attributes',\n    mjml: `\n<mj-text\n    padding-left=\"16px\"\n\n    padding-right=\"16px\">\n    <a href=\"https://www.test.com\" style=\"color: #60788c\">View blog ]]post</a>\n</mj-text>\n    `,\n    validJson: {\n      \"file\": \".\",\n      \"line\": 2,\n      \"includedIn\": [],\n      \"tagName\": \"mj-text\",\n      \"attributes\": {\n        \"padding-left\": \"16px\",\n        \"padding-right\": \"16px\"\n      },\n      \"content\": \"<a href=\\\"https://www.test.com\\\" style=\\\"color: #60788c\\\">View blog ]]post</a>\"\n    }\n  },\n  {\n    test: 'Self closing Ending Tags',\n    mjml: `\n      <mjml>\n        <mj-head>\n          <mj-title></mj-title>\n          <mj-attributes>\n            <mj-text font-size=\"27px\" />\n          </mj-attributes>\n        </mj-head>\n        <mj-body>\n          <mj-section>\n            <mj-column width=\"65%\">\n              <mj-text mj-class=\"small\" align=\"left\" font-family=\"Helvetica\" color=\"#000000\" padding-top=\"20px\">\n                coin\n                <a href=\"https://test\" style=\"text-decoration:underline;color:#336666;font-weight:bold\" class=\"mobile-small-letters\">Majors and Minors</a>\n                bla\n                <a href=\"https://test\" style=\"text-decoration:underline;color:#336666;font-weight:bold\" class=\"mobile-small-letters\">Majors and Minors</a>\n                <mj-raw>\n                  coin\n                </mj-raw>\n              </mj-text>\n            </mj-column>\n          </mj-section>\n        </mj-body>\n      </mjml>\n    `,\n    validJson: {\n      file: '.',\n      line: 2,\n      includedIn: [],\n      tagName: 'mjml',\n      children:\n       [ { file: '.',\n           line: 3,\n           includedIn: [],\n           tagName: 'mj-head',\n           children:\n            [ { file: '.',\n                line: 4,\n                includedIn: [],\n                tagName: 'mj-title',\n                attributes: {} },\n              { file: '.',\n                line: 5,\n                includedIn: [],\n                tagName: 'mj-attributes',\n                children:\n                 [ { file: '.',\n                     line: 6,\n                     includedIn: [],\n                     tagName: 'mj-text',\n                     attributes: { 'font-size': '27px' } } ],\n                attributes: {} } ],\n           attributes: {} },\n         { file: '.',\n           line: 9,\n           includedIn: [],\n           tagName: 'mj-body',\n           children:\n            [ { file: '.',\n                line: 10,\n                includedIn: [],\n                tagName: 'mj-section',\n                children:\n                 [ { file: '.',\n                     line: 11,\n                     includedIn: [],\n                     tagName: 'mj-column',\n                     attributes: { width: '65%' },\n                     children:\n                      [ { file: '.',\n                          line: 12,\n                          includedIn: [],\n                          tagName: 'mj-text',\n                          attributes:\n                           { 'mj-class': 'small',\n                             align: 'left',\n                             'font-family': 'Helvetica',\n                             color: '#000000',\n                             'padding-top': '20px' },\n                          content: 'coin\\n                <a href=\"https://test\" style=\"text-decoration:underline;color:#336666;font-weight:bold\" class=\"mobile-small-letters\">Majors and Minors</a>\\n                bla\\n                <a href=\"https://test\" style=\"text-decoration:underline;color:#336666;font-weight:bold\" class=\"mobile-small-letters\">Majors and Minors</a>\\n                <mj-raw>\\n                  coin\\n                </mj-raw>' } ] } ],\n                attributes: {} } ],\n           attributes: {} } ],\n      attributes: {}\n    }\n  },\n  {\n    test: 'Include',\n    mjml: `\n      <mjml>\n        <mj-body>\n          <mj-section>\n            <mj-include path=\"./test/incl.mjml\" />\n          </mj-section>\n        </mj-body>\n      </mjml>\n    `,\n    validJson: {\n      file: '.',\n      line: 2,\n      includedIn: [],\n      tagName: 'mjml',\n      children:\n       [ { file: '.',\n           line: 3,\n           includedIn: [],\n           tagName: 'mj-body',\n           children:\n            [ { file: '.',\n                line: 4,\n                includedIn: [],\n                tagName: 'mj-section',\n                children:\n                 [ { file: '.',\n                     line: 1,\n                     includedIn:\n                      [ { file: '.',\n                          line: 5 } ],\n                     tagName: 'mj-column',\n                     children:\n                      [ { file: '.',\n                          line: 2,\n                          includedIn:\n                           [ { file: '.',\n                               line: 5 } ],\n                          tagName: 'mj-text',\n                          attributes: { 'font-size': '22px' },\n                          content: 'COIN\\n    <a src=\"test\">aze</a>' },\n                        { file: '.',\n                          line: 6,\n                          includedIn:\n                           [ { file: '.',\n                               line: 5 } ],\n                          tagName: 'mj-text',\n                          attributes: { 'font-size': '22px' },\n                          content: 'COIN2\\n    <a src=\"test\">aze2</a>' } ],\n                     attributes: {} } ],\n                attributes: {} } ],\n           attributes: {} } ],\n      attributes: {}\n    }\n  },\n  {\n    test: 'Single opening tag in endingTag, single and multi-line',\n    mjml: `\n      <mjml>\n        <mj-body>\n          <mj-section>\n            <mj-column>\n              <mj-raw test=\"test\"><?php endif ?></mj-raw>\n              <mj-raw>\n                <?php endif ?>\n              </mj-raw>\n            </mj-column>\n          </mj-section>\n        </mj-body>\n      </mjml>\n    `,\n    validJson: { line: 2,\n      includedIn: [],\n      tagName: 'mjml',\n      children:\n       [ { line: 3,\n           includedIn: [],\n           tagName: 'mj-body',\n           children:\n            [ { line: 4,\n                includedIn: [],\n                tagName: 'mj-section',\n                children:\n                 [ { line: 5,\n                     includedIn: [],\n                     tagName: 'mj-column',\n                     children:\n                      [ { line: 6,\n                          includedIn: [],\n                          tagName: 'mj-raw',\n                          attributes: { test: 'test' },\n                          content: '<?php endif ?>' },\n                        { line: 7,\n                          includedIn: [],\n                          tagName: 'mj-raw',\n                          content: '<?php endif ?>',\n                          attributes: {} } ],\n                     attributes: {} } ],\n                attributes: {} } ],\n           attributes: {} } ],\n      attributes: {} }\n  }\n]\n/* eslint-enable comma-dangle */\n"
  },
  {
    "path": "packages/mjml-parser-xml/test/test.js",
    "content": "const MJMLParser = require('../lib/index.js')\nrequire('mjml')\nconst components = require('mjml-core').components\nconst chai = require('chai')\nconst displayDiff = require('./test-utils').displayDiff\nconst omitDeepLodash = require('./test-utils').omitDeepLodash\nconst testValues = require('./test-values')\n\n/*\n  If test fails, run it with --debug to log the details of the diff\n*/\n\nconst parse = mjml => MJMLParser(mjml, {\n  keepComments: true,\n  components,\n  filePath: '.'\n})\n\ntestValues.forEach(testUnit => {\n  const { test, mjml, validJson } = testUnit\n\n  if (process.argv.indexOf('--debug') !== -1) {\n    displayDiff(omitDeepLodash(validJson, 'file'), omitDeepLodash(parse(mjml), ['absoluteFilePath', 'file']))\n  }\n\n  chai.expect(omitDeepLodash(validJson, 'file'), `${test} test failed`)\n      .to.deep.equal(omitDeepLodash(parse(mjml), ['absoluteFilePath', 'file']))\n})\n"
  },
  {
    "path": "packages/mjml-preset-core/README.md",
    "content": "## mjml-preset-core\n\n### Installation\n\n```bash\nnpm install --save mjml-preset-core\n```\n\nThis is the set of mjml components bundled together for simple setup.\n\n### Usage\n\n```javascript\nimport mjml2html from 'mjml-core'\nimport presetCore from 'mjml-preset-core'\n\nconsole.log(mjml2html(`code`, { presets: [presetCore] }))\n```\n"
  },
  {
    "path": "packages/mjml-preset-core/package.json",
    "content": "{\n  \"name\": \"mjml-preset-core\",\n  \"description\": \"mjml-preset-core\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-preset-core\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"mjml-accordion\": \"4.18.0\",\n    \"mjml-body\": \"4.18.0\",\n    \"mjml-button\": \"4.18.0\",\n    \"mjml-carousel\": \"4.18.0\",\n    \"mjml-column\": \"4.18.0\",\n    \"mjml-divider\": \"4.18.0\",\n    \"mjml-group\": \"4.18.0\",\n    \"mjml-head\": \"4.18.0\",\n    \"mjml-head-attributes\": \"4.18.0\",\n    \"mjml-head-breakpoint\": \"4.18.0\",\n    \"mjml-head-font\": \"4.18.0\",\n    \"mjml-head-html-attributes\": \"4.18.0\",\n    \"mjml-head-preview\": \"4.18.0\",\n    \"mjml-head-style\": \"4.18.0\",\n    \"mjml-head-title\": \"4.18.0\",\n    \"mjml-hero\": \"4.18.0\",\n    \"mjml-image\": \"4.18.0\",\n    \"mjml-navbar\": \"4.18.0\",\n    \"mjml-raw\": \"4.18.0\",\n    \"mjml-section\": \"4.18.0\",\n    \"mjml-social\": \"4.18.0\",\n    \"mjml-spacer\": \"4.18.0\",\n    \"mjml-table\": \"4.18.0\",\n    \"mjml-text\": \"4.18.0\",\n    \"mjml-wrapper\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-preset-core/src/dependencies.js",
    "content": "export default {\n  mjml: ['mj-body', 'mj-head', 'mj-raw'],\n  'mj-accordion': ['mj-accordion-element', 'mj-raw'],\n  'mj-accordion-element': ['mj-accordion-title', 'mj-accordion-text', 'mj-raw'],\n  'mj-accordion-title': [],\n  'mj-accordion-text': [],\n  'mj-attributes': [/^.*^/],\n  'mj-body': ['mj-raw', 'mj-section', 'mj-wrapper', 'mj-hero'],\n  'mj-button': [],\n  'mj-carousel': ['mj-carousel-image'],\n  'mj-carousel-image': [],\n  'mj-column': [\n    'mj-accordion',\n    'mj-button',\n    'mj-carousel',\n    'mj-divider',\n    'mj-image',\n    'mj-raw',\n    'mj-social',\n    'mj-spacer',\n    'mj-table',\n    'mj-text',\n    'mj-navbar',\n  ],\n  'mj-html-attribute': [],\n  'mj-html-attributes': ['mj-selector'],\n  'mj-divider': [],\n  'mj-group': ['mj-column', 'mj-raw'],\n  'mj-head': [\n    'mj-attributes',\n    'mj-breakpoint',\n    'mj-html-attributes',\n    'mj-font',\n    'mj-preview',\n    'mj-style',\n    'mj-title',\n    'mj-raw',\n  ],\n  'mj-hero': [\n    'mj-accordion',\n    'mj-button',\n    'mj-carousel',\n    'mj-divider',\n    'mj-image',\n    'mj-social',\n    'mj-spacer',\n    'mj-table',\n    'mj-text',\n    'mj-navbar',\n    'mj-raw',\n  ],\n  'mj-image': [],\n  'mj-navbar': ['mj-navbar-link', 'mj-raw'],\n  'mj-raw': [],\n  'mj-section': ['mj-column', 'mj-group', 'mj-raw'],\n  'mj-selector': ['mj-html-attribute'],\n  'mj-social': ['mj-social-element', 'mj-raw'],\n  'mj-social-element': [],\n  'mj-spacer': [],\n  'mj-table': [],\n  'mj-text': [],\n  'mj-wrapper': ['mj-hero', 'mj-raw', 'mj-section'],\n}\n"
  },
  {
    "path": "packages/mjml-preset-core/src/index.js",
    "content": "import { Social, SocialElement } from 'mjml-social'\nimport { Navbar, NavbarLink } from 'mjml-navbar'\nimport { Carousel, CarouselImage } from 'mjml-carousel'\nimport {\n  Accordion,\n  AccordionElement,\n  AccordionText,\n  AccordionTitle,\n} from 'mjml-accordion'\nimport Body from 'mjml-body'\nimport Head from 'mjml-head'\nimport HeadAttributes from 'mjml-head-attributes'\nimport HeadBreakpoint from 'mjml-head-breakpoint'\nimport HeadHtmlAttributes from 'mjml-head-html-attributes'\nimport HeadFont from 'mjml-head-font'\nimport HeadPreview from 'mjml-head-preview'\nimport HeadStyle from 'mjml-head-style'\nimport HeadTitle from 'mjml-head-title'\nimport Hero from 'mjml-hero'\nimport Button from 'mjml-button'\nimport Column from 'mjml-column'\nimport Divider from 'mjml-divider'\nimport Group from 'mjml-group'\nimport Image from 'mjml-image'\nimport Raw from 'mjml-raw'\nimport Section from 'mjml-section'\nimport Spacer from 'mjml-spacer'\nimport Text from 'mjml-text'\nimport Table from 'mjml-table'\nimport Wrapper from 'mjml-wrapper'\nimport dependencies from './dependencies'\n\nconst components = [\n  Body,\n  Head,\n  HeadAttributes,\n  HeadBreakpoint,\n  HeadHtmlAttributes,\n  HeadFont,\n  HeadPreview,\n  HeadStyle,\n  HeadTitle,\n  Hero,\n  Button,\n  Column,\n  Divider,\n  Group,\n  Image,\n\n  Raw,\n  Section,\n  Spacer,\n  Text,\n  Table,\n  Wrapper,\n\n  Social,\n  SocialElement,\n  Navbar,\n  NavbarLink,\n  Accordion,\n  AccordionElement,\n  AccordionText,\n  AccordionTitle,\n  Carousel,\n  CarouselImage,\n]\n\nconst presetCore = {\n  components,\n  dependencies,\n}\n\nexport default presetCore\n"
  },
  {
    "path": "packages/mjml-raw/README.md",
    "content": "### mj-raw\n\nDisplays raw HTML that is not parsed by the MJML engine. Anything left inside this tag should be raw, responsive HTML. If placed inside the `mj-head` tag, its content will be added at the end of the HTML `<head>` tag.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-raw>\n      <!-- Your content goes here -->\n    </mj-raw>\n  </mj-body>\n</mjml>\n```\n\nYou can tell the minifier to ignore some content by wrapping it between two `<!-- htmlmin:ignore -->` tags.\n\nYou can use `mj-raw` to add a templating language. Note that if you and use the `minify` option, you might get a `Parsing error`, especially when using the `<` character. These can be ignored by using the `<!-- htmlmin:ignore -->` tags.mlmin:ignore -->` tags.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-raw>\n      <!-- htmlmin:ignore --> {% if foo < 5 %} <!-- htmlmin:ignore -->\n    </mj-raw>\n      <!-- Some mjml section -->\n    <mj-raw>\n      {% endif %}\n    </mj-raw>\n  </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-raw</code> is an \"ending tag\", which means that it can contain HTML code but it cannot contain other MJML components.</p> \n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\nYou can also use `mj-raw` to add text at the beginning of the generated html, before the `<!doctype html>` line. For this you need to:\n\n- add the `mj-raw` tag inside the `mjml` tag, outside of the `mj-head` and `mj-body` tags.\n- add the `position=\"file-start\"` attribute to the `mj-raw` tag:\n\nNote that if you put multiple lines in this `mj-raw` and use the minify option, these lines will be joined into a single line by the minifier. These can be ignored by using the `<!-- htmlmin:ignore -->` tags.\n\n```xml\n<mjml>\n  <mj-raw position=\"file-start\">This will be added at the beginning of the file</mj-raw>\n  <mj-body>\n    <!-- Your content goes here -->\n  </mj-body>\n</mjml>\n```\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/raw\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-raw/package.json",
    "content": "{\n  \"name\": \"mjml-raw\",\n  \"description\": \"mjml-raw\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-raw\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-raw/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjRaw extends BodyComponent {\n  static componentName = 'mj-raw'\n\n  static endingTag = true\n\n  static rawElement = true\n\n  static allowedAttributes = {\n    position: 'enum(file-start)',\n  }\n\n  render() {\n    return this.getContent()\n  }\n}\n"
  },
  {
    "path": "packages/mjml-section/README.md",
    "content": "### mj-section\n\nSections are rows within your email. They will be used to structure the layout.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section full-width=\"full-width\" background-color=\"red\">\n      <!-- Your columns go here -->\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\nThe `full-width` attribute will be used to manage the background width. Setting it will change the width of the section from the default 600px to 100%.\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p>To invert the order in which columns display in the desktop view, first setup your columns in the order you want them to appear stacked in the mobile view and then add <code>direction=\"rtl\"</code> to the <code>mj-section</code> tag.</p>\n</div>\n\n<div class=\"alert alert-caution\" role=\"alert\">\n  <p>Caution</p>\n  <p><code>mj-section</code> tags cannot nest in other <code>mj-section</code> tags</p>\n</div>\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>Limitations of <code>background-image</code> <code>background-size</code> and <code>background-position</code> in Outlook desktop :</p>\n  <ul>\n    <li>If <code>background-size</code> is not specified, <code>no-repeat</code> will be ignored in Outlook.</li>\n    <li>If the specified <code>background-size</code> is a single attribute in percent, the height will be <code>auto</code> as in standard CSS. In Outlook, the image will never overflow the element, it will shrink instead of being cropped similar to other clients.</li>\n  </ul>\n</div>\n\n#### Attributes\n\n| attribute             | accepts                 | description                                                                                            | default value |\n| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------ | ------------- |\n| background-color      | CSS color formats       | section color                                                                                          |               |\n| background-position   | string                  | CSS values, i.e. `left` `center` `right` + `top` `center` `bottom` <br>(see outlook limitations below) | `top center`  |\n| background-position-x | string                  | CSS values, i.e. `left` `center` `right` <br>(see outlook limitations below)                           |               |\n| background-position-y | string                  | CSS values, i.e. `top` `center` `bottom` <br>(see outlook limitations below)                           |               |\n| background-repeat     | `repeat` `no-repeat`    | set the background image to repeat                                                                     |\n| background-size       | string                  | CSS values e.g. `auto` `cover` `contain` `px` `%` size                                                 | `auto`        |\n| background-url        | string                  | background image, in URL format                                                                        |               |\n| border                | string                  | CSS border format                                                                                      |               |\n| border-bottom         | string                  | CSS border format                                                                                      |               |\n| border-left           | string                  | CSS border format                                                                                      |               |\n| border-radius         | string                  | border radius                                                                                          |               |\n| border-right          | string                  | CSS border format                                                                                      |               |\n| border-top            | string                  | CSS border format                                                                                      |               |\n| css-class             | string                  | class name, added to the root HTML element created                                                     |               |\n| direction             | `ltr` `rtl`             | set the display order of direct children                                                               | `ltr`         |\n| full-width            | `full-width` `false`    | make the section full-width                                                                            |               |\n| padding               | `px` `%`                | section padding, supports up to 4 parameters                                                           | `20px 0`      |\n| padding-bottom        | `px` `%`                | section bottom padding                                                                                 |               |\n| padding-left          | `px` `%`                | section left padding                                                                                   |               |\n| padding-right         | `px` `%`                | section right padding                                                                                  |               |\n| padding-top           | `px` `%`                | section top padding                                                                                    |               |\n| text-align            | `left` `center` `right` | CSS text-align                                                                                         | `center`      |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/section\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-section/package.json",
    "content": "{\n  \"name\": \"mjml-section\",\n  \"description\": \"mjml-section\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-section\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-section/src/index.js",
    "content": "import { BodyComponent, suffixCssClasses } from 'mjml-core'\nimport { flow, identity, join, filter } from 'lodash/fp'\n\nconst makeBackgroundString = flow(filter(identity), join(' '))\n\nexport default class MjSection extends BodyComponent {\n  static componentName = 'mj-section'\n\n  static allowedAttributes = {\n    'background-color': 'color',\n    'background-url': 'string',\n    'background-repeat': 'enum(repeat,no-repeat)',\n    'background-size': 'string',\n    'background-position': 'string',\n    'background-position-x': 'string',\n    'background-position-y': 'string',\n    border: 'string',\n    'border-bottom': 'string',\n    'border-left': 'string',\n    'border-radius': 'string',\n    'border-right': 'string',\n    'border-top': 'string',\n    direction: 'enum(ltr,rtl)',\n    'full-width': 'enum(full-width,false,)',\n    padding: 'unit(px,%){1,4}',\n    'padding-top': 'unit(px,%)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'text-align': 'enum(left,center,right)',\n    'text-padding': 'unit(px,%){1,4}',\n  }\n\n  static defaultAttributes = {\n    'background-repeat': 'repeat',\n    'background-size': 'auto',\n    'background-position': 'top center',\n    direction: 'ltr',\n    padding: '20px 0',\n    'text-align': 'center',\n    'text-padding': '4px 4px 4px 0',\n  }\n\n  getChildContext() {\n    const { box } = this.getBoxWidths()\n\n    return {\n      ...this.context,\n      containerWidth: `${box}px`,\n      gap: this.getAttribute('gap'),\n    }\n  }\n\n  getStyles() {\n    const { containerWidth } = this.context\n\n    const fullWidth = this.isFullWidth()\n\n    const hasBorderRadius = this.hasBorderRadius()\n\n    const isFirstSection = this.props.index === 0\n\n    const background = this.getAttribute('background-url')\n      ? {\n          background: this.getBackground(),\n          // background size, repeat and position has to be seperate since yahoo does not support shorthand background css property\n          'background-position': this.getBackgroundString(),\n          'background-repeat': this.getAttribute('background-repeat'),\n          'background-size': this.getAttribute('background-size'),\n        }\n      : {\n          background: this.getAttribute('background-color'),\n          'background-color': this.getAttribute('background-color'),\n        }\n\n    return {\n      tableFullwidth: {\n        ...(fullWidth ? background : {}),\n        width: '100%',\n      },\n      table: {\n        ...(fullWidth ? {} : background),\n        width: '100%',\n        ...(hasBorderRadius && { 'border-collapse': 'separate' }),\n      },\n      td: {\n        border: this.getAttribute('border'),\n        'border-bottom': this.getAttribute('border-bottom'),\n        'border-left': this.getAttribute('border-left'),\n        'border-right': this.getAttribute('border-right'),\n        'border-top': this.getAttribute('border-top'),\n        'border-radius': this.getAttribute('border-radius'),\n        direction: this.getAttribute('direction'),\n        'font-size': '0px',\n        padding: this.getAttribute('padding'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n        'padding-left': this.getAttribute('padding-left'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-top': this.getAttribute('padding-top'),\n        'text-align': this.getAttribute('text-align'),\n      },\n      div: {\n        ...(fullWidth ? {} : background),\n        margin: '0px auto',\n        'max-width': containerWidth,\n        'border-radius': this.getAttribute('border-radius'),\n        ...(hasBorderRadius && { overflow: 'hidden' }),\n        'margin-top': !isFirstSection ? this.context.gap : undefined,\n      },\n      innerDiv: {\n        'line-height': '0',\n        'font-size': '0',\n      },\n    }\n  }\n\n  getBackground() {\n    return makeBackgroundString([\n      this.getAttribute('background-color'),\n      ...(this.hasBackground()\n        ? [\n            `url('${this.getAttribute('background-url')}')`,\n            this.getBackgroundString(),\n            `/ ${this.getAttribute('background-size')}`,\n            this.getAttribute('background-repeat'),\n          ]\n        : []),\n    ])\n  }\n\n  getBackgroundString() {\n    const { posX, posY } = this.getBackgroundPosition()\n    return `${posX} ${posY}`\n  }\n\n  getBackgroundPosition() {\n    const { x, y } = this.parseBackgroundPosition()\n\n    return {\n      posX: this.getAttribute('background-position-x') || x,\n      posY: this.getAttribute('background-position-y') || y,\n    }\n  }\n\n  parseBackgroundPosition() {\n    const posSplit = this.getAttribute('background-position').split(' ')\n\n    if (posSplit.length === 1) {\n      const val = posSplit[0]\n      // here we must determine if x or y was provided ; other will be center\n      if (['top', 'bottom'].includes(val)) {\n        return {\n          x: 'center',\n          y: val,\n        }\n      }\n\n      return {\n        x: val,\n        y: 'center',\n      }\n    }\n\n    if (posSplit.length === 2) {\n      // x and y can be put in any order in background-position so we need to determine that based on values\n      const val1 = posSplit[0]\n      const val2 = posSplit[1]\n\n      if (\n        ['top', 'bottom'].includes(val1) ||\n        (val1 === 'center' && ['left', 'right'].includes(val2))\n      ) {\n        return {\n          x: val2,\n          y: val1,\n        }\n      }\n\n      return {\n        x: val1,\n        y: val2,\n      }\n    }\n\n    // more than 2 values is not supported, let's treat as default value\n    return { x: 'center', y: 'top' }\n  }\n\n  hasBackground() {\n    return this.getAttribute('background-url') != null\n  }\n\n  isFullWidth() {\n    return this.getAttribute('full-width') === 'full-width'\n  }\n\n  hasBorderRadius() {\n    const borderRadius = this.getAttribute('border-radius')\n    return borderRadius !== '' && typeof borderRadius !== 'undefined'\n  }\n\n  hasGap() {\n    const { gap } = this.context\n    return gap != null && gap !== ''\n  }\n\n  renderBefore() {\n    const { containerWidth } = this.context\n    const bgcolorAttr = this.getAttribute('background-color')\n      ? { bgcolor: this.getAttribute('background-color') }\n      : {}\n\n    const isFirstSection = this.props.index === 0\n\n    const hasGap = this.hasGap()\n\n    return `\n      <!--[if mso | IE]>\n      <table\n        ${this.htmlAttributes({\n          align: 'center',\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          class: suffixCssClasses(this.getAttribute('css-class'), 'outlook'),\n          role: 'presentation',\n          style: {\n            width: `${containerWidth}`,\n            'padding-top': !isFirstSection ? this.context.gap : undefined,\n          },\n          width: parseInt(containerWidth, 10),\n          ...(!hasGap && { ...bgcolorAttr }),\n        })}\n      >\n        <tr>\n          <td style=\"line-height:0px;font-size:0px;mso-line-height-rule:exactly;\">\n      <![endif]-->\n    `\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  renderAfter() {\n    return `\n      <!--[if mso | IE]>\n          </td>\n        </tr>\n      </table>\n      <![endif]-->\n    `\n  }\n\n  renderWrappedChildren() {\n    const { children } = this.props\n\n    return `\n      <!--[if mso | IE]>\n        <tr>\n      <![endif]-->\n      ${this.renderChildren(children, {\n        renderer: (component) =>\n          component.constructor.isRawElement()\n            ? component.render()\n            : `\n          <!--[if mso | IE]>\n            <td\n              ${component.htmlAttributes({\n                align: component.getAttribute('align'),\n                class: suffixCssClasses(\n                  component.getAttribute('css-class'),\n                  'outlook',\n                ),\n                style: 'tdOutlook',\n              })}\n            >\n          <![endif]-->\n            ${component.render()}\n          <!--[if mso | IE]>\n            </td>\n          <![endif]-->\n    `,\n      })}\n\n      <!--[if mso | IE]>\n        </tr>\n      <![endif]-->\n    `\n  }\n\n  renderWithBackground(content) {\n    const fullWidth = this.isFullWidth()\n\n    const { containerWidth } = this.context\n\n    const isPercentage = (str) => /^\\d+(\\.\\d+)?%$/.test(str)\n\n    let vSizeAttributes = {}\n    let { posX: bgPosX, posY: bgPosY } = this.getBackgroundPosition()\n\n    switch (bgPosX) {\n      case 'left':\n        bgPosX = '0%'\n        break\n      case 'center':\n        bgPosX = '50%'\n        break\n      case 'right':\n        bgPosX = '100%'\n        break\n      default:\n        if (!isPercentage(bgPosX)) {\n          bgPosX = '50%'\n        }\n        break\n    }\n    switch (bgPosY) {\n      case 'top':\n        bgPosY = '0%'\n        break\n      case 'center':\n        bgPosY = '50%'\n        break\n      case 'bottom':\n        bgPosY = '100%'\n        break\n      default:\n        if (!isPercentage(bgPosY)) {\n          bgPosY = '0%'\n        }\n        break\n    }\n\n    // this logic is different when using repeat or no-repeat\n    let [[vOriginX, vPosX], [vOriginY, vPosY]] = ['x', 'y'].map(\n      (coordinate) => {\n        const isX = coordinate === 'x'\n        const bgRepeat = this.getAttribute('background-repeat') === 'repeat'\n        let pos = isX ? bgPosX : bgPosY\n        let origin = isX ? bgPosX : bgPosY\n\n        if (isPercentage(pos)) {\n          // Should be percentage at this point\n          const percentageValue = pos.match(/^(\\d+(\\.\\d+)?)%$/)[1]\n          const decimal = parseInt(percentageValue, 10) / 100\n\n          if (bgRepeat) {\n            pos = decimal\n            origin = decimal\n          } else {\n            pos = (-50 + decimal * 100) / 100\n            origin = (-50 + decimal * 100) / 100\n          }\n        } else if (bgRepeat) {\n          // top (y) or center (x)\n          origin = isX ? '0.5' : '0'\n          pos = isX ? '0.5' : '0'\n        } else {\n          origin = isX ? '0' : '-0.5'\n          pos = isX ? '0' : '-0.5'\n        }\n\n        return [origin, pos]\n      },\n      this,\n    )\n\n    // If background size is either cover or contain, we tell VML to keep the aspect\n    // and fill the entire element.\n    if (\n      this.getAttribute('background-size') === 'cover' ||\n      this.getAttribute('background-size') === 'contain'\n    ) {\n      vSizeAttributes = {\n        size: '1,1',\n        aspect:\n          this.getAttribute('background-size') === 'cover'\n            ? 'atleast'\n            : 'atmost',\n      }\n    } else if (this.getAttribute('background-size') !== 'auto') {\n      const bgSplit = this.getAttribute('background-size').split(' ')\n\n      if (bgSplit.length === 1) {\n        vSizeAttributes = {\n          size: this.getAttribute('background-size'),\n          aspect: 'atmost', // reproduces height auto\n        }\n      } else {\n        vSizeAttributes = {\n          size: bgSplit.join(','),\n        }\n      }\n    }\n\n    let vmlType =\n      this.getAttribute('background-repeat') === 'no-repeat' ? 'frame' : 'tile'\n\n    if (this.getAttribute('background-size') === 'auto') {\n      vmlType = 'tile' // if no size provided, keep old behavior because outlook can't use original image size with \"frame\"\n      ;[[vOriginX, vPosX], [vOriginY, vPosY]] = [\n        [0.5, 0.5],\n        [0, 0],\n      ] // also ensure that images are still cropped the same way\n    }\n\n    return `\n      <!--[if mso | IE]>\n        <v:rect ${this.htmlAttributes({\n          style: fullWidth\n            ? { 'mso-width-percent': '1000' }\n            : { width: containerWidth },\n          'xmlns:v': 'urn:schemas-microsoft-com:vml',\n          fill: 'true',\n          stroke: 'false',\n        })}>\n        <v:fill ${this.htmlAttributes({\n          origin: `${vOriginX}, ${vOriginY}`,\n          position: `${vPosX}, ${vPosY}`,\n          src: this.getAttribute('background-url'),\n          color: this.getAttribute('background-color'),\n          type: vmlType,\n          ...vSizeAttributes,\n        })} />\n        <v:textbox style=\"mso-fit-shape-to-text:true\" inset=\"0,0,0,0\">\n      <![endif]-->\n          ${content}\n        <!--[if mso | IE]>\n        </v:textbox>\n      </v:rect>\n    <![endif]-->\n    `\n  }\n\n  renderSection() {\n    const hasBackground = this.hasBackground()\n\n    return `\n      <div ${this.htmlAttributes({\n        class: this.isFullWidth() ? null : this.getAttribute('css-class'),\n        style: 'div',\n      })}>\n        ${\n          hasBackground\n            ? `<div ${this.htmlAttributes({ style: 'innerDiv' })}>`\n            : ''\n        }\n        <table\n          ${this.htmlAttributes({\n            align: 'center',\n            background: this.isFullWidth()\n              ? null\n              : this.getAttribute('background-url'),\n            border: '0',\n            cellpadding: '0',\n            cellspacing: '0',\n            role: 'presentation',\n            style: 'table',\n          })}\n        >\n          <tbody>\n            <tr>\n              <td\n                ${this.htmlAttributes({\n                  style: 'td',\n                })}\n              >\n                <!--[if mso | IE]>\n                  <table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                <![endif]-->\n                  ${this.renderWrappedChildren()}\n                <!--[if mso | IE]>\n                  </table>\n                <![endif]-->\n              </td>\n            </tr>\n          </tbody>\n        </table>\n        ${hasBackground ? '</div>' : ''}\n      </div>\n    `\n  }\n\n  renderFullWidth() {\n    const content = this.hasBackground()\n      ? this.renderWithBackground(`\n        ${this.renderBefore()}\n        ${this.renderSection()}\n        ${this.renderAfter()}\n      `)\n      : `\n        ${this.renderBefore()}\n        ${this.renderSection()}\n        ${this.renderAfter()}\n      `\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          align: 'center',\n          class: this.getAttribute('css-class'),\n          background: this.getAttribute('background-url'),\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n          style: 'tableFullwidth',\n        })}\n      >\n        <tbody>\n          <tr>\n            <td>\n              ${content}\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    `\n  }\n\n  renderSimple() {\n    const section = this.renderSection()\n\n    return `\n      ${this.renderBefore()}\n      ${this.hasBackground() ? this.renderWithBackground(section) : section}\n      ${this.renderAfter()}\n    `\n  }\n\n  render() {\n    return this.isFullWidth() ? this.renderFullWidth() : this.renderSimple()\n  }\n}\n"
  },
  {
    "path": "packages/mjml-social/README.md",
    "content": "### mj-social\n\nDisplays calls-to-action for various social networks with their associated logo. You can add multiple social networks using `mj-social-element` tags.\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/social-example.png\" alt=\"desktop\" style=\"width: 250px;\"/>\n</figure>\n\n<div class=\"alert alert-caution\" role=\"alert\">\n  <p>Caution</p>\n  <p>The <code>mj-social-element</code> <code>name</code> attribute is a shortcut for some common social elements. You should avoid rely too much on this as those icons are hosted by Mailjet for their Email Builder. Use <a href=\"#custom-social-element\">custom element syntax instead.</a></p>\n</div>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-social font-size=\"15px\" icon-size=\"30px\" mode=\"horizontal\">\n          <mj-social-element name=\"facebook\" href=\"https://mjml.io/\">\n            Facebook\n          </mj-social-element>\n          <mj-social-element name=\"google\" href=\"https://mjml.io/\">\n            Google\n          </mj-social-element>\n          <mj-social-element name=\"twitter\" href=\"https://mjml.io/\">\n            Twitter\n          </mj-social-element>\n          <mj-social-element name=\"x\" href=\"https://mjml.io/\">\n            X\n          </mj-social-element>\n        </mj-social>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute                  | accepts                 | description                                        | default value                          |\n| -------------------------- | ----------------------- | -------------------------------------------------- | -------------------------------------- |\n| align                      | `left` `right` `center` | align content                                      | `center`                               |\n| border-radius              | `px` `%`                | border radius                                      | `3px`                                  |\n| color                      | CSS color formats       | text color                                         | `#333333`                              |\n| css-class                  | string                  | class name, added to the root HTML element created |                                        |\n| container-background-color | CSS color formats       | inner element background color                     |                                        |\n| font-family                | string                  | font name                                          | `Ubuntu, Helvetica, Arial, sans-serif` |\n| font-size                  | `px`                    | font size                                          | `13px`                                 |\n| font-style                 | string                  | font style                                         | normal                                 |\n| font-weight                | string                  | font weight                                        | normal                                 |\n| icon-height                | `px` `%`                | icon height, overrides `icon-size`                 | icon-size                              |\n| icon-padding               | `px` `%`                | padding around the icons                           |                                        |\n| icon-size                  | `px` `%`                | icon size (width and height)                       | `20px`                                 |\n| inner-padding              | `px` `%`                | social network surrounding padding                 | `null`                                 |\n| line-height                | `px` `%`                | space between lines                                | `22px`                                 |\n| mode                       | `horizontal` `vertical` | direction of social elements                       | `horizontal`                           |\n| padding                    | `px` `%`                | social padding, supports up to 4 parameters        | `10px 25px`                            |\n| padding-bottom             | `px` `%`                | bottom padding                                     |                                        |\n| padding-left               | `px` `%`                | left padding                                       |                                        |\n| padding-right              | `px` `%`                | right padding                                      |                                        |\n| padding-top                | `px` `%`                | top padding                                        |                                        |\n| text-padding               | `px` `%`                | padding around the text                            |                                        |\n| text-decoration            | string                  | CSS values, e.g. `underline` `overline` `none`     | `none`                                 |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/social\">Try it live</a></p>\n\n#### mj-social-element\n\nThis component enables you to display a given social network inside `mj-social`.\nNote that default icons are transparent, which allows `background-color` to actually be the icon color.\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-social-element</code> is an \"ending tag\", which means that it can contain HTML code but it cannot contain other MJML components.</p> \n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n\n#### Attributes\n\n| attribute        | accepts                 | description                                                                     | default value                          |\n| ---------------- | ----------------------- | ------------------------------------------------------------------------------- | -------------------------------------- |\n| align            | `left` `center` `right` | align content                                                                   | `center`                               |\n| alt              | string                  | image alt attribute                                                             | `''`                                   |\n| background-color | CSS color formats       | icon color                                                                      | Each social `name` has its own default |\n| border-radius    | `px`                    | border radius                                                                   | `3px`                                  |\n| color            | CSS color formats       | text color                                                                      | `#000`                                 |\n| css-class        | string                  | class name, added to the root HTML element created                              |                                        |\n| font-family      | string                  | font name                                                                       | `Ubuntu, Helvetica, Arial, sans-serif` |\n| font-size        | `px`                    | font size                                                                       | `13px`                                 |\n| font-style       | string                  | font style                                                                      |                                        |\n| font-weight      | string                  | font weight                                                                     |                                        |\n| href             | string                  | button redirection, in URL format                                               |                                        |\n| icon-height      | percent/px              | icon height, overrides icon-size                                                | `icon-size`                            |\n| icon-padding     | `px` `%`                | padding around the icon                                                         |                                        |\n| icon-position    | `left` `right`          | sets the side of the icon                                                       |                                        |\n| icon-size        | `px` `%`                | icon size (width and height)                                                    |                                        |\n| line-height      | `px` `%`                | space between lines                                                             | `1`                                    |\n| name             | string                  | social network name, see supported list below                                   |                                        |\n| padding          | `px` `%`                | social element padding, supports up to 4 parameters                             | `4px`                                  |\n| padding-bottom   | `px` `%`                | bottom padding                                                                  |                                        |\n| padding-left     | `px` `%`                | left padding                                                                    |                                        |\n| padding-right    | `px` `%`                | right padding                                                                   |                                        |\n| padding-top      | `px` `%`                | top padding                                                                     |                                        |\n| rel              | string                  | specify the rel attribute for the link                                          |                                        |\n| sizes            | string                  | set icon width based on query                                                   |                                        |\n| src              | string                  | image source, in URL format                                                     | Each social `name` has its own default |\n| srcset           | string                  | enables to set a different image source based on the viewport, using CSS syntax |                                        |\n| target           | string                  | link target                                                                     | `_blank`                               |\n| text-decoration  | string                  | CSS values, e.g. `underline` `overline` `none`                                  | `none`                                 |\n| text-padding     | `px` `%`                | padding around the text                                                         | `4px 4px 4px 0`                        |\n| title            | string                  | image title attribute                                                           |                                        |\n| vertical-align   | `top` `middle` `bottom` | vertically align elements                                                       | `middle`                               |\n\nSupported networks with a share url:\n\n- `facebook`\n- `twitter`\n- `x`\n- `google`\n- `pinterest`\n- `linkedin`\n- `tumblr`\n- `xing`\n\nWithout a share url:\n\n- `github`\n- `instagram`\n- `web`\n- `snapchat`\n- `youtube`\n- `vimeo`\n- `medium`\n- `soundcloud`\n- `dribbble`\n\nWhen using a network with share url, the `href` attribute will be inserted in the share url (i.e. `https://www.facebook.com/sharer/sharer.php?u=[[URL]]`). To keep your `href` unchanged, add `-noshare` to the network name. Example :\n\n`<mj-social-element name=\"twitter-noshare\" href=\"my-unchanged-url\">Twitter</mj-social-element>`\n\n#### Custom Social Element\n\nYou can add any unsupported network like this:\n\n```xml\n<mj-social-element href=\"url\" background-color=\"#FF00FF\" src=\"path-to-your-icon\">\n  Optional label\n</mj-social-element>\n```\n\nYou can also use mj-social this way with no `href` attribute to make a simple list of inlined images-texts.\n"
  },
  {
    "path": "packages/mjml-social/package.json",
    "content": "{\n  \"name\": \"mjml-social\",\n  \"description\": \"mjml-social\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-social\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-social/src/Social.js",
    "content": "import { BodyComponent } from 'mjml-core'\nimport { isNil } from 'lodash'\n\nexport default class MjSocial extends BodyComponent {\n  static componentName = 'mj-social'\n\n  static allowedAttributes = {\n    align: 'enum(left,right,center)',\n    'border-radius': 'unit(px,%)',\n    'container-background-color': 'color',\n    color: 'color',\n    'font-family': 'string',\n    'font-size': 'unit(px)',\n    'font-style': 'string',\n    'font-weight': 'string',\n    'icon-size': 'unit(px,%)',\n    'icon-height': 'unit(px,%)',\n    'icon-padding': 'unit(px,%){1,4}',\n    'inner-padding': 'unit(px,%){1,4}',\n    'line-height': 'unit(px,%,)',\n    mode: 'enum(horizontal,vertical)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    'table-layout': 'enum(auto,fixed)',\n    'text-padding': 'unit(px,%){1,4}',\n    'text-decoration': 'string',\n    'vertical-align': 'enum(top,bottom,middle)',\n  }\n\n  static defaultAttributes = {\n    align: 'center',\n    'border-radius': '3px',\n    color: '#333333',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'font-size': '13px',\n    'icon-size': '20px',\n    'inner-padding': null,\n    'line-height': '22px',\n    mode: 'horizontal',\n    padding: '10px 25px',\n    'text-decoration': 'none',\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  getStyles() {\n    return {\n      tableVertical: {\n        margin: '0px',\n      },\n    }\n  }\n\n  getSocialElementAttributes() {\n    const base = {}\n    if (this.getAttribute('inner-padding')) {\n      base.padding = this.getAttribute('inner-padding')\n    }\n\n    return [\n      'border-radius',\n      'color',\n      'font-family',\n      'font-size',\n      'font-weight',\n      'font-style',\n      'icon-size',\n      'icon-height',\n      'icon-padding',\n      'text-padding',\n      'line-height',\n      'text-decoration',\n    ]\n      .filter((e) => !isNil(this.getAttribute(e)))\n      .reduce((res, attr) => {\n        res[attr] = this.getAttribute(attr)\n        return res\n      }, base)\n  }\n\n  renderHorizontal() {\n    const { children } = this.props\n\n    return `\n     <!--[if mso | IE]>\n      <table\n        ${this.htmlAttributes({\n          align: this.getAttribute('align'),\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n        })}\n      >\n        <tr>\n      <![endif]-->\n      ${this.renderChildren(children, {\n        attributes: this.getSocialElementAttributes(),\n        renderer: (component) =>\n          component.constructor.isRawElement()\n            ? component.render()\n            : `\n            <!--[if mso | IE]>\n              <td>\n            <![endif]-->\n              <table\n                ${component.htmlAttributes({\n                  align: this.getAttribute('align'),\n                  border: '0',\n                  cellpadding: '0',\n                  cellspacing: '0',\n                  role: 'presentation',\n                  style: {\n                    float: 'none',\n                    display: 'inline-table',\n                  },\n                })}\n              >\n                <tbody>\n                  ${component.render()}\n                </tbody>\n              </table>\n            <!--[if mso | IE]>\n              </td>\n            <![endif]-->\n          `,\n      })}\n      <!--[if mso | IE]>\n          </tr>\n        </table>\n      <![endif]-->\n    `\n  }\n\n  renderVertical() {\n    const { children } = this.props\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          border: '0',\n          cellpadding: '0',\n          cellspacing: '0',\n          role: 'presentation',\n          style: 'tableVertical',\n        })}\n      >\n        <tbody>\n          ${this.renderChildren(children, {\n            attributes: this.getSocialElementAttributes(),\n          })}\n        </tbody>\n      </table>\n    `\n  }\n\n  render() {\n    return `\n      ${\n        this.getAttribute('mode') === 'horizontal'\n          ? this.renderHorizontal()\n          : this.renderVertical()\n      }\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-social/src/SocialElement.js",
    "content": "import { BodyComponent } from 'mjml-core'\nimport { get, each } from 'lodash'\n\nconst IMG_BASE_URL = 'https://www.mailjet.com/images/theme/v1/icons/ico-social/'\n\nconst defaultSocialNetworks = {\n  facebook: {\n    'share-url': 'https://www.facebook.com/sharer/sharer.php?u=[[URL]]',\n    'background-color': '#3b5998',\n    src: `${IMG_BASE_URL}facebook.png`,\n  },\n  twitter: {\n    'share-url': 'https://twitter.com/intent/tweet?url=[[URL]]',\n    'background-color': '#55acee',\n    src: `${IMG_BASE_URL}twitter.png`,\n  },\n  x: {\n    'share-url': 'https://twitter.com/intent/tweet?url=[[URL]]',\n    'background-color': '#000000',\n    src: `${IMG_BASE_URL}twitter-x.png`,\n  },\n  google: {\n    'share-url': 'https://plus.google.com/share?url=[[URL]]',\n    'background-color': '#dc4e41',\n    src: `${IMG_BASE_URL}google-plus.png`,\n  },\n  pinterest: {\n    'share-url':\n      'https://pinterest.com/pin/create/button/?url=[[URL]]&media=&description=',\n    'background-color': '#bd081c',\n    src: `${IMG_BASE_URL}pinterest.png`,\n  },\n  linkedin: {\n    'share-url':\n      'https://www.linkedin.com/shareArticle?mini=true&url=[[URL]]&title=&summary=&source=',\n    'background-color': '#0077b5',\n    src: `${IMG_BASE_URL}linkedin.png`,\n  },\n  instagram: {\n    'background-color': '#3f729b',\n    src: `${IMG_BASE_URL}instagram.png`,\n  },\n  web: {\n    src: `${IMG_BASE_URL}web.png`,\n    'background-color': '#4BADE9',\n  },\n  snapchat: {\n    src: `${IMG_BASE_URL}snapchat.png`,\n    'background-color': '#FFFA54',\n  },\n  youtube: {\n    src: `${IMG_BASE_URL}youtube.png`,\n    'background-color': '#EB3323',\n  },\n  tumblr: {\n    src: `${IMG_BASE_URL}tumblr.png`,\n    'share-url':\n      'https://www.tumblr.com/widgets/share/tool?canonicalUrl=[[URL]]',\n    'background-color': '#344356',\n  },\n  github: {\n    src: `${IMG_BASE_URL}github.png`,\n    'background-color': '#000000',\n  },\n  xing: {\n    src: `${IMG_BASE_URL}xing.png`,\n    'share-url': 'https://www.xing.com/app/user?op=share&url=[[URL]]',\n    'background-color': '#296366',\n  },\n  vimeo: {\n    src: `${IMG_BASE_URL}vimeo.png`,\n    'background-color': '#53B4E7',\n  },\n  medium: {\n    src: `${IMG_BASE_URL}medium.png`,\n    'background-color': '#000000',\n  },\n  soundcloud: {\n    src: `${IMG_BASE_URL}soundcloud.png`,\n    'background-color': '#EF7F31',\n  },\n  dribbble: {\n    src: `${IMG_BASE_URL}dribbble.png`,\n    'background-color': '#D95988',\n  },\n}\n\neach(defaultSocialNetworks, (val, key) => {\n  defaultSocialNetworks[`${key}-noshare`] = {\n    ...val,\n    'share-url': '[[URL]]',\n  }\n})\n\nexport default class MjSocialElement extends BodyComponent {\n  static componentName = 'mj-social-element'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    align: 'enum(left,center,right)',\n    'icon-position': 'enum(left,right)',\n    'background-color': 'color',\n    color: 'color',\n    'border-radius': 'unit(px)',\n    'font-family': 'string',\n    'font-size': 'unit(px)',\n    'font-style': 'string',\n    'font-weight': 'string',\n    href: 'string',\n    'icon-size': 'unit(px,%)',\n    'icon-height': 'unit(px,%)',\n    'icon-padding': 'unit(px,%){1,4}',\n    'line-height': 'unit(px,%,)',\n    name: 'string',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    'text-padding': 'unit(px,%){1,4}',\n    rel: 'string',\n    src: 'string',\n    srcset: 'string',\n    sizes: 'string',\n    alt: 'string',\n    title: 'string',\n    target: 'string',\n    'text-decoration': 'string',\n    'vertical-align': 'enum(top,middle,bottom)',\n  }\n\n  static defaultAttributes = {\n    alt: '',\n    align: 'left',\n    'icon-position': 'left',\n    color: '#000',\n    'border-radius': '3px',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'font-size': '13px',\n    'line-height': '1',\n    padding: '4px',\n    'text-padding': '4px 4px 4px 0',\n    target: '_blank',\n    'text-decoration': 'none',\n    'vertical-align': 'middle',\n  }\n\n  getStyles() {\n    const {\n      'icon-size': iconSize,\n      'icon-height': iconHeight,\n      'background-color': backgroundColor,\n    } = this.getSocialAttributes()\n\n    return {\n      td: {\n        padding: this.getAttribute('padding'),\n        'padding-top': this.getAttribute('padding-top'),\n        'padding-right': this.getAttribute('padding-right'),\n        'padding-bottom': this.getAttribute('padding-bottom'),\n        'padding-left': this.getAttribute('padding-left'),\n        'vertical-align': this.getAttribute('vertical-align'),\n      },\n      table: {\n        background: backgroundColor,\n        'border-radius': this.getAttribute('border-radius'),\n        width: iconSize,\n      },\n      icon: {\n        padding: this.getAttribute('icon-padding'),\n        'font-size': '0',\n        height: iconHeight || iconSize,\n        'vertical-align': 'middle',\n        width: iconSize,\n      },\n      img: {\n        'border-radius': this.getAttribute('border-radius'),\n        display: 'block',\n      },\n      tdText: {\n        'vertical-align': 'middle',\n        padding: this.getAttribute('text-padding'),\n        'text-align': this.getAttribute('align'),\n      },\n      text: {\n        color: this.getAttribute('color'),\n        'font-size': this.getAttribute('font-size'),\n        'font-weight': this.getAttribute('font-weight'),\n        'font-style': this.getAttribute('font-style'),\n        'font-family': this.getAttribute('font-family'),\n        'line-height': this.getAttribute('line-height'),\n        'text-decoration': this.getAttribute('text-decoration'),\n      },\n    }\n  }\n\n  getSocialAttributes() {\n    const socialNetwork = defaultSocialNetworks[this.getAttribute('name')] || {}\n    let href = this.getAttribute('href')\n\n    if (href && get(socialNetwork, 'share-url')) {\n      href = socialNetwork['share-url'].replace('[[URL]]', href)\n    }\n\n    const attrs = [\n      'icon-size',\n      'icon-height',\n      'srcset',\n      'sizes',\n      'src',\n      'background-color',\n    ].reduce(\n      (r, attr) => ({\n        ...r,\n        [attr]: this.getAttribute(attr) || socialNetwork[attr],\n      }),\n      {},\n    )\n\n    return {\n      href,\n      ...attrs,\n    }\n  }\n\n  render() {\n    const {\n      src,\n      srcset,\n      sizes,\n      href,\n      'icon-size': iconSize,\n    } = this.getSocialAttributes()\n\n    const hasLink = !!this.getAttribute('href')\n    const iconPosition = this.getAttribute('icon-position')\n\n    const makeIcon = () => `\n        <td ${this.htmlAttributes({ style: 'td' })}>\n          <table\n            ${this.htmlAttributes({\n              border: '0',\n              cellpadding: '0',\n              cellspacing: '0',\n              role: 'presentation',\n              style: 'table',\n            })}\n          >\n            <tbody>\n              <tr>\n                <td ${this.htmlAttributes({ style: 'icon' })}>\n                  ${\n                    hasLink\n                      ? `<a ${this.htmlAttributes({\n                          href,\n                          rel: this.getAttribute('rel'),\n                          target: this.getAttribute('target'),\n                        })}>`\n                      : ''\n                  }\n                    <img\n                      ${this.htmlAttributes({\n                        alt: this.getAttribute('alt'),\n                        title: this.getAttribute('title'),\n                        src,\n                        style: 'img',\n                        width: parseInt(iconSize, 10),\n                        sizes,\n                        srcset,\n                      })}\n                    />\n                  ${hasLink ? `</a>` : ''}\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </td>\n      `\n\n    const makeContent = () => `\n        ${\n          this.getContent()\n            ? `\n          <td ${this.htmlAttributes({ style: 'tdText' })}>\n            ${\n              hasLink\n                ? `<a\n                ${this.htmlAttributes({\n                  href,\n                  style: 'text',\n                  rel: this.getAttribute('rel'),\n                  target: this.getAttribute('target'),\n                })}>`\n                : `<span\n                    ${this.htmlAttributes({\n                      style: 'text',\n                    })}>`\n            }\n              ${this.getContent()}\n            ${hasLink ? `</a>` : '</span>'}\n          </td>\n          `\n            : ''\n        }\n      `\n\n    const renderLeft = () => `${makeIcon()} ${makeContent()}`\n    const renderRight = () => `${makeContent()} ${makeIcon()}`\n\n    return `\n      <tr\n        ${this.htmlAttributes({\n          class: this.getAttribute('css-class'),\n        })}\n      >\n        ${iconPosition === 'left' ? renderLeft() : renderRight()}\n      </tr>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-social/src/index.js",
    "content": "export { default as Social } from './Social'\nexport { default as SocialElement } from './SocialElement'\n"
  },
  {
    "path": "packages/mjml-spacer/README.md",
    "content": "### mj-spacer\n\nDisplays a blank space, that can be used to separate content.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-text>A first line of text</mj-text>\n        <mj-spacer height=\"50px\" />\n        <mj-text>A second line of text</mj-text>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n#### Attributes\n\n| attribute                  | accepts           | description                                        | default value |\n| -------------------------- | ----------------- | -------------------------------------------------- | ------------- |\n| container-background-color | CSS color formats | inner element background color                     |               |\n| css-class                  | string            | class name, added to the root HTML element created |               |\n| height                     | `px` `%`          | spacer height                                      | `0px`         |\n| padding                    | `px` `%`          | spacer padding, supports up to 4 parameters        |               |\n| padding-bottom             | `px` `%`          | bottom padding                                     |               |\n| padding-left               | `px` `%`          | left padding                                       |               |\n| padding-right              | `px` `%`          | right padding                                      |               |\n| padding-top                | `px` `%`          | top padding                                        |               |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/spacer\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-spacer/package.json",
    "content": "{\n  \"name\": \"mjml-spacer\",\n  \"description\": \"mjml-spacer\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-spacer\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-spacer/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjSpacer extends BodyComponent {\n  static componentName = 'mj-spacer'\n\n  static allowedAttributes = {\n    border: 'string',\n    'border-bottom': 'string',\n    'border-left': 'string',\n    'border-right': 'string',\n    'border-top': 'string',\n    'container-background-color': 'color',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    height: 'unit(px,%)',\n  }\n\n  static defaultAttributes = {\n    height: '20px',\n  }\n\n  getStyles() {\n    return {\n      div: {\n        height: this.getAttribute('height'),\n        'line-height': this.getAttribute('height'),\n      },\n    }\n  }\n\n  render() {\n    return `\n      <div\n        ${this.htmlAttributes({\n          style: 'div',\n        })}\n      >&#8202;</div>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-table/README.md",
    "content": "### mj-table\n\nDisplay a data table. It only accepts plain HTML.\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-section>\n      <mj-column>\n        <mj-table>\n          <tr style=\"border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;\">\n            <th style=\"padding: 0 15px 0 0;\">Year</th>\n            <th style=\"padding: 0 15px;\">Language</th>\n            <th style=\"padding: 0 0 0 15px;\">Inspired from</th>\n          </tr>\n          <tr>\n            <td style=\"padding: 0 15px 0 0;\">1995</td>\n            <td style=\"padding: 0 15px;\">PHP</td>\n            <td style=\"padding: 0 0 0 15px;\">C, Shell Unix</td>\n          </tr>\n          <tr>\n            <td style=\"padding: 0 15px 0 0;\">1995</td>\n            <td style=\"padding: 0 15px;\">JavaScript</td>\n            <td style=\"padding: 0 0 0 15px;\">Scheme, Self</td>\n          </tr>\n        </mj-table>\n      </mj-column>\n    </mj-section>\n  </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-table</code> is an \"ending tag\", which means that it can contain HTML code but it cannot contain other MJML components. Therefore, it will accept any tag you would add inside an HTML table tag.</p>\n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n#### Attributes\n\n| attribute                  | accepts                            | description                                        | default value                          |\n| -------------------------- | ---------------------------------- | -------------------------------------------------- | -------------------------------------- |\n| align                      | `left` `right` `center`            | table horizontal alignment                         | `left`                                 |\n| border                     | string                             | CSS border format                                  | `none`                                 |\n| cellpadding                | integer                            | space between cells                                | `0`                                    |\n| cellspacing                | integer                            | space between cell and border                      | `0`                                    |\n| color                      | CSS color formats                  | text header & footer color                         | `#000000`                              |\n| container-background-color | CSS color formats                  | inner element background color                     |                                        |\n| css-class                  | string                             | class name, added to the root HTML element created |                                        |\n| font-family                | string                             | font name                                          | `Ubuntu, Helvetica, Arial, sans-serif` |\n| font-size                  | `px`                               | font size                                          | `13px`                                 |\n| line-height                | `px` `%`                           | space between lines                                | `22px`                                 |\n| padding                    | `px` `%`                           | outer table padding, supports up to 4 parameters   | `10px 25px`                            |\n| padding-bottom             | `px` `%`                           | bottom padding                                     |                                        |\n| padding-left               | `px` `%`                           | left padding                                       |                                        |\n| padding-right              | `px` `%`                           | right padding                                      |                                        |\n| padding-top                | `px` `%`                           | top padding                                        |                                        |\n| role                       | `none` `presentation`              | specify the role attribute                         |                                        |\n| table-layout               | `auto` `fixed` `initial` `inherit` | sets the table layout                              | `auto`                                 |\n| width                      | `px` `%` `auto`                    | table width                                        | `100%`                                 |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/table\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-table/package.json",
    "content": "{\n  \"name\": \"mjml-table\",\n  \"description\": \"mjml-atable\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-table\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-table/src/index.js",
    "content": "import widthParser from 'mjml-core/lib/helpers/widthParser'\n\nimport { BodyComponent } from 'mjml-core'\nimport { reduce } from 'lodash'\n\nexport default class MjTable extends BodyComponent {\n  static componentName = 'mj-table'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    align: 'enum(left,right,center)',\n    border: 'string',\n    cellpadding: 'integer',\n    cellspacing: 'integer',\n    'container-background-color': 'color',\n    color: 'color',\n    'font-family': 'string',\n    'font-size': 'unit(px)',\n    'font-weight': 'string',\n    'line-height': 'unit(px,%,)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    role: 'enum(none,presentation)',\n    'table-layout': 'enum(auto,fixed,initial,inherit)',\n    'vertical-align': 'enum(top,bottom,middle)',\n    width: 'unit(px,%,auto)',\n  }\n\n  static defaultAttributes = {\n    align: 'left',\n    border: 'none',\n    cellpadding: '0',\n    cellspacing: '0',\n    color: '#000000',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'font-size': '13px',\n    'line-height': '22px',\n    padding: '10px 25px',\n    'table-layout': 'auto',\n    width: '100%',\n  }\n\n  getStyles() {\n    const hasCellspacing = this.hasCellspacing()\n    return {\n      table: {\n        color: this.getAttribute('color'),\n        'font-family': this.getAttribute('font-family'),\n        'font-size': this.getAttribute('font-size'),\n        'line-height': this.getAttribute('line-height'),\n        'table-layout': this.getAttribute('table-layout'),\n        width: this.getAttribute('width'),\n        border: this.getAttribute('border'),\n        ...(hasCellspacing && { 'border-collapse': 'separate' }),\n      },\n    }\n  }\n\n  getWidth() {\n    const width = this.getAttribute('width')\n\n    if (width === 'auto') {\n      return width\n    }\n\n    const { parsedWidth, unit } = widthParser(width)\n    return unit === '%' ? width : parsedWidth\n  }\n\n  hasCellspacing() {\n    const cellspacing = this.getAttribute('cellspacing')\n    const numericValue = parseFloat(String(cellspacing).replace(/[^\\d.]/g, ''))\n    return !Number.isNaN(numericValue) && numericValue > 0\n  }\n\n  render() {\n    const tableAttributes = reduce(\n      ['cellpadding', 'cellspacing', 'role'],\n      (acc, v) => ({\n        ...acc,\n        [v]: this.getAttribute(v),\n      }),\n      {},\n    )\n\n    return `\n      <table\n        ${this.htmlAttributes({\n          ...tableAttributes,\n          width: this.getWidth(),\n          border: '0',\n          style: 'table',\n        })}\n      >\n        ${this.getContent()}\n      </table>\n    `\n  }\n}\n"
  },
  {
    "path": "packages/mjml-text/README.md",
    "content": "### mj-text\n\nDisplays text which can be styled.\n\n```xml\n<mjml>\n <mj-body>\n   <mj-section>\n     <mj-column>\n       <mj-text font-family=\"Helvetica\" color=\"#F45E43\">\n         <h1>Title</h1>\n         <p>Paragraph</p>\n         <p style=\"font-family:Comic Sans Ms\">Another paragraph</p>\n       </mj-text>\n     </mj-column>\n   </mj-section>\n </mj-body>\n</mjml>\n```\n\n<div class=\"alert alert-note\" role=\"alert\">\n  <p>Note</p>\n  <p><code>mj-text</code> is an \"ending tag\", which means that it can contain HTML code  but it cannot contain other MJML components.</p> \n  <p>More information about ending tags <a href=\"#ending-tags\">in this section</a>.</p>\n</div>\n\n#### Attributes\n\n| attribute                  | accepts                           | description                                                  | default value                          |\n| -------------------------- | --------------------------------- | ------------------------------------------------------------ | -------------------------------------- |\n| align                      | `left` `right` `center` `justify` | text-alignment                                               | `left`                                 |\n| color                      | CSS color formats                 | text color                                                   | `#000000`                              |\n| container-background-color | CSS color formats                 | inner element background color                               |                                        |\n| css-class                  | string                            | class name, added to the root HTML element created           |                                        |\n| font-family                | string                            | font                                                         | `Ubuntu, Helvetica, Arial, sans-serif` |\n| font-size                  | `px`                              | text size                                                    | `13px`                                 |\n| font-style                 | string                            | CSS values, e.g. `normal` `italic` `oblique`                 |                                        |\n| font-weight                | string                            | text thickness                                               |                                        |\n| height                     | `px`                              | height of the element                                        |                                        |\n| letter-spacing             | `px` `em`                         | letter spacing                                               |                                        |\n| line-height                | `px` `%`                          | space between the lines                                      | `1`                                    |\n| padding                    | `px` `%`                          | text padding, supports up to 4 parameters                    | `10px 25px`                            |\n| padding-bottom             | `px` `%`                          | bottom offset                                                |                                        |\n| padding-left               | `px` `%`                          | left offset                                                  |                                        |\n| padding-right              | `px` `%`                          | right offset                                                 |                                        |\n| padding-top                | `px` `%`                          | top offset                                                   |                                        |\n| text-decoration            | string                            | CSS values, e.g. `underline` `overline` `none`               |\n| text-transform             | string                            | CSS values, i.e. `capitalize` `uppercase` `lowercase` `none` |                                        |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/text\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-text/package.json",
    "content": "{\n  \"name\": \"mjml-text\",\n  \"description\": \"mjml-text\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-text\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-text/src/index.js",
    "content": "import { BodyComponent } from 'mjml-core'\n\nimport conditionalTag from 'mjml-core/lib/helpers/conditionalTag'\n\nexport default class MjText extends BodyComponent {\n  static componentName = 'mj-text'\n\n  static endingTag = true\n\n  static allowedAttributes = {\n    align: 'enum(left,right,center,justify)',\n    'background-color': 'color',\n    color: 'color',\n    'container-background-color': 'color',\n    'font-family': 'string',\n    'font-size': 'unit(px)',\n    'font-style': 'string',\n    'font-weight': 'string',\n    height: 'unit(px,%)',\n    'letter-spacing': 'unitWithNegative(px,em)',\n    'line-height': 'unit(px,%,)',\n    'padding-bottom': 'unit(px,%)',\n    'padding-left': 'unit(px,%)',\n    'padding-right': 'unit(px,%)',\n    'padding-top': 'unit(px,%)',\n    padding: 'unit(px,%){1,4}',\n    'text-decoration': 'string',\n    'text-transform': 'string',\n    'vertical-align': 'enum(top,bottom,middle)',\n  }\n\n  static defaultAttributes = {\n    align: 'left',\n    color: '#000000',\n    'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',\n    'font-size': '13px',\n    'line-height': '1',\n    padding: '10px 25px',\n  }\n\n  getStyles() {\n    return {\n      text: {\n        'font-family': this.getAttribute('font-family'),\n        'font-size': this.getAttribute('font-size'),\n        'font-style': this.getAttribute('font-style'),\n        'font-weight': this.getAttribute('font-weight'),\n        'letter-spacing': this.getAttribute('letter-spacing'),\n        'line-height': this.getAttribute('line-height'),\n        'text-align': this.getAttribute('align'),\n        'text-decoration': this.getAttribute('text-decoration'),\n        'text-transform': this.getAttribute('text-transform'),\n        color: this.getAttribute('color'),\n        height: this.getAttribute('height'),\n      },\n    }\n  }\n\n  renderContent() {\n    return `\n      <div\n        ${this.htmlAttributes({\n          style: 'text',\n        })}\n      >${this.getContent()}</div>\n    `\n  }\n\n  render() {\n    const height = this.getAttribute('height')\n\n    return height\n      ? `\n        ${conditionalTag(`\n          <table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td height=\"${height}\" style=\"vertical-align:top;height:${height};\">\n        `)}\n        ${this.renderContent()}\n        ${conditionalTag(`\n          </td></tr></table>\n        `)}\n      `\n      : this.renderContent()\n  }\n}\n"
  },
  {
    "path": "packages/mjml-validator/README.md",
    "content": "## Validating MJML\n\nMJML provides a validation layer that helps you building your email. It can detect if you misplaced or mispelled a MJML component, or if you used any unauthorised attribute on a specific component. It supports 3 levels of validation:\n\n- `skip`: your document is rendered without going through validation\n- `soft`: your document will go through validation and is rendered, even if it has errors\n- `strict`: your document is going through validation and is not rendered if it has any error\n\nBy default, the level is set to `soft`.\n\n### In CLI\n\nWhen using the `mjml` command line, you can add the option `-c.validationLevel` or `--config.validationLevel` with the validation level you want.\n\n> Set the validation level to `skip` (so that the file is not validated) and render the file\n\n```bash\nmjml --config.validationLevel=skip template.mjml\n```\n\nAlternatively, you can validate the file without rendering by adding the `--validate` option\n\n```bash\nmjml --validate template.mjml\n```\n\n### In Javascript\n\nIn Javascript, you can provide the level through the `options` parameters on `mjml2html`. E.g. `mjml2html(inputMJML, { validationLevel: 'strict' })`\n\nSetting to `strict` will raise a `MJMLValidationError` exception. This object has 2 methods:\n\n- `getErrors` returns an array of objects with `line`, `message`, `tagName` as well as a `formattedMessage` which contains the `line`, `message` and `tagName` concatenated in a sentence.\n- `getMessages` returns an array of `formattedMessage`.\n\nWhen setting to `soft`, no exception will be raised. You can get the errors in the object returned by `mjml2html`. It is the same object returned by `getErrors` on strict mode.\n"
  },
  {
    "path": "packages/mjml-validator/package.json",
    "content": "{\n  \"name\": \"mjml-validator\",\n  \"description\": \"mjml-validator\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-validator\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/MJMLRulesCollection.js",
    "content": "import validAttributes from './rules/validAttributes'\nimport validChildren from './rules/validChildren'\nimport validTag from './rules/validTag'\nimport validTypes from './rules/validTypes'\nimport errorAttr from './rules/errorAttr'\n\nconst MJMLRulesCollection = {\n  validAttributes,\n  validChildren,\n  validTag,\n  validTypes,\n  errorAttr,\n}\n\nexport function registerRule(rule, name) {\n  if (typeof rule !== 'function') {\n    return console.error('Your rule must be a function')\n  }\n\n  if (name) {\n    MJMLRulesCollection[name] = rule\n  } else {\n    MJMLRulesCollection[rule.name] = rule\n  }\n\n  return true\n}\n\nexport default MJMLRulesCollection\n"
  },
  {
    "path": "packages/mjml-validator/src/dependencies.js",
    "content": "export const assignDependencies = (target, ...sources) => {\n  if (sources.length === 0) {\n    return target\n  }\n\n  for (const source of sources) {\n    if (typeof source === 'object' && source !== null) {\n      for (const tag of Object.keys(source)) {\n        if (typeof tag === 'string') {\n          const list = []\n          if (target[tag]) {\n            list.push(...target[tag])\n          }\n          if (source[tag]) {\n            list.push(...source[tag])\n          }\n          target[tag] = Array.from(new Set(list))\n        } else {\n          console.warn('dependency \"tag\" must be of type string')\n        }\n      }\n    } else {\n      console.warn('\"dependencies\" must be an object.')\n    }\n  }\n  return target\n}\n\nconst dependencies = {}\n\nexport const registerDependencies = (dep) => {\n  assignDependencies(dependencies, dep)\n}\n\nexport default dependencies\n"
  },
  {
    "path": "packages/mjml-validator/src/index.js",
    "content": "import ruleError from './rules/ruleError'\nimport rulesCollection, { registerRule } from './MJMLRulesCollection'\nimport dependencies, {\n  registerDependencies,\n  assignDependencies,\n} from './dependencies'\n\nconst SKIP_ELEMENTS = ['mjml']\n\nexport const formatValidationError = ruleError\n\nexport { rulesCollection, registerRule }\n\nexport { dependencies, registerDependencies, assignDependencies }\n\nexport default function MJMLValidator(element, options = {}) {\n  const { children, tagName } = element\n  const errors = []\n\n  const skipElements = options.skipElements || SKIP_ELEMENTS\n\n  if (options.dependencies == null) {\n    console.warn('\"dependencies\" option should be provided to mjml validator')\n  }\n\n  if (!skipElements.includes(tagName)) {\n    for (const rule of Object.values(rulesCollection)) {\n      const ruleError = rule(element, {\n        dependencies,\n        skipElements,\n        ...options,\n      })\n      if (Array.isArray(ruleError)) {\n        errors.push(...ruleError)\n      } else if (ruleError) {\n        errors.push(ruleError)\n      }\n    }\n  }\n\n  if (children && children.length > 0) {\n    for (const child of children) {\n      errors.push(...MJMLValidator(child, options))\n    }\n  }\n\n  return errors\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/rules/errorAttr.js",
    "content": "import ruleError from './ruleError'\n\nexport default function errorAttr(element) {\n  const { errors } = element\n\n  if (!errors) return null\n\n  return errors.map((error) => {\n    switch (error.type) {\n      case 'include': {\n        const { file, partialPath } = error.params\n\n        return ruleError(\n          `mj-include fails to read file : ${file} at ${partialPath}`,\n          element,\n        )\n      }\n      default:\n        return null\n    }\n  })\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/rules/ruleError.js",
    "content": "function formatInclude(element) {\n  const { includedIn } = element\n  if (!(includedIn && includedIn.length)) return ''\n\n  const formattedIncluded = includedIn\n    .slice()\n    .reverse()\n    .map(({ line, file }) => `line ${line} of file ${file}`)\n    .join(', itself included at ')\n\n  return `, included at ${formattedIncluded}`\n}\n\nexport default function ruleError(message, element) {\n  const { line, tagName, absoluteFilePath } = element\n\n  return {\n    line,\n    message,\n    tagName,\n    formattedMessage: `Line ${line} of ${absoluteFilePath}${formatInclude(\n      element,\n    )} (${tagName}) — ${message}`,\n  }\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/rules/validAttributes.js",
    "content": "import ruleError from './ruleError'\n\nconst WHITELIST = ['mj-class', 'css-class']\n\nexport default function validateAttribute(element, { components }) {\n  const { attributes, tagName } = element\n\n  const Component = components[tagName]\n\n  if (!Component) {\n    return null\n  }\n\n  const availableAttributes = [\n    ...Object.keys(Component.allowedAttributes || {}),\n    ...WHITELIST,\n  ]\n  const unknownAttributes = Object.keys(attributes || {}).filter(\n    (attribute) => !availableAttributes.includes(attribute),\n  )\n\n  if (unknownAttributes.length === 0) {\n    return null\n  }\n\n  const { attribute, illegal } = {\n    attribute: unknownAttributes.length > 1 ? 'Attributes' : 'Attribute',\n    illegal: unknownAttributes.length > 1 ? 'are illegal' : 'is illegal',\n  }\n\n  return ruleError(\n    `${attribute} ${unknownAttributes.join(', ')} ${illegal}`,\n    element,\n  )\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/rules/validChildren.js",
    "content": "import ruleError from './ruleError'\n\nexport default function validChildren(\n  element,\n  { components, dependencies, skipElements },\n) {\n  const { children, tagName } = element\n\n  const Component = components[tagName]\n\n  if (!Component || !children || !children.length) {\n    return null\n  }\n\n  const errors = []\n\n  for (const child of children) {\n    const childTagName = child.tagName\n    const ChildComponent = components[childTagName]\n    const parentDependencies = dependencies[tagName] || []\n\n    const childIsValid =\n      !ChildComponent ||\n      skipElements.includes(childTagName) ||\n      parentDependencies.includes(childTagName) ||\n      parentDependencies.some(\n        (dep) => dep instanceof RegExp && dep.test(childTagName),\n      )\n\n    if (childIsValid === false) {\n      const allowedDependencies = Object.keys(dependencies).filter(\n        (key) =>\n          dependencies[key].includes(childTagName) ||\n          dependencies[key].some(\n            (dep) => dep instanceof RegExp && dep.test(childTagName),\n          ),\n      )\n\n      errors.push(\n        ruleError(\n          `${childTagName} cannot be used inside ${tagName}, only inside: ${allowedDependencies.join(\n            ', ',\n          )}`,\n          child,\n        ),\n      )\n    }\n  }\n\n  return errors\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/rules/validTag.js",
    "content": "import ruleError from './ruleError'\n\n// Tags that have no associated components but are allowed even so\nconst componentLessTags = [\n  'mj-all',\n  'mj-class',\n  'mj-selector',\n  'mj-html-attribute',\n]\n\nexport default function validateTag(element, { components }) {\n  const { tagName } = element\n\n  if (componentLessTags.includes(tagName)) return null\n\n  const Component = components[tagName]\n\n  if (!Component) {\n    return ruleError(\n      `Element ${tagName} doesn't exist or is not registered`,\n      element,\n    )\n  }\n\n  return null\n}\n"
  },
  {
    "path": "packages/mjml-validator/src/rules/validTypes.js",
    "content": "import ruleError from './ruleError'\n\nexport default function validateType(element, { components, initializeType }) {\n  const { attributes, tagName } = element\n\n  const Component = components[tagName]\n\n  if (!Component) {\n    return null\n  }\n\n  const errors = []\n\n  for (const [attr, value] of Object.entries(attributes || {})) {\n    const attrType =\n      Component.allowedAttributes && Component.allowedAttributes[attr]\n    if (attrType) {\n      const TypeChecker = initializeType(attrType)\n      const result = new TypeChecker(value)\n      if (result.isValid() === false) {\n        errors.push(\n          ruleError(`Attribute ${attr} ${result.getErrorMessage()}`, element),\n        )\n      }\n    }\n  }\n\n  return errors\n}\n"
  },
  {
    "path": "packages/mjml-wrapper/README.md",
    "content": "### mj-wrapper\n\nEnables you to wrap multiple `mj-section` tags together. It's especially useful to achieve nested layouts with shared border or background images across sections.\n\n<figure>\n  <img src=\"https://static.mailjet.com/mjml-website/documentation/wrapper-example.png\" alt=\"wrapper\" />\n</figure>\n\n```xml\n<mjml>\n  <mj-body>\n    <mj-wrapper border=\"1px solid #000000\" padding=\"50px 30px\">\n      <mj-section border-top=\"1px solid #aaaaaa\" border-left=\"1px solid #aaaaaa\" border-right=\"1px solid #aaaaaa\" padding=\"20px\">\n        <mj-column>\n          <mj-image padding=\"0\" src=\"https://placeholdit.imgix.net/~text?&w=350&h=150\" />\n        </mj-column>\n      </mj-section>\n      <mj-section border-left=\"1px solid #aaaaaa\" border-right=\"1px solid #aaaaaa\" padding=\"20px\" border-bottom=\"1px solid #aaaaaa\">\n        <mj-column border=\"1px solid #dddddd\">\n          <mj-text padding=\"20px\"> First line of text </mj-text>\n          <mj-divider border-width=\"1px\" border-style=\"dashed\" border-color=\"lightgrey\" padding=\"0 20px\" />\n          <mj-text padding=\"20px\"> Second line of text </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-wrapper>\n  </mj-body>\n</mjml>\n```\n\nThe `full-width` attribute will be used to manage the background width.\nSetting it will change the width of the section from the default 600px to 100%.\n\n<div class=\"alert alert-important\" role=\"alert\">\n  <p>Important</p>\n  <p>When applying <code>full-width</code> to <code>mj-wrapper</code>, any <code>mj-section</code> tags which are also set to <code>full-width</code> will default to standard width.</p>\n</div>\n\n<div class=\"alert alert-caution\" role=\"alert\">\n  <p>Caution</p>\n  <p>If you're using the <code>background-url</code> attribute for <code>mj-wrapper</code> then do not add one into a child <code>mj-section</code> as this is not supported for Outlook desktop</p>\n  <p>Also, if you’re using the <code>background-color</code> attribute for <code>mj-wrapper</code> and the <code>background-url</code> attribute on its <code>mj-section</code> or <code>mj-hero</code> children, the <code>background-color</code> will appear over the <code>background-image</code> on Outlook desktop.</p>\n</div>\n\n#### Attributes\n\n| attribute             | accepts                 | description                                                                                            | default value |\n| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------ | ------------- |\n| background-color      | CSS color formats       | section color                                                                                          |               |\n| background-position   | string                  | CSS values, i.e. `left` `center` `right` + `top` `center` `bottom` <br>(see outlook limitations below) | `top center`  |\n| background-position-x | string                  | CSS values, i.e. `left` `center` `right` <br>(see outlook limitations below)                           |               |\n| background-position-y | string                  | CSS values, i.e. `top` `center` `bottom` <br>(see outlook limitations below)                           |               |\n| background-repeat     | `repeat` `no-repeat`    | set the background image to repeat                                                                     |               |\n| background-size       | string                  | CSS values e.g. `auto` `cover` `contain` `px` `%` size                                                 | `auto`        |\n| background-url        | string                  | background image, in URL format                                                                        |               |\n| border                | string                  | CSS border format                                                                                      |               |\n| border-bottom         | string                  | CSS border format                                                                                      |               |\n| border-left           | string                  | CSS border format                                                                                      |               |\n| border-radius         | string                  | border radius                                                                                          |               |\n| border-right          | string                  | CSS border format                                                                                      |               |\n| border-top            | string                  | CSS border format                                                                                      |               |\n| css-class             | string                  | class name, added to the root HTML element created                                                     |               |\n| full-width            | `full-width` `false`    | make the section full-width                                                                            |               |\n| gap                   | `px`                    | applies a vertical gap between child `mj-section` instances                                            |               |\n| padding               | `px` `%`                | section padding, supports up to 4 parameters                                                           | `20px 0`      |\n| padding-bottom        | `px` `%`                | section bottom padding                                                                                 |               |\n| padding-left          | `px` `%`                | section left padding                                                                                   |               |\n| padding-right         | `px` `%`                | section right padding                                                                                  |               |\n| padding-top           | `px` `%`                | section top padding                                                                                    |               |\n| text-align            | `left` `center` `right` | CSS text-align                                                                                         | `center`      |\n\n<p class=\"cta-container\"><a class=\"cta\" href=\"https://mjml.io/try-it-live/components/wrapper\">Try it live</a></p>\n"
  },
  {
    "path": "packages/mjml-wrapper/package.json",
    "content": "{\n  \"name\": \"mjml-wrapper\",\n  \"description\": \"mjml-wrapper\",\n  \"version\": \"4.18.0\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mjmlio/mjml.git\",\n    \"directory\": \"packages/mjml-wrapper\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mjmlio/mjml/issues\"\n  },\n  \"homepage\": \"https://mjml.io\",\n  \"scripts\": {\n    \"clean\": \"rimraf lib\",\n    \"build\": \"babel src --out-dir lib --root-mode upward\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mjml-core\": \"4.18.0\",\n    \"mjml-section\": \"4.18.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.4\",\n    \"rimraf\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/mjml-wrapper/src/index.js",
    "content": "import MjSection from 'mjml-section'\nimport { suffixCssClasses } from 'mjml-core'\n\nexport default class MjWrapper extends MjSection {\n  static componentName = 'mj-wrapper'\n\n  static allowedAttributes = {\n    ...MjSection.allowedAttributes,\n    gap: 'unit(px)',\n  }\n\n  renderWrappedChildren() {\n    const { children } = this.props\n    const { containerWidth } = this.context\n\n    return `\n      ${this.renderChildren(children, {\n        renderer: (component) =>\n          component.constructor.isRawElement()\n            ? component.render()\n            : `\n          <!--[if mso | IE]>\n            <tr>\n              <td\n                ${component.htmlAttributes({\n                  align: component.getAttribute('align'),\n                  class: suffixCssClasses(\n                    component.getAttribute('css-class'),\n                    'outlook',\n                  ),\n                  width: containerWidth,\n                })}\n              >\n          <![endif]-->\n            ${component.render()}\n          <!--[if mso | IE]>\n              </td>\n            </tr>\n          <![endif]-->\n        `,\n      })}\n    `\n  }\n}\n"
  },
  {
    "path": "readme-ja.md",
    "content": "# MJML 4\n\nもしも、MJML 3.3.Xについて探しているのであれば、[このブランチ](https://github.com/mjmlio/mjml/tree/3.3.x)をご確認ください。\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://mjml.io\" target=\"_blank\">\n    <img width=\"250\"src=\"https://mjml.io/assets/img/litmus/mjmlbymailjet.png\">\n\n  </a>\n</p>\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://github.com/mjmlio/mjml/actions\">\n    <img src=\"https://github.com/mjmlio/mjml/workflows/Mjml%20CI/badge.svg?branch=master\" alt=\"github actions\">\n  </a>\n  <a href=\"https://www.codacy.com/app/gbadi/mjml\">\n    <img src=\"https://api.codacy.com/project/badge/grade/575339cb861f4ff4b0dbb3f9e1759c35\"/>\n  </a>\n</p>\n\n\n<p style=\"text-align: center;\" >\n  | <b><a href=\"#translated-documentation\">翻訳されたドキュメント</a></b>\n  | <b><a href=\"#introduction\">紹介</a></b>\n  | <b><a href=\"#installation\">インストール</a></b>\n  | <b><a href=\"#usage\">使い方</a></b>\n  | <b><a href=\"#contribute\">貢献</a></b> |\n</p>\n\n---\n# 翻訳されたドキュメント\n\n| 言語 | ドキュメントのリンク |\n| :-: | :-: |\n| 日本語 | [日本語ドキュメント](https://github.com/mjmlio/mjml/blob/master/readme-ja.md) |\n\n# 紹介\n\n`MJML`は[Mailjet](https://www.mailjet.com/)によって作成されたマークアップ言語で、レスポンシブemailをコーディングする際に生じる負担を軽減する設計がされています。そのセマンティックな構文は言語を簡単完結にし、その豊富な標準コンポーネントライブラリはあなたの開発時間とコードベースを短縮するでしょう。MJMLのオープンソースエンジンは、あなたの書いたMJMLをレスポンシブHTMLに変換します。\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://mjml.io\" target=\"_blank\">\n    <img width=\"75%\" src=\"https://cloud.githubusercontent.com/assets/6558790/12450760/ee034178-bf85-11e5-9dda-98d0c8f9f8d6.png\">\n  </a>\n</p>\n\n\n# インストール\n\n`MJML`は`NPM`と一緒にインストールすることで、NodeJSやCommand Line Interfaceから使用できます。これらについてわからないの場合は、<a href=\"#使い方\">使い方</a>から他の方法をご確認ください。\n\n```bash\nnpm install mjml\n```\n\n# 開発\n\nMJMLに変更を加えたり、マージリクエストを提出するといった作業をする場合は、[yarn](https://yarnpkg.com/lang/en/docs/install/)をダウンロードして、インストールすることで簡単に開発できるようにしましょう。\n\n```bash\ngit clone https://github.com/mjmlio/mjml.git && cd mjml\nyarn\nyarn build\n```\n\n`yarn build:watch`を実行することで、コードを書きながらパッケージを再構築することもできます。\n\n# 使い方\n\n## オンライン\n\n何もインストールしたくないですか？それならば、無料のオンラインエディターを使いましょう！\n\n<p style=\"text-align: center;\" >\n  <a href=\"https://mjml.io/try-it-live\" target=\"_blank\"><img src=\"https://cloud.githubusercontent.com/assets/6558790/12195421/58a40618-b5f7-11e5-9ed3-80463874ab14.png\" alt=\"try it live\" width=\"75%\"></a>\n</p>\n<br>\n\n## アプリケーションとプラグイン\n\nMJMLにはツールやプラグインといったエコシステムが備わっています。以下をご確認ください:\n- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJMLが含まれています)\n- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJMLが含まれています)\n- [Atom plugin](https://atom.io/users/mjmlio) (MJMLを別途インストールする必要があります)\n- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJMLを別途インストールする必要があります)\n\nその他のツールについては[コミュニティ](https://mjml.io/community)ページをご覧ください。\n\n## Command line interface\n\n> ファイルをコンパイルし、HTMLを`output.html`として出力します。\n\n```bash\nmjml input.mjml -o output.html\n```\n\n任意の`引数`をCLIに渡すことができます。これらは複数合わせて渡すこともできます。\n\n引数 | 説明 | 初期値\n---------|--------|--------------\n`mjml -m [input]` | v3のMJMLファイルをv4の構文にマイグレートする | NA\n`mjml [input] -o [output]` | 出力を[output]に書き込みます | NA\n`mjml [input] -s` | 出力を`stdout`に書き込みます | NA\n`mjml -w [input]` | `[input]`（ファイルまたはフォルダー）の変更を監視します | NA\n`mjml [input] --config.beautify` | 出力を整えます(`true`または`false`) | true\n`mjml [input] --config.minify` | 出力をminify化します(`true`または`false`) | false\n\n設定オプションの詳細については[mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md)をご覧ください。\n\n## Node.js\n\n```javascript\nimport mjml2html from 'mjml'\n\n/*\n  mjml文字列をコンパイルする\n*/\nconst htmlOutput = mjml2html(`\n  <mjml>\n    <mj-body>\n      <mj-section>\n        <mj-column>\n          <mj-text>\n            Hello World!\n          </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-body>\n  </mjml>\n`, options)\n\n\n/*\n  生成されたレスポンシブHTMLとMJMLのエラーがあれば表示します\n*/\nconsole.log(htmlOutput)\n```\n\n任意でオブジェクト形式の`オプション`を`mjml2html`関数に渡すことができます:\n\nオプション   | 型   | 説明  | 初期値\n-------------|--------|--------------|---------------\nfonts  | object | 初期フォントをインポートしたHTMLを描画する | 初期フォントについては[index.js](https://github.com/mjmlio/mjml/blob/master/packages/mjml-core/src/index.js#L100-L108)をご覧ください。\nkeepComments | boolean | 出力されるHTMLにコメントを残すオプション | true\nignoreIncludes | boolean | mj-includesを無視するオプション | false\nbeautify | boolean | 出力されるHTMLを整えるオプション | false\nminify | boolean | 出力されるHTMLをminify化するオプション | false\nvalidationLevel | string | [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml)で利用する値: 'strict', 'soft', 'skip'  | 'soft'\nfilePath | string | mj-includesの相対パスに使われるファイルパス | '.'\npreprocessors | array of functions | xmlのパース前に適用するプリプロセッサー。入力はjsonではなく、必ずxmlでなければなりません。関数の場合は必ず (xml: string) => string としなければなりません。 | []\njuicePreserveTags | cssをインライン化する際にタグを保持する。詳しくは[mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md)をご覧ください。 | NA\nminifyOptions | htmlのminify化に関するオプション。詳しくは[mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md)をご覧ください。 | NA\nmjmlConfigPath | string | `.mjmlconfig`ファイルのパスもしくはディレクトリー(カスタムコンポーネントの場合) | `process.cwd()`\nuseMjmlConfigOptions | `.mjmlconfig`ファイルで`options`属性の使用を許可する | false\n\n## クライアントサイド (ブラウザー)\n\n```javascript\nvar mjml2html = require('mjml-browser')\n\n/*\n  mjml文字列をコンパイルする\n*/\nvar htmlOutput = mjml2html(`\n  <mjml>\n    <mj-body>\n      <mj-section>\n        <mj-column>\n          <mj-text>\n            Hello World!\n          </mj-text>\n        </mj-column>\n      </mj-section>\n    </mj-body>\n  </mjml>\n`, options)\n\n\n/*\n  生成されたレスポンシブHTMLとMJMLのエラーがあれば表示します\n*/\nconsole.log(htmlOutput)\n```\n\n## API\n\n無料のMJML APIを利用することで、あなたのアプリケーションにMJMLを簡単に統合できます。\nAPIの詳細については[ここ](https://mjml.io/api)をご覧ください。\n\n# MJML Slack\n\nMJMLはその素晴らしいコミュニティなくしてはここまで良いものにならなかったでしょう。[コミュニティ Slack](https://join.slack.com/t/mjml/shared_invite/zt-gqmwfwmr-kPBnfuuB7wof5httaTcXxg)から、MJML'er達に会いにいきましょう。\n\n# 貢献者\n\n- [Maxime](https://github.com/iRyusa)\n- [Nicolas](https://github.com/ngarnier)\n- [Cedric](https://github.com/kmcb777)\n- [Loeck](https://github.com/lohek)\n- [Robin](https://github.com/robink)\n- [Guillaume](https://github.com/GuillaumeBadi)\n- [Meriadec](https://github.com/meriadec)\n- [Arnaud](https://github.com/arnaudbreton)\n- [HTeuMeuLeu](https://github.com/hteumeuleu)\n- [Emmanuel Payet](https://github.com/epayet)\n- [Matthieu](https://github.com/swibge)\n- [Rogier](https://github.com/rogierslag)\n"
  },
  {
    "path": "test.js",
    "content": "require('@babel/register')\n\nconst mjml2html = require('./packages/mjml/src/index')\n\nconst xml = `\n<mjml>\n    <mj-head>\n        <mj-attributes>\n            <mj-all\n                padding=\"0px\"\n            />\n            <mj-wrapper\n                background-color=\"yellow\"\n                padding=\"80px\"\n            />\n        </mj-attributes>\n    </mj-head>\n    <mj-body>\n        <mj-wrapper>\n            <mj-section>\n                <mj-column>\n                    <mj-text>\n                        lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem\n                    </mj-text>\n                </mj-column>\n            </mj-section>\n        </mj-wrapper>\n    </mj-body>\n</mjml>\n`\n\nconsole.time('mjml2html')\n\nconst { html } = mjml2html(xml, {\n  beautify: true,\n})\n\nconsole.timeEnd('mjml2html')\n\nif (process.argv.includes('--output')) {\n  console.log(html)\n}\n\nif (process.argv.includes('--open')) {\n  const open = require('open')\n  const path = require('path')\n  const fs = require('fs')\n\n  const testFile = path.resolve(__dirname, './test.html')\n\n  fs.writeFileSync(testFile, html)\n\n  open(testFile)\n}\n"
  },
  {
    "path": "type.js",
    "content": "const types = require('./packages/mjml-core/lib/types/type.js')\n\nconst enumtype = types.initializeType('enum(top,left,center)')\nconst colortype = types.initializeType('color')\nconst booleantype = types.initializeType('boolean')\nconst unittype = types.initializeType('unit(px,%){1,3}')\nconst stringtype = types.initializeType('string')\n\nconsole.log(stringtype)\n\nconst output = (t) => { console.log(`Type: ${t.constructor.name} — Value: ${t.value} — isValid: ${t.isValid()} ${t.getErrorMessage()}`) }\n\n[new colortype('grey'),\n  new colortype('rgba(0,255,3,0.3)'),\n  new colortype('#DDF'),\n  new colortype('#DF'),\n  new booleantype('true'),\n  new booleantype('false'),\n  new booleantype('banana'),\n  new unittype('10 20px 20'),\n  new unittype('10px 20px 20px'),\n  new unittype('10px'),\n  new unittype('10%'),\n  new unittype('10px 10px'),\n  new unittype('0'),\n  new stringtype('hello world'),\n].map(output)\n\n"
  }
]