diff --git a/.eslintrc b/.eslintrc
index f7cde9d16..34a3463e0 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,26 +1,12 @@
{
"extends": "airbnb",
"parser": "babel-eslint",
-
"rules": {
- "no-unused-expressions": 0,
- "no-unused-vars": [2, { "varsIgnorePattern": "^debug$" }],
- "strict": 0,
- "new-cap": 0,
- "camelcase": 0,
- "react/jsx-uses-react": 1,
- "semi": 0,
-
- "react/sort-comp": [0, {
- "order": [
- "lifecycle",
- "everything-else",
- "render"
- ]
- }],
-
"array-bracket-spacing": 0,
"arrow-body-style": 0,
+ "arrow-parens": 0,
+ "camelcase": 0,
+ "class-methods-use-this": 0,
"comma-dangle": [2, "never"],
"consistent-return": 0,
"default-case": 0,
@@ -32,16 +18,21 @@
"indent": [2, 2, {"SwitchCase": 1}],
"key-spacing": 0,
"max-len": 0,
+ "new-cap": 0,
"no-case-declarations": 0,
"no-confusing-arrow": 0,
"no-console": 2,
+ "no-mixed-operators": 0,
"no-multi-spaces": 0,
"no-nested-ternary": 0,
"no-param-reassign": 0,
+ "no-plusplus": 0,
"no-return-assign": 0,
"no-shadow": 0,
"no-throw-literal": 0,
"no-underscore-dangle": 0,
+ "no-unused-expressions": 0,
+ "no-unused-vars": [2, { "varsIgnorePattern": "^debug$" }],
"no-use-before-define": 0,
"object-curly-spacing": 0,
"object-shorthand": 0,
@@ -50,30 +41,41 @@
"quote-props": 0,
"quotes": 0,
"radix": 0,
+ "semi": 0,
"space-before-function-paren": [2, "always"],
"space-in-parens": 0,
+ "strict": 0,
"vars-on-top": 0,
- "no-mixed-operators": 0,
- "import/no-unresolved": 0,
+ "import/extensions": 0,
"import/imports-first": 0,
+ "import/no-dynamic-require": 0,
"import/no-extraneous-dependencies": 0,
+ "import/no-unresolved": 0,
"import/prefer-default-export": 0,
"react/jsx-boolean-value": 0,
"react/jsx-closing-bracket-location": 0,
"react/jsx-curly-spacing": 0,
+ "react/jsx-filename-extension": 0,
"react/jsx-indent-props": 0,
"react/jsx-no-bind": 0,
+ "react/jsx-no-target-blank": 0,
+ "react/jsx-uses-react": 1,
+ "react/no-danger": 0,
"react/no-did-mount-set-state": 0,
"react/no-did-update-set-state": 0,
"react/no-multi-comp": 0,
"react/prefer-stateless-function": 0,
"react/prop-types": 0,
- "react/jsx-filename-extension": 0,
- "react/jsx-no-target-blank": 0
+ "react/sort-comp": [0, {
+ "order": [
+ "lifecycle",
+ "everything-else",
+ "render"
+ ]
+ }]
},
-
"env": {
"browser": true,
"node": true,
diff --git a/.travis.yml b/.travis.yml
index 28f570829..3e25e06d5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,29 @@
sudo: false
+env:
+ - CXX=g++-4.8
+language: node_js
+addons:
+ apt:
+ sources:
+ - ubuntu-toolchain-r-test
+ packages:
+ - gcc-4.8
+ - g++-4.8
language: node_js
node_js:
- 4
- 5
- 6
script:
+ - npm install
+ - gulp build
- npm run lint
- - cd packages/mjml-core
+ - cd packages/mjml-validator
+ - npm link mjml-core
+ - npm link
+ - npm install
+ - cd ../mjml-core
+ - npm link mjml-validator
- npm install
+ - npm link
- npm test
diff --git a/doc/components.md b/doc/components.md
index 79d616e7e..50136528b 100644
--- a/doc/components.md
+++ b/doc/components.md
@@ -1,8 +1,9 @@
# Components
-
Components are the core of MJML. A component is an abstraction of a more complex email-responsive HTML layout. It exposes attributes, enabling you to interact with the final component visual aspect.
+MJML comes out of the box with a set of standard components to help you build easily your first templates without having to reinvent the wheel.
+
For instance, the `mj-button` components is, on the inside, a complex HTML layout:
``` html
@@ -25,8 +26,3 @@ For instance, the `mj-button` components is, on the inside, a complex HTML layou
```
-
-# Standard components
-
-MJML comes out of the box with a set of standard components to help you build easily your first templates without having to reinvent the wheel.
-
diff --git a/doc/config.json b/doc/config.json
index 8a7567121..baedfeac9 100644
--- a/doc/config.json
+++ b/doc/config.json
@@ -28,6 +28,7 @@
"mjml/packages/mjml-spacer/README.md",
"mjml/packages/mjml-table/README.md",
"mjml/packages/mjml-text/README.md",
+ "mjml/packages/mjml-validator/README.md",
"mjml/doc/create.md",
"mjml/doc/tooling.md"
]
diff --git a/doc/create.md b/doc/create.md
index 922c4b50a..8d7640918 100644
--- a/doc/create.md
+++ b/doc/create.md
@@ -1,197 +1,5 @@
# Create a Component
-Creating a component is easy! With custom components, you can abstract complex patterns and reuse them easily whenever you need them in your emails!
+One of the great advantages of MJML is that it's component based. Components abstract complex patterns and can easily be reused. Added to the standard library of components, it is also possible to create your own components!
-Let's create a simple `Title` component.
-
-### Generate the template file
-
-```
-
-$> mjml --init-component title
-
-```
-run the following in your terminal. It will create a `Title.js` file in the current working directory.
-
-### Imports
-
-``` javascript
-
-import React, { Component } from 'react'
-import {
- MJMLElement,
- elements,
- registerElement,
-} from 'mjml'
-
-```
-These are the required modules to build your component:
-
-[React](https://facebook.github.io/react/) is used by the engine to abstract higher level components and render them into HTML.
-
-[MJMLElement](https://github.com/mjmlio/mjml/blob/master/src/components/MJMLElement.js)
-
-[elements](https://github.com/mjmlio/mjml/blob/master/src/MJMLElementsCollection.js) contains all the standard MJML components.
-
-[registerElement](https://github.com/mjmlio/mjml/blob/master/src/MJMLElementsCollection.js#L17) is a helper function that allows you to register the component within the MJML engine.
-
-### Declare your dependencies
-
-``` javascript
-/*
- * Wrap your dependencies here.
- */
-const {
- text: MjText,
- // ...
-} = elements;
-
-```
-
-The first thing to do is to declare your dependencies at the top of your file.
-The key is the component name, and its value is the name you're going to use in the file.
-By convention it should be capitalized.
-
-## Class definition
-
-All MJML component have some special static that can be use to change the behaviour of your componenet
-
-``` javascript
-Title.tagName = 'title'
-Title.defaultMJMLDefinition = {
- attributes: {
- 'color': '#424242',
- 'font-family': 'Helvetica',
- 'margin-top': '10px'
- }
-}
-Title.endingTag = true
-Title.baseStyles = {
- div: {
- color: "blue"
- }
-}
-Title.postRender = ($) => {
- $('.title').removeAttr('data-title-color');
- return $
-}
-```
-
-- tagName: modify the tag name of your component, here it will be `
`
-- endingTag: set to false if your component can include some other MJML component (example: mj-body/mj-section/mj-column are not ending tags, and mj-text/mj-image are both ending tags)`
-
-## Default and readonly attributes
-
-``` javascript
-const defaultMJMLDefinition = {
- attributes: {
- 'color': '#424242',
- 'font-family': 'Helvetica',
- 'margin-top': '10px'
- }
-}
-```
-
-Here you can modify and change your element's default and/or readonly attributes.
-The attributes are stored within the defaultMJMLDefinition variable at the top.
-It can contain any CSS property or component property, but please make sure it will be compatible with most email clients to keep MJML responsive and compatible.
-
-## Post render
-In some case, you'll need to modify the rendered html, like replace some placeholder for outlook by conditional tag then you can define a postRender static function that take jQuery/[Cheerio](https://github.com/cheeriojs/cheerio) with the rendered document.
-
-``` javascript
-Title.postRender = $ => {
- $('.title').prepend(``)
- $('.title').append(``)
-
- return $
-}
-```
-
-Please note that postRender should return a valid jQuery/Cheerio object
-
-## Define your public attributes
-
-``` javascript
- /*
- * Build your styling here
- */
- getStyles() {
- const { mjAttribute, color } = this.props
-
- return _.merge({}, baseStyles, {
- text: {
- /*
- * Get the color attribute
- * Example: content
- */
- color: mjAttribute('color')
- }
- })
- }
-```
-
-The getStyles method allows you to expose public attributes to the end user with `mjAttribute`. If the user does not provide any value, it will keep the default one.
-
-## Render your component
-
-``` javascript
-
- render() {
-
- const css = this.getStyles(),
- content = 'Hello World!'
-
- return (
-
- { content }
-
- )
- }
-}
-
-```
-
-To render your component, you need to load your style.
-
-Finally, use the JSX syntax to define your component. Find out more about JSX [here](https://facebook.github.io/react/docs/jsx-in-depth.html).
-
-# Import your component
-
-## .mjmlconfig inside the folder
-
-You can add a simple .mjmlconfig file with path to your class file simple as :
-
-``` javascript
-{
- "packages": [
- "./Title.js",
- "mjml-github-component"
- ]
-}
-```
-Note that if you install a MJML componenet from npm, you can declare them in the .mjmlconfig file
-
-The file should be at the root of where you launch the command in order to be use
-
-## Manually with a Javascript file
-
-``` javascript
-import { mjml2html, registerMJElement } from 'mjml'
-import Title from './Title'
-
-registerMJElement(Title)
-
-console.log(mjml2html(`
-
-
- Hello world!
-
-
-```
-
-Then launch it with node script.js and the result will be shown in the console
+To learn how to create your own component, follow this [step-by-step guide](https://medium.com/mjml-making-responsive-email-easy/tutorial-creating-your-own-mjml-component-d3a236ab7093#.pz0ebb537) which also includes a ready-to-use boilerplate.
diff --git a/install.sh b/install.sh
index 1314e28c9..bb9e5f103 100755
--- a/install.sh
+++ b/install.sh
@@ -23,23 +23,30 @@ BPurple='\033[1;35m' # Purple
BCyan='\033[1;36m' # Cyan
BWhite='\033[1;37m' # White
-printf "${Yellow}Installing npm depencies for mono repo ${Color_Off} \n"
+printf "${BYellow}Installing npm depencies for mono repo ${Color_Off} \n"
npm install
printf "${BGreen}Done.${Color_Off} \n"
cd packages
-printf "${Yellow}Linking dependencies for every mjml packages.${Color_Off} \n"
+printf "${BYellow}Linking dependencies for every mjml packages.${Color_Off} \n"
+# Core dependencies
+printf "${Yellow}Linking core dependencies${Color_Off} \n"
+cd mjml-validator && npm link && npm link mjml-core && cd ..
+# Core
+printf "${Yellow}Linking core${Color_Off} \n"
+cd mjml-core && npm link && npm link mjml-validator && cd ..
+# Mj elements
+printf "${Yellow}Linking MJML standard elements${Color_Off} \n"
cd mjml-button && npm link && npm link mjml-core && cd ..
-cd mjml-cli && npm link && npm link mjml-core && cd ..
cd mjml-column && npm link && npm link mjml-core && cd ..
cd mjml-container && npm link && npm link mjml-core && cd ..
-cd mjml-core && npm link && npm link mjml-core && cd ..
cd mjml-divider && npm link && npm link mjml-core && cd ..
cd mjml-group && npm link && npm link mjml-core && cd ..
cd mjml-head-attributes && npm link && npm link mjml-core && cd ..
cd mjml-head-font && npm link && npm link mjml-core && cd ..
+cd mjml-head-style && npm link && cd ..
cd mjml-head-title && npm link && npm link mjml-core && cd ..
cd mjml-hero && npm link && npm link mjml-core && cd ..
cd mjml-html && npm link && npm link mjml-core && cd ..
@@ -54,10 +61,13 @@ cd mjml-social && npm link && npm link mjml-core && cd ..
cd mjml-spacer && npm link && npm link mjml-core && cd ..
cd mjml-table && npm link && npm link mjml-core && cd ..
cd mjml-text && npm link && npm link mjml-core && cd ..
+# Cli
+printf "${Yellow}Linking core${Color_Off} \n"
+cd mjml-cli && npm link && npm link mjml-core && cd ..
printf "${BGreen}Done.${Color_Off} \n"
-printf "${Yellow}Linking dependencies for MJML package.${Color_Off} \n"
+printf "${BYellow}Linking dependencies for MJML package.${Color_Off} \n"
cd mjml
npm link mjml-button
@@ -69,6 +79,7 @@ npm link mjml-divider
npm link mjml-group
npm link mjml-head-attributes
npm link mjml-head-font
+npm link mjml-head-style
npm link mjml-head-title
npm link mjml-hero
npm link mjml-html
@@ -83,11 +94,14 @@ npm link mjml-social
npm link mjml-spacer
npm link mjml-table
npm link mjml-text
+npm link mjml-validator
printf "${BGreen}Done.${Color_Off} \n"
-printf "${Yellow}Installing npm depencies for each MJML packages ${Color_Off} \n"
+printf "${BYellow}Installing npm depencies for each MJML packages ${Color_Off} \n"
gulp install
cd ../..
-printf "${BGreen}Done.${Color_Off} Happy coding ! 🍺 \n"
+printf "${BGreen}Done.${Color_Off} ${Green}Building the project ${Color_Off} \n"
+gulp build
+printf "${BGreen}Done.🍺 ${Color_Off} \n ${Green}Use ${Color_Off}${BGreen}gulp build${Color_Off}${Green} to build the whole monorepo ! \n And run ${Color_Off}${BGreen}node test.js${Color_Off}${Green} inside ${Color_Off}${BGreen}packages/mjml${Color_Off}${Green} to test your installation ${Color_Off}\n"
diff --git a/package.json b/package.json
index 8f230f884..9c20d4742 100644
--- a/package.json
+++ b/package.json
@@ -15,24 +15,24 @@
},
"devDependencies": {
"babel": "^6.5.2",
- "babel-core": "^6.13.2",
- "babel-eslint": "^6.1.2",
+ "babel-core": "^6.14.0",
+ "babel-eslint": "^7.0.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
- "babel-preset-es2015": "^6.13.2",
+ "babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
- "babel-register": "^6.11.6",
- "eslint": "^3.2.2",
- "eslint-config-airbnb": "^10.0.0",
- "eslint-plugin-import": "^1.13.0",
- "eslint-plugin-jsx-a11y": "^2.1.0",
- "eslint-plugin-react": "^6.0.0",
+ "babel-register": "^6.14.0",
+ "eslint": "^3.6.1",
+ "eslint-config-airbnb": "^12.0.0",
+ "eslint-plugin-import": "^1.16.0",
+ "eslint-plugin-jsx-a11y": "^2.2.2",
+ "eslint-plugin-react": "^6.3.0",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-newer": "^1.2.0",
"pre-commit": "^1.1.3",
- "shelljs": "^0.7.3",
+ "shelljs": "^0.7.4",
"through2": "^2.0.1",
- "yargs": "^4.8.1"
+ "yargs": "^5.0.0"
}
}
diff --git a/packages/mjml-button/README.md b/packages/mjml-button/README.md
index 24c2cb4a8..7af0a9ad2 100644
--- a/packages/mjml-button/README.md
+++ b/packages/mjml-button/README.md
@@ -32,13 +32,17 @@ attribute | unit | description
----------------------------|-------------|--------------------------------------------------|---------------------
background-color | color | button background-color | #414141
container-background-color | color | button container background color | n/a
+border | string | css border format | none
+border-bottom | string | css border format | n/a
+border-left | string | css border format | n/a
+border-right | string | css border format | n/a
+border-top | string | css border format | n/a
border-radius | px | border radius | 3px
font-style | string | normal/italic/oblique | n/a
font-size | px | text size | 13px
font-weight | number | text thickness | bold
font-family | string | font name | Ubuntu, Helvetica, Arial, sans-serif
color | color | text color | #ffffff
-border | string | css border format | none
text-decoration | string | underline/overline/none | none
text-transform | string | capitalize/uppercase/lowercase | none
align | string | horizontal alignment | center
@@ -50,3 +54,5 @@ padding-top | px | top offset
padding-bottom | px | bottom offset | n/a
padding-left | px | left offset | n/a
padding-right | px | right offset | n/a
+width | px | button width | n/a
+height | px | button height | n/a
diff --git a/packages/mjml-button/package.json b/packages/mjml-button/package.json
index 05b827725..253eb77d1 100644
--- a/packages/mjml-button/package.json
+++ b/packages/mjml-button/package.json
@@ -1,7 +1,7 @@
{
"name": "mjml-button",
"description": "mjml-button",
- "version": "2.0.10",
+ "version": "3.0.0-beta.3",
"main": "lib/index.js",
"repository": {
"type": "git",
@@ -13,8 +13,8 @@
},
"homepage": "https://mjml.io",
"dependencies": {
- "lodash": "^4.14.2",
- "mjml-core": "^2.3.2",
- "react": "^15.3.0"
+ "lodash": "^4.16.2",
+ "mjml-core": "~3.0.0-beta.3",
+ "react": "^15.3.2"
}
}
diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js
index 43a2e9c4e..fc64481d4 100644
--- a/packages/mjml-button/src/index.js
+++ b/packages/mjml-button/src/index.js
@@ -3,24 +3,39 @@ import merge from 'lodash/merge'
import React, { Component } from 'react'
const tagName = 'mj-button'
+const parentTag = ['mj-column', 'mj-hero-content']
+const endingTag = true
const defaultMJMLDefinition = {
content: '',
attributes: {
- 'align': 'center',
- 'background-color': '#414141',
- 'border-radius': '3px',
- 'color': '#ffffff',
- 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',
- 'font-size': '13px',
- 'font-weight': 'normal',
- 'href': '',
+ "background-color": "#414141",
+ "border": "none",
+ "border-bottom": null,
+ "border-left": null,
+ "border-radius": "3px",
+ "border-right": null,
+ "border-top": null,
+ "container-background-color": null,
+ "font-style": null,
+ "font-size": "13px",
+ "font-weight": "normal",
+ "font-family": "Ubuntu, Helvetica, Arial, sans-serif",
+ "color": "#ffffff",
+ "text-decoration": "none",
+ "text-transform": "none",
+ "align": "center",
+ "vertical-align": "middle",
+ "href": null,
+ "inner-padding": "10px 25px",
'padding': '10px 25px',
- 'inner-padding': '10px 25px',
- 'text-decoration': 'none',
- 'vertical-align': 'middle'
+ "padding-top": null,
+ "padding-bottom": null,
+ "padding-left": null,
+ "padding-right": null,
+ "width": null,
+ "height": null
}
}
-const endingTag = true
const baseStyles = {
table: {
borderCollapse: 'separate'
@@ -40,22 +55,29 @@ class Button extends Component {
const { mjAttribute, defaultUnit } = this.props
return merge({}, baseStyles, {
+ table: {
+ width: mjAttribute('width')
+ },
td: {
border: mjAttribute('border'),
+ borderBottom: mjAttribute('border-bottom'),
+ borderLeft: mjAttribute('border-left'),
borderRadius: defaultUnit(mjAttribute('border-radius'), "px"),
+ borderRight: mjAttribute('border-right'),
+ borderTop: mjAttribute('border-top'),
color: mjAttribute('color'),
cursor: 'auto',
fontStyle: mjAttribute('font-style'),
+ height: mjAttribute('height'),
padding: defaultUnit(mjAttribute('inner-padding'), "px")
},
a: {
background: mjAttribute('background-color'),
color: mjAttribute('color'),
fontFamily: mjAttribute('font-family'),
- fontSize: defaultUnit(mjAttribute('font-size'), "px"),
+ fontSize: defaultUnit(mjAttribute('font-size')),
fontStyle: mjAttribute('font-style'),
fontWeight: mjAttribute('font-weight'),
- lineHeight: mjAttribute('height'),
textDecoration: mjAttribute('text-decoration'),
textTransform: mjAttribute('text-transform'),
margin: "0px"
@@ -112,8 +134,9 @@ class Button extends Component {
}
Button.tagName = tagName
-Button.defaultMJMLDefinition = defaultMJMLDefinition
+Button.parentTag = parentTag
Button.endingTag = endingTag
+Button.defaultMJMLDefinition = defaultMJMLDefinition
Button.baseStyles = baseStyles
export default Button
diff --git a/packages/mjml-cli/bin/mjml b/packages/mjml-cli/bin/mjml
index 01d0c8d5a..74966645a 100755
--- a/packages/mjml-cli/bin/mjml
+++ b/packages/mjml-cli/bin/mjml
@@ -15,21 +15,25 @@ binary
stdout: process.argv.indexOf('-s') !== -1 || process.argv.indexOf('--stdout') !== -1,
output: binary.output
}
-
+
cli.renderFile(files, options)
})
+ .option('-l, --level [level]', 'Specifies the level of validation of MJML parser (skip/soft/strict)', /^(skip|soft|strict)$/i, 'soft')
.option('-r, --render ', 'Compiles an MJML file')
.option('-i, --stdin', 'Compiles an MJML file from input stream')
.option('-w, --watch ', 'Watch and render an MJML file')
.option('-o, --output ', 'Redirect the HTML to a file')
.option('-s, --stdout', 'Redirect the HTML to stdout')
.option('-m, --min', 'Minify the final output file', 'false')
- .option('-e, --ending', 'Specifies that the newly created component is an ending tag')
- .option('-c, --column', 'Specifies that the newly created component is an column element')
- .option('--init-component ', 'Initialize an MJML component')
+ .option('-f, --format ', 'Output format for MJML validation', /^(json|text)$/i, 'text')
+ .option('--validate ', 'Validate a MJML Document')
.parse(process.argv)
switch (true) {
+ case (!!binary.validate):
+ cli.validate(binary.validate, binary)
+ break
+
case (!!binary.watch):
cli.watch(binary.watch, binary)
break
@@ -41,8 +45,4 @@ switch (true) {
case (!!binary.stdin):
cli.renderStream(binary)
break
-
- case (!!binary.initComponent):
- cli.initComponent(binary.initComponent, binary.ending, binary.column)
- break
}
diff --git a/packages/mjml-cli/package.json b/packages/mjml-cli/package.json
index d47663c83..5526c39e2 100644
--- a/packages/mjml-cli/package.json
+++ b/packages/mjml-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "mjml-cli",
"description": "MJML: the only framework that makes responsive-email easy",
- "version": "2.3.2",
+ "version": "3.0.0-beta.3",
"main": "bin/mjml",
"bin": {
"mjml-cli": "bin/mjml"
@@ -18,12 +18,12 @@
"dependencies": {
"commander": "^2.9.0",
"fs-promise": "^0.5.0",
- "glob": "^7.0.3",
- "lodash": "^4.14.2",
- "mjml-core": "^2.3.2"
+ "glob": "^7.1.0",
+ "lodash": "^4.16.2",
+ "mjml-core": "~3.0.0-beta.3"
},
"devDependencies": {
"chai": "^3.5.0",
- "mocha": "^2.4.5"
+ "mocha": "^3.1.0"
}
}
diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js
index ad7048534..60a281074 100644
--- a/packages/mjml-cli/src/client.js
+++ b/packages/mjml-cli/src/client.js
@@ -1,10 +1,7 @@
-import { MJMLRenderer, version } from 'mjml-core'
+import { MJMLRenderer, version, documentParser, MJMLValidator } from 'mjml-core'
import fs from 'fs'
import glob from 'glob'
import path from 'path'
-import camelCase from 'lodash/camelCase'
-import upperFirst from 'lodash/upperFirst'
-import createComponent from './createComponent'
/*
* The version number is the NPM
@@ -26,6 +23,15 @@ const promisify = fn =>
*/
const error = e => console.log(e.stack || e) // eslint-disable-line no-console
+const isDirectory = (file) => {
+ try {
+ const outputPath = path.resolve(process.cwd(), file)
+ return fs.statSync(outputPath).isDirectory()
+ } catch (e) {
+ return false
+ }
+}
+
/*
* Stdin to string buffer
*/
@@ -47,18 +53,31 @@ const stdinToBuffer = (stream, callback) => {
* read: read a fileexists: ensure the file exists
*/
const write = promisify(fs.writeFile)
-const mkdir = promisify(fs.mkdir)
const read = promisify(fs.readFile)
const readStdin = promisify(stdinToBuffer)
/*
* Render an input promise
*/
-const render = (bufferPromise, { min, output, stdout }) => {
+const render = (bufferPromise, { min, output, stdout, fileName, level }) => {
bufferPromise
- .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min }).render())
- .then(result => stdout ? process.stdout.write(result) : write(output, result))
- .catch(error)
+ .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min, level }).render())
+ .then(result => {
+ const { html: content, errors } = result
+
+ if (errors) {
+ error(errors.map(err => err.formattedMessage).join('\n'))
+ }
+
+ stdout ? process.stdout.write(content) : write(output, content)
+ })
+ .catch(e => {
+ if (e.getMessages) {
+ return error(`${fileName ? `File: ${fileName} \n` : ``}${e.getMessages()}`)
+ }
+
+ return error(e)
+ })
}
/*
@@ -66,28 +85,32 @@ const render = (bufferPromise, { min, output, stdout }) => {
* min: boolean that specify the output format (pretty/minified)
*/
export const renderFile = (input, options) => {
+ const outputIsDirectory = !!options.output && isDirectory(options.output)
+
const renderFiles = files => {
files.forEach((file, index) => {
const inFile = path.basename(file, '.mjml')
let output
if (options.output) {
- const extension = path.extname(options.output) || '.html'
- const outFile = path.join(path.dirname(options.output), path.basename(options.output, extension))
-
- if (files.length > 1) {
- output = `${outFile}-${index + 1}${extension}`
+ const outputExtension = path.extname(options.output) || '.html'
+ const outFile = path.join(path.dirname(options.output), path.basename(options.output, outputExtension))
+ const multipleFiles = files.length > 1
+
+ if (multipleFiles && outputIsDirectory) {
+ output = `${options.output}/${inFile}${outputExtension}`
+ } else if (multipleFiles) {
+ output = `${outFile}-${index + 1}${outputExtension}`
} else {
- output = `${outFile}${extension}`
+ output = `${outFile}${outputExtension}`
}
} else {
- const extension = path.extname(inFile) || '.html'
- output = `${inFile}${extension}`
+ output = `${inFile}${path.extname(inFile) || ".html"}`
}
const filePath = path.resolve(process.cwd(), file)
- render(read(filePath), { min: options.min, stdout: options.stdout, output })
+ render(read(filePath), { min: options.min, stdout: options.stdout, output, fileName: file, level: options.level })
})
}
@@ -103,21 +126,37 @@ export const renderFile = (input, options) => {
*/
export const renderStream = options => render(readStdin(process.stdin), options)
+const availableOutputFormat = {
+ json: JSON.stringify,
+ text: (errs) => errs.map(e => e.formattedMessage).join('\n')
+}
+
+/**
+ * Validate an MJML document
+ */
+export const validate = (input, { format }) => {
+ read(input)
+ .then(content => {
+ const MJMLDocument = documentParser(content.toString())
+ const report = MJMLValidator(MJMLDocument)
+
+ const outputFormat = availableOutputFormat[format] || availableOutputFormat['text']
+
+ process.stdout.write(outputFormat(report))
+ })
+ .catch(e => {
+ return error(`Error: ${e}`)
+ })
+}
+
/*
* Watch changes on a specific input file by calling render on each change
*/
export const watch = (input, options) => {
renderFile(input, options)
- const now = new Date();
- fs.watchFile(input, () => console.log(`[${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}] Reloading MJML`) || renderFile(input, options)) // eslint-disable-line no-console
-}
+ fs.watchFile(input, () => {
+ const now = new Date()
-/*
- * Create a new component based on the default template
- */
-export const initComponent = (name, ending) => {
- mkdir(`./${name}`)
- .then(() => mkdir(`./${name}/src`))
- .then(() => write(`./${name}/src/index.js`, createComponent(upperFirst(camelCase(name)), ending)))
- .then(() => console.log(`Component created: ${name}`)) // eslint-disable-line no-console
+ console.log(`[${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}] Reloading MJML`) || renderFile(input, options) // eslint-disable-line no-console
+ })
}
diff --git a/packages/mjml-cli/src/createComponent.js b/packages/mjml-cli/src/createComponent.js
deleted file mode 100644
index d68c5e9e3..000000000
--- a/packages/mjml-cli/src/createComponent.js
+++ /dev/null
@@ -1,73 +0,0 @@
-export default (name, endingTag = 'false') => {
- const lowerName = name.toLowerCase()
-
- return `import { MJMLElement } from 'mjml-core'
-import merge from 'lodash/merge'
-import MJMLText from 'mjml-text'
-import React, { Component } from 'react'
-
-const tagName = '${lowerName}'
-const endingTag = ${endingTag}
-
-/*
- * Add your default mjml-attributes here
- */
-const defaultMJMLDefinition = {
- attributes: {
- 'color': '#424242',
- 'font-family': 'Helvetica',
- 'margin-top': '10px'
- }
-}
-
-/*
- * Add you default style here
- */
-const baseStyles = {
- div: {
- cursor: 'auto'
- }
-}
-
-@MJMLElement
-class ${name} extends Component {
-
- /*
- * Build your styling here
- */
- getStyles () {
- const { mjAttribute, color } = this.props
-
- return merge({}, this.constructor.baseStyles, {
- text: {
- /*
- * Get the color attribute
- * Example: content
- */
- color: mjAttribute('color')
- }
- })
- }
-
- render () {
- const css = this.getStyles()
- const content = 'Hello World!'
-
- return (
-
- {content}
-
- )
- }
-
-}
-
-${name}.tagName = tagName
-${name}.defaultMJMLDefinition = defaultMJMLDefinition
-${name}.endingTag = endingTag
-${name}.baseStyles = baseStyles
-
-export default ${name}
-
-`
-}
diff --git a/packages/mjml-column/README.md b/packages/mjml-column/README.md
index ea3d44640..abdcc9cea 100644
--- a/packages/mjml-column/README.md
+++ b/packages/mjml-column/README.md
@@ -34,6 +34,12 @@ Every single column has to contain something because they are responsive contain
attribute | unit | description | default attributes
--------------------|-------------|--------------------------------|--------------------------------------
+background-color | string | background color for a column | n/a
+border | string | css border format | none
+border-bottom | string | css border format | n/a
+border-left | string | css border format | n/a
+border-right | string | css border format | n/a
+border-top | string | css border format | n/a
+border-radius | px | border radius | n/a
width | percent/px | column width | (100 / number of columns in section)%
vertical-align | string | middle/top/bottom | top
-background-color | string | background color for a column | n/a
diff --git a/packages/mjml-column/package.json b/packages/mjml-column/package.json
index 37fcac323..cec45e669 100644
--- a/packages/mjml-column/package.json
+++ b/packages/mjml-column/package.json
@@ -1,7 +1,7 @@
{
"name": "mjml-column",
"description": "mjml-column",
- "version": "2.0.11",
+ "version": "3.0.0-beta.3",
"main": "lib/index.js",
"repository": {
"type": "git",
@@ -13,8 +13,9 @@
},
"homepage": "https://mjml.io",
"dependencies": {
- "lodash": "^4.14.2",
- "mjml-core": "^2.3.2",
- "react": "^15.3.0"
+ "classnames": "^2.2.5",
+ "lodash": "^4.16.2",
+ "mjml-core": "~3.0.0-beta.3",
+ "react": "^15.3.2"
}
}
diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js
index 011d32959..3884fdd79 100644
--- a/packages/mjml-column/src/index.js
+++ b/packages/mjml-column/src/index.js
@@ -1,10 +1,26 @@
import { MJMLElement, helpers } from 'mjml-core'
+import cx from 'classnames'
import each from 'lodash/each'
import merge from 'lodash/merge'
import React, { Component } from 'react'
import uniq from 'lodash/uniq'
const tagName = 'mj-column'
+const parentTag = ['mj-section', 'mj-group', 'mj-navbar']
+const defaultMJMLDefinition = {
+ attributes: {
+ 'background': null,
+ 'background-color': null,
+ "border": null,
+ "border-bottom": null,
+ "border-left": null,
+ "border-radius": null,
+ "border-right": null,
+ "border-top": null,
+ 'vertical-align': null,
+ 'width': null
+ }
+}
const baseStyles = {
div: {
verticalAlign: 'top'
@@ -48,19 +64,26 @@ class Column extends Component {
styles = this.getStyles()
getStyles () {
- const { mjAttribute } = this.props
+ const { mjAttribute, defaultUnit } = this.props
return merge({}, baseStyles, {
div: {
display: 'inline-block',
- verticalAlign: mjAttribute('vertical-align'),
+ direction: 'ltr',
fontSize: '13px',
textAlign: 'left',
+ verticalAlign: mjAttribute('vertical-align'),
width: this.getMobileWidth()
},
table: {
- verticalAlign: mjAttribute('vertical-align'),
- background: mjAttribute('background-color')
+ background: mjAttribute('background-color'),
+ border: mjAttribute('border'),
+ borderBottom: mjAttribute('border-bottom'),
+ borderLeft: mjAttribute('border-left'),
+ borderRadius: defaultUnit(mjAttribute('border-radius'), "px"),
+ borderRight: mjAttribute('border-right'),
+ borderTop: mjAttribute('border-top'),
+ verticalAlign: mjAttribute('vertical-align')
}
})
}
@@ -110,11 +133,12 @@ class Column extends Component {
const { mjAttribute, children, sibling } = this.props
const width = mjAttribute('width') || `${100 / sibling}%`
const mjColumnClass = this.getColumnClass()
+ const divClasses = cx(mjColumnClass, 'outlook-group-fix')
return (