Showing preview only (520K chars total). Download the full file or copy to clipboard to get everything.
Repository: mjmlio/mjml
Branch: master
Commit: 4cb9615c5e48
Files: 230
Total size: 467.7 KB
Directory structure:
gitextract_vim4znp0/
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── build-documentation.yml
│ ├── mjml-workflow.yml
│ └── npm-publish.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── babel.config.js
├── doc/
│ ├── basic.md
│ ├── body_components.md
│ ├── community-components.md
│ ├── community-contributions.md
│ ├── components_1.md
│ ├── components_2.md
│ ├── config.json
│ ├── create.md
│ ├── ending-tags.md
│ ├── getting_started.md
│ ├── guide.md
│ ├── head_components.md
│ ├── install.md
│ ├── mjml-bar-chart.md
│ ├── mjml-chart.md
│ ├── mjml-chartjs.md
│ ├── mjml-mso-button.md
│ ├── mjml-qr-code.md
│ ├── ports.md
│ ├── tooling.md
│ └── using_mjml_in_json.md
├── lerna.json
├── package.json
├── packages/
│ ├── mjml/
│ │ ├── README.md
│ │ ├── bin/
│ │ │ └── mjml
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.js
│ │ └── test/
│ │ ├── accordion-fontFamily.test.js
│ │ ├── accordion-padding.test.js
│ │ ├── accordionTitle-fontWeight.test.js
│ │ ├── carousel-hoverSupported.test.js
│ │ ├── column-border-radius.test.js
│ │ ├── html-attributes.test.js
│ │ ├── html-comments.test.js
│ │ ├── lazy-head-style.test.js
│ │ ├── navbar-ico-padding.test.js
│ │ ├── social-align.test.js
│ │ ├── social-icon-height.test.js
│ │ ├── table-cellspacing.test.js
│ │ ├── tableWidth.test.js
│ │ ├── wrapper-border-radius.test.js
│ │ └── wrapper-gap.test.js
│ ├── mjml-accordion/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── Accordion.js
│ │ ├── AccordionElement.js
│ │ ├── AccordionText.js
│ │ ├── AccordionTitle.js
│ │ └── index.js
│ ├── mjml-body/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-browser/
│ │ ├── README.md
│ │ ├── browser-mocks/
│ │ │ ├── fs.js
│ │ │ ├── path.js
│ │ │ └── uglify-js.js
│ │ ├── package.json
│ │ └── webpack.config.js
│ ├── mjml-button/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-carousel/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── Carousel.js
│ │ ├── CarouselImage.js
│ │ └── index.js
│ ├── mjml-cli/
│ │ ├── README.md
│ │ ├── bin/
│ │ │ └── mjml
│ │ ├── package.json
│ │ └── src/
│ │ ├── client.js
│ │ ├── commands/
│ │ │ ├── outputToConsole.js
│ │ │ ├── outputToFile.js
│ │ │ ├── readFile.js
│ │ │ ├── readStream.js
│ │ │ └── watchFiles.js
│ │ └── helpers/
│ │ ├── defaultOptions.js
│ │ └── fileContext.js
│ ├── mjml-column/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-core/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── components.js
│ │ │ ├── createComponent.js
│ │ │ ├── helpers/
│ │ │ │ ├── conditionalTag.js
│ │ │ │ ├── fonts.js
│ │ │ │ ├── formatAttributes.js
│ │ │ │ ├── genRandomHexString.js
│ │ │ │ ├── jsonToXML.js
│ │ │ │ ├── makeLowerBreakpoint.js
│ │ │ │ ├── mediaQueries.js
│ │ │ │ ├── mergeOutlookConditionnals.js
│ │ │ │ ├── minifyOutlookConditionnals.js
│ │ │ │ ├── mjmlconfig.js
│ │ │ │ ├── preview.js
│ │ │ │ ├── shorthandParser.js
│ │ │ │ ├── skeleton.js
│ │ │ │ ├── styles.js
│ │ │ │ ├── suffixCssClasses.js
│ │ │ │ └── widthParser.js
│ │ │ ├── index.js
│ │ │ └── types/
│ │ │ ├── boolean.js
│ │ │ ├── color.js
│ │ │ ├── enum.js
│ │ │ ├── helpers/
│ │ │ │ └── colors.js
│ │ │ ├── index.js
│ │ │ ├── integer.js
│ │ │ ├── string.js
│ │ │ ├── type.js
│ │ │ └── unit.js
│ │ └── tests/
│ │ ├── .eslintrc
│ │ ├── index.js
│ │ ├── jsonToXml-test.js
│ │ ├── mergeOutlookConditionnals-test.js
│ │ ├── minifyOutlookConditionnals-test.js
│ │ ├── shorthandParser-test.js
│ │ ├── skeleton-test.js
│ │ └── widthParser-test.js
│ ├── mjml-divider/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-group/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head/
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-attributes/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-breakpoint/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-font/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-html-attributes/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-preview/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-style/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-head-title/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-hero/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-image/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-migrate/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── cli.js
│ │ ├── config.js
│ │ └── migrate.js
│ ├── mjml-navbar/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── Navbar.js
│ │ ├── NavbarLink.js
│ │ └── index.js
│ ├── mjml-parser-xml/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── helpers/
│ │ │ │ ├── cleanNode.js
│ │ │ │ ├── convertBooleansOnAttrs.js
│ │ │ │ └── setEmptyAttributes.js
│ │ │ └── index.js
│ │ └── test/
│ │ ├── incl.mjml
│ │ ├── test-preprocessors.js
│ │ ├── test-utils.js
│ │ ├── test-values.js
│ │ └── test.js
│ ├── mjml-preset-core/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── dependencies.js
│ │ └── index.js
│ ├── mjml-raw/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-section/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-social/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── Social.js
│ │ ├── SocialElement.js
│ │ └── index.js
│ ├── mjml-spacer/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-table/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-text/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ ├── mjml-validator/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── MJMLRulesCollection.js
│ │ ├── dependencies.js
│ │ ├── index.js
│ │ └── rules/
│ │ ├── errorAttr.js
│ │ ├── ruleError.js
│ │ ├── validAttributes.js
│ │ ├── validChildren.js
│ │ ├── validTag.js
│ │ └── validTypes.js
│ └── mjml-wrapper/
│ ├── README.md
│ ├── package.json
│ └── src/
│ └── index.js
├── readme-ja.md
├── test.js
└── type.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# https://editorconfig.org
# A special property that should be specified at the top of the file outside of
# any sections. Set to true to stop .editor config file search on current file
root = true
# Indentation style
# Possible values - tab, space
indent_style = space
# Indentation size in single-spaced characters
# Possible values - an integer, tab
indent_size = 2
# Line ending file format
# Possible values - lf, crlf, cr
end_of_line = lf
# File character encoding
# Possible values - latin1, utf-8, utf-16be, utf-16le
charset = utf-8
# Denotes whether to trim whitespace at the end of lines
# Possible values - true, false
trim_trailing_whitespace = true
# Denotes whether file should end with a newline
# Possible values - true, false
insert_final_newline = true
================================================
FILE: .eslintignore
================================================
node_modules
lib
packages/mjml-core/src/types*
type.js
test-html-attributes.js
test.js
babel.config.js
================================================
FILE: .eslintrc
================================================
{
"extends": ["airbnb-base", "prettier"],
"parser": "babel-eslint",
"rules": {
"comma-dangle": [2, "always-multiline"],
"semi": [2, "never"],
"no-mixed-operators": 0,
"no-shadow": 0,
"no-param-reassign": 0,
"no-restricted-syntax": 0,
},
"env": {
"node": true,
"mocha": true,
},
"overrides": [
{
"files": ["packages/mjml/test/*.test.js"],
"rules": {
"prefer-arrow-callback": "off",
"func-names": "off",
},
},
],
}
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Create a file with this MJML code: `<mjml>...</mjml>`
2. Render it to HTML by doing '...'
3. Send the HTML to an email address with '...'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**MJML environment (please complete the following information):**
- OS: [e.g. MacOS]
- MJML Version [e.g. 4.2.0]
- MJML tool used [e.g MJML App]
**Email sending environment(for rendering issues)**:
- Platform used to send the email [e.g [Putsmail](https://putsmail.com/)]
**Affected email clients (for rendering issues):**
- Email Client [e.g Gmail]
- OS: [e.g. Windows]
- Browser [e.g. Google Chrome]
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/build-documentation.yml
================================================
name: Build Documentation
on:
push:
branches:
- master
jobs:
buildDoc:
runs-on: ubuntu-latest
steps:
- name: Trigger Documentation Build
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.documentation_token }}
repository: mjmlio/slate
event-type: build-doc
================================================
FILE: .github/workflows/mjml-workflow.yml
================================================
name: Mjml CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Run linting & tests
run: |
yarn install
yarn build
yarn lint
yarn test
================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: Publish Package to NPM
on:
workflow_dispatch:
inputs:
release_type:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- run: yarn install
- run: yarn build
- run: yarn build-browser
- name: Configure git user
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump version
id: bump
run: |
yarn lerna version ${{ github.event.inputs.release_type }} --no-push --no-git-tag-version --yes --no-private --force-publish
NEW_VERSION=$(jq -r '.version' lerna.json)
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Commit & tag release
run: |
git add lerna.json packages/*/package.json
git commit -m "chore(release): v${{ steps.bump.outputs.version }}"
git push origin HEAD
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.bump.outputs.version }}
generate_release_notes: true
- name: Publish packages to NPM
run: yarn lerna publish from-package --yes
================================================
FILE: .gitignore
================================================
.DS_Store
*.log
.idea/
lib
node_modules
test.html
/**/npmignore
/testing-manual
================================================
FILE: .prettierignore
================================================
package.json
================================================
FILE: .prettierrc
================================================
{
"printWidth": 80,
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
================================================
FILE: CONTRIBUTING.md
================================================
# What should I know before I get started?
## Code of Conduct
This 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).
## Packages
MJML 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.
There are 3 types of packages:
* `mjml-core`: the engine that renders mjml components
* `mjml-cli`: the client, base on the mjml-core interface
* `mjml`: a standalone client including the standard library of components
* one standalone package for each component
# How Can I Contribute?
## Reporting Bugs
Here are the guidelines to help maintainers and the community better understand and solve your issue.
## Before Submitting a Bug Report or Enhancement
* **Check the [FAQ](https://mjml.io/faq)** for a list of common questions and problems
* **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
* **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
## How To Submit A Good Bug Report or Enhancement?
Explain the problem you’re facing and include as many details as you can to help maintainers reproduce the problem:
* **Use a clear and self-explanatory title**
* **Provide all the specific information that might be needed to reproduce the problem, such as:**
* **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)
* The **version of MJML** you’re using
* 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/)
* The **name and version of the email client(s)** on which a bug is encountered
* **Screenshots** of the issue / behaviour before enhancement on the **given email clients**
* Explain why **what you encountered is a bug** / how your enhancement would **improve MJML**: what did you expect to see and why?
* 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)**
## Template For Submitting Bug Reports
[Short description of problem here]
**Reproduction Steps:**
1. [First Step]
2. [Second Step]
3. [Other Steps...]
**Expected behavior:**
[Describe expected behavior here]
**Observed behavior:**
[Describe observed behavior here]
**Screenshots and GIFs**

**MJML version:**
[Enter MJML version here]
**Email clients the bug is seen on:**
[Enter email clients names and versions here]
## Your First Code Contribution
If 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.
## How to Submit A Good Pull Request
* Document your code
* Update the documentation (example: table of a component’s supported attributes if you add an attribute to this component)
* Test your pull request locally
* 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)
* Provide the MJML code you used to test locally and on the screenshots
* We suggest following the [React Styleguide](https://github.com/airbnb/javascript/tree/master/react) by Airbnb
# Additional Notes
## Discussions vs Bugs & Enhancements
## Tags categories
Type of issue and issue state
#### Type of Issue and Issue State
| Label name | Description |
| --- | --- |
| `Feature request` | Feature requests or improvements |
| `Bug` | Confirmed bugs or reports likely to be bugs |
| `Community-help-wanted` | The MJML team would appreciate help from the community in implementing these issues |
| `Beginner` | Less complex issues that would be good first issues to work on for users who want to contribute to MJML |
| `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)) |
| `Needs reproduction` | Likely bugs we couldn’t reproduce |
| `Duplicate` | Issues that are duplicates of other issues |
| `Invalid` | Issues which aren’t valid (e.g user errors) |
| `Tooling idea` | Feature requests that might be good candidates for tools around MJML instead of extending MJML |
#### Topic categories
| Label name | Description |
| --- | --- |
| `Not rendering` | the engine won’t render a template without a valid reason |
| `General rendering issue` | the HTML rendered is not responsive while respecting MJML’s best practices |
| `Email client name` | The HTML rendered is not responsive for a specific email client |
| `CLI`| issues related to the MJML Command Line Interface |
| `Documentation` | issues related to the MJML documentation |
#### Pull Requests labels
| Label name | Description |
| --- | --- |
| `Work in progress` | PR which are still being worked on, more changes will follow |
| `Needs review `| Pull requests which need code review and approval |
| `Under review` | PR being reviewed |
| `Requires changes` | PR which need to be updated based on review comments and then reviewed again
| `Needs testing` | PRs which need testing on [Litmus](https://litmus.com/) or [Email On Acid](https://www.emailonacid.com/) |
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) 2016 Mailjet SAS, https://mjml.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# MJML 4
If you're looking for MJML 3.3.X check [this branch](https://github.com/mjmlio/mjml/tree/3.3.x)
<p style="text-align: center;" >
<a href="https://mjml.io" target="_blank">
<img width="250"src="https://mjml.io/assets/img/litmus/mjmlbymailjet.png">
</a>
</p>
<p style="text-align: center;" >
<a href="https://github.com/mjmlio/mjml/actions">
<img src="https://github.com/mjmlio/mjml/workflows/Mjml%20CI/badge.svg?branch=master" alt="github actions">
</a>
<a href="https://www.codacy.com/app/gbadi/mjml">
<img src="https://api.codacy.com/project/badge/grade/575339cb861f4ff4b0dbb3f9e1759c35"/>
</a>
</p>
<p style="text-align: center;" >
| <b><a href="#translated-documentation">Translated documentation</a></b>
| <b><a href="#introduction">Introduction</a></b>
| <b><a href="#installation">Installation</a></b>
| <b><a href="#usage">Usage</a></b> |
</p>
---
# Translated documentation
| Language | Link for documentation |
| :-: | :-: |
| 日本語 | [日本語ドキュメント](https://github.com/mjmlio/mjml/blob/master/readme-ja.md) |
# Introduction
`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.
<p style="text-align: center;" >
<a href="https://mjml.io" target="_blank">
<img width="75%" src="https://cloud.githubusercontent.com/assets/6558790/12450760/ee034178-bf85-11e5-9dda-98d0c8f9f8d6.png">
</a>
</p>
# Installation
You 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.
```bash
npm install mjml
```
# Development
To work on MJML, make changes and create merge requests, download and install [yarn](https://yarnpkg.com/lang/en/docs/install/) for easy development.
```bash
git clone https://github.com/mjmlio/mjml.git && cd mjml
yarn
yarn build
```
You can also run `yarn build:watch` to rebuild the package as you code.
# Usage
## Online
Don't want to install anything? Use the free online editor!
<p style="text-align: center;" >
<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>
</p>
<br>
## Applications and plugins
MJML comes with an ecosystem of tools and plugins, check out:
- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJML is included)
- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJML is included)
- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJML needs to be installed separately)
For more tools, check the [Community](https://mjml.io/community) page.
## Command line interface
> Compiles the file and outputs the HTML generated in `output.html`
```bash
mjml input.mjml -o output.html
```
You can pass optional `arguments` to the CLI and combine them.
argument | description | default value
---------|--------|--------------
`mjml -m [input]` | Migrates a v3 MJML file to the v4 syntax | NA
`mjml [input] -o [output]` | Writes the output to [output] | NA
`mjml [input] -s` | Writes the output to `stdout` | NA
`mjml -w [input]` | Watches the changes made to `[input]` (file or folder) | NA
`mjml [input] --config.beautify` | Beautifies the output (`true` or `false`) | true
`mjml [input] --config.minify` | Minifies the output (`true` or `false`) | false
See [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more information about config options.
## Inside Node.js
```javascript
import mjml2html from 'mjml'
/*
Compile an mjml string
*/
const htmlOutput = mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`, options)
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)
```
You can pass optional `options` as an object to the `mjml2html` function:
option | unit | description | default value
-------------|--------|--------------|---------------
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)
keepComments | boolean | Option to keep comments in the HTML output | true
ignoreIncludes | boolean | Option to ignore mj-includes | false
beautify | boolean | Option to beautify the HTML output | false
minify | boolean | Option to minify the HTML output | false
validationLevel | string | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): 'strict', 'soft', 'skip' | 'soft'
filePath | string | Path of file, used for relative paths in mj-includes | '.'
preprocessors | array of functions | Preprocessors applied to the xml before parsing. Input must be xml, not json. Functions must be (xml: string) => string | []
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 | NA
minifyOptions | Options for html minifier, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info | NA
mjmlConfigPath | string | The path or directory of the `.mjmlconfig` file (for custom components use) | `process.cwd()`
useMjmlConfigOptions | Allows to use the `options` attribute from `.mjmlconfig` file | false
## Client-side (in browser)
```javascript
var mjml2html = require('mjml-browser')
/*
Compile a mjml string
*/
var htmlOutput = mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`, options)
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)
```
## API
A 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.
# MJML Slack
MJML 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.
# Contributors
- [Maxime](https://github.com/iRyusa)
- [Nicolas](https://github.com/ngarnier)
- [Cedric](https://github.com/kmcb777)
- [Loeck](https://github.com/lohek)
- [Robin](https://github.com/robink)
- [Guillaume](https://github.com/GuillaumeBadi)
- [Meriadec](https://github.com/meriadec)
- [Arnaud](https://github.com/arnaudbreton)
- [HTeuMeuLeu](https://github.com/hteumeuleu)
- [Emmanuel Payet](https://github.com/epayet)
- [Matthieu](https://github.com/swibge)
- [Rogier](https://github.com/rogierslag)
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: [['@babel/env', {
targets: { node: '10' },
include: ['transform-classes'],
}]],
plugins: [
'@babel/proposal-class-properties',
[
'@babel/transform-runtime',
{
// by default the plugin assumes we have 7.0.0-beta.0 version of runtime
// and inline all missing helpers instead of requiring them
version: require('@babel/plugin-transform-runtime/package.json')
.version,
},
],
'add-module-exports',
'lodash',
],
}
================================================
FILE: doc/basic.md
================================================
## Basic layout example
In this section, you're going to learn how to code a basic email template using MJML.
Here‘s what we’re building:
<figure>
<img width="350px" src="https://static.mailjet.com/mjml-website/documentation/basic-layout-example.png" alt="Basic email layout" />
</figure>
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live/templates/basic">Try it live</a></p>
### Sections
```html
<mjml>
<mj-body>
<!-- Company Header -->
<mj-section background-color="#f0f0f0"></mj-section>
<!-- Image Header -->
<mj-section background-color="#f0f0f0"></mj-section>
<!-- Introduction Text -->
<mj-section background-color="#fafafa"></mj-section>
<!-- 2 columns section -->
<mj-section background-color="white"></mj-section>
<!-- Icons -->
<mj-section background-color="#fbfbfb"></mj-section>
<!-- Social icons -->
<mj-section background-color="#f0f0f0"></mj-section>
</mj-body>
</mjml>
```
First, we’ll create the basic structure, dividing the email into six sections.
#### Company Header
```html
<!-- Company Header -->
<mj-section background-color="#f0f0f0">
<mj-column>
<mj-text
align="center"
font-style="italic"
font-size="20px"
color="#626262"
>
My Company
</mj-text>
</mj-column>
</mj-section>
```
The 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.
<div class="alert alert-important" role="alert">
<p>Important</p>
<p>Remember everything has to be contained within the column.</p>
</div>
#### Image Header
```html
<!-- Image Header -->
<mj-section
background-url="https://1.bp.blogspot.com/-TPrfhxbYpDY/Uh3Refzk02I/AAAAAAAALw8/5sUJ0UUGYuw/s1600/New+York+in+The+1960's+-+70's+(2).jpg"
background-size="cover"
background-repeat="no-repeat"
>
<mj-column width="600px">
<mj-text
align="center"
color="#fff"
font-size="40px"
font-family="Helvetica Neue"
>
Slogan here
</mj-text>
<mj-button background-color="#F63A4D" href="#"> Promotion </mj-button>
</mj-column>
</mj-section>
```
Next 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.
To add the image header, you will have to replace the section's `background-color` with a `background-url`.
Similarly to the first company header, you will have to center the text.
The button `href` sets where the button links to.
In order to have the background rendered full-width in the column, set the column width to 600px with `width="600px"`.
#### Introduction Text
```html
<!-- Intro text -->
<mj-section background-color="#fafafa">
<mj-column width="400px">
<mj-text
font-style="italic"
font-size="20px"
font-family="Helvetica Neue"
color="#626262"
>
My Awesome Text
</mj-text>
<mj-text color="#525252">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin rutrum enim
eget magna efficitur, eu semper augue semper. Aliquam erat volutpat. Cras
id dui lectus. Vestibulum sed finibus lectus, sit amet suscipit nibh.
Proin nec commodo purus. Sed eget nulla elit. Nulla aliquet mollis
faucibus.
</mj-text>
<mj-button background-color="#F45E43" href="#">Learn more</mj-button>
</mj-column>
</mj-section>
```
The introduction text will consist of a heading, the main text and a button.
The title is a regular `mj-text` tag that can be styled as a heading.
#### 2 Columns Section
```html
<!-- Side image -->
<mj-section background-color="white">
<!-- Left image -->
<mj-column>
<mj-image
width="200px"
src="https://designspell.files.wordpress.com/2012/01/sciolino-paris-bw.jpg"
/>
</mj-column>
<!-- right paragraph -->
<mj-column>
<mj-text
font-style="italic"
font-size="20px"
font-family="Helvetica Neue"
color="#626262"
>
Find amazing places
</mj-text>
<mj-text color="#525252">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin rutrum enim
eget magna efficitur, eu semper augue semper. Aliquam erat volutpat. Cras
id dui lectus. Vestibulum sed finibus lectus.</mj-text
>
</mj-column>
</mj-section>
```
This section is made up of two columns. One containing an image, the other containing text.
For the image, note that when a tag does not have any children, you can use the XML self-closing tag syntax: `<mj-image />`
For 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.
#### Icons
```html
<!-- Icons -->
<mj-section background-color="#fbfbfb">
<mj-column>
<mj-image
padding="10px"
width="100px"
src="https://191n.mj.am/img/191n/3s/x0l.png"
/>
</mj-column>
<mj-column>
<mj-image
padding="10px"
width="100px"
src="https://191n.mj.am/img/191n/3s/x01.png"
/>
</mj-column>
<mj-column>
<mj-image
padding="10px"
width="100px"
src="https://191n.mj.am/img/191n/3s/x0s.png"
/>
</mj-column>
</mj-section>
```
This section uses a 3-column layout to display the 3 icons horizontally across the email.
#### Social Icons
```html
<mj-section background-color="#e7e7e7">
<mj-column>
<mj-social>
<mj-social-element name="facebook">Share</mj-social-element>
</mj-social>
</mj-column>
</mj-section>
```
MJML 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.
================================================
FILE: doc/body_components.md
================================================
## Standard Body components
Body components ease your development process by providing ready made responsive layouts that you can use to create your email template.
================================================
FILE: doc/community-components.md
================================================
## Community components
In addition to the standard components available in MJML, our awesome community is contributing by creating their own components.
To use a community component, proceed as follows:
- Install MJML locally with `npm install mjml` in a folder
- Install the community component with `npm install {component-name}` in the same folder
- Create a `.mjmlconfig` file in the same folder with this code:
```json
{
"packages": ["component-name/path-to-js-file"]
}
```
You 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`.
================================================
FILE: doc/community-contributions.md
================================================
## Community Contributions
The 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!
Getting 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)!
Here are some tools that utilise MJML:
### Mailjet
[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.
The 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.
When your template is ready, you can export it in MJML or HTML, or send emails directly through Mailjet.
### Parcel
[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.
Use 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.
Export MJML to HTML with a click.
### IntelliJ IDEA Plugin - MJML Support
[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.
It is available on the [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/16418-mjml-support).
### Gradle Plugin - MJML Compilation
[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).
It 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/).
### Neos CMS
[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.
It is available on [packagist](https://packagist.org/packages/garagist/mjml).
### Easy-email
[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.
### Email Love
The [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.
================================================
FILE: doc/components_1.md
================================================
## Components
Components 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.
MJML 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.
For instance, the `mj-button` component is, on the inside, a complex HTML layout:
```html
<!-- MJML -->
<mj-button href="#"> Hello There! </mj-button>
<!-- HTML -->
<table
cellpadding="0"
cellspacing="0"
style="border:none;border-radius:3px;"
align="center"
>
<tbody>
<tr>
<td
style="background-color:#414141;border-radius:3px;color:#ffffff;cursor:auto;"
align="center"
valign="middle"
bgcolor="#414141"
>
<a
class="mj-content"
href="#"
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;"
target="_blank"
>
Hello There!
</a>
</td>
</tr>
</tbody>
</table>
```
### Which email clients/versions are supported?
For full details of component support, [please visit our support matrix](https://mjml.io/compatibility).
### mjml
An 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.
#### Attributes
| attribute | accepts | description | default value |
| --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| 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` |
| lang | string | adds a `lang` attribute in the `html` and `body > div` tags | `und` |
| dir | string | adds a `dir` attribute in the `html` and `body > div` tags | `auto` |
### mj-head
Contains components related to the document head such as style and meta elements (see [head components](#standard-head-components)).
================================================
FILE: doc/components_2.md
================================================
### mj-include
The `mjml-core` package allows you to include external `.mjml` files
to build your email template.
```xml
<!-- header.mjml -->
<mj-section>
<mj-column>
<mj-text>This is a header</mj-text>
</mj-column>
</mj-section>
```
You can wrap your external `.mjml` files inside the default `mjml > mj-body`
tags to make it easier to preview outside the main template.
```xml
<!-- main.mjml -->
<mjml>
<mj-body>
<mj-include path="./header.mjml" />
</mj-body>
</mjml>
```
The MJML engine will then replace your included files before starting the rendering process.
#### Other file types
You 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.
If you want the CSS to be inlined, you can use the `css-inline="inline"` attribute.
```xml
<!-- main.mjml -->
<mj-include path="./styles.css" type="css" />
<mj-include path="./inline-styles.css" type="css" css-inline="inline" />
```
You 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"`.
```xml
<!-- main.mjml -->
<mj-include path="./partial.html" type="html" />
```
================================================
FILE: doc/config.json
================================================
[
"mjml/doc/guide.md",
"mjml/doc/install.md",
"mjml/doc/getting_started.md",
"mjml/doc/basic.md",
"mjml/doc/components_1.md",
"mjml/packages/mjml-body/README.md",
"mjml/doc/components_2.md",
"mjml/doc/head_components.md",
"mjml/packages/mjml-head-attributes/README.md",
"mjml/packages/mjml-head-breakpoint/README.md",
"mjml/packages/mjml-head-font/README.md",
"mjml/packages/mjml-head-html-attributes/README.md",
"mjml/packages/mjml-head-preview/README.md",
"mjml/packages/mjml-head-style/README.md",
"mjml/packages/mjml-head-title/README.md",
"mjml/doc/body_components.md",
"mjml/packages/mjml-accordion/README.md",
"mjml/packages/mjml-button/README.md",
"mjml/packages/mjml-carousel/README.md",
"mjml/packages/mjml-column/README.md",
"mjml/packages/mjml-divider/README.md",
"mjml/packages/mjml-group/README.md",
"mjml/packages/mjml-hero/README.md",
"mjml/packages/mjml-image/README.md",
"mjml/packages/mjml-navbar/README.md",
"mjml/packages/mjml-raw/README.md",
"mjml/packages/mjml-section/README.md",
"mjml/packages/mjml-social/README.md",
"mjml/packages/mjml-spacer/README.md",
"mjml/packages/mjml-table/README.md",
"mjml/packages/mjml-text/README.md",
"mjml/packages/mjml-wrapper/README.md",
"mjml/doc/ending-tags.md",
"mjml/doc/community-components.md",
"mjml/doc/ports.md",
"mjml/doc/mjml-bar-chart.md",
"mjml/doc/mjml-chart.md",
"mjml/doc/mjml-chartjs.md",
"mjml/doc/mjml-qr-code.md",
"mjml/doc/mjml-mso-button.md",
"mjml/packages/mjml-validator/README.md",
"mjml/doc/create.md",
"mjml/doc/using_mjml_in_json.md",
"mjml/doc/tooling.md",
"mjml/doc/community-contributions.md"
]
================================================
FILE: doc/create.md
================================================
## Creating a Component
One 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!
We 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.
================================================
FILE: doc/ending-tags.md
================================================
### Ending tags
Some MJML components are "ending tags". These are mostly the components that will contain text content, like `mj-text` or `mj-button`.
These components can contain both text and HTML content, which will remain unprocessed by the MJML engine. You cannot use other MJML components.
Since 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 `<` and `<`.
There 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.
If 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.
Here is the list of all ending tags :
- `mj-accordion-text`
- `mj-accordion-title`
- `mj-button`
- `mj-navbar-link`
- `mj-raw`
- `mj-social-element`
- `mj-text`
- `mj-table`
================================================
FILE: doc/getting_started.md
================================================
## Getting Started
This is a responsive email:
<figure>
<img width="300px" src="https://static.mailjet.com/mjml-website/documentation/getting-started-1.png" alt="layout">
</figure>
Like a regular HTML template, we can split this one into different parts to fit in a grid.
The body of your email, represented by the `mj-body` tag contains the entire content of your document:
<figure>
<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">
</figure>
From here, you can first define your sections:
<figure>
<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">
</figure>
Inside any section, there should be columns (even if you need only one column). Columns are what makes MJML responsive.
<figure>
<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">
</figure>
Below, 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.
### Column sizing
#### Auto sizing
The 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.
<div class="alert alert-note" role="alert">
<p>Note</p>
<p>Any MJML component included in a column will have a width equivalent to 100% of this column's width.</p>
</div>
Let's take the following layout to illustrate this:
```html
<mjml>
<mj-body>
<mj-section>
<mj-column>
<!-- First column content -->
</mj-column>
<mj-column>
<!-- Second column content -->
</mj-column>
</mj-section>
</mj-body>
</mjml>
```
Since 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%.
#### Manual sizing
You can also manually set the size of your columns, in pixels or percentage, by using the `width` attribute on `mj-column`.
Let's take the following layout to illustrate this:
```html
<mjml>
<mj-body>
<mj-section>
<mj-column width="200px">
<!-- First column content -->
</mj-column>
<mj-column width="400px">
<!-- Second column content -->
</mj-column>
</mj-section>
</mj-body>
</mjml>
```
================================================
FILE: doc/guide.md
================================================
---
title: API Reference
language_tabs:
- html: MJML
toc_footers:
- <a href='https://github.com/mjmlio/mjml'>Fork me on Github</a>
- <a href='https://github.com/mjmlio/mjml/issues'>Submit an Issue</a>
search: true
---
# MJML Guide
MJML (Mailjet Markup Language) is a markup language designed to reduce the pain of coding a responsive email.
Its semantic syntax makes it easy and straightforward whilst its rich standard components library speeds up your development time and lightens your email codebase.
MJML’s open-source engine generates high quality responsive HTML compliant with best practices.
## Overview
MJML rolls up all of what Mailjet has learned about HTML email design and abstracts the whole layer of complexity related to responsive email design.
Get 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>`.
MJML has been designed with responsiveness in mind. The abstraction it offers guarantee that you will always be up-to-date with the industry practices.
Email 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.
```html
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-image
width="100px"
src="https://mjml.io/assets/img/logo-small.png"
></mj-image>
<mj-divider border-color="#F45E43"></mj-divider>
<mj-text font-size="20px" color="#F45E43" font-family="helvetica"
>Hello World</mj-text
>
</mj-column>
</mj-section>
</mj-body>
</mjml>
```
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live/intro">Try it live</a></p>
================================================
FILE: doc/head_components.md
================================================
## Standard Head components
Head components ease your development process, for example, enabling you to import fonts, define default styles or create classes for MJML components.
================================================
FILE: doc/install.md
================================================
## Installation
You 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,
[head over to Usage](#usage) for other ways to use MJML.
```bash
npm install mjml
```
## Development
To work on MJML, make changes and create merge requests, [download and
install yarn](https://yarnpkg.com/lang/en/docs/install/) for easy development.
```bash
git clone https://github.com/mjmlio/mjml.git && cd mjml
yarn
yarn build
```
You can also run `yarn build:watch` to rebuild the package as you code.
## Usage
### Online
Don't want to install anything? [Use the free online editor](https://mjml.io/try-it-live)!
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/usage-online.png" alt="try it live" width="75%">
</figure>
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live">Try it live</a></p>
### Applications and plugins
MJML comes with an ecosystem of tools and plugins, check out:
- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJML is included)
- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJML is included)
- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJML needs to be installed separately)
For more information, [check the Tooling section](#tooling).
For more tools, [check the Community page](https://mjml.io/community).
### Command line interface
> Compiles the file and outputs the HTML generated in `output.html`
```bash
mjml input.mjml -o output.html
```
You can pass optional `arguments` to the CLI and combine them.
| argument | description | default value |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `mjml -m [input]` | Migrates a v3 MJML file to the v4 syntax | |
| `mjml [input] -o [output]` | Writes the output to [output] | |
| `mjml [input] -s` | Writes the output to `stdout` | |
| `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} -->` |
| `mjml -w [input]` | Watches the changes made to `[input]` (file or folder) | |
| `mjml [input] --config.beautify` | Beautifies the output (`true` or `false`) | `true` |
| `mjml [input] --config.minify` | Minifies the output (`true` or `false`) | `false` |
| `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 | |
| `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 | |
| `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_ |
| `mjml [input] --config.useMjmlConfigOptions` | Allows to use the `options` attribute from `.mjmlconfig` file | `false` |
| `mjml [input] --config.validationLevel` | [Validation level](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): `strict`, `soft` or `skip` | `soft` |
### Inside Node.js
```javascript
import mjml2html from 'mjml'
/*
Compile an mjml string
*/
const htmlOutput = mjml2html(
`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`,
options,
)
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)
```
You can pass optional `options` as an object to the `mjml2html` function:
| option | accepts | description | default value |
| -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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) |
| keepComments | boolean | Option to keep comments in the HTML output | `true` |
| beautify | boolean | Option to beautify the HTML output | `false` |
| minify | boolean | Option to minify the HTML output | `false` |
| validationLevel | string | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): `strict` `soft` `skip` | `soft` |
| filePath | string | Full path of the specified file to use when resolving paths from [`mj-include` components](#mj-include) | `.` |
| mjmlConfigPath | string | The path or directory of the [`.mjmlconfig` file](#community-components) | `process.cwd()` |
| useMjmlConfigOptions | boolean | Allows to use the `options` attribute from `.mjmlconfig` file | `false` |
| 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 }, }, ], }, },` |
| 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 | |
### API
A free-to-use MJML API is available to make it easy to integrate MJML in your application. Head
over here to [learn more about the API](https://mjml.io/api).
================================================
FILE: doc/mjml-bar-chart.md
================================================
### mj-bar-chart
An open-source component that allows you to create fully embedded static bar charts in your MJML templates.
These bar charts are easily customizable, 100% built with HTML tables, and do not require any external dependencies.
The package is available on [GitHub](https://github.com/Freezystem/mjml-bar-chart) and [NPM](https://www.npmjs.com/package/@freezystem/mjml-bar-chart).
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/bar-chart-example-1.png" alt="Basic mjml-bar-chart rendering" />
</figure>
You can also render as stacked bars, add a link to your sources, vertically align labels, and much more:
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/bar-chart-example-2.png" alt="Stacked rendering with vertically aligned legends" />
</figure>
This component is lightweight, written with TypeScript, and is thoroughly tested.
It is available as UMD (CJS + AMD), ESM and TS module format.
================================================
FILE: doc/mjml-chart.md
================================================
### mj-chart
Displays charts as images in your email.
Thanks 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).
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/chart-example.png" alt="mj-chart demo" />
</figure>
================================================
FILE: doc/mjml-chartjs.md
================================================
### mj-chartjs
Displays [Chart.js](https://www.chartjs.org/) charts as images in your email. Chart.js is an open-source Javascript charting library.
It’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.
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/chartjs-example.webp" alt="mj-chartjs demo" />
</figure>
================================================
FILE: doc/mjml-mso-button.md
================================================
### mjml-msobutton
A 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
It uses the same attributes as the standard `mj-button` but includes three additional ones:
| attribute | accepts | description | default value |
| ---------- | ------- | ------------------------------ | ------------- |
| mso-proof | boolean | Active the bulletproof mode | `false` |
| mso-width | `px` | The width of the VML solution | `200px` |
| mso-height | `px` | The height of the VML solution | `40px` |
These new attributes allow MJML to generate a “bulletproof button“ which incorporates radius, stroke and alignment, [using this method](https://buttons.cm/),
It's available on [Github](https://github.com/adrien-zinger/mjml-mso-button) and [NPM](https://www.npmjs.com/package/mjml-msobutton).
**Usage**
Use it like a standard `mj-button`:
```html
<mj-msobutton mso-proof="true">Click !</mj-msobutton>
```
**Problems that you should know**
1. This cannot be used with an image in background
2. It creates a duplication of code in the HTML
3. The width and the height cannot be used with the auto value.
> Sample project on github [here](https://github.com/adrien-zinger/mjml-msobutton-sample)
================================================
FILE: doc/mjml-qr-code.md
================================================
### mj-qr-code
Displays 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).
By default, it uses the open-source QuickChart [QR code API](https://quickchart.io/).
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/qr-code-example.webp" alt="mj-qr-code demo" />
</figure>
================================================
FILE: doc/ports.md
================================================
## Ports and Language Bindings
MJML is also available for other platforms to use. The community has created ports to these and wrappers for the official Node implementation.
Note: These contributions are not directly supported by the MJML team.
### Rust: MRML
This project is a reimplementation of the nice MJML markup language in Rust.
[https://github.com/jdrouet/mrml](https://github.com/jdrouet/mrml)
#### Missing implementations / components:
- `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.
### .NET: MJML.NET
A blazingly-fast unofficial port of MJML 4 to .NET 6.
[https://github.com/SebastianStehle/mjml-net](https://github.com/SebastianStehle/mjml-net)
### Elixir: MJML (Rust NIFs for Elixir)
Native Implemented Function (NIF) bindings for the MJML Rust implementation (mrml).
[https://github.com/adoptoposs/mjml_nif](https://github.com/adoptoposs/mjml_nif)
### Ruby: MRML Ruby
Ruby wrapper for MRML, the MJML markup language implementation in Rust.
[https://github.com/hardpixel/mrml-ruby](https://github.com/hardpixel/mrml-ruby)
### React: mjml-react
React components for MJML components.
[https://github.com/faire/mjml-react#readme](https://github.com/faire/mjml-react#readme)
### Python: mjml-python
Python wrapper for MRML, the MJML markup language implementation in Rust.
[https://github.com/mgd020/mjml-python](https://github.com/mgd020/mjml-python)
### Python: mjml-python
Python implementation for MJML.
[https://github.com/FelixSchwarz/mjml-python](https://github.com/FelixSchwarz/mjml-python)
### Python / Django: django-mjml
The simplest way to use MJML in Django templates.
[https://github.com/liminspace/django-mjml](https://github.com/liminspace/django-mjml)
### PHP / Laravel: Laravel MJML
Build responsive e-mails easily using MJML and Laravel Mailables.
- [https://github.com/EvanSchleret/lara-mjml](https://github.com/EvanSchleret/lara-mjml)
- [https://github.com/asahasrabuddhe/laravel-mjml](https://github.com/asahasrabuddhe/laravel-mjml) (not maintained)
================================================
FILE: doc/tooling.md
================================================
## Tooling
In 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:
### Visual Studio Code
[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.
It 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).
### Sublime Text
[Sublime Text](https://www.sublimetext.com/) is a powerful text editor. We’ve provided you with a package to color MJML tags.
It is available [on Github](https://github.com/mjmlio/mjml-syntax) and through the [Sublime Package Control](https://packagecontrol.io/packages/MJML-syntax).
### Gulp
[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.
It is available here on [Github](https://github.com/mjmlio/gulp-mjml).
================================================
FILE: doc/using_mjml_in_json.md
================================================
## Using MJML in JSON
MJML can not only be used as a markup, but also as a JSON object, very useful for
programmatic manipulation or with the MJML API.
With the JSON format, a MJML component is defined as an `object` with the following properties:
- a `tagName` as a `string`
- a list of attributes as an `object`
- either a `content` as a `string` or a list of `children` tags as an `array`.
Exactly like using MJML as a markup, the JSON definition can be passed as an object to the `mjml2html` function.
Here is working example:
```javascript
var mjml2html = require('mjml')
console.log(
mjml2html({
tagName: 'mjml',
attributes: {},
children: [
{
tagName: 'mj-body',
attributes: {},
children: [
{
tagName: 'mj-section',
attributes: {},
children: [
{
tagName: 'mj-column',
attributes: {},
children: [
{
tagName: 'mj-image',
attributes: {
width: '100px',
src: '/assets/img/logo-small.png',
},
},
{
tagName: 'mj-divider',
attributes: {
'border-color': '#F46E43',
},
},
{
tagName: 'mj-text',
attributes: {
'font-size': '20px',
color: '#F45E43',
'font-family': 'Helvetica',
},
content: 'Hello World',
},
],
},
],
},
],
},
],
}),
)
```
================================================
FILE: lerna.json
================================================
{
"packages": [
"packages/*"
],
"command": {
"publish": {
"exact": true
}
},
"npmClient": "yarn",
"useWorkspaces": true,
"version": "4.18.0"
}
================================================
FILE: package.json
================================================
{
"name": "mjml-master",
"private": true,
"scripts": {
"build:watch": "lerna run build --parallel -- -- -w",
"build": "lerna run build --parallel --ignore mjml-browser",
"build-browser": "cd packages/mjml-browser && yarn build",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"postinstall": "lerna bootstrap",
"prettier": "prettier --write \"packages/**/{src,bin}/**/*.?(js|json)\"",
"test": "lerna run test"
},
"workspaces": [
"packages/*"
],
"devDependencies": {
"@babel/core": "^7.28.4",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@babel/register": "^7.28.3",
"babel-eslint": "^10.1.0",
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-lodash": "^3.3.4",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.21.1",
"lerna": "^3.22.1",
"mocha": "10",
"open": "^7.3.0",
"prettier": "^3.2.4",
"rimraf": "^3.0.2"
},
"resolutions": {
"@babel/runtime": "7.28.4"
}
}
================================================
FILE: packages/mjml/README.md
================================================
# MJML 4
<p style="text-align: center;" >
<a href="http://mjml.io" target="_blank">
<img width="250"src="https://mjml.io/assets/img/litmus/mjmlbymailjet.png">
</a>
</p>
<p style="text-align: center;" >
<a href="https://github.com/mjmlio/mjml/actions">
<img src="https://github.com/mjmlio/mjml/workflows/Mjml%20CI/badge.svg?branch=master" alt="github actions">
</a>
<a href="https://www.codacy.com/app/gbadi/mjml">
<img src="https://api.codacy.com/project/badge/grade/575339cb861f4ff4b0dbb3f9e1759c35"/>
</a>
</p>
<p style="text-align: center;" >
| <b><a href="#introduction">Introduction</a></b>
| <b><a href="#installation">Installation</a></b>
| <b><a href="#usage">Usage</a></b>
| <b><a href="#contribute">Contribute</a></b> |
</p>
---
# Introduction
`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.
<p style="text-align: center;" >
<a href="http://mjml.io" target="_blank">
<img width="75%" src="https://cloud.githubusercontent.com/assets/6558790/12450760/ee034178-bf85-11e5-9dda-98d0c8f9f8d6.png">
</a>
</p>
# Installation
You 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.
```bash
npm install mjml
```
# Usage
## Online
Don't want to install anything? Use the free online editor!
<p style="text-align: center;" >
<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>
</p>
<br>
## Applications and plugins
MJML comes with an ecosystem of tools and plugins, check out:
- The [MJML App](https://mjmlio.github.io/mjml-app/) (MJML is included)
- [Visual Studio Code plugin](https://github.com/mjmlio/vscode-mjml) (MJML is included)
- [Atom plugin](https://atom.io/users/mjmlio) (MJML needs to be installed separately)
- [Sublime Text plugin](https://packagecontrol.io/packages/MJML-syntax) (MJML needs to be installed separately)
For more tools, check the [Community](https://mjml.io/community) page.
## Command line interface
> Compiles the file and outputs the HTML generated in `output.html`
```bash
mjml input.mjml -o output.html
```
You can pass optional `arguments` to the CLI and combine them.
argument | description | default value
---------|--------|--------------
`mjml -m [input]` | Migrates a v3 MJML file to the v4 syntax | NA
`mjml [input] -o [output]` | Writes the output to [output] | NA
`mjml [input] -s` | Writes the output to `stdout` | NA
`mjml -w [input]` | Watches the changes made to `[input]` (file or folder) | NA
`mjml [input] --config.beautify` | Beautifies the output (`true` or `false`) | true
`mjml [input] --config.minify` | Minifies the output (`true` or `false`) | false
See [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more information about config options.
## Inside Node.js
```javascript
import mjml2html from 'mjml'
/*
Compile an mjml string
*/
const htmlOutput = mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`, options)
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)
```
You can pass optional `options` as an object to the `mjml2html` function:
option | unit | description | default value
-------------|--------|--------------|---------------
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)
keepComments | boolean | Option to keep comments in the HTML output | true
ignoreIncludes | boolean | Option to ignore mj-includes | false
beautify | boolean | Option to beautify the HTML output | false
minify | boolean | Option to minify the HTML output | false
validationLevel | string | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): 'strict', 'soft', 'skip' | 'soft'
filePath | string | Path of file, used for relative paths in mj-includes | '.'
preprocessors | array of functions | Preprocessors applied to the xml before parsing. Input must be xml, not json. Functions must be (xml: string) => string | []
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 | NA
minifyOptions | Options for html minifier, see [mjml-cli documentation](https://github.com/mjmlio/mjml/blob/master/packages/mjml-cli/README.md) for more info | NA
mjmlConfigPath | string | The path or directory of the `.mjmlconfig` file (for custom components use) | `process.cwd()`
useMjmlConfigOptions | Allows to use the `options` attribute from `.mjmlconfig` file | false
Note 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.
## API
A 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.
# MJML Slack
MJML 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.
================================================
FILE: packages/mjml/bin/mjml
================================================
#!/usr/bin/env node
require('../lib/index')
require('mjml-cli')
================================================
FILE: packages/mjml/package.json
================================================
{
"name": "mjml",
"description": "MJML: the only framework that makes responsive-email easy",
"version": "4.18.0",
"main": "lib/index.js",
"bin": {
"mjml": "bin/mjml"
},
"files": [
"bin",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib --root-mode upward",
"test": "mocha ./test/*.test.js"
},
"dependencies": {
"@babel/runtime": "^7.28.4",
"mjml-cli": "4.18.0",
"mjml-core": "4.18.0",
"mjml-migrate": "4.18.0",
"mjml-preset-core": "4.18.0",
"mjml-validator": "4.18.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"chai": "^4.1.1",
"chai-spies": "^1.0.0",
"cheerio": "1.0.0-rc.12",
"lodash": "^4.17.21",
"rimraf": "^3.0.2"
}
}
================================================
FILE: packages/mjml/src/index.js
================================================
import mjml2html, { components, assignComponents } from 'mjml-core'
import { dependencies, assignDependencies } from 'mjml-validator'
import presetCore from 'mjml-preset-core'
assignComponents(components, presetCore.components)
assignDependencies(dependencies, presetCore.dependencies)
export default mjml2html
================================================
FILE: packages/mjml/test/accordion-fontFamily.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-accordion font-family inheritance', function () {
it('should render correct font-family in CSS style values on accordion-title and accordion-text', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-accordion css-class="my-accordion-1" font-family="serif">
<mj-accordion-element>
<mj-accordion-title>Why use an accordion?</mj-accordion-title>
<mj-accordion-text>
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.
</mj-accordion-text>
</mj-accordion-element>
</mj-accordion>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-accordion css-class="my-accordion-2" font-family="serif">
<mj-accordion-element font-family="sans-serif">
<mj-accordion-title font-family="monospace">Why use an accordion?</mj-accordion-title>
<mj-accordion-text font-family="monospace">
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.
</mj-accordion-text>
</mj-accordion-element>
</mj-accordion>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// style values should be correct
chai
.expect(
$(
'.my-accordion-1 .mj-accordion-title td:first-child, .my-accordion-1 .mj-accordion-content td:first-child',
'.my-accordion-2 .mj-accordion-title td:first-child, .my-accordion-2 .mj-accordion-content td:first-child, ',
)
.map(function getAttr() {
const start = $(this).attr('style').indexOf('font-family:') + 12
const end = $(this).attr('style').indexOf(';', start)
const result = $(this).attr('style').substring(start, end)
return result
})
.get(),
'Font-family in CSS style values on accordion-title',
)
.to.eql(['serif', 'serif', 'monospace', 'monospace'])
})
})
================================================
FILE: packages/mjml/test/accordion-padding.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-accordion padding-X', function () {
it('should render correct padding in CSS style values on accordion-title and accordion-text', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-accordion>
<mj-accordion-element>
<mj-accordion-title padding="20px" padding-bottom="40px" padding-left="40px" padding-right="40px" padding-top="40px">Why use an accordion?</mj-accordion-title>
<mj-accordion-text padding="20px" padding-bottom="40px" padding-left="40px" padding-right="40px" padding-top="40px">
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.
</mj-accordion-text>
</mj-accordion-element>
</mj-accordion>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
function extractPadding(style, prop) {
const start = style.indexOf(`${prop}:`) + prop.length + 1
const end = style.indexOf(';', start)
return style.substring(start, end).trim()
}
const paddings = [
'padding-left',
'padding-right',
'padding-top',
'padding-bottom',
]
const results = paddings.map((padding) =>
$(
'.mj-accordion-title td:first-child, .mj-accordion-content td:first-child',
)
.map(function () {
const style = $(this).attr('style')
return extractPadding(style, padding)
})
.get(),
)
// Each padding should be ['40px', '40px']
paddings.forEach((padding, idx) => {
chai
.expect(
results[idx],
`${padding} in CSS style values on accordion-title and accordion-text`,
)
.to.eql(['40px', '40px'])
})
})
})
================================================
FILE: packages/mjml/test/accordionTitle-fontWeight.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-accordion-title font-weight', function () {
it('should render correct font-weight in CSS style values on accordion-title', function () {
const input = `
<mjml>
<mj-head>
<mj-attributes>
<mj-accordion border="none" padding="1px" />
<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" />
<mj-accordion-title font-family="Roboto, Open Sans, Helvetica, Arial, sans-serif" background-color="#fff" color="#031017" padding="15px" font-size="18px" />
<mj-accordion-text font-family="Open Sans, Helvetica, Arial, sans-serif" background-color="#fafafa" padding="15px" color="#505050" font-size="14px" />
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px" background-color="#ffffff">
<mj-column background-color="#dededd">
<mj-accordion>
<mj-accordion-element>
<mj-accordion-title font-weight="bold" css-class="accordion-title">Why use an accordion?</mj-accordion-title>
<mj-accordion-text font-weight="bold">
<span style="line-height:20px">
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.
</span>
</mj-accordion-text>
</mj-accordion-element>
<mj-accordion-element>
<mj-accordion-title font-weight="700" css-class="accordion-title">How it works</mj-accordion-title>
<mj-accordion-text font-weight="700">
<span style="line-height:20px">
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.
</span>
</mj-accordion-text>
</mj-accordion-element>
</mj-accordion>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// style values should be correct
chai
.expect(
$('.accordion-title')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('font-weight:') + 12
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Font-weight in CSS style values on accordion-title',
)
.to.eql(['bold', '700'])
})
})
================================================
FILE: packages/mjml/test/carousel-hoverSupported.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-carousel-thumbnail thumbnails supported', function () {
it('should render correct display in CSS style values on mj-carousel-thumbnail', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-carousel thumbnails="supported">
<mj-carousel-image src="https://placehold.co/450x300/333/ccc/png" />
<mj-carousel-image src="https://placehold.co/450x300/ccc/000/png" />
<mj-carousel-image src="https://placehold.co/450x300/f45e43/fff/png" />
</mj-carousel>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// style values should be correct
chai
.expect(
$('.mj-carousel-thumbnail')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('display:') + 8
const end = $(this).attr('style').indexOf(';', start)
const result = $(this).attr('style').substring(start, end)
return result
})
.get(),
'Display CSS style values on mj-carousel-thumbnail',
)
.to.eql(['none', 'none', 'none'])
})
})
================================================
FILE: packages/mjml/test/column-border-radius.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-column border-radius', function () {
it('should render correct border-radius / inner-border-radius (and border-collapse) in CSS style values on mj-column', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column border-radius="50px" inner-border-radius="40px" padding="50px" border="5px solid #000" inner-border="5px solid #666">
<mj-text>Hello World</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// border radius values should be correct
chai
.expect(
$(
'.mj-column-per-100 > table > tbody > tr > td, .mj-column-per-100 > table > tbody > tr > td > table',
)
.map(function getAttr() {
const start = $(this).attr('style').indexOf('border-radius:') + 14
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Border-radius / inner-border-radius in CSS style values on mj-column',
)
.to.eql(['50px', '40px'])
// border collapse values should be correct
chai
.expect(
$(
'.mj-column-per-100 > table > tbody > tr > td, .mj-column-per-100 > table > tbody > tr > td > table',
)
.map(function getAttr() {
const start = $(this).attr('style').indexOf('border-collapse:') + 16
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Border-collapse in CSS style values on mj-column',
)
.to.eql(['separate', 'separate'])
})
})
================================================
FILE: packages/mjml/test/html-attributes.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const { sortBy } = require('lodash')
const mjml = require('../lib')
describe('html-attributes', function () {
it('should put the attributes at the right place', function () {
const input = `
<mjml>
<mj-head>
<mj-html-attributes>
<mj-selector path=".text div">
<mj-html-attribute name="data-id">42</mj-html-attribute>
</mj-selector>
<mj-selector path=".image td">
<mj-html-attribute name="data-name">43</mj-html-attribute>
</mj-selector>
</mj-html-attributes>
</mj-head>
<mj-body>
<mj-raw>{ if item < 5 }</mj-raw>
<mj-section css-class="section">
<mj-column>
<mj-raw>{ if item > 10 }</mj-raw>
<mj-text css-class="text">
Hello World! { item }
</mj-text>
<mj-raw>{ end if }</mj-raw>
<mj-text css-class="text">
Hello World! { item + 1 }
</mj-text>
<mj-image css-class="image" src="https://via.placeholder.com/150x30"/>
</mj-column>
</mj-section>
<mj-raw>{ end if }</mj-raw>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// should put the attributes at the right place
chai
.expect(
$('.text div')
.map(function getAttr() {
return $(this).attr('data-id')
})
.get(),
'Custom attributes added on texts',
)
.to.eql(['42', '42'])
chai
.expect(
$('.image td')
.map(function getAttr() {
return $(this).attr('data-name')
})
.get(),
'Custom attributes added on image',
)
.to.eql(['43'])
// should not alter templating syntax, or move the content that is outside any tag (mj-raws)
const expected = [
'{ if item < 5 }',
'class="section"',
'{ if item > 10 }',
'class="text"',
'{ item }',
'{ end if }',
'{ item + 1 }',
]
const indexes = expected.map((str) => html.indexOf(str))
chai.expect(indexes, 'Templating syntax unaltered').to.not.include(-1)
chai
.expect(sortBy(indexes), 'Mj-raws kept same positions')
.to.deep.eql(indexes)
})
})
================================================
FILE: packages/mjml/test/html-comments.test.js
================================================
const chai = require('chai')
const mjml = require('../lib')
describe('HTML comments', function () {
it('should not alter the whitespace between the opening/closing comment tags and the comment content', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
<p>View source to see comments below</p>
<!-- comment with standard spaces -->
<br>
<!--comment without spaces-->
<br>
<!-- comment with 5 spaces -->
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
// should not alter templating syntax, or move the content that is outside any tag (mj-raws)
const expected = [
'<!-- comment with standard spaces -->',
'<!--comment without spaces-->',
'<!-- comment with 5 spaces -->',
]
const indexes = expected.map((str) => html.indexOf(str))
chai.expect(indexes, 'Cmment syntax unaltered').to.not.include(-1)
})
})
================================================
FILE: packages/mjml/test/lazy-head-style.test.js
================================================
const chai = require('chai')
const spies = require('chai-spies')
const mjml = require('../lib')
const {
HeadComponent,
registerComponent,
} = require('../../mjml-core/lib/index')
chai.use(spies)
describe('lazy-head-style', function () {
it('should call style with correct breakpoint', function () {
const addStyle = chai.spy(
(breakpoint) => `
@media only screen and (max-width:${breakpoint}) {
h1 {
font-size: 20px;
}
}
`,
)
class HeadComponentWithFunctionStyle extends HeadComponent {
handler() {
const { add } = this.context
add('style', addStyle)
}
}
HeadComponentWithFunctionStyle.componentName =
'mj-head-component-with-function-style'
HeadComponentWithFunctionStyle.endingTag = true
HeadComponentWithFunctionStyle.allowedAttributes = {}
registerComponent(HeadComponentWithFunctionStyle)
mjml(`
<mjml>
<mj-head>
<mj-head-component-with-function-style />
<mj-breakpoint width="300px" />
</mj-head>
<mj-body>
</mj-body>
</mjml>
`)
chai.expect(addStyle).to.have.been.called.with('300px')
})
})
================================================
FILE: packages/mjml/test/navbar-ico-padding.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-navbar ico-padding-X', function () {
it('should render correct padding in CSS style values on navbar hamburger icon', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-navbar hamburger="hamburger" ico-padding="20px" ico-padding-bottom="20px" ico-padding-left="30px" ico-padding-right="40px" ico-padding-top="50px" >
<mj-navbar-link href="/gettings-started-onboard" color="#ffffff">Getting started</mj-navbar-link>
<mj-navbar-link href="/try-it-live" color="#ffffff">Try it live</mj-navbar-link>
</mj-navbar>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
function extractPadding(style, prop) {
const start = style.indexOf(`${prop}:`) + prop.length + 1
const end = style.indexOf(';', start)
return style.substring(start, end).trim()
}
const paddings = [
'padding-bottom',
'padding-left',
'padding-right',
'padding-top',
]
const results = paddings.map((padding) =>
$('.mj-menu-label')
.map(function () {
const style = $(this).attr('style')
return extractPadding(style, padding)
})
.get(),
)
// Padding should be ['20px', '30px', '40px', '50px']
const expected = {
'padding-bottom': ['20px'],
'padding-left': ['30px'],
'padding-right': ['40px'],
'padding-top': ['50px'],
}
paddings.forEach((padding, idx) => {
chai
.expect(results[idx], `${padding} in CSS style values on navbar icon`)
.to.eql(expected[padding])
})
})
})
================================================
FILE: packages/mjml/test/social-align.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-social-element align', function () {
it('should render correct align in CSS style values on mj-social-element', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-social mode="vertical">
<mj-social-element name="facebook" href="https://mjml.io/" icon-position="right" align="right"css-class="my-social-element">
Facebook
</mj-social-element>
</mj-social>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// align values should be correct
chai
.expect(
$('.my-social-element > td:first-child')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('text-align:') + 11
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'align values on social elements',
)
.to.eql(['right'])
})
})
================================================
FILE: packages/mjml/test/social-icon-height.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-social icon-height', function () {
it('should render correct icon-height align in CSS style values on mj-social', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column css-class="my-social-element">
<mj-social icon-height="40px">
<mj-social-element name="facebook" href="https://mjml.io/" css-class="my-social-element">
Facebook
</mj-social-element>
</mj-social>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// height values should be correct
chai
.expect(
$('.my-social-element > td > table > tbody > tr > td')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('height:') + 7
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'icon-height values on social elements',
)
.to.eql(['40px'])
chai
.expect(
$('.my-social-element > td > table > tbody > tr > td img')
.map(function getAttr() {
return $(this).attr('height')
})
.get(),
)
.to.satisfy((arr) => arr.every((val) => !val))
})
})
================================================
FILE: packages/mjml/test/table-cellspacing.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-table cellspacing', function () {
it('should render correct cellspacing (and border-collapse) in HTML tag / CSS style values on mj-table', function () {
const input = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-table border="1px solid #000" width="auto" cellpadding="20" cellspacing="10" css-class="my-table">
<tr style="border-bottom:1px solid #000;text-align:left;">
<th style="background:#ddd;">Year</th>
<th style="background:#ddd;">Language</th>
<th style="background:#ddd;">Inspired from</th>
</tr>
<tr>
<td style="background:#ddd;">1995</td>
<td style="background:#ddd;">PHP</td>
<td style="background:#ddd;">C, Shell Unix</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// border radius values should be correct
chai
.expect(
$('.my-table > table')
.map(function getAttr() {
return $(this).attr('cellspacing')
})
.get(),
'cellspacing values on table elements',
)
.to.eql(['10'])
// border collapse values should be correct
chai
.expect(
$('.my-table > table')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('border-collapse:') + 16
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Border-collapse in CSS style values on mj-table',
)
.to.eql(['separate'])
})
})
================================================
FILE: packages/mjml/test/tableWidth.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-table width', function () {
it('should render correct width in CSS style values on mj-table', function () {
const input = `
<mjml>
<mj-body>
<mj-wrapper>
<mj-section>
<mj-column>
<mj-table css-class="table">
<tr>
<th style="border: 1px solid black;text-align: left;">
Default Width
</th>
<td style="border: 1px solid black;">
100%
</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-table width="500px" css-class="table">
<tr>
<th style="border: 1px solid black;text-align: left;">
Pixel Width
</th>
<td style="border: 1px solid black;">
500px
</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-table width="80%" css-class="table">
<tr>
<th style="border: 1px solid black;text-align: left;">
Percentage Width
</th>
<td style="border: 1px solid black;">
80%
</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
<mj-section css-class="section">
<mj-column>
<mj-table width="auto" css-class="table">
<tr>
<th style="border: 1px solid black;text-align: left;">
Auto Width
</th>
<td style="border: 1px solid black;">
Auto
</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// width values should be correct
chai
.expect(
$('.table table')
.map(function getAttr() {
return $(this).attr('width')
})
.get(),
'Width values on tables',
)
.to.eql(['100%', '500', '80%', 'auto'])
// style values should be correct
chai
.expect(
$('.table table')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('width:') + 6
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Width in CSS style values on tables',
)
.to.eql(['100%', '500px', '80%', 'auto'])
})
})
================================================
FILE: packages/mjml/test/wrapper-border-radius.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-wrapper and mj-section border-radius', function () {
it('should render correct border-radius (and border-collapse) in CSS style values on mj-wrapper and mj-section', function () {
const input = `
<mjml>
<mj-body>
<mj-wrapper border="1px solid red" border-radius="10px">
<mj-section>
<mj-column>
<mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello World</mj-text>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// border radius values should be correct
chai
.expect(
$(
'body > div > div > table:first-child > tbody > tr > td, body > div > div',
)
.map(function getAttr() {
const start = $(this).attr('style').indexOf('border-radius:') + 14
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Border-radius in CSS style values on mj-wrapper',
)
.to.eql(['10px', '10px'])
// overflow value should be correct
chai
.expect(
$('body > div > div')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('overflow:') + 9
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Overflow in CSS style values on mj-wrapper',
)
.to.eql(['hidden'])
// border collapse values should be correct
chai
.expect(
$('body > div > div > table:first-child')
.map(function getAttr() {
const start = $(this).attr('style').indexOf('border-collapse:') + 16
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
})
.get(),
'Border-collapse in CSS style values on mj-wrapper',
)
.to.eql(['separate'])
})
})
================================================
FILE: packages/mjml/test/wrapper-gap.test.js
================================================
const chai = require('chai')
const { load } = require('cheerio')
const mjml = require('../lib')
describe('mj-wrapper gap', function () {
it('should render correct gap values in CSS style values on children mj-section', function () {
const input = `
<mjml>
<mj-body>
<mj-wrapper gap="20px" css-class="my-wrapper" background-color="#000">
<mj-section css-class="my-section" background-color="#f45e43" padding="10px">
<mj-column>
<mj-text>Section 1</mj-text>
</mj-column>
</mj-section>
<mj-section css-class="my-section" background-color="#ccc" padding="10px">
<mj-column>
<mj-text>Section 2</mj-text>
</mj-column>
</mj-section>
<mj-section css-class="my-section" background-color="#333" padding="10px">
<mj-column>
<mj-text color="#fff">Section 3</mj-text>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>
`
const { html } = mjml(input)
const $ = load(html)
// gap values should be correct
chai
.expect(
$('.my-section')
.map(function getAttr() {
const str = $(this).attr('style')
const substr = 'margin-top:'
if (str.includes(substr)) {
const start = $(this).attr('style').indexOf(substr) + 11
const end = $(this).attr('style').indexOf(';', start)
return $(this).attr('style').substring(start, end)
}
return undefined
})
.get(),
'Gap in CSS style values on mj-wrapper',
)
.to.eql(['20px', '20px'])
})
})
================================================
FILE: packages/mjml-accordion/README.md
================================================
### mj-accordion
An interactive MJML component that stacks content in tabs, so the information is collapsed and only the titles are visible.
Readers can interact by clicking on the tabs to reveal the content, providing a better experience for mobile users by reducing the amount of scrolling.
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/accordion-example.gif" alt="accordion" />
</figure>
<div class="alert alert-note" role="alert">
<p>Note</p>
<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>
<p>More information about ending tags <a href="#ending-tags">in this section</a>.</p>
</div>
```xml
<mjml>
<mj-head>
<mj-attributes>
<mj-accordion border="none" padding="1px" />
<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" />
<mj-accordion-title font-family="Roboto, Open Sans, Helvetica, Arial, sans-serif" background-color="#fff" color="#031017" padding="15px" font-size="18px" />
<mj-accordion-text font-family="Open Sans, Helvetica, Arial, sans-serif" background-color="#fafafa" padding="15px" color="#505050" font-size="14px" />
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px" background-color="#ffffff">
<mj-column background-color="#dededd">
<mj-accordion>
<mj-accordion-element>
<mj-accordion-title>Why use an accordion?</mj-accordion-title>
<mj-accordion-text>
<span style="line-height:20px">
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.
</span>
</mj-accordion-text>
</mj-accordion-element>
<mj-accordion-element>
<mj-accordion-title>How it works</mj-accordion-title>
<mj-accordion-text>
<span style="line-height:20px">
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.
</span>
</mj-accordion-text>
</mj-accordion-element>
</mj-accordion>
</mj-column>
</mj-section>
</mj-body>
</mjml>
```
#### Attributes
| attribute | accepts | description | default value |
| -------------------------- | ----------------------- | -------------------------------------------------- | -------------------------------------- |
| border | string | CSS border format | `2px solid black` |
| container-background-color | CSS color formats | background-color of the cell | |
| css-class | string | class name, added to the root HTML element created | |
| font-family | string | font | `Ubuntu, Helvetica, Arial, sans-serif` |
| icon-align | `top` `middle` `bottom` | icon alignment | `middle` |
| icon-height | `px` `%` | icon height | `32px` |
| icon-position | left,<br>right | display icon left or right | `right` |
| icon-unwrapped-alt | string | alt text when accordion is unwrapped | `-` |
| icon-unwrapped-url | string | icon when accordion is unwrapped | `https://i.imgur.com/w4uTygT.png` |
| icon-width | `px` `%` | icon width | `32px` |
| icon-wrapped-alt | string | alt text when accordion is wrapped | `+` |
| icon-wrapped-url | string | icon when accordion is wrapped | `https://i.imgur.com/bIXv1bk.png` |
| padding | `px` `%` | accordion padding, supports up to 4 parameters | `10px 25px` |
| padding-bottom | `px` `%` | accordion bottom padding | |
| padding-left | `px` `%` | accordion left padding | |
| padding-right | `px` `%` | accordion right padding | |
| padding-top | `px` `%` | accordion top padding | |
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live/components/accordion">Try it live</a></p>
#### mj-accordion-element
Creates an accordion title/text pair. An accordion can have any number of these pairs.
<div class="alert alert-note" role="alert">
<p>Note</p>
<p>Inheritance applies for attributes supported in both <code>mj-accordion</code> and
<code>mj-accordion-element</code> except where the latter overrides.</p>
</div>
##### Attributes
| attribute | accepts | description | default value |
| ------------------ | ----------------------- | ----------------------------------------------------------------------------------------- | ------------- |
| background-color | CSS color formats | background color | |
| border | string | CSS border format. <br>affects each horizontal border in the accordion except the top one | |
| css-class | string | class name, added to the root HTML element created | |
| font-family | string | font | |
| icon-align | `top` `middle` `bottom` | icon alignment | |
| icon-height | `px` `%` | icon width | `32px` |
| icon-position | `left` `right` | postion of icon | |
| icon-unwrapped-alt | string | alt text when accordion is unwrapped | |
| icon-unwrapped-url | string | icon when accordion is unwrapped | |
| icon-width | `px` `%` | icon height | `32px` |
| icon-wrapped-alt | string | alt text when accordion is wrapped | |
| icon-wrapped-url | string | icon when accordion is wrapped | |
#### mj-accordion-title
Displays the title in a title/text pair.
##### Attributes
| attribute | accepts | description | default value |
| ---------------- | ----------------- | ---------------------------------------------------- | ------------- |
| background-color | CSS color formats | background color | |
| color | CSS color formats | text color | |
| css-class | string | class name, added to the root HTML element created | |
| font-family | string | font family | |
| font-size | `px` | font size | `13px` |
| font-weight | string | text thickness | |
| padding | `px` `%` | accordion title padding, supports up to 4 parameters | `16px` |
| padding-bottom | `px` `%` | accordion title bottom padding | |
| padding-left | `px` `%` | accordion title left padding | |
| padding-right | `px` `%` | accordion title right padding | |
| padding-top | `px` `%` | accordion title top padding | |
#### mj-accordion-text
Displays the text in a title/text pair.
##### Attributes
| attribute | accepts | description | default value |
| ---------------- | ----------------- | --------------------------------------------------- | ------------- |
| background-color | CSS color formats | background color | |
| color | CSS color formats | text color | |
| css-class | string | class name, added to the root HTML element created | |
| font-family | string | font family | |
| font-size | `px` | font size | `13px` |
| font-weight | string | text thickness | |
| letter-spacing | `px` `em` | letter spacing | |
| line-height | `px` `%` | space between the lines | `1` |
| padding | `px` `%` | accordion text padding, supports up to 4 parameters | `16px` |
| padding-bottom | `px` `%` | accordion text bottom padding | |
| padding-left | `px` `%` | accordion text left padding | |
| padding-right | `px` `%` | accordion text right padding | |
| padding-top | `px` `%` | accordion text top padding | |
================================================
FILE: packages/mjml-accordion/package.json
================================================
{
"name": "mjml-accordion",
"description": "mjml-accordion",
"version": "4.18.0",
"main": "lib/index.js",
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml-accordion"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib --root-mode upward"
},
"dependencies": {
"@babel/runtime": "^7.28.4",
"lodash": "^4.17.21",
"mjml-core": "4.18.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"rimraf": "^3.0.2"
}
}
================================================
FILE: packages/mjml-accordion/src/Accordion.js
================================================
import { BodyComponent } from 'mjml-core'
export default class MjAccordion extends BodyComponent {
static componentName = 'mj-accordion'
static allowedAttributes = {
'container-background-color': 'color',
border: 'string',
'font-family': 'string',
'icon-align': 'enum(top,middle,bottom)',
'icon-width': 'unit(px,%)',
'icon-height': 'unit(px,%)',
'icon-wrapped-url': 'string',
'icon-wrapped-alt': 'string',
'icon-unwrapped-url': 'string',
'icon-unwrapped-alt': 'string',
'icon-position': 'enum(left,right)',
'padding-bottom': 'unit(px,%)',
'padding-left': 'unit(px,%)',
'padding-right': 'unit(px,%)',
'padding-top': 'unit(px,%)',
padding: 'unit(px,%){1,4}',
}
static defaultAttributes = {
border: '2px solid black',
'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',
'icon-align': 'middle',
'icon-wrapped-url': 'https://i.imgur.com/bIXv1bk.png',
'icon-wrapped-alt': '+',
'icon-unwrapped-url': 'https://i.imgur.com/w4uTygT.png',
'icon-unwrapped-alt': '-',
'icon-position': 'right',
'icon-height': '32px',
'icon-width': '32px',
padding: '10px 25px',
}
headStyle = () =>
`
noinput.mj-accordion-checkbox { display:block!important; }
@media yahoo, only screen and (min-width:0) {
.mj-accordion-element { display:block; }
input.mj-accordion-checkbox, .mj-accordion-less { display:none!important; }
input.mj-accordion-checkbox + * .mj-accordion-title { cursor:pointer; touch-action:manipulation; -webkit-user-select:none; -moz-user-select:none; user-select:none; }
input.mj-accordion-checkbox + * .mj-accordion-content { overflow:hidden; display:none; }
input.mj-accordion-checkbox + * .mj-accordion-more { display:block!important; }
input.mj-accordion-checkbox:checked + * .mj-accordion-content { display:block; }
input.mj-accordion-checkbox:checked + * .mj-accordion-more { display:none!important; }
input.mj-accordion-checkbox:checked + * .mj-accordion-less { display:block!important; }
}
.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; }
.moz-text-html input.mj-accordion-checkbox + * .mj-accordion-content { overflow: hidden; display: block; }
.moz-text-html input.mj-accordion-checkbox + * .mj-accordion-ico { display: none; }
@goodbye { @gmail }
`
getStyles() {
return {
table: {
width: '100%',
'border-collapse': 'collapse',
border: this.getAttribute('border'),
'border-bottom': 'none',
'font-family': this.getAttribute('font-family'),
},
}
}
getChildContext() {
return {
...this.context,
accordionFontFamily: this.getAttribute('font-family'),
}
}
render() {
const childrenAttr = [
'border',
'icon-align',
'icon-width',
'icon-height',
'icon-position',
'icon-wrapped-url',
'icon-wrapped-alt',
'icon-unwrapped-url',
'icon-unwrapped-alt',
].reduce(
(res, val) => ({
...res,
[val]: this.getAttribute(val),
}),
{},
)
return `
<table
${this.htmlAttributes({
cellspacing: '0',
cellpadding: '0',
class: 'mj-accordion',
style: 'table',
})}
>
<tbody>
${this.renderChildren(this.props.children, {
attributes: childrenAttr,
})}
</tbody>
</table>
`
}
}
================================================
FILE: packages/mjml-accordion/src/AccordionElement.js
================================================
import { BodyComponent } from 'mjml-core'
import { find } from 'lodash'
import conditionalTag from 'mjml-core/lib/helpers/conditionalTag'
import AccordionText from './AccordionText'
import AccordionTitle from './AccordionTitle'
export default class MjAccordionElement extends BodyComponent {
static componentName = 'mj-accordion-element'
static allowedAttributes = {
'background-color': 'color',
border: 'string',
'font-family': 'string',
'icon-align': 'enum(top,middle,bottom)',
'icon-width': 'unit(px,%)',
'icon-height': 'unit(px,%)',
'icon-wrapped-url': 'string',
'icon-wrapped-alt': 'string',
'icon-unwrapped-url': 'string',
'icon-unwrapped-alt': 'string',
'icon-position': 'enum(left,right)',
}
static defaultAttributes = {
title: {
img: {
width: '32px',
height: '32px',
},
},
}
getStyles() {
return {
td: {
padding: '0px',
'background-color': this.getAttribute('background-color'),
},
label: {
'font-size': '13px',
'font-family': this.getAttribute('font-family'),
},
input: {
display: 'none',
},
}
}
handleMissingChildren() {
const { children } = this.props
const childrenAttr = [
'border',
'icon-align',
'icon-width',
'icon-height',
'icon-position',
'icon-wrapped-url',
'icon-wrapped-alt',
'icon-unwrapped-url',
'icon-unwrapped-alt',
].reduce(
(res, val) => ({
...res,
[val]: this.getAttribute(val),
}),
{},
)
const result = []
if (!find(children, { tagName: 'mj-accordion-title' })) {
result.push(
new AccordionTitle({
attributes: childrenAttr,
context: this.getChildContext(),
}).render(),
)
}
result.push(this.renderChildren(children, { attributes: childrenAttr }))
if (!find(children, { tagName: 'mj-accordion-text' })) {
result.push(
new AccordionText({
attributes: childrenAttr,
context: this.getChildContext(),
}).render(),
)
}
return result.join('\n')
}
getChildContext() {
return {
...this.context,
elementFontFamily: this.getAttribute('font-family'),
}
}
render() {
return `
<tr
${this.htmlAttributes({
class: this.getAttribute('css-class'),
})}
>
<td ${this.htmlAttributes({ style: 'td' })}>
<label
${this.htmlAttributes({
class: 'mj-accordion-element',
style: 'label',
})}
>
${conditionalTag(
`
<input
${this.htmlAttributes({
class: 'mj-accordion-checkbox',
type: 'checkbox',
style: 'input',
})}
/>
`,
true,
)}
<div>
${this.handleMissingChildren()}
</div>
</label>
</td>
</tr>
`
}
}
================================================
FILE: packages/mjml-accordion/src/AccordionText.js
================================================
import { BodyComponent } from 'mjml-core'
export default class MjAccordionText extends BodyComponent {
static componentName = 'mj-accordion-text'
static endingTag = true
static allowedAttributes = {
'background-color': 'color',
'font-size': 'unit(px)',
'font-family': 'string',
'font-weight': 'string',
'letter-spacing': 'unitWithNegative(px,em)',
'line-height': 'unit(px,%,)',
color: 'color',
'padding-bottom': 'unit(px,%)',
'padding-left': 'unit(px,%)',
'padding-right': 'unit(px,%)',
'padding-top': 'unit(px,%)',
padding: 'unit(px,%){1,4}',
}
static defaultAttributes = {
'font-size': '13px',
'line-height': '1',
padding: '16px',
}
getStyles() {
return {
td: {
background: this.getAttribute('background-color'),
'font-size': this.getAttribute('font-size'),
'font-family': this.resolveFontFamily(),
'font-weight': this.getAttribute('font-weight'),
'letter-spacing': this.getAttribute('letter-spacing'),
'line-height': this.getAttribute('line-height'),
color: this.getAttribute('color'),
padding: this.getAttribute('padding'),
'padding-bottom': this.getAttribute('padding-bottom'),
'padding-left': this.getAttribute('padding-left'),
'padding-right': this.getAttribute('padding-right'),
'padding-top': this.getAttribute('padding-top'),
},
table: {
width: '100%',
'border-bottom': this.getAttribute('border'),
},
}
}
renderContent() {
return `
<td
${this.htmlAttributes({
class: this.getAttribute('css-class'),
style: 'td',
})}
>
${this.getContent()}
</td>
`
}
resolveFontFamily() {
if (
this.props &&
this.props.rawAttrs &&
Object.prototype.hasOwnProperty.call(this.props.rawAttrs, 'font-family')
) {
return this.getAttribute('font-family')
}
if (this.context && this.context.elementFontFamily) {
return this.context.elementFontFamily
}
if (this.context && this.context.accordionFontFamily) {
return this.context.accordionFontFamily
}
return MjAccordionText.defaultAttributes.fontFamily
}
render() {
return `
<div
${this.htmlAttributes({
class: 'mj-accordion-content',
})}
>
<table
${this.htmlAttributes({
cellspacing: '0',
cellpadding: '0',
style: 'table',
})}
>
<tbody>
<tr>
${this.renderContent()}
</tr>
</tbody>
</table>
</div>
`
}
}
================================================
FILE: packages/mjml-accordion/src/AccordionTitle.js
================================================
import { BodyComponent } from 'mjml-core'
import conditionalTag from 'mjml-core/lib/helpers/conditionalTag'
export default class MjAccordionTitle extends BodyComponent {
static componentName = 'mj-accordion-title'
static endingTag = true
static allowedAttributes = {
'background-color': 'color',
color: 'color',
'font-size': 'unit(px)',
'font-family': 'string',
'font-weight': 'string',
'padding-bottom': 'unit(px,%)',
'padding-left': 'unit(px,%)',
'padding-right': 'unit(px,%)',
'padding-top': 'unit(px,%)',
padding: 'unit(px,%){1,4}',
}
static defaultAttributes = {
'font-size': '13px',
padding: '16px',
}
getStyles() {
return {
td: {
width: '100%',
'background-color': this.getAttribute('background-color'),
color: this.getAttribute('color'),
'font-size': this.getAttribute('font-size'),
'font-family': this.resolveFontFamily(),
'font-weight': this.getAttribute('font-weight'),
padding: this.getAttribute('padding'),
'padding-bottom': this.getAttribute('padding-bottom'),
'padding-left': this.getAttribute('padding-left'),
'padding-right': this.getAttribute('padding-right'),
'padding-top': this.getAttribute('padding-top'),
},
table: {
width: '100%',
'border-bottom': this.getAttribute('border'),
},
td2: {
padding: '16px',
background: this.getAttribute('background-color'),
'vertical-align': this.getAttribute('icon-align'),
},
img: {
display: 'none',
width: this.getAttribute('icon-width'),
height: this.getAttribute('icon-height'),
},
}
}
resolveFontFamily() {
if (
this.props &&
this.props.rawAttrs &&
Object.prototype.hasOwnProperty.call(this.props.rawAttrs, 'font-family')
) {
return this.getAttribute('font-family')
}
if (this.context && this.context.elementFontFamily) {
return this.context.elementFontFamily
}
if (this.context && this.context.accordionFontFamily) {
return this.context.accordionFontFamily
}
return MjAccordionTitle.defaultAttributes.fontFamily
}
renderTitle() {
return `
<td
${this.htmlAttributes({
class: this.getAttribute('css-class'),
style: 'td',
})}
>
${this.getContent()}
</td>
`
}
renderIcons() {
return conditionalTag(
`
<td
${this.htmlAttributes({
class: 'mj-accordion-ico',
style: 'td2',
})}
>
<img
${this.htmlAttributes({
src: this.getAttribute('icon-wrapped-url'),
alt: this.getAttribute('icon-wrapped-alt'),
class: 'mj-accordion-more',
style: 'img',
})}
/>
<img
${this.htmlAttributes({
src: this.getAttribute('icon-unwrapped-url'),
alt: this.getAttribute('icon-unwrapped-alt'),
class: 'mj-accordion-less',
style: 'img',
})}
/>
</td>
`,
true,
)
}
render() {
const contentElements = [this.renderTitle(), this.renderIcons()]
const content = (
this.getAttribute('icon-position') === 'right'
? contentElements
: contentElements.reverse()
).join('\n')
return `
<div ${this.htmlAttributes({ class: 'mj-accordion-title' })}>
<table
${this.htmlAttributes({
cellspacing: '0',
cellpadding: '0',
style: 'table',
})}
>
<tbody>
<tr>
${content}
</tr>
</tbody>
</table>
</div>
`
}
}
================================================
FILE: packages/mjml-accordion/src/index.js
================================================
export { default as Accordion } from './Accordion'
export { default as AccordionElement } from './AccordionElement'
export { default as AccordionText } from './AccordionText'
export { default as AccordionTitle } from './AccordionTitle'
================================================
FILE: packages/mjml-body/README.md
================================================
### mj-body
This 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.
```xml
<mjml>
<mj-body>
<!-- Your email goes here -->
</mj-body>
</mjml>
```
#### Attributes
| attribute | accepts | description | default value |
| ------------------- | ----------------- | -------------------------------------------------- | ------------- |
| background-color | CSS color formats | the general background color | |
| css-class | string | class name, added to the root HTML element created | |
| width | `px` | email width | `600px` |
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live/components/body">Try it live</a></p>
================================================
FILE: packages/mjml-body/package.json
================================================
{
"name": "mjml-body",
"description": "mjml-body",
"version": "4.18.0",
"main": "lib/index.js",
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml-body"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib --root-mode upward"
},
"dependencies": {
"@babel/runtime": "^7.28.4",
"lodash": "^4.17.21",
"mjml-core": "4.18.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"rimraf": "^3.0.2"
}
}
================================================
FILE: packages/mjml-body/src/index.js
================================================
import { BodyComponent } from 'mjml-core'
export default class MjBody extends BodyComponent {
static componentName = 'mj-body'
static allowedAttributes = {
width: 'unit(px)',
'background-color': 'color',
}
static defaultAttributes = {
width: '600px',
}
getChildContext() {
return {
...this.context,
containerWidth: this.getAttribute('width'),
}
}
getStyles() {
return {
div: {
'background-color': this.getAttribute('background-color'),
},
}
}
render() {
const {
setBackgroundColor,
globalData: { lang, dir, title },
} = this.context
setBackgroundColor(this.getAttribute('background-color'))
return `
<div
${this.htmlAttributes({
...(title && { 'aria-label': title }),
'aria-roledescription': 'email',
class: this.getAttribute('css-class'),
style: 'div',
role: 'article',
lang,
dir,
})}
>
${this.renderChildren()}
</div>
`
}
}
================================================
FILE: packages/mjml-browser/README.md
================================================
## MJML Browser build
This package allows MJML to be used client-side.
### Usage
It can be used as the regular mjml package :
```javascript
var mjml2html = require('mjml-browser')
var result = mjml2html(mjml, options)
```
### Unavailable features
- `mj-include` tags are unavailable and will be ignored.
- features involving the `.mjmlconfig` file are unavailable, which means no custom components.
================================================
FILE: packages/mjml-browser/browser-mocks/fs.js
================================================
module.exports = {
readFileSync: () => {
console.warn('fs should not be used in browser build') // eslint-disable-line no-console
return null
},
}
================================================
FILE: packages/mjml-browser/browser-mocks/path.js
================================================
const mockFn = () => {
console.warn('fs should not be used in browser build') // eslint-disable-line no-console
return null
}
module.exports = {
parse: mockFn,
resolve: mockFn,
join: mockFn,
dirname: mockFn,
isAbsolute: mockFn,
}
================================================
FILE: packages/mjml-browser/browser-mocks/uglify-js.js
================================================
module.exports = {}
================================================
FILE: packages/mjml-browser/package.json
================================================
{
"name": "mjml-browser",
"description": "MJML: the only framework that makes responsive-email easy",
"version": "4.18.0",
"main": "lib/index.js",
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml-browser"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "webpack"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-proposal-export-default-from": "^7.8.3",
"@babel/plugin-proposal-function-bind": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"babel-loader": "^8.0.6",
"rimraf": "^3.0.2",
"uglifyjs-webpack-plugin": "^2.1.3",
"webpack": "^4.36.1",
"webpack-cli": "^3.3.6"
}
}
================================================
FILE: packages/mjml-browser/webpack.config.js
================================================
const path = require('path')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
mode: 'production',
entry: {
"mjml": ['../mjml/lib/index'],
},
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
ecma: 5,
keep_classnames: true,
keep_fnames: true,
compress: {
passes: 2,
keep_fargs: false,
},
output: {
beautify: false,
},
mangle: true,
},
}),
],
},
output: {
library: 'mjml',
filename: 'index.js',
path: path.resolve(__dirname, './lib'),
libraryTarget: 'umd',
umdNamedDefine: true,
},
resolve: {
alias: {
'path': path.resolve(__dirname, 'browser-mocks/path'),
'fs': path.resolve(__dirname, 'browser-mocks/fs'),
'uglify-js': path.resolve(__dirname, 'browser-mocks/uglify-js'),
},
},
module: {
rules: [
{
test: /\.js$/,
exclude: path.join(__dirname, 'node_modules'),
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
],
plugins: [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"@babel/plugin-proposal-function-bind",
"@babel/plugin-proposal-export-default-from",
],
babelrc: false,
},
},
],
},
],
},
}
================================================
FILE: packages/mjml-button/README.md
================================================
### mj-button
Displays a customizable button.
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/button-example.png"
alt="desktop" width="150px" />
</figure>
<div class="alert alert-important" role="alert">
<p>Important</p>
<p>The <code>mj-button</code> won't be fully clickable because of client support.
See discussion at
<a href="https://github.com/mjmlio/mjml/issues/359">Issue #359</a>.</p>
</div>
```xml
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-button font-family="Helvetica" background-color="#f45e43" color="white">
Don't click me!
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
```
<div class="alert alert-note" role="alert">
<p>Note</p>
<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>
<p>More information about ending tags <a href="#ending-tags">in this section</a>.</p>
</div>
#### Attributes
| attribute | accepts | description | default value |
| -------------------------- | ---------------------------------- | ----------------------------------------------------- | -------------------------------------- |
| align | `left` `center` `right` | horizontal alignment | `center` |
| background-color | CSS color formats | button background-color | `#414141` |
| border | string | CSS border format | `none` |
| border-bottom | string | CSS border format | |
| border-left | string | CSS border format | |
| border-radius | string | border radius | `3px` |
| border-right | string | CSS border format | |
| border-top | string | CSS border format | |
| color | CSS color formats | text color | `#ffffff` |
| container-background-color | CSS color formats | button container background color | |
| css-class | string | class name, added to the root HTML element created | |
| font-family | string | font name | `Ubuntu, Helvetica, Arial, sans-serif` |
| font-size | `px` | text size | `13px` |
| font-style | string | CSS values, e.g. `normal` `italic` `oblique` | |
| font-weight | string | text thickness | `normal` |
| height | `px` `%` | button height | |
| href | string | URL format | |
| inner-padding | `px` `%` | inner button padding, <br>supports up to 4 parameters | `10px 25px` |
| letter-spacing | `px` `em` | letter-spacing | |
| line-height | `px` `%` | line-height on link | `120%` |
| name | string | specify the name attribute for the button link |
| padding | `px` `%` | button container padding, supports up to 4 parameters | `10px 25px` |
| padding-bottom | `px` `%` | button container bottom padding | |
| padding-left | `px` `%` | button container left padding | |
| padding-right | `px` `%` | button container right padding | |
| padding-top | `px` `%` | button container top padding | |
| rel | string | specify the rel attribute for the button link | |
| target | string | specify the target attribute for the button link | `_blank` |
| text-align | `left` `center` `right` | text-align button content | |
| text-decoration | string | underline/overline/none | `none` |
| text-transform | string | capitalize/uppercase/lowercase | `none` |
| title | string | tooltip & accessibility | |
| vertical-align | `top` `bottom` `middle` `vertical` | vertical alignment | `middle` |
| width | `px` `%` | button width | |
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live/components/button">Try it live</a></p>
================================================
FILE: packages/mjml-button/package.json
================================================
{
"name": "mjml-button",
"description": "mjml-button",
"version": "4.18.0",
"main": "lib/index.js",
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml-button"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib --root-mode upward"
},
"dependencies": {
"@babel/runtime": "^7.28.4",
"lodash": "^4.17.21",
"mjml-core": "4.18.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"rimraf": "^3.0.2"
}
}
================================================
FILE: packages/mjml-button/src/index.js
================================================
import { BodyComponent } from 'mjml-core'
import widthParser from 'mjml-core/lib/helpers/widthParser'
export default class MjButton extends BodyComponent {
static componentName = 'mj-button'
static endingTag = true
static allowedAttributes = {
align: 'enum(left,center,right)',
'background-color': 'color',
'border-bottom': 'string',
'border-left': 'string',
'border-radius': 'string',
'border-right': 'string',
'border-top': 'string',
border: 'string',
color: 'color',
'container-background-color': 'color',
'font-family': 'string',
'font-size': 'unit(px)',
'font-style': 'string',
'font-weight': 'string',
height: 'unit(px,%)',
href: 'string',
name: 'string',
title: 'string',
'inner-padding': 'unit(px,%){1,4}',
'letter-spacing': 'unitWithNegative(px,em)',
'line-height': 'unit(px,%,)',
'padding-bottom': 'unit(px,%)',
'padding-left': 'unit(px,%)',
'padding-right': 'unit(px,%)',
'padding-top': 'unit(px,%)',
padding: 'unit(px,%){1,4}',
rel: 'string',
target: 'string',
'text-decoration': 'string',
'text-transform': 'string',
'vertical-align': 'enum(top,bottom,middle)',
'text-align': 'enum(left,right,center)',
width: 'unit(px,%)',
}
static defaultAttributes = {
align: 'center',
'background-color': '#414141',
border: 'none',
'border-radius': '3px',
color: '#ffffff',
'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',
'font-size': '13px',
'font-weight': 'normal',
'inner-padding': '10px 25px',
'line-height': '120%',
padding: '10px 25px',
target: '_blank',
'text-decoration': 'none',
'text-transform': 'none',
'vertical-align': 'middle',
}
getStyles() {
return {
table: {
'border-collapse': 'separate',
width: this.getAttribute('width'),
'line-height': '100%',
},
td: {
border: this.getAttribute('border'),
'border-bottom': this.getAttribute('border-bottom'),
'border-left': this.getAttribute('border-left'),
'border-radius': this.getAttribute('border-radius'),
'border-right': this.getAttribute('border-right'),
'border-top': this.getAttribute('border-top'),
cursor: 'auto',
'font-style': this.getAttribute('font-style'),
height: this.getAttribute('height'),
'mso-padding-alt': this.getAttribute('inner-padding'),
'text-align': this.getAttribute('text-align'),
background: this.getAttribute('background-color'),
},
content: {
display: 'inline-block',
width: this.calculateAWidth(this.getAttribute('width')),
background: this.getAttribute('background-color'),
color: this.getAttribute('color'),
'font-family': this.getAttribute('font-family'),
'font-size': this.getAttribute('font-size'),
'font-style': this.getAttribute('font-style'),
'font-weight': this.getAttribute('font-weight'),
'line-height': this.getAttribute('line-height'),
'letter-spacing': this.getAttribute('letter-spacing'),
margin: '0',
'text-decoration': this.getAttribute('text-decoration'),
'text-transform': this.getAttribute('text-transform'),
padding: this.getAttribute('inner-padding'),
'mso-padding-alt': '0px',
'border-radius': this.getAttribute('border-radius'),
},
}
}
calculateAWidth(width) {
if (!width) return null
const { parsedWidth, unit } = widthParser(width)
// impossible to handle percents because it depends on padding and text width
if (unit !== 'px') return null
const { borders } = this.getBoxWidths()
const innerPaddings =
this.getShorthandAttrValue('inner-padding', 'left') +
this.getShorthandAttrValue('inner-padding', 'right')
return `${parsedWidth - innerPaddings - borders}px`
}
render() {
const tag = this.getAttribute('href') ? 'a' : 'p'
return `
<table
${this.htmlAttributes({
border: '0',
cellpadding: '0',
cellspacing: '0',
role: 'presentation',
style: 'table',
})}
>
<tbody>
<tr>
<td
${this.htmlAttributes({
align: 'center',
bgcolor:
this.getAttribute('background-color') === 'none'
? undefined
: this.getAttribute('background-color'),
role: 'presentation',
style: 'td',
valign: this.getAttribute('vertical-align'),
})}
>
<${tag}
${this.htmlAttributes({
href: this.getAttribute('href'),
name: this.getAttribute('name'),
rel: this.getAttribute('rel'),
title: this.getAttribute('title'),
style: 'content',
target: tag === 'a' ? this.getAttribute('target') : undefined,
})}
>
${this.getContent()}
</${tag}>
</td>
</tr>
</tbody>
</table>
`
}
}
================================================
FILE: packages/mjml-carousel/README.md
================================================
### mj-carousel
Displays a gallery of images or "carousel". Readers can interact by hovering and clicking on thumbnails depending on the email client they use.
<figure>
<img src="https://static.mailjet.com/mjml-website/documentation/carousel-example.gif" alt="desktop" />
</figure>
```xml
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-carousel>
<mj-carousel-image src="https://static.mailjet.com/mjml-website/documentation/carousel-1.jpg" />
<mj-carousel-image src="https://static.mailjet.com/mjml-website/documentation/carousel-2.jpg" />
<mj-carousel-image src="https://static.mailjet.com/mjml-website/documentation/carousel-3.jpg" />
</mj-carousel>
</mj-column>
</mj-section>
</mj-body>
</mjml>
```
#### Attributes
| attribute | accepts | description | default value |
| -------------------------- | ------------------------------ | ------------------------------------------------------ | --------------------------------- |
| align | `left` `center` `right` | horizontal alignment | `center` |
| border-radius | `px` `%` | border radius | `6px` |
| container-background-color | CSS color formats | column background color | |
| css-class | string | class name, added to the root HTML element created | |
| icon-width | `px` `%` | width of the icons on left and right of the main image | `44px` |
| left-icon | string | icon on the left of the main image | `https://i.imgur.com/xTh3hln.png` |
| padding | `px` `%` | carousel padding, supports up to 4 parameters | |
| padding-bottom | `px` `%` | carousel bottom padding | |
| padding-left | `px` `%` | carousel left padding | |
| padding-right | `px` `%` | carousel right padding | |
| padding-top | `px` `%` | carousel top padding | |
| right-icon | string | icon on the right of the main image | `https://i.imgur.com/os7o9kz.png` |
| tb-border | string | border of the thumbnails in CSS border format | `2px solid transparent` |
| tb-border-radius | `px` `%` | border-radius of the thumbnails | `6px` |
| tb-hover-border-color | CSS color formats | border color of the hovered thumbnail | `#fead0d` |
| tb-selected-border-color | CSS color formats | border color of the selected thumbnail | `#ccc` |
| tb-width | `px` `%` | thumbnail width | |
| thumbnails | `visible` `hidden` `supported` | display the thumbnails | `hidden` |
<p class="cta-container"><a class="cta" href="https://mjml.io/try-it-live/components/carousel">Try it live</a></p>
#### mj-carousel-image
Enables you to add and style the images in the carousel.
<div class="alert alert-note" role="alert">
<p>Note</p>
<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>
<p>More information about ending tags <a href="#ending-tags">in this section</a>.</p>
</div>
#### Attributes
| attribute | accepts | description | default value |
| ---------------- | -------- | -------------------------------------------------- | ------------- |
| alt | string | image description | `''` |
| border-radius | `px` `%` | border radius of the main image | |
| css-class | string | class name, added to the root HTML element created | |
| href | string | link to redirect to on click, <br>URL format | |
| rel | string | specify the rel attribute | |
| src | string | URL format | |
| target | string | link target on click | `_blank` |
| tb-border | string | CSS border format | |
| tb-border-radius | `px` `%` | border radius of the thumbnail | |
| thumbnails-src | string | specify a different thumbnail image in URL format | |
| title | string | tooltip & accessibility | |
================================================
FILE: packages/mjml-carousel/package.json
================================================
{
"name": "mjml-carousel",
"description": "mjml-carousel",
"version": "4.18.0",
"main": "lib/index.js",
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml-carousel"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib --root-mode upward"
},
"dependencies": {
"@babel/runtime": "^7.28.4",
"lodash": "^4.17.21",
"mjml-core": "4.18.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"rimraf": "^3.0.2"
}
}
================================================
FILE: packages/mjml-carousel/src/Carousel.js
================================================
import { BodyComponent } from 'mjml-core'
import { range, repeat, min, map } from 'lodash'
import { msoConditionalTag } from 'mjml-core/lib/helpers/conditionalTag'
import genRandomHexString from 'mjml-core/lib/helpers/genRandomHexString'
export default class MjCarousel extends BodyComponent {
static componentName = 'mj-carousel'
static allowedAttributes = {
align: 'enum(left,center,right)',
'border-radius': 'unit(px,%){1,4}',
'container-background-color': 'color',
'icon-width': 'unit(px,%)',
'left-icon': 'string',
padding: 'unit(px,%){1,4}',
'padding-top': 'unit(px,%)',
'padding-bottom': 'unit(px,%)',
'padding-left': 'unit(px,%)',
'padding-right': 'unit(px,%)',
'right-icon': 'string',
thumbnails: 'enum(visible,hidden,supported)',
'tb-border': 'string',
'tb-border-radius': 'unit(px,%)',
'tb-hover-border-color': 'color',
'tb-selected-border-color': 'color',
'tb-width': 'unit(px,%)',
}
static defaultAttributes = {
align: 'center',
'border-radius': '6px',
'icon-width': '44px',
'left-icon': 'https://i.imgur.com/xTh3hln.png',
'right-icon': 'https://i.imgur.com/os7o9kz.png',
thumbnails: 'visible',
'tb-border': '2px solid transparent',
'tb-border-radius': '6px',
'tb-hover-border-color': '#fead0d',
'tb-selected-border-color': '#ccc',
}
constructor(initialDatas = {}) {
super(initialDatas)
this.carouselId = genRandomHexString(16)
}
componentHeadStyle = () => {
const { length } = this.props.children
const { carouselId } = this
if (!length) return ''
const carouselCss = `
.mj-carousel {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.mj-carousel-${carouselId}-icons-cell {
display: table-cell !important;
width: ${this.getAttribute('icon-width')} !important;
}
.mj-carousel-radio,
.mj-carousel-next,
.mj-carousel-previous {
display: none !important;
}
.mj-carousel-thumbnail,
.mj-carousel-next,
.mj-carousel-previous {
touch-action: manipulation;
}
${range(0, length)
.map(
(i) =>
`.mj-carousel-${carouselId}-radio:checked ${repeat(
'+ * ',
i,
)}+ .mj-carousel-content .mj-carousel-image`,
)
.join(',')} {
display: none !important;
}
${range(0, length)
.map(
(i) =>
`.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-content .mj-carousel-image-${i + 1}`,
)
.join(',')} {
display: block !important;
}
.mj-carousel-previous-icons,
.mj-carousel-next-icons,
${range(0, length).map(
(i) =>
`.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-content .mj-carousel-next-${
((i + (1 % length) + length) % length) + 1
}`,
)},
${range(0, length).map(
(i) =>
`.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-content .mj-carousel-previous-${
((i - (1 % length) + length) % length) + 1
}`,
)} {
display: block !important;
}
${range(0, length)
.map(
(i) =>
`.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-content .mj-carousel-${carouselId}-thumbnail-${
i + 1
}`,
)
.join(',')} {
border-color: ${this.getAttribute('tb-selected-border-color')} !important;
}
${range(0, length)
.map(
(i) =>
`.mj-carousel-${carouselId}-radio-${i + 1}:checked ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-content .mj-carousel-${carouselId}-thumbnail
`,
)
.join(',')} {
display: inline-block !important;
}
.mj-carousel-image img + div,
.mj-carousel-thumbnail img + div {
display: none !important;
}
${range(0, length)
.map(
(i) =>
`.mj-carousel-${carouselId}-thumbnail:hover ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-main .mj-carousel-image`,
)
.join(',')} {
display: none !important;
}
.mj-carousel-thumbnail:hover {
border-color: ${this.getAttribute('tb-hover-border-color')} !important;
}
${range(0, length)
.map(
(i) =>
`.mj-carousel-${carouselId}-thumbnail-${i + 1}:hover ${repeat(
'+ * ',
length - i - 1,
)}+ .mj-carousel-main .mj-carousel-image-${i + 1}`,
)
.join(',')} {
display: block !important;
}
`
const fallback = `
.mj-carousel noinput { display:block !important; }
.mj-carousel noinput .mj-carousel-image-1 { display: block !important; }
.mj-carousel noinput .mj-carousel-arrows,
.mj-carousel noinput .mj-carousel-thumbnails { display: none !important; }
[owa] .mj-carousel-thumbnail { display: none !important; }
@media screen yahoo {
.mj-carousel-${this.carouselId}-icons-cell,
.mj-carousel-previous-icons,
.mj-carousel-next-icons {
display: none !important;
}
.mj-carousel-${carouselId}-radio-1:checked ${repeat(
'+ *',
length - 1,
)}+ .mj-carousel-content .mj-carousel-${carouselId}-thumbnail-1 {
border-color: transparent;
}
}
`
return `${carouselCss}\n${fallback}`
}
getStyles() {
return {
carousel: {
div: {
display: 'table',
width: '100%',
'table-layout': 'fixed',
'text-align': 'center',
'font-size': '0px',
},
table: {
'caption-side': 'top',
display: 'table-caption',
'table-layout': 'fixed',
width: '100%',
},
},
images: {
td: {
padding: '0px',
},
},
controls: {
div: {
display: 'none',
'mso-hide': 'all',
},
img: {
display: 'block',
width: this.getAttribute('icon-width'),
height: 'auto',
},
td: {
'font-size': '0px',
display: 'none',
'mso-hide': 'all',
padding: '0px',
},
},
}
}
thumbnailsWidth() {
if (!this.props.children.length) return 0
return (
this.getAttribute('tb-width') ||
`${min([this.context.parentWidth / this.props.children.length, 110])}px`
)
}
imagesAttributes() {
return map(this.children, 'attributes')
}
generateRadios() {
return this.renderChildren(this.props.children, {
renderer: (component) => component.renderRadio(),
attributes: {
carouselId: this.carouselId,
},
})
}
generateThumbnails() {
if (!['visible', 'supported'].includes(this.getAttribute('thumbnails')))
return ''
return this.renderChildren(this.props.children, {
attributes: {
'tb-border': this.getAttribute('tb-border'),
'tb-border-radius': this.getAttribute('tb-border-radius'),
'tb-width': this.thumbnailsWidth(),
carouselId: this.carouselId,
},
renderer: (component) => component.renderThumbnail(),
})
}
generateControls(direction, icon) {
const iconWidth = parseInt(this.getAttribute('icon-width'), 10)
return `
<td
${this.htmlAttributes({
class: `mj-carousel-${this.carouselId}-icons-cell`,
style: 'controls.td',
})}
>
<div
${this.htmlAttributes({
class: `mj-carousel-${direction}-icons`,
style: 'controls.div',
})}
>
${range(1, this.props.children.length + 1)
.map(
(i) => `
<label
${this.htmlAttributes({
for: `mj-carousel-${this.carouselId}-radio-${i}`,
class: `mj-carousel-${direction} mj-carousel-${direction}-${i}`,
})}
>
<img
${this.htmlAttributes({
src: icon,
alt: direction,
style: 'controls.img',
width: iconWidth,
})}
/>
</label>
`,
)
.join('')}
</div>
</td>
`
}
generateImages() {
return `
<td
${this.htmlAttributes({
style: 'images.td',
})}
>
<div
${this.htmlAttributes({
class: 'mj-carousel-images',
})}
>
${this.renderChildren(this.props.children, {
attributes: {
'border-radius': this.getAttribute('border-radius'),
},
})}
</div>
</td>
`
}
generateCarousel() {
return `
<table
${this.htmlAttributes({
style: 'carousel.table',
border: '0',
cellpadding: '0',
cellspacing: '0',
width: '100%',
role: 'presentation',
class: 'mj-carousel-main',
})}
>
<tbody>
<tr>
${this.generateControls('previous', this.getAttribute('left-icon'))}
${this.generateImages()}
${this.generateControls('next', this.getAttribute('right-icon'))}
</tr>
</tbody>
</table>
`
}
renderFallback() {
const { children } = this.props
if (children.length === 0) return ''
return msoConditionalTag(
this.renderChildren([children[0]], {
attributes: {
'border-radius': this.getAttribute('border-radius'),
},
}),
)
}
getChildContext() {
return {
...this.context,
thumbnails: this.getAttribute('thumbnails'),
}
}
render() {
return `
${msoConditionalTag(
`
<div
${this.htmlAttributes({
class: 'mj-carousel',
})}
>
${this.generateRadios()}
<div
${this.htmlAttributes({
class: `mj-carousel-content mj-carousel-${this.carouselId}-content`,
style: 'carousel.div',
})}
>
${this.generateThumbnails()}
${this.generateCarousel()}
</div>
</div>
`,
true,
)}
${this.renderFallback()}
`
}
}
================================================
FILE: packages/mjml-carousel/src/CarouselImage.js
================================================
import { BodyComponent, suffixCssClasses } from 'mjml-core'
export default class MjCarouselImage extends BodyComponent {
static componentName = 'mj-carousel-image'
static endingTag = true
static allowedAttributes = {
alt: 'string',
href: 'string',
rel: 'string',
target: 'string',
title: 'string',
src: 'string',
'thumbnails-src': 'string',
'border-radius': 'unit(px,%){1,4}',
'tb-border': 'string',
'tb-border-radius': 'unit(px,%){1,4}',
}
static defaultAttributes = {
alt: '',
target: '_blank',
}
getStyles() {
const hasThumbnailsSupported = this.hasThumbnailsSupported()
return {
images: {
img: {
'border-radius': this.getAttribute('border-radius'),
display: 'block',
width: this.context.containerWidth,
'max-width': '100%',
height: 'auto',
},
firstImageDiv: {},
otherImageDiv: {
display: 'none',
'mso-hide': 'all',
},
},
radio: {
input: {
display: 'none',
'mso-hide': 'all',
},
},
thumbnails: {
a: {
border: this.getAttribute('tb-border'),
'border-radius': this.getAttribute('tb-border-radius'),
display: hasThumbnailsSupported ? 'none' : 'inline-block',
overflow: 'hidden',
width: this.getAttribute('tb-width'),
},
img: {
display: 'block',
width: '100%',
height: 'auto',
},
},
}
}
hasThumbnailsSupported() {
const thumbnails =
this.getAttribute('thumbnails') || this.context.thumbnails
return thumbnails === 'supported'
}
renderThumbnail() {
const { carouselId, src, alt, 'tb-width': width, target } = this.attributes
const imgIndex = this.props.index + 1
const cssClass = suffixCssClasses(
this.getAttribute('css-class'),
'thumbnail',
)
return `
<a
${this.htmlAttributes({
style: 'thumbnails.a',
href: `#${imgIndex}`,
target,
class: `mj-carousel-thumbnail mj-carousel-${carouselId}-thumbnail mj-carousel-${carouselId}-thumbnail-${imgIndex} ${cssClass}`,
})}
>
<label ${this.htmlAttributes({
for: `mj-carousel-${carouselId}-radio-${imgIndex}`,
})}>
<img
${this.htmlAttributes({
style: 'thumbnails.img',
src: this.getAttribute('thumbnails-src') || src,
alt,
width: parseInt(width, 10),
})}
/>
</label>
</a>
`
}
renderRadio() {
const { index } = this.props
const carouselId = this.getAttribute('carouselId')
return `
<input
${this.htmlAttributes({
class: `mj-carousel-radio mj-carousel-${carouselId}-radio mj-carousel-${carouselId}-radio-${
index + 1
}`,
checked: index === 0 ? 'checked' : null,
type: 'radio',
name: `mj-carousel-radio-${carouselId}`,
id: `mj-carousel-${carouselId}-radio-${index + 1}`,
style: 'radio.input',
})}
/>
`
}
render() {
const { src, alt, href, rel, title } = this.attributes
const { index } = this.props
const image = `
<img
${this.htmlAttributes({
title,
src,
alt,
style: 'images.img',
width: parseInt(this.context.containerWidth, 10),
border: '0',
})} />
`
const cssClass = this.getAttribute('css-class') || ''
return `
<div
${this.htmlAttributes({
class: `mj-carousel-image mj-carousel-image-${index + 1} ${cssClass}`,
style: index === 0 ? 'images.firstImageDiv' : 'images.otherImageDiv',
})}
>
${
href
? `<a ${this.htmlAttributes({ href, rel, target: '_blank' })}>${image}</a>`
: image
}
</div>
`
}
}
================================================
FILE: packages/mjml-carousel/src/index.js
================================================
export { default as Carousel } from './Carousel'
export { default as CarouselImage } from './CarouselImage'
================================================
FILE: packages/mjml-cli/README.md
================================================
## mjml-cli
# Installation
We recommend installing and using MJML locally, in a project folder where you'll use MJML:
```bash
npm install mjml
```
In the folder where you installed MJML you can now run:
```bash
./node_modules/.bin/mjml input.mjml
```
To avoid typing `./node_modules/.bin/`, add it to your PATH:
```bash
export PATH="$PATH:./node_modules/.bin"
```
You can now run MJML directly, in that folder:
```bash
mjml input.mjml
```
MJML is written with [NodeJS](https://nodejs.org/en/)
You can download and install the MJML engine from [NPM](https://www.npmjs.com).
# Command Line Interface
In 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.
### Render MJML to HTML
```bash
mjml input.mjml
```
It will output a HTML file called `input.html`.
Input can also be a directory.
### Migrate MJML3 to MJML4
```bash
$> mjml -m input.mjml -o result.mjml
```
It will output a MJML file called `result.mjml`.
### Validate MJML
```bash
$> mjml -v input.mjml
```
It will log validation errors. If there are errors, exits with code 1. Otherwise, exits with code 0.
### Render and redirect the result to stdout
```bash
mjml -s input.mjml
# or
mjml --stdout input.mjml
```
### Render and redirect the result to a file
```bash
mjml input.mjml -o my-email.html
# or
mjml input.mjml --output my-email.html
```
You can output the resulting email responsive HTML in a file.
If the output file does not exist it will be created, but output directories must already exist.
If output is a directory, output file(s) will be `output/input-file-name.html`
### Set the validation mode
```bash
mjml -l skip -r input.mjml
```
Accepted values are
- 'normal' : *(default)* will display validation messages but compile anyway
- 'skip' : the file is rendered without being validated
- 'strict' : will throw an error if validation fails
### Watch changes on a file
```bash
mjml -w input.mjml
# or
mjml --watch input.mjml
```
If 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.
It can be time-saving when you can just split you screen and see the HTML output modified when you modify your MJML.
Of course, the `-w` option can be used with an `--output` option too.
### Available options
```bash
mjml input.mjml --config.optionName value
# or
mjml input.mjml -c.optionName value
```
All the options that can be passed to mjml2html (see general documentation) can be provided. The most common ones are detailed below.
### Minify and beautify the output HTML
```bash
$> mjml input.mjml --config.beautify true --config.minify false
```
These are the default options.
### Change minify options
```bash
$> mjml input.mjml --config.minifyOptions='{"minifyCSS": true, "removeEmptyAttributes": false}'
```
The defaults are "collapseWhitespace": true, "minifyCSS": false, "removeEmptyAttributes": true
See html-minifier documentation for more available options
### Change juice options (library used for inlining mj-style css)
```bash
$> mjml input.mjml --config.juiceOptions='{"preserveImportant": true}'
```
The defaults are "applyStyleTags": false, "insertPreservedExtraCss": false, "removeStyleTags": false
See juice documentation for more available options
### Preserve specific tags when using inline mj-style
```bash
$> mjml input.mjml --config.juicePreserveTags='{"myTag": { "start": "<#", "end": "</#" }}'
```
When 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.
### Override base path for mj-include relative paths
```bash
$> mjml ./my-project/input.mjml --config.filePath ./my-partials/
```
If 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`.
### Log error stack
```bash
$> mjml input.mjml --config.stack true
```
================================================
FILE: packages/mjml-cli/bin/mjml
================================================
#!/usr/bin/env node
require('../lib/client.js')()
================================================
FILE: packages/mjml-cli/package.json
================================================
{
"name": "mjml-cli",
"description": "MJML: the only framework that makes responsive-email easy",
"version": "4.18.0",
"main": "bin/mjml",
"bin": {
"mjml-cli": "bin/mjml"
},
"files": [
"bin",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mjmlio/mjml.git",
"directory": "packages/mjml-cli"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mjmlio/mjml/issues"
},
"homepage": "https://mjml.io",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib --root-mode upward"
},
"dependencies": {
"@babel/runtime": "^7.28.4",
"chokidar": "^3.0.0",
"glob": "^10.3.10",
"html-minifier": "^4.0.0",
"js-beautify": "^1.6.14",
"lodash": "^4.17.21",
"minimatch": "^9.0.3",
"mjml-core": "4.18.0",
"mjml-migrate": "4.18.0",
"mjml-parser-xml": "4.18.0",
"mjml-validator": "4.18.0",
"yargs": "^17.7.2"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"rimraf": "^3.0.2"
}
}
================================================
FILE: packages/mjml-cli/src/client.js
================================================
import path from 'path'
import yargs from 'yargs'
import { flow, pick, isNil, negate, pickBy } from 'lodash/fp'
import { isArray, isEmpty, map, get, omit } from 'lodash'
import { html as htmlBeautify } from 'js-beautify'
import { minify as htmlMinify } from 'html-minifier'
import mjml2html, { components, initializeType } from 'mjml-core'
import migrate from 'mjml-migrate'
import validate, { dependencies } from 'mjml-validator'
import MJMLParser from 'mjml-parser-xml'
import { version as coreVersion } from 'mjml-core/package.json'
import readFile, { flatMapPaths } from './commands/readFile'
import watchFiles from './commands/watchFiles'
import readStream from './commands/readStream'
import outputToFile, { isDirectory } from './commands/outputToFile'
import outputToConsole from './commands/outputToConsole'
import { version as cliVersion } from '../package.json'
import DEFAULT_OPTIONS from './helpers/defaultOptions'
const beautifyConfig = {
indent_size: 2,
wrap_attributes_indent_size: 2,
max_preserve_newline: 0,
preserve_newlines: false,
end_with_newline: true,
}
const minifyConfig = {
collapseWhitespace: true,
minifyCSS: false,
caseSensitive: true,
removeEmptyAttributes: true,
}
export default async () => {
let EXIT_CODE = 0
let KEEP_OPEN = false
const error = (msg) => {
console.error('\nCommand line error:') // eslint-disable-line no-console
console.error(msg) // eslint-disable-line no-console
process.exit(1)
}
const pickArgs = (args) =>
flow(
pick(args),
pickBy((e) => negate(isNil)(e) && !(isArray(e) && isEmpty(e))),
)
const { argv } = yargs
.version(false) // cf. https://github.com/yargs/yargs/issues/961
.options({
r: {
alias: 'read',
describe: 'Compile MJML File(s)',
type: 'array',
},
m: {
alias: 'migrate',
describe: 'Migrate MJML3 File(s) (deprecated)',
type: 'array',
},
v: {
alias: 'validate',
describe: 'Run validator on File(s)',
type: 'array',
},
w: {
alias: 'watch',
type: 'array',
describe: 'Watch and compile MJML File(s) when modified',
},
i: {
alias: 'stdin',
describe: 'Compiles MJML from input stream',
},
s: {
alias: 'stdout',
describe: 'Output HTML to stdout',
},
o: {
alias: 'output',
type: 'string',
describe: 'Filename/Directory to output compiled files',
},
c: {
alias: 'config',
type: 'object',
describe: 'Option to pass to mjml-core',
},
version: {
alias: 'V',
},
noStdoutFileComment: {
type: 'boolean',
describe: 'Add no file comment to stdout',
},
})
.help()
.version(`mjml-core: ${coreVersion}\nmjml-cli: ${cliVersion}`)
let juiceOptions
let minifyOptions
let juicePreserveTags
let fonts
try {
juiceOptions =
argv.c && argv.c.juiceOptions && JSON.parse(argv.c.juiceOptions)
} catch (e) {
error(`Failed to decode JSON for config.juiceOptions argument`)
}
try {
minifyOptions =
argv.c && argv.c.minifyOptions && JSON.parse(argv.c.minifyOptions)
} catch (e) {
error(`Failed to decode JSON for config.minifyOptions argument`)
}
try {
juicePreserveTags =
argv.c && argv.c.juicePreserveTags && JSON.parse(argv.c.juicePreserveTags)
} catch (e) {
error(`Failed to decode JSON for config.juicePreserveTags argument`)
}
try {
fonts = argv.c && argv.c.fonts && JSON.parse(argv.c.fonts)
} catch (e) {
error(`Failed to decode JSON for config.fonts argument`)
}
const filePath = argv.c && argv.c.filePath
const config = Object.assign(
DEFAULT_OPTIONS,
argv.c,
fonts && { fonts },
minifyOptions && { minifyOptions },
juiceOptions && { juiceOptions },
juicePreserveTags && { juicePreserveTags },
argv.c && argv.c.keepComments === 'false' && { keepComments: false },
)
const inputArgs = pickArgs(['r', 'w', 'i', '_', 'm', 'v'])(argv)
const outputArgs = pickArgs(['o', 's'])(argv)
// implies (until yargs pr is accepted)
;[
[Object.keys(inputArgs).length === 0, 'No input argument received'],
[Object.keys(inputArgs).length > 1, 'Too many input arguments received'],
[Object.keys(outputArgs).length > 1, 'Too many output arguments received'],
[
argv.w && argv.w.length > 1 && !argv.o,
'Need an output option when watching files',
],
[
argv.w &&
argv.w.length > 1 &&
argv.o &&
!isDirectory(argv.o) &&
argv.o !== '',
'Need an output option when watching files',
],
].forEach((v) => (v[0] ? error(v[1]) : null))
const inputOpt = Object.keys(inputArgs)[0]
const outputOpt = Object.keys(outputArgs)[0] || 's'
const inputFiles = isArray(inputArgs[inputOpt])
? inputArgs[inputOpt]
: [inputArgs[inputOpt]]
const inputs = []
switch (inputOpt) {
case 'r':
case 'v':
case 'm':
case '_': {
flatMapPaths(inputFiles).forEach((file) => {
inputs.push(readFile(file))
})
if (!inputs.length) {
error('No input files found')
return
}
break
}
case 'w':
watchFiles(inputFiles, {
...argv,
config,
minifyConfig,
beautifyConfig,
})
KEEP_OPEN = true
break
case 'i':
inputs.push(await readStream())
break
default:
error('Command line error: Incorrect input options')
}
const convertedStream = []
const failedStream = []
inputs.forEach((i) => {
try {
let compiled
switch (inputOpt) {
case 'm':
compiled = { html: migrate(i.mjml, { beautify: true }) }
break
case 'v': // eslint-disable-next-line no-case-declarations
const mjmlJson = MJMLParser(i.mjml, {
components,
filePath: filePath || i.file,
actualPath: i.file,
})
compiled = {
errors: validate(mjmlJson, {
dependencies,
components,
initializeType,
}),
}
break
default: {
const beautify = config.beautify && config.beautify !== 'false'
const minify = config.minify && config.minify !== 'false'
compiled = mjml2html(i.mjml, {
...omit(config, ['minify', 'beautify']),
filePath: filePath || i.file,
actualPath: i.file,
})
if (beautify) {
compiled.html = htmlBeautify(compiled.html, beautifyConfig)
}
if (minify) {
compiled.html = htmlMinify(compiled.html, {
...minifyConfig,
...config.minifyOptions,
})
}
}
}
convertedStream.push({ ...i, compiled })
} catch (e) {
EXIT_CODE = 2
failedStream.push({ file: i.file, error: e })
}
})
convertedStream.forEach((s) => {
if (get(s, 'compiled.errors.length')) {
console.error(map(s.compiled.errors, 'formattedMessage').join('\n')) // eslint-disable-line no-console
}
})
failedStream.forEach(({ error, file }) => {
console.error(`${file ? `File: ${file}\n` : null}${error}`) // eslint-disable-line no-console
if (config.stack) {
console.error(error.stack) // eslint-disable-line no-console
}
})
if (inputOpt === 'v') {
const isInvalid =
failedStream.length ||
convertedStream.some((s) => !!get(s, 'compiled.errors.length'))
if (isInvalid) {
error('Validation failed')
return
}
process.exitCode = 0
return
}
if (!KEEP_OPEN && convertedStream.length === 0) {
error('Input file(s) failed to render')
}
switch (outputOpt) {
case 'o': {
if (inputs.length > 1 && !isDirectory(argv.o) && argv.o !== '') {
error(
`Multiple input files, but output option should be either an existing directory or an empty string: ${argv.o} given`,
)
}
const fullOutputPath = path.parse(path.resolve(process.cwd(), argv.o))
if (inputs.length === 1 && !isDirectory(fullOutputPath.dir)) {
error(`Output directory doesn’t exist for path : ${argv.o}`)
}
Promise.all(convertedStream.map(outputToFile(argv.o)))
.then(() => {
if (!KEEP_OPEN) {
process.exitCode = EXIT_CODE
}
})
.catch(({ outputName, err }) => {
if (!KEEP_OPEN) {
error(`Error writing file - ${outputName} : ${err}`)
}
})
break
}
case 's': {
const addFileHeaderComment = !argv.noStdoutFileComment
Promise.all(
convertedStream.map((converted) =>
outputToConsole(converted, addFileHeaderComment),
),
)
.then(() => (process.exitCode = EXIT_CODE)) // eslint-disable-line no-return-assign
.catch(() => (process.exitCode = 1)) // eslint-disable-line no-return-assign
break
}
default:
error('Command line error: No output option available')
}
}
================================================
FILE: packages/mjml-cli/src/commands/outputToConsole.js
================================================
export default ({ compiled: { html }, file }, addFileHeaderComment) =>
new Promise((resolve) => {
let output = ''
if (addFileHeaderComment) {
output = `<!-- FILE: ${file} -->\n`
}
output += `${html}\n`
process.stdout.write(output, resolve)
})
================================================
FILE: packages/mjml-cli/src/commands/outputToFile.js
================================================
import fs from 'fs'
import path from 'path'
export const isDirectory = (file) => {
try {
const outputPath = path.resolve(process.cwd(), file)
return fs.statSync(outputPath).isDirectory()
} catch (e) {
return false
}
}
const replaceExtension = (input) =>
input.replace(
'.mjml',
input.replace('.mjml', '').match(/(.)*\.(.)+$/g) ? '' : '.html',
)
const stripPath = (input) => input.match(/[^/\\]+$/g)[0]
const makeGuessOutputName = (outputPath) => {
if (isDirectory(outputPath)) {
return (input) => path.join(outputPath, replaceExtension(stripPath(input)))
}
return (input) => {
if (!outputPath) {
return replaceExtension(stripPath(input))
}
return outputPath
}
}
export default (outputPath) => {
const guessOutputName = makeGuessOutputName(outputPath)
return ({ file, compiled: { html } }) =>
new Promise((resolve, reject) => {
const outputName = guessOutputName(file)
fs.writeFile(outputName, html, (err) => {
if (err) {
// eslint-disable-next-line prefer-promise-reject-errors
return reject({ outputName, err })
}
return resolve(outputName)
})
})
}
================================================
FILE: packages/mjml-cli/src/commands/readFile.js
================================================
import fs from 'fs'
import { sync } from 'glob'
import { flatMap } from 'lodash'
export const flatMapPaths = (paths) =>
flatMap(paths, (p) => sync(p, { nodir: true }))
export default (path) => {
try {
return { file: path, mjml: fs.readFileSync(path).toString() }
} catch (e) {
// eslint-disable-next-line
console.warn(`Cannot read file: ${path} doesn't exist or no access`, e)
return {}
}
}
================================================
FILE: packages/mjml-cli/src/commands/readStream.js
================================================
const stdinSync = () =>
new Promise((res) => {
let buffer = ''
const stream = process.stdin
stream.on('data', (chunck) => {
buffer += chunck
})
stream.on('end', () => res(buffer))
})
export default async () => {
const mjml = await stdinSync()
return { mjml }
}
================================================
FILE: packages/mjml-cli/src/commands/watchFiles.js
================================================
/* eslint-disable no-console */
import chokidar from 'chokidar'
import { sync } from 'glob'
import { match } from 'minimatch'
import path from 'path'
import mjml2html from 'mjml-core'
import { flow, pickBy, flatMap, uniq, difference, remove } from 'lodash/fp'
import { omit } from 'lodash'
import { html as htmlBeautify } from 'js-beautify'
import { minify as htmlMinify } from 'html-minifier'
import readFile from './readFile'
import makeOutputToFile from './outputToFile'
import fileContext from '../helpers/fileContext'
let dirty = []
const _flatMap = flatMap.convert({ cap: false }) // eslint-disable-line no-underscore-dangle
const flatMapAndJoin = _flatMap((v, k) => v.map((p) => path.join(k, p)))
const flatMapKeyAndValues = flow(
_flatMap((v, k) => [k, ...v]),
uniq,
)
export default (input, options) => {
const dependencies = {}
const outputToFile = makeOutputToFile(options.o)
const getRelatedFiles = (file) =>
flow(
pickBy((v, k) => k === file || v.indexOf(file) !== -1),
Object.keys,
)(dependencies)
const synch
gitextract_vim4znp0/ ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── build-documentation.yml │ ├── mjml-workflow.yml │ └── npm-publish.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── doc/ │ ├── basic.md │ ├── body_components.md │ ├── community-components.md │ ├── community-contributions.md │ ├── components_1.md │ ├── components_2.md │ ├── config.json │ ├── create.md │ ├── ending-tags.md │ ├── getting_started.md │ ├── guide.md │ ├── head_components.md │ ├── install.md │ ├── mjml-bar-chart.md │ ├── mjml-chart.md │ ├── mjml-chartjs.md │ ├── mjml-mso-button.md │ ├── mjml-qr-code.md │ ├── ports.md │ ├── tooling.md │ └── using_mjml_in_json.md ├── lerna.json ├── package.json ├── packages/ │ ├── mjml/ │ │ ├── README.md │ │ ├── bin/ │ │ │ └── mjml │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.js │ │ └── test/ │ │ ├── accordion-fontFamily.test.js │ │ ├── accordion-padding.test.js │ │ ├── accordionTitle-fontWeight.test.js │ │ ├── carousel-hoverSupported.test.js │ │ ├── column-border-radius.test.js │ │ ├── html-attributes.test.js │ │ ├── html-comments.test.js │ │ ├── lazy-head-style.test.js │ │ ├── navbar-ico-padding.test.js │ │ ├── social-align.test.js │ │ ├── social-icon-height.test.js │ │ ├── table-cellspacing.test.js │ │ ├── tableWidth.test.js │ │ ├── wrapper-border-radius.test.js │ │ └── wrapper-gap.test.js │ ├── mjml-accordion/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── Accordion.js │ │ ├── AccordionElement.js │ │ ├── AccordionText.js │ │ ├── AccordionTitle.js │ │ └── index.js │ ├── mjml-body/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-browser/ │ │ ├── README.md │ │ ├── browser-mocks/ │ │ │ ├── fs.js │ │ │ ├── path.js │ │ │ └── uglify-js.js │ │ ├── package.json │ │ └── webpack.config.js │ ├── mjml-button/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-carousel/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── Carousel.js │ │ ├── CarouselImage.js │ │ └── index.js │ ├── mjml-cli/ │ │ ├── README.md │ │ ├── bin/ │ │ │ └── mjml │ │ ├── package.json │ │ └── src/ │ │ ├── client.js │ │ ├── commands/ │ │ │ ├── outputToConsole.js │ │ │ ├── outputToFile.js │ │ │ ├── readFile.js │ │ │ ├── readStream.js │ │ │ └── watchFiles.js │ │ └── helpers/ │ │ ├── defaultOptions.js │ │ └── fileContext.js │ ├── mjml-column/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-core/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components.js │ │ │ ├── createComponent.js │ │ │ ├── helpers/ │ │ │ │ ├── conditionalTag.js │ │ │ │ ├── fonts.js │ │ │ │ ├── formatAttributes.js │ │ │ │ ├── genRandomHexString.js │ │ │ │ ├── jsonToXML.js │ │ │ │ ├── makeLowerBreakpoint.js │ │ │ │ ├── mediaQueries.js │ │ │ │ ├── mergeOutlookConditionnals.js │ │ │ │ ├── minifyOutlookConditionnals.js │ │ │ │ ├── mjmlconfig.js │ │ │ │ ├── preview.js │ │ │ │ ├── shorthandParser.js │ │ │ │ ├── skeleton.js │ │ │ │ ├── styles.js │ │ │ │ ├── suffixCssClasses.js │ │ │ │ └── widthParser.js │ │ │ ├── index.js │ │ │ └── types/ │ │ │ ├── boolean.js │ │ │ ├── color.js │ │ │ ├── enum.js │ │ │ ├── helpers/ │ │ │ │ └── colors.js │ │ │ ├── index.js │ │ │ ├── integer.js │ │ │ ├── string.js │ │ │ ├── type.js │ │ │ └── unit.js │ │ └── tests/ │ │ ├── .eslintrc │ │ ├── index.js │ │ ├── jsonToXml-test.js │ │ ├── mergeOutlookConditionnals-test.js │ │ ├── minifyOutlookConditionnals-test.js │ │ ├── shorthandParser-test.js │ │ ├── skeleton-test.js │ │ └── widthParser-test.js │ ├── mjml-divider/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-group/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head/ │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-attributes/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-breakpoint/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-font/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-html-attributes/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-preview/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-style/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-head-title/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-hero/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-image/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-migrate/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── cli.js │ │ ├── config.js │ │ └── migrate.js │ ├── mjml-navbar/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── Navbar.js │ │ ├── NavbarLink.js │ │ └── index.js │ ├── mjml-parser-xml/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── helpers/ │ │ │ │ ├── cleanNode.js │ │ │ │ ├── convertBooleansOnAttrs.js │ │ │ │ └── setEmptyAttributes.js │ │ │ └── index.js │ │ └── test/ │ │ ├── incl.mjml │ │ ├── test-preprocessors.js │ │ ├── test-utils.js │ │ ├── test-values.js │ │ └── test.js │ ├── mjml-preset-core/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── dependencies.js │ │ └── index.js │ ├── mjml-raw/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-section/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-social/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── Social.js │ │ ├── SocialElement.js │ │ └── index.js │ ├── mjml-spacer/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-table/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-text/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── index.js │ ├── mjml-validator/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── MJMLRulesCollection.js │ │ ├── dependencies.js │ │ ├── index.js │ │ └── rules/ │ │ ├── errorAttr.js │ │ ├── ruleError.js │ │ ├── validAttributes.js │ │ ├── validChildren.js │ │ ├── validTag.js │ │ └── validTypes.js │ └── mjml-wrapper/ │ ├── README.md │ ├── package.json │ └── src/ │ └── index.js ├── readme-ja.md ├── test.js └── type.js
SYMBOL INDEX (243 symbols across 68 files)
FILE: packages/mjml-accordion/src/Accordion.js
class MjAccordion (line 3) | class MjAccordion extends BodyComponent {
method getStyles (line 61) | getStyles() {
method getChildContext (line 73) | getChildContext() {
method render (line 80) | render() {
FILE: packages/mjml-accordion/src/AccordionElement.js
class MjAccordionElement (line 7) | class MjAccordionElement extends BodyComponent {
method getStyles (line 33) | getStyles() {
method handleMissingChildren (line 49) | handleMissingChildren() {
method getChildContext (line 94) | getChildContext() {
method render (line 101) | render() {
FILE: packages/mjml-accordion/src/AccordionText.js
class MjAccordionText (line 3) | class MjAccordionText extends BodyComponent {
method getStyles (line 29) | getStyles() {
method renderContent (line 52) | renderContent() {
method resolveFontFamily (line 65) | resolveFontFamily() {
method render (line 82) | render() {
FILE: packages/mjml-accordion/src/AccordionTitle.js
class MjAccordionTitle (line 4) | class MjAccordionTitle extends BodyComponent {
method getStyles (line 27) | getStyles() {
method resolveFontFamily (line 59) | resolveFontFamily() {
method renderTitle (line 76) | renderTitle() {
method renderIcons (line 89) | renderIcons() {
method render (line 120) | render() {
FILE: packages/mjml-body/src/index.js
class MjBody (line 3) | class MjBody extends BodyComponent {
method getChildContext (line 15) | getChildContext() {
method getStyles (line 22) | getStyles() {
method render (line 30) | render() {
FILE: packages/mjml-button/src/index.js
class MjButton (line 5) | class MjButton extends BodyComponent {
method getStyles (line 64) | getStyles() {
method calculateAWidth (line 106) | calculateAWidth(width) {
method render (line 123) | render() {
FILE: packages/mjml-carousel/src/Carousel.js
class MjCarousel (line 7) | class MjCarousel extends BodyComponent {
method constructor (line 43) | constructor(initialDatas = {}) {
method getStyles (line 213) | getStyles() {
method thumbnailsWidth (line 255) | thumbnailsWidth() {
method imagesAttributes (line 263) | imagesAttributes() {
method generateRadios (line 267) | generateRadios() {
method generateThumbnails (line 276) | generateThumbnails() {
method generateControls (line 291) | generateControls(direction, icon) {
method generateImages (line 333) | generateImages() {
method generateCarousel (line 355) | generateCarousel() {
method renderFallback (line 379) | renderFallback() {
method getChildContext (line 392) | getChildContext() {
method render (line 399) | render() {
FILE: packages/mjml-carousel/src/CarouselImage.js
class MjCarouselImage (line 3) | class MjCarouselImage extends BodyComponent {
method getStyles (line 26) | getStyles() {
method hasThumbnailsSupported (line 66) | hasThumbnailsSupported() {
method renderThumbnail (line 72) | renderThumbnail() {
method renderRadio (line 105) | renderRadio() {
method render (line 125) | render() {
FILE: packages/mjml-column/src/index.js
class MjColumn (line 5) | class MjColumn extends BodyComponent {
method getChildContext (line 38) | getChildContext() {
method getStyles (line 70) | getStyles() {
method getMobileWidth (line 124) | getMobileWidth() {
method getWidthAsPixel (line 150) | getWidthAsPixel() {
method getParsedWidth (line 163) | getParsedWidth(toString) {
method getColumnClass (line 182) | getColumnClass() {
method hasBorderRadius (line 210) | hasBorderRadius() {
method hasInnerBorderRadius (line 215) | hasInnerBorderRadius() {
method hasGutter (line 220) | hasGutter() {
method renderGutter (line 230) | renderGutter() {
method renderColumn (line 257) | renderColumn() {
method render (line 307) | render() {
FILE: packages/mjml-core/src/components.js
function assignComponents (line 6) | function assignComponents(target, source) {
function registerComponent (line 12) | function registerComponent(Component, options = {}) {
FILE: packages/mjml-core/src/createComponent.js
function initComponent (line 20) | function initComponent({ initialDatas, name }) {
class Component (line 39) | class Component {
method getTagName (line 40) | static getTagName() {
method isRawElement (line 44) | static isRawElement() {
method constructor (line 50) | constructor(initialDatas = {}) {
method getChildContext (line 83) | getChildContext() {
method getAttribute (line 87) | getAttribute(name) {
method getContent (line 91) | getContent() {
method renderMJML (line 95) | renderMJML(mjml, options = {}) {
class BodyComponent (line 112) | class BodyComponent extends Component {
method getStyles (line 114) | getStyles() {
method getShorthandAttrValue (line 118) | getShorthandAttrValue(attribute, direction) {
method getShorthandBorderValue (line 133) | getShorthandBorderValue(direction, attribute = 'border') {
method getBoxWidths (line 141) | getBoxWidths() {
method htmlAttributes (line 161) | htmlAttributes(attributes) {
method styles (line 178) | styles(styles) {
method renderChildren (line 201) | renderChildren(children, options = {}) {
class HeadComponent (line 264) | class HeadComponent extends Component {
method getTagName (line 265) | static getTagName() {
method handlerChildren (line 269) | handlerChildren() {
FILE: packages/mjml-core/src/helpers/conditionalTag.js
function conditionalTag (line 8) | function conditionalTag(content, negation = false) {
function msoConditionalTag (line 16) | function msoConditionalTag(content, negation = false) {
FILE: packages/mjml-core/src/helpers/fonts.js
function buildFontsTags (line 4) | function buildFontsTags(content, inlineStyle, fonts = {}) {
FILE: packages/mjml-core/src/helpers/genRandomHexString.js
function genRandomHexString (line 1) | function genRandomHexString(length) {
FILE: packages/mjml-core/src/helpers/makeLowerBreakpoint.js
function makeLowerBreakpoint (line 1) | function makeLowerBreakpoint(breakpoint) {
FILE: packages/mjml-core/src/helpers/mediaQueries.js
function buildMediaQueriesTags (line 4) | function buildMediaQueriesTags(
FILE: packages/mjml-core/src/helpers/mjmlconfig.js
function readMjmlConfig (line 7) | function readMjmlConfig(configPathOrDir = process.cwd()) {
function resolveComponentPath (line 42) | function resolveComponentPath(compPath, componentRootPath) {
function registerCustomComponent (line 70) | function registerCustomComponent(
function handleMjmlConfigComponents (line 84) | function handleMjmlConfigComponents(
function handleMjmlConfig (line 127) | function handleMjmlConfig(
FILE: packages/mjml-core/src/helpers/shorthandParser.js
function borderParser (line 27) | function borderParser(border) {
FILE: packages/mjml-core/src/helpers/skeleton.js
function skeleton (line 7) | function skeleton(options) {
FILE: packages/mjml-core/src/helpers/styles.js
function buildStyleFromComponents (line 3) | function buildStyleFromComponents(
function buildStyleFromTags (line 22) | function buildStyleFromTags(breakpoint, styles) {
FILE: packages/mjml-core/src/helpers/widthParser.js
function widthParser (line 3) | function widthParser(width, options = {}) {
FILE: packages/mjml-core/src/index.js
class ValidationError (line 46) | class ValidationError extends Error {
method constructor (line 47) | constructor(message, errors) {
function mjml2html (line 54) | function mjml2html(mjml, options = {}) {
FILE: packages/mjml-core/src/types/boolean.js
method constructor (line 7) | constructor(boolean) {
method isValid (line 13) | isValid() {
FILE: packages/mjml-core/src/types/color.js
method constructor (line 12) | constructor(color) {
method getValue (line 23) | getValue() {
FILE: packages/mjml-core/src/types/enum.js
method constructor (line 14) | constructor(value) {
FILE: packages/mjml-core/src/types/integer.js
method constructor (line 7) | constructor(value) {
FILE: packages/mjml-core/src/types/string.js
method constructor (line 7) | constructor(value) {
FILE: packages/mjml-core/src/types/type.js
class Type (line 24) | class Type {
method constructor (line 25) | constructor(value) {
method isValid (line 29) | isValid() {
method getErrorMessage (line 33) | getErrorMessage() {
method check (line 45) | static check(type) {
method getValue (line 49) | getValue() {
FILE: packages/mjml-core/src/types/unit.js
method constructor (line 21) | constructor(value) {
FILE: packages/mjml-divider/src/index.js
class MjDivider (line 5) | class MjDivider extends BodyComponent {
method getStyles (line 31) | getStyles() {
method getOutlookWidth (line 56) | getOutlookWidth() {
method renderAfter (line 79) | renderAfter() {
method render (line 103) | render() {
FILE: packages/mjml-group/src/index.js
class MjGroup (line 5) | class MjGroup extends BodyComponent {
method getChildContext (line 19) | getChildContext() {
method getStyles (line 49) | getStyles() {
method getParsedWidth (line 68) | getParsedWidth(toString) {
method getWidthAsPixel (line 87) | getWidthAsPixel() {
method getColumnClass (line 100) | getColumnClass() {
method render (line 127) | render() {
FILE: packages/mjml-head-attributes/src/index.js
class MjAttributes (line 5) | class MjAttributes extends HeadComponent {
method handler (line 8) | handler() {
FILE: packages/mjml-head-breakpoint/src/index.js
class MjBreakpoint (line 3) | class MjBreakpoint extends HeadComponent {
method handler (line 12) | handler() {
FILE: packages/mjml-head-font/src/index.js
class MjFont (line 3) | class MjFont extends HeadComponent {
method handler (line 11) | handler() {
FILE: packages/mjml-head-html-attributes/src/index.js
class MjHtmlAttributes (line 4) | class MjHtmlAttributes extends HeadComponent {
method handler (line 7) | handler() {
FILE: packages/mjml-head-preview/src/index.js
class MjPreview (line 3) | class MjPreview extends HeadComponent {
method handler (line 8) | handler() {
FILE: packages/mjml-head-style/src/index.js
class MjStyle (line 3) | class MjStyle extends HeadComponent {
method handler (line 12) | handler() {
FILE: packages/mjml-head-title/src/index.js
class MjTitle (line 3) | class MjTitle extends HeadComponent {
method handler (line 8) | handler() {
FILE: packages/mjml-head/src/index.js
class MjHead (line 3) | class MjHead extends HeadComponent {
method handler (line 6) | handler() {
FILE: packages/mjml-hero/src/index.js
class MjHero (line 8) | class MjHero extends BodyComponent {
method getChildContext (line 49) | getChildContext() {
method getStyles (line 76) | getStyles() {
method renderContent (line 155) | renderContent() {
method renderMode (line 254) | renderMode() {
method render (line 308) | render() {
FILE: packages/mjml-image/src/index.js
class MjImage (line 7) | class MjImage extends BodyComponent {
method getStyles (line 51) | getStyles() {
method getContentWidth (line 88) | getContentWidth() {
method renderImage (line 98) | renderImage() {
method render (line 145) | render() {
FILE: packages/mjml-migrate/src/migrate.js
function removeContainerTag (line 17) | function removeContainerTag(bodyTag) {
function addPx (line 27) | function addPx(value) {
function fixUnits (line 35) | function fixUnits(attribute, value) {
function cleanAttributes (line 45) | function cleanAttributes(attributes) {
constant DEFAULT_SOCIAL_DISPLAY (line 52) | const DEFAULT_SOCIAL_DISPLAY = 'facebook twitter google'
function migrateSocialSyntax (line 54) | function migrateSocialSyntax(socialTag) {
function migrateNavbarSyntax (line 101) | function migrateNavbarSyntax(navbarTag) {
function migrateHeroSyntax (line 107) | function migrateHeroSyntax(heroTag) {
function isSupportedTag (line 120) | function isSupportedTag(tag) {
function loopThrough (line 124) | function loopThrough(tree) {
function checkV3Through (line 167) | function checkV3Through(node) {
function migrate (line 189) | function migrate(input, options = {}) {
function handleMjml3 (line 202) | function handleMjml3(mjml, options = {}) {
FILE: packages/mjml-navbar/src/Navbar.js
class MjNavbar (line 8) | class MjNavbar extends BodyComponent {
method getStyles (line 66) | getStyles() {
method renderHamburger (line 107) | renderHamburger() {
method render (line 152) | render() {
FILE: packages/mjml-navbar/src/NavbarLink.js
class MjNavbarLink (line 5) | class MjNavbarLink extends BodyComponent {
method getStyles (line 43) | getStyles() {
method renderContent (line 72) | renderContent() {
method render (line 97) | render() {
FILE: packages/mjml-parser-xml/src/helpers/cleanNode.js
function cleanNode (line 3) | function cleanNode(node) {
FILE: packages/mjml-parser-xml/src/helpers/convertBooleansOnAttrs.js
function convertBooleansOnAttrs (line 8) | function convertBooleansOnAttrs(attrs) {
FILE: packages/mjml-parser-xml/src/helpers/setEmptyAttributes.js
function setEmptyAttributes (line 3) | function setEmptyAttributes(node) {
FILE: packages/mjml-parser-xml/src/index.js
function MJMLParser (line 29) | function MJMLParser(xml, options = {}, includedIn = []) {
FILE: packages/mjml-parser-xml/test/test-utils.js
function omitDeepLodash (line 3) | function omitDeepLodash(input, props) {
function deepDiff (line 32) | function deepDiff(object, base) {
function displayDiff (line 43) | function displayDiff(obj1, obj2) {
FILE: packages/mjml-raw/src/index.js
class MjRaw (line 3) | class MjRaw extends BodyComponent {
method render (line 14) | render() {
FILE: packages/mjml-section/src/index.js
class MjSection (line 6) | class MjSection extends BodyComponent {
method getChildContext (line 44) | getChildContext() {
method getStyles (line 54) | getStyles() {
method getBackground (line 117) | getBackground() {
method getBackgroundString (line 131) | getBackgroundString() {
method getBackgroundPosition (line 136) | getBackgroundPosition() {
method parseBackgroundPosition (line 145) | parseBackgroundPosition() {
method hasBackground (line 189) | hasBackground() {
method isFullWidth (line 193) | isFullWidth() {
method hasBorderRadius (line 197) | hasBorderRadius() {
method hasGap (line 202) | hasGap() {
method renderBefore (line 207) | renderBefore() {
method renderAfter (line 242) | renderAfter() {
method renderWrappedChildren (line 252) | renderWrappedChildren() {
method renderWithBackground (line 289) | renderWithBackground(content) {
method renderSection (line 433) | renderSection() {
method renderFullWidth (line 482) | renderFullWidth() {
method renderSimple (line 519) | renderSimple() {
method render (line 529) | render() {
FILE: packages/mjml-social/src/Social.js
class MjSocial (line 4) | class MjSocial extends BodyComponent {
method getStyles (line 48) | getStyles() {
method getSocialElementAttributes (line 56) | getSocialElementAttributes() {
method renderHorizontal (line 83) | renderHorizontal() {
method renderVertical (line 137) | renderVertical() {
method render (line 159) | render() {
FILE: packages/mjml-social/src/SocialElement.js
constant IMG_BASE_URL (line 4) | const IMG_BASE_URL = 'https://www.mailjet.com/images/theme/v1/icons/ico-...
class MjSocialElement (line 95) | class MjSocialElement extends BodyComponent {
method getStyles (line 149) | getStyles() {
method getSocialAttributes (line 198) | getSocialAttributes() {
method render (line 227) | render() {
FILE: packages/mjml-spacer/src/index.js
class MjSpacer (line 3) | class MjSpacer extends BodyComponent {
method getStyles (line 25) | getStyles() {
method render (line 34) | render() {
FILE: packages/mjml-table/src/index.js
class MjTable (line 6) | class MjTable extends BodyComponent {
method getStyles (line 47) | getStyles() {
method getWidth (line 63) | getWidth() {
method hasCellspacing (line 74) | hasCellspacing() {
method render (line 80) | render() {
FILE: packages/mjml-text/src/index.js
class MjText (line 5) | class MjText extends BodyComponent {
method getStyles (line 41) | getStyles() {
method renderContent (line 59) | renderContent() {
method render (line 69) | render() {
FILE: packages/mjml-validator/src/MJMLRulesCollection.js
function registerRule (line 15) | function registerRule(rule, name) {
FILE: packages/mjml-validator/src/index.js
constant SKIP_ELEMENTS (line 8) | const SKIP_ELEMENTS = ['mjml']
function MJMLValidator (line 16) | function MJMLValidator(element, options = {}) {
FILE: packages/mjml-validator/src/rules/errorAttr.js
function errorAttr (line 3) | function errorAttr(element) {
FILE: packages/mjml-validator/src/rules/ruleError.js
function formatInclude (line 1) | function formatInclude(element) {
function ruleError (line 14) | function ruleError(message, element) {
FILE: packages/mjml-validator/src/rules/validAttributes.js
constant WHITELIST (line 3) | const WHITELIST = ['mj-class', 'css-class']
function validateAttribute (line 5) | function validateAttribute(element, { components }) {
FILE: packages/mjml-validator/src/rules/validChildren.js
function validChildren (line 3) | function validChildren(
FILE: packages/mjml-validator/src/rules/validTag.js
function validateTag (line 11) | function validateTag(element, { components }) {
FILE: packages/mjml-validator/src/rules/validTypes.js
function validateType (line 3) | function validateType(element, { components, initializeType }) {
FILE: packages/mjml-wrapper/src/index.js
class MjWrapper (line 4) | class MjWrapper extends MjSection {
method renderWrappedChildren (line 12) | renderWrappedChildren() {
FILE: packages/mjml/test/accordion-padding.test.js
function extractPadding (line 29) | function extractPadding(style, prop) {
FILE: packages/mjml/test/lazy-head-style.test.js
class HeadComponentWithFunctionStyle (line 23) | class HeadComponentWithFunctionStyle extends HeadComponent {
method handler (line 24) | handler() {
FILE: packages/mjml/test/navbar-ico-padding.test.js
function extractPadding (line 25) | function extractPadding(style, prop) {
Condensed preview — 230 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (514K chars).
[
{
"path": ".editorconfig",
"chars": 778,
"preview": "# https://editorconfig.org\n\n# A special property that should be specified at the top of the file outside of\n# any sectio"
},
{
"path": ".eslintignore",
"chars": 103,
"preview": "node_modules\nlib\npackages/mjml-core/src/types*\ntype.js\ntest-html-attributes.js\ntest.js\nbabel.config.js\n"
},
{
"path": ".eslintrc",
"chars": 503,
"preview": "{\n \"extends\": [\"airbnb-base\", \"prettier\"],\n \"parser\": \"babel-eslint\",\n \"rules\": {\n \"comma-dangle\": [2, \"always-mul"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 997,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise descriptio"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 560,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? "
},
{
"path": ".github/workflows/build-documentation.yml",
"chars": 349,
"preview": "name: Build Documentation\n\non:\n push:\n branches:\n - master\n\njobs:\n buildDoc:\n runs-on: ubuntu-latest\n st"
},
{
"path": ".github/workflows/mjml-workflow.yml",
"chars": 482,
"preview": "name: Mjml CI\non: [push, pull_request]\njobs:\n build:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n nod"
},
{
"path": ".github/workflows/npm-publish.yml",
"chars": 1642,
"preview": "name: Publish Package to NPM\non:\n workflow_dispatch:\n inputs:\n release_type:\n description: 'Version bump"
},
{
"path": ".gitignore",
"chars": 80,
"preview": ".DS_Store\n*.log\n.idea/\nlib\nnode_modules\ntest.html\n/**/npmignore\n/testing-manual\n"
},
{
"path": ".prettierignore",
"chars": 13,
"preview": "package.json\n"
},
{
"path": ".prettierrc",
"chars": 89,
"preview": "{\n \"printWidth\": 80,\n \"semi\": false,\n \"singleQuote\": true,\n \"trailingComma\": \"all\"\n}\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 6321,
"preview": "# What should I know before I get started?\n\n## Code of Conduct\n\nThis project adheres to the Contributor Covenant [code o"
},
{
"path": "LICENSE.md",
"chars": 1095,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Mailjet SAS, https://mjml.io\n\nPermission is hereby granted, free of charge, to"
},
{
"path": "README.md",
"chars": 7246,
"preview": "# 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=\"tex"
},
{
"path": "babel.config.js",
"chars": 526,
"preview": "module.exports = {\n presets: [['@babel/env', {\n targets: { node: '10' },\n include: ['transform-classes'],\n }]],\n"
},
{
"path": "doc/basic.md",
"chars": 5787,
"preview": "\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 "
},
{
"path": "doc/body_components.md",
"chars": 166,
"preview": "## Standard Body components\n\nBody components ease your development process by providing ready made responsive layouts th"
},
{
"path": "doc/community-components.md",
"chars": 731,
"preview": "## Community components\n\nIn addition to the standard components available in MJML, our awesome community is contributing"
},
{
"path": "doc/community-contributions.md",
"chars": 3169,
"preview": "## Community Contributions\n\nThe MJML ecosystem has a dedicated community that we count to help make it grow and provide "
},
{
"path": "doc/components_1.md",
"chars": 2873,
"preview": "## Components\n\nComponents are the core of MJML. A component is an abstraction of a more complex responsive HTML layout. "
},
{
"path": "doc/components_2.md",
"chars": 1330,
"preview": "### mj-include\n\nThe `mjml-core` package allows you to include external `.mjml` files\nto build your email template.\n\n```x"
},
{
"path": "doc/config.json",
"chars": 1675,
"preview": "[\n \"mjml/doc/guide.md\",\n \"mjml/doc/install.md\",\n \"mjml/doc/getting_started.md\",\n \"mjml/doc/basic.md\",\n \"mjml/doc/co"
},
{
"path": "doc/create.md",
"chars": 675,
"preview": "## Creating a Component\n\nOne of the great advantages of MJML is that it's component-based. Components abstract complex p"
},
{
"path": "doc/ending-tags.md",
"chars": 1088,
"preview": "### Ending tags\n\nSome MJML components are \"ending tags\". These are mostly the components that will contain text content,"
},
{
"path": "doc/getting_started.md",
"chars": 2647,
"preview": "## Getting Started\n\nThis is a responsive email:\n\n<figure>\n <img width=\"300px\" src=\"https://static.mailjet.com/mjml-webs"
},
{
"path": "doc/guide.md",
"chars": 1916,
"preview": "---\ntitle: API Reference\n\nlanguage_tabs:\n - html: MJML\n\ntoc_footers:\n - <a href='https://github.com/mjmlio/mjml'>Fork "
},
{
"path": "doc/head_components.md",
"chars": 180,
"preview": "## Standard Head components\n\nHead components ease your development process, for example, enabling you to import fonts, d"
},
{
"path": "doc/install.md",
"chars": 13237,
"preview": "## Installation\n\nYou can [install MJML](https://www.npmjs.com/package/mjml) with NPM to use it with NodeJS or the Comman"
},
{
"path": "doc/mjml-bar-chart.md",
"chars": 969,
"preview": "### mj-bar-chart\n\nAn open-source component that allows you to create fully embedded static bar charts in your MJML templ"
},
{
"path": "doc/mjml-chart.md",
"chars": 405,
"preview": "### mj-chart\n\nDisplays charts as images in your email.\n\nThanks to [image-charts](https://image-charts.com/) for their co"
},
{
"path": "doc/mjml-chartjs.md",
"chars": 503,
"preview": "### mj-chartjs\n\nDisplays [Chart.js](https://www.chartjs.org/) charts as images in your email. Chart.js is an open-source"
},
{
"path": "doc/mjml-mso-button.md",
"chars": 1342,
"preview": "### mjml-msobutton\n\nA button that uses the [VML](https://docs.microsoft.com/en-us/windows/win32/vml/shape-element--vml) "
},
{
"path": "doc/mjml-qr-code.md",
"chars": 390,
"preview": "### mj-qr-code\n\nDisplays QR codes in your email. It's available on [Github](https://github.com/typpo/mjml-qr-code) and ["
},
{
"path": "doc/ports.md",
"chars": 2261,
"preview": "## Ports and Language Bindings\n\nMJML is also available for other platforms to use. The community has created ports to th"
},
{
"path": "doc/tooling.md",
"chars": 1332,
"preview": "## Tooling\n\nIn order to provide you with the best and most efficient experience using MJML, we've developed some tools t"
},
{
"path": "doc/using_mjml_in_json.md",
"chars": 1802,
"preview": "## Using MJML in JSON\n\nMJML can not only be used as a markup, but also as a JSON object, very useful for\nprogrammatic ma"
},
{
"path": "lerna.json",
"chars": 175,
"preview": "{\n \"packages\": [\n \"packages/*\"\n ],\n \"command\": {\n \"publish\": {\n \"exact\": true\n }\n },\n \"npmClient\": \"y"
},
{
"path": "package.json",
"chars": 1165,
"preview": "{\n \"name\": \"mjml-master\",\n \"private\": true,\n \"scripts\": {\n \"build:watch\": \"lerna run build --parallel -- -- -w\",\n "
},
{
"path": "packages/mjml/README.md",
"chars": 5979,
"preview": "# MJML 4\n\n<p style=\"text-align: center;\" >\n <a href=\"http://mjml.io\" target=\"_blank\">\n <img width=\"250\"src=\"https://"
},
{
"path": "packages/mjml/bin/mjml",
"chars": 65,
"preview": "#!/usr/bin/env node\n\nrequire('../lib/index')\nrequire('mjml-cli')\n"
},
{
"path": "packages/mjml/package.json",
"chars": 1009,
"preview": "{\n \"name\": \"mjml\",\n \"description\": \"MJML: the only framework that makes responsive-email easy\",\n \"version\": \"4.18.0\","
},
{
"path": "packages/mjml/src/index.js",
"chars": 313,
"preview": "import mjml2html, { components, assignComponents } from 'mjml-core'\nimport { dependencies, assignDependencies } from 'mj"
},
{
"path": "packages/mjml/test/accordion-fontFamily.test.js",
"chars": 2490,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-accordion "
},
{
"path": "packages/mjml/test/accordion-padding.test.js",
"chars": 2070,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-accordion "
},
{
"path": "packages/mjml/test/accordionTitle-fontWeight.test.js",
"chars": 2859,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-accordion-"
},
{
"path": "packages/mjml/test/carousel-hoverSupported.test.js",
"chars": 1339,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-carousel-t"
},
{
"path": "packages/mjml/test/column-border-radius.test.js",
"chars": 1870,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-column bor"
},
{
"path": "packages/mjml/test/html-attributes.test.js",
"chars": 2236,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst { sortBy } = require('lodash')\nconst mjml = requi"
},
{
"path": "packages/mjml/test/html-comments.test.js",
"chars": 1101,
"preview": "const chai = require('chai')\nconst mjml = require('../lib')\n\ndescribe('HTML comments', function () {\n it('should not al"
},
{
"path": "packages/mjml/test/lazy-head-style.test.js",
"chars": 1197,
"preview": "const chai = require('chai')\nconst spies = require('chai-spies')\nconst mjml = require('../lib')\nconst {\n HeadComponent,"
},
{
"path": "packages/mjml/test/navbar-ico-padding.test.js",
"chars": 1827,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-navbar ico"
},
{
"path": "packages/mjml/test/social-align.test.js",
"chars": 1200,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-social-ele"
},
{
"path": "packages/mjml/test/social-icon-height.test.js",
"chars": 1477,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-social ico"
},
{
"path": "packages/mjml/test/table-cellspacing.test.js",
"chars": 1895,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-table cell"
},
{
"path": "packages/mjml/test/tableWidth.test.js",
"chars": 3641,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-table widt"
},
{
"path": "packages/mjml/test/wrapper-border-radius.test.js",
"chars": 2228,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-wrapper an"
},
{
"path": "packages/mjml/test/wrapper-gap.test.js",
"chars": 1724,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst mjml = require('../lib')\n\ndescribe('mj-wrapper ga"
},
{
"path": "packages/mjml-accordion/README.md",
"chars": 11505,
"preview": "### mj-accordion\n\nAn interactive MJML component that stacks content in tabs, so the information is collapsed and only th"
},
{
"path": "packages/mjml-accordion/package.json",
"chars": 696,
"preview": "{\n \"name\": \"mjml-accordion\",\n \"description\": \"mjml-accordion\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"fil"
},
{
"path": "packages/mjml-accordion/src/Accordion.js",
"chars": 3636,
"preview": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjAccordion extends BodyComponent {\n static componentNa"
},
{
"path": "packages/mjml-accordion/src/AccordionElement.js",
"chars": 3118,
"preview": "import { BodyComponent } from 'mjml-core'\nimport { find } from 'lodash'\nimport conditionalTag from 'mjml-core/lib/helper"
},
{
"path": "packages/mjml-accordion/src/AccordionText.js",
"chars": 2706,
"preview": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjAccordionText extends BodyComponent {\n static compone"
},
{
"path": "packages/mjml-accordion/src/AccordionTitle.js",
"chars": 3784,
"preview": "import { BodyComponent } from 'mjml-core'\nimport conditionalTag from 'mjml-core/lib/helpers/conditionalTag'\n\nexport defa"
},
{
"path": "packages/mjml-accordion/src/index.js",
"chars": 236,
"preview": "export { default as Accordion } from './Accordion'\nexport { default as AccordionElement } from './AccordionElement'\nexpo"
},
{
"path": "packages/mjml-body/README.md",
"chars": 1195,
"preview": "### mj-body\n\nThis is the starting point of your email. To aid accessibility, MJML automatically adds a `div` tag as the "
},
{
"path": "packages/mjml-body/package.json",
"chars": 681,
"preview": "{\n \"name\": \"mjml-body\",\n \"description\": \"mjml-body\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n "
},
{
"path": "packages/mjml-body/src/index.js",
"chars": 1059,
"preview": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjBody extends BodyComponent {\n static componentName = "
},
{
"path": "packages/mjml-browser/README.md",
"chars": 410,
"preview": "## 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 p"
},
{
"path": "packages/mjml-browser/browser-mocks/fs.js",
"chars": 159,
"preview": "module.exports = {\n readFileSync: () => {\n console.warn('fs should not be used in browser build') // eslint-disable-"
},
{
"path": "packages/mjml-browser/browser-mocks/path.js",
"chars": 245,
"preview": "const mockFn = () => {\n console.warn('fs should not be used in browser build') // eslint-disable-line no-console\n retu"
},
{
"path": "packages/mjml-browser/browser-mocks/uglify-js.js",
"chars": 20,
"preview": "module.exports = {}\n"
},
{
"path": "packages/mjml-browser/package.json",
"chars": 1002,
"preview": "{\n \"name\": \"mjml-browser\",\n \"description\": \"MJML: the only framework that makes responsive-email easy\",\n \"version\": \""
},
{
"path": "packages/mjml-browser/webpack.config.js",
"chars": 1626,
"preview": "const path = require('path')\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')\n\nmodule.exports = {\n mode: 'prod"
},
{
"path": "packages/mjml-button/README.md",
"chars": 7029,
"preview": "### mj-button\n\nDisplays a customizable button.\n\n<figure>\n <img src=\"https://static.mailjet.com/mjml-website/documentati"
},
{
"path": "packages/mjml-button/package.json",
"chars": 687,
"preview": "{\n \"name\": \"mjml-button\",\n \"description\": \"mjml-button\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": ["
},
{
"path": "packages/mjml-button/src/index.js",
"chars": 5247,
"preview": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default c"
},
{
"path": "packages/mjml-carousel/README.md",
"chars": 5764,
"preview": "### mj-carousel\n\nDisplays a gallery of images or \"carousel\". Readers can interact by hovering and clicking on thumbnails"
},
{
"path": "packages/mjml-carousel/package.json",
"chars": 693,
"preview": "{\n \"name\": \"mjml-carousel\",\n \"description\": \"mjml-carousel\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files"
},
{
"path": "packages/mjml-carousel/src/Carousel.js",
"chars": 10796,
"preview": "import { BodyComponent } from 'mjml-core'\nimport { range, repeat, min, map } from 'lodash'\n\nimport { msoConditionalTag }"
},
{
"path": "packages/mjml-carousel/src/CarouselImage.js",
"chars": 4034,
"preview": "import { BodyComponent, suffixCssClasses } from 'mjml-core'\n\nexport default class MjCarouselImage extends BodyComponent "
},
{
"path": "packages/mjml-carousel/src/index.js",
"chars": 108,
"preview": "export { default as Carousel } from './Carousel'\nexport { default as CarouselImage } from './CarouselImage'\n"
},
{
"path": "packages/mjml-cli/README.md",
"chars": 4575,
"preview": "## mjml-cli\n\n# Installation\n\nWe recommend installing and using MJML locally, in a project folder where you'll use MJML:\n"
},
{
"path": "packages/mjml-cli/bin/mjml",
"chars": 51,
"preview": "#!/usr/bin/env node\n\nrequire('../lib/client.js')()\n"
},
{
"path": "packages/mjml-cli/package.json",
"chars": 1034,
"preview": "{\n \"name\": \"mjml-cli\",\n \"description\": \"MJML: the only framework that makes responsive-email easy\",\n \"version\": \"4.18"
},
{
"path": "packages/mjml-cli/src/client.js",
"chars": 9206,
"preview": "import path from 'path'\nimport yargs from 'yargs'\nimport { flow, pick, isNil, negate, pickBy } from 'lodash/fp'\nimport {"
},
{
"path": "packages/mjml-cli/src/commands/outputToConsole.js",
"chars": 274,
"preview": "export default ({ compiled: { html }, file }, addFileHeaderComment) =>\n new Promise((resolve) => {\n let output = ''\n"
},
{
"path": "packages/mjml-cli/src/commands/outputToFile.js",
"chars": 1195,
"preview": "import fs from 'fs'\nimport path from 'path'\n\nexport const isDirectory = (file) => {\n try {\n const outputPath = path."
},
{
"path": "packages/mjml-cli/src/commands/readFile.js",
"chars": 417,
"preview": "import fs from 'fs'\nimport { sync } from 'glob'\nimport { flatMap } from 'lodash'\n\nexport const flatMapPaths = (paths) =>"
},
{
"path": "packages/mjml-cli/src/commands/readStream.js",
"chars": 299,
"preview": "const stdinSync = () =>\n new Promise((res) => {\n let buffer = ''\n\n const stream = process.stdin\n\n stream.on('d"
},
{
"path": "packages/mjml-cli/src/commands/watchFiles.js",
"chars": 3861,
"preview": "/* eslint-disable no-console */\nimport chokidar from 'chokidar'\nimport { sync } from 'glob'\nimport { match } from 'minim"
},
{
"path": "packages/mjml-cli/src/helpers/defaultOptions.js",
"chars": 54,
"preview": "export default {\n beautify: true,\n minify: false,\n}\n"
},
{
"path": "packages/mjml-cli/src/helpers/fileContext.js",
"chars": 2007,
"preview": "import fs from 'fs'\nimport path from 'path'\n\nconst includeRegexp =\n /<mj-include[^<>]+path=['\"](.*(?:\\.mjml|\\.css|\\.htm"
},
{
"path": "packages/mjml-column/README.md",
"chars": 6366,
"preview": "### mj-column\n\nColumns enable you to organize the content of your sections into distinct columns which stack when viewed"
},
{
"path": "packages/mjml-column/package.json",
"chars": 687,
"preview": "{\n \"name\": \"mjml-column\",\n \"description\": \"mjml-column\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": ["
},
{
"path": "packages/mjml-column/src/index.js",
"chars": 9050,
"preview": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default c"
},
{
"path": "packages/mjml-core/README.md",
"chars": 264,
"preview": "## mjml-core\n\n### Installation\n\n```bash\nnpm install --save mjml-core\n```\n\nThis is the core mjml library, composed by a s"
},
{
"path": "packages/mjml-core/package.json",
"chars": 952,
"preview": "{\n \"name\": \"mjml-core\",\n \"description\": \"mjml-core\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n "
},
{
"path": "packages/mjml-core/src/components.js",
"chars": 536,
"preview": "import { kebabCase } from 'lodash'\nimport { registerDependencies } from 'mjml-validator'\n\nconst components = {}\n\nexport "
},
{
"path": "packages/mjml-core/src/createComponent.js",
"chars": 6562,
"preview": "// eslint-disable-next-line max-classes-per-file\nimport {\n get,\n forEach,\n identity,\n reduce,\n kebabCase,\n find,\n "
},
{
"path": "packages/mjml-core/src/helpers/conditionalTag.js",
"chars": 823,
"preview": "export const startConditionalTag = '<!--[if mso | IE]>'\nexport const startMsoConditionalTag = '<!--[if mso]>'\nexport con"
},
{
"path": "packages/mjml-core/src/helpers/fonts.js",
"chars": 857,
"preview": "import { forEach, map } from 'lodash'\n\n// eslint-disable-next-line import/prefer-default-export\nexport function buildFon"
},
{
"path": "packages/mjml-core/src/helpers/formatAttributes.js",
"chars": 599,
"preview": "import { reduce } from 'lodash'\nimport { initializeType } from '../types/type'\n\nexport default (attributes, allowedAttri"
},
{
"path": "packages/mjml-core/src/helpers/genRandomHexString.js",
"chars": 182,
"preview": "export default function genRandomHexString(length) {\n let str = ''\n for (let i = 0; i < length; i += 1) {\n str += M"
},
{
"path": "packages/mjml-core/src/helpers/jsonToXML.js",
"chars": 432,
"preview": "const jsonToXML = ({ tagName, attributes, children, content }) => {\n const subNode =\n children && children.length > "
},
{
"path": "packages/mjml-core/src/helpers/makeLowerBreakpoint.js",
"chars": 209,
"preview": "export default function makeLowerBreakpoint(breakpoint) {\n try {\n const pixels = Number.parseInt(breakpoint.match('["
},
{
"path": "packages/mjml-core/src/helpers/mediaQueries.js",
"chars": 1229,
"preview": "import { map, isEmpty } from 'lodash'\n\n// eslint-disable-next-line import/prefer-default-export\nexport default function "
},
{
"path": "packages/mjml-core/src/helpers/mergeOutlookConditionnals.js",
"chars": 174,
"preview": "// # OPTIMIZE ME: — check if previous conditionnal is `<!--[if mso | I`]>` too\nexport default (content) =>\n content.rep"
},
{
"path": "packages/mjml-core/src/helpers/minifyOutlookConditionnals.js",
"chars": 480,
"preview": "export default (content) =>\n // find conditionnal comment blocks\n content.replace(\n /(<!--\\[if\\s[^\\]]+]>)([\\s\\S]*?)"
},
{
"path": "packages/mjml-core/src/helpers/mjmlconfig.js",
"chars": 4098,
"preview": "import path from 'path'\nimport fs from 'fs'\nimport { registerDependencies } from 'mjml-validator'\n\nimport { registerComp"
},
{
"path": "packages/mjml-core/src/helpers/preview.js",
"chars": 243,
"preview": "export default function (content) {\n if (content === '') {\n return ''\n }\n\n return `\n <div style=\"display:none;f"
},
{
"path": "packages/mjml-core/src/helpers/shorthandParser.js",
"chars": 717,
"preview": "import { get } from 'lodash'\n\nexport default function (cssValue, direction) {\n const splittedCssValue = cssValue.trim()"
},
{
"path": "packages/mjml-core/src/helpers/skeleton.js",
"chars": 2431,
"preview": "import { negate, isNil } from 'lodash'\nimport buildPreview from './preview'\nimport { buildFontsTags } from './fonts'\nimp"
},
{
"path": "packages/mjml-core/src/helpers/styles.js",
"chars": 773,
"preview": "import { isFunction } from 'lodash'\n\nexport function buildStyleFromComponents(\n breakpoint,\n componentsHeadStyles,\n h"
},
{
"path": "packages/mjml-core/src/helpers/suffixCssClasses.js",
"chars": 146,
"preview": "export default (classes, suffix) =>\n classes\n ? classes\n .split(' ')\n .map((c) => `${c}-${suffix}`)\n "
},
{
"path": "packages/mjml-core/src/helpers/widthParser.js",
"chars": 457,
"preview": "const unitRegex = /[\\d.,]*(\\D*)$/\n\nexport default function widthParser(width, options = {}) {\n const { parseFloatToInt "
},
{
"path": "packages/mjml-core/src/index.js",
"chars": 11718,
"preview": "import {\n find,\n filter,\n get,\n identity,\n map,\n omit,\n reduce,\n isObject,\n each,\n isEmpty,\n} from 'lodash'\nim"
},
{
"path": "packages/mjml-core/src/types/boolean.js",
"chars": 302,
"preview": "import Type from './type'\n\nexport const matcher = /^boolean/gim\n\nexport default () =>\n class Boolean extends Type {\n "
},
{
"path": "packages/mjml-core/src/types/color.js",
"chars": 761,
"preview": "import Type from './type'\nimport colors from './helpers/colors'\n\nexport const matcher = /^color/gim\n\nconst shorthandRege"
},
{
"path": "packages/mjml-core/src/types/enum.js",
"chars": 480,
"preview": "import { escapeRegExp } from 'lodash'\nimport Type from './type'\n\nexport const matcher = /^enum/gim\n\nexport default (para"
},
{
"path": "packages/mjml-core/src/types/helpers/colors.js",
"chars": 2259,
"preview": "export default [\n 'aliceblue',\n 'antiquewhite',\n 'aqua',\n 'aquamarine',\n 'azure',\n 'beige',\n 'bisque',\n 'black',"
},
{
"path": "packages/mjml-core/src/types/index.js",
"chars": 738,
"preview": "import NBoolean, { matcher as booleanMatcher } from './boolean'\nimport Color, { matcher as colorMatcher } from './color'"
},
{
"path": "packages/mjml-core/src/types/integer.js",
"chars": 203,
"preview": "import Type from './type'\n\nexport const matcher = /^integer/gim\n\nexport default () =>\n class NInteger extends Type {\n "
},
{
"path": "packages/mjml-core/src/types/string.js",
"chars": 200,
"preview": "import Type from './type'\n\nexport const matcher = /^string/gim\n\nexport default () =>\n class NString extends Type {\n "
},
{
"path": "packages/mjml-core/src/types/type.js",
"chars": 1089,
"preview": "import { some, find } from 'lodash'\nimport typesConstructors from './index'\n\n// Avoid recreate existing types\nexport con"
},
{
"path": "packages/mjml-core/src/types/unit.js",
"chars": 1008,
"preview": "import { escapeRegExp } from 'lodash'\nimport Type from './type'\n\nexport const matcher = /^(unit|unitWithNegative)\\(.*\\)/"
},
{
"path": "packages/mjml-core/tests/.eslintrc",
"chars": 134,
"preview": "{\n \"extends\": \"../../../.eslintrc\",\n \"rules\": {\n \"import/no-extraneous-dependencies\": [\"error\", {\"devDependencies\":"
},
{
"path": "packages/mjml-core/tests/index.js",
"chars": 208,
"preview": "require('./jsonToXml-test')\nrequire('./mergeOutlookConditionnals-test')\nrequire('./minifyOutlookConditionnals-test')\nreq"
},
{
"path": "packages/mjml-core/tests/jsonToXml-test.js",
"chars": 1269,
"preview": "const chai = require('chai')\nconst jsonToXml = require('../lib/helpers/jsonToXML')\n\nconst json = {\n line: 1,\n included"
},
{
"path": "packages/mjml-core/tests/mergeOutlookConditionnals-test.js",
"chars": 1212,
"preview": "const chai = require('chai')\nconst mergeOutlookConditionnals = require('../lib/helpers/mergeOutlookConditionnals')\n\ncons"
},
{
"path": "packages/mjml-core/tests/minifyOutlookConditionnals-test.js",
"chars": 4854,
"preview": "const chai = require('chai')\nconst minifyOutlookConditionnals = require('../lib/helpers/minifyOutlookConditionnals')\n\nco"
},
{
"path": "packages/mjml-core/tests/shorthandParser-test.js",
"chars": 879,
"preview": "const chai = require('chai')\nconst helper = require('../lib/helpers/shorthandParser')\n\nconst shorthandParser = helper &&"
},
{
"path": "packages/mjml-core/tests/skeleton-test.js",
"chars": 1655,
"preview": "const chai = require('chai')\nconst { load } = require('cheerio')\nconst skeleton = require('../lib/helpers/skeleton')\n\n//"
},
{
"path": "packages/mjml-core/tests/widthParser-test.js",
"chars": 676,
"preview": "const chai = require('chai')\nconst widthParser = require('../lib/helpers/widthParser')\n\nconst testValues = [\n {\n inp"
},
{
"path": "packages/mjml-divider/README.md",
"chars": 2199,
"preview": "### mj-divider\n\nDisplays a horizontal divider that can be customized like a HTML border.\n\n```xml\n<mjml>\n <mj-body>\n "
},
{
"path": "packages/mjml-divider/package.json",
"chars": 690,
"preview": "{\n \"name\": \"mjml-divider\",\n \"description\": \"mjml-divider\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\":"
},
{
"path": "packages/mjml-divider/src/index.js",
"chars": 2741,
"preview": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default c"
},
{
"path": "packages/mjml-group/README.md",
"chars": 2838,
"preview": "### mj-group\n\nPrevent adjacent `mj-column` instances from stacking on mobile by wrapping them inside an `mj-group` tag, "
},
{
"path": "packages/mjml-group/package.json",
"chars": 684,
"preview": "{\n \"name\": \"mjml-group\",\n \"description\": \"mjml-group\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n "
},
{
"path": "packages/mjml-group/src/index.js",
"chars": 5309,
"preview": "import { BodyComponent } from 'mjml-core'\n\nimport widthParser from 'mjml-core/lib/helpers/widthParser'\n\nexport default c"
},
{
"path": "packages/mjml-head/package.json",
"chars": 681,
"preview": "{\n \"name\": \"mjml-head\",\n \"description\": \"mjml-head\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n "
},
{
"path": "packages/mjml-head/src/index.js",
"chars": 185,
"preview": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjHead extends HeadComponent {\n static componentName = "
},
{
"path": "packages/mjml-head-attributes/README.md",
"chars": 1318,
"preview": "### mj-attributes\n\nInside the `mj-attributes` tag, you can cite other MJML components, like `mj-text` for example, to ov"
},
{
"path": "packages/mjml-head-attributes/package.json",
"chars": 714,
"preview": "{\n \"name\": \"mjml-head-attributes\",\n \"description\": \"mjml-head-attributes\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index"
},
{
"path": "packages/mjml-head-attributes/src/index.js",
"chars": 832,
"preview": "import { forEach, omit, reduce } from 'lodash'\n\nimport { HeadComponent } from 'mjml-core'\n\nexport default class MjAttrib"
},
{
"path": "packages/mjml-head-breakpoint/README.md",
"chars": 698,
"preview": "### mj-breakpoint\n\nAllows you to control at what width the layout should change from the desktop/mobile view to the desk"
},
{
"path": "packages/mjml-head-breakpoint/package.json",
"chars": 714,
"preview": "{\n \"name\": \"mjml-head-breakpoint\",\n \"description\": \"mjml-head-breakpoint\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index"
},
{
"path": "packages/mjml-head-breakpoint/src/index.js",
"chars": 333,
"preview": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjBreakpoint extends HeadComponent {\n static componentN"
},
{
"path": "packages/mjml-head-font/README.md",
"chars": 1032,
"preview": "### mj-font\n\nImports external fonts and is only applied if the template uses the font.\n\nThe `href` attribute should poin"
},
{
"path": "packages/mjml-head-font/package.json",
"chars": 696,
"preview": "{\n \"name\": \"mjml-head-font\",\n \"description\": \"mjml-head-font\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"fil"
},
{
"path": "packages/mjml-head-font/src/index.js",
"chars": 332,
"preview": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjFont extends HeadComponent {\n static componentName = "
},
{
"path": "packages/mjml-head-html-attributes/README.md",
"chars": 1343,
"preview": "### mj-html-attributes\n\nAllows you to add custom attributes on any HTML tag within the generated HTML, using CSS selecto"
},
{
"path": "packages/mjml-head-html-attributes/package.json",
"chars": 729,
"preview": "{\n \"name\": \"mjml-head-html-attributes\",\n \"description\": \"mjml-head-html-attributes\",\n \"version\": \"4.18.0\",\n \"main\": "
},
{
"path": "packages/mjml-head-html-attributes/src/index.js",
"chars": 826,
"preview": "import { get } from 'lodash'\nimport { HeadComponent } from 'mjml-core'\n\nexport default class MjHtmlAttributes extends He"
},
{
"path": "packages/mjml-head-preview/README.md",
"chars": 517,
"preview": "### mj-preview\n\nThis tag allows you to set the preview text that will be displayed in the inbox of the recipient.\n\n```xm"
},
{
"path": "packages/mjml-head-preview/package.json",
"chars": 705,
"preview": "{\n \"name\": \"mjml-head-preview\",\n \"description\": \"mjml-head-preview\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n"
},
{
"path": "packages/mjml-head-preview/src/index.js",
"chars": 256,
"preview": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjPreview extends HeadComponent {\n static componentName"
},
{
"path": "packages/mjml-head-style/README.md",
"chars": 1844,
"preview": "### mj-style\n\nAllows you to set CSS styles that will be applied to your MJML document as well as the outputted HTML.\n\nTh"
},
{
"path": "packages/mjml-head-style/package.json",
"chars": 699,
"preview": "{\n \"name\": \"mjml-head-style\",\n \"description\": \"mjml-head-style\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"f"
},
{
"path": "packages/mjml-head-style/src/index.js",
"chars": 386,
"preview": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjStyle extends HeadComponent {\n static componentName ="
},
{
"path": "packages/mjml-head-title/README.md",
"chars": 652,
"preview": "### mj-title\n\nDefines the document's title by populating the title tag. This can be shown in the browsers title bar in s"
},
{
"path": "packages/mjml-head-title/package.json",
"chars": 699,
"preview": "{\n \"name\": \"mjml-head-title\",\n \"description\": \"mjml-head-title\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"f"
},
{
"path": "packages/mjml-head-title/src/index.js",
"chars": 250,
"preview": "import { HeadComponent } from 'mjml-core'\n\nexport default class MjTitle extends HeadComponent {\n static componentName ="
},
{
"path": "packages/mjml-hero/README.md",
"chars": 5411,
"preview": "### mj-hero\n\nDisplays a hero image and behaves like an `mj-section` tag with a single `mj-column` tag.\n\nThe `background-"
},
{
"path": "packages/mjml-hero/package.json",
"chars": 681,
"preview": "{\n \"name\": \"mjml-hero\",\n \"description\": \"mjml-hero\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n "
},
{
"path": "packages/mjml-hero/src/index.js",
"chars": 11087,
"preview": "import { BodyComponent } from 'mjml-core'\nimport { flow, identity, join, filter } from 'lodash/fp'\n\nimport widthParser f"
},
{
"path": "packages/mjml-image/README.md",
"chars": 5591,
"preview": "### mj-image\n\nDisplays a responsive image in your email. It is similar to the HTML `<img />` tag.\n\nNote that if no width"
},
{
"path": "packages/mjml-image/package.json",
"chars": 684,
"preview": "{\n \"name\": \"mjml-image\",\n \"description\": \"mjml-image\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n "
},
{
"path": "packages/mjml-image/src/index.js",
"chars": 4655,
"preview": "import { min } from 'lodash'\n\nimport { BodyComponent, makeLowerBreakpoint } from 'mjml-core'\n\nimport widthParser from 'm"
},
{
"path": "packages/mjml-migrate/LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2017 Nicolas Garnier\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "packages/mjml-migrate/README.md",
"chars": 493,
"preview": "# mjml-migrate\n\n## Purpose\n\nMakes a template following the MJML 3 syntax compatible with MJML 4.\n\n## Installation\n\nClone"
},
{
"path": "packages/mjml-migrate/package.json",
"chars": 791,
"preview": "{\n \"name\": \"mjml-migrate\",\n \"version\": \"4.18.0\",\n \"description\": \"A tool to migrate a template from MJML 3 to MJML 4\""
},
{
"path": "packages/mjml-migrate/src/cli.js",
"chars": 638,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport yargs from 'yargs'\nimport migrate from './migrate'\nimport { version } fr"
},
{
"path": "packages/mjml-migrate/src/config.js",
"chars": 659,
"preview": "const unavailableTags = ['mj-html', 'mj-invoice', 'mj-list', 'mj-location']\n\nconst attributesWithUnit = [\n 'background-"
},
{
"path": "packages/mjml-migrate/src/migrate.js",
"chars": 5869,
"preview": "/* eslint-disable no-console */\n\nimport { keys, find, isNil } from 'lodash'\nimport MJMLParser from 'mjml-parser-xml'\nimp"
},
{
"path": "packages/mjml-navbar/README.md",
"chars": 11202,
"preview": "### mj-navbar\n\nDisplays a navigation menu with an optional `hamburger` mode for mobile devices.\n\n```xml\n<mjml>\n <mj-bod"
},
{
"path": "packages/mjml-navbar/package.json",
"chars": 687,
"preview": "{\n \"name\": \"mjml-navbar\",\n \"description\": \"mjml-navbar\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": ["
},
{
"path": "packages/mjml-navbar/src/Navbar.js",
"chars": 5613,
"preview": "import { BodyComponent, makeLowerBreakpoint } from 'mjml-core'\n\nimport conditionalTag, {\n msoConditionalTag,\n} from 'mj"
},
{
"path": "packages/mjml-navbar/src/NavbarLink.js",
"chars": 3380,
"preview": "import { BodyComponent, suffixCssClasses } from 'mjml-core'\n\nimport conditionalTag from 'mjml-core/lib/helpers/condition"
},
{
"path": "packages/mjml-navbar/src/index.js",
"chars": 98,
"preview": "export { default as Navbar } from './Navbar'\nexport { default as NavbarLink } from './NavbarLink'\n"
},
{
"path": "packages/mjml-parser-xml/package.json",
"chars": 835,
"preview": "{\n \"name\": \"mjml-parser-xml\",\n \"description\": \"mjml-parser-xml\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"f"
},
{
"path": "packages/mjml-parser-xml/src/helpers/cleanNode.js",
"chars": 383,
"preview": "import _ from 'lodash'\n\nexport default function cleanNode(node) {\n delete node.parent\n\n // Delete children if needed\n "
},
{
"path": "packages/mjml-parser-xml/src/helpers/convertBooleansOnAttrs.js",
"chars": 347,
"preview": "import { mapValues } from 'lodash'\n\n/**\n * Convert \"true\" and \"false\" string attributes values\n * to corresponding Boole"
},
{
"path": "packages/mjml-parser-xml/src/helpers/setEmptyAttributes.js",
"chars": 216,
"preview": "import { forEach } from 'lodash'\n\nexport default function setEmptyAttributes(node) {\n if (!node.attributes) {\n node."
},
{
"path": "packages/mjml-parser-xml/src/index.js",
"chars": 9251,
"preview": "import { Parser } from 'htmlparser2'\n\nimport { isObject, findLastIndex, find } from 'lodash'\nimport { filter, map, flow "
},
{
"path": "packages/mjml-parser-xml/test/incl.mjml",
"chars": 181,
"preview": "<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 "
},
{
"path": "packages/mjml-parser-xml/test/test-preprocessors.js",
"chars": 850,
"preview": "const { template } = require('lodash')\nconst MJMLParser = require('../lib')\nconst mjml2html = require('../../mjml/lib')\n"
},
{
"path": "packages/mjml-parser-xml/test/test-utils.js",
"chars": 1396,
"preview": "const _ = require('lodash')\n\nfunction omitDeepLodash(input, props) {\n function omitDeepOnOwnProps(obj) {\n if (!_.isA"
},
{
"path": "packages/mjml-parser-xml/test/test-values.js",
"chars": 15212,
"preview": "/* eslint-disable comma-dangle */\nmodule.exports = [\n {\n test: 'Special characters',\n mjml: `\n<mjml>\n <mj-body>\n"
},
{
"path": "packages/mjml-parser-xml/test/test.js",
"chars": 874,
"preview": "const MJMLParser = require('../lib/index.js')\nrequire('mjml')\nconst components = require('mjml-core').components\nconst c"
},
{
"path": "packages/mjml-preset-core/README.md",
"chars": 323,
"preview": "## mjml-preset-core\n\n### Installation\n\n```bash\nnpm install --save mjml-preset-core\n```\n\nThis is the set of mjml componen"
},
{
"path": "packages/mjml-preset-core/package.json",
"chars": 1417,
"preview": "{\n \"name\": \"mjml-preset-core\",\n \"description\": \"mjml-preset-core\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n "
},
{
"path": "packages/mjml-preset-core/src/dependencies.js",
"chars": 1492,
"preview": "export default {\n mjml: ['mj-body', 'mj-head', 'mj-raw'],\n 'mj-accordion': ['mj-accordion-element', 'mj-raw'],\n 'mj-a"
},
{
"path": "packages/mjml-preset-core/src/index.js",
"chars": 1556,
"preview": "import { Social, SocialElement } from 'mjml-social'\nimport { Navbar, NavbarLink } from 'mjml-navbar'\nimport { Carousel, "
},
{
"path": "packages/mjml-raw/README.md",
"chars": 2027,
"preview": "### mj-raw\n\nDisplays raw HTML that is not parsed by the MJML engine. Anything left inside this tag should be raw, respon"
},
{
"path": "packages/mjml-raw/package.json",
"chars": 678,
"preview": "{\n \"name\": \"mjml-raw\",\n \"description\": \"mjml-raw\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\": [\n \""
},
{
"path": "packages/mjml-raw/src/index.js",
"chars": 302,
"preview": "import { BodyComponent } from 'mjml-core'\n\nexport default class MjRaw extends BodyComponent {\n static componentName = '"
},
{
"path": "packages/mjml-section/README.md",
"chars": 5773,
"preview": "### mj-section\n\nSections are rows within your email. They will be used to structure the layout.\n\n```xml\n<mjml>\n <mj-bod"
},
{
"path": "packages/mjml-section/package.json",
"chars": 690,
"preview": "{\n \"name\": \"mjml-section\",\n \"description\": \"mjml-section\",\n \"version\": \"4.18.0\",\n \"main\": \"lib/index.js\",\n \"files\":"
},
{
"path": "packages/mjml-section/src/index.js",
"chars": 14473,
"preview": "import { BodyComponent, suffixCssClasses } from 'mjml-core'\nimport { flow, identity, join, filter } from 'lodash/fp'\n\nco"
},
{
"path": "packages/mjml-social/README.md",
"chars": 12160,
"preview": "### mj-social\n\nDisplays calls-to-action for various social networks with their associated logo. You can add multiple soc"
}
]
// ... and 30 more files (download for full content)
About this extraction
This page contains the full source code of the mjmlio/mjml GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 230 files (467.7 KB), approximately 120.0k tokens, and a symbol index with 243 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.