From d9c806dad74a8be6f0d85c3274b2c657a9b75d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Mon, 9 May 2016 17:52:16 +0200 Subject: [PATCH 01/87] Start work on xsd for xml validation --- packages/mjml-column/src/index.js | 10 ++++++++++ packages/mjml-container/src/index.js | 8 ++++++++ packages/mjml-core/src/MJMLElementsCollection.js | 4 +++- packages/mjml-core/src/MJMLRenderer.js | 15 ++++++++++----- packages/mjml-core/src/configs/defaultXsd.js | 12 ++++++++++++ packages/mjml-section/src/index.js | 8 ++++++++ 6 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 packages/mjml-core/src/configs/defaultXsd.js diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index e262a20b4..3ec68b4b8 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -40,6 +40,15 @@ const postRender = $ => { return $ } +const schemaXsd = elements => { + const columnElements = Object.keys(elements).map(element => elements[element].columnElement ? elements[element].tagName : null).filter(Boolean) + + return ` + + ${(columnElements.map(element => ``).join(''))} + + ` +} @MJMLElement class Column extends Component { @@ -116,5 +125,6 @@ class Column extends Component { Column.tagName = tagName Column.baseStyles = baseStyles Column.postRender = postRender +Column.schemaXsd = schemaXsd export default Column diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index 8f06bc6f5..acf1ead51 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -54,6 +54,13 @@ const postRender = $ => { return $ } +const schemaXsd = () => ( + ` + + + + ` +) @MJMLElement class Container extends Component { @@ -91,6 +98,7 @@ class Container extends Component { Container.tagName = tagName Container.defaultMJMLDefinition = defaultMJMLDefinition Container.postRender = postRender +Container.schemaXsd = schemaXsd // Support V1.X MJML mj-body elements["mj-body"] = Container diff --git a/packages/mjml-core/src/MJMLElementsCollection.js b/packages/mjml-core/src/MJMLElementsCollection.js index 057e11f88..e53303c61 100644 --- a/packages/mjml-core/src/MJMLElementsCollection.js +++ b/packages/mjml-core/src/MJMLElementsCollection.js @@ -2,9 +2,10 @@ import warning from 'warning' export const endingTags = [] export const postRenders = [] +export const schemaXsds = [] export const registerMJElement = Component => { - const { endingTag, postRender, tagName } = Component + const { endingTag, postRender, tagName, schemaXsd } = Component if (!tagName) { return warning(false, 'Component has no TagName') @@ -12,6 +13,7 @@ export const registerMJElement = Component => { endingTag && endingTags.push(tagName) postRender && postRenders.push(postRender) + schemaXsd && schemaXsds.push(schemaXsd) MJMLElementsCollection[tagName] = Component } diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index f6788ac01..e457b1ecf 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -1,14 +1,15 @@ +import _ from 'lodash' import { EmptyMJMLError } from './Error' -import { html as beautify } from 'js-beautify' import { fixLegacyAttrs, removeCDATA } from './helpers/postRender' +import { html as beautify } from 'js-beautify' import { parseInstance } from './helpers/mjml' import defaultContainer from './configs/defaultContainer' +import defaultXsd from './configs/defaultXsd' import documentParser from './parsers/document' import dom from './helpers/dom' import fs from 'fs' -import _ from 'lodash' import getFontsImports from './helpers/getFontsImports' -import MJMLElementsCollection, { postRenders, registerMJElement } from './MJMLElementsCollection' +import MJMLElementsCollection, { schemaXsds, postRenders, registerMJElement } from './MJMLElementsCollection' import React from 'react' import ReactDOMServer from 'react-dom/server' import warning from 'warning' @@ -54,7 +55,9 @@ export default class MJMLRenderer { parseDocument () { debug('Start parsing document') this.content = documentParser(this.content) + this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElementsCollection))) debug('Content parsed') + console.log('this.schemaXsd', this.schemaXsd) } render () { @@ -75,10 +78,12 @@ export default class MJMLRenderer { fonts: getFontsImports({ content: renderedMJML }) }) - return this._postRender(MJMLDocument) + return this.postRender(MJMLDocument) } - _postRender (MJMLDocument) { + + + postRender (MJMLDocument) { let $ = dom.parseHTML(MJMLDocument) $ = fixLegacyAttrs($) diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js new file mode 100644 index 000000000..fedd1fc7e --- /dev/null +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -0,0 +1,12 @@ +export default (schemas = '') => { + return (` + + + + + + + ${schemas} + +`) +} diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 651a07d78..041cd7514 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -78,6 +78,13 @@ const postRender = $ => { return $ } +const schemaXsd = () => ( + ` + + + + ` +) @MJMLElement class Section extends Component { @@ -179,5 +186,6 @@ Section.tagName = tagName Section.defaultMJMLDefinition = defaultMJMLDefinition Section.baseStyles = baseStyles Section.postRender = postRender +Section.schemaXsd = schemaXsd export default Section From 493dc16430477068e92cbae990fef1abb91517b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Wed, 11 May 2016 13:15:23 +0200 Subject: [PATCH 02/87] Add helper for detect node<>browser --- packages/mjml/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 925d17447..64a7ca17e 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,6 +1,6 @@ var mjml = require('./lib/index') -console.log(mjml.mjml2html(` +mjml.mjml2html(` @@ -53,4 +53,4 @@ console.log(mjml.mjml2html(` -`, { beautify: true })) +`, { beautify: true }) From 6558eaafed064c5b4b54790a8e52b6ae1425c767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Wed, 11 May 2016 13:16:40 +0200 Subject: [PATCH 03/87] Test xsd --- packages/mjml-cli/src/client.js | 4 +- packages/mjml-column/src/index.js | 6 +- packages/mjml-core/package.json | 4 ++ packages/mjml-core/src/MJMLRenderer.js | 60 ++++++++++---------- packages/mjml-core/src/configs/defaultXsd.js | 24 ++++---- packages/mjml-core/src/helpers/dom.js | 5 +- packages/mjml-core/src/helpers/isBrowser.js | 1 + packages/mjml-core/src/index.js | 1 - packages/mjml-table/src/index.js | 4 +- 9 files changed, 58 insertions(+), 51 deletions(-) create mode 100644 packages/mjml-core/src/helpers/isBrowser.js diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index 3141eed52..a7045d031 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -1,4 +1,4 @@ -import { MJMLRenderer, version } from 'mjml-core' +import { MJMLRenderer } from 'mjml-core' import camelCase from 'lodash/camelCase' import createComponent from './createComponent' import fs from 'fs' @@ -10,7 +10,7 @@ import upperFirst from 'lodash/upperFirst' * The version number is the NPM * version number. It should be the same as the MJML engine */ -export { version } +export const version = () => require('../package.json').version /* * Turns a callback style to a Promise style one diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 3ec68b4b8..402c2531f 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -21,7 +21,7 @@ const postRender = $ => { $(this).removeAttr('data-column-width') }) - uniq(columnWidths).forEach((width) => { + uniq(columnWidths).forEach(width => { const mediaQueryClass = `${className}-${width}` mediaQueries.push(`.${mediaQueryClass}, * [aria-labelledby="${mediaQueryClass}"] { width:${width}${unit}!important; }`) @@ -43,9 +43,9 @@ const postRender = $ => { const schemaXsd = elements => { const columnElements = Object.keys(elements).map(element => elements[element].columnElement ? elements[element].tagName : null).filter(Boolean) - return ` + return ` - ${(columnElements.map(element => ``).join(''))} + ${(columnElements.map(element => ``).join(`\n`))} ` } diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index c96b8cfc3..1fef902ac 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -27,6 +27,10 @@ "react": "^15.0.1", "warning": "^2.1.0" }, + "browser": { + "cheerio": false, + "html-minifier": false + }, "devDependencies": { "chai": "^3.5.0", "mocha": "^2.4.5" diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index e457b1ecf..8eccbd1ab 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -7,9 +7,9 @@ import defaultContainer from './configs/defaultContainer' import defaultXsd from './configs/defaultXsd' import documentParser from './parsers/document' import dom from './helpers/dom' -import fs from 'fs' +import elements, { schemaXsds, postRenders, registerMJElement } from './MJMLElementsCollection' import getFontsImports from './helpers/getFontsImports' -import MJMLElementsCollection, { schemaXsds, postRenders, registerMJElement } from './MJMLElementsCollection' +import isBrowser from './helpers/isBrowser' import React from 'react' import ReactDOMServer from 'react-dom/server' import warning from 'warning' @@ -19,7 +19,9 @@ const debug = require('debug')('mjml-engine/mjml2html') export default class MJMLRenderer { constructor (content, options = {}) { - this.registerDotfile() + if (!isBrowser) { + this.registerDotfile() + } this.content = content this.options = options @@ -30,6 +32,8 @@ export default class MJMLRenderer { } registerDotfile () { + const fs = require('fs') + try { const path = process.cwd() const mjmlConfig = JSON.parse(fs.readFileSync(`${path}/.mjmlconfig`).toString()) @@ -48,41 +52,18 @@ export default class MJMLRenderer { } }) } catch (e) { - warning(!_.isEmpty(MJMLElementsCollection), 'No .mjmlconfig found in path, please consider to add one') + warning(!_.isEmpty(elements), 'No .mjmlconfig found in path, please consider to add one') } } parseDocument () { debug('Start parsing document') this.content = documentParser(this.content) - this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElementsCollection))) + this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(elements)).join(`\n`)) debug('Content parsed') console.log('this.schemaXsd', this.schemaXsd) } - render () { - if (!this.content) { - throw new EmptyMJMLError(`.render: No MJML to render in options ${this.options.toString()}`) - } - - const rootElemComponent = React.createElement(MJMLElementsCollection[this.content.tagName], { mjml: parseInstance(this.content) }) - - debug('Render to static markup') - const renderedMJML = ReactDOMServer.renderToStaticMarkup(rootElemComponent) - - debug('React rendering done. Continue with special overrides.') - - const MJMLDocument = defaultContainer({ - title: this.options.title, - content: renderedMJML, - fonts: getFontsImports({ content: renderedMJML }) - }) - - return this.postRender(MJMLDocument) - } - - - postRender (MJMLDocument) { let $ = dom.parseHTML(MJMLDocument) @@ -105,7 +86,6 @@ export default class MJMLRenderer { } if (this.options.minify) { - const minify = require('html-minifier').minify finalMJMLDocument = minify(finalMJMLDocument, { @@ -118,4 +98,26 @@ export default class MJMLRenderer { return finalMJMLDocument } + render () { + if (!this.content) { + throw new EmptyMJMLError(`.render: No MJML to render in options ${this.options.toString()}`) + } + + const rootElemComponent = React.createElement(elements[this.content.tagName], { mjml: parseInstance(this.content) }) + + debug('Render to static markup') + const renderedMJML = ReactDOMServer.renderToStaticMarkup(rootElemComponent) + + debug('React rendering done. Continue with special overrides.') + + const MJMLDocument = defaultContainer({ + title: this.options.title, + content: renderedMJML, + fonts: getFontsImports({ content: renderedMJML }) + }) + + return this.postRender(MJMLDocument) + } + + } diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index fedd1fc7e..011522266 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -1,12 +1,12 @@ -export default (schemas = '') => { - return (` - - - - - - - ${schemas} - -`) -} +export default (schemas = '') => ( + ` + + + + + + + ${schemas} + + ` +) diff --git a/packages/mjml-core/src/helpers/dom.js b/packages/mjml-core/src/helpers/dom.js index 7acacf909..1415de4be 100644 --- a/packages/mjml-core/src/helpers/dom.js +++ b/packages/mjml-core/src/helpers/dom.js @@ -1,7 +1,8 @@ -const inBrowser = typeof window !== 'undefined' +import isBrowser from './isBrowser' + const dom = {} -if (inBrowser) { +if (isBrowser) { const jquery = require('jquery') const parseMarkup = str => { diff --git a/packages/mjml-core/src/helpers/isBrowser.js b/packages/mjml-core/src/helpers/isBrowser.js new file mode 100644 index 000000000..93c242550 --- /dev/null +++ b/packages/mjml-core/src/helpers/isBrowser.js @@ -0,0 +1 @@ +export default typeof window != 'undefined' && this === window diff --git a/packages/mjml-core/src/index.js b/packages/mjml-core/src/index.js index b2f91a7e6..7f7602cc5 100644 --- a/packages/mjml-core/src/index.js +++ b/packages/mjml-core/src/index.js @@ -7,7 +7,6 @@ import * as helpers from './helpers' export documentParser from './parsers/document' export MJMLElement from './decorators/MJMLElement' export { MJMLRenderer, registerMJElement, elements, helpers } -export const version = () => require('../package.json').version export const mjml2html = (mjml, options = {}) => new MJMLRenderer(mjml, options).render() export const registerElement = Component => { warning(false, 'Please now use registerMJElement, registerElement is deprecated will no longer be supported soon') diff --git a/packages/mjml-table/src/index.js b/packages/mjml-table/src/index.js index 78073557a..052be3d81 100644 --- a/packages/mjml-table/src/index.js +++ b/packages/mjml-table/src/index.js @@ -30,8 +30,8 @@ class Table extends Component { table: { color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), "px"), - lineHeight: defaultUnit(mjAttribute('line-height'), "px"), + fontSize: defaultUnit(mjAttribute('font-size'), 'px'), + lineHeight: defaultUnit(mjAttribute('line-height'), 'px'), tableLayout: mjAttribute('table-layout') } } From 892fa40f0f962aa4dd243abc437ae9750bc09ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Wed, 11 May 2016 13:16:55 +0200 Subject: [PATCH 04/87] Add xsd for mj-container --- packages/mjml-container/src/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index acf1ead51..27657b8ab 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -4,7 +4,7 @@ import React, { Component } from 'react' const tagName = 'mj-container' const defaultMJMLDefinition = { attributes: { - 'width': '600' + 'width': '600px' }, inheritedAttributes: [ 'width' @@ -54,10 +54,10 @@ const postRender = $ => { return $ } -const schemaXsd = () => ( - ` +const schemaXsd = () => ( + ` - + ` ) @@ -73,14 +73,14 @@ class Container extends Component { return { div: { backgroundColor: mjAttribute('background-color'), - fontSize: defaultUnit(mjAttribute('font-size'), "px") + fontSize: defaultUnit(mjAttribute('font-size'), 'px') } } } render () { - const { renderWrappedOutlookChildren, mjAttribute, children } = this.props - const { width } = helpers.widthParser(mjAttribute('width')) + const { renderWrappedOutlookChildren, defaultUnit, mjAttribute, children } = this.props + const { width } = helpers.widthParser(defaultUnit(mjAttribute('width'), 'px')) return (
Date: Thu, 12 May 2016 09:13:09 +0200 Subject: [PATCH 05/87] Add default attributes on all packages --- packages/mjml-button/src/index.js | 33 ++++++--- packages/mjml-column/src/index.js | 10 ++- packages/mjml-container/src/index.js | 20 +++-- .../mjml-core/src/MJMLElementsCollection.js | 9 ++- packages/mjml-core/src/configs/defaultXsd.js | 8 +- .../mjml-core/src/decorators/MJMLElement.js | 73 +++++++++---------- packages/mjml-divider/src/index.js | 34 ++++++--- packages/mjml-html/src/index.js | 27 +++++-- packages/mjml-image/src/index.js | 38 +++++++--- packages/mjml-invoice-item/src/index.js | 10 ++- packages/mjml-invoice/src/index.js | 12 +-- packages/mjml-list/src/index.js | 30 ++++++-- packages/mjml-location/src/index.js | 27 +++++-- packages/mjml-raw/src/index.js | 2 + packages/mjml-section/src/index.js | 30 ++++---- packages/mjml-social/src/index.js | 40 ++++++---- packages/mjml-spacer/src/index.js | 23 +++++- packages/mjml-table/src/index.js | 28 +++++-- packages/mjml-text/src/index.js | 30 ++++++-- packages/mjml/test.js | 8 +- 20 files changed, 328 insertions(+), 164 deletions(-) diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index 11ed852a3..32095ef98 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -3,6 +3,8 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-button' +const parentTag = 'mj-column' +const endingTag = true const defaultMJMLDefinition = { content: '', attributes: { @@ -11,23 +13,35 @@ const defaultMJMLDefinition = { 'border-radius': '3px', 'border': 'none', 'color': '#ffffff', + 'container-background-color': null, 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', 'font-size': '13px', 'font-weight': 'normal', 'href': '', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, 'padding': '10px 25px', 'text-decoration': 'none', 'vertical-align': 'middle' } } -const endingTag = true -const columnElement = true const baseStyles = { a: { display: 'inline-block', textDecoration: 'none' } } +const schemaXsd = () => ( + ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + ` +) @MJMLElement class Button extends Component { @@ -40,25 +54,25 @@ class Button extends Component { return merge({}, baseStyles, { td: { background: mjAttribute('background-color'), - borderRadius: defaultUnit(mjAttribute('border-radius'), "px"), + borderRadius: defaultUnit(mjAttribute('border-radius')), color: mjAttribute('color'), cursor: 'auto', fontStyle: mjAttribute('font-style') }, table: { border: mjAttribute('border'), - borderRadius: defaultUnit(mjAttribute('border-radius'), "px") + borderRadius: defaultUnit(mjAttribute('border-radius')) }, a: { background: mjAttribute('background-color'), border: `1px solid ${mjAttribute('background-color')}`, - borderRadius: defaultUnit(mjAttribute('border-radius'), "px"), + borderRadius: defaultUnit(mjAttribute('border-radius')), 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'), - padding: defaultUnit(mjAttribute('padding'), "px"), + padding: defaultUnit(mjAttribute('padding')), textDecoration: mjAttribute('text-decoration') } }) @@ -104,9 +118,10 @@ class Button extends Component { } Button.tagName = tagName -Button.defaultMJMLDefinition = defaultMJMLDefinition +Button.parentTag = parentTag Button.endingTag = endingTag -Button.columnElement = columnElement +Button.defaultMJMLDefinition = defaultMJMLDefinition Button.baseStyles = baseStyles +Button.schemaXsd = schemaXsd export default Button diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 402c2531f..7455b63ea 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -5,6 +5,7 @@ import React, { Component } from 'react' import uniq from 'lodash/uniq' const tagName = 'mj-column' +const parentTag = 'mj-section' const baseStyles = { div: { verticalAlign: 'top' @@ -41,7 +42,7 @@ const postRender = $ => { return $ } const schemaXsd = elements => { - const columnElements = Object.keys(elements).map(element => elements[element].columnElement ? elements[element].tagName : null).filter(Boolean) + const columnElements = Object.keys(elements).map(element => elements[element].parentTag === tagName ? elements[element].tagName : null).filter(Boolean) return ` @@ -61,14 +62,14 @@ class Column extends Component { return merge({}, baseStyles, { div: { display: 'inline-block', - verticalAlign: mjAttribute('vertical-align'), fontSize: '13px', textAlign: 'left', + verticalAlign: mjAttribute('vertical-align'), width: '100%' }, table: { - verticalAlign: mjAttribute('vertical-align'), - background: mjAttribute('background-color') + background: mjAttribute('background-color'), + verticalAlign: mjAttribute('vertical-align') } }) } @@ -123,6 +124,7 @@ class Column extends Component { } Column.tagName = tagName +Column.parentTag = parentTag Column.baseStyles = baseStyles Column.postRender = postRender Column.schemaXsd = schemaXsd diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index b973ad3c3..922b73eab 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -2,6 +2,7 @@ import { MJMLElement, helpers, elements } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-container' +const parentTag = 'mj-body' const defaultMJMLDefinition = { attributes: { 'width': '600px' @@ -54,13 +55,15 @@ const postRender = $ => { return $ } -const schemaXsd = () => ( - ` - - - +const schemaXsd = elements => { + const containerElements = Object.keys(elements).map(element => elements[element].parentTag === tagName ? elements[element].tagName : null).filter(Boolean) + + return ` + + ${(containerElements.map(element => ``).join(`\n`))} + ` -) +} @MJMLElement class Container extends Component { @@ -73,14 +76,14 @@ class Container extends Component { return { div: { backgroundColor: mjAttribute('background-color'), - fontSize: defaultUnit(mjAttribute('font-size'), 'px') + fontSize: defaultUnit(mjAttribute('font-size')) } } } render () { const { renderWrappedOutlookChildren, defaultUnit, mjAttribute, children } = this.props - const { width } = helpers.widthParser(defaultUnit(mjAttribute('width'), 'px')) + const { width } = helpers.widthParser(defaultUnit(mjAttribute('width'))) return (
{ - const { endingTag, postRender, tagName, schemaXsd } = Component + const { closingTag, endingTag, postRender, tagName, schemaXsd } = Component if (!tagName) { - return warning(false, 'Component has no TagName') + return warning(false, 'Component has no tagName') } - endingTag && endingTags.push(tagName) + closingTag === false && closingTags.push(tagName) + endingTag === true && endingTags.push(tagName) + postRender && postRenders.push(postRender) schemaXsd && schemaXsds.push(schemaXsd) diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index 011522266..30dadfcb2 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -1,11 +1,17 @@ export default (schemas = '') => ( ` - + + + + + + + ${schemas} ` diff --git a/packages/mjml-core/src/decorators/MJMLElement.js b/packages/mjml-core/src/decorators/MJMLElement.js index 3ec5383bc..59b83c6d7 100644 --- a/packages/mjml-core/src/decorators/MJMLElement.js +++ b/packages/mjml-core/src/decorators/MJMLElement.js @@ -1,6 +1,5 @@ import { widthParser, defaultUnit } from '../helpers/mjAttribute' import Immutable from 'immutable' -import merge from 'lodash/merge' import MJMLElementsCollection from '../MJMLElementsCollection' import React, { Component } from 'react' import ReactDOMServer from 'react-dom/server' @@ -36,12 +35,6 @@ let siblingCount = 1 function createComponent (ComposedComponent) { - const baseStyles = { - td: { - wordBreak: 'break-word' - } - } - class MJMLElement extends Component { constructor (props) { @@ -52,24 +45,7 @@ function createComponent (ComposedComponent) { mjAttribute = name => this.mjml.getIn(['attributes', name]) - getStyles () { - return merge({}, baseStyles, { - td: { - background: this.mjAttribute('container-background-color'), - fontSize: '1px', - padding: defaultUnit(this.mjAttribute('padding'), 'px'), - paddingTop: defaultUnit(this.mjAttribute('padding-top'), 'px'), - paddingBottom: defaultUnit(this.mjAttribute('padding-bottom'), 'px'), - paddingRight: defaultUnit(this.mjAttribute('padding-right'), 'px'), - paddingLeft: defaultUnit(this.mjAttribute('padding-left'), 'px'), - textAlign: this.mjAttribute('align') - } - }) - } - - mjName = () => { - return this.constructor.tagName - } + mjName = () => this.constructor.tagName mjContent = () => { const content = this.mjml.get('content') @@ -134,7 +110,7 @@ function createComponent (ComposedComponent) { childProps.mjml = childProps.mjml.mergeIn(['attributes', this.inheritedAttributes()]) } } else { - Object.assign(childProps, {rawPxWidth: elementsWidth[i]}) + Object.assign(childProps, { rawPxWidth: elementsWidth[i] }) if (this.mjml.get('inheritedAttributes')) { Object.assign(childProps, this.inheritedAttributes()) @@ -144,7 +120,7 @@ function createComponent (ComposedComponent) { const childWithProps = React.cloneElement(child, childProps) wrappedElements.push(childWithProps) - if (childWithProps.type.tagName !== 'mj-raw' && i < realChildren.length - 1) { + if (!childWithProps.type.rawElement && i < realChildren.length - 1) { wrappedElements.push(
) } }) @@ -254,25 +230,44 @@ function createComponent (ComposedComponent) { } } - render () { - if (this.constructor.columnElement) { - this.styles = this.getStyles() + isColumnElement = () => this.constructor.parentTag === 'mj-column' - return ( - - - - - - ) + renderColumnContainer () { + const styles = { + td: { + background: this.mjAttribute('container-background-color'), + fontSize: '1px', + padding: defaultUnit(this.mjAttribute('padding')), + paddingBottom: defaultUnit(this.mjAttribute('padding-bottom')), + paddingLeft: defaultUnit(this.mjAttribute('padding-left')), + paddingRight: defaultUnit(this.mjAttribute('padding-right')), + paddingTop: defaultUnit(this.mjAttribute('padding-top')), + textAlign: this.mjAttribute('align'), + verticalAlign: this.mjAttribute('vertical-align'), + wordBreak: 'break-word' + } } + return ( + + + + + + ) + } + + renderDefaultContainer () { return ( ) } + + render () { + return this.isColumnElement() ? this.renderColumnContainer() : this.renderDefaultContainer() + } } return MJMLElement diff --git a/packages/mjml-divider/src/index.js b/packages/mjml-divider/src/index.js index a53d8a973..ec9fbfa0c 100644 --- a/packages/mjml-divider/src/index.js +++ b/packages/mjml-divider/src/index.js @@ -3,20 +3,28 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-divider' +const parentTag = 'mj-column' +const closingTag = false const defaultMJMLDefinition = { attributes: { + 'align': null, 'border-color': '#000000', 'border-style': 'solid', 'border-width': '4px', + 'container-background-color': null, + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, 'padding': '10px 25px', + 'vertical-align': null, 'width': '100%' } } -const columnElement = true const baseStyles = { p: { fontSize: '1px', - margin: '0 auto' + margin: '0px auto' } } const postRender = $ => { @@ -31,6 +39,11 @@ const postRender = $ => { return $ } +const schemaXsd = () => ( + ` + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + ` +) @MJMLElement class Divider extends Component { @@ -38,12 +51,12 @@ class Divider extends Component { styles = this.getStyles() getStyles () { - const { mjAttribute } = this.props + const { mjAttribute, defaultUnit } = this.props return merge({}, baseStyles, { p: { - borderTop: `${mjAttribute('border-width')} ${mjAttribute('border-style')} ${mjAttribute('border-color')}`, - width: mjAttribute('width') + borderTop: `${defaultUnit(mjAttribute('border-width'))} ${mjAttribute('border-style')} ${mjAttribute('border-color')}`, + width: defaultUnit(mjAttribute('width')) } }) } @@ -54,12 +67,11 @@ class Divider extends Component { const { width, unit } = helpers.widthParser(mjAttribute('width')) switch (unit) { - case '%': { + case '%': return parentWidth * width / 100 - } - default: { + + default: return width - } } } @@ -75,9 +87,11 @@ class Divider extends Component { } Divider.tagName = tagName +Divider.parentTag = parentTag +Divider.closingTag = closingTag Divider.defaultMJMLDefinition = defaultMJMLDefinition -Divider.columnElement = columnElement Divider.baseStyles = baseStyles Divider.postRender = postRender +Divider.schemaXsd = schemaXsd export default Divider diff --git a/packages/mjml-html/src/index.js b/packages/mjml-html/src/index.js index d60309b66..42fe53316 100644 --- a/packages/mjml-html/src/index.js +++ b/packages/mjml-html/src/index.js @@ -3,19 +3,35 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-html' +const parentTag = 'mj-column' +const endingTag = true const defaultMJMLDefinition = { content: '', attributes: { - 'padding': '0px' + 'align': null, + 'container-background-color': null, + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, + 'padding': '0px', + 'vertical-align': null } } -const endingTag = true -const columnElement = true const baseStyles = { div: { fontSize: '13px' } } +const schemaXsd = () => ( + ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + ` +) @MJMLElement class Html extends Component { @@ -39,9 +55,10 @@ class Html extends Component { } Html.tagName = tagName -Html.defaultMJMLDefinition = defaultMJMLDefinition +Html.parentTag = parentTag Html.endingTag = endingTag -Html.columnElement = columnElement +Html.defaultMJMLDefinition = defaultMJMLDefinition Html.baseStyles = baseStyles +Html.schemaXsd = schemaXsd export default Html diff --git a/packages/mjml-image/src/index.js b/packages/mjml-image/src/index.js index a1c046e67..b7d5478fd 100644 --- a/packages/mjml-image/src/index.js +++ b/packages/mjml-image/src/index.js @@ -4,20 +4,27 @@ import min from 'lodash/min' import React, { Component } from 'react' const tagName = 'mj-image' +const parentTag = 'mj-column' +const endingTag = true +const closingTag = false const defaultMJMLDefinition = { attributes: { - 'height': 'auto', - 'padding': '10px 25px', 'align': 'center', 'alt': '', 'border': 'none', + 'container-background-color': null, + 'height': 'auto', 'href': '', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, + 'padding': '10px 25px', 'src': '', - 'target': '_blank' + 'target': '_blank', + 'vertical-align': null } } -const endingTag = true -const columnElement = true const baseStyles = { table: { borderCollapse: 'collapse', @@ -31,6 +38,11 @@ const baseStyles = { width: '100%' } } +const schemaXsd = () => ( + ` + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + ` +) @MJMLElement class Image extends Component { @@ -51,27 +63,27 @@ class Image extends Component { } getStyles () { - const { mjAttribute } = this.props + const { mjAttribute, defaultUnit } = this.props return merge({}, baseStyles, { td: { - width: this.getContentWidth() + width: defaultUnit(this.getContentWidth()) }, img: { border: mjAttribute('border'), - height: mjAttribute('height') + height: defaultUnit(mjAttribute('height')) } }) } renderImage () { - const { mjAttribute } = this.props + const { mjAttribute, defaultUnit } = this.props const img = ( {mjAttribute('alt')} @@ -114,9 +126,11 @@ class Image extends Component { } Image.tagName = tagName -Image.defaultMJMLDefinition = defaultMJMLDefinition +Image.parentTag = parentTag Image.endingTag = endingTag -Image.columnElement = columnElement +Image.closingTag = closingTag +Image.defaultMJMLDefinition = defaultMJMLDefinition Image.baseStyles = baseStyles +Image.schemaXsd = schemaXsd export default Image diff --git a/packages/mjml-invoice-item/src/index.js b/packages/mjml-invoice-item/src/index.js index 70b4104eb..94b8d576b 100644 --- a/packages/mjml-invoice-item/src/index.js +++ b/packages/mjml-invoice-item/src/index.js @@ -3,6 +3,8 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-invoice-item' +const parentTag = 'mj-invoice' +const endingTag = true const defaultMJMLDefinition = { attributes: { 'color': '#747474', @@ -15,7 +17,6 @@ const defaultMJMLDefinition = { 'text-align': 'left' } } -const endingTag = true const baseStyles = { td: { fontWeight: '500', @@ -41,8 +42,8 @@ class InvoiceItem extends Component { td: { color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), "px"), - padding: defaultUnit(mjAttribute('padding'), "px"), + fontSize: defaultUnit(mjAttribute('font-size')), + padding: defaultUnit(mjAttribute('padding')), textAlign: mjAttribute('text-align') } }) @@ -68,8 +69,9 @@ class InvoiceItem extends Component { } InvoiceItem.tagName = tagName -InvoiceItem.defaultMJMLDefinition = defaultMJMLDefinition +InvoiceItem.parentTag = parentTag InvoiceItem.endingTag = endingTag +InvoiceItem.defaultMJMLDefinition = defaultMJMLDefinition InvoiceItem.baseStyles = baseStyles export default InvoiceItem diff --git a/packages/mjml-invoice/src/index.js b/packages/mjml-invoice/src/index.js index 27676e807..775e6bca0 100644 --- a/packages/mjml-invoice/src/index.js +++ b/packages/mjml-invoice/src/index.js @@ -54,13 +54,13 @@ class Invoice extends Component { table: { color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), "px"), - lineHeight: defaultUnit(mjAttribute('line-height'), "px") + fontSize: defaultUnit(mjAttribute('font-size')), + lineHeight: defaultUnit(mjAttribute('line-height')) }, th: { fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), "px"), - lineHeight: defaultUnit(mjAttribute('line-height'), "px") + fontSize: defaultUnit(mjAttribute('font-size')), + lineHeight: defaultUnit(mjAttribute('line-height')) }, thead: { borderBottom: mjAttribute('border') @@ -70,9 +70,9 @@ class Invoice extends Component { }, total: { fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), "px"), + fontSize: defaultUnit(mjAttribute('font-size')), fontWeight: '700', - lineHeight: defaultUnit(mjAttribute('line-height'), "px"), + lineHeight: defaultUnit(mjAttribute('line-height')), padding: '10px 20px', textAlign: 'right' } diff --git a/packages/mjml-list/src/index.js b/packages/mjml-list/src/index.js index 1309a6c0d..6ee685737 100644 --- a/packages/mjml-list/src/index.js +++ b/packages/mjml-list/src/index.js @@ -3,19 +3,25 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-list' +const parentTag = 'mj-column' +const endingTag = true const defaultMJMLDefinition = { content: '', attributes: { 'align': 'left', 'color': '#000000', + 'container-background-color': null, 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', 'font-size': '13px', 'line-height': '22px', - 'padding': '10px 25px' + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, + 'padding': '10px 25px', + 'vertical-align': null } } -const endingTag = true -const columnElement = true const baseStyles = { ul: { display: 'inline-block', @@ -23,6 +29,15 @@ const baseStyles = { textAlign: 'left' } } +const schemaXsd = () => ( + ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + ` +) @MJMLElement class List extends Component { @@ -36,8 +51,8 @@ class List extends Component { ul: { color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), "px"), - lineHeight: mjAttribute('line-height') + fontSize: defaultUnit(mjAttribute('font-size')), + lineHeight: defaultUnit(mjAttribute('line-height')) } }) } @@ -55,9 +70,10 @@ class List extends Component { } List.tagName = tagName -List.defaultMJMLDefinition = defaultMJMLDefinition +List.parentTag = parentTag List.endingTag = endingTag -List.columnElement = columnElement +List.defaultMJMLDefinition = defaultMJMLDefinition List.baseStyles = baseStyles +List.schemaXsd = schemaXsd export default List diff --git a/packages/mjml-location/src/index.js b/packages/mjml-location/src/index.js index bf25f99c0..387b9fd85 100644 --- a/packages/mjml-location/src/index.js +++ b/packages/mjml-location/src/index.js @@ -4,18 +4,30 @@ import MJMLText from 'mjml-text' import React, { Component } from 'react' const tagName = 'mj-location' +const parentTag = 'mj-column' +const endingTag = true const defaultMJMLDefinition = { attributes: { + 'align': null, 'color': '#3aa7ed', + 'container-background-color': null, 'font-family': 'Roboto, sans-serif', 'font-size': '18px', 'font-weight': '500', + 'img-src': 'http://i.imgur.com/DPCJHhy.png', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, 'padding': '10px 25px', - 'img-src': 'http://i.imgur.com/DPCJHhy.png' + 'vertical-align': null } } -const endingTag = true -const columnElement = true +const schemaXsd = () => ( + ` + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + ` +) @MJMLElement class Location extends Component { @@ -34,12 +46,12 @@ class Location extends Component { } getAttributes () { - const { mjAttribute } = this.props + const { mjAttribute, defaultUnit } = this.props return { text: { 'font-family': mjAttribute('font-family'), - 'font-size': mjAttribute('font-size'), + 'font-size': defaultUnit(mjAttribute('font-size')), 'font-weight': mjAttribute('font-weight'), 'padding': '0px', 'text-decoration': mjAttribute('text-decoration') @@ -85,8 +97,9 @@ class Location extends Component { } Location.tagName = tagName -Location.defaultMJMLDefinition = defaultMJMLDefinition +Location.parentTag = parentTag Location.endingTag = endingTag -Location.columnElement = columnElement +Location.defaultMJMLDefinition = defaultMJMLDefinition +Location.schemaXsd = schemaXsd export default Location diff --git a/packages/mjml-raw/src/index.js b/packages/mjml-raw/src/index.js index 2f8f7b5b0..fe73e3ab6 100644 --- a/packages/mjml-raw/src/index.js +++ b/packages/mjml-raw/src/index.js @@ -3,6 +3,7 @@ import React, { Component } from 'react' const tagName = 'mj-raw' const endingTag = true +const rawElement = true const postRender = $ => { $('.mj-raw').each(function () { $(this).replaceWith($(this).html()) @@ -39,6 +40,7 @@ class Raw extends Component { Raw.tagName = tagName Raw.endingTag = endingTag +Raw.rawElement = rawElement Raw.postRender = postRender export default Raw diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 86f02b5bd..50a59e942 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -4,16 +4,17 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-section' +const parentTag = 'mj-container' const defaultMJMLDefinition = { attributes: { 'background-repeat': 'repeat', - 'padding': '20px 0', + 'padding': '20px 0px', 'background-size': 'auto' } } const baseStyles = { div: { - margin: '0 auto' + margin: '0px auto' }, table: { fontSize: '1px', @@ -78,13 +79,15 @@ const postRender = $ => { return $ } -const schemaXsd = () => ( - ` - - - +const schemaXsd = elements => { + const sectionElements = Object.keys(elements).map(element => elements[element].parentTag === tagName ? elements[element].tagName : null).filter(Boolean) + + return ` + + ${(sectionElements.map(element => ``).join(`\n`))} + ` -) +} @MJMLElement class Section extends Component { @@ -109,11 +112,11 @@ class Section extends Component { return merge({}, baseStyles, { td: { fontSize: '1px', - padding: defaultUnit(mjAttribute('padding'), "px"), - paddingBottom: defaultUnit(mjAttribute('padding-bottom'), "px"), - paddingLeft: defaultUnit(mjAttribute('padding-left'), "px"), - paddingRight: defaultUnit(mjAttribute('padding-right'), "px"), - paddingTop: defaultUnit(mjAttribute('padding-top'), "px"), + padding: defaultUnit(mjAttribute('padding')), + paddingBottom: defaultUnit(mjAttribute('padding-bottom')), + paddingLeft: defaultUnit(mjAttribute('padding-left')), + paddingRight: defaultUnit(mjAttribute('padding-right')), + paddingTop: defaultUnit(mjAttribute('padding-top')), textAlign: mjAttribute('text-align'), verticalAlign: mjAttribute('vertical-align') }, @@ -183,6 +186,7 @@ class Section extends Component { } Section.tagName = tagName +Section.parentTag = parentTag Section.defaultMJMLDefinition = defaultMJMLDefinition Section.baseStyles = baseStyles Section.postRender = postRender diff --git a/packages/mjml-social/src/index.js b/packages/mjml-social/src/index.js index e343e1d13..a0cce6b96 100644 --- a/packages/mjml-social/src/index.js +++ b/packages/mjml-social/src/index.js @@ -5,8 +5,15 @@ import clone from 'lodash/clone' import React, { Component } from 'react' const tagName = 'mj-social' +const parentTag = 'mj-column' +const closingTag = false const defaultMJMLDefinition = { attributes: { + 'align': 'center', + 'base-url': 'https://www.mailjet.com/images/theme/v1/icons/ico-social/', + 'color': '#333333', + 'container-background-color': null, + 'display': 'facebook:share twitter:share google:share', 'facebook-content': 'Share', 'facebook-href': '[[SHORT_PERMALINK]]', 'facebook-icon-color' : '#3b5998', @@ -23,8 +30,12 @@ const defaultMJMLDefinition = { 'linkedin-content': 'Share', 'linkedin-href': '[[SHORT_PERMALINK]]', 'linkedin-icon-color' : '#0077b5', - 'padding': '10px 25px', 'mode': 'horizontal', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, + 'padding': '10px 25px', 'pinterest-content': 'Pin it', 'pinterest-href': '[[SHORT_PERMALINK]]', 'pinterest-icon-color': '#bd081c', @@ -33,13 +44,9 @@ const defaultMJMLDefinition = { 'twitter-content': 'Tweet', 'twitter-href': '[[SHORT_PERMALINK]]', 'twitter-icon-color': '#55acee', - 'align': 'center', - 'color': '#333333', - 'display': 'facebook:share twitter:share google:share', - 'base-url': 'https://www.mailjet.com/images/theme/v1/icons/ico-social/' + 'vertical-align': null } } -const columnElement = true const baseStyles = { div: { textAlign: 'center' @@ -119,6 +126,11 @@ const postRender = $ => { return $ } +const schemaXsd = () => ( + ` + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + ` +) @MJMLElement class Social extends Component { @@ -135,18 +147,18 @@ class Social extends Component { a: { 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: defaultUnit(mjAttribute('line-height'), "px"), + lineHeight: defaultUnit(mjAttribute('line-height')), textDecoration: mjAttribute('text-decoration') }, td1: { padding: this.isHorizontal() ? '0 4px' : '4px 0' }, td2: { - width: defaultUnit(mjAttribute('icon-size'), "px"), - height: defaultUnit(mjAttribute('icon-size'), "px") + width: defaultUnit(mjAttribute('icon-size')), + height: defaultUnit(mjAttribute('icon-size')) } }) } @@ -160,7 +172,7 @@ class Social extends Component { isInTextMode () { const { mjAttribute } = this.props - return mjAttribute('text-mode') == true || mjAttribute('text-mode') == 'true' + return mjAttribute('text-mode') === true || mjAttribute('text-mode') === 'true' } renderSocialButton (platform, share) { @@ -243,7 +255,7 @@ class Social extends Component { return null } - return this.renderSocialButton(platform, share != "url") + return this.renderSocialButton(platform, share !== 'url') }) } @@ -301,10 +313,12 @@ class Social extends Component { } Social.tagName = tagName +Social.parentTag = parentTag +Social.closingTag = closingTag Social.defaultMJMLDefinition = defaultMJMLDefinition -Social.columnElement = columnElement Social.baseStyles = baseStyles Social.buttonDefinitions = buttonDefinitions Social.postRender = postRender +Social.schemaXsd = schemaXsd export default Social diff --git a/packages/mjml-spacer/src/index.js b/packages/mjml-spacer/src/index.js index 693db846d..9da59a829 100644 --- a/packages/mjml-spacer/src/index.js +++ b/packages/mjml-spacer/src/index.js @@ -2,12 +2,25 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-spacer' +const parentTag = 'mj-column' +const closingTag = false const defaultMJMLDefinition = { attributes: { - 'height': '20px' + 'align': null, + 'container-background-color': null, + 'height': '20px', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, + 'vertical-align': null } } -const columnElement = true +const schemaXsd = () => ( + ` + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + ` +) @MJMLElement class Spacer extends Component { @@ -20,7 +33,7 @@ class Spacer extends Component { return { div: { fontSize: '1px', - lineHeight: defaultUnit(mjAttribute('height'), 'px') + lineHeight: defaultUnit(mjAttribute('height')) } } } @@ -36,7 +49,9 @@ class Spacer extends Component { } Spacer.tagName = tagName +Spacer.parentTag = parentTag +Spacer.closingTag = closingTag Spacer.defaultMJMLDefinition = defaultMJMLDefinition -Spacer.columnElement = columnElement +Spacer.schemaXsd = schemaXsd export default Spacer diff --git a/packages/mjml-table/src/index.js b/packages/mjml-table/src/index.js index 052be3d81..637f9965a 100644 --- a/packages/mjml-table/src/index.js +++ b/packages/mjml-table/src/index.js @@ -2,21 +2,36 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-table' +const parentTag = 'mj-column' +const endingTag = true const defaultMJMLDefinition = { content: '', attributes: { 'align': 'left', 'color': '#000', + 'container-background-color': null, 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', 'font-size': '13px', 'line-height': '22px', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, 'padding': '10px 25px', 'table-layout': 'auto', + 'vertical-align': null, 'width': '100%' } } -const endingTag = true -const columnElement = true +const schemaXsd = () => ( + ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + ` +) @MJMLElement class Table extends Component { @@ -30,8 +45,8 @@ class Table extends Component { table: { color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size'), 'px'), - lineHeight: defaultUnit(mjAttribute('line-height'), 'px'), + fontSize: defaultUnit(mjAttribute('font-size')), + lineHeight: defaultUnit(mjAttribute('line-height')), tableLayout: mjAttribute('table-layout') } } @@ -54,8 +69,9 @@ class Table extends Component { } Table.tagName = tagName -Table.defaultMJMLDefinition = defaultMJMLDefinition +Table.parentTag = parentTag Table.endingTag = endingTag -Table.columnElement = columnElement +Table.defaultMJMLDefinition = defaultMJMLDefinition +Table.schemaXsd = schemaXsd export default Table diff --git a/packages/mjml-text/src/index.js b/packages/mjml-text/src/index.js index 5173f1034..3bb6ecf22 100644 --- a/packages/mjml-text/src/index.js +++ b/packages/mjml-text/src/index.js @@ -3,24 +3,39 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-text' +const parentTag = 'mj-column' +const endingTag = true const defaultMJMLDefinition = { content: '', attributes: { 'align': 'left', 'color': '#000000', + 'container-background-color': null, 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', 'font-size': '13px', 'line-height': '22px', - 'padding': '10px 25px' + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, + 'padding': '10px 25px', + 'vertical-align': null } } -const endingTag = true -const columnElement = true const baseStyles = { div: { cursor: 'auto' } } +const schemaXsd = () => ( + ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + ` +) @MJMLElement class Text extends Component { @@ -34,10 +49,10 @@ class Text extends Component { div: { 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: defaultUnit(mjAttribute('line-height'), "px"), + lineHeight: defaultUnit(mjAttribute('line-height')), textDecoration: mjAttribute('text-decoration') } }) @@ -56,9 +71,10 @@ class Text extends Component { } Text.tagName = tagName -Text.defaultMJMLDefinition = defaultMJMLDefinition +Text.parentTag = parentTag Text.endingTag = endingTag -Text.columnElement = columnElement +Text.defaultMJMLDefinition = defaultMJMLDefinition Text.baseStyles = baseStyles +Text.schemaXsd = schemaXsd export default Text diff --git a/packages/mjml/test.js b/packages/mjml/test.js index a86b493f7..96b9b099d 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,6 +1,6 @@ var mjml = require('./lib/index') -mjml.mjml2html(` +console.log(mjml.mjml2html(` @@ -23,10 +23,6 @@ mjml.mjml2html(` Scheme, Self - - - - My button @@ -53,4 +49,4 @@ mjml.mjml2html(` -`, { beautify: true }) +`, { beautify: true })) From 320f4803da1b4795913f92f7c212b622a141aa28 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 30 Jun 2016 14:29:48 +0200 Subject: [PATCH 06/87] Merge master into xsd --- .eslintrc | 3 +- CONTRIBUTING.md | 143 +++++++++- README.md | 4 +- UPGRADE.md | 2 - doc/config.json | 32 +++ doc/create.md | 22 +- doc/document.md | 46 ++++ gulpfile.babel.js | 44 ++- install.sh | 24 +- package.json | 24 +- packages/mjml-button/README.md | 17 +- packages/mjml-button/package.json | 11 +- packages/mjml-button/src/index.js | 37 +-- packages/mjml-cli/README.md | 2 +- packages/mjml-cli/package.json | 10 +- packages/mjml-cli/src/client.js | 14 +- packages/mjml-cli/src/createComponent.js | 4 +- packages/mjml-cli/test/cli.spec.js | 7 +- packages/mjml-column/README.md | 20 +- packages/mjml-column/package.json | 11 +- packages/mjml-column/src/index.js | 26 +- packages/mjml-container/README.md | 15 +- packages/mjml-container/package.json | 9 +- packages/mjml-container/src/index.js | 33 ++- packages/mjml-core/package.json | 27 +- packages/mjml-core/src/MJMLHead.js | 7 + packages/mjml-core/src/MJMLRenderer.js | 88 +++--- .../mjml-core/src/configs/defaultContainer.js | 22 +- .../mjml-core/src/decorators/MJMLElement.js | 82 +++--- .../mjml-core/src/helpers/getFontsImports.js | 37 --- packages/mjml-core/src/helpers/html.js | 2 + packages/mjml-core/src/helpers/importFonts.js | 32 +++ packages/mjml-core/src/helpers/index.js | 3 +- packages/mjml-core/src/helpers/mjAttribute.js | 16 +- packages/mjml-core/src/helpers/mjml.js | 16 +- packages/mjml-core/src/includeExternal.js | 20 ++ packages/mjml-core/src/index.js | 10 +- packages/mjml-core/src/parsers/document.js | 44 ++- packages/mjml-divider/README.md | 9 +- packages/mjml-divider/package.json | 11 +- packages/mjml-divider/src/index.js | 2 +- .../.npmignore | 0 packages/mjml-group/README.md | 65 +++++ .../package.json | 9 +- packages/mjml-group/src/index.js | 115 ++++++++ packages/mjml-head-attributes/.npmignore | 3 + packages/mjml-head-attributes/README.md | 36 +++ packages/mjml-head-attributes/package.json | 18 ++ packages/mjml-head-attributes/src/index.js | 17 ++ packages/mjml-head-font/.npmignore | 3 + packages/mjml-head-font/README.md | 28 ++ packages/mjml-head-font/package.json | 15 + packages/mjml-head-font/src/index.js | 3 + packages/mjml-head-title/.npmignore | 3 + packages/mjml-head-title/README.md | 28 ++ packages/mjml-head-title/package.json | 15 + packages/mjml-head-title/src/index.js | 5 + packages/mjml-hero/.npmignore | 3 + packages/mjml-hero/README.md | 145 ++++++++++ packages/mjml-hero/package.json | 20 ++ packages/mjml-hero/src/Hero.js | 258 ++++++++++++++++++ packages/mjml-hero/src/HeroContent.js | 118 ++++++++ packages/mjml-hero/src/index.js | 4 + packages/mjml-html/package.json | 11 +- packages/mjml-image/README.md | 12 +- packages/mjml-image/package.json | 11 +- packages/mjml-image/src/index.js | 7 +- packages/mjml-invoice-item/README.md | 37 --- packages/mjml-invoice/README.md | 54 +++- packages/mjml-invoice/package.json | 13 +- packages/mjml-invoice/src/Invoice.js | 161 +++++++++++ .../src/InvoiceItem.js} | 0 packages/mjml-invoice/src/index.js | 163 +---------- packages/mjml-list/README.md | 8 +- packages/mjml-list/package.json | 11 +- packages/mjml-location/README.md | 15 +- packages/mjml-location/package.json | 15 +- packages/mjml-navbar/.npmignore | 3 + packages/mjml-navbar/README.md | 179 ++++++++++++ packages/mjml-navbar/package.json | 21 ++ packages/mjml-navbar/src/InlineLinks.js | 186 +++++++++++++ packages/mjml-navbar/src/Link.js | 89 ++++++ packages/mjml-navbar/src/Navbar.js | 32 +++ packages/mjml-navbar/src/index.js | 5 + packages/mjml-raw/README.md | 8 +- packages/mjml-raw/package.json | 9 +- packages/mjml-section/README.md | 10 +- packages/mjml-section/package.json | 11 +- packages/mjml-section/src/index.js | 42 +-- packages/mjml-social/README.md | 30 +- packages/mjml-social/package.json | 11 +- packages/mjml-social/src/index.js | 53 ++-- packages/mjml-spacer/README.md | 4 +- packages/mjml-spacer/package.json | 9 +- packages/mjml-table/README.md | 10 +- packages/mjml-table/package.json | 9 +- packages/mjml-table/src/index.js | 2 + packages/mjml-text/README.md | 22 +- packages/mjml-text/package.json | 11 +- packages/mjml-text/src/index.js | 5 +- packages/mjml/package.json | 46 ++-- packages/mjml/src/index.js | 31 ++- packages/mjml/test.js | 63 +---- 103 files changed, 2574 insertions(+), 744 deletions(-) create mode 100644 doc/config.json create mode 100644 doc/document.md create mode 100644 packages/mjml-core/src/MJMLHead.js delete mode 100644 packages/mjml-core/src/helpers/getFontsImports.js create mode 100644 packages/mjml-core/src/helpers/html.js create mode 100644 packages/mjml-core/src/helpers/importFonts.js create mode 100644 packages/mjml-core/src/includeExternal.js rename packages/{mjml-invoice-item => mjml-group}/.npmignore (100%) create mode 100644 packages/mjml-group/README.md rename packages/{mjml-invoice-item => mjml-group}/package.json (70%) create mode 100644 packages/mjml-group/src/index.js create mode 100644 packages/mjml-head-attributes/.npmignore create mode 100644 packages/mjml-head-attributes/README.md create mode 100644 packages/mjml-head-attributes/package.json create mode 100644 packages/mjml-head-attributes/src/index.js create mode 100644 packages/mjml-head-font/.npmignore create mode 100644 packages/mjml-head-font/README.md create mode 100644 packages/mjml-head-font/package.json create mode 100644 packages/mjml-head-font/src/index.js create mode 100644 packages/mjml-head-title/.npmignore create mode 100644 packages/mjml-head-title/README.md create mode 100644 packages/mjml-head-title/package.json create mode 100644 packages/mjml-head-title/src/index.js create mode 100644 packages/mjml-hero/.npmignore create mode 100644 packages/mjml-hero/README.md create mode 100644 packages/mjml-hero/package.json create mode 100644 packages/mjml-hero/src/Hero.js create mode 100644 packages/mjml-hero/src/HeroContent.js create mode 100644 packages/mjml-hero/src/index.js delete mode 100644 packages/mjml-invoice-item/README.md create mode 100644 packages/mjml-invoice/src/Invoice.js rename packages/{mjml-invoice-item/src/index.js => mjml-invoice/src/InvoiceItem.js} (100%) create mode 100644 packages/mjml-navbar/.npmignore create mode 100644 packages/mjml-navbar/README.md create mode 100644 packages/mjml-navbar/package.json create mode 100644 packages/mjml-navbar/src/InlineLinks.js create mode 100644 packages/mjml-navbar/src/Link.js create mode 100644 packages/mjml-navbar/src/Navbar.js create mode 100644 packages/mjml-navbar/src/index.js diff --git a/.eslintrc b/.eslintrc index 491b844c2..8f0a3a6b6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,6 +28,7 @@ "eqeqeq": 0, "func-names": 0, "global-require": 0, + "import/no-mutable-exports": 0, "indent": [2, 2, {"SwitchCase": 1}], "key-spacing": 0, "max-len": 0, @@ -49,7 +50,7 @@ "quote-props": 0, "quotes": 0, "radix": 0, - "space-before-function-paren": 0, + "space-before-function-paren": [2, "always"], "space-in-parens": 0, "vars-on-top": 0, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07734b046..e7c358d7d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,140 @@ +# What should I know before I get started? -## Guidelines +## Code of Conduct - - Clone the full mjml repository - - Apply your feature under the required folder - - Add documentation in the appropriate readme file - - Create your pull request +This project adheres to the Contributor Covenant [code of conduct](http://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://mjml.io/documentation/)** 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** + + ![Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url) + + **MJML version:** + + [Enter Atom 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/) | diff --git a/README.md b/README.md index d3a592e8e..618d22b48 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ Get your hands dirty by trying the MJML online editor! Write awesome code on the Loeck Guillaume Meriadec + Nicolas Arnaud HTeuMeuLeu Emmanuel Payer @@ -157,7 +158,8 @@ Get your hands dirty by trying the MJML online editor! Write awesome code on the Loeck Guillaume Meriadec - Arnaud + Nicolas + Arnaud HTeuMeuLeu Emmanuel Payet Matthieu diff --git a/UPGRADE.md b/UPGRADE.md index 3dd1c632e..2a5def16d 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -105,7 +105,6 @@ const defaultMJMLDefinition = { 'width': '100%' } } -const columnElement = true const baseStyles = { p: { fontSize: '1px', @@ -132,7 +131,6 @@ class Divider extends Component { Divider.tagName = tagName Divider.defaultMJMLDefinition = defaultMJMLDefinition -Divider.columnElement = columnElement Divider.baseStyles = baseStyles Divider.postRender = postRender diff --git a/doc/config.json b/doc/config.json new file mode 100644 index 000000000..5ec732a70 --- /dev/null +++ b/doc/config.json @@ -0,0 +1,32 @@ +[ + "mjml/doc/guide.md", + "mjml/doc/install.md", + "mjml/doc/getting_started.md", + "mjml/doc/basic.md", + "mjml/doc/advanced.md", + "mjml/doc/components.md", + "mjml/doc/document.md", + "mjml/packages/mjml-button/README.md", + "mjml/packages/mjml-column/README.md", + "mjml/packages/mjml-group/README.md", + "mjml/packages/mjml-container/README.md", + "mjml/packages/mjml-divider/README.md", + "mjml/packages/mjml-head-attributes/README.md", + "mjml/packages/mjml-head-font/README.md", + "mjml/packages/mjml-head-title/README.md", + "mjml/packages/mjml-hero/README.md", + "mjml/packages/mjml-html/README.md", + "mjml/packages/mjml-image/README.md", + "mjml/packages/mjml-invoice/README.md", + "mjml/packages/mjml-list/README.md", + "mjml/packages/mjml-location/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/doc/create.md", + "mjml/doc/tooling.md" +] diff --git a/doc/create.md b/doc/create.md index 93df5c077..922c4b50a 100644 --- a/doc/create.md +++ b/doc/create.md @@ -8,7 +8,7 @@ Let's create a simple `Title` component. ``` -$> mjml --register title +$> mjml --init-component title ``` run the following in your terminal. It will create a `Title.js` file in the current working directory. @@ -66,21 +66,19 @@ Title.defaultMJMLDefinition = { } } Title.endingTag = true -Title.columnElement = true Title.baseStyles = { div: { color: "blue" } } -Title.postRender = ($) =>Â { +Title.postRender = ($) => { $('.title').removeAttr('data-title-color'); return $ } ``` -- tagName: modify the tag name of your component, here it will be +- tagName: modify the tag name of your component, here it will be `<title>` - 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)` -- columnElement: if your component is included in a `mj-column` then it should be set to true. It will wrap everything in a `td` that supports `padding` for example ## Default and readonly attributes @@ -102,13 +100,13 @@ It can contain any CSS property or component property, but please make sure it w 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(`<!--[if mso]> - <table border="0" cellpadding="0" cellspacing="0" width="600" align="center" style="width:600}px;"><tr><td> - <![endif]-->`); - $('.title').append(`<!--[if mso]> - </td></tr></table> - <![endif]-->`); +Title.postRender = $ => { + $('.title').prepend(`<!--[if mso | IE]> + <table border="0" cellpadding="0" cellspacing="0" width="600" align="center" style="width:600}px;"><tr><td> + <![endif]-->`) + $('.title').append(`<!--[if mso | IE]> + </td></tr></table> + <![endif]-->`) return $ } diff --git a/doc/document.md b/doc/document.md new file mode 100644 index 000000000..ff2cbac97 --- /dev/null +++ b/doc/document.md @@ -0,0 +1,46 @@ +# MJML document + +An MJML Document starts with a `<mjml>` tag, it can contains only `mj-head` and `mj-body` tags. Both have the same purpose of `head` and `body` in a HTML document. + +## MJ-Head + +Mj-Head contains everything related to the document such as style and meta element. It supports custom head elements and can be registered through `registerMJHeadElement(<string> name, <function> handler)` api from `mjml-core`, it acts as a pre-render hook. + + +## MJ-Body + +Mj-Body contains everything related to the content of your email. It supports custom elements too and can be registered either through `registerMJElement(<MJMLElement> class)` api from `mjml-core` or via a `.mjmlconfig` file. Non-known element from `mjml-core` are simply ignored. Note that `mj-body` should have only one root element due to how React work. + + +## MJ-Inlcude + +The mjml-core package allows you to include external mjml files to build your email template. + +`header.mjml` +``` xml +<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 > mj-container` +tags to make it easier to preview outside the main template + +`main.mjml` +``` xml +<mjml> + <mj-body> + <mj-container> + <mj-include path="./header" /> <!-- or 'header.mjml' --> + </mj-container> + </mj-body> +</mjml> +``` + +The mjml engine will then replace your included files before starting the rendering process + +<aside class="notice"> +Note that the file must be a file with a `.mjml` extension +</aside> diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 1ab7350c8..138dfd19d 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -28,7 +28,11 @@ if (path.win32 === path) { gulp.task('build', () => { return gulp.src(`${PACKAGES_PATH}/*/src/**/*.js`) - .pipe(through.obj((file, enc, callback) => { + .pipe(through.obj((file, encoding, callback) => { + file.contents = new Buffer(String(file.contents).replace(/__MJML_VERSION__/g, require(path.resolve(PACKAGES_PATH, `${file.relative.split(path.sep)[0]}/package.json`)).version)) + callback(null, file) + })) + .pipe(through.obj((file, encoding, callback) => { file._path = file.path file.path = file.path.replace(srcEx, libFragment) callback(null, file) @@ -40,38 +44,32 @@ gulp.task('build', () => { gulp.task('install', () => { return Promise.all( - Object.keys(packages).map(packageName => { - return new Promise(resolve => { - cd(packages[packageName]) - exec('npm install') - resolve() - }) - }) + Object.keys(packages).map(packageName => new Promise(resolve => { + cd(packages[packageName]) + exec('npm install') + resolve() + })) ) }) gulp.task('test', () => { return Promise.all( - Object.keys(packages).map(packageName => { - return new Promise(resolve => { - cd(packages[packageName]) - // test if there's a test directory - exec('mocha --compilers js:babel-register') - resolve() - }) - }) + Object.keys(packages).map(packageName => new Promise(resolve => { + cd(packages[packageName]) + // test if there's a test directory + exec('mocha --compilers js:babel-register') + resolve() + })) ) }) gulp.task('clean', () => { // Remove package node_modules return Promise.all( - Object.keys(packages).map(packageName => { - return new Promise(resolve => { - rm('-rf', path.resolve(packages[packageName], 'node_modules'), path.resolve(packages[packageName], 'lib'), path.resolve(packages[packageName], 'dist')) - resolve() - }) - }) + Object.keys(packages).map(packageName => new Promise(resolve => { + rm('-rf', path.resolve(packages[packageName], 'node_modules'), path.resolve(packages[packageName], 'lib'), path.resolve(packages[packageName], 'dist')) + resolve() + })) ) }) @@ -92,7 +90,7 @@ gulp.task('version', () => { // Bump the version cd(packages[packageName]) const execResult = exec(`npm version ${version}`) - const bumpedVersion = execResult.output.replace('\n', '').replace('v', '') + const bumpedVersion = execResult.toString().replace('\n', '').replace('v', '') // Commit and tag exec(`git add ${packages[packageName]}/package.json`) diff --git a/install.sh b/install.sh index 59425f604..1314e28c9 100755 --- a/install.sh +++ b/install.sh @@ -23,9 +23,8 @@ BPurple='\033[1;35m' # Purple BCyan='\033[1;36m' # Cyan BWhite='\033[1;37m' # White -printf "${Yellow}Installing npm depencies for each MJML packages ${Color_Off} \n" +printf "${Yellow}Installing npm depencies for mono repo ${Color_Off} \n" npm install -gulp install printf "${BGreen}Done.${Color_Off} \n" cd packages @@ -38,12 +37,17 @@ 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-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 .. cd mjml-image && npm link && npm link mjml-core && cd .. -cd mjml-invoice && npm link && npm link mjml-core && npm link mjml-table && npm link mjml-invoice-item && cd .. -cd mjml-invoice-item && npm link && npm link mjml-core && cd .. +cd mjml-invoice && npm link && npm link mjml-core && npm link mjml-table && cd .. cd mjml-list && npm link && npm link mjml-core && cd .. cd mjml-location && npm link && npm link mjml-core && npm link mjml-text && cd .. +cd mjml-navbar && npm link && npm link mjml-core && npm link mjml-section && cd .. cd mjml-raw && npm link && npm link mjml-core && cd .. cd mjml-section && npm link && npm link mjml-core && cd .. cd mjml-social && npm link && npm link mjml-core && cd .. @@ -52,6 +56,7 @@ cd mjml-table && npm link && npm link mjml-core && cd .. cd mjml-text && npm link && npm link mjml-core && cd .. printf "${BGreen}Done.${Color_Off} \n" + printf "${Yellow}Linking dependencies for MJML package.${Color_Off} \n" cd mjml @@ -61,12 +66,17 @@ npm link mjml-column npm link mjml-container npm link mjml-core npm link mjml-divider +npm link mjml-group +npm link mjml-head-attributes +npm link mjml-head-font +npm link mjml-head-title +npm link mjml-hero npm link mjml-html npm link mjml-image npm link mjml-invoice -npm link mjml-invoice npm link mjml-list npm link mjml-location +npm link mjml-navbar npm link mjml-raw npm link mjml-section npm link mjml-social @@ -74,6 +84,10 @@ npm link mjml-spacer npm link mjml-table npm link mjml-text +printf "${BGreen}Done.${Color_Off} \n" + +printf "${Yellow}Installing npm depencies for each MJML packages ${Color_Off} \n" +gulp install cd ../.. printf "${BGreen}Done.${Color_Off} Happy coding ! 🍺 \n" diff --git a/package.json b/package.json index 900815765..fa9720df6 100644 --- a/package.json +++ b/package.json @@ -6,29 +6,33 @@ "scripts": { "lint": "eslint ." }, + "pre-commit": [ + "lint" + ], "devEngines": { "node": "4.x || 5.x || 6.x", "npm": "2.x || 3.x" }, "devDependencies": { "babel": "^6.5.2", - "babel-core": "^6.8.0", + "babel-core": "^6.9.1", "babel-eslint": "^6.0.4", "babel-plugin-transform-decorators-legacy": "^1.3.4", - "babel-preset-es2015": "^6.6.0", + "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-0": "^6.5.0", - "babel-register": "^6.8.0", - "eslint": "^2.9.0", - "eslint-config-airbnb": "^8.0.0", - "eslint-plugin-import": "^1.6.1", - "eslint-plugin-jsx-a11y": "^1.0.4", - "eslint-plugin-react": "^5.0.1", + "babel-register": "^6.9.0", + "eslint": "^2.13.0", + "eslint-config-airbnb": "^9.0.1", + "eslint-plugin-import": "^1.8.1", + "eslint-plugin-jsx-a11y": "^1.5.3", + "eslint-plugin-react": "^5.2.2", "gulp": "^3.9.1", "gulp-babel": "^6.1.2", - "gulp-newer": "^1.1.0", + "gulp-newer": "^1.2.0", + "pre-commit": "^1.1.3", "shelljs": "^0.7.0", "through2": "^2.0.1", - "yargs": "^4.7.0" + "yargs": "^4.7.1" } } diff --git a/packages/mjml-button/README.md b/packages/mjml-button/README.md index 38919fd35..2662cc9f3 100644 --- a/packages/mjml-button/README.md +++ b/packages/mjml-button/README.md @@ -1,12 +1,18 @@ ## mjml-button +<p align="center"> + <img src="https://cloud.githubusercontent.com/assets/6558790/12751346/fd993192-c9bc-11e5-8c91-37d616bf5874.png" alt="desktop" width="150px" /> +</p> + +Displays a customizable button. + ```xml <mjml> <mj-body> <mj-container> <mj-section> <mj-column> - <mj-button font-family="Helvetica" background-color="#F45E43" color="white"> + <mj-button font-family="Helvetica" background-color="#f45e43" color="white"> Don't click me! </mj-button> </mj-column> @@ -16,14 +22,8 @@ </mjml> ``` -Displays a customizable button. - -<p align="center"> - <img src="https://cloud.githubusercontent.com/assets/6558790/12751346/fd993192-c9bc-11e5-8c91-37d616bf5874.png" alt="desktop" width="150px" /> -</p> - <p align="center"> - <a href="/try-it-live/button"> + <a href="https://mjml.io/try-it-live/component/button"> <img width="100px" src="http://imgh.us/TRYITLIVE.svg" alt="sexy" /> </a> </p> @@ -40,6 +40,7 @@ font-family | string | font name 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 vertical-align | string | vertical alignment | middle href | link | link to be triggered when the button is clicked | n/a diff --git a/packages/mjml-button/package.json b/packages/mjml-button/package.json index b32093246..cee3ac98c 100644 --- a/packages/mjml-button/package.json +++ b/packages/mjml-button/package.json @@ -1,6 +1,7 @@ { "name": "mjml-button", - "version": "2.0.1", + "description": "mjml-button", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index 32095ef98..899dc27c7 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -10,8 +10,7 @@ const defaultMJMLDefinition = { attributes: { 'align': 'center', 'background-color': '#414141', - 'border-radius': '3px', - 'border': 'none', + 'border': '1px solid #414141', 'color': '#ffffff', 'container-background-color': null, 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', @@ -23,6 +22,7 @@ const defaultMJMLDefinition = { 'padding-right': null, 'padding-top': null, 'padding': '10px 25px', + 'inner-padding': '10px', 'text-decoration': 'none', 'vertical-align': 'middle' } @@ -53,27 +53,24 @@ class Button extends Component { return merge({}, baseStyles, { td: { - background: mjAttribute('background-color'), - borderRadius: defaultUnit(mjAttribute('border-radius')), color: mjAttribute('color'), cursor: 'auto', fontStyle: mjAttribute('font-style') }, - table: { - border: mjAttribute('border'), - borderRadius: defaultUnit(mjAttribute('border-radius')) - }, a: { background: mjAttribute('background-color'), - border: `1px solid ${mjAttribute('background-color')}`, - borderRadius: defaultUnit(mjAttribute('border-radius')), + border: mjAttribute('border'), + borderRadius: defaultUnit(mjAttribute('border-radius'), "px"), color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), fontSize: defaultUnit(mjAttribute('font-size')), fontStyle: mjAttribute('font-style'), fontWeight: mjAttribute('font-weight'), - padding: defaultUnit(mjAttribute('padding')), - textDecoration: mjAttribute('text-decoration') + lineHeight: mjAttribute('height'), + padding: defaultUnit(mjAttribute('inner-padding'), "px"), + textDecoration: mjAttribute('text-decoration'), + textTransform: mjAttribute('text-transform'), + margin: "0px" } }) } @@ -81,12 +78,20 @@ class Button extends Component { renderButton () { const { mjContent, mjAttribute } = this.props + if (mjAttribute('href')) { + return ( + <a + dangerouslySetInnerHTML={{ __html: mjContent() }} + href={mjAttribute('href')} + style={this.styles.a} + target="_blank" /> + ) + } + return ( - <a + <p dangerouslySetInnerHTML={{ __html: mjContent() }} - href={mjAttribute('href')} - style={this.styles.a} - target="_blank" /> + style={this.styles.a} /> ) } diff --git a/packages/mjml-cli/README.md b/packages/mjml-cli/README.md index 3bcf1d659..62825e907 100644 --- a/packages/mjml-cli/README.md +++ b/packages/mjml-cli/README.md @@ -2,7 +2,7 @@ # Installation -``` bash +```bash # with npm npm i -g mjml ``` diff --git a/packages/mjml-cli/package.json b/packages/mjml-cli/package.json index 9d7df3caf..75150a2d3 100644 --- a/packages/mjml-cli/package.json +++ b/packages/mjml-cli/package.json @@ -1,11 +1,11 @@ { "name": "mjml-cli", - "version": "2.0.3", + "description": "MJML: the only framework that makes responsive-email easy", + "version": "2.3.0", "main": "bin/mjml", "bin": { "mjml-cli": "bin/mjml" }, - "description": "MJML: the only framework that makes responsive-email easy", "repository": { "type": "git", "url": "git+https://github.com/mjmlio/mjml.git" @@ -14,13 +14,13 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { "commander": "^2.9.0", "fs-promise": "^0.5.0", "glob": "^7.0.3", - "lodash": "^4.11.2", - "mjml-core": "^2.0.1" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0" }, "devDependencies": { "chai": "^3.5.0", diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index ddca64280..5d055bf24 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -72,15 +72,17 @@ export const renderFile = (input, options) => { let output if (options.output) { - const outFile = path.join(path.dirname(options.output), path.basename(options.output, '.html')) + 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}.html` + output = `${outFile}-${index + 1}${extension}` } else { - output = `${outFile}.html` + output = `${outFile}${extension}` } } else { - output = `${inFile}.html` + const extension = path.extname(inFile) || '.html' + output = `${inFile}${extension}` } const filePath = path.resolve(process.cwd(), file) @@ -112,9 +114,9 @@ export const watch = (input, options) => { /* * Create a new component based on the default template */ -export const initComponent = (name, ending, columnElement) => { +export const initComponent = (name, ending) => { mkdir(`./${name}`) .then(() => mkdir(`./${name}/src`)) - .then(() => write(`./${name}/src/index.js`, createComponent(upperFirst(camelCase(name)), ending, columnElement))) + .then(() => write(`./${name}/src/index.js`, createComponent(upperFirst(camelCase(name)), ending))) .then(() => console.log(`Component created: ${name}`)) // eslint-disable-line no-console } diff --git a/packages/mjml-cli/src/createComponent.js b/packages/mjml-cli/src/createComponent.js index 40d0171b9..d68c5e9e3 100644 --- a/packages/mjml-cli/src/createComponent.js +++ b/packages/mjml-cli/src/createComponent.js @@ -1,4 +1,4 @@ -export default (name, endingTag = 'false', columnElement = 'false') => { +export default (name, endingTag = 'false') => { const lowerName = name.toLowerCase() return `import { MJMLElement } from 'mjml-core' @@ -8,7 +8,6 @@ import React, { Component } from 'react' const tagName = '${lowerName}' const endingTag = ${endingTag} -const columnElement = ${columnElement} /* * Add your default mjml-attributes here @@ -66,7 +65,6 @@ class ${name} extends Component { ${name}.tagName = tagName ${name}.defaultMJMLDefinition = defaultMJMLDefinition ${name}.endingTag = endingTag -${name}.columnElement = columnElement ${name}.baseStyles = baseStyles export default ${name} diff --git a/packages/mjml-cli/test/cli.spec.js b/packages/mjml-cli/test/cli.spec.js index 26cf10ab8..80ce48cfb 100644 --- a/packages/mjml-cli/test/cli.spec.js +++ b/packages/mjml-cli/test/cli.spec.js @@ -20,7 +20,12 @@ import path from 'path' */ const format = input => { if (input) { - return input.toLowerCase().replace(/>/g, ' > ').replace(/</g, ' < ').match(/\S+/g).join('\n') + return input + .toLowerCase() + .replace(/>/g, ' > ') + .replace(/</g, ' < ') + .match(/\S+/g) + .join('\n') } return null diff --git a/packages/mjml-column/README.md b/packages/mjml-column/README.md index 19cec2d0c..ea3d44640 100644 --- a/packages/mjml-column/README.md +++ b/packages/mjml-column/README.md @@ -1,5 +1,10 @@ ## mjml-column +Columns enable you to horizontally organize the content within your sections. They must be located under `mj-section` tags in order to be considered by the engine. +To be responsive, columns are expressed in terms of percentage. + +Every single column has to contain something because they are responsive containers, and will be vertically stacked on a mobile view. + ```xml <mjml> <mj-body> @@ -17,19 +22,16 @@ </mjml> ``` -Columns enable you to horizontally organize the content within your sections. They must be located under `mj-section` tags in order to be considered by the engine. -To be responsive, columns are expressed in terms of percentage. - -Every single column has to contain something because they are responsive containers, and will be vertically stacked on a mobile view. +<p align="center"> + <a href="https://mjml.io/try-it-live/component/column"> + <img width="100px" src="http://imgh.us/TRYITLIVE.svg" alt="sexy" /> + </a> +</p> <aside class="notice"> -Columns are meant to be used as a container for your content. They must not be used as offset. Any mj-element included in a column will have a width equivalent to 100% of this column's width. + Columns are meant to be used as a container for your content. They must not be used as offset. Any mj-element included in a column will have a width equivalent to 100% of this column's width. </aside> -<p align="center"> - <a href="/try-it-live/column"><img width="100px" src="http://imgh.us/TRYITLIVE.svg" alt="sexy" /></a> -</p> - attribute | unit | description | default attributes --------------------|-------------|--------------------------------|-------------------------------------- width | percent/px | column width | (100 / number of columns in section)% diff --git a/packages/mjml-column/package.json b/packages/mjml-column/package.json index d52763b1a..d08e5de1e 100644 --- a/packages/mjml-column/package.json +++ b/packages/mjml-column/package.json @@ -1,6 +1,7 @@ { "name": "mjml-column", - "version": "2.0.1", + "description": "mjml-column", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 7455b63ea..2cae68866 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -64,8 +64,7 @@ class Column extends Component { display: 'inline-block', fontSize: '13px', textAlign: 'left', - verticalAlign: mjAttribute('vertical-align'), - width: '100%' + width: this.getMobileWidth() }, table: { background: mjAttribute('background-color'), @@ -94,6 +93,27 @@ class Column extends Component { } } + getMobileWidth () { + const { mjAttribute, sibling, parentWidth, mobileWidth } = this.props + const width = mjAttribute('width') + + if (mobileWidth != "mobileWidth" ) { + return '100%' + } else if (width == undefined) { + return `${parseInt(100 / sibling)}%` + } + + const { width: parsedWidth, unit } = helpers.widthParser(width) + + switch (unit) { + case '%': + return width + case 'px': + default: + return `${parsedWidth / parentWidth}%` + } + } + render () { const { mjAttribute, children, sibling } = this.props const width = mjAttribute('width') || (100 / sibling) @@ -114,7 +134,7 @@ class Column extends Component { style={this.styles.table} width="100%"> <tbody> - {children} + {children.map(child => React.cloneElement(child, { columnElement: true }))} </tbody> </table> </div> diff --git a/packages/mjml-container/README.md b/packages/mjml-container/README.md index dc0b385f7..e66a7edb9 100644 --- a/packages/mjml-container/README.md +++ b/packages/mjml-container/README.md @@ -1,5 +1,7 @@ ## mjml-container +This is the starting point of your email. + ```xml <mjml> <mj-body> @@ -10,18 +12,17 @@ </mjml> ``` +<p align="center"> + <a href="https://mjml.io/try-it-live/component/container"> + <img width="100px" src="http://imgh.us/TRYITLIVE.svg" alt="sexy" /> + </a> +</p> + <aside class="notice"> MJ-Container was MJ-Body in 1.X </aside> -This is the starting point of your email. - -<p align="center"> - <a href="/try-it-live/body"><img width="100px" src="http://imgh.us/TRYITLIVE.svg" alt="sexy" /></a> -</p> - attribute | unit | description | default value ---------------------|---------------|--------------------------------|--------------- width | px | email's width | 600px background-color | color formats | the general background color | n/a -font-size | px | the general text size | n/a diff --git a/packages/mjml-container/package.json b/packages/mjml-container/package.json index f80825261..5162fe5d5 100644 --- a/packages/mjml-container/package.json +++ b/packages/mjml-container/package.json @@ -1,6 +1,7 @@ { "name": "mjml-container", - "version": "2.0.1", + "description": "mjml-container", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,9 +11,9 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index 922b73eab..0fc71dfc2 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -15,24 +15,28 @@ const postRender = $ => { const containerWidth = $('.mj-container').data('width') $('.mj-container-outlook-open').each(function () { - $(this).replaceWith(`<!--[if mso]> - <table border="0" cellpadding="0" cellspacing="0" width="${containerWidth}" align="center" style="width:${containerWidth}px;"><tr><td> - <![endif]-->`) + $(this).replaceWith(`${helpers.startConditionalTag} + <table border="0" cellpadding="0" cellspacing="0" width="${containerWidth}" align="center" style="width:${containerWidth}px;"> + <tr> + <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"> + ${helpers.endConditionalTag}`) }) $('.mj-container-outlook-line').each(function () { - $(this).replaceWith(`<!--[if mso]> + $(this).replaceWith(`${helpers.startConditionalTag} </td></tr></table> - <![endif]--> - <!--[if mso]> - <table border="0" cellpadding="0" cellspacing="0" width="${containerWidth}" align="center" style="width:${containerWidth}px;"><tr><td> - <![endif]-->`) + ${helpers.endConditionalTag} + ${helpers.startConditionalTag} + <table border="0" cellpadding="0" cellspacing="0" width="${containerWidth}" align="center" style="width:${containerWidth}px;"> + <tr> + <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"> + ${helpers.endConditionalTag}`) }) $('.mj-container-outlook-close').each(function () { - $(this).replaceWith(`<!--[if mso]> + $(this).replaceWith(`${helpers.startConditionalTag} </td></tr></table> - <![endif]-->`) + ${helpers.endConditionalTag}`) }) $('body') @@ -71,18 +75,17 @@ class Container extends Component { styles = this.getStyles() getStyles () { - const { mjAttribute, defaultUnit } = this.props + const { mjAttribute } = this.props return { div: { - backgroundColor: mjAttribute('background-color'), - fontSize: defaultUnit(mjAttribute('font-size')) + backgroundColor: mjAttribute('background-color') } } } render () { - const { renderWrappedOutlookChildren, defaultUnit, mjAttribute, children } = this.props + const { defaultUnit, mjAttribute, children } = this.props const { width } = helpers.widthParser(defaultUnit(mjAttribute('width'))) return ( @@ -91,7 +94,7 @@ class Container extends Component { data-background-color={mjAttribute('background-color')} data-width={width} style={this.styles.div}> - {renderWrappedOutlookChildren(children)} + {children} </div> ) } diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index f6408d5f2..94173ee51 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -1,6 +1,7 @@ { "name": "mjml-core", - "version": "2.0.1", + "description": "mjml-core", + "version": "2.3.0", "main": "lib/index.js", "scripts": { "test": "mocha --compilers js:babel-register" @@ -13,19 +14,25 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", + "browser": { + "cheerio": false, + "fs": false, + "html-minifier": false + }, "dependencies": { "cheerio": "^0.20.0", "classnames": "^2.2.5", "debug": "^2.2.0", - "html-minifier": "^2.1.2", + "he": "^1.1.0", + "html-minifier": "^2.1.5", "immutable": "^3.8.1", - "jquery": "^2.2.3", - "js-beautify": "^1.6.2", - "lodash": "^4.11.2", - "react-dom": "^15.0.2", - "react": "^15.0.2", - "warning": "^2.1.0" + "jquery": "^3.0.0", + "js-beautify": "^1.6.3", + "lodash": "^4.13.1", + "react": "^15.1.0", + "react-dom": "^15.1.0", + "warning": "^3.0.0" }, "browser": { "cheerio": false, @@ -33,6 +40,6 @@ }, "devDependencies": { "chai": "^3.5.0", - "mocha": "^2.4.5" + "mocha": "^2.5.3" } } diff --git a/packages/mjml-core/src/MJMLHead.js b/packages/mjml-core/src/MJMLHead.js new file mode 100644 index 000000000..0bcc88029 --- /dev/null +++ b/packages/mjml-core/src/MJMLHead.js @@ -0,0 +1,7 @@ +export const registerMJHeadElement = (tagName, handler) => { + MJMLHeadElementsCollection[tagName] = handler +} + +const MJMLHeadElementsCollection = {} + +export default MJMLHeadElementsCollection diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 8eccbd1ab..2b2a1e23f 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -1,14 +1,15 @@ -import _ from 'lodash' import { EmptyMJMLError } from './Error' import { fixLegacyAttrs, removeCDATA } from './helpers/postRender' -import { html as beautify } from 'js-beautify' import { parseInstance } from './helpers/mjml' +import cloneDeep from 'lodash/cloneDeep' import defaultContainer from './configs/defaultContainer' import defaultXsd from './configs/defaultXsd' -import documentParser from './parsers/document' -import dom from './helpers/dom' -import elements, { schemaXsds, postRenders, registerMJElement } from './MJMLElementsCollection' -import getFontsImports from './helpers/getFontsImports' +import defaultFonts from './configs/listFontsImports' +import he from 'he' +import importFonts from './helpers/importFonts' +import includeExternal from './includeExternal' +import isEmpty from 'lodash/isEmpty' +import MJMLElementsCollection, { schemaXsds, postRenders, registerMJElement } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' import React from 'react' import ReactDOMServer from 'react-dom/server' @@ -23,7 +24,14 @@ export default class MJMLRenderer { this.registerDotfile() } - this.content = content + this.attributes = { + container: defaultContainer(), + defaultAttributes: {}, + cssClasses: {}, + fonts: cloneDeep(defaultFonts) + } + + this.content = includeExternal(content) this.options = options if (typeof this.content === 'string') { @@ -33,9 +41,15 @@ export default class MJMLRenderer { registerDotfile () { const fs = require('fs') + const path = process.cwd() + + try { + fs.statSync(`${path}/.mjmlconfig`) + } catch (e) { + return warning(!isEmpty(MJMLElementsCollection), `No .mjmlconfig found in path ${path}, consider to add one`) + } try { - const path = process.cwd() const mjmlConfig = JSON.parse(fs.readFileSync(`${path}/.mjmlconfig`).toString()) const { packages } = mjmlConfig @@ -48,25 +62,45 @@ export default class MJMLRenderer { const Component = require.main.require(file) registerMJElement(Component.default || Component) } catch (e) { - warning(false, `.mjmlconfig file ${file} has an error : ${e}`) + warning(false, `.mjmlconfig file ${file} opened from ${path} has an error : ${e}`) } }) } catch (e) { - warning(!_.isEmpty(elements), 'No .mjmlconfig found in path, please consider to add one') + warning(false, `.mjmlconfig has a ParseError: ${e}`) } } parseDocument () { + const documentParser = require('./parsers/document').default + debug('Start parsing document') - this.content = documentParser(this.content) - this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(elements)).join(`\n`)) + this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElementsCollection)).join(`\n`)) + this.content = documentParser(this.content, this.attributes) debug('Content parsed') - console.log('this.schemaXsd', this.schemaXsd) + } + + render () { + if (!this.content) { + throw new EmptyMJMLError(`.render: No MJML to render in options ${this.options.toString()}`) + } + + const rootElemComponent = React.createElement(MJMLElementsCollection[this.content.tagName], { mjml: parseInstance(this.content, this.attributes ) }) + + debug('Render to static markup') + const renderedMJML = ReactDOMServer.renderToStaticMarkup(rootElemComponent) + + debug('React rendering done. Continue with special overrides.') + const MJMLDocument = this.attributes.container.replace('__content__', renderedMJML) + + return this.postRender(MJMLDocument) } postRender (MJMLDocument) { + const dom = require('./helpers/dom').default + let $ = dom.parseHTML(MJMLDocument) + importFonts({ $, fonts: this.attributes.fonts }) $ = fixLegacyAttrs($) postRenders.forEach(postRender => { @@ -78,7 +112,9 @@ export default class MJMLRenderer { let finalMJMLDocument = dom.getHTML($) finalMJMLDocument = removeCDATA(finalMJMLDocument) - if (this.options.beautify && beautify) { + if (this.options.beautify) { + const beautify = require('js-beautify').html + finalMJMLDocument = beautify(finalMJMLDocument, { indent_size: 2, wrap_attributes_indent_size: 2 @@ -95,29 +131,9 @@ export default class MJMLRenderer { }) } - return finalMJMLDocument - } - - render () { - if (!this.content) { - throw new EmptyMJMLError(`.render: No MJML to render in options ${this.options.toString()}`) - } - - const rootElemComponent = React.createElement(elements[this.content.tagName], { mjml: parseInstance(this.content) }) + finalMJMLDocument = he.decode(finalMJMLDocument) - debug('Render to static markup') - const renderedMJML = ReactDOMServer.renderToStaticMarkup(rootElemComponent) - - debug('React rendering done. Continue with special overrides.') - - const MJMLDocument = defaultContainer({ - title: this.options.title, - content: renderedMJML, - fonts: getFontsImports({ content: renderedMJML }) - }) - - return this.postRender(MJMLDocument) + return finalMJMLDocument } - } diff --git a/packages/mjml-core/src/configs/defaultContainer.js b/packages/mjml-core/src/configs/defaultContainer.js index 1c3321c6d..5e3934337 100644 --- a/packages/mjml-core/src/configs/defaultContainer.js +++ b/packages/mjml-core/src/configs/defaultContainer.js @@ -1,25 +1,31 @@ import defaultStyle from './defaultStyle' -export default (options = {}) => { - const { title = '', content = '', fonts = {} } = options - +export default () => { return (`<!doctype html> -<html xmlns="http://www.w3.org/1999/xhtml"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"> <head> + <title> -${title} -${fonts.import || ''} + ${fonts.link || ''} + + - ${content} + __content__ `) } diff --git a/packages/mjml-core/src/decorators/MJMLElement.js b/packages/mjml-core/src/decorators/MJMLElement.js index 59b83c6d7..c1866f9ea 100644 --- a/packages/mjml-core/src/decorators/MJMLElement.js +++ b/packages/mjml-core/src/decorators/MJMLElement.js @@ -4,6 +4,7 @@ import MJMLElementsCollection from '../MJMLElementsCollection' import React, { Component } from 'react' import ReactDOMServer from 'react-dom/server' import trim from 'lodash/trim' +import merge from 'lodash/merge' import warning from 'warning' const getElementWidth = ({ element, siblings, parentWidth }) => { @@ -35,6 +36,12 @@ let siblingCount = 1 function createComponent (ComposedComponent) { + const baseStyles = { + td: { + wordBreak: 'break-word' + } + } + class MJMLElement extends Component { constructor (props) { @@ -45,7 +52,23 @@ function createComponent (ComposedComponent) { mjAttribute = name => this.mjml.getIn(['attributes', name]) - mjName = () => this.constructor.tagName + getStyles () { + return merge({}, baseStyles, { + td: { + background: this.mjAttribute('container-background-color'), + fontSize: '0px', + padding: defaultUnit(this.mjAttribute('padding'), 'px'), + paddingTop: defaultUnit(this.mjAttribute('padding-top'), 'px'), + paddingBottom: defaultUnit(this.mjAttribute('padding-bottom'), 'px'), + paddingRight: defaultUnit(this.mjAttribute('padding-right'), 'px'), + paddingLeft: defaultUnit(this.mjAttribute('padding-left'), 'px') + } + }) + } + + mjName = () => { + return this.constructor.tagName + } mjContent = () => { const content = this.mjml.get('content') @@ -73,6 +96,7 @@ function createComponent (ComposedComponent) { isInheritedAttributes = name => this.mjml.get('inheritedAttributes') && this.mjml.get('inheritedAttributes').includes(name) getWidth = () => this.mjAttribute('rawPxWidth') || this.mjAttribute('width') + getParentWidth = () => this.mjAttribute('parentWidth') renderWrappedOutlookChildren = children => { children = React.Children.toArray(children) @@ -130,9 +154,9 @@ function createComponent (ComposedComponent) { return wrappedElements } - paddingParser = direction => { - const paddingDirection = this.mjAttribute(`padding-${direction}`) - const padding = this.mjAttribute('padding') + paddingParser = (direction, prefix = '') => { + const paddingDirection = this.mjAttribute(`${prefix}padding-${direction}`) + const padding = this.mjAttribute(`${prefix}padding`) if (typeof paddingDirection !== 'undefined') { return parseInt(paddingDirection) @@ -170,7 +194,7 @@ function createComponent (ComposedComponent) { } return parentMjml.get('children').map((mjml, i) => { - const childMjml = mjml.setIn(['attributes', 'parentWidth'], this.mjAttribute('rawPxWidth')) + const childMjml = mjml.setIn(['attributes', 'parentWidth'], this.getWidth()) const tag = childMjml.get('tagName') const Element = MJMLElementsCollection[tag] @@ -199,7 +223,7 @@ function createComponent (ComposedComponent) { ] // assign sibling count for element and children - if (parentMjml && this.mjName() === 'mj-column') { + if (parentMjml) { siblingCount = parentMjml.get('children').size } @@ -217,7 +241,7 @@ function createComponent (ComposedComponent) { // siblings count, can change display sibling: siblingCount, - parentWidth: this.getWidth(), + parentWidth: this.getParentWidth(), getPadding: this.paddingParser, defaultUnit, @@ -230,44 +254,26 @@ function createComponent (ComposedComponent) { } } - isColumnElement = () => this.constructor.parentTag === 'mj-column' + render () { + if (this.props.columnElement) { + this.styles = this.getStyles() - renderColumnContainer () { - const styles = { - td: { - background: this.mjAttribute('container-background-color'), - fontSize: '1px', - padding: defaultUnit(this.mjAttribute('padding')), - paddingBottom: defaultUnit(this.mjAttribute('padding-bottom')), - paddingLeft: defaultUnit(this.mjAttribute('padding-left')), - paddingRight: defaultUnit(this.mjAttribute('padding-right')), - paddingTop: defaultUnit(this.mjAttribute('padding-top')), - textAlign: this.mjAttribute('align'), - verticalAlign: this.mjAttribute('vertical-align'), - wordBreak: 'break-word' - } + return ( + + + + + + ) } - return ( - - - - - - ) - } - - renderDefaultContainer () { return ( ) } - - render () { - return this.isColumnElement() ? this.renderColumnContainer() : this.renderDefaultContainer() - } } return MJMLElement diff --git a/packages/mjml-core/src/helpers/getFontsImports.js b/packages/mjml-core/src/helpers/getFontsImports.js deleted file mode 100644 index 4b2f29c5c..000000000 --- a/packages/mjml-core/src/helpers/getFontsImports.js +++ /dev/null @@ -1,37 +0,0 @@ -import listFontsImports from '../configs/listFontsImports' - -const buildTags = toImport => { - const tags = {} - - if (!toImport.length) { - return tags - } - - tags.link = `\n${toImport.map(url => ``).join('\n')}` - tags.import = `\n` - - return tags -} - -export default (options = {}) => { - const { importAll, content } = options - - if (importAll) { - return buildTags(listFontsImports.map(font => font.url)) - } - - const toImport = [] - - listFontsImports.forEach(font => { - const { name, url } = font - const regex = new RegExp(`"[^"]*font-family:[^"]*${name}[^"]*"`) - - if (content.match(regex)) { - toImport.push(url) - } - }) - - return buildTags(toImport) -} diff --git a/packages/mjml-core/src/helpers/html.js b/packages/mjml-core/src/helpers/html.js new file mode 100644 index 000000000..7c4e157f6 --- /dev/null +++ b/packages/mjml-core/src/helpers/html.js @@ -0,0 +1,2 @@ +export const startConditionalTag = '' diff --git a/packages/mjml-core/src/helpers/importFonts.js b/packages/mjml-core/src/helpers/importFonts.js new file mode 100644 index 000000000..3a9993421 --- /dev/null +++ b/packages/mjml-core/src/helpers/importFonts.js @@ -0,0 +1,32 @@ +import dom from '../helpers/dom' + +const buildTags = ($, toImport) => { + if (!toImport.length) { + return + } + + $('head').append(`\n + ${toImport.map(url => ``).join('\n')} + + `) +} + +export default (options = {}) => { + const { fonts, $ } = options + const content = dom.getHTML($) + + const toImport = [] + + fonts.forEach(font => { + const { name, url } = font + const regex = new RegExp(`"[^"]*font-family:[^"]*${name}[^"]*"`) + + if (content.match(regex)) { + toImport.push(url) + } + }) + + return buildTags($, toImport) +} diff --git a/packages/mjml-core/src/helpers/index.js b/packages/mjml-core/src/helpers/index.js index 06b2ebffb..d23322c97 100644 --- a/packages/mjml-core/src/helpers/index.js +++ b/packages/mjml-core/src/helpers/index.js @@ -1 +1,2 @@ -export { widthParser, defaultUnit } from './mjAttribute' +export * from './mjAttribute' +export * from './html' diff --git a/packages/mjml-core/src/helpers/mjAttribute.js b/packages/mjml-core/src/helpers/mjAttribute.js index 99c80e400..b7917944b 100644 --- a/packages/mjml-core/src/helpers/mjAttribute.js +++ b/packages/mjml-core/src/helpers/mjAttribute.js @@ -7,12 +7,16 @@ export const widthParser = width => { } export const defaultUnit = (units, defaultUnit = 'px') => { - if (!units || units === '') { - return units + if (units === undefined || units === '' || units === null) { + return undefined } - return units.toString().split(' ').map(unit => { - const parsedUnit = unitRegex.exec(unit.toString())[1] - return parsedUnit ? unit : unit.toString() + defaultUnit - }).join(' ') + return units + .toString() + .split(' ') + .map(unit => { + const parsedUnit = unitRegex.exec(unit.toString())[1] + return parsedUnit ? unit : unit.toString() + defaultUnit + }) + .join(' ') } diff --git a/packages/mjml-core/src/helpers/mjml.js b/packages/mjml-core/src/helpers/mjml.js index 67cb6996f..d580c06c4 100644 --- a/packages/mjml-core/src/helpers/mjml.js +++ b/packages/mjml-core/src/helpers/mjml.js @@ -1,14 +1,22 @@ import { fromJS } from 'immutable' import defaultsDeep from 'lodash/defaultsDeep' -import MJMLElementsCollection from '../MJMLElementsCollection' +import merge from 'lodash/merge' +import MJMLElementsCollection from '../MJMLElementsCollection' -export const parseInstance = instance => { +export const parseInstance = (instance, attributes) => { const parseNode = (node) => { - const Component = MJMLElementsCollection[node.tagName] + node['attributes'] = node['attributes'] || {} + + const Component = MJMLElementsCollection[node.tagName] + const nodeClasses = node['attributes']['mj-class'] + + const classAttributes = !nodeClasses ? {} : merge({}, ...nodeClasses.split(' ').map(nodeClass => { + return { attributes: attributes.cssClasses[nodeClass] } + })) return !Component ? {} : { // copy all existing props, applying defaults - ...defaultsDeep(node, Component.defaultMJMLDefinition), + ...defaultsDeep(node, classAttributes, { attributes: attributes.defaultAttributes[node.tagName] }, { attributes: attributes.defaultAttributes["mj-all"] } || {}, Component.defaultMJMLDefinition), // do same to children children: (node.children || []).map(parseNode) } diff --git a/packages/mjml-core/src/includeExternal.js b/packages/mjml-core/src/includeExternal.js new file mode 100644 index 000000000..f87765f3d --- /dev/null +++ b/packages/mjml-core/src/includeExternal.js @@ -0,0 +1,20 @@ + +import fs from 'fs' + +const includes = /|>\s*<\/mj-include>)/g + +const getContent = input => + input + .replace(/[\n\s\t]+[\n\s\t]+/, '') + .replace(/<\/mj-container>[\n\s\t]+<\/mj-body>[\n\s\t]+<\/mjml>/, '') + +export default mjml => mjml.replace(includes, (_, path) => { + const mjmlExtension = file => file.trim().match(/.mjml$/) && file || `${file}.mjml` + + const template = fs.readFileSync(mjmlExtension(path), 'utf8') + const content = getContent(template) + + if (!content) { throw new Error(`Error while parsing file: ${path}`) } + + return content +}) diff --git a/packages/mjml-core/src/index.js b/packages/mjml-core/src/index.js index 7f7602cc5..0cd4e89c0 100644 --- a/packages/mjml-core/src/index.js +++ b/packages/mjml-core/src/index.js @@ -2,11 +2,17 @@ import warning from 'warning' import MJMLRenderer from './MJMLRenderer' import elements, { registerMJElement } from './MJMLElementsCollection' +import MJMLHeadElements, { registerMJHeadElement } from './MJMLHead' import * as helpers from './helpers' -export documentParser from './parsers/document' export MJMLElement from './decorators/MJMLElement' -export { MJMLRenderer, registerMJElement, elements, helpers } +export { MJMLRenderer, registerMJElement, elements, helpers, registerMJHeadElement, MJMLHeadElements } +export const documentParser = content => { + const documentParser = require('./parsers/document').default + + return documentParser(content) +} +export const version = () => '__MJML_VERSION__' export const mjml2html = (mjml, options = {}) => new MJMLRenderer(mjml, options).render() export const registerElement = Component => { warning(false, 'Please now use registerMJElement, registerElement is deprecated will no longer be supported soon') diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 49ef5d0c6..a67e45b21 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -1,20 +1,32 @@ import { ParseError, EmptyMJMLError, NullElementError } from '../Error' import compact from 'lodash/compact' import dom from '../helpers/dom' +import each from 'lodash/each' import filter from 'lodash/filter' import MJMLElements, { endingTags } from '../MJMLElementsCollection' +import MJMLHeadElements from '../MJMLHead' import warning from 'warning' +const regexTag = tag => new RegExp(`<${tag}([^>]*)>([^]*?)`, 'gmi') + /** * Avoid htmlparser to parse ending tags */ const safeEndingTags = content => { + const regexpBody = regexTag('mj-body') + let bodyContent = content.match(regexpBody) + + if (!bodyContent) { + return content + } + + bodyContent = bodyContent[0] + endingTags.forEach(tag => { - const regex = new RegExp(`<${tag}([^>]*)>([^]*?)`, 'gmi') - content = content.replace(regex, dom.replaceContentByCdata(tag)) + bodyContent = bodyContent.replace(regexTag(tag), dom.replaceContentByCdata(tag)) }) - return content + return content.replace(regexpBody, bodyContent) } /** @@ -46,18 +58,36 @@ const mjmlElementParser = elem => { return element } +const parseHead = (head, attributes) => { + const $container = dom.parseHTML(attributes.container) + + each(compact(filter(dom.getChildren(head), child => child.tagName)), element => { + const handler = MJMLHeadElements[element.tagName.toLowerCase()] + + if (handler) { + handler(element, { $container, ...attributes }) + } else { + warning(false, `No handler found for: ${element.tagName}, in mj-head, skipping it`) + } + }) + + attributes.container = dom.getHTML($container) +} + /** * Import an html document containing some mjml * returns JSON * - container: the mjml container * - mjml: a json representation of the mjml */ -const documentParser = content => { +const documentParser = (content, attributes) => { let root + let head try { const $ = dom.parseXML(safeEndingTags(content)) - root = $('mjml mj-body') + root = $('mjml > mj-body') + head = $('mjml > mj-head') if (root.length < 1) { root = $('mj-body').get(0) @@ -73,6 +103,10 @@ const documentParser = content => { throw new EmptyMJMLError('No root "" or "" found in the file') } + if (head && head.length === 1) { + parseHead(head.get(0), attributes) + } + return mjmlElementParser(root) } diff --git a/packages/mjml-divider/README.md b/packages/mjml-divider/README.md index ae1ec6209..2544b7d59 100644 --- a/packages/mjml-divider/README.md +++ b/packages/mjml-divider/README.md @@ -1,5 +1,7 @@ ## mjml-divider +Displays a horizontal divider that can be customized like a HTML border. + ```xml @@ -14,11 +16,10 @@ ``` -Displays a horizontal divider that can be customized like a HTML border. - -

- sexy + + sexy +

attribute | unit | description | default value diff --git a/packages/mjml-divider/package.json b/packages/mjml-divider/package.json index 0c5dab75a..23c81d917 100644 --- a/packages/mjml-divider/package.json +++ b/packages/mjml-divider/package.json @@ -1,6 +1,7 @@ { "name": "mjml-divider", - "version": "2.0.2", + "description": "mjml-divider", + "version": "2.0.8", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-divider/src/index.js b/packages/mjml-divider/src/index.js index ec9fbfa0c..0af8444e8 100644 --- a/packages/mjml-divider/src/index.js +++ b/packages/mjml-divider/src/index.js @@ -34,7 +34,7 @@ const postRender = $ => { $(this) .removeAttr('data-divider-width') .removeAttr('class') - .after(``) + .after(`${helpers.startConditionalTag}${insertNode}${helpers.endConditionalTag}`) }) return $ diff --git a/packages/mjml-invoice-item/.npmignore b/packages/mjml-group/.npmignore similarity index 100% rename from packages/mjml-invoice-item/.npmignore rename to packages/mjml-group/.npmignore diff --git a/packages/mjml-group/README.md b/packages/mjml-group/README.md new file mode 100644 index 000000000..9ebee0782 --- /dev/null +++ b/packages/mjml-group/README.md @@ -0,0 +1,65 @@ +## mjml-group + + +

+ Desktop
+ +

+ +

+ Mobile
+ +

+ +MJ-Group allows you to handle how columns are displayed on mobile. Allowing 2 or more columns in mobile. + +```xml + + + + + + + + +

Easy and quick

+

Write less code, save time and code more efficiently with MJML’s semantic syntax.

+
+
+ + + +

Responsive

+

MJML is responsive by design on most-popular email clients, even Outlook.

+
+
+
+
+
+
+
+``` + +

+ sexy +

+ + + + + + + + + +attribute | unit | description | default attributes +--------------------|-------------|--------------------------------|-------------------------------------- +width | percent/px | group width | (100 / number of sibling in section)% +vertical-align | string | middle/top/bottom | top +background-color | string | background color for a group | n/a diff --git a/packages/mjml-invoice-item/package.json b/packages/mjml-group/package.json similarity index 70% rename from packages/mjml-invoice-item/package.json rename to packages/mjml-group/package.json index b6534220c..752db702d 100644 --- a/packages/mjml-invoice-item/package.json +++ b/packages/mjml-group/package.json @@ -1,5 +1,6 @@ { - "name": "mjml-invoice-item", + "name": "mjml-group", + "description": "mjml-group", "version": "2.0.1", "main": "lib/index.js", "repository": { @@ -12,8 +13,8 @@ }, "homepage": "https://github.com/mjmlio/mjml", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-group/src/index.js b/packages/mjml-group/src/index.js new file mode 100644 index 000000000..088af2344 --- /dev/null +++ b/packages/mjml-group/src/index.js @@ -0,0 +1,115 @@ +import { MJMLElement, helpers } from 'mjml-core' +import merge from 'lodash/merge' +import React, { Component } from 'react' + +const tagName = 'mj-group' +const baseStyles = { + div: { + verticalAlign: 'top' + } +} +const postRender = $ => { + $('.mj-group-outlook-open').each(function () { + const $columnDiv = $(this).next() + + $(this).replaceWith(`${helpers.startConditionalTag} +
+ ${helpers.endConditionalTag}`) + + $columnDiv.removeAttr('data-vertical-align') + }) + + $('.mj-group-outlook-line').each(function () { + const $columnDiv = $(this).next() + + $(this).replaceWith(`${helpers.startConditionalTag} + + ${helpers.endConditionalTag}`) + + $columnDiv.removeAttr('data-vertical-align') + }) + + $('.mj-group-outlook-close').each(function () { + $(this).replaceWith(`${helpers.startConditionalTag} +
+ ${helpers.endConditionalTag}`) + }) + + return $ +} + +@MJMLElement +class Group extends Component { + + styles = this.getStyles() + + getStyles () { + const { mjAttribute } = this.props + + return merge({}, baseStyles, { + div: { + display: 'inline-block', + verticalAlign: mjAttribute('vertical-align'), + fontSize: '0px', + lineHeight: '0px', + textAlign: 'left', + width: '100%' + }, + table: { + verticalAlign: mjAttribute('vertical-align'), + background: mjAttribute('background-color') + } + }) + } + + getGroupClass () { + const { mjAttribute, sibling } = this.props + const width = mjAttribute('width') + const parentWidth = this.props.parentWidth || mjAttribute('parentWidth') + + if (width == undefined) { + return `mj-column-per-${parseInt(100 / sibling)}` + } + + const { width: parsedWidth, unit } = helpers.widthParser(width) + + switch (unit) { + case '%': + return `mj-column-per-${parsedWidth}` + + case 'px': + const percentWidth = parseInt(width) / parentWidth * 100 + return `mj-column-per-${percentWidth}` + } + } + + renderChildren () { + const { children } = this.props + + return children.map(child => React.cloneElement(child, { mobileWidth: "mobileWidth" })) + } + + render () { + const { mjAttribute, sibling, renderWrappedOutlookChildren } = this.props + const width = mjAttribute('width') || (100 / sibling) + const mjGroupClass = this.getGroupClass() + + return ( +
+ {renderWrappedOutlookChildren(this.renderChildren())} +
+ ) + } + +} + +Group.tagName = tagName +Group.baseStyles = baseStyles +Group.postRender = postRender + +export default Group diff --git a/packages/mjml-head-attributes/.npmignore b/packages/mjml-head-attributes/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-head-attributes/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-head-attributes/README.md b/packages/mjml-head-attributes/README.md new file mode 100644 index 000000000..343adb916 --- /dev/null +++ b/packages/mjml-head-attributes/README.md @@ -0,0 +1,36 @@ +## mjml-head-attributes + +This tag allows you to modify default attributes on a `mj-tag` and add `mj-class` to them. + + ```xml + + + + + + + + + + + + + + Hello World! + + + + + + + ``` + +

+ + sexy + +

+ + diff --git a/packages/mjml-head-attributes/package.json b/packages/mjml-head-attributes/package.json new file mode 100644 index 000000000..0317f4d6f --- /dev/null +++ b/packages/mjml-head-attributes/package.json @@ -0,0 +1,18 @@ +{ + "name": "mjml-head-attributes", + "description": "mjml-head-attributes", + "version": "2.0.5", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io", + "dependencies": { + "lodash": "^4.13.1" + } +} diff --git a/packages/mjml-head-attributes/src/index.js b/packages/mjml-head-attributes/src/index.js new file mode 100644 index 000000000..bed0fb466 --- /dev/null +++ b/packages/mjml-head-attributes/src/index.js @@ -0,0 +1,17 @@ +import compact from 'lodash/compact' +import each from 'lodash/each' +import filter from 'lodash/filter' +import omit from 'lodash/omit' + +export default ($, { defaultAttributes, cssClasses }) => { + each(compact(filter($.children, child => child.tagName)), elem => { + const tagName = elem.tagName.toLowerCase() + const attributes = elem.attribs + + if (tagName === 'mj-class') { + return cssClasses[attributes.name] = omit(attributes, ['name']) + } + + defaultAttributes[tagName] = attributes + }) +} diff --git a/packages/mjml-head-font/.npmignore b/packages/mjml-head-font/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-head-font/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-head-font/README.md b/packages/mjml-head-font/README.md new file mode 100644 index 000000000..fb82072f8 --- /dev/null +++ b/packages/mjml-head-font/README.md @@ -0,0 +1,28 @@ +## mjml-head-font + +This tag allows you to import fonts if used in your MJML document + + ```xml + + + + + + + + + + Hello World! + + + + + + + ``` + +

+ + sexy + +

diff --git a/packages/mjml-head-font/package.json b/packages/mjml-head-font/package.json new file mode 100644 index 000000000..26a38fc0f --- /dev/null +++ b/packages/mjml-head-font/package.json @@ -0,0 +1,15 @@ +{ + "name": "mjml-head-font", + "description": "mjml-head-font", + "version": "2.0.0", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io" +} diff --git a/packages/mjml-head-font/src/index.js b/packages/mjml-head-font/src/index.js new file mode 100644 index 000000000..d7681fa3e --- /dev/null +++ b/packages/mjml-head-font/src/index.js @@ -0,0 +1,3 @@ +export default (el, { fonts }) => { + fonts.push({ name: el.attribs.name, url: el.attribs.href }) +} diff --git a/packages/mjml-head-title/.npmignore b/packages/mjml-head-title/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-head-title/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-head-title/README.md b/packages/mjml-head-title/README.md new file mode 100644 index 000000000..3ed3468da --- /dev/null +++ b/packages/mjml-head-title/README.md @@ -0,0 +1,28 @@ +## mjml-head-title + +This tag allows you to set the title of an MJML document + + ```xml + + + Hello MJML + + + + + + + Hello World! + + + + + + + ``` + +

+ + sexy + +

diff --git a/packages/mjml-head-title/package.json b/packages/mjml-head-title/package.json new file mode 100644 index 000000000..b0df79baa --- /dev/null +++ b/packages/mjml-head-title/package.json @@ -0,0 +1,15 @@ +{ + "name": "mjml-head-title", + "description": "mjml-head-title", + "version": "2.0.0", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io" +} diff --git a/packages/mjml-head-title/src/index.js b/packages/mjml-head-title/src/index.js new file mode 100644 index 000000000..5a6584e5f --- /dev/null +++ b/packages/mjml-head-title/src/index.js @@ -0,0 +1,5 @@ +export default (el, { $container }) => { + const innerText = el.children.map(child => child.type === 'text' && child.data).join('') + + $container('title').text(innerText) +} diff --git a/packages/mjml-hero/.npmignore b/packages/mjml-hero/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-hero/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-hero/README.md b/packages/mjml-hero/README.md new file mode 100644 index 000000000..a0702ba98 --- /dev/null +++ b/packages/mjml-hero/README.md @@ -0,0 +1,145 @@ +## mjml-hero + +Display a section with a background image and some content inside (mj-text, mj-button, mj-image ...) wrapped in mj-hero-content component + +Fixed height + +

+ +

+ +```xml + + + + + + + + GO TO SPACE + + + ORDER YOUR TICKET NOW + + + + + + + ``` + +

+ + sexy + +

+ +Fluid height + +

+ +

+ +```xml + + + + + + + + GO TO SPACE + + + ORDER YOUR TICKET NOW + + + + + + +``` + +

+ + sexy + +

+ + + + + + + + + +attribute | unit | description | default value +--------------------|-------------------------------------|----------------------------------------------------------------------|-------------- +width | px | hero container width | parent element width +mode | fluid-height/fixed-height | choose if the height is fixed based on the height attribute or fluid | fluid-height +height | px | hero section height (required for fixed-height mode) | 0px +background-width | px | width of the image used | 0px +background-height | px | height of the image used | 0px +background-url | url | absolute background url | n/a +background-color | color | hero background color | #ffffff +background-position | top/center/bottom left/center/right | background image position | center center +padding | px | supports up to 4 parameters | 0px +padding-top | px | top offset | 0px +padding-right | px | right offset | 0px +padding-left | px | left offset | 0px +padding-bottom | px | bottom offset | 0px + +### mjml-hero-content + +Display some content in an `mj-hero` component + + + +attribute | unit | description | default value +-----------------|-------------------|------------------------------------------------|------------------------------ +width | px/percent | content width | 100% +align | left/center/right | horizontal alignment | center +background-color | color | content background color | transparent +padding | px | supports up to 4 parameters | 0px +padding-top | px | top offset | 0px +padding-right | px | right offset | 0px +padding-left | px | left offset | 0px +padding-bottom | px | bottom offset | 0px diff --git a/packages/mjml-hero/package.json b/packages/mjml-hero/package.json new file mode 100644 index 000000000..704f7b7e4 --- /dev/null +++ b/packages/mjml-hero/package.json @@ -0,0 +1,20 @@ +{ + "name": "mjml-hero", + "description": "mjml-hero", + "version": "2.0.7", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io", + "dependencies": { + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" + } +} diff --git a/packages/mjml-hero/src/Hero.js b/packages/mjml-hero/src/Hero.js new file mode 100644 index 000000000..d93dc76f2 --- /dev/null +++ b/packages/mjml-hero/src/Hero.js @@ -0,0 +1,258 @@ +import { MJMLElement, helpers } from 'mjml-core' +import React, { Component } from 'react' +import merge from 'lodash/merge' + +const tagName = 'mj-hero' +const defaultMJMLDefinition = { + attributes: { + 'mode': 'fixed-height', + 'height': '0px', + 'background-width': '0px', + 'background-height': '0px', + 'background-position': 'center center', + 'padding': '0px', + 'background-color': '#ffffff' + } +} + +const endingTag = false + +const baseStyles = { + div: { + margin: '0 auto' + }, + table: { + width: '100%' + }, + tr: { + verticalAlign: 'top' + }, + edge: { + paddingBottom: '0%', + width: '0.01%', + msoPaddingBottomAlt: 0 + }, + hero: { + backgroundRepeat: 'no-repeat', + verticalAlign: 'top', + backgroundSize: 'cover' + } +} + +const postRender = $ => { + let backgroundCropTop = 0 + let backgroundCropBottom = 0 + let dataCrop = '' + let $element + let backgroundWidth = 0 + let backgroundHeight = 0 + let backgroundUrl = '' + + $('.mj-hero-outlook').each(function () { + backgroundUrl = $(this).data('background-url') + backgroundWidth = $(this).data('background-width') + backgroundHeight = $(this).data('background-height') + backgroundCropTop = 0 + backgroundCropBottom = 0 + dataCrop = '' + + $element = $(this).find('.mj-hero-fixed-height').first() + + if ($element.length) { + dataCrop = $element.data('crop') + backgroundHeight = $element.data('background-height') + + backgroundCropTop = dataCrop.split(', ')[0].split(':')[1] + backgroundCropBottom = dataCrop.split(', ')[1].split(':')[1] + + $element + .removeAttr('class') + .removeAttr('data-background-height') + .removeAttr('data-crop') + } + + backgroundWidth = parseInt(backgroundWidth.replace('px', '')) * 0.75 + backgroundHeight = parseInt(backgroundHeight.replace('px', '')) * 0.75 + + $(this) + .before(`${helpers.startConditionalTag} + + ${helpers.endConditionalTag}`) + .removeAttr('class') + .removeAttr('data-background-width') + .removeAttr('data-background-height') + .removeAttr('data-background-url') + }) + + return $ +} + +@MJMLElement +class Hero extends Component { + + styles = this.getStyles() + + getFixedHeight () { + const { mjAttribute, getPadding } = this.props + let height = 0 + let paddingTop = 0 + let paddingBottom = 0 + + height = parseInt(mjAttribute('height').replace('px', '')) + paddingTop = Math.abs( + getPadding('top') + ) + paddingBottom = Math.abs( + getPadding('bottom') + ) + + return height - paddingTop - paddingBottom + } + + getBackgroundCrop () { + const { mjAttribute } = this.props + const height = parseInt(mjAttribute('height').replace('px', '')) + const backgroundHeight = parseInt(mjAttribute('background-height').replace('px', '')) + const backgroundPositionTop = mjAttribute('background-position').trim().split(' ')[0] || 'center' + let cropTop = 0 + let cropBottom = 0 + + if (height < backgroundHeight) { + switch (backgroundPositionTop) { + case 'top': + cropBottom = Math.round((backgroundHeight - height) / backgroundHeight * 100) / 100 + break; + case 'center': + cropTop = Math.round((backgroundHeight - height) / 2 / backgroundHeight * 100) / 100 + cropBottom = cropTop + break + case 'bottom': + cropTop = Math.round((backgroundHeight - height) / backgroundHeight * 100) / 100 + break; + } + } + + return `croptop:${cropTop}, cropbottom:${cropBottom}` + } + + getBackgroundRatio () { + const { mjAttribute } = this.props + const backgroundWidth = parseInt(mjAttribute('background-width').replace('px', '')) + const backgroundHeight = parseInt(mjAttribute('background-height').replace('px', '')) + + return Math.round((backgroundHeight / backgroundWidth * 100) * 10000) / 10000 + } + + getBackgroundStyle () { + const { mjAttribute } = this.props + let background = '' + + background += `${mjAttribute('background-color') || ''} ` + background += mjAttribute('background-url') ? `url(${mjAttribute('background-url')}) ` : '' + background += 'no-repeat ' + background += `${mjAttribute('background-position')} ` + background += '/ cover' + + return background + } + + getStyles () { + const { mjAttribute, getPadding, defaultUnit, parentWidth } = this.props + const backgroundRatio = this.getBackgroundRatio() + const backgroundStyle = this.getBackgroundStyle() + let width = parentWidth + + if (mjAttribute('width')) { + width = mjAttribute('width') + } + + return merge({}, baseStyles, { + div: { + maxWidth: defaultUnit(width, 'px') + }, + edge: { + paddingBottom: `${backgroundRatio}%` + }, + hero: { + background: backgroundStyle, + paddingTop: getPadding('top'), + paddingLeft: getPadding('left'), + paddingRight: getPadding('right'), + paddingBottom: getPadding('bottom'), + backgroundPosition: mjAttribute('background-position') + } + }) + } + + isFixedHeight () { + const { mjAttribute } = this.props + + return mjAttribute('mode') == 'fixed-height' + } + + renderFixedHeight () { + const { mjAttribute, children, defaultUnit } = this.props + + return ( + + + {children} + + + ) + } + + renderFluidHeight () { + const { mjAttribute, children } = this.props + + return ( + + + + {children} + + + + ) + } + + render () { + const { mjAttribute, defaultUnit } = this.props + + return ( +
+ + + {this.isFixedHeight() ? this.renderFixedHeight() : this.renderFluidHeight()} + +
+
+ ) + } + +} + +Hero.tagName = tagName +Hero.defaultMJMLDefinition = defaultMJMLDefinition +Hero.endingTag = endingTag +Hero.baseStyles = baseStyles +Hero.postRender = postRender + +export default Hero diff --git a/packages/mjml-hero/src/HeroContent.js b/packages/mjml-hero/src/HeroContent.js new file mode 100644 index 000000000..16df54847 --- /dev/null +++ b/packages/mjml-hero/src/HeroContent.js @@ -0,0 +1,118 @@ +import { MJMLElement, helpers } from 'mjml-core' +import React, { Component } from 'react' +import merge from 'lodash/merge' + +const tagName = 'mj-hero-content' +const defaultMJMLDefinition = { + attributes: { + 'width': '100%', + 'padding': '0px', + 'align': 'center', + 'background-color': 'transparent' + } +} +const endingTag = false + +const baseStyles = { + div: { + float: 'center' + }, + table: { + width: '100%', + margin: '0px' + } +} + +const postRender = $ => { + const $mjHeroContent = $('.mj-hero-content') + + $mjHeroContent.each(function () { + const width = $(this).css('width') + const align = $(this).data('align') + const backgroundColor = $(this).data('background-color') + + $(this).before(`${helpers.startConditionalTag} +
+ ${helpers.endConditionalTag}`) + .after(`${helpers.startConditionalTag} +
+ ${helpers.endConditionalTag}`) + .removeAttr('data-background-color') + .removeAttr('data-align') + }) + + if ($mjHeroContent.length > 0 ) { + $('head').append(``) + } + + return $ +} + +@MJMLElement +class HeroContent extends Component { + styles = this.getStyles() + + getStyles () { + const { mjAttribute, defaultUnit, getPadding } = this.props + + return merge({}, baseStyles, { + div: { + float: mjAttribute('align'), + margin: mjAttribute('align') === 'center' ? '0px auto' : '0px', + width: defaultUnit(mjAttribute('width'), 'px'), + backgroundColor: mjAttribute('background-color') + }, + td: { + paddingTop: getPadding('top'), + paddingLeft: getPadding('left'), + paddingRight: getPadding('right'), + paddingBottom: getPadding('bottom') + } + }) + } + + render () { + const { mjAttribute, children } = this.props + + return ( +
+ + + + + + +
+ + + {children} + +
+
+
+ ) + } +} + +HeroContent.tagName = tagName +HeroContent.defaultMJMLDefinition = defaultMJMLDefinition +HeroContent.endingTag = endingTag +HeroContent.baseStyles = baseStyles +HeroContent.postRender = postRender + +export default HeroContent diff --git a/packages/mjml-hero/src/index.js b/packages/mjml-hero/src/index.js new file mode 100644 index 000000000..6eaf48c6e --- /dev/null +++ b/packages/mjml-hero/src/index.js @@ -0,0 +1,4 @@ +import Hero from './Hero' +import HeroContent from './HeroContent' + +export default { Hero, HeroContent } diff --git a/packages/mjml-html/package.json b/packages/mjml-html/package.json index 111e25841..e984522b9 100644 --- a/packages/mjml-html/package.json +++ b/packages/mjml-html/package.json @@ -1,6 +1,7 @@ { "name": "mjml-html", - "version": "2.0.1", + "description": "mjml-html", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-image/README.md b/packages/mjml-image/README.md index 16ca62d6e..faf364303 100644 --- a/packages/mjml-image/README.md +++ b/packages/mjml-image/README.md @@ -1,5 +1,8 @@ ## mjml-image +Displays a responsive image in your email. It is similar to the HTML `` tag. +Note that if no width is provided, the image will use the parent column width. + ```xml @@ -14,11 +17,10 @@ ``` -Displays a responsive image in your email. It is similar to the HTML `` tag. -Note that if no width is provided, the image will use the parent column width. -

- sexy + + sexy +

@@ -31,9 +33,11 @@ padding-left | px | left offset | padding-right | px | right offset | n/a container-background-color | color | inner element background color | n/a border | string | css border definition | none +border-radius | px | border radius | n/a width | px | image width | 100% height | px | image height | auto src | url | image source | n/a href | url | link to redirect to on click | n/a alt | string | image description | n/a align | position | image alignment | center +title | string | tooltip & accessibility | n/a \ No newline at end of file diff --git a/packages/mjml-image/package.json b/packages/mjml-image/package.json index 2c4608e0e..e454ae7e7 100644 --- a/packages/mjml-image/package.json +++ b/packages/mjml-image/package.json @@ -1,6 +1,7 @@ { "name": "mjml-image", - "version": "2.0.1", + "description": "mjml-image", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-image/src/index.js b/packages/mjml-image/src/index.js index b7d5478fd..9d44f6c2b 100644 --- a/packages/mjml-image/src/index.js +++ b/packages/mjml-image/src/index.js @@ -12,6 +12,7 @@ const defaultMJMLDefinition = { 'align': 'center', 'alt': '', 'border': 'none', + 'border-radius': '', 'container-background-color': null, 'height': 'auto', 'href': '', @@ -22,6 +23,7 @@ const defaultMJMLDefinition = { 'padding': '10px 25px', 'src': '', 'target': '_blank', + 'title': '', 'vertical-align': null } } @@ -32,6 +34,7 @@ const baseStyles = { }, img: { border: 'none', + borderRadius: '', display: 'block', outline: 'none', textDecoration: 'none', @@ -71,7 +74,8 @@ class Image extends Component { }, img: { border: mjAttribute('border'), - height: defaultUnit(mjAttribute('height')) + height: mjAttribute('height'), + borderRadius: defaultUnit(mjAttribute('border-radius'), "px") } }) } @@ -82,6 +86,7 @@ class Image extends Component { const img = ( {mjAttribute('alt')} - - - - - - - - - - - - -
-``` - -Display a row in an `mj-invoice` component - -attribute | unit | description | default value -----------------|---------------|-------------------------------------------------------|-------------- -color | color | text color | #747474 -font-family | string | font name | Roboto, Ubuntu, Helvetica, Arial, sans-serif -font-size | px/em | font size | 14px -line-height | percent/px | space between lines | 22px -border | string | border-bottom header & border-top footer | 1px solid #ecedee -text-align | string | css text align | left (quantity column: right) -padding | px | supports up to 4 parameters | 10px 25px -padding-top | px | top offset | n/a -padding-bottom | px | bottom offset | n/a -padding-left | px | left offset | n/a -padding-right | px | right offset | n/a -name | string | item name | n/a -price | string/number | price (should already be formatted) | 0 -quantity | number | quantity | 0 diff --git a/packages/mjml-invoice/README.md b/packages/mjml-invoice/README.md index 61477095e..5350adfa2 100644 --- a/packages/mjml-invoice/README.md +++ b/packages/mjml-invoice/README.md @@ -1,5 +1,11 @@ ## mjml-invoice +

+ +

+ +Display a table of items with calculated total price. + ```xml @@ -18,13 +24,9 @@ ```

- -

- -Display a table of items with calculated total price. - -

- sexy + + sexy +

attribute | unit | description | default value @@ -42,3 +44,41 @@ padding-left | px | left offset padding-right | px | right offset | n/a intl | string | formatted string to set wording for header and footer | "name:Name;quantity:Quantity;price:Price" format | string | how to format total price, based on [numeraljs](http://numeraljs.com/) | n/a + +### mjml-invoice-item + +Display a row in an `mj-invoice` component + +```xml + + + + + + + + + + + + + + +``` + +attribute | unit | description | default value +----------------|---------------|-------------------------------------------------------|-------------- +color | color | text color | #747474 +font-family | string | font name | Roboto, Ubuntu, Helvetica, Arial, sans-serif +font-size | px/em | font size | 14px +line-height | percent/px | space between lines | 22px +border | string | border-bottom header & border-top footer | 1px solid #ecedee +text-align | string | css text align | left (quantity column: right) +padding | px | supports up to 4 parameters | 10px 25px +padding-top | px | top offset | n/a +padding-bottom | px | bottom offset | n/a +padding-left | px | left offset | n/a +padding-right | px | right offset | n/a +name | string | item name | n/a +price | string/number | price (should already be formatted) | 0 +quantity | number | quantity | 0 diff --git a/packages/mjml-invoice/package.json b/packages/mjml-invoice/package.json index 8a93b2302..102f65e8f 100644 --- a/packages/mjml-invoice/package.json +++ b/packages/mjml-invoice/package.json @@ -1,6 +1,7 @@ { "name": "mjml-invoice", - "version": "2.0.2", + "description": "mjml-invoice", + "version": "2.0.9", "main": "lib/index.js", "repository": { "type": "git", @@ -10,12 +11,12 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "mjml-table": "^2.0.1", + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "mjml-table": "^2.0.7", "numeral": "^1.5.3", - "react": "^15.0.2" + "react": "^15.1.0" } } diff --git a/packages/mjml-invoice/src/Invoice.js b/packages/mjml-invoice/src/Invoice.js new file mode 100644 index 000000000..27676e807 --- /dev/null +++ b/packages/mjml-invoice/src/Invoice.js @@ -0,0 +1,161 @@ +import { MJMLElement } from 'mjml-core' +import cloneDeep from 'lodash/cloneDeep' +import merge from 'lodash/merge' +import MJMLTable from 'mjml-table' +import numeral from 'numeral' +import React, { Component } from 'react' + +const tagName = 'mj-invoice' +const defaultMJMLDefinition = { + attributes: { + 'border': '1px solid #ecedee', + 'color': '#b9b9b9', + 'font-family': 'Roboto, Ubuntu, Helvetica, Arial, sans-serif', + 'font-size': '13px', + 'intl': 'name:Name;price:Price;quantity:Quantity', + 'line-height': '22px' + } +} +const baseStyles = { + th: { + fontWeight: '700', + padding: '10px 20px', + textAlign: 'left', + textTransform: 'uppercase' + } +} +const intl = { + name: 'Name', + price: 'Price', + quantity: 'Quantity', + total: 'Total:' +} + +@MJMLElement +class Invoice extends Component { + + constructor (props) { + super(props) + + const format = props.mjAttribute('format') + const currencies = format.match(/([^-\d.,])/g) + + this.items = props.mjml.get('children').filter(child => child.get('tagName') === 'mj-invoice-item') + this.format = format.replace(/([^-\d.,])/g, '$') + this.currency = currencies[0] || null + } + + styles = this.getStyles() + + getStyles () { + const { mjAttribute, defaultUnit } = this.props + + const styles = merge({}, baseStyles, { + table: { + color: mjAttribute('color'), + fontFamily: mjAttribute('font-family'), + fontSize: defaultUnit(mjAttribute('font-size'), "px"), + lineHeight: defaultUnit(mjAttribute('line-height'), "px") + }, + th: { + fontFamily: mjAttribute('font-family'), + fontSize: defaultUnit(mjAttribute('font-size'), "px"), + lineHeight: defaultUnit(mjAttribute('line-height'), "px") + }, + thead: { + borderBottom: mjAttribute('border') + }, + tfoot: { + borderTop: mjAttribute('border') + }, + total: { + fontFamily: mjAttribute('font-family'), + fontSize: defaultUnit(mjAttribute('font-size'), "px"), + fontWeight: '700', + lineHeight: defaultUnit(mjAttribute('line-height'), "px"), + padding: '10px 20px', + textAlign: 'right' + } + }) + + styles.thQuantity = merge({}, styles.th, { textAlign: 'right' }) + + return styles + } + + getIntlValue () { + const { mjAttribute } = this.props + + const intlValue = cloneDeep(intl) + + mjAttribute('intl').split(';').forEach(t => { + if (t && t.indexOf(':') != -1) { + t = t.split(':') + intlValue[t[0].trim()] = t[1].trim() + } + }) + + return intlValue + } + + getTotal () { + const format = this.format + const currency = this.currency + + const total = this.items.reduce((prev, item) => { + const unitPrice = parseFloat(numeral().unformat(item.getIn(['attributes', 'price']))) || 0 + const quantity = parseInt(item.getIn(['attributes', 'quantity'])) || 1 + + return prev + unitPrice * quantity + }, 0) + + return numeral(total).format(format).replace(/([^-\d.,])/g, currency) + } + + render () { + const { children } = this.props + const intlValue = this.getIntlValue() + const total = this.getTotal() + + return ( + + + + + {intlValue['name']} + + + {intlValue['price']} + + + {intlValue['quantity']} + + + + + {children} + + + + + {intlValue['total']} + + + {total} + + + + + ) + } + +} + +Invoice.tagName = tagName +Invoice.defaultMJMLDefinition = defaultMJMLDefinition +Invoice.baseStyles = baseStyles +Invoice.intl = intl + +export default Invoice diff --git a/packages/mjml-invoice-item/src/index.js b/packages/mjml-invoice/src/InvoiceItem.js similarity index 100% rename from packages/mjml-invoice-item/src/index.js rename to packages/mjml-invoice/src/InvoiceItem.js diff --git a/packages/mjml-invoice/src/index.js b/packages/mjml-invoice/src/index.js index 775e6bca0..3b42d4d03 100644 --- a/packages/mjml-invoice/src/index.js +++ b/packages/mjml-invoice/src/index.js @@ -1,161 +1,4 @@ -import { MJMLElement } from 'mjml-core' -import cloneDeep from 'lodash/cloneDeep' -import merge from 'lodash/merge' -import MJMLTable from 'mjml-table' -import numeral from 'numeral' -import React, { Component } from 'react' +import Invoice from './Invoice' +import InvoiceItem from './InvoiceItem' -const tagName = 'mj-invoice' -const defaultMJMLDefinition = { - attributes: { - 'border': '1px solid #ecedee', - 'color': '#b9b9b9', - 'font-family': 'Roboto, Ubuntu, Helvetica, Arial, sans-serif', - 'font-size': '13px', - 'intl': 'name:Name;price:Price;quantity:Quantity', - 'line-height': '22px' - } -} -const baseStyles = { - th: { - fontWeight: '700', - padding: '10px 20px', - textAlign: 'left', - textTransform: 'uppercase' - } -} -const intl = { - name: 'Name', - price: 'Price', - quantity: 'Quantity', - total: 'Total:' -} - -@MJMLElement -class Invoice extends Component { - - constructor (props) { - super(props) - - const format = props.mjAttribute('format') - const currencies = format.match(/([^-\d.,])/g) - - this.items = props.mjml.get('children').filter(child => child.get('tagName') === 'mj-invoice-item') - this.format = format.replace(/([^-\d.,])/g, '$') - this.currency = currencies[0] || null - } - - styles = this.getStyles() - - getStyles () { - const { mjAttribute, defaultUnit } = this.props - - const styles = merge({}, baseStyles, { - table: { - color: mjAttribute('color'), - fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size')), - lineHeight: defaultUnit(mjAttribute('line-height')) - }, - th: { - fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size')), - lineHeight: defaultUnit(mjAttribute('line-height')) - }, - thead: { - borderBottom: mjAttribute('border') - }, - tfoot: { - borderTop: mjAttribute('border') - }, - total: { - fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size')), - fontWeight: '700', - lineHeight: defaultUnit(mjAttribute('line-height')), - padding: '10px 20px', - textAlign: 'right' - } - }) - - styles.thQuantity = merge({}, styles.th, { textAlign: 'right' }) - - return styles - } - - getIntlValue () { - const { mjAttribute } = this.props - - const intlValue = cloneDeep(intl) - - mjAttribute('intl').split(';').forEach(t => { - if (t && t.indexOf(':') != -1) { - t = t.split(':') - intlValue[t[0].trim()] = t[1].trim() - } - }) - - return intlValue - } - - getTotal () { - const format = this.format - const currency = this.currency - - const total = this.items.reduce((prev, item) => { - const unitPrice = parseFloat(numeral().unformat(item.getIn(['attributes', 'price']))) || 0 - const quantity = parseInt(item.getIn(['attributes', 'quantity'])) || 1 - - return prev + unitPrice * quantity - }, 0) - - return numeral(total).format(format).replace(/([^-\d.,])/g, currency) - } - - render () { - const { children } = this.props - const intlValue = this.getIntlValue() - const total = this.getTotal() - - return ( - - - - - {intlValue['name']} - - - {intlValue['price']} - - - {intlValue['quantity']} - - - - - {children} - - - - - {intlValue['total']} - - - {total} - - - - - ) - } - -} - -Invoice.tagName = tagName -Invoice.defaultMJMLDefinition = defaultMJMLDefinition -Invoice.baseStyles = baseStyles -Invoice.intl = intl - -export default Invoice +export default { Invoice, InvoiceItem } diff --git a/packages/mjml-list/README.md b/packages/mjml-list/README.md index b404c354f..7f98d3c2e 100644 --- a/packages/mjml-list/README.md +++ b/packages/mjml-list/README.md @@ -1,5 +1,7 @@ ## mjml-list +`mj-list` enables you to create unordered lists and enables you to wrap `li`s tag. + ```xml @@ -19,10 +21,10 @@ ``` -`mj-list` enables you to create unordered lists and enables you to wrap `li`s tag. -

- sexy + + sexy +

attribute | unit | description | default value diff --git a/packages/mjml-list/package.json b/packages/mjml-list/package.json index 8a2524ba5..c48029869 100644 --- a/packages/mjml-list/package.json +++ b/packages/mjml-list/package.json @@ -1,6 +1,7 @@ { "name": "mjml-list", - "version": "2.0.1", + "description": "mjml-list", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-location/README.md b/packages/mjml-location/README.md index f42f627a4..62b46c7a3 100644 --- a/packages/mjml-location/README.md +++ b/packages/mjml-location/README.md @@ -1,5 +1,12 @@ ## mjml-location +

+ +

+ + +Display a Google Maps location link + ```xml @@ -15,11 +22,11 @@ ```

- + + sexy +

-Display a Google Maps location link - attribute | unit | description | default value ----------------|---------------|-------------------------------|-------------- color | color | text color | #3aa7ed @@ -31,4 +38,4 @@ padding-top | px | top offset | n/a padding-bottom | px | bottom offset | n/a padding-left | px | left offset | n/a padding-right | px | right offset | n/a -src | string | image source | http://i.imgur.com/DPCJHhy.png +img-src | string | image source | http://i.imgur.com/DPCJHhy.png diff --git a/packages/mjml-location/package.json b/packages/mjml-location/package.json index 7f4c87fd0..a8c841966 100644 --- a/packages/mjml-location/package.json +++ b/packages/mjml-location/package.json @@ -1,6 +1,7 @@ { "name": "mjml-location", - "version": "2.0.1", + "description": "mjml-location", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,12 +11,12 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "mjml-image": "^2.0.1", - "mjml-text": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "mjml-image": "^2.0.7", + "mjml-text": "^2.0.7", + "react": "^15.1.0" } } diff --git a/packages/mjml-navbar/.npmignore b/packages/mjml-navbar/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-navbar/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-navbar/README.md b/packages/mjml-navbar/README.md new file mode 100644 index 000000000..6ed776817 --- /dev/null +++ b/packages/mjml-navbar/README.md @@ -0,0 +1,179 @@ +## mjml-navbar + +

+ +

+ +Displays a full width section for navigation + +```xml + + + + + + + + + + Getting started + Try it live + Templates + Components + Documentation + + + + + + +``` + +

+ + sexy + +

+ +attribute | unit | description | default value +--------------------|-------------|--------------------------------|--------------- +background-color | color | section color | n/a +background-url | url | background url | n/a +background-repeat | string | css background repeat | repeat +background-size | percent/px | css background size | auto +vertical-align | string | css vertical-align | top +text-align | string | css text-align | center +padding | px | supports up to 4 parameters | 20px 0 +padding-top | px | section top offset | n/a +padding-bottom | px | section bottom offset | n/a +padding-left | px | section left offset | n/a +padding-right | px | section right offset | n/a + +### mjml-inline-links + +To display some links horizontally + +Standard Desktop: + +

+ +

+ +Standard Mobile: + +

+ +

+ +Mode hamburger enabled: + +

+ +

+ +```xml + + + + + + + + + + Getting started + Try it live + Templates + Components + Documentation + + + + + + +``` + +

+ + sexy + +

+ + + + + +attribute | unit | description | default value +----------------------------|--------------------|--------------------------------------------------------------------------------------------------|----------------- +base url | string | base url for children components | n/a +hamburger | string | activate the hamburger navigation on mobile if the value is hamburger | n/a +align | string | align content left/center/right | center +ico-open | ASCII code decimal | char code for a custom open icon (hamburger mode required) | 9776 +ico-close | ASCII code decimal | char code for a custom close icon (hamburger mode required) | 8855 +ico-padding | px | hamburger icon padding, supports up to 4 parameters (hamburger mode required) | 10px +ico-padding-top | px | hamburger icon top offset (hamburger mode required) | 10px +ico-padding-right | px | hamburger icon right offset (hamburger mode required) | 10px +ico-padding-bottom | px | hamburger icon bottom offset (hamburger mode required) | 10px +ico-padding-left | px | hamburger icon left offset (hamburger mode required) | 10px +ico-align | string | hamburger icon alignment, left/center/right (hamburger mode required) | center +ico-color | color format | hamburger icon color (hamburger mode required) | #000000 +ico-font-size | px | hamburger icon size (hamburger mode required) | Ubuntu, Helvetica, Arial, sans-serif +ico-font-family | string | hamburger icon font (only on hamburger mode) | 30px +ico-text-transform | string | hamburger icon text transformation none/capitalize/uppercase/lowercase (hamburger mode required) | none +ico-text-decoration | string | hamburger icon text decoration none/underline/overline/line-through (hamburger mode required) | none +ico-line-height | px | hamburger icon line height (hamburger mode required) | 30px + +### mjml-link + +```xml + + + + + + + + + Getting started + Try it live + Documentation + + + + + + +``` + +Displays a horizontal navbar. + +

+ + sexy + +

+ + + +attribute | unit | description | default value +-----------------|---------------|---------------------------------------|------------------------------ +color | color | text color | #000000 +font-family | string | font | Ubuntu, Helvetica, Arial, sans-serif +font-size | px | text size | 13px +font-style | string | normal/italic/oblique | n/a +font-weight | number | text thickness | n/a +line-height | px | space between the lines | 22px +text-decoration | string | underline/overline/none | n/a +text-transform | string | capitalize/uppercase/lowercase/none | uppercase +padding | px | supports up to 4 parameters | 10px 25px +padding-top | px | top offset | n/a +padding-bottom | px | bottom offset | n/a +padding-left | px | left offset | n/a +padding-right | px | right offset | n/a diff --git a/packages/mjml-navbar/package.json b/packages/mjml-navbar/package.json new file mode 100644 index 000000000..7db807ca3 --- /dev/null +++ b/packages/mjml-navbar/package.json @@ -0,0 +1,21 @@ +{ + "name": "mjml-navbar", + "description": "mjml-navbar", + "version": "2.0.5", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io", + "dependencies": { + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "mjml-section": "^2.0.7", + "react": "^15.1.0" + } +} diff --git a/packages/mjml-navbar/src/InlineLinks.js b/packages/mjml-navbar/src/InlineLinks.js new file mode 100644 index 000000000..5951c0035 --- /dev/null +++ b/packages/mjml-navbar/src/InlineLinks.js @@ -0,0 +1,186 @@ +import { MJMLElement } from 'mjml-core' +import merge from 'lodash/merge' +import url from 'url' +import React, { Component } from 'react' +import crypto from 'crypto' + +const tagName = 'mj-inline-links' +const defaultMJMLDefinition = { + attributes: { + 'align': 'center', + 'ico-align': 'center', + 'ico-open': '9776', + 'ico-close': '8855', + 'ico-color': '#000000', + 'ico-font-size': '30px', + 'ico-font-family': 'Ubuntu, Helvetica, Arial, sans-serif', + 'ico-text-transform': 'uppercase', + 'ico-padding': '10px', + 'ico-text-decoration': 'none', + 'ico-line-height': '30px' + } +} + +const baseStyles = { + div: { + 'width': '100%' + }, + trigger: { + display: 'none', + maxHeight: '0px', + maxWidth: '0px', + fontSize: '0px', + overflow: 'hidden' + }, + label: { + display: 'block', + cursor: 'pointer', + msoHide: 'all', + MozUserSelect: 'none', + userSelect: 'none' + }, + icoOpen: { + msoHide: 'all' + }, + icoClose: { + display: 'none', + msoHide: 'all' + } +} +const postRender = $ => { + $('.mj-inline-links') + .each(function () { + $(this) + .prepend(``) + .append(``) + .removeAttr('data-align') + }) + + $('.mj-menu-trigger') + .each(function () { + const id = $(this).children('label').attr('for') + + $(this) + .before(` + + `) + }) + + if ($('.mj-menu-trigger').length) { + $('head') + .append(``) + } + + return $ +} + +@MJMLElement +class InlineLinks extends Component { + + styles = this.getStyles() + + getStyles () { + const { mjAttribute, defaultUnit, getPadding } = this.props + + return merge({}, baseStyles, { + div: { + textAlign: mjAttribute('align') + }, + label: { + textAlign: mjAttribute('ico-align'), + color: mjAttribute('ico-color'), + fontSize: defaultUnit(mjAttribute('ico-font-size'), 'px'), + fontFamily: mjAttribute('ico-font-family'), + textTransform: mjAttribute('ico-text-transform'), + textDecoration: mjAttribute('ico-text-decoration'), + lineHeight: defaultUnit(mjAttribute('ico-line-height'), 'px'), + paddingTop: getPadding('top', 'ico-'), + paddingLeft: getPadding('left', 'ico-'), + paddingRight: getPadding('right', 'ico-'), + paddingBottom: getPadding('bottom', 'ico-') + } + }) + } + + renderChildren () { + const { children, mjAttribute } = this.props + const baseUrl = mjAttribute('base-url') + const perform = (mjml) => { + if (mjml.get('tagName') === 'mj-link') { + mjml = mjml.setIn(['attributes', 'href'], url.resolve(baseUrl, mjml.getIn(['attributes', 'href']))) + } + return mjml + } + + return children.map(child => React.cloneElement(child, { mjml: perform(child.props.mjml) })) + } + + renderHamburger () { + const { mjAttribute } = this.props + const key = crypto.randomBytes(8).toString('hex') + + return ( +
+ +
+ ) + } + + render () { + const { mjAttribute } = this.props + + return ( +
+ { mjAttribute('hamburger') === 'hamburger' + ? this.renderHamburger() + : null } +
+ {this.renderChildren()} +
+
+ ) + } +} + +InlineLinks.tagName = tagName +InlineLinks.defaultMJMLDefinition = defaultMJMLDefinition +InlineLinks.baseStyles = baseStyles +InlineLinks.postRender = postRender + +export default InlineLinks diff --git a/packages/mjml-navbar/src/Link.js b/packages/mjml-navbar/src/Link.js new file mode 100644 index 000000000..c0f6be5b7 --- /dev/null +++ b/packages/mjml-navbar/src/Link.js @@ -0,0 +1,89 @@ +import { MJMLElement } from 'mjml-core' +import merge from 'lodash/merge' +import React, { Component } from 'react' + +const tagName = 'mj-link' +const defaultMJMLDefinition = { + attributes: { + 'padding': '15px 10px', + 'color': '#000000', + 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', + 'font-size': '13px', + 'font-weight': 'normal', + 'line-height': '22px' + } +} +const baseStyles = { + a: { + display: 'inline-block', + textDecoration: 'none', + textTransform: 'uppercase' + } +} +const endingTag = true +const postRender = $ => { + $('.mj-link') + .each(function () { + $(this) + .before(``) + .after(``) + .removeAttr('data-padding') + .removeAttr('class') + }) + + return $ +} + +@MJMLElement +class Link extends Component { + + styles = this.getStyles() + + getStyles () { + const { mjAttribute } = this.props + + return merge({}, baseStyles, { + a: { + color: mjAttribute('color'), + fontFamily: mjAttribute('font-family'), + fontSize: mjAttribute('font-size'), + fontWeight: mjAttribute('font-weight'), + lineHeight: mjAttribute('line-height'), + textDecoration: mjAttribute('text-decoration'), + textTransform: mjAttribute('text-transform'), + padding: mjAttribute('padding'), + paddingTop: mjAttribute('padding-top'), + paddingLeft: mjAttribute('padding-left'), + paddingRight: mjAttribute('padding-right'), + paddingBottom: mjAttribute('padding-bottom') + } + }) + } + + render () { + const { mjAttribute, mjContent } = this.props + + return ( + + ) + } + +} + +Link.tagName = tagName +Link.defaultMJMLDefinition = defaultMJMLDefinition +Link.baseStyles = baseStyles +Link.endingTag = endingTag +Link.postRender = postRender + +export default Link diff --git a/packages/mjml-navbar/src/Navbar.js b/packages/mjml-navbar/src/Navbar.js new file mode 100644 index 000000000..f9fe54a2c --- /dev/null +++ b/packages/mjml-navbar/src/Navbar.js @@ -0,0 +1,32 @@ +import { MJMLElement } from 'mjml-core' +import MJMLSection from 'mjml-section' +import React, { Component } from 'react' + +const tagName = 'mj-navbar' +const defaultMJMLDefinition = { + attributes: { + 'navbar-hamburger': '', + 'padding': '10px 25px', + 'width': '100%' + } +} + +@MJMLElement +class Navbar extends Component { + + render () { + const { children } = this.props + + return ( + + {children} + + ) + } + +} + +Navbar.tagName = tagName +Navbar.defaultMJMLDefinition = defaultMJMLDefinition + +export default Navbar diff --git a/packages/mjml-navbar/src/index.js b/packages/mjml-navbar/src/index.js new file mode 100644 index 000000000..8a1dc303b --- /dev/null +++ b/packages/mjml-navbar/src/index.js @@ -0,0 +1,5 @@ +import Navbar from './Navbar' +import InlineLinks from './InlineLinks' +import Link from './Link' + +export default { Navbar, InlineLinks, Link } diff --git a/packages/mjml-raw/README.md b/packages/mjml-raw/README.md index fcda3c1e8..8090bbebc 100644 --- a/packages/mjml-raw/README.md +++ b/packages/mjml-raw/README.md @@ -1,5 +1,7 @@ ## mjml-raw +Displays raw HTML that is not going to be parsed by the MJML engine. Anything left inside this tag should be raw, responsive HTML. + ```xml @@ -12,8 +14,8 @@ ``` -Displays raw HTML that is not going to be parsed by the MJML engine. Anything left inside this tag should be raw, responsive HTML. -

- sexy + + sexy +

diff --git a/packages/mjml-raw/package.json b/packages/mjml-raw/package.json index 2746dd219..1ac17a2ca 100644 --- a/packages/mjml-raw/package.json +++ b/packages/mjml-raw/package.json @@ -1,6 +1,7 @@ { "name": "mjml-raw", - "version": "2.0.1", + "description": "mjml-raw", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,9 +11,9 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-section/README.md b/packages/mjml-section/README.md index d559f3645..1e8377af1 100644 --- a/packages/mjml-section/README.md +++ b/packages/mjml-section/README.md @@ -1,5 +1,8 @@ ## mjml-section +Sections are intended to be used as rows within your email. +They will be used to structure the layout. + ```xml @@ -12,11 +15,10 @@ ``` -Sections are intended to be used as rows within your email. -They will be used to structure the layout. -

- sexy + + sexy +

The `full-width` property will be used to manage the background width. diff --git a/packages/mjml-section/package.json b/packages/mjml-section/package.json index 3b333d878..1c05d25a3 100644 --- a/packages/mjml-section/package.json +++ b/packages/mjml-section/package.json @@ -1,6 +1,7 @@ { "name": "mjml-section", - "version": "2.0.1", + "description": "mjml-section", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 50a59e942..8395302df 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -1,4 +1,4 @@ -import { MJMLElement } from 'mjml-core' +import { MJMLElement, helpers } from 'mjml-core' import cloneDeep from 'lodash/cloneDeep' import merge from 'lodash/merge' import React, { Component } from 'react' @@ -17,7 +17,7 @@ const baseStyles = { margin: '0px auto' }, table: { - fontSize: '1px', + fontSize: '0px', width: '100%' }, td: { @@ -39,24 +39,24 @@ const postRender = $ => { return } - $(this).before(``) + ${helpers.endConditionalTag}`) - $(this).after(``) + ${helpers.endConditionalTag}`) }) $('.mj-section-outlook-open').each(function () { const $columnDiv = $(this).next() - $(this).replaceWith(``) + ${helpers.endConditionalTag}`) $columnDiv.removeAttr('data-vertical-align') }) @@ -64,17 +64,17 @@ const postRender = $ => { $('.mj-section-outlook-line').each(function () { const $columnDiv = $(this).next() - $(this).replaceWith(``) + $(this).replaceWith(`${helpers.startConditionalTag} + + ${helpers.endConditionalTag}`) $columnDiv.removeAttr('data-vertical-align') }) $('.mj-section-outlook-close').each(function () { - $(this).replaceWith(``) + ${helpers.endConditionalTag}`) }) return $ @@ -104,24 +104,24 @@ class Section extends Component { const { mjAttribute, parentWidth, defaultUnit } = this.props const background = mjAttribute('background-url') ? { - background: `url(${mjAttribute('background-url')}) top center / ${mjAttribute('background-size') || ''} ${mjAttribute('background-repeat') || ''}` + background: `${mjAttribute('background-color') || ''} url(${mjAttribute('background-url')}) top center / ${mjAttribute('background-size') || ''} ${mjAttribute('background-repeat') || ''}`.trim() } : { background: mjAttribute('background-color') } return merge({}, baseStyles, { td: { - fontSize: '1px', - padding: defaultUnit(mjAttribute('padding')), - paddingBottom: defaultUnit(mjAttribute('padding-bottom')), - paddingLeft: defaultUnit(mjAttribute('padding-left')), - paddingRight: defaultUnit(mjAttribute('padding-right')), - paddingTop: defaultUnit(mjAttribute('padding-top')), + fontSize: '0px', + padding: defaultUnit(mjAttribute('padding'), 'px'), + paddingBottom: defaultUnit(mjAttribute('padding-bottom'), 'px'), + paddingLeft: defaultUnit(mjAttribute('padding-left'), 'px'), + paddingRight: defaultUnit(mjAttribute('padding-right'), 'px'), + paddingTop: defaultUnit(mjAttribute('padding-top'), 'px'), textAlign: mjAttribute('text-align'), verticalAlign: mjAttribute('vertical-align') }, div: { - maxWidth: parentWidth + maxWidth: defaultUnit(parentWidth) } }, { div: this.isFullWidth() ? {} : cloneDeep(background), diff --git a/packages/mjml-social/README.md b/packages/mjml-social/README.md index f5ea661e5..0f743dc31 100644 --- a/packages/mjml-social/README.md +++ b/packages/mjml-social/README.md @@ -1,5 +1,12 @@ ## mjml-social +

+ desktop +

+ +Displays calls-to-action for various social networks with their associated logo. +You can activate/deactivate any icon, with `display` property. + ```xml @@ -20,17 +27,20 @@ ``` -Displays calls-to-action for various social networks with their associated logo. -You can activate/deactivate any icon, with `display` property. +

+ + sexy + +

-

-desktop -

- -

- sexy -

- attribute | unit | description | default value ----------------------------|-------------|--------------------------------------------------------------------------|-------------------------------------------- facebook-content | string | button text content | Share diff --git a/packages/mjml-social/package.json b/packages/mjml-social/package.json index 0a2dd90ee..d0366436d 100644 --- a/packages/mjml-social/package.json +++ b/packages/mjml-social/package.json @@ -1,6 +1,7 @@ { "name": "mjml-social", - "version": "2.0.1", + "description": "mjml-social", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-social/src/index.js b/packages/mjml-social/src/index.js index a0cce6b96..e2d5008f8 100644 --- a/packages/mjml-social/src/index.js +++ b/packages/mjml-social/src/index.js @@ -1,4 +1,4 @@ -import { MJMLElement } from 'mjml-core' +import { MJMLElement, helpers } from 'mjml-core' import merge from 'lodash/merge' import tap from 'lodash/tap' import clone from 'lodash/clone' @@ -48,29 +48,27 @@ const defaultMJMLDefinition = { } } const baseStyles = { - div: { - textAlign: 'center' - }, tableHorizontal: { float: 'none', display: 'inline-table' }, tableVertical: { - margin: '0 auto' + margin: '0px' }, td1: { + padding: '4px', verticalAlign: 'middle' }, td2: { - textAlign: 'center', verticalAlign: 'middle' }, tdText: { - padding: '8px 8px 8px 0', + padding: '4px 4px 4px 0', verticalAlign: 'middle' }, a: { textDecoration: 'none', + textAlign: 'left', display: 'block', borderRadius: '3px' }, @@ -107,21 +105,21 @@ const buttonDefinitions = { } const postRender = $ => { $('.mj-social-outlook-open').each(function () { - $(this).replaceWith(``) + ${helpers.endConditionalTag}`) }) $('.mj-social-outlook-line').each(function () { - $(this).replaceWith(``) + ${helpers.endConditionalTag}`) }) $('.mj-social-outlook-close').each(function () { - $(this).replaceWith(``) + ${helpers.endConditionalTag}`) }) return $ @@ -141,24 +139,18 @@ class Social extends Component { const { mjAttribute, defaultUnit } = this.props return merge({}, baseStyles, { - div: { - textAlign: mjAttribute('align') - }, a: { color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), - fontSize: defaultUnit(mjAttribute('font-size')), + fontSize: defaultUnit(mjAttribute('font-size'), 'px'), fontStyle: mjAttribute('font-style'), fontWeight: mjAttribute('font-weight'), - lineHeight: defaultUnit(mjAttribute('line-height')), + lineHeight: defaultUnit(mjAttribute('line-height'), 'px'), textDecoration: mjAttribute('text-decoration') }, - td1: { - padding: this.isHorizontal() ? '0 4px' : '4px 0' - }, td2: { - width: defaultUnit(mjAttribute('icon-size')), - height: defaultUnit(mjAttribute('icon-size')) + width: defaultUnit(mjAttribute('icon-size'), 'px'), + height: defaultUnit(mjAttribute('icon-size'), 'px') } }) } @@ -196,7 +188,8 @@ class Social extends Component { style={iconStyle}> - + {platform} @@ -280,7 +273,7 @@ class Social extends Component { result.push(
) return result - }, [
]) + }, [
]) socialButtons[socialButtons.length - 1] =
@@ -288,12 +281,14 @@ class Social extends Component { } renderVertical () { + const { mjAttribute } = this.props + return ( {this.renderSocialButtons()} @@ -304,7 +299,7 @@ class Social extends Component { render () { return ( -
+
{ this.isHorizontal() ? this.renderHorizontal() : this.renderVertical() }
) diff --git a/packages/mjml-spacer/README.md b/packages/mjml-spacer/README.md index e09fec710..e9e124bb5 100644 --- a/packages/mjml-spacer/README.md +++ b/packages/mjml-spacer/README.md @@ -1,5 +1,7 @@ ## mjml-spacer +Displays a blank space. + ```xml @@ -14,8 +16,6 @@ ``` -Displays a blank space. - attribute | unit | description | default value ----------------------------|-------------|--------------------------------|------------------------------ height | px | spacer height | 20px diff --git a/packages/mjml-spacer/package.json b/packages/mjml-spacer/package.json index 20ceaec17..b191c6d36 100644 --- a/packages/mjml-spacer/package.json +++ b/packages/mjml-spacer/package.json @@ -1,6 +1,7 @@ { "name": "mjml-spacer", - "version": "2.0.0", + "description": "mjml-spacer", + "version": "2.0.6", "main": "lib/index.js", "repository": { "type": "git", @@ -10,9 +11,9 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-table/README.md b/packages/mjml-table/README.md index 2dd42e06c..b027421aa 100644 --- a/packages/mjml-table/README.md +++ b/packages/mjml-table/README.md @@ -1,5 +1,7 @@ ## mjml-table +This tag allows you to display table and filled it with data. + ```xml @@ -30,15 +32,17 @@ ``` -This tag allows you to display table and filled it with data. -

- sexy + + sexy +

attribute | unit | description | default value ----------------------------|-----------------------------|------------------------------- |-------------- color | color | text header & footer color | #000 +cellpadding | pixels | space between cells | n/a +cellspacing | pixels | space between cell and border | n/a font-family | string | font name | Ubuntu, Helvetica, Arial, sans-serif font-size | px/em | font size | 13px line-height | percent/px | space between lines | 22px diff --git a/packages/mjml-table/package.json b/packages/mjml-table/package.json index cfe138217..768c10bed 100644 --- a/packages/mjml-table/package.json +++ b/packages/mjml-table/package.json @@ -1,6 +1,7 @@ { "name": "mjml-table", - "version": "2.0.1", + "description": "mjml-table", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,9 +11,9 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-table/src/index.js b/packages/mjml-table/src/index.js index 637f9965a..81d0e8ace 100644 --- a/packages/mjml-table/src/index.js +++ b/packages/mjml-table/src/index.js @@ -43,6 +43,8 @@ class Table extends Component { return { table: { + cellpadding: mjAttribute('cellspadding'), + cellspacing: mjAttribute('cellspacing'), color: mjAttribute('color'), fontFamily: mjAttribute('font-family'), fontSize: defaultUnit(mjAttribute('font-size')), diff --git a/packages/mjml-text/README.md b/packages/mjml-text/README.md index 4d608bfde..af35bf169 100644 --- a/packages/mjml-text/README.md +++ b/packages/mjml-text/README.md @@ -1,5 +1,7 @@ ## mjml-text +This tag allows you to display text in your email. + ```xml @@ -13,28 +15,21 @@ - - - - Hey There! - - - ``` -This tag allows you to display text in your email. +

+ + sexy + +

-

- sexy -

- attribute | unit | description | default value ------------------------------|---------------|--------------------------------|------------------------------------- color | color | text color | #000000 @@ -44,6 +39,7 @@ This tag allows you to display text in your email. font-weight | number | text thickness | n/a line-height | px | space between the lines | 22px text-decoration | string | underline/overline/none | n/a + text-transform | string | uppercase/lowercase/capitalize | n/a align | string | left/right/center | left container-background-color | color | inner element background color | n/a padding | px | supports up to 4 parameters | 10px 25px diff --git a/packages/mjml-text/package.json b/packages/mjml-text/package.json index cf77b35bd..da77d7edd 100644 --- a/packages/mjml-text/package.json +++ b/packages/mjml-text/package.json @@ -1,6 +1,7 @@ { "name": "mjml-text", - "version": "2.0.1", + "description": "mjml-text", + "version": "2.0.7", "main": "lib/index.js", "repository": { "type": "git", @@ -10,10 +11,10 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.11.2", - "mjml-core": "^2.0.1", - "react": "^15.0.2" + "lodash": "^4.13.1", + "mjml-core": "^2.3.0", + "react": "^15.1.0" } } diff --git a/packages/mjml-text/src/index.js b/packages/mjml-text/src/index.js index 3bb6ecf22..2018435d1 100644 --- a/packages/mjml-text/src/index.js +++ b/packages/mjml-text/src/index.js @@ -52,8 +52,9 @@ class Text extends Component { fontSize: defaultUnit(mjAttribute('font-size')), fontStyle: mjAttribute('font-style'), fontWeight: mjAttribute('font-weight'), - lineHeight: defaultUnit(mjAttribute('line-height')), - textDecoration: mjAttribute('text-decoration') + lineHeight: defaultUnit(mjAttribute('line-height'), "px"), + textDecoration: mjAttribute('text-decoration'), + textTransform: mjAttribute('text-transform') } }) } diff --git a/packages/mjml/package.json b/packages/mjml/package.json index 647eddfc1..79ac6f514 100644 --- a/packages/mjml/package.json +++ b/packages/mjml/package.json @@ -1,6 +1,7 @@ { "name": "mjml", - "version": "2.0.3", + "description": "MJML: the only framework that makes responsive-email easy", + "version": "2.3.0", "main": "lib/index.js", "bin": { "mjml": "bin/mjml" @@ -13,25 +14,30 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://github.com/mjmlio/mjml", + "homepage": "https://mjml.io", "dependencies": { - "mjml-button": "^2.0.1", - "mjml-cli": "^2.0.3", - "mjml-column": "^2.0.1", - "mjml-container": "^2.0.1", - "mjml-core": "^2.0.1", - "mjml-divider": "^2.0.2", - "mjml-html": "^2.0.1", - "mjml-image": "^2.0.1", - "mjml-invoice-item": "^2.0.1", - "mjml-invoice": "^2.0.2", - "mjml-list": "^2.0.1", - "mjml-location": "^2.0.1", - "mjml-raw": "^2.0.1", - "mjml-section": "^2.0.1", - "mjml-social": "^2.0.1", - "mjml-spacer": "^2.0.0", - "mjml-table": "^2.0.1", - "mjml-text": "^2.0.1" + "mjml-button": "^2.0.7", + "mjml-cli": "^2.3.0", + "mjml-column": "^2.0.7", + "mjml-container": "^2.0.7", + "mjml-core": "^2.3.0", + "mjml-divider": "^2.0.8", + "mjml-group": "^2.0.1", + "mjml-head-attributes": "^2.0.5", + "mjml-head-font": "^2.0.0", + "mjml-head-title": "^2.0.0", + "mjml-hero": "^2.0.7", + "mjml-html": "^2.0.7", + "mjml-image": "^2.0.7", + "mjml-invoice": "^2.0.9", + "mjml-list": "^2.0.7", + "mjml-location": "^2.0.7", + "mjml-navbar": "^2.0.5", + "mjml-raw": "^2.0.7", + "mjml-section": "^2.0.7", + "mjml-social": "^2.0.7", + "mjml-spacer": "^2.0.6", + "mjml-table": "^2.0.7", + "mjml-text": "^2.0.7" } } diff --git a/packages/mjml/src/index.js b/packages/mjml/src/index.js index 18d29e8fd..b398bbd7e 100644 --- a/packages/mjml/src/index.js +++ b/packages/mjml/src/index.js @@ -1,14 +1,17 @@ -import { registerMJElement } from 'mjml-core' +import { registerMJElement, registerMJHeadElement } from 'mjml-core' + import Button from 'mjml-button' import Column from 'mjml-column' import Container from 'mjml-container' import Divider from 'mjml-divider' +import Group from 'mjml-group' import Html from 'mjml-html' import Image from 'mjml-image' -import Invoice from 'mjml-invoice' -import InvoiceItem from 'mjml-invoice-item' import List from 'mjml-list' import Location from 'mjml-location' +import MJHero from 'mjml-hero' +import MJInvoice from 'mjml-invoice' +import MJNavbar from 'mjml-navbar' import Raw from 'mjml-raw' import Section from 'mjml-section' import Social from 'mjml-social' @@ -16,16 +19,30 @@ import Spacer from 'mjml-spacer' import Table from 'mjml-table' import Text from 'mjml-text' -[ Container, - Button, +import MJHeadAttributes from 'mjml-head-attributes' +import MJHeadFont from 'mjml-head-font' +import MJHeadTitle from 'mjml-head-title' + +const { Hero, HeroContent } = MJHero +const { Invoice, InvoiceItem } = MJInvoice +const { Navbar, InlineLinks, Link } = MJNavbar; + +[ Button, Column, + Container, Divider, + Group, + Hero, + HeroContent, Html, Image, + InlineLinks, Invoice, InvoiceItem, + Link, List, Location, + Navbar, Raw, Section, Social, @@ -33,4 +50,8 @@ import Text from 'mjml-text' Table, Text ].map(registerMJElement) +registerMJHeadElement('mj-attributes', MJHeadAttributes) +registerMJHeadElement('mj-font', MJHeadFont) +registerMJHeadElement('mj-title', MJHeadTitle) + export * from 'mjml-core' diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 96b9b099d..9e4f90af6 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,52 +1,17 @@ var mjml = require('./lib/index') console.log(mjml.mjml2html(` - - - - - - -
- - - - - - - - - - - - - - - - - - My button - - - My text - - - -
YearLanguageInspired from
1995PHPC, Shell Unix
1995JavaScriptScheme, Self
- - - -
- My Html -
- - - - <% if (undefined) {} %> - - - - - - -`, { beautify: true })) + + + + + + + + + + + + + + `, { beautify: true })) From d5d76d8ea776d060bc990be77e8c2ef4399a95e3 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 4 Jul 2016 16:34:27 +0200 Subject: [PATCH 07/87] mj-style --- install.sh | 2 ++ packages/mjml-core/src/MJMLRenderer.js | 8 +++++++ packages/mjml-head-style/.npmignore | 3 +++ packages/mjml-head-style/README.md | 32 ++++++++++++++++++++++++++ packages/mjml-head-style/package.json | 15 ++++++++++++ packages/mjml-head-style/src/index.js | 8 +++++++ packages/mjml/package.json | 3 ++- packages/mjml/src/index.js | 2 ++ packages/mjml/test.js | 28 +++++----------------- 9 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 packages/mjml-head-style/.npmignore create mode 100644 packages/mjml-head-style/README.md create mode 100644 packages/mjml-head-style/package.json create mode 100644 packages/mjml-head-style/src/index.js diff --git a/install.sh b/install.sh index 1314e28c9..5cef97b1b 100755 --- a/install.sh +++ b/install.sh @@ -40,6 +40,7 @@ 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 .. @@ -69,6 +70,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 diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 2ce4b497c..8b59b65b0 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -12,6 +12,7 @@ import MJMLElementsCollection, { postRenders, registerMJElement } from './MJMLEl import React from 'react' import ReactDOMServer from 'react-dom/server' import warning from 'warning' +import juice from 'juice' const debug = require('debug')('mjml-engine/mjml2html') @@ -24,6 +25,7 @@ export default class MJMLRenderer { container: defaultContainer(), defaultAttributes: {}, cssClasses: {}, + css: [], fonts: cloneDeep(defaultFonts) } @@ -127,6 +129,12 @@ export default class MJMLRenderer { } finalMJMLDocument = he.decode(finalMJMLDocument) + finalMJMLDocument = juice(finalMJMLDocument, { + extraCss: `${this.attributes.css.join('')}`, + removeStyleTags: false, + applyStyleTags: false, + insertPreservedExtraCss: false + }) return finalMJMLDocument } diff --git a/packages/mjml-head-style/.npmignore b/packages/mjml-head-style/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-head-style/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-head-style/README.md b/packages/mjml-head-style/README.md new file mode 100644 index 000000000..3c6ed727b --- /dev/null +++ b/packages/mjml-head-style/README.md @@ -0,0 +1,32 @@ +## mjml-style + +This tag allow you to specify CSS style for your MJML document. Style will be inlined on the final HTML document + + ```xml + + + + .red-color { + color: red; + } + + + + + + + +

Hello World!

+
+
+
+
+
+
+ ``` + +

+ + sexy + +

diff --git a/packages/mjml-head-style/package.json b/packages/mjml-head-style/package.json new file mode 100644 index 000000000..0d312e6c4 --- /dev/null +++ b/packages/mjml-head-style/package.json @@ -0,0 +1,15 @@ +{ + "name": "mjml-head-style", + "description": "mjml-head-style", + "version": "2.0.0", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io" +} diff --git a/packages/mjml-head-style/src/index.js b/packages/mjml-head-style/src/index.js new file mode 100644 index 000000000..6ac0fb6cb --- /dev/null +++ b/packages/mjml-head-style/src/index.js @@ -0,0 +1,8 @@ +export default { + name: "mj-style", + handler: (el, { css }) => { + const innerText = el.children.map(child => child.type === 'text' && child.data).join('') + + css.push(innerText) + } +} diff --git a/packages/mjml/package.json b/packages/mjml/package.json index 79ac6f514..73f13a300 100644 --- a/packages/mjml/package.json +++ b/packages/mjml/package.json @@ -1,7 +1,7 @@ { "name": "mjml", "description": "MJML: the only framework that makes responsive-email easy", - "version": "2.3.0", + "version": "2.2.0", "main": "lib/index.js", "bin": { "mjml": "bin/mjml" @@ -25,6 +25,7 @@ "mjml-group": "^2.0.1", "mjml-head-attributes": "^2.0.5", "mjml-head-font": "^2.0.0", + "mjml-head-style": "^2.0.0", "mjml-head-title": "^2.0.0", "mjml-hero": "^2.0.7", "mjml-html": "^2.0.7", diff --git a/packages/mjml/src/index.js b/packages/mjml/src/index.js index eebc1bee8..1c0d31bfd 100644 --- a/packages/mjml/src/index.js +++ b/packages/mjml/src/index.js @@ -21,6 +21,7 @@ import Text from 'mjml-text' import MJHeadAttributes from 'mjml-head-attributes' import MJHeadFont from 'mjml-head-font' +import MJHeadStyle from 'mjml-head-style' import MJHeadTitle from 'mjml-head-title' const { Hero, HeroContent } = MJHero @@ -52,6 +53,7 @@ const { Navbar, InlineLinks, Link } = MJNavbar; [ MJHeadAttributes, MJHeadFont, + MJHeadStyle, MJHeadTitle ].map( headElement => registerMJHeadElement(headElement.name, headElement.handler)) export * from 'mjml-core' diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 8db441c60..51154baff 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -3,29 +3,13 @@ var mjml = require('./lib/index') console.log(mjml.mjml2html(` - Hello MJML - + + .testage { + color: red; + } + - - - - - Check out promotions ! - - - foo © bar ≠ baz 𝌆 qux - - - - +

Hello Peepz

`, { beautify: true })) From 0d7fa1970e8c502655d0c04681cadd13f98c5013 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 4 Jul 2016 16:51:26 +0200 Subject: [PATCH 08/87] move import --- packages/mjml-core/src/MJMLRenderer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 8b59b65b0..e9f9ee3f1 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -8,6 +8,7 @@ import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' import isEmpty from 'lodash/isEmpty' +import juice from 'juice' import MJMLElementsCollection, { postRenders, registerMJElement } from './MJMLElementsCollection' import React from 'react' import ReactDOMServer from 'react-dom/server' From ec1c9693084c1bff2b86e5ddd762c4d86a51e794 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 4 Jul 2016 17:12:13 +0200 Subject: [PATCH 09/87] Unused + revert test.js --- packages/mjml-core/src/MJMLRenderer.js | 1 - packages/mjml/test.js | 28 ++++++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index e9f9ee3f1..cef70c5bc 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -13,7 +13,6 @@ import MJMLElementsCollection, { postRenders, registerMJElement } from './MJMLEl import React from 'react' import ReactDOMServer from 'react-dom/server' import warning from 'warning' -import juice from 'juice' const debug = require('debug')('mjml-engine/mjml2html') diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 51154baff..8db441c60 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -3,13 +3,29 @@ var mjml = require('./lib/index') console.log(mjml.mjml2html(` - - .testage { - color: red; - } - + Hello MJML + -

Hello Peepz

+ + + + + Check out promotions ! + + + foo © bar ≠ baz 𝌆 qux + + + +
`, { beautify: true })) From 5e24f9218c314c0a737bb25f032cfefb81b09353 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 4 Jul 2016 18:02:17 +0200 Subject: [PATCH 10/87] packagejson --- packages/mjml-core/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index b6cb2b677..818672c1a 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -29,6 +29,7 @@ "immutable": "^3.8.1", "jquery": "^3.0.0", "js-beautify": "^1.6.3", + "juice": "^2.0.0", "lodash": "^4.13.1", "react": "^15.1.0", "react-dom": "^15.1.0", From b4ccdfa965ed9e023d98aefec318fc03181379ac Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 8 Jul 2016 10:40:56 +0200 Subject: [PATCH 11/87] basic XSD output --- packages/mjml-button/src/index.js | 17 +++++++++--- packages/mjml-column/src/index.js | 36 ++++++++++++++++++++------ packages/mjml-core/src/MJMLRenderer.js | 2 ++ packages/mjml-divider/src/index.js | 2 +- packages/mjml-html/src/index.js | 5 +++- packages/mjml-image/src/index.js | 8 +++--- packages/mjml-list/src/index.js | 3 +++ packages/mjml-location/src/index.js | 6 +++-- packages/mjml-section/src/index.js | 27 +++++++++++++------ packages/mjml-social/src/index.js | 2 +- packages/mjml-spacer/src/index.js | 2 +- packages/mjml-table/src/index.js | 2 +- packages/mjml-text/src/index.js | 17 +++++++++--- 13 files changed, 95 insertions(+), 34 deletions(-) diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index 899dc27c7..ade7d6536 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-button' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -34,13 +34,22 @@ const baseStyles = { } } const schemaXsd = () => ( - ` - + ` + + + + + + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` + + + ` ) @MJMLElement diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 2cae68866..a52dfe425 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -3,9 +3,18 @@ import each from 'lodash/each' import merge from 'lodash/merge' import React, { Component } from 'react' import uniq from 'lodash/uniq' +import include from 'lodash/include' const tagName = 'mj-column' -const parentTag = 'mj-section' +const parentTag = ['mj-section'] +const defaultMJMLDefinition = { + attributes: { + 'width': null, + 'background': null, + 'background-color': null, + 'vertical-align': null + } +} const baseStyles = { div: { verticalAlign: 'top' @@ -42,13 +51,24 @@ const postRender = $ => { return $ } const schemaXsd = elements => { - const columnElements = Object.keys(elements).map(element => elements[element].parentTag === tagName ? elements[element].tagName : null).filter(Boolean) - - return ` - - ${(columnElements.map(element => ``).join(`\n`))} - - ` + const columnElements = Object.keys(elements).map(element => include(elements[element].parentTag, tagName) ? elements[element].tagName : null).filter(Boolean) + + return ` + + + ${columnElements.map(elem => ``)} + + + + + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + + + ` } @MJMLElement diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 2b2a1e23f..10c50f77a 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -75,6 +75,8 @@ export default class MJMLRenderer { debug('Start parsing document') this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElementsCollection)).join(`\n`)) + + // console.log(this.schemaXsd) this.content = documentParser(this.content, this.attributes) debug('Content parsed') } diff --git a/packages/mjml-divider/src/index.js b/packages/mjml-divider/src/index.js index 0af8444e8..ddb285435 100644 --- a/packages/mjml-divider/src/index.js +++ b/packages/mjml-divider/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-divider' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const closingTag = false const defaultMJMLDefinition = { attributes: { diff --git a/packages/mjml-html/src/index.js b/packages/mjml-html/src/index.js index 42fe53316..a4ebb0e5c 100644 --- a/packages/mjml-html/src/index.js +++ b/packages/mjml-html/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-html' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -26,6 +26,9 @@ const baseStyles = { const schemaXsd = () => ( ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} diff --git a/packages/mjml-image/src/index.js b/packages/mjml-image/src/index.js index 9d44f6c2b..26571b6fc 100644 --- a/packages/mjml-image/src/index.js +++ b/packages/mjml-image/src/index.js @@ -4,7 +4,7 @@ import min from 'lodash/min' import React, { Component } from 'react' const tagName = 'mj-image' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const endingTag = true const closingTag = false const defaultMJMLDefinition = { @@ -44,7 +44,10 @@ const baseStyles = { const schemaXsd = () => ( ` ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` + + + ` + ) @MJMLElement @@ -87,7 +90,6 @@ class Image extends Component { {mjAttribute('alt')} ( ` + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} diff --git a/packages/mjml-location/src/index.js b/packages/mjml-location/src/index.js index 387b9fd85..e616a456d 100644 --- a/packages/mjml-location/src/index.js +++ b/packages/mjml-location/src/index.js @@ -4,7 +4,7 @@ import MJMLText from 'mjml-text' import React, { Component } from 'react' const tagName = 'mj-location' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const endingTag = true const defaultMJMLDefinition = { attributes: { @@ -26,7 +26,9 @@ const defaultMJMLDefinition = { const schemaXsd = () => ( ` ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` + + + ` ) @MJMLElement diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 8395302df..6745cab74 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -1,10 +1,11 @@ import { MJMLElement, helpers } from 'mjml-core' import cloneDeep from 'lodash/cloneDeep' +import include from "lodash/include" import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-section' -const parentTag = 'mj-container' +const parentTag = ['mj-container'] const defaultMJMLDefinition = { attributes: { 'background-repeat': 'repeat', @@ -80,13 +81,23 @@ const postRender = $ => { return $ } const schemaXsd = elements => { - const sectionElements = Object.keys(elements).map(element => elements[element].parentTag === tagName ? elements[element].tagName : null).filter(Boolean) - - return ` - - ${(sectionElements.map(element => ``).join(`\n`))} - - ` + const sectionElements = Object.keys(elements).map(element => include(elements[element].parentTag, tagName) ? elements[element].tagName : null).filter(Boolean) + + return ` + + ${sectionElements.map(elem => ``)} + + + + + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + + + ` } @MJMLElement diff --git a/packages/mjml-social/src/index.js b/packages/mjml-social/src/index.js index e2d5008f8..8b27167df 100644 --- a/packages/mjml-social/src/index.js +++ b/packages/mjml-social/src/index.js @@ -5,7 +5,7 @@ import clone from 'lodash/clone' import React, { Component } from 'react' const tagName = 'mj-social' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const closingTag = false const defaultMJMLDefinition = { attributes: { diff --git a/packages/mjml-spacer/src/index.js b/packages/mjml-spacer/src/index.js index 9da59a829..c4e6b7126 100644 --- a/packages/mjml-spacer/src/index.js +++ b/packages/mjml-spacer/src/index.js @@ -2,7 +2,7 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-spacer' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const closingTag = false const defaultMJMLDefinition = { attributes: { diff --git a/packages/mjml-table/src/index.js b/packages/mjml-table/src/index.js index 81d0e8ace..5c36be91d 100644 --- a/packages/mjml-table/src/index.js +++ b/packages/mjml-table/src/index.js @@ -2,7 +2,7 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-table' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const endingTag = true const defaultMJMLDefinition = { content: '', diff --git a/packages/mjml-text/src/index.js b/packages/mjml-text/src/index.js index 2018435d1..1a14efbab 100644 --- a/packages/mjml-text/src/index.js +++ b/packages/mjml-text/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-text' -const parentTag = 'mj-column' +const parentTag = ['mj-column'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -28,13 +28,22 @@ const baseStyles = { } } const schemaXsd = () => ( - ` - + ` + + + + + + + + ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` + + + ` ) @MJMLElement From e7dba7bf8ceb38a390a9b5c3b5ba9c2333bf6a56 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 8 Jul 2016 16:40:17 +0200 Subject: [PATCH 12/87] XSD is alive --- packages/mjml-button/src/index.js | 21 +------- packages/mjml-column/src/index.js | 25 +--------- packages/mjml-container/src/index.js | 15 ++---- packages/mjml-core/package.json | 3 +- .../mjml-core/src/MJMLElementsCollection.js | 7 ++- packages/mjml-core/src/MJMLRenderer.js | 5 +- packages/mjml-core/src/configs/defaultXsd.js | 5 +- .../mjml-core/src/decorators/MJMLElement.js | 4 +- packages/mjml-core/src/helpers/xsd.js | 49 +++++++++++++++++++ packages/mjml-divider/src/index.js | 12 ++--- packages/mjml-group/src/index.js | 10 ++++ packages/mjml-hero/src/Hero.js | 2 + packages/mjml-hero/src/HeroContent.js | 2 + packages/mjml-html/src/index.js | 15 +----- packages/mjml-image/src/index.js | 15 ++---- packages/mjml-invoice/src/Invoice.js | 2 + packages/mjml-invoice/src/InvoiceItem.js | 2 +- packages/mjml-list/src/index.js | 15 +----- packages/mjml-location/src/index.js | 12 ++--- packages/mjml-navbar/src/InlineLinks.js | 2 + packages/mjml-navbar/src/Link.js | 2 + packages/mjml-navbar/src/Navbar.js | 2 + packages/mjml-raw/src/index.js | 7 +++ packages/mjml-section/src/index.js | 21 -------- packages/mjml-social/src/index.js | 12 ++--- packages/mjml-spacer/src/index.js | 12 ++--- packages/mjml-table/src/index.js | 12 +---- packages/mjml-text/src/index.js | 21 +------- packages/mjml/test.js | 13 ++--- 29 files changed, 122 insertions(+), 203 deletions(-) create mode 100644 packages/mjml-core/src/helpers/xsd.js diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index ade7d6536..8516eda7b 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-button' -const parentTag = ['mj-column'] +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -33,24 +33,6 @@ const baseStyles = { textDecoration: 'none' } } -const schemaXsd = () => ( - ` - - - - - - - - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - - - ` -) @MJMLElement class Button extends Component { @@ -136,6 +118,5 @@ Button.parentTag = parentTag Button.endingTag = endingTag Button.defaultMJMLDefinition = defaultMJMLDefinition Button.baseStyles = baseStyles -Button.schemaXsd = schemaXsd export default Button diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index a52dfe425..92ec708a5 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -3,10 +3,9 @@ import each from 'lodash/each' import merge from 'lodash/merge' import React, { Component } from 'react' import uniq from 'lodash/uniq' -import include from 'lodash/include' const tagName = 'mj-column' -const parentTag = ['mj-section'] +const parentTag = ['mj-section', 'mj-group'] const defaultMJMLDefinition = { attributes: { 'width': null, @@ -50,26 +49,6 @@ const postRender = $ => { return $ } -const schemaXsd = elements => { - const columnElements = Object.keys(elements).map(element => include(elements[element].parentTag, tagName) ? elements[element].tagName : null).filter(Boolean) - - return ` - - - ${columnElements.map(elem => ``)} - - - - - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - - - ` -} @MJMLElement class Column extends Component { @@ -163,10 +142,10 @@ class Column extends Component { } +Column.defaultMJMLDefinition = defaultMJMLDefinition Column.tagName = tagName Column.parentTag = parentTag Column.baseStyles = baseStyles Column.postRender = postRender -Column.schemaXsd = schemaXsd export default Column diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index 0fc71dfc2..eebe3e6e9 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -2,10 +2,11 @@ import { MJMLElement, helpers, elements } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-container' -const parentTag = 'mj-body' +const parentTag = ['mj-body'] const defaultMJMLDefinition = { attributes: { - 'width': '600px' + 'width': '600px', + 'background-color': null }, inheritedAttributes: [ 'width' @@ -59,15 +60,6 @@ const postRender = $ => { return $ } -const schemaXsd = elements => { - const containerElements = Object.keys(elements).map(element => elements[element].parentTag === tagName ? elements[element].tagName : null).filter(Boolean) - - return ` - - ${(containerElements.map(element => ``).join(`\n`))} - - ` -} @MJMLElement class Container extends Component { @@ -105,7 +97,6 @@ Container.tagName = tagName Container.parentTag = parentTag Container.defaultMJMLDefinition = defaultMJMLDefinition Container.postRender = postRender -Container.schemaXsd = schemaXsd // Support V1.X MJML mj-body elements['mj-body'] = Container diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index 94173ee51..f9678d30c 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -17,7 +17,6 @@ "homepage": "https://mjml.io", "browser": { "cheerio": false, - "fs": false, "html-minifier": false }, "dependencies": { @@ -25,10 +24,12 @@ "classnames": "^2.2.5", "debug": "^2.2.0", "he": "^1.1.0", + "hoist-non-react-statics": "^1.2.0", "html-minifier": "^2.1.5", "immutable": "^3.8.1", "jquery": "^3.0.0", "js-beautify": "^1.6.3", + "libxml-xsd": "^0.5.2", "lodash": "^4.13.1", "react": "^15.1.0", "react-dom": "^15.1.0", diff --git a/packages/mjml-core/src/MJMLElementsCollection.js b/packages/mjml-core/src/MJMLElementsCollection.js index 06b0406d0..80058e7d4 100644 --- a/packages/mjml-core/src/MJMLElementsCollection.js +++ b/packages/mjml-core/src/MJMLElementsCollection.js @@ -1,22 +1,21 @@ import warning from 'warning' +import defaultXsd from './helpers/xsd' -export const closingTags = [] export const endingTags = [] export const postRenders = [] export const schemaXsds = [] export const registerMJElement = Component => { - const { closingTag, endingTag, postRender, tagName, schemaXsd } = Component + const { endingTag, postRender, tagName, schemaXsd } = Component if (!tagName) { return warning(false, 'Component has no tagName') } - closingTag === false && closingTags.push(tagName) endingTag === true && endingTags.push(tagName) postRender && postRenders.push(postRender) - schemaXsd && schemaXsds.push(schemaXsd) + schemaXsds.push(schemaXsd || defaultXsd(Component)) MJMLElementsCollection[tagName] = Component } diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 10c50f77a..92f577610 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -14,6 +14,7 @@ import isBrowser from './helpers/isBrowser' import React from 'react' import ReactDOMServer from 'react-dom/server' import warning from 'warning' +import xsd from 'libxml-xsd' const debug = require('debug')('mjml-engine/mjml2html') @@ -76,7 +77,9 @@ export default class MJMLRenderer { debug('Start parsing document') this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElementsCollection)).join(`\n`)) - // console.log(this.schemaXsd) + const schema = xsd.parse(this.schemaXsd) + console.log(schema.validate(this.content)) // eslint-disable-line no-console + this.content = documentParser(this.content, this.attributes) debug('Content parsed') } diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index 30dadfcb2..a40f4b3a3 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -1,7 +1,6 @@ export default (schemas = '') => ( ` - - + @@ -9,7 +8,7 @@ export default (schemas = '') => ( - + ${schemas} diff --git a/packages/mjml-core/src/decorators/MJMLElement.js b/packages/mjml-core/src/decorators/MJMLElement.js index c1866f9ea..99612dad9 100644 --- a/packages/mjml-core/src/decorators/MJMLElement.js +++ b/packages/mjml-core/src/decorators/MJMLElement.js @@ -6,6 +6,7 @@ import ReactDOMServer from 'react-dom/server' import trim from 'lodash/trim' import merge from 'lodash/merge' import warning from 'warning' +import hoistNonReactStatic from 'hoist-non-react-statics'; const getElementWidth = ({ element, siblings, parentWidth }) => { const { mjml } = element.props @@ -276,8 +277,7 @@ function createComponent (ComposedComponent) { } } - return MJMLElement - + return hoistNonReactStatic(MJMLElement, ComposedComponent) } export default createComponent diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js new file mode 100644 index 000000000..734eee62b --- /dev/null +++ b/packages/mjml-core/src/helpers/xsd.js @@ -0,0 +1,49 @@ +import elements from '../MJMLElementsCollection' +import includes from 'lodash/includes' + +const endingTagXsd = (Component) => { + + return `${Component.selfClosingTag ? '' : ` + + + + + `} + + + ${Component.selfClosingTag ? "" : ` + + `} + ${Object.keys(Component.defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + ${Component.selfClosingTag ? "" : ` + + `} + + + ` +} + +const defaultXsd = (Component) => { + const allowedElements = Object.keys(elements).map(element => includes(elements[element].parentTag, Component.tagName) ? elements[element].tagName : null).filter(Boolean) + + return ` + + + ${allowedElements.map(elem => ``).join(`\n`)} + + + + + + + ${Object.keys(Component.defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} + + + + + ` +} + +export default (Component) => { + return () => Component.endingTag ? endingTagXsd(Component) : defaultXsd(Component) +} diff --git a/packages/mjml-divider/src/index.js b/packages/mjml-divider/src/index.js index ddb285435..5d81faf43 100644 --- a/packages/mjml-divider/src/index.js +++ b/packages/mjml-divider/src/index.js @@ -3,8 +3,8 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-divider' -const parentTag = ['mj-column'] -const closingTag = false +const parentTag = ['mj-column', 'mj-hero-content'] +const selfClosingTag = true const defaultMJMLDefinition = { attributes: { 'align': null, @@ -39,11 +39,6 @@ const postRender = $ => { return $ } -const schemaXsd = () => ( - ` - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` -) @MJMLElement class Divider extends Component { @@ -88,10 +83,9 @@ class Divider extends Component { Divider.tagName = tagName Divider.parentTag = parentTag -Divider.closingTag = closingTag +Divider.selfClosingTag = selfClosingTag Divider.defaultMJMLDefinition = defaultMJMLDefinition Divider.baseStyles = baseStyles Divider.postRender = postRender -Divider.schemaXsd = schemaXsd export default Divider diff --git a/packages/mjml-group/src/index.js b/packages/mjml-group/src/index.js index 088af2344..6b2238e52 100644 --- a/packages/mjml-group/src/index.js +++ b/packages/mjml-group/src/index.js @@ -3,6 +3,14 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-group' +const parentTag = ['mj-section'] +const defaultMJMLDefinition = { + attributes: { + 'width': null, + 'background-color': null, + 'vertical-align': null + } +} const baseStyles = { div: { verticalAlign: 'top' @@ -111,5 +119,7 @@ class Group extends Component { Group.tagName = tagName Group.baseStyles = baseStyles Group.postRender = postRender +Group.parentTag = parentTag +Group.defaultMJMLDefinition = defaultMJMLDefinition export default Group diff --git a/packages/mjml-hero/src/Hero.js b/packages/mjml-hero/src/Hero.js index d93dc76f2..e838c2de7 100644 --- a/packages/mjml-hero/src/Hero.js +++ b/packages/mjml-hero/src/Hero.js @@ -3,6 +3,7 @@ import React, { Component } from 'react' import merge from 'lodash/merge' const tagName = 'mj-hero' +const parentTag = ['mj-container'] const defaultMJMLDefinition = { attributes: { 'mode': 'fixed-height', @@ -254,5 +255,6 @@ Hero.defaultMJMLDefinition = defaultMJMLDefinition Hero.endingTag = endingTag Hero.baseStyles = baseStyles Hero.postRender = postRender +Hero.parentTag = parentTag export default Hero diff --git a/packages/mjml-hero/src/HeroContent.js b/packages/mjml-hero/src/HeroContent.js index 16df54847..6ebeb86e7 100644 --- a/packages/mjml-hero/src/HeroContent.js +++ b/packages/mjml-hero/src/HeroContent.js @@ -3,6 +3,7 @@ import React, { Component } from 'react' import merge from 'lodash/merge' const tagName = 'mj-hero-content' +const parentTag = ['mj-hero'] const defaultMJMLDefinition = { attributes: { 'width': '100%', @@ -114,5 +115,6 @@ HeroContent.defaultMJMLDefinition = defaultMJMLDefinition HeroContent.endingTag = endingTag HeroContent.baseStyles = baseStyles HeroContent.postRender = postRender +HeroContent.parentTag = parentTag export default HeroContent diff --git a/packages/mjml-html/src/index.js b/packages/mjml-html/src/index.js index a4ebb0e5c..f5bc2c0dd 100644 --- a/packages/mjml-html/src/index.js +++ b/packages/mjml-html/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-html' -const parentTag = ['mj-column'] +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -23,18 +23,6 @@ const baseStyles = { fontSize: '13px' } } -const schemaXsd = () => ( - ` - - - - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - ` -) @MJMLElement class Html extends Component { @@ -62,6 +50,5 @@ Html.parentTag = parentTag Html.endingTag = endingTag Html.defaultMJMLDefinition = defaultMJMLDefinition Html.baseStyles = baseStyles -Html.schemaXsd = schemaXsd export default Html diff --git a/packages/mjml-image/src/index.js b/packages/mjml-image/src/index.js index 26571b6fc..ade62c193 100644 --- a/packages/mjml-image/src/index.js +++ b/packages/mjml-image/src/index.js @@ -4,9 +4,9 @@ import min from 'lodash/min' import React, { Component } from 'react' const tagName = 'mj-image' -const parentTag = ['mj-column'] +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true -const closingTag = false +const selfClosingTag = true const defaultMJMLDefinition = { attributes: { 'align': 'center', @@ -41,14 +41,6 @@ const baseStyles = { width: '100%' } } -const schemaXsd = () => ( - ` - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - ` - -) @MJMLElement class Image extends Component { @@ -135,9 +127,8 @@ class Image extends Component { Image.tagName = tagName Image.parentTag = parentTag Image.endingTag = endingTag -Image.closingTag = closingTag +Image.selfClosingTag = selfClosingTag Image.defaultMJMLDefinition = defaultMJMLDefinition Image.baseStyles = baseStyles -Image.schemaXsd = schemaXsd export default Image diff --git a/packages/mjml-invoice/src/Invoice.js b/packages/mjml-invoice/src/Invoice.js index 27676e807..f6314a94a 100644 --- a/packages/mjml-invoice/src/Invoice.js +++ b/packages/mjml-invoice/src/Invoice.js @@ -6,6 +6,7 @@ import numeral from 'numeral' import React, { Component } from 'react' const tagName = 'mj-invoice' +const parentTag = ['mj-column'] const defaultMJMLDefinition = { attributes: { 'border': '1px solid #ecedee', @@ -154,6 +155,7 @@ class Invoice extends Component { } Invoice.tagName = tagName +Invoice.parentTag = parentTag Invoice.defaultMJMLDefinition = defaultMJMLDefinition Invoice.baseStyles = baseStyles Invoice.intl = intl diff --git a/packages/mjml-invoice/src/InvoiceItem.js b/packages/mjml-invoice/src/InvoiceItem.js index 94b8d576b..44905ef2e 100644 --- a/packages/mjml-invoice/src/InvoiceItem.js +++ b/packages/mjml-invoice/src/InvoiceItem.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-invoice-item' -const parentTag = 'mj-invoice' +const parentTag = ['mj-invoice'] const endingTag = true const defaultMJMLDefinition = { attributes: { diff --git a/packages/mjml-list/src/index.js b/packages/mjml-list/src/index.js index ead4f6226..b04ed5ad4 100644 --- a/packages/mjml-list/src/index.js +++ b/packages/mjml-list/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-list' -const parentTag = 'mj-column' +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -29,18 +29,6 @@ const baseStyles = { textAlign: 'left' } } -const schemaXsd = () => ( - ` - - - - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - ` -) @MJMLElement class List extends Component { @@ -77,6 +65,5 @@ List.parentTag = parentTag List.endingTag = endingTag List.defaultMJMLDefinition = defaultMJMLDefinition List.baseStyles = baseStyles -List.schemaXsd = schemaXsd export default List diff --git a/packages/mjml-location/src/index.js b/packages/mjml-location/src/index.js index e616a456d..c7e1a5bdc 100644 --- a/packages/mjml-location/src/index.js +++ b/packages/mjml-location/src/index.js @@ -4,8 +4,9 @@ import MJMLText from 'mjml-text' import React, { Component } from 'react' const tagName = 'mj-location' -const parentTag = ['mj-column'] +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true +const selfClosingTag = true const defaultMJMLDefinition = { attributes: { 'align': null, @@ -23,13 +24,6 @@ const defaultMJMLDefinition = { 'vertical-align': null } } -const schemaXsd = () => ( - ` - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - ` -) @MJMLElement class Location extends Component { @@ -102,6 +96,6 @@ Location.tagName = tagName Location.parentTag = parentTag Location.endingTag = endingTag Location.defaultMJMLDefinition = defaultMJMLDefinition -Location.schemaXsd = schemaXsd +Location.selfClosingTag = selfClosingTag export default Location diff --git a/packages/mjml-navbar/src/InlineLinks.js b/packages/mjml-navbar/src/InlineLinks.js index 5951c0035..5bab51ac1 100644 --- a/packages/mjml-navbar/src/InlineLinks.js +++ b/packages/mjml-navbar/src/InlineLinks.js @@ -5,6 +5,7 @@ import React, { Component } from 'react' import crypto from 'crypto' const tagName = 'mj-inline-links' +const parentTag = ['mj-column'] const defaultMJMLDefinition = { attributes: { 'align': 'center', @@ -182,5 +183,6 @@ InlineLinks.tagName = tagName InlineLinks.defaultMJMLDefinition = defaultMJMLDefinition InlineLinks.baseStyles = baseStyles InlineLinks.postRender = postRender +InlineLinks.parentTag = parentTag export default InlineLinks diff --git a/packages/mjml-navbar/src/Link.js b/packages/mjml-navbar/src/Link.js index c0f6be5b7..982498d01 100644 --- a/packages/mjml-navbar/src/Link.js +++ b/packages/mjml-navbar/src/Link.js @@ -3,6 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-link' +const parentTag = ['mj-inline-links'] const defaultMJMLDefinition = { attributes: { 'padding': '15px 10px', @@ -85,5 +86,6 @@ Link.defaultMJMLDefinition = defaultMJMLDefinition Link.baseStyles = baseStyles Link.endingTag = endingTag Link.postRender = postRender +Link.parentTag = parentTag export default Link diff --git a/packages/mjml-navbar/src/Navbar.js b/packages/mjml-navbar/src/Navbar.js index f9fe54a2c..59104ccaa 100644 --- a/packages/mjml-navbar/src/Navbar.js +++ b/packages/mjml-navbar/src/Navbar.js @@ -3,6 +3,7 @@ import MJMLSection from 'mjml-section' import React, { Component } from 'react' const tagName = 'mj-navbar' +const parentTag = ['mj-container'] const defaultMJMLDefinition = { attributes: { 'navbar-hamburger': '', @@ -28,5 +29,6 @@ class Navbar extends Component { Navbar.tagName = tagName Navbar.defaultMJMLDefinition = defaultMJMLDefinition +Navbar.parentTag = parentTag export default Navbar diff --git a/packages/mjml-raw/src/index.js b/packages/mjml-raw/src/index.js index fe73e3ab6..726c1814a 100644 --- a/packages/mjml-raw/src/index.js +++ b/packages/mjml-raw/src/index.js @@ -2,8 +2,13 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-raw' +const parentTag = ['mj-body', 'mj-container', 'mj-section', 'mj-column'] const endingTag = true const rawElement = true +const defaultMJMLDefinition = { + attributes: { + } +} const postRender = $ => { $('.mj-raw').each(function () { $(this).replaceWith($(this).html()) @@ -39,8 +44,10 @@ class Raw extends Component { } Raw.tagName = tagName +Raw.parentTag = parentTag Raw.endingTag = endingTag Raw.rawElement = rawElement Raw.postRender = postRender +Raw.defaultMJMLDefinition = defaultMJMLDefinition export default Raw diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 6745cab74..5ab5b69e2 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -1,6 +1,5 @@ import { MJMLElement, helpers } from 'mjml-core' import cloneDeep from 'lodash/cloneDeep' -import include from "lodash/include" import merge from 'lodash/merge' import React, { Component } from 'react' @@ -80,25 +79,6 @@ const postRender = $ => { return $ } -const schemaXsd = elements => { - const sectionElements = Object.keys(elements).map(element => include(elements[element].parentTag, tagName) ? elements[element].tagName : null).filter(Boolean) - - return ` - - ${sectionElements.map(elem => ``)} - - - - - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - - - ` -} @MJMLElement class Section extends Component { @@ -201,6 +181,5 @@ Section.parentTag = parentTag Section.defaultMJMLDefinition = defaultMJMLDefinition Section.baseStyles = baseStyles Section.postRender = postRender -Section.schemaXsd = schemaXsd export default Section diff --git a/packages/mjml-social/src/index.js b/packages/mjml-social/src/index.js index 8b27167df..15c70cedb 100644 --- a/packages/mjml-social/src/index.js +++ b/packages/mjml-social/src/index.js @@ -5,8 +5,8 @@ import clone from 'lodash/clone' import React, { Component } from 'react' const tagName = 'mj-social' -const parentTag = ['mj-column'] -const closingTag = false +const parentTag = ['mj-column', 'mj-hero-content'] +const selfClosingTag = true const defaultMJMLDefinition = { attributes: { 'align': 'center', @@ -124,11 +124,6 @@ const postRender = $ => { return $ } -const schemaXsd = () => ( - ` - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` -) @MJMLElement class Social extends Component { @@ -309,11 +304,10 @@ class Social extends Component { Social.tagName = tagName Social.parentTag = parentTag -Social.closingTag = closingTag +Social.selfClosingTag = selfClosingTag Social.defaultMJMLDefinition = defaultMJMLDefinition Social.baseStyles = baseStyles Social.buttonDefinitions = buttonDefinitions Social.postRender = postRender -Social.schemaXsd = schemaXsd export default Social diff --git a/packages/mjml-spacer/src/index.js b/packages/mjml-spacer/src/index.js index c4e6b7126..474019891 100644 --- a/packages/mjml-spacer/src/index.js +++ b/packages/mjml-spacer/src/index.js @@ -2,8 +2,8 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-spacer' -const parentTag = ['mj-column'] -const closingTag = false +const parentTag = ['mj-column', 'mj-hero-content'] +const selfClosingTag = true const defaultMJMLDefinition = { attributes: { 'align': null, @@ -16,11 +16,6 @@ const defaultMJMLDefinition = { 'vertical-align': null } } -const schemaXsd = () => ( - ` - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ` -) @MJMLElement class Spacer extends Component { @@ -50,8 +45,7 @@ class Spacer extends Component { Spacer.tagName = tagName Spacer.parentTag = parentTag -Spacer.closingTag = closingTag +Spacer.selfClosingTag = selfClosingTag Spacer.defaultMJMLDefinition = defaultMJMLDefinition -Spacer.schemaXsd = schemaXsd export default Spacer diff --git a/packages/mjml-table/src/index.js b/packages/mjml-table/src/index.js index 5c36be91d..c33b4d396 100644 --- a/packages/mjml-table/src/index.js +++ b/packages/mjml-table/src/index.js @@ -2,7 +2,7 @@ import { MJMLElement } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-table' -const parentTag = ['mj-column'] +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -23,15 +23,6 @@ const defaultMJMLDefinition = { 'width': '100%' } } -const schemaXsd = () => ( - ` - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - ` -) @MJMLElement class Table extends Component { @@ -74,6 +65,5 @@ Table.tagName = tagName Table.parentTag = parentTag Table.endingTag = endingTag Table.defaultMJMLDefinition = defaultMJMLDefinition -Table.schemaXsd = schemaXsd export default Table diff --git a/packages/mjml-text/src/index.js b/packages/mjml-text/src/index.js index 1a14efbab..e1c88ab47 100644 --- a/packages/mjml-text/src/index.js +++ b/packages/mjml-text/src/index.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-text' -const parentTag = ['mj-column'] +const parentTag = ['mj-column', 'mj-hero-content'] const endingTag = true const defaultMJMLDefinition = { content: '', @@ -27,24 +27,6 @@ const baseStyles = { cursor: 'auto' } } -const schemaXsd = () => ( - ` - - - - - - - - - - ${Object.keys(defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - - - ` -) @MJMLElement class Text extends Component { @@ -85,6 +67,5 @@ Text.parentTag = parentTag Text.endingTag = endingTag Text.defaultMJMLDefinition = defaultMJMLDefinition Text.baseStyles = baseStyles -Text.schemaXsd = schemaXsd export default Text diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 9e4f90af6..14926ec3e 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -4,14 +4,11 @@ console.log(mjml.mjml2html(` - - - - - - - - + + + + + `, { beautify: true })) From 02200d83e403cdc66ed393dc0b68798f08fd0e2c Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 8 Jul 2016 17:08:06 +0200 Subject: [PATCH 13/87] merge errors --- doc/document.md | 46 -------------------------------------- packages/mjml/src/index.js | 4 ---- 2 files changed, 50 deletions(-) delete mode 100644 doc/document.md diff --git a/doc/document.md b/doc/document.md deleted file mode 100644 index ff2cbac97..000000000 --- a/doc/document.md +++ /dev/null @@ -1,46 +0,0 @@ -# MJML document - -An MJML Document starts with a `` tag, it can contains only `mj-head` and `mj-body` tags. Both have the same purpose of `head` and `body` in a HTML document. - -## MJ-Head - -Mj-Head contains everything related to the document such as style and meta element. It supports custom head elements and can be registered through `registerMJHeadElement( name, handler)` api from `mjml-core`, it acts as a pre-render hook. - - -## MJ-Body - -Mj-Body contains everything related to the content of your email. It supports custom elements too and can be registered either through `registerMJElement( class)` api from `mjml-core` or via a `.mjmlconfig` file. Non-known element from `mjml-core` are simply ignored. Note that `mj-body` should have only one root element due to how React work. - - -## MJ-Inlcude - -The mjml-core package allows you to include external mjml files to build your email template. - -`header.mjml` -``` xml - - - This is a header - - -``` - -You can wrap your external mjml files inside the default `mjml > mj-body > mj-container` -tags to make it easier to preview outside the main template - -`main.mjml` -``` xml - - - - - - - -``` - -The mjml engine will then replace your included files before starting the rendering process - - diff --git a/packages/mjml/src/index.js b/packages/mjml/src/index.js index d8dbec124..eebc1bee8 100644 --- a/packages/mjml/src/index.js +++ b/packages/mjml/src/index.js @@ -54,8 +54,4 @@ const { Navbar, InlineLinks, Link } = MJNavbar; MJHeadFont, MJHeadTitle ].map( headElement => registerMJHeadElement(headElement.name, headElement.handler)) -registerMJHeadElement('mj-attributes', MJHeadAttributes) -registerMJHeadElement('mj-font', MJHeadFont) -registerMJHeadElement('mj-title', MJHeadTitle) - export * from 'mjml-core' From 955c580052f420f1b97f5e31e00129f885d21d76 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 11 Jul 2016 11:29:24 +0200 Subject: [PATCH 14/87] revert some merge issue --- packages/mjml-cli/src/client.js | 4 ++-- packages/mjml-container/src/index.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index 5d055bf24..a750bf09e 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -1,4 +1,4 @@ -import { MJMLRenderer } from 'mjml-core' +import { MJMLRenderer, version } from 'mjml-core' import camelCase from 'lodash/camelCase' import createComponent from './createComponent' import fs from 'fs' @@ -10,7 +10,7 @@ import upperFirst from 'lodash/upperFirst' * The version number is the NPM * version number. It should be the same as the MJML engine */ -export const version = () => require('../package.json').version +export { version } /* * Turns a callback style to a Promise style one diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index eebe3e6e9..47e583b4b 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -77,8 +77,8 @@ class Container extends Component { } render () { - const { defaultUnit, mjAttribute, children } = this.props - const { width } = helpers.widthParser(defaultUnit(mjAttribute('width'))) + const { renderWrappedOutlookChildren, mjAttribute, children } = this.props + const { width } = helpers.widthParser(mjAttribute('width')) return (
- {children} + {renderWrappedOutlookChildren(children)}
) } From e64455bad037631529ad9d3a243b62cb1986ebb7 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 11 Jul 2016 17:57:45 +0200 Subject: [PATCH 15/87] Support mj head + move on document parser --- packages/mjml-core/src/MJMLRenderer.js | 9 +-------- packages/mjml-core/src/configs/defaultXsd.js | 8 ++++++++ packages/mjml-core/src/parsers/document.js | 17 +++++++++++++++-- packages/mjml-group/src/index.js | 8 ++++++++ packages/mjml-section/src/index.js | 15 ++++++++++++--- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 92f577610..f19c1c850 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -3,18 +3,16 @@ import { fixLegacyAttrs, removeCDATA } from './helpers/postRender' import { parseInstance } from './helpers/mjml' import cloneDeep from 'lodash/cloneDeep' import defaultContainer from './configs/defaultContainer' -import defaultXsd from './configs/defaultXsd' import defaultFonts from './configs/listFontsImports' import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' import isEmpty from 'lodash/isEmpty' -import MJMLElementsCollection, { schemaXsds, postRenders, registerMJElement } from './MJMLElementsCollection' +import MJMLElementsCollection, { postRenders, registerMJElement } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' import React from 'react' import ReactDOMServer from 'react-dom/server' import warning from 'warning' -import xsd from 'libxml-xsd' const debug = require('debug')('mjml-engine/mjml2html') @@ -75,11 +73,6 @@ export default class MJMLRenderer { const documentParser = require('./parsers/document').default debug('Start parsing document') - this.schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElementsCollection)).join(`\n`)) - - const schema = xsd.parse(this.schemaXsd) - console.log(schema.validate(this.content)) // eslint-disable-line no-console - this.content = documentParser(this.content, this.attributes) debug('Content parsed') } diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index a40f4b3a3..6d959de6a 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -3,9 +3,15 @@ export default (schemas = '') => ( + + + + + + @@ -13,5 +19,7 @@ export default (schemas = '') => ( ${schemas} + + ` ) diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index a67e45b21..047756aad 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -3,9 +3,11 @@ import compact from 'lodash/compact' import dom from '../helpers/dom' import each from 'lodash/each' import filter from 'lodash/filter' -import MJMLElements, { endingTags } from '../MJMLElementsCollection' +import MJMLElements, { endingTags, schemaXsds } from '../MJMLElementsCollection' import MJMLHeadElements from '../MJMLHead' import warning from 'warning' +import defaultXsd from '../configs/defaultXsd' +import libXsd from 'libxml-xsd' const regexTag = tag => new RegExp(`<${tag}([^>]*)>([^]*?)`, 'gmi') @@ -74,6 +76,13 @@ const parseHead = (head, attributes) => { attributes.container = dom.getHTML($container) } +const validateDocument = (content) => { + const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) + + const schema = libXsd.parse(schemaXsd) + console.log(schema.validate(content)) // eslint-disable-line no-console +} + /** * Import an html document containing some mjml * returns JSON @@ -84,8 +93,12 @@ const documentParser = (content, attributes) => { let root let head + const safeContent = safeEndingTags(content) + + validateDocument(safeContent) + try { - const $ = dom.parseXML(safeEndingTags(content)) + const $ = dom.parseXML(safeContent) root = $('mjml > mj-body') head = $('mjml > mj-head') diff --git a/packages/mjml-group/src/index.js b/packages/mjml-group/src/index.js index 088af2344..1d2d78872 100644 --- a/packages/mjml-group/src/index.js +++ b/packages/mjml-group/src/index.js @@ -3,6 +3,13 @@ import merge from 'lodash/merge' import React, { Component } from 'react' const tagName = 'mj-group' +const defaultMJMLDefinition = { + attributes: { + 'width': null, + 'background-color': null, + 'vertical-align': null + } +} const baseStyles = { div: { verticalAlign: 'top' @@ -111,5 +118,6 @@ class Group extends Component { Group.tagName = tagName Group.baseStyles = baseStyles Group.postRender = postRender +Group.defaultMJMLDefinition = defaultMJMLDefinition export default Group diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 5ab5b69e2..59ffa5028 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -7,9 +7,18 @@ const tagName = 'mj-section' const parentTag = ['mj-container'] const defaultMJMLDefinition = { attributes: { - 'background-repeat': 'repeat', - 'padding': '20px 0px', - 'background-size': 'auto' + "background-color": null, + "background-url": null, + "background-repeat": "repeat", + "background-size": "auto", + "full-width": null, + "padding": "20px 0", + "padding-top": null, + "padding-bottom": null, + "padding-left": null, + "padding-right": null, + "text-align": "center", + "vertical-align": "top" } } const baseStyles = { From b0d52af5b28f68ad22d17a6942f647b760bd9036 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 21 Jul 2016 16:37:04 +0200 Subject: [PATCH 16/87] XSD errors are finally parsed... --- packages/mjml-button/src/index.js | 38 +++++++------- packages/mjml-container/src/index.js | 5 +- packages/mjml-core/src/helpers/xsd.js | 58 ++++++++++++++++++++++ packages/mjml-core/src/parsers/document.js | 20 ++++---- 4 files changed, 90 insertions(+), 31 deletions(-) diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index f001c534d..8718fbd74 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -8,24 +8,26 @@ const endingTag = true const defaultMJMLDefinition = { content: '', attributes: { - 'align': 'center', - 'background-color': '#414141', - 'border': null, - 'border-radius': '3px', - 'color': '#ffffff', - 'container-background-color': null, - 'font-family': 'Ubuntu, Helvetica, Arial, sans-serif', - 'font-size': '13px', - 'font-weight': 'normal', - 'href': '', - 'padding-bottom': null, - 'padding-left': null, - 'padding-right': null, - 'padding-top': null, - 'padding': '10px 25px', - 'inner-padding': '10px 25px', - 'text-decoration': 'none', - 'vertical-align': 'middle' + "background-color": "#414141", + "border-radius": "3px", + "container-background-color": null, + "font-style": null, + "font-size": "13px", + "font-weight": "bold", + "font-family": "Ubuntu, Helvetica, Arial, sans-serif", + "color": "#ffffff", + "border": "none", + "text-decoration": "none", + "text-transform": "none", + "align": "center", + "vertical-align": "middle", + "href": null, + "inner-padding": "15px 25px", + "padding": "15px 25px", + "padding-top": null, + "padding-bottom": null, + "padding-left": null, + "padding-right": null } } const baseStyles = { diff --git a/packages/mjml-container/src/index.js b/packages/mjml-container/src/index.js index 47e583b4b..12671131a 100644 --- a/packages/mjml-container/src/index.js +++ b/packages/mjml-container/src/index.js @@ -1,4 +1,4 @@ -import { MJMLElement, helpers, elements } from 'mjml-core' +import { MJMLElement, helpers } from 'mjml-core' import React, { Component } from 'react' const tagName = 'mj-container' @@ -98,7 +98,4 @@ Container.parentTag = parentTag Container.defaultMJMLDefinition = defaultMJMLDefinition Container.postRender = postRender -// Support V1.X MJML mj-body -elements['mj-body'] = Container - export default Container diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js index 734eee62b..2d089a31a 100644 --- a/packages/mjml-core/src/helpers/xsd.js +++ b/packages/mjml-core/src/helpers/xsd.js @@ -1,6 +1,64 @@ import elements from '../MJMLElementsCollection' import includes from 'lodash/includes' +class XsdError { + constructor (errors) { + this.errors = errors + } + + defaultError ({line, column, code, message}) { + return this.formatLine(line, column, `Uknown MJML error, please open an issue on github.com/mjmlio/mjml with code: ${code} and message: ${message}`) + } + + formatLine (line, column, message) { + return { line, column, message } + } + + format (error) { + const cleanedErrorMessage = error.message.replace(/'/gmi, '"') + const errorFormater = XsdError.CODES[error.code] + + if (!errorFormater) { + return this.defaultError(error) + } + + const { regexp, message } = errorFormater + const matching = [] + let matched + + do { + matched = regexp.exec(cleanedErrorMessage) + + if (matched) { + matching.push(matched[1].trim()) + } + } while (matched) + + if (matching.length < 1) { + return this.defaultError(error) + } + + return this.formatLine(error.line, error.column, matching.reduce((finalMessage, value, index) => finalMessage.replace(`$${index}`, value), message)) + } + + getErrors () { + return this.errors.map((error) => this.format(error)) + } +} + +XsdError.CODES = { + "1866": { + regexp: /"(.*?)"/gmi, + message: `$0 : has no attribute "$1"` + }, + "1871": { + regexp: /[\(|"](.*?)["|\)]/gmi, + message: `$0 is not allowed here, only $1 are accepted` + } +} + +export { XsdError } + const endingTagXsd = (Component) => { return `${Component.selfClosingTag ? '' : ` diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 047756aad..fa701dd19 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -7,6 +7,7 @@ import MJMLElements, { endingTags, schemaXsds } from '../MJMLElementsCollection' import MJMLHeadElements from '../MJMLHead' import warning from 'warning' import defaultXsd from '../configs/defaultXsd' +import { XsdError } from '../helpers/xsd' import libXsd from 'libxml-xsd' const regexTag = tag => new RegExp(`<${tag}([^>]*)>([^]*?)`, 'gmi') @@ -80,7 +81,11 @@ const validateDocument = (content) => { const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) const schema = libXsd.parse(schemaXsd) - console.log(schema.validate(content)) // eslint-disable-line no-console + const errors = schema.validate(content) + + if (errors.length > 0) { + throw new XsdError(errors) + } } /** @@ -90,22 +95,17 @@ const validateDocument = (content) => { * - mjml: a json representation of the mjml */ const documentParser = (content, attributes) => { - let root - let head - const safeContent = safeEndingTags(content) - validateDocument(safeContent) + let root + let head try { const $ = dom.parseXML(safeContent) root = $('mjml > mj-body') head = $('mjml > mj-head') - if (root.length < 1) { - root = $('mj-body').get(0) - warning(false, 'Please upgrade your MJML markup to add a root tag, as root will no longer be supported soon, see https://github.com/mjmlio/mjml/blob/master/UPGRADE.md') - } else { + if (root.length > 0) { root = root.children().get(0) } } catch (e) { @@ -116,6 +116,8 @@ const documentParser = (content, attributes) => { throw new EmptyMJMLError('No root "" or "" found in the file') } + validateDocument(safeContent) + if (head && head.length === 1) { parseHead(head.get(0), attributes) } From 2fba7d68eee57f4d9823de9f31e324e79ff8fbba Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 25 Jul 2016 17:28:13 +0200 Subject: [PATCH 17/87] client now display errors properly --- packages/mjml-cli/src/client.js | 9 ++++++++- packages/mjml-core/src/helpers/xsd.js | 10 +++++++--- packages/mjml-core/src/parsers/document.js | 2 +- packages/mjml/test.js | 17 +++++++++++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index a750bf09e..4a7552de3 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -58,7 +58,14 @@ const render = (bufferPromise, { min, output, stdout }) => { bufferPromise .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min }).render()) .then(result => stdout ? process.stdout.write(result) : write(output, result)) - .catch(error) + .catch(e => { + // XSD validation error ? + if (e.getErrors) { + return error(e.getErrors().map( v => `Line ${v.line}: ${v.message}` ).join('\n')) + } + + return error(e) + }) } /* diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js index 2d089a31a..9e38dc3b3 100644 --- a/packages/mjml-core/src/helpers/xsd.js +++ b/packages/mjml-core/src/helpers/xsd.js @@ -47,13 +47,17 @@ class XsdError { } XsdError.CODES = { + "1843": { + regexp: /"(.*?)"/gmi, + message: `Plain text content inside "$0" isn't allowed` + }, "1866": { regexp: /"(.*?)"/gmi, - message: `$0 : has no attribute "$1"` + message: `Tag "$0" has no attribute "$1"` }, "1871": { regexp: /[\(|"](.*?)["|\)]/gmi, - message: `$0 is not allowed here, only $1 are accepted` + message: `Tag "$0" is not allowed here, only "$1" are accepted` } } @@ -85,7 +89,7 @@ const defaultXsd = (Component) => { const allowedElements = Object.keys(elements).map(element => includes(elements[element].parentTag, Component.tagName) ? elements[element].tagName : null).filter(Boolean) return ` - + ${allowedElements.map(elem => ``).join(`\n`)} diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index fa701dd19..d4d7ee971 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -83,7 +83,7 @@ const validateDocument = (content) => { const schema = libXsd.parse(schemaXsd) const errors = schema.validate(content) - if (errors.length > 0) { + if (errors && errors.length > 0) { throw new XsdError(errors) } } diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 8db441c60..447f2827f 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,6 +1,6 @@ var mjml = require('./lib/index') - -console.log(mjml.mjml2html(` +try { +const azeaze = mjml.mjml2html(` Hello MJML @@ -11,11 +11,12 @@ console.log(mjml.mjml2html(` @@ -28,4 +29,12 @@ console.log(mjml.mjml2html(` -`, { beautify: true })) +`, { beautify: true }) +} catch(e) { + if (e.getErrors) { + console.log(e.getErrors()) + } else { + throw e + } + +} From 0f53b49f206332b5941f4fea1beadec6f938c060 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 28 Jul 2016 14:28:04 +0200 Subject: [PATCH 18/87] Fix default xsd mj-body + #317 --- packages/mjml-cli/src/client.js | 36 ++++++++++++++------ packages/mjml-core/src/configs/defaultXsd.js | 13 ++++--- packages/mjml-core/src/helpers/xsd.js | 4 +++ packages/mjml-core/src/parsers/document.js | 14 ++++---- packages/mjml-core/test/MockComponent.js | 5 +++ packages/mjml-core/test/MockListComponent.js | 4 +++ 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index 4a7552de3..f5a3f0b56 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -26,6 +26,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 */ @@ -54,14 +63,14 @@ const readStdin = promisify(stdinToBuffer) /* * Render an input promise */ -const render = (bufferPromise, { min, output, stdout }) => { +const render = (bufferPromise, { min, output, stdout, fileName }) => { bufferPromise .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min }).render()) .then(result => stdout ? process.stdout.write(result) : write(output, result)) .catch(e => { // XSD validation error ? if (e.getErrors) { - return error(e.getErrors().map( v => `Line ${v.line}: ${v.message}` ).join('\n')) + return error(`${fileName ? `File: ${fileName} \n` : ``}${e.getMessages()}`) } return error(e) @@ -73,28 +82,33 @@ 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') + const inputExtension = path.extname(inFile) 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}${inputExtension}` } 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 }) }) } diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index 6d959de6a..12c3e4888 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -1,5 +1,10 @@ -export default (schemas = '') => ( - ` +import elements from '../MJMLElementsCollection' +import includes from 'lodash/includes' + +export default (schemas = '') => { + const allowedElements = Object.keys(elements).map(element => includes(elements[element].parentTag, 'mj-body') ? elements[element].tagName : null).filter(Boolean) + + return ` @@ -14,7 +19,7 @@ export default (schemas = '') => ( - + ${allowedElements.map(elem => ``).join(`\n`)} ${schemas} @@ -22,4 +27,4 @@ export default (schemas = '') => ( ` -) +} diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js index 9e38dc3b3..f11d7d725 100644 --- a/packages/mjml-core/src/helpers/xsd.js +++ b/packages/mjml-core/src/helpers/xsd.js @@ -44,6 +44,10 @@ class XsdError { getErrors () { return this.errors.map((error) => this.format(error)) } + + getMessages () { + return this.getErrors().map( v => `Line ${v.line}: ${v.message}` ).join('\n') + } } XsdError.CODES = { diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index d4d7ee971..8b62a9e1a 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -97,22 +97,24 @@ const validateDocument = (content) => { const documentParser = (content, attributes) => { const safeContent = safeEndingTags(content) - let root + // let root + let body let head try { const $ = dom.parseXML(safeContent) - root = $('mjml > mj-body') + body = $('mjml > mj-body') + // root = $('mjml') head = $('mjml > mj-head') - if (root.length > 0) { - root = root.children().get(0) + if (body.length > 0) { + body = body.children().get(0) } } catch (e) { throw new ParseError('Error while parsing the file') } - if (!root || root.length < 1) { + if (!body || body.length < 1) { throw new EmptyMJMLError('No root "" or "" found in the file') } @@ -122,7 +124,7 @@ const documentParser = (content, attributes) => { parseHead(head.get(0), attributes) } - return mjmlElementParser(root) + return mjmlElementParser(body) } export default documentParser diff --git a/packages/mjml-core/test/MockComponent.js b/packages/mjml-core/test/MockComponent.js index 90e48d574..94b10c0e1 100644 --- a/packages/mjml-core/test/MockComponent.js +++ b/packages/mjml-core/test/MockComponent.js @@ -3,7 +3,11 @@ import React, { Component } from 'react' const tagName = 'mj-mock' const endingTag = true +const parentTag = ['mj-mock-list'] const defaultMJMLDefinition = { + attributes: { + + } } @MJMLElement @@ -19,6 +23,7 @@ class MockComponent extends Component { MockComponent.tagName = tagName MockComponent.endingTag = endingTag +MockComponent.parentTag = parentTag MockComponent.defaultMJMLDefinition = defaultMJMLDefinition export default MockComponent diff --git a/packages/mjml-core/test/MockListComponent.js b/packages/mjml-core/test/MockListComponent.js index facb75fbf..526af8e74 100644 --- a/packages/mjml-core/test/MockListComponent.js +++ b/packages/mjml-core/test/MockListComponent.js @@ -2,7 +2,10 @@ import { MJMLElement } from '../src/index' import React, { Component } from 'react' const tagName = 'mj-mock-list' +const parentTag = ['mj-body'] const defaultMJMLDefinition = { + attributes: { + } } @MJMLElement @@ -17,6 +20,7 @@ class MockListComponent extends Component { } MockListComponent.tagName = tagName +MockListComponent.parentTag = parentTag MockListComponent.defaultMJMLDefinition = defaultMJMLDefinition export default MockListComponent From 9291cac5d8cc95be86e77935d7dfed11349acea8 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 28 Jul 2016 17:40:34 +0200 Subject: [PATCH 19/87] validation document attribute --- packages/mjml-core/src/configs/defaultXsd.js | 17 ++++++++++++++++- packages/mjml-core/src/helpers/xsd.js | 5 +++++ packages/mjml-core/src/parsers/document.js | 8 ++------ packages/mjml/test.js | 3 +-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index 12c3e4888..7bc15c2d3 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -6,12 +6,27 @@ export default (schemas = '') => { return ` - + + + + + + + + + + + + + + + + diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js index f11d7d725..45913c89a 100644 --- a/packages/mjml-core/src/helpers/xsd.js +++ b/packages/mjml-core/src/helpers/xsd.js @@ -49,12 +49,17 @@ class XsdError { return this.getErrors().map( v => `Line ${v.line}: ${v.message}` ).join('\n') } } +XsdError.SKIP_CODES = [ "1824" ] XsdError.CODES = { "1843": { regexp: /"(.*?)"/gmi, message: `Plain text content inside "$0" isn't allowed` }, + "1840": { + regexp: /[\{|"](.*?)["|\}]/gmi, + message: `Tag "$0" doesn't support "$3" value on "$1" attribute, only $4 are accepted` + }, "1866": { regexp: /"(.*?)"/gmi, message: `Tag "$0" has no attribute "$1"` diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 8b62a9e1a..7eb48a827 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -77,14 +77,13 @@ const parseHead = (head, attributes) => { attributes.container = dom.getHTML($container) } -const validateDocument = (content) => { +const validateDocument = (content, root) => { const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) - + const validationMode = root.attr('validate') || "strict" const schema = libXsd.parse(schemaXsd) const errors = schema.validate(content) if (errors && errors.length > 0) { - throw new XsdError(errors) } } @@ -97,14 +96,12 @@ const validateDocument = (content) => { const documentParser = (content, attributes) => { const safeContent = safeEndingTags(content) - // let root let body let head try { const $ = dom.parseXML(safeContent) body = $('mjml > mj-body') - // root = $('mjml') head = $('mjml > mj-head') if (body.length > 0) { @@ -118,7 +115,6 @@ const documentParser = (content, attributes) => { throw new EmptyMJMLError('No root "" or "" found in the file') } - validateDocument(safeContent) if (head && head.length === 1) { parseHead(head.get(0), attributes) diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 447f2827f..e980757b4 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,7 +1,7 @@ var mjml = require('./lib/index') try { const azeaze = mjml.mjml2html(` - + Hello MJML @@ -36,5 +36,4 @@ const azeaze = mjml.mjml2html(` } else { throw e } - } From ec405afc3dca987d26078d61391bbd3f2b6b2abd Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 28 Jul 2016 17:55:47 +0200 Subject: [PATCH 20/87] should fix tests --- packages/mjml-core/src/parsers/document.js | 10 ++++++++++ packages/mjml-core/test/input-output.spec.js | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 7eb48a827..090691239 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -84,6 +84,11 @@ const validateDocument = (content, root) => { const errors = schema.validate(content) if (errors && errors.length > 0) { + if (validationMode == "soft") { + console.err(new XsdError(errors).getMessages()) // eslint-disable-line no-console + } else { + throw new XsdError(errors) + } } } @@ -96,12 +101,14 @@ const validateDocument = (content, root) => { const documentParser = (content, attributes) => { const safeContent = safeEndingTags(content) + let root let body let head try { const $ = dom.parseXML(safeContent) body = $('mjml > mj-body') + root = $('mjml') head = $('mjml > mj-head') if (body.length > 0) { @@ -115,6 +122,9 @@ const documentParser = (content, attributes) => { throw new EmptyMJMLError('No root "" or "" found in the file') } + if (root.attr('validate') != "skip") { + validateDocument(safeContent, root) + } if (head && head.length === 1) { parseHead(head.get(0), attributes) diff --git a/packages/mjml-core/test/input-output.spec.js b/packages/mjml-core/test/input-output.spec.js index 25a997799..2d80d3fcc 100644 --- a/packages/mjml-core/test/input-output.spec.js +++ b/packages/mjml-core/test/input-output.spec.js @@ -27,7 +27,7 @@ describe('MJML Renderer', () => { describe('Invalid MJML', () => { it('should throw if no elements registered', () => { expect(() => new MJMLRenderer(` - + @@ -41,7 +41,7 @@ describe('MJML Renderer', () => { describe('Partial MJML registered', () => { it('should warn user that document will not be entirely parsed', () => { expect(new MJMLRenderer(` - + @@ -56,7 +56,7 @@ describe('MJML Renderer', () => { it('should render a MJML document', () => { registerMJElement(MockComponent) expect(new MJMLRenderer(` - + From 527fb12e5427df3f070cb0cae100e79d3e1955c9 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 28 Jul 2016 18:05:22 +0200 Subject: [PATCH 21/87] should fix travis error --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 28f570829..a96a8adfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,12 @@ node_js: - 4 - 5 - 6 +before_install: + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo apt-get -qq update + - sudo apt-get -qq install g++-4.8 +env: + - CXX=g++-4.8 script: - npm run lint - cd packages/mjml-core From 6e5f691bb4f6253b7eb81651918b938cf67ad470 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 28 Jul 2016 18:07:38 +0200 Subject: [PATCH 22/87] addons ? --- .travis.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a96a8adfd..dcea7701a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,19 @@ 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 -before_install: - - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - - sudo apt-get -qq update - - sudo apt-get -qq install g++-4.8 -env: - - CXX=g++-4.8 script: - npm run lint - cd packages/mjml-core From 9717570cf2383330a749f5bd51cb6f0ef5198eab Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 28 Jul 2016 18:17:39 +0200 Subject: [PATCH 23/87] skipping validation --- packages/mjml-core/test/input-output.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mjml-core/test/input-output.spec.js b/packages/mjml-core/test/input-output.spec.js index 2d80d3fcc..2a2a268d2 100644 --- a/packages/mjml-core/test/input-output.spec.js +++ b/packages/mjml-core/test/input-output.spec.js @@ -27,7 +27,7 @@ describe('MJML Renderer', () => { describe('Invalid MJML', () => { it('should throw if no elements registered', () => { expect(() => new MJMLRenderer(` - + @@ -41,7 +41,7 @@ describe('MJML Renderer', () => { describe('Partial MJML registered', () => { it('should warn user that document will not be entirely parsed', () => { expect(new MJMLRenderer(` - + @@ -56,7 +56,7 @@ describe('MJML Renderer', () => { it('should render a MJML document', () => { registerMJElement(MockComponent) expect(new MJMLRenderer(` - + From 0a389ee9615eb90714631db0d0e66fa47b10fb12 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 9 Aug 2016 17:50:40 +0200 Subject: [PATCH 24/87] Adding inner padding to social #327 --- packages/mjml-social/README.md | 1 + packages/mjml-social/src/index.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/mjml-social/README.md b/packages/mjml-social/README.md index 0f743dc31..e6722db25 100644 --- a/packages/mjml-social/README.md +++ b/packages/mjml-social/README.md @@ -84,6 +84,7 @@ color | color | text color base-url | string | icon base url | https://www.mailjet.com/images/theme/v1/icons/ico-social/ display | string | List of social icons to display separated by a space, | facebook twitter google | | available values: `facebook google instagram pinterest linkedin twitter` | +inner-padding | px | social network surrounding padding | 4px padding | px | supports up to 4 parameters | 10px 25px padding-top | px | top offset | n/a padding-bottom | px | bottom offset | n/a diff --git a/packages/mjml-social/src/index.js b/packages/mjml-social/src/index.js index 15c70cedb..f90de4cc9 100644 --- a/packages/mjml-social/src/index.js +++ b/packages/mjml-social/src/index.js @@ -143,6 +143,9 @@ class Social extends Component { lineHeight: defaultUnit(mjAttribute('line-height'), 'px'), textDecoration: mjAttribute('text-decoration') }, + td1: { + padding: defaultUnit(mjAttribute('inner-padding')) + }, td2: { width: defaultUnit(mjAttribute('icon-size'), 'px'), height: defaultUnit(mjAttribute('icon-size'), 'px') From 17a787d6ad55e9b4a53da5a89f1c7183a73d40bb Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 10 Aug 2016 15:48:17 +0200 Subject: [PATCH 25/87] Fix #341 ... not the best workaround --- packages/mjml-core/src/parsers/document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 090691239..7427cf98a 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -23,7 +23,7 @@ const safeEndingTags = content => { return content } - bodyContent = bodyContent[0] + bodyContent = bodyContent[0].replace('$', '$') // $ is a protected chars for regexp... avoid issue with duplicate content endingTags.forEach(tag => { bodyContent = bodyContent.replace(regexTag(tag), dom.replaceContentByCdata(tag)) From d4029abedd6ea5d495106267274e8ae291171864 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 12 Aug 2016 16:13:08 +0200 Subject: [PATCH 26/87] Add border to column/section, validation level is now on cli --- packages/mjml-cli/bin/mjml | 3 ++- packages/mjml-cli/src/client.js | 4 +-- packages/mjml-column/src/index.js | 6 +++-- packages/mjml-core/src/MJMLRenderer.js | 3 ++- packages/mjml-core/src/parsers/document.js | 13 ++++------ packages/mjml-section/src/index.js | 2 ++ packages/mjml/test.js | 23 +++++++++-------- packages/mjml/test.mjml | 30 ++++++++++++++++++++++ 8 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 packages/mjml/test.mjml diff --git a/packages/mjml-cli/bin/mjml b/packages/mjml-cli/bin/mjml index 01d0c8d5a..7bcaa0c48 100755 --- a/packages/mjml-cli/bin/mjml +++ b/packages/mjml-cli/bin/mjml @@ -15,9 +15,10 @@ binary stdout: process.argv.indexOf('-s') !== -1 || process.argv.indexOf('--stdout') !== -1, output: binary.output } - + cli.renderFile(files, options) }) + .option('-v, --validateLevel [level]', 'Specifies the level of validation of MJML parser (skip/soft/strict)', /^(skip|soft|strict)$/i, 'strict') .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') diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index f5a3f0b56..f9e9f70f2 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -63,9 +63,9 @@ const readStdin = promisify(stdinToBuffer) /* * Render an input promise */ -const render = (bufferPromise, { min, output, stdout, fileName }) => { +const render = (bufferPromise, { min, output, stdout, fileName, validationLevel }) => { bufferPromise - .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min }).render()) + .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min, validationLevel }).render()) .then(result => stdout ? process.stdout.write(result) : write(output, result)) .catch(e => { // XSD validation error ? diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 92ec708a5..a41c5548d 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -8,10 +8,11 @@ const tagName = 'mj-column' const parentTag = ['mj-section', 'mj-group'] const defaultMJMLDefinition = { attributes: { - 'width': null, 'background': null, 'background-color': null, - 'vertical-align': null + 'border': null, + 'vertical-align': null, + 'width': null } } const baseStyles = { @@ -67,6 +68,7 @@ class Column extends Component { }, table: { background: mjAttribute('background-color'), + border: mjAttribute('border'), verticalAlign: mjAttribute('vertical-align') } }) diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index f19c1c850..f76138f4b 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -32,6 +32,7 @@ export default class MJMLRenderer { this.content = includeExternal(content) this.options = options + this.options["validationLevel"] = this.options["validationLevel"] || "strict" if (typeof this.content === 'string') { this.parseDocument() @@ -73,7 +74,7 @@ export default class MJMLRenderer { const documentParser = require('./parsers/document').default debug('Start parsing document') - this.content = documentParser(this.content, this.attributes) + this.content = documentParser(this.content, this.attributes, this.options) debug('Content parsed') } diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 7427cf98a..05c554013 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -77,14 +77,13 @@ const parseHead = (head, attributes) => { attributes.container = dom.getHTML($container) } -const validateDocument = (content, root) => { +const validateDocument = (content, validateLevel) => { const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) - const validationMode = root.attr('validate') || "strict" const schema = libXsd.parse(schemaXsd) const errors = schema.validate(content) if (errors && errors.length > 0) { - if (validationMode == "soft") { + if (validateLevel == "soft") { console.err(new XsdError(errors).getMessages()) // eslint-disable-line no-console } else { throw new XsdError(errors) @@ -98,17 +97,15 @@ const validateDocument = (content, root) => { * - container: the mjml container * - mjml: a json representation of the mjml */ -const documentParser = (content, attributes) => { +const documentParser = (content, attributes, options) => { const safeContent = safeEndingTags(content) - let root let body let head try { const $ = dom.parseXML(safeContent) body = $('mjml > mj-body') - root = $('mjml') head = $('mjml > mj-head') if (body.length > 0) { @@ -122,8 +119,8 @@ const documentParser = (content, attributes) => { throw new EmptyMJMLError('No root "" or "" found in the file') } - if (root.attr('validate') != "skip") { - validateDocument(safeContent, root) + if (options.validateLevel != "skip") { + validateDocument(safeContent, options.validateLevel) } if (head && head.length === 1) { diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 59ffa5028..17d797e2e 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -11,6 +11,7 @@ const defaultMJMLDefinition = { "background-url": null, "background-repeat": "repeat", "background-size": "auto", + "border": null, "full-width": null, "padding": "20px 0", "padding-top": null, @@ -111,6 +112,7 @@ class Section extends Component { return merge({}, baseStyles, { td: { + 'border': mjAttribute('border'), fontSize: '0px', padding: defaultUnit(mjAttribute('padding'), 'px'), paddingBottom: defaultUnit(mjAttribute('padding-bottom'), 'px'), diff --git a/packages/mjml/test.js b/packages/mjml/test.js index e980757b4..87eebd4e5 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,18 +1,19 @@ var mjml = require('./lib/index') try { -const azeaze = mjml.mjml2html(` - - - Hello MJML - - +const azeaze = console.log(mjml.mjml2html(` + - - - + + + + + 2 coloumnes $2 + + + -`, { beautify: true }) +`, { beautify: true })) } catch(e) { if (e.getErrors) { console.log(e.getErrors()) diff --git a/packages/mjml/test.mjml b/packages/mjml/test.mjml new file mode 100644 index 000000000..b410367e4 --- /dev/null +++ b/packages/mjml/test.mjml @@ -0,0 +1,30 @@ + + + + + + + 2 coloumnes $2 + + + + + Check out promotions ! + + + foo © bar ≠ baz 𝌆 qux + + + + + + From f1fb2902334351988272b1fc8e59e73fda112c8a Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 12 Aug 2016 16:35:21 +0200 Subject: [PATCH 27/87] Should fix CI issues + remove legacy validate xsd --- packages/mjml-core/src/configs/defaultXsd.js | 17 +---------------- packages/mjml-core/test/input-output.spec.js | 12 ++++++------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js index 7bc15c2d3..12c3e4888 100644 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ b/packages/mjml-core/src/configs/defaultXsd.js @@ -6,27 +6,12 @@ export default (schemas = '') => { return ` - + - - - - - - - - - - - - - - - diff --git a/packages/mjml-core/test/input-output.spec.js b/packages/mjml-core/test/input-output.spec.js index 2a2a268d2..b9a8289ef 100644 --- a/packages/mjml-core/test/input-output.spec.js +++ b/packages/mjml-core/test/input-output.spec.js @@ -27,13 +27,13 @@ describe('MJML Renderer', () => { describe('Invalid MJML', () => { it('should throw if no elements registered', () => { expect(() => new MJMLRenderer(` - + - `).render() + `, { validationLevel: "skip" }).render() ).to.throw(/EmptyMJMLError/) }) }) @@ -41,13 +41,13 @@ describe('MJML Renderer', () => { describe('Partial MJML registered', () => { it('should warn user that document will not be entirely parsed', () => { expect(new MJMLRenderer(` - + - `).render() + `, { validationLevel: "skip" }).render() ).to.not.contain('Mocked Component!') }) }) @@ -56,13 +56,13 @@ describe('MJML Renderer', () => { it('should render a MJML document', () => { registerMJElement(MockComponent) expect(new MJMLRenderer(` - + - `).render() + `, { validationLevel: "skip" }).render() ).to.contain('Mocked Component!') }) }) From 33d95aba8ab5182b9f56348f60862faabeb275db Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 12 Aug 2016 16:43:49 +0200 Subject: [PATCH 28/87] typo for test --- packages/mjml-cli/src/client.js | 2 +- packages/mjml-core/src/parsers/document.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index f9e9f70f2..c06562096 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -108,7 +108,7 @@ export const renderFile = (input, options) => { const filePath = path.resolve(process.cwd(), file) - render(read(filePath), { min: options.min, stdout: options.stdout, output, fileName: file }) + render(read(filePath), { min: options.min, stdout: options.stdout, output, fileName: file, validationLevel: options.validationLevel }) }) } diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 05c554013..4e1ee5a88 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -77,13 +77,13 @@ const parseHead = (head, attributes) => { attributes.container = dom.getHTML($container) } -const validateDocument = (content, validateLevel) => { +const validateDocument = (content, validationLevel) => { const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) const schema = libXsd.parse(schemaXsd) const errors = schema.validate(content) if (errors && errors.length > 0) { - if (validateLevel == "soft") { + if (validationLevel == "soft") { console.err(new XsdError(errors).getMessages()) // eslint-disable-line no-console } else { throw new XsdError(errors) @@ -119,8 +119,8 @@ const documentParser = (content, attributes, options) => { throw new EmptyMJMLError('No root "" or "" found in the file') } - if (options.validateLevel != "skip") { - validateDocument(safeContent, options.validateLevel) + if (options.validationLevel != "skip") { + validateDocument(safeContent, options.validationLevel) } if (head && head.length === 1) { From ce28f41d4a1081c1111501f1b8f356024722c3b5 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 16 Aug 2016 15:51:08 +0200 Subject: [PATCH 29/87] use ~ instead of ^ for mjml inter dependencies --- packages/mjml-button/package.json | 2 +- packages/mjml-column/package.json | 2 +- packages/mjml-container/package.json | 2 +- packages/mjml-divider/package.json | 2 +- packages/mjml-group/package.json | 2 +- packages/mjml-hero/package.json | 2 +- packages/mjml-html/package.json | 2 +- packages/mjml-image/package.json | 2 +- packages/mjml-invoice/package.json | 4 +-- packages/mjml-list/package.json | 2 +- packages/mjml-location/package.json | 6 ++-- packages/mjml-navbar/package.json | 4 +-- packages/mjml-raw/package.json | 2 +- packages/mjml-section/package.json | 2 +- packages/mjml-social/package.json | 2 +- packages/mjml-spacer/package.json | 2 +- packages/mjml-table/package.json | 2 +- packages/mjml-text/package.json | 2 +- packages/mjml/package.json | 48 ++++++++++++++-------------- packages/mjml/test.js | 3 +- 20 files changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/mjml-button/package.json b/packages/mjml-button/package.json index 1d39fd8c4..011057184 100644 --- a/packages/mjml-button/package.json +++ b/packages/mjml-button/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-column/package.json b/packages/mjml-column/package.json index 1278ed48c..fb7baebe1 100644 --- a/packages/mjml-column/package.json +++ b/packages/mjml-column/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-container/package.json b/packages/mjml-container/package.json index 1539bbd7a..de19e492e 100644 --- a/packages/mjml-container/package.json +++ b/packages/mjml-container/package.json @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-divider/package.json b/packages/mjml-divider/package.json index f5012911e..94f8d80cb 100644 --- a/packages/mjml-divider/package.json +++ b/packages/mjml-divider/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-group/package.json b/packages/mjml-group/package.json index a9e87da1e..ad1458be8 100644 --- a/packages/mjml-group/package.json +++ b/packages/mjml-group/package.json @@ -14,7 +14,7 @@ "homepage": "https://github.com/mjmlio/mjml", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-hero/package.json b/packages/mjml-hero/package.json index 7fe95ac39..4dceac8a6 100644 --- a/packages/mjml-hero/package.json +++ b/packages/mjml-hero/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-html/package.json b/packages/mjml-html/package.json index 12a4fa99a..71d6bdc20 100644 --- a/packages/mjml-html/package.json +++ b/packages/mjml-html/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-image/package.json b/packages/mjml-image/package.json index 06418a999..16b3f1621 100644 --- a/packages/mjml-image/package.json +++ b/packages/mjml-image/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-invoice/package.json b/packages/mjml-invoice/package.json index 4d000c8bf..88c017fbc 100644 --- a/packages/mjml-invoice/package.json +++ b/packages/mjml-invoice/package.json @@ -14,8 +14,8 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", - "mjml-table": "^2.0.9", + "mjml-core": "~2.3.2", + "mjml-table": "~2.0.9", "numeral": "^1.5.3", "react": "^15.3.0" } diff --git a/packages/mjml-list/package.json b/packages/mjml-list/package.json index 91cd63278..d6afe1b3b 100644 --- a/packages/mjml-list/package.json +++ b/packages/mjml-list/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-location/package.json b/packages/mjml-location/package.json index 7f306e111..878f07925 100644 --- a/packages/mjml-location/package.json +++ b/packages/mjml-location/package.json @@ -14,9 +14,9 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", - "mjml-image": "^2.0.9", - "mjml-text": "^2.0.9", + "mjml-core": "~2.3.2", + "mjml-image": "~2.0.9", + "mjml-text": "~2.0.9", "react": "^15.3.0" } } diff --git a/packages/mjml-navbar/package.json b/packages/mjml-navbar/package.json index 6314ca8e8..bba4b97d5 100644 --- a/packages/mjml-navbar/package.json +++ b/packages/mjml-navbar/package.json @@ -14,8 +14,8 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", - "mjml-section": "^2.0.9", + "mjml-core": "~2.3.2", + "mjml-section": "~2.0.9", "react": "^15.3.0" } } diff --git a/packages/mjml-raw/package.json b/packages/mjml-raw/package.json index 64f5e3a91..713cdeb45 100644 --- a/packages/mjml-raw/package.json +++ b/packages/mjml-raw/package.json @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-section/package.json b/packages/mjml-section/package.json index b3614f7bc..996f561aa 100644 --- a/packages/mjml-section/package.json +++ b/packages/mjml-section/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-social/package.json b/packages/mjml-social/package.json index 2d35d044a..8222199d5 100644 --- a/packages/mjml-social/package.json +++ b/packages/mjml-social/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-spacer/package.json b/packages/mjml-spacer/package.json index 45624a205..e00d39c41 100644 --- a/packages/mjml-spacer/package.json +++ b/packages/mjml-spacer/package.json @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-table/package.json b/packages/mjml-table/package.json index 67908f488..92564d246 100644 --- a/packages/mjml-table/package.json +++ b/packages/mjml-table/package.json @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml-text/package.json b/packages/mjml-text/package.json index be14bb5f0..d6385f422 100644 --- a/packages/mjml-text/package.json +++ b/packages/mjml-text/package.json @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.14.2", - "mjml-core": "^2.3.2", + "mjml-core": "~2.3.2", "react": "^15.3.0" } } diff --git a/packages/mjml/package.json b/packages/mjml/package.json index fe9d00654..4a799ba19 100644 --- a/packages/mjml/package.json +++ b/packages/mjml/package.json @@ -16,29 +16,29 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-button": "^2.0.9", - "mjml-cli": "^2.3.2", - "mjml-column": "^2.0.9", - "mjml-container": "^2.0.9", - "mjml-core": "^2.3.2", - "mjml-divider": "^2.0.10", - "mjml-group": "^2.0.3", - "mjml-head-attributes": "^2.0.7", - "mjml-head-font": "^2.0.1", - "mjml-head-style": "^2.0.0", - "mjml-head-title": "^2.0.1", - "mjml-hero": "^2.0.9", - "mjml-html": "^2.0.9", - "mjml-image": "^2.0.9", - "mjml-invoice": "^2.0.11", - "mjml-list": "^2.0.9", - "mjml-location": "^2.0.9", - "mjml-navbar": "^2.0.7", - "mjml-raw": "^2.0.9", - "mjml-section": "^2.0.9", - "mjml-social": "^2.0.9", - "mjml-spacer": "^2.0.8", - "mjml-table": "^2.0.9", - "mjml-text": "^2.0.9" + "mjml-button": "~2.0.9", + "mjml-cli": "~2.3.2", + "mjml-column": "~2.0.9", + "mjml-container": "~2.0.9", + "mjml-core": "~2.3.2", + "mjml-divider": "~2.0.10", + "mjml-group": "~2.0.3", + "mjml-head-attributes": "~2.0.7", + "mjml-head-font": "~2.0.1", + "mjml-head-style": "~2.0.0", + "mjml-head-title": "~2.0.1", + "mjml-hero": "~2.0.9", + "mjml-html": "~2.0.9", + "mjml-image": "~2.0.9", + "mjml-invoice": "~2.0.11", + "mjml-list": "~2.0.9", + "mjml-location": "~2.0.9", + "mjml-navbar": "~2.0.7", + "mjml-raw": "~2.0.9", + "mjml-section": "~2.0.9", + "mjml-social": "~2.0.9", + "mjml-spacer": "~2.0.8", + "mjml-table": "~2.0.9", + "mjml-text": "~2.0.9" } } diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 87eebd4e5..5ddf31957 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -5,6 +5,7 @@ const azeaze = console.log(mjml.mjml2html(` + azeaz 2 coloumnes $2 @@ -33,7 +34,7 @@ const azeaze = console.log(mjml.mjml2html(` `, { beautify: true })) } catch(e) { if (e.getErrors) { - console.log(e.getErrors()) + console.log(e.getMessages()) } else { throw e } From 08c4311093979f47903eb095612c049e69b555f7 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 16 Aug 2016 17:02:04 +0200 Subject: [PATCH 30/87] ignore some useless codes --- packages/mjml-core/src/helpers/xsd.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js index 45913c89a..515a5f81b 100644 --- a/packages/mjml-core/src/helpers/xsd.js +++ b/packages/mjml-core/src/helpers/xsd.js @@ -1,5 +1,6 @@ import elements from '../MJMLElementsCollection' import includes from 'lodash/includes' +import filter from 'lodash/filter' class XsdError { constructor (errors) { @@ -42,7 +43,7 @@ class XsdError { } getErrors () { - return this.errors.map((error) => this.format(error)) + return filter(this.errors, (error) => !includes(XsdError.SKIP_CODES, error.code)).map((error) => this.format(error)) } getMessages () { From 63ae66daab9e8b54c12eee167d583e4aaa8e418d Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 17 Aug 2016 15:13:43 +0200 Subject: [PATCH 31/87] err..or --- packages/mjml-core/src/parsers/document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index 4e1ee5a88..a25cff1a1 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -84,7 +84,7 @@ const validateDocument = (content, validationLevel) => { if (errors && errors.length > 0) { if (validationLevel == "soft") { - console.err(new XsdError(errors).getMessages()) // eslint-disable-line no-console + console.error(new XsdError(errors).getMessages()) // eslint-disable-line no-console } else { throw new XsdError(errors) } From 382aa53fc9521cdb148120feec5f1a03a90428ae Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 17 Aug 2016 16:37:41 +0200 Subject: [PATCH 32/87] Allow to modify default font #354 --- packages/mjml-head-font/package.json | 5 ++++- packages/mjml-head-font/src/index.js | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/mjml-head-font/package.json b/packages/mjml-head-font/package.json index 9361f3b35..ab15677aa 100644 --- a/packages/mjml-head-font/package.json +++ b/packages/mjml-head-font/package.json @@ -11,5 +11,8 @@ "bugs": { "url": "https://github.com/mjmlio/mjml/issues" }, - "homepage": "https://mjml.io" + "homepage": "https://mjml.io", + "dependencies": { + "lodash": "^4.15.0" + } } diff --git a/packages/mjml-head-font/src/index.js b/packages/mjml-head-font/src/index.js index f34133caa..30832a6a1 100644 --- a/packages/mjml-head-font/src/index.js +++ b/packages/mjml-head-font/src/index.js @@ -1,6 +1,14 @@ +import _ from "lodash" + export default { name: "mj-font", handler: (el, { fonts }) => { - fonts.push({ name: el.attribs.name, url: el.attribs.href }) + const font = _.find(fonts, ['name', el.attribs.name]) + + if (font) { + font.url = el.attribs.href + } else { + fonts.push({ name: el.attribs.name, url: el.attribs.href }) + } } } From d52768e185503bd7d796ee1331e2040583dded43 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 17 Aug 2016 16:49:26 +0200 Subject: [PATCH 33/87] Should fix #336 --- packages/mjml-core/src/includeExternal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mjml-core/src/includeExternal.js b/packages/mjml-core/src/includeExternal.js index 3fde5e443..81864b76d 100644 --- a/packages/mjml-core/src/includeExternal.js +++ b/packages/mjml-core/src/includeExternal.js @@ -1,6 +1,6 @@ import fs from 'fs' -const includes = /|>\s*<\/mj-include>)/g +const includes = /|>\s*<\/mj-include>)/g const getContent = input => input From 6aaac0894afee89043d72ba294b59f679ecd0651 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 22 Aug 2016 16:20:15 +0200 Subject: [PATCH 34/87] fix react warning on mj location --- packages/mjml-location/src/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/mjml-location/src/index.js b/packages/mjml-location/src/index.js index c7e1a5bdc..7ef654979 100644 --- a/packages/mjml-location/src/index.js +++ b/packages/mjml-location/src/index.js @@ -46,6 +46,7 @@ class Location extends Component { return { text: { + 'columnElement': true, 'font-family': mjAttribute('font-family'), 'font-size': defaultUnit(mjAttribute('font-size')), 'font-weight': mjAttribute('font-weight'), @@ -53,6 +54,7 @@ class Location extends Component { 'text-decoration': mjAttribute('text-decoration') }, img: { + 'columnElement': true, 'padding': '0px', 'src': mjAttribute('img-src') } From 1886cb523fdfad11a075889b30da55e62e20267d Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 24 Aug 2016 10:37:58 +0200 Subject: [PATCH 35/87] Mj include is now recursive --- packages/mjml-core/src/includeExternal.js | 14 ++++++++++++-- packages/mjml/test.js | 7 ++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/mjml-core/src/includeExternal.js b/packages/mjml-core/src/includeExternal.js index 81864b76d..123cde590 100644 --- a/packages/mjml-core/src/includeExternal.js +++ b/packages/mjml-core/src/includeExternal.js @@ -7,7 +7,7 @@ const getContent = input => .replace(/[\n\s\t]+[\n\s\t]+/, '') .replace(/<\/mj-container>[\n\s\t]+<\/mj-body>[\n\s\t]+<\/mjml>/, '') -export default mjml => mjml.replace(includes, (_, path) => { +const replaceContent = (_, path) => { const mjmlExtension = file => file.trim().match(/.mjml$/) && file || `${file}.mjml` const template = fs.readFileSync(mjmlExtension(path), 'utf8') @@ -16,4 +16,14 @@ export default mjml => mjml.replace(includes, (_, path) => { if (!content) { throw new Error(`Error while parsing file: ${path}`) } return content -}) +} + +export default (baseMjml) => { + let mjml = baseMjml + + while (mjml.match(includes)) { + mjml = mjml.replace(includes, replaceContent) + } + + return mjml +} diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 5ddf31957..a697a8758 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,11 +1,10 @@ var mjml = require('./lib/index') try { const azeaze = console.log(mjml.mjml2html(` - + - + - azeaz 2 coloumnes $2 @@ -13,12 +12,10 @@ const azeaze = console.log(mjml.mjml2html(` From 027f739f86dff22e5ef5c1a5cef71c7c4a863ed3 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 24 Aug 2016 11:39:56 +0200 Subject: [PATCH 36/87] border bottom top left right + radius for column & section #363 --- packages/mjml-button/src/index.js | 10 +++++++++- packages/mjml-column/src/index.js | 14 ++++++++++++-- packages/mjml-section/src/index.js | 12 +++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index 9bb372161..40eb15cb0 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -9,14 +9,18 @@ const defaultMJMLDefinition = { content: '', attributes: { "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": "bold", "font-family": "Ubuntu, Helvetica, Arial, sans-serif", "color": "#ffffff", - "border": "none", "text-decoration": "none", "text-transform": "none", "align": "center", @@ -51,7 +55,11 @@ class Button extends Component { return merge({}, baseStyles, { 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'), diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index c644f0ee7..fe7c0656e 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -10,7 +10,12 @@ const defaultMJMLDefinition = { attributes: { 'background': null, 'background-color': null, - 'border': null, + "border": null, + "border-bottom": null, + "border-left": null, + "border-radius": null, + "border-right": null, + "border-top": null, 'vertical-align': null, 'width': null } @@ -57,7 +62,7 @@ class Column extends Component { styles = this.getStyles() getStyles () { - const { mjAttribute } = this.props + const { mjAttribute, defaultUnit } = this.props return merge({}, baseStyles, { div: { @@ -69,6 +74,11 @@ class Column extends Component { table: { 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') } }) diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 872a9c0d5..bda6792ec 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -12,6 +12,11 @@ const defaultMJMLDefinition = { "background-repeat": "repeat", "background-size": "auto", "border": null, + "border-bottom": null, + "border-left": null, + "border-radius": null, + "border-right": null, + "border-top": null, "full-width": null, "padding": "20px 0", "padding-top": null, @@ -112,7 +117,12 @@ class Section extends Component { return merge({}, baseStyles, { td: { - 'border': mjAttribute('border'), + 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'), fontSize: '0px', padding: defaultUnit(mjAttribute('padding'), 'px'), paddingBottom: defaultUnit(mjAttribute('padding-bottom'), 'px'), From ed749276e0c28407fff68aad077d88fc83d23e7c Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 24 Aug 2016 11:48:34 +0200 Subject: [PATCH 37/87] Missing border in documentation --- packages/mjml-button/README.md | 6 +++++- packages/mjml-column/README.md | 8 +++++++- packages/mjml-section/README.md | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/mjml-button/README.md b/packages/mjml-button/README.md index 24c2cb4a8..7e862499a 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 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-section/README.md b/packages/mjml-section/README.md index 1e8377af1..45b5cbfff 100644 --- a/packages/mjml-section/README.md +++ b/packages/mjml-section/README.md @@ -28,6 +28,12 @@ changed to 100%. attribute | unit | description | default value --------------------|-------------|--------------------------------|--------------- full-width | string | make the section full-width | 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 background-color | color | section color | n/a background-url | url | background url | n/a background-repeat | string | css background repeat | repeat From 10fdadc98e74833edb0ab6bd3e2da851f90e4ccd Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 25 Aug 2016 11:29:46 +0200 Subject: [PATCH 38/87] button width/height --- packages/mjml-button/README.md | 2 ++ packages/mjml-button/src/index.js | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/mjml-button/README.md b/packages/mjml-button/README.md index 7e862499a..7af0a9ad2 100644 --- a/packages/mjml-button/README.md +++ b/packages/mjml-button/README.md @@ -54,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/src/index.js b/packages/mjml-button/src/index.js index 40eb15cb0..718056cae 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -31,7 +31,9 @@ const defaultMJMLDefinition = { "padding-top": null, "padding-bottom": null, "padding-left": null, - "padding-right": null + "padding-right": null, + "width": null, + "height": null } } const baseStyles = { @@ -63,7 +65,9 @@ class Button extends Component { color: mjAttribute('color'), cursor: 'auto', fontStyle: mjAttribute('font-style'), - padding: defaultUnit(mjAttribute('inner-padding'), "px") + height: mjAttribute('height'), + padding: defaultUnit(mjAttribute('inner-padding'), "px"), + width: mjAttribute('width') }, a: { background: mjAttribute('background-color'), From 2073ef025d816259322dcd0ba451a26860c5b505 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 25 Aug 2016 16:57:02 +0200 Subject: [PATCH 39/87] Start refactoring config parser from renderer --- doc/components.md | 2 - packages/mjml-core/src/MJMLRenderer.js | 42 ++---------------- packages/mjml-core/src/parsers/config.js | 54 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 packages/mjml-core/src/parsers/config.js diff --git a/doc/components.md b/doc/components.md index 79d616e7e..9a9fbd27c 100644 --- a/doc/components.md +++ b/doc/components.md @@ -1,6 +1,5 @@ # 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. For instance, the `mj-button` components is, on the inside, a complex HTML layout: @@ -29,4 +28,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/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 72601b820..5ca0d20a9 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -2,18 +2,18 @@ import { EmptyMJMLError } from './Error' import { fixLegacyAttrs, removeCDATA } from './helpers/postRender' import { parseInstance } from './helpers/mjml' import cloneDeep from 'lodash/cloneDeep' +import configParser from './parsers/config' +import documentParser from './parsers/document' import defaultContainer from './configs/defaultContainer' import defaultFonts from './configs/listFontsImports' import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' -import isEmpty from 'lodash/isEmpty' import juice from 'juice' -import MJMLElementsCollection, { postRenders, registerMJElement } from './MJMLElementsCollection' +import MJMLElementsCollection, { postRenders } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' import React from 'react' import ReactDOMServer from 'react-dom/server' -import warning from 'warning' const debug = require('debug')('mjml-engine/mjml2html') @@ -21,7 +21,7 @@ export default class MJMLRenderer { constructor (content, options = {}) { if (!isBrowser) { - this.registerDotfile() + configParser() } this.attributes = { @@ -42,41 +42,7 @@ export default class MJMLRenderer { } } - registerDotfile () { - const fs = require('fs') - - const path = process.cwd() - - try { - fs.statSync(`${path}/.mjmlconfig`) - } catch (e) { - return warning(!isEmpty(MJMLElementsCollection), `No .mjmlconfig found in path ${path}, consider to add one`) - } - - try { - const mjmlConfig = JSON.parse(fs.readFileSync(`${path}/.mjmlconfig`).toString()) - const { packages } = mjmlConfig - - packages.forEach(file => { - if (!file) { - return - } - - try { - const Component = require.main.require(file) - registerMJElement(Component.default || Component) - } catch (e) { - warning(false, `.mjmlconfig file ${file} opened from ${path} has an error : ${e}`) - } - }) - } catch (e) { - warning(false, `.mjmlconfig has a ParseError: ${e}`) - } - } - parseDocument () { - const documentParser = require('./parsers/document').default - debug('Start parsing document') this.content = documentParser(this.content, this.attributes, this.options) debug('Content parsed') diff --git a/packages/mjml-core/src/parsers/config.js b/packages/mjml-core/src/parsers/config.js new file mode 100644 index 000000000..846d7c638 --- /dev/null +++ b/packages/mjml-core/src/parsers/config.js @@ -0,0 +1,54 @@ +import fs from 'fs' +import path from 'path' +import warning from 'warning' +import some from 'lodash/some' +import startsWith from 'lodash/startsWith' +import isEmpty from 'lodash/isEmpty' +import MJMLElementsCollection, { registerMJElement } from './MJMLElementsCollection' + +const cwd = process.cwd() + +const isFile = (name) => { + return some(['./', '.', '../'], (matcher) => startsWith(name, matcher)) +} + +const checkIfConfigFileExist = () => { + try { + fs.statSync(`${cwd}/.mjmlconfig`) + } catch (e) { + return warning(!isEmpty(MJMLElementsCollection), `No .mjmlconfig found in path ${cwd}, consider to add one`) + } +} + +const parseConfigFile = () => { + checkIfConfigFileExist() + + try { + return JSON.parse(fs.readFileSync(`${cwd}/.mjmlconfig`).toString()) + } catch (e) { + warning(false, `.mjmlconfig has a ParseError: ${e}`) + } +} + +const parsePackages = (packages) => { + packages.forEach(file => { + if (!file) { + return + } + + try { + const filename = path.join(process.cwd(), file) + const Component = isFile(file) ? require(filename) : require.main.require(file) + + registerMJElement(Component.default || Component) + } catch (e) { + warning(false, `.mjmlconfig file ${file} opened from ${cwd} has an error : ${e}`) + } + }) +} + +export default () => { + const config = parseConfigFile() + + parsePackages(config.packages) +} From 7e3ebe99a04e2dbc2c37253f7639c932dd74310c Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 25 Aug 2016 17:13:02 +0200 Subject: [PATCH 40/87] config parser fix --- packages/mjml-core/src/parsers/config.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/mjml-core/src/parsers/config.js b/packages/mjml-core/src/parsers/config.js index 846d7c638..2d28a8101 100644 --- a/packages/mjml-core/src/parsers/config.js +++ b/packages/mjml-core/src/parsers/config.js @@ -4,7 +4,7 @@ import warning from 'warning' import some from 'lodash/some' import startsWith from 'lodash/startsWith' import isEmpty from 'lodash/isEmpty' -import MJMLElementsCollection, { registerMJElement } from './MJMLElementsCollection' +import MJMLElementsCollection, { registerMJElement } from '../MJMLElementsCollection' const cwd = process.cwd() @@ -15,13 +15,17 @@ const isFile = (name) => { const checkIfConfigFileExist = () => { try { fs.statSync(`${cwd}/.mjmlconfig`) + return true } catch (e) { - return warning(!isEmpty(MJMLElementsCollection), `No .mjmlconfig found in path ${cwd}, consider to add one`) + warning(!isEmpty(MJMLElementsCollection), `No .mjmlconfig found in path ${cwd}, consider to add one`) + return false } } const parseConfigFile = () => { - checkIfConfigFileExist() + if (!checkIfConfigFileExist()) { + return false + } try { return JSON.parse(fs.readFileSync(`${cwd}/.mjmlconfig`).toString()) @@ -31,6 +35,10 @@ const parseConfigFile = () => { } const parsePackages = (packages) => { + if (!packages) { + return; + } + packages.forEach(file => { if (!file) { return @@ -50,5 +58,9 @@ const parsePackages = (packages) => { export default () => { const config = parseConfigFile() + if (!config) { + return; + } + parsePackages(config.packages) } From a114a303582b2c0fdccec11ba478940bc40b72ae Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 16 Sep 2016 16:08:39 +0200 Subject: [PATCH 41/87] Multiple fix for release --- packages/mjml-cli/bin/mjml | 2 +- packages/mjml-cli/src/client.js | 19 ++++++++++------ packages/mjml-core/src/MJMLRenderer.js | 25 +++++++++++++--------- packages/mjml-core/src/parsers/config.js | 4 ++-- packages/mjml-core/src/parsers/document.js | 18 +++++++++------- packages/mjml/test.html | 20 +++++++++++++++++ packages/mjml/test.mjml | 7 +++--- 7 files changed, 64 insertions(+), 31 deletions(-) create mode 100644 packages/mjml/test.html diff --git a/packages/mjml-cli/bin/mjml b/packages/mjml-cli/bin/mjml index 7bcaa0c48..b1f59f8e6 100755 --- a/packages/mjml-cli/bin/mjml +++ b/packages/mjml-cli/bin/mjml @@ -18,7 +18,7 @@ binary cli.renderFile(files, options) }) - .option('-v, --validateLevel [level]', 'Specifies the level of validation of MJML parser (skip/soft/strict)', /^(skip|soft|strict)$/i, 'strict') + .option('-l, --level [level]', 'Specifies the level of validation of MJML parser (skip/soft/strict)', /^(skip|soft|strict)$/i, 'strict') .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') diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index 232f2d766..dca1fa76c 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -63,10 +63,18 @@ const readStdin = promisify(stdinToBuffer) /* * Render an input promise */ -const render = (bufferPromise, { min, output, stdout, fileName, validationLevel }) => { +const render = (bufferPromise, { min, output, stdout, fileName, level }) => { bufferPromise - .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min, validationLevel }).render()) - .then(result => stdout ? process.stdout.write(result) : write(output, result)) + .then(mjml => new MJMLRenderer(mjml.toString(), { minify: min, level }).render()) + .then(result => { + const { html: content, errors } = result + + if (errors) { + process.stderr.write(errors) + } + + stdout ? process.stdout.write(content) : write(output, content) + }) .catch(e => { // XSD validation error ? if (e.getErrors) { @@ -87,7 +95,6 @@ export const renderFile = (input, options) => { const renderFiles = files => { files.forEach((file, index) => { const inFile = path.basename(file, '.mjml') - const inputExtension = path.extname(inFile) let output if (options.output) { @@ -103,12 +110,12 @@ export const renderFile = (input, options) => { output = `${outFile}${outputExtension}` } } else { - output = `${inFile}${inputExtension}` + output = `${inFile}${path.extname(inFile) || ".html"}` } const filePath = path.resolve(process.cwd(), file) - render(read(filePath), { min: options.min, stdout: options.stdout, output, fileName: file, validationLevel: options.validationLevel }) + render(read(filePath), { min: options.min, stdout: options.stdout, output, fileName: file, level: options.level }) }) } diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 5ca0d20a9..b1e817990 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -34,17 +34,21 @@ export default class MJMLRenderer { this.content = content this.options = options - this.options["validationLevel"] = this.options["validationLevel"] || "strict" + this.options["level"] = this.options["level"] || "strict" if (typeof this.content === 'string') { - this.content = includeExternal(this.content) this.parseDocument() } } parseDocument () { + this.content = includeExternal(this.content) + debug('Start parsing document') - this.content = documentParser(this.content, this.attributes, this.options) + const { html, errors } = documentParser(this.content, this.attributes, this.options) + + this.content = html + this.errors = errors debug('Content parsed') } @@ -61,7 +65,7 @@ export default class MJMLRenderer { debug('React rendering done. Continue with special overrides.') const MJMLDocument = this.attributes.container.replace('__content__', renderedMJML) - return this.postRender(MJMLDocument) + return { errors: this.errors, html: this.postRender(MJMLDocument) } } postRender (MJMLDocument) { @@ -81,6 +85,13 @@ export default class MJMLRenderer { let finalMJMLDocument = dom.getHTML($) finalMJMLDocument = removeCDATA(finalMJMLDocument) + finalMJMLDocument = juice(finalMJMLDocument, { + extraCss: `${this.attributes.css.join('')}`, + removeStyleTags: false, + applyStyleTags: false, + insertPreservedExtraCss: false + }) + if (this.options.beautify) { const beautify = require('js-beautify').html @@ -101,12 +112,6 @@ export default class MJMLRenderer { } finalMJMLDocument = he.decode(finalMJMLDocument) - finalMJMLDocument = juice(finalMJMLDocument, { - extraCss: `${this.attributes.css.join('')}`, - removeStyleTags: false, - applyStyleTags: false, - insertPreservedExtraCss: false - }) return finalMJMLDocument } diff --git a/packages/mjml-core/src/parsers/config.js b/packages/mjml-core/src/parsers/config.js index 2d28a8101..f6127f9a1 100644 --- a/packages/mjml-core/src/parsers/config.js +++ b/packages/mjml-core/src/parsers/config.js @@ -8,7 +8,7 @@ import MJMLElementsCollection, { registerMJElement } from '../MJMLElementsCollec const cwd = process.cwd() -const isFile = (name) => { +const isRelativePath = (name) => { return some(['./', '.', '../'], (matcher) => startsWith(name, matcher)) } @@ -46,7 +46,7 @@ const parsePackages = (packages) => { try { const filename = path.join(process.cwd(), file) - const Component = isFile(file) ? require(filename) : require.main.require(file) + const Component = isRelativePath(file) ? require(filename) : require.main.require(file) registerMJElement(Component.default || Component) } catch (e) { diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index a25cff1a1..c15971617 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -77,17 +77,17 @@ const parseHead = (head, attributes) => { attributes.container = dom.getHTML($container) } -const validateDocument = (content, validationLevel) => { +const validateDocument = (content, level) => { const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) const schema = libXsd.parse(schemaXsd) const errors = schema.validate(content) if (errors && errors.length > 0) { - if (validationLevel == "soft") { - console.error(new XsdError(errors).getMessages()) // eslint-disable-line no-console - } else { - throw new XsdError(errors) + if (level == "soft") { + return new XsdError(errors).getMessages() } + + throw new XsdError(errors) } } @@ -102,9 +102,11 @@ const documentParser = (content, attributes, options) => { let body let head + let errors try { const $ = dom.parseXML(safeContent) + body = $('mjml > mj-body') head = $('mjml > mj-head') @@ -119,15 +121,15 @@ const documentParser = (content, attributes, options) => { throw new EmptyMJMLError('No root "" or "" found in the file') } - if (options.validationLevel != "skip") { - validateDocument(safeContent, options.validationLevel) + if (options.level != "skip") { + errors = validateDocument(safeContent, options.level) } if (head && head.length === 1) { parseHead(head.get(0), attributes) } - return mjmlElementParser(body) + return { errors, html: mjmlElementParser(body) } } export default documentParser diff --git a/packages/mjml/test.html b/packages/mjml/test.html new file mode 100644 index 000000000..4c42f068d --- /dev/null +++ b/packages/mjml/test.html @@ -0,0 +1,20 @@ +
2 coloumnes $2
Check out promotions !
foo © bar ≠ baz 𝌆 qux
\ No newline at end of file diff --git a/packages/mjml/test.mjml b/packages/mjml/test.mjml index b410367e4..e9f9257b5 100644 --- a/packages/mjml/test.mjml +++ b/packages/mjml/test.mjml @@ -1,15 +1,14 @@ - + - + 2 coloumnes $2 Check out promotions ! - + foo © bar ≠ baz 𝌆 qux From dd7ff78b35212c6e13b3a9d32f76a9901ed21c83 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 21 Sep 2016 17:36:14 +0200 Subject: [PATCH 42/87] New json validator instead of XSD --- .../mjml-core/src/MJMLElementsCollection.js | 5 +- packages/mjml-core/src/MJMLRenderer.js | 14 +- packages/mjml-core/src/configs/defaultXsd.js | 30 ----- .../mjml-core/src/decorators/MJMLElement.js | 14 +- packages/mjml-core/src/helpers/dom.js | 2 +- packages/mjml-core/src/helpers/xsd.js | 121 ------------------ packages/mjml-core/src/parsers/document.js | 44 ++----- packages/mjml-core/src/parsers/validator.js | 17 +++ packages/mjml-core/src/rules/index.js | 3 + packages/mjml-core/src/rules/ruleError.js | 10 ++ .../mjml-core/src/rules/validAttributes.js | 23 ++++ packages/mjml-core/src/rules/validChildren.js | 32 +++++ packages/mjml-core/src/rules/validTag.js | 11 ++ packages/mjml/test.js | 19 +-- 14 files changed, 128 insertions(+), 217 deletions(-) delete mode 100644 packages/mjml-core/src/configs/defaultXsd.js delete mode 100644 packages/mjml-core/src/helpers/xsd.js create mode 100644 packages/mjml-core/src/parsers/validator.js create mode 100644 packages/mjml-core/src/rules/index.js create mode 100644 packages/mjml-core/src/rules/ruleError.js create mode 100644 packages/mjml-core/src/rules/validAttributes.js create mode 100644 packages/mjml-core/src/rules/validChildren.js create mode 100644 packages/mjml-core/src/rules/validTag.js diff --git a/packages/mjml-core/src/MJMLElementsCollection.js b/packages/mjml-core/src/MJMLElementsCollection.js index 80058e7d4..fe01ba352 100644 --- a/packages/mjml-core/src/MJMLElementsCollection.js +++ b/packages/mjml-core/src/MJMLElementsCollection.js @@ -1,12 +1,10 @@ import warning from 'warning' -import defaultXsd from './helpers/xsd' export const endingTags = [] export const postRenders = [] -export const schemaXsds = [] export const registerMJElement = Component => { - const { endingTag, postRender, tagName, schemaXsd } = Component + const { endingTag, postRender, tagName } = Component if (!tagName) { return warning(false, 'Component has no tagName') @@ -15,7 +13,6 @@ export const registerMJElement = Component => { endingTag === true && endingTags.push(tagName) postRender && postRenders.push(postRender) - schemaXsds.push(schemaXsd || defaultXsd(Component)) MJMLElementsCollection[tagName] = Component } diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index b1e817990..03590f81b 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -10,6 +10,7 @@ import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' import juice from 'juice' +import MJMLValidator from './parsers/validator' import MJMLElementsCollection, { postRenders } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' import React from 'react' @@ -45,21 +46,24 @@ export default class MJMLRenderer { this.content = includeExternal(this.content) debug('Start parsing document') - const { html, errors } = documentParser(this.content, this.attributes, this.options) - - this.content = html - this.errors = errors + this.content = documentParser(this.content, this.attributes, this.options) debug('Content parsed') } + validate () { + this.errors = MJMLValidator(this.content) + } + render () { if (!this.content) { throw new EmptyMJMLError(`.render: No MJML to render in options ${this.options.toString()}`) } - const rootElemComponent = React.createElement(MJMLElementsCollection[this.content.tagName], { mjml: parseInstance(this.content, this.attributes ) }) + debug('Validating markup') + this.validate() debug('Render to static markup') + const rootElemComponent = React.createElement(MJMLElementsCollection[this.content.tagName], { mjml: parseInstance(this.content, this.attributes ) }) const renderedMJML = ReactDOMServer.renderToStaticMarkup(rootElemComponent) debug('React rendering done. Continue with special overrides.') diff --git a/packages/mjml-core/src/configs/defaultXsd.js b/packages/mjml-core/src/configs/defaultXsd.js deleted file mode 100644 index 12c3e4888..000000000 --- a/packages/mjml-core/src/configs/defaultXsd.js +++ /dev/null @@ -1,30 +0,0 @@ -import elements from '../MJMLElementsCollection' -import includes from 'lodash/includes' - -export default (schemas = '') => { - const allowedElements = Object.keys(elements).map(element => includes(elements[element].parentTag, 'mj-body') ? elements[element].tagName : null).filter(Boolean) - - return ` - - - - - - - - - - - - - - - ${allowedElements.map(elem => ``).join(`\n`)} - - - ${schemas} - - - - ` -} diff --git a/packages/mjml-core/src/decorators/MJMLElement.js b/packages/mjml-core/src/decorators/MJMLElement.js index 73f3e79ce..0d1538f7a 100644 --- a/packages/mjml-core/src/decorators/MJMLElement.js +++ b/packages/mjml-core/src/decorators/MJMLElement.js @@ -5,7 +5,6 @@ import React, { Component } from 'react' import ReactDOMServer from 'react-dom/server' import trim from 'lodash/trim' import merge from 'lodash/merge' -import warning from 'warning' import hoistNonReactStatic from 'hoist-non-react-statics'; const getElementWidth = ({ element, siblings, parentWidth }) => { @@ -201,8 +200,7 @@ function createComponent (ComposedComponent) { const Element = MJMLElementsCollection[tag] if (!Element) { - warning(false, `Could not find element for : ${tag}`) - return null + return null; } return ( @@ -214,8 +212,14 @@ function createComponent (ComposedComponent) { }) } + validChildren () { + const { children } = this.props + + return (children || this.generateChildren()).filter(Boolean) + } + buildProps () { - const { parentMjml, children } = this.props + const { parentMjml } = this.props const childMethods = [ 'mjAttribute', @@ -237,7 +241,7 @@ function createComponent (ComposedComponent) { mjName: this.mjName(), // generate children - children: children || this.generateChildren(), + children: this.validChildren(), // siblings count, can change display sibling: siblingCount, diff --git a/packages/mjml-core/src/helpers/dom.js b/packages/mjml-core/src/helpers/dom.js index f30fe40e6..8984330fe 100644 --- a/packages/mjml-core/src/helpers/dom.js +++ b/packages/mjml-core/src/helpers/dom.js @@ -67,7 +67,7 @@ if (isBrowser) { dom.parseHTML = str => parseMarkup(str, { xmlMode: false, decodeEntities: false }) - dom.parseXML = str => parseMarkup(str, { xmlMode: true, decodeEntities: false }) + dom.parseXML = str => parseMarkup(str, { xmlMode: true, decodeEntities: false, withStartIndices: true }) dom.getAttributes = element => element.attribs || {} diff --git a/packages/mjml-core/src/helpers/xsd.js b/packages/mjml-core/src/helpers/xsd.js deleted file mode 100644 index 515a5f81b..000000000 --- a/packages/mjml-core/src/helpers/xsd.js +++ /dev/null @@ -1,121 +0,0 @@ -import elements from '../MJMLElementsCollection' -import includes from 'lodash/includes' -import filter from 'lodash/filter' - -class XsdError { - constructor (errors) { - this.errors = errors - } - - defaultError ({line, column, code, message}) { - return this.formatLine(line, column, `Uknown MJML error, please open an issue on github.com/mjmlio/mjml with code: ${code} and message: ${message}`) - } - - formatLine (line, column, message) { - return { line, column, message } - } - - format (error) { - const cleanedErrorMessage = error.message.replace(/'/gmi, '"') - const errorFormater = XsdError.CODES[error.code] - - if (!errorFormater) { - return this.defaultError(error) - } - - const { regexp, message } = errorFormater - const matching = [] - let matched - - do { - matched = regexp.exec(cleanedErrorMessage) - - if (matched) { - matching.push(matched[1].trim()) - } - } while (matched) - - if (matching.length < 1) { - return this.defaultError(error) - } - - return this.formatLine(error.line, error.column, matching.reduce((finalMessage, value, index) => finalMessage.replace(`$${index}`, value), message)) - } - - getErrors () { - return filter(this.errors, (error) => !includes(XsdError.SKIP_CODES, error.code)).map((error) => this.format(error)) - } - - getMessages () { - return this.getErrors().map( v => `Line ${v.line}: ${v.message}` ).join('\n') - } -} -XsdError.SKIP_CODES = [ "1824" ] - -XsdError.CODES = { - "1843": { - regexp: /"(.*?)"/gmi, - message: `Plain text content inside "$0" isn't allowed` - }, - "1840": { - regexp: /[\{|"](.*?)["|\}]/gmi, - message: `Tag "$0" doesn't support "$3" value on "$1" attribute, only $4 are accepted` - }, - "1866": { - regexp: /"(.*?)"/gmi, - message: `Tag "$0" has no attribute "$1"` - }, - "1871": { - regexp: /[\(|"](.*?)["|\)]/gmi, - message: `Tag "$0" is not allowed here, only "$1" are accepted` - } -} - -export { XsdError } - -const endingTagXsd = (Component) => { - - return `${Component.selfClosingTag ? '' : ` - - - - - `} - - - ${Component.selfClosingTag ? "" : ` - - `} - ${Object.keys(Component.defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - ${Component.selfClosingTag ? "" : ` - - `} - - - ` -} - -const defaultXsd = (Component) => { - const allowedElements = Object.keys(elements).map(element => includes(elements[element].parentTag, Component.tagName) ? elements[element].tagName : null).filter(Boolean) - - return ` - - - ${allowedElements.map(elem => ``).join(`\n`)} - - - - - - - ${Object.keys(Component.defaultMJMLDefinition.attributes).map(attribute => ``).join(`\n`)} - - - - - ` -} - -export default (Component) => { - return () => Component.endingTag ? endingTagXsd(Component) : defaultXsd(Component) -} diff --git a/packages/mjml-core/src/parsers/document.js b/packages/mjml-core/src/parsers/document.js index c15971617..82203d07b 100644 --- a/packages/mjml-core/src/parsers/document.js +++ b/packages/mjml-core/src/parsers/document.js @@ -3,12 +3,9 @@ import compact from 'lodash/compact' import dom from '../helpers/dom' import each from 'lodash/each' import filter from 'lodash/filter' -import MJMLElements, { endingTags, schemaXsds } from '../MJMLElementsCollection' +import { endingTags } from '../MJMLElementsCollection' import MJMLHeadElements from '../MJMLHead' import warning from 'warning' -import defaultXsd from '../configs/defaultXsd' -import { XsdError } from '../helpers/xsd' -import libXsd from 'libxml-xsd' const regexTag = tag => new RegExp(`<${tag}([^>]*)>([^]*?)`, 'gmi') @@ -35,27 +32,23 @@ const safeEndingTags = content => { /** * converts MJML body into a JSON representation */ -const mjmlElementParser = elem => { +const mjmlElementParser = (elem, content) => { if (!elem) { throw new NullElementError('Null element found in mjmlElementParser') } + const lineNumber = content.substr(0, elem.startIndex).match(/\n/g).length + 1 const tagName = elem.tagName.toLowerCase() const attributes = dom.getAttributes(elem) - const element = { tagName, attributes } - - if (!MJMLElements[tagName]) { - warning(false, `Unregistered element: ${tagName}, skipping it`) - return - } + const element = { tagName, attributes, lineNumber } if (endingTags.indexOf(tagName) !== -1) { - const $ = dom.parseXML(elem) - element.content = $(tagName).html().trim() + const $local = dom.parseXML(elem) + element.content = $local(tagName).html().trim() } else { const children = dom.getChildren(elem) - element.children = children ? compact(filter(children, child => child.tagName).map(mjmlElementParser)) : [] + element.children = children ? compact(filter(children, child => child.tagName).map(child => mjmlElementParser(child, content))) : [] } return element @@ -77,32 +70,17 @@ const parseHead = (head, attributes) => { attributes.container = dom.getHTML($container) } -const validateDocument = (content, level) => { - const schemaXsd = defaultXsd(schemaXsds.map(schemaXsd => schemaXsd(MJMLElements)).join(`\n`)) - const schema = libXsd.parse(schemaXsd) - const errors = schema.validate(content) - - if (errors && errors.length > 0) { - if (level == "soft") { - return new XsdError(errors).getMessages() - } - - throw new XsdError(errors) - } -} - /** * Import an html document containing some mjml * returns JSON * - container: the mjml container * - mjml: a json representation of the mjml */ -const documentParser = (content, attributes, options) => { +const documentParser = (content, attributes) => { const safeContent = safeEndingTags(content) let body let head - let errors try { const $ = dom.parseXML(safeContent) @@ -121,15 +99,11 @@ const documentParser = (content, attributes, options) => { throw new EmptyMJMLError('No root "" or "" found in the file') } - if (options.level != "skip") { - errors = validateDocument(safeContent, options.level) - } - if (head && head.length === 1) { parseHead(head.get(0), attributes) } - return { errors, html: mjmlElementParser(body) } + return mjmlElementParser(body, safeContent) } export default documentParser diff --git a/packages/mjml-core/src/parsers/validator.js b/packages/mjml-core/src/parsers/validator.js new file mode 100644 index 000000000..ee6acbc3b --- /dev/null +++ b/packages/mjml-core/src/parsers/validator.js @@ -0,0 +1,17 @@ +import concat from 'lodash/concat' +import filter from 'lodash/filter' +import values from 'lodash/values' +import * as rules from '../rules' + +const validateNode = element => { + const { children } = element + let errors = concat(errors, ...values(rules).map(rule => rule(element))) + + if (children && children.length > 0) { + errors = concat(errors, ...children.map(validateNode)) + } + + return filter(errors) +} + +export default validateNode diff --git a/packages/mjml-core/src/rules/index.js b/packages/mjml-core/src/rules/index.js new file mode 100644 index 000000000..73292abc1 --- /dev/null +++ b/packages/mjml-core/src/rules/index.js @@ -0,0 +1,3 @@ +export * from './validAttributes' +export * from './validChildren' +export * from './validTag' diff --git a/packages/mjml-core/src/rules/ruleError.js b/packages/mjml-core/src/rules/ruleError.js new file mode 100644 index 000000000..f1e959c93 --- /dev/null +++ b/packages/mjml-core/src/rules/ruleError.js @@ -0,0 +1,10 @@ +export default (message, element) => { + const { lineNumber: line, tagName } = element + + return { + line, + message, + tagName, + formattedMessage: `Line ${line} (${tagName}) — ${message}` + } +} diff --git a/packages/mjml-core/src/rules/validAttributes.js b/packages/mjml-core/src/rules/validAttributes.js new file mode 100644 index 000000000..73572f543 --- /dev/null +++ b/packages/mjml-core/src/rules/validAttributes.js @@ -0,0 +1,23 @@ +import MJMLElementsCollection from '../MJMLElementsCollection' +import keys from 'lodash/keys' +import includes from 'lodash/includes' +import filter from 'lodash/filter' +import ruleError from './ruleError' + +export const validateAttribute = (element) => { + const { attributes, tagName } = element + const Component = MJMLElementsCollection[tagName] + + if (!Component) { + return; + } + + const avalaibleAttributes = keys(Component.defaultMJMLDefinition.attributes) + const unknownAttributes = filter(keys(attributes), attribute => !includes(avalaibleAttributes, attribute)) + + if (unknownAttributes.length == 0) { + return; + } + + return ruleError(`Attributes ${unknownAttributes.join(', ')} ${unknownAttributes.length > 1 ? "are illegals" : "is illegal"}`, element) +} diff --git a/packages/mjml-core/src/rules/validChildren.js b/packages/mjml-core/src/rules/validChildren.js new file mode 100644 index 000000000..fc932281a --- /dev/null +++ b/packages/mjml-core/src/rules/validChildren.js @@ -0,0 +1,32 @@ +import MJMLElementsCollection from '../MJMLElementsCollection' +import filter from 'lodash/filter' +import includes from 'lodash/includes' +import ruleError from './ruleError' + +export const validChildren = (element) => { + const { children, tagName } = element + const Component = MJMLElementsCollection[tagName] + + if (!Component) { + return; + } + + if (!children || children.length == 0) { + return; + } + + return filter(children.map((child) => { + const childTagName = child.tagName + const ChildComponent = MJMLElementsCollection[childTagName] + + if (!ChildComponent) { + return null; + } + + if (includes(ChildComponent.parentTag, tagName)) { + return null; + } + + return ruleError(`Cannot be used inside ${tagName}, only in : ${ChildComponent.parentTag.join(', ')}`, child) + })) +} diff --git a/packages/mjml-core/src/rules/validTag.js b/packages/mjml-core/src/rules/validTag.js new file mode 100644 index 000000000..582a4add2 --- /dev/null +++ b/packages/mjml-core/src/rules/validTag.js @@ -0,0 +1,11 @@ +import MJMLElementsCollection from '../MJMLElementsCollection' +import ruleError from './ruleError' + +export const validateTag = (element) => { + const { tagName } = element + const Component = MJMLElementsCollection[tagName] + + if (!Component) { + return ruleError(`Element ${tagName} doesn't exist or is not registered`, element) + } +} diff --git a/packages/mjml/test.js b/packages/mjml/test.js index a697a8758..0c28050a9 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -5,25 +5,12 @@ const azeaze = console.log(mjml.mjml2html(` + zaopae - + 2 coloumnes $2 - - - - Check out promotions ! - - - foo © bar ≠ baz 𝌆 qux - + From cce1c796445aa9d2c5fa126b057f7d3c508044ad Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 22 Sep 2016 09:02:37 +0200 Subject: [PATCH 43/87] bring error level back --- packages/mjml-cli/src/client.js | 3 +-- packages/mjml-core/src/Error.js | 14 ++++++++++---- packages/mjml-core/src/MJMLRenderer.js | 10 +++++++++- packages/mjml-core/src/parsers/validator.js | 2 +- packages/mjml-core/src/rules/ruleError.js | 2 +- packages/mjml/test.js | 4 ++-- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index dca1fa76c..7b702e306 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -76,8 +76,7 @@ const render = (bufferPromise, { min, output, stdout, fileName, level }) => { stdout ? process.stdout.write(content) : write(output, content) }) .catch(e => { - // XSD validation error ? - if (e.getErrors) { + if (e.getMessages) { return error(`${fileName ? `File: ${fileName} \n` : ``}${e.getMessages()}`) } diff --git a/packages/mjml-core/src/Error.js b/packages/mjml-core/src/Error.js index f9492e905..3fb62658f 100644 --- a/packages/mjml-core/src/Error.js +++ b/packages/mjml-core/src/Error.js @@ -1,4 +1,3 @@ - /* * Create a custom Error class */ @@ -40,7 +39,14 @@ export const EmptyMJMLError = error('EmptyMJMLError', 2) */ export const NullElementError = error('EmptyMJMLError', 3) +class MJMLValidationError { + constructor (errors) { + this.errors = errors + } -/* - * TODO: Warnings - */ + getMessages () { + return this.errors.map(error => error.formattedMessage).join('') + } +} + +export { MJMLValidationError } diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 03590f81b..9915073ff 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -1,4 +1,4 @@ -import { EmptyMJMLError } from './Error' +import { EmptyMJMLError, MJMLValidationError } from './Error' import { fixLegacyAttrs, removeCDATA } from './helpers/postRender' import { parseInstance } from './helpers/mjml' import cloneDeep from 'lodash/cloneDeep' @@ -51,7 +51,15 @@ export default class MJMLRenderer { } validate () { + if (this.options.level == "skip") { + return; + } + this.errors = MJMLValidator(this.content) + + if (this.options.level == "strict" && this.errors.length > 0) { + throw new MJMLValidationError(this.errors) + } } render () { diff --git a/packages/mjml-core/src/parsers/validator.js b/packages/mjml-core/src/parsers/validator.js index ee6acbc3b..9f28bf0dc 100644 --- a/packages/mjml-core/src/parsers/validator.js +++ b/packages/mjml-core/src/parsers/validator.js @@ -5,7 +5,7 @@ import * as rules from '../rules' const validateNode = element => { const { children } = element - let errors = concat(errors, ...values(rules).map(rule => rule(element))) + let errors = concat([], ...values(rules).map(rule => rule(element))) if (children && children.length > 0) { errors = concat(errors, ...children.map(validateNode)) diff --git a/packages/mjml-core/src/rules/ruleError.js b/packages/mjml-core/src/rules/ruleError.js index f1e959c93..71bf4458d 100644 --- a/packages/mjml-core/src/rules/ruleError.js +++ b/packages/mjml-core/src/rules/ruleError.js @@ -5,6 +5,6 @@ export default (message, element) => { line, message, tagName, - formattedMessage: `Line ${line} (${tagName}) — ${message}` + formattedMessage: `Line ${line} (${tagName}) — ${message}\n` } } diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 0c28050a9..ba29ec4c5 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -15,9 +15,9 @@ const azeaze = console.log(mjml.mjml2html(` -`, { beautify: true })) +`, { beautify: true, level: "strict" })) } catch(e) { - if (e.getErrors) { + if (e.getMessages) { console.log(e.getMessages()) } else { throw e From 19e2079889d9b2aff5e3f9abbaee5a4c3db3d6ee Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 22 Sep 2016 15:44:24 +0200 Subject: [PATCH 44/87] Move mjml-validator in it's own package --- install.sh | 26 +- packages/mjml-cli/bin/mjml | 2 +- packages/mjml-cli/src/client.js | 2 +- packages/mjml-core/package.json | 2 +- packages/mjml-core/src/MJMLRenderer.js | 4 +- packages/mjml-image/src/index.js | 3 +- packages/mjml-validator/package.json | 18 + .../src/index.js} | 2 +- .../src/rules/index.js | 0 .../src/rules/ruleError.js | 2 +- .../src/rules/validAttributes.js | 4 +- .../src/rules/validChildren.js | 6 +- .../src/rules/validTag.js | 4 +- packages/mjml/test.js | 333 +++++++++++++++++- 14 files changed, 373 insertions(+), 35 deletions(-) create mode 100644 packages/mjml-validator/package.json rename packages/{mjml-core/src/parsers/validator.js => mjml-validator/src/index.js} (92%) rename packages/{mjml-core => mjml-validator}/src/rules/index.js (100%) rename packages/{mjml-core => mjml-validator}/src/rules/ruleError.js (67%) rename packages/{mjml-core => mjml-validator}/src/rules/validAttributes.js (85%) rename packages/{mjml-core => mjml-validator}/src/rules/validChildren.js (77%) rename packages/{mjml-core => mjml-validator}/src/rules/validTag.js (65%) diff --git a/install.sh b/install.sh index 5cef97b1b..bb9e5f103 100755 --- a/install.sh +++ b/install.sh @@ -23,19 +23,25 @@ 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 .. @@ -55,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 @@ -85,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/packages/mjml-cli/bin/mjml b/packages/mjml-cli/bin/mjml index b1f59f8e6..8f90fbab6 100755 --- a/packages/mjml-cli/bin/mjml +++ b/packages/mjml-cli/bin/mjml @@ -18,7 +18,7 @@ binary cli.renderFile(files, options) }) - .option('-l, --level [level]', 'Specifies the level of validation of MJML parser (skip/soft/strict)', /^(skip|soft|strict)$/i, 'strict') + .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') diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index 7b702e306..e338cc076 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -70,7 +70,7 @@ const render = (bufferPromise, { min, output, stdout, fileName, level }) => { const { html: content, errors } = result if (errors) { - process.stderr.write(errors) + process.stderr.write(errors.map(err => err.formattedMessage).join('\n')) } stdout ? process.stdout.write(content) : write(output, content) diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index eb61530fd..190d071c3 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -31,8 +31,8 @@ "jquery": "^3.1.0", "js-beautify": "^1.6.3", "juice": "^2.0.0", - "libxml-xsd": "^0.5.2", "lodash": "^4.14.2", + "mjml-validator": "^3.0.0", "react": "^15.3.0", "react-dom": "^15.3.0", "warning": "^3.0.0" diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 9915073ff..26dc20cb3 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -10,7 +10,7 @@ import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' import juice from 'juice' -import MJMLValidator from './parsers/validator' +import MJMLValidator from 'mjml-validator' import MJMLElementsCollection, { postRenders } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' import React from 'react' @@ -35,7 +35,7 @@ export default class MJMLRenderer { this.content = content this.options = options - this.options["level"] = this.options["level"] || "strict" + this.options["level"] = this.options["level"] || "soft" if (typeof this.content === 'string') { this.parseDocument() diff --git a/packages/mjml-image/src/index.js b/packages/mjml-image/src/index.js index 2435d29d0..1178faceb 100644 --- a/packages/mjml-image/src/index.js +++ b/packages/mjml-image/src/index.js @@ -24,7 +24,8 @@ const defaultMJMLDefinition = { 'src': '', 'target': '_blank', 'title': '', - 'vertical-align': null + 'vertical-align': null, + 'width': null } } const baseStyles = { diff --git a/packages/mjml-validator/package.json b/packages/mjml-validator/package.json new file mode 100644 index 000000000..64e9fd768 --- /dev/null +++ b/packages/mjml-validator/package.json @@ -0,0 +1,18 @@ +{ + "name": "mjml-validator", + "description": "mjml-validator", + "version": "3.0.0", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/mjmlio/mjml.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mjmlio/mjml/issues" + }, + "homepage": "https://mjml.io", + "dependencies": { + "lodash": "^4.16.1" + } +} diff --git a/packages/mjml-core/src/parsers/validator.js b/packages/mjml-validator/src/index.js similarity index 92% rename from packages/mjml-core/src/parsers/validator.js rename to packages/mjml-validator/src/index.js index 9f28bf0dc..7dae1427a 100644 --- a/packages/mjml-core/src/parsers/validator.js +++ b/packages/mjml-validator/src/index.js @@ -1,7 +1,7 @@ import concat from 'lodash/concat' import filter from 'lodash/filter' import values from 'lodash/values' -import * as rules from '../rules' +import * as rules from './rules' const validateNode = element => { const { children } = element diff --git a/packages/mjml-core/src/rules/index.js b/packages/mjml-validator/src/rules/index.js similarity index 100% rename from packages/mjml-core/src/rules/index.js rename to packages/mjml-validator/src/rules/index.js diff --git a/packages/mjml-core/src/rules/ruleError.js b/packages/mjml-validator/src/rules/ruleError.js similarity index 67% rename from packages/mjml-core/src/rules/ruleError.js rename to packages/mjml-validator/src/rules/ruleError.js index 71bf4458d..f1e959c93 100644 --- a/packages/mjml-core/src/rules/ruleError.js +++ b/packages/mjml-validator/src/rules/ruleError.js @@ -5,6 +5,6 @@ export default (message, element) => { line, message, tagName, - formattedMessage: `Line ${line} (${tagName}) — ${message}\n` + formattedMessage: `Line ${line} (${tagName}) — ${message}` } } diff --git a/packages/mjml-core/src/rules/validAttributes.js b/packages/mjml-validator/src/rules/validAttributes.js similarity index 85% rename from packages/mjml-core/src/rules/validAttributes.js rename to packages/mjml-validator/src/rules/validAttributes.js index 73572f543..5e7e623c9 100644 --- a/packages/mjml-core/src/rules/validAttributes.js +++ b/packages/mjml-validator/src/rules/validAttributes.js @@ -1,4 +1,4 @@ -import MJMLElementsCollection from '../MJMLElementsCollection' +import { elements } from 'mjml-core' import keys from 'lodash/keys' import includes from 'lodash/includes' import filter from 'lodash/filter' @@ -6,7 +6,7 @@ import ruleError from './ruleError' export const validateAttribute = (element) => { const { attributes, tagName } = element - const Component = MJMLElementsCollection[tagName] + const Component = elements[tagName] if (!Component) { return; diff --git a/packages/mjml-core/src/rules/validChildren.js b/packages/mjml-validator/src/rules/validChildren.js similarity index 77% rename from packages/mjml-core/src/rules/validChildren.js rename to packages/mjml-validator/src/rules/validChildren.js index fc932281a..37adce93c 100644 --- a/packages/mjml-core/src/rules/validChildren.js +++ b/packages/mjml-validator/src/rules/validChildren.js @@ -1,11 +1,11 @@ -import MJMLElementsCollection from '../MJMLElementsCollection' +import { elements } from 'mjml-core' import filter from 'lodash/filter' import includes from 'lodash/includes' import ruleError from './ruleError' export const validChildren = (element) => { const { children, tagName } = element - const Component = MJMLElementsCollection[tagName] + const Component = elements[tagName] if (!Component) { return; @@ -17,7 +17,7 @@ export const validChildren = (element) => { return filter(children.map((child) => { const childTagName = child.tagName - const ChildComponent = MJMLElementsCollection[childTagName] + const ChildComponent = elements[childTagName] if (!ChildComponent) { return null; diff --git a/packages/mjml-core/src/rules/validTag.js b/packages/mjml-validator/src/rules/validTag.js similarity index 65% rename from packages/mjml-core/src/rules/validTag.js rename to packages/mjml-validator/src/rules/validTag.js index 582a4add2..d298d837b 100644 --- a/packages/mjml-core/src/rules/validTag.js +++ b/packages/mjml-validator/src/rules/validTag.js @@ -1,9 +1,9 @@ -import MJMLElementsCollection from '../MJMLElementsCollection' +import { elements } from 'mjml-core' import ruleError from './ruleError' export const validateTag = (element) => { const { tagName } = element - const Component = MJMLElementsCollection[tagName] + const Component = elements[tagName] if (!Component) { return ruleError(`Element ${tagName} doesn't exist or is not registered`, element) diff --git a/packages/mjml/test.js b/packages/mjml/test.js index ba29ec4c5..08e5b134d 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -1,21 +1,328 @@ var mjml = require('./lib/index') -try { -const azeaze = console.log(mjml.mjml2html(` - + +const inputMJML = ` - - - zaopae - - - 2 coloumnes $2 - - - + + + + + [[HEADLINE]] + + + + + [[PERMALINK_LABEL]] + + + + + + + + + + + +

home               + blog            visit store  >

+
+
+
+ + + +

SPRING PROMO +

+

50% +

+

OFFER +

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit

+
+ + SHOP NOW + +
+ + + + + +
+ + + + + + + + +

FREE SHIPPING ON ORDER OVER 55€ + +

+
+
+
+ + + + + + +

CHESTERK TANK +

+

15€ + +

+
+ + BUY NOW + +
+ + + + + +

BEYOND BACKPACK +

+

20€ + +

+
+ + BUY NOW + +
+ + + + + +

JENSEN SHORTS +

+

28€ + +

+
+ + BUY NOW + +
+
+ + + + + + +

VERDANT CAP +

+

20€ + +

+
+ + BUY NOW + +
+ + + + + +

BLAKE POLO SHIRT +

+

25€ + +

+
+ + BUY NOW + +
+ + + + + +

SKETCH FLORAL +

+

23€ + +

+
+ + BUY NOW + +
+
+ + + + + + + + +

ANDERSON SWEATER +

+

75€ +

+

The Anderson Sweater features a floral all-over print with contrast colour.

+
+ + BUY NOW + +
+
+ + + +

ALDER TWO JONES JACKET +

+

100€ +

+

Colour-block design, zip entry, oxford hood lining, side pockets & TC lining.

+
+ + BUY NOW + +
+ + + + + +
+ + + +

DISCOVER OUR +

+

SUMMER COLLECTION +

+
+
+
+ + + + + + +

TOPAZ C3 SHOES +

+

70€ + +

+
+ + BUY NOW + +
+ + + + + +

CAMDEN BACKPACK +

+

50€ + +

+
+ + BUY NOW + +
+
+ + + + + + +

PAYMENT METHODS + +

+

+ We accept all majors payments options +

+
+
+ + + + + +

CURRENCIES CHOICE + +

+

+ You have the choice to pay with your own currencies +

+
+
+ + + + + +

EXPRESS SHIPPING + +

+

+ Delivered tomorrow before noon +

+
+
+
+ + + + + + + + +

Privacy policy

+
+
+ + + + + +
+ + + +

[[DELIVERY_INFO]]

+
+ +

[[POSTAL_ADDRESS]]

+
+
-
`, { beautify: true, level: "strict" })) +
` + +try { + const azeaze = console.log(mjml.mjml2html(inputMJML, { beautify: true, level: "soft" })) } catch(e) { if (e.getMessages) { console.log(e.getMessages()) From 0b9cc7f0fef210f01be130bbfe86cd3377d0b476 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 22 Sep 2016 16:18:24 +0200 Subject: [PATCH 45/87] Will fix button with merge of #387 --- packages/mjml-button/src/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index 718056cae..fe1568cbb 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -55,6 +55,9 @@ class Button extends Component { const { mjAttribute, defaultUnit } = this.props return merge({}, baseStyles, { + table: { + width: mjAttribute('width') + }, td: { border: mjAttribute('border'), borderBottom: mjAttribute('border-bottom'), @@ -66,8 +69,7 @@ class Button extends Component { cursor: 'auto', fontStyle: mjAttribute('font-style'), height: mjAttribute('height'), - padding: defaultUnit(mjAttribute('inner-padding'), "px"), - width: mjAttribute('width') + padding: defaultUnit(mjAttribute('inner-padding'), "px") }, a: { background: mjAttribute('background-color'), @@ -76,7 +78,6 @@ class Button extends Component { 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" From b13be77df19d2132b78da0a72d354361c2322d69 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 10:42:57 +0200 Subject: [PATCH 46/87] Doc + minor fix --- doc/config.json | 1 + packages/mjml-core/src/Error.js | 6 +++++- packages/mjml-validator/.npmignore | 3 +++ packages/mjml-validator/README.md | 20 ++++++++++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 packages/mjml-validator/.npmignore create mode 100644 packages/mjml-validator/README.md 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/packages/mjml-core/src/Error.js b/packages/mjml-core/src/Error.js index 3fb62658f..f889b9322 100644 --- a/packages/mjml-core/src/Error.js +++ b/packages/mjml-core/src/Error.js @@ -45,7 +45,11 @@ class MJMLValidationError { } getMessages () { - return this.errors.map(error => error.formattedMessage).join('') + return this.errors.map(error => error.formattedMessage) + } + + getErrors () { + return this.errors } } diff --git a/packages/mjml-validator/.npmignore b/packages/mjml-validator/.npmignore new file mode 100644 index 000000000..246c4aa25 --- /dev/null +++ b/packages/mjml-validator/.npmignore @@ -0,0 +1,3 @@ +node_modules +src +test diff --git a/packages/mjml-validator/README.md b/packages/mjml-validator/README.md new file mode 100644 index 000000000..39b594684 --- /dev/null +++ b/packages/mjml-validator/README.md @@ -0,0 +1,20 @@ +# mjml-validator + +MJML provide a validation layer that help you building your email. It can detects if you misplaced or mispelled an element, same with attributes. It supports 3 level of validation : +- skip : your document is not validated at all +- soft (default) : your document is validated, but even if it has errors it will be rendered +- strict : your document is validated but if it has errors it will not render anything + +## In cli + +In `mjml` command line you can add a `-l` or `--level` with validation level you want. Ex: `mjml -l strict -r my_template.mjml` + +## In Javascript + +In Javascript you can provide the level through the `options` params on `MJMLRenderer`. Ex: `new MJMLRenderer(inputMJML, { level: strict })` + +`strict` will raise an `MJMLValidationError` exception. This object has 2 methods: +- `getErrors` that returns you an array of object with `line`, `message` and `tagName` and a `formattedMessage`. +- `getMessages` that returns you an array of `formattedMessage`. + +`soft` will not raise an exception, instead you can find errors inside the object returned by `render` methods : `errors`. It the same object returned by `getErrors` on strict mode From 758c6f3cb25477a95680efefce1826cbcd736f0a Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 16:49:21 +0200 Subject: [PATCH 47/87] Cleaner renderer + fix CI ? --- packages/mjml-core/src/MJMLRenderer.js | 48 ++++++-------------- packages/mjml-core/test/input-output.spec.js | 6 +-- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 26dc20cb3..0fe6e4c17 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -3,13 +3,17 @@ import { fixLegacyAttrs, removeCDATA } from './helpers/postRender' import { parseInstance } from './helpers/mjml' import cloneDeep from 'lodash/cloneDeep' import configParser from './parsers/config' +import curryRight from 'lodash/curryRight' import documentParser from './parsers/document' import defaultContainer from './configs/defaultContainer' import defaultFonts from './configs/listFontsImports' +import dom from './helpers/dom' import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' import juice from 'juice' +import { html as beautify } from 'js-beautify' +import { minify } from 'html-minifier' import MJMLValidator from 'mjml-validator' import MJMLElementsCollection, { postRenders } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' @@ -18,6 +22,10 @@ import ReactDOMServer from 'react-dom/server' const debug = require('debug')('mjml-engine/mjml2html') +const minifyHTML = htmlDocument => minify(htmlDocument, { collapseWhitespace: true, removeEmptyAttributes: true, minifyCSS: true }) +const beautifyHTML = htmlDocument => beautify(htmlDocument, { indent_size: 2, wrap_attributes_indent_size: 2 }) +const inlineExternal = (htmlDocument, css) => juice(htmlDocument, { extraCss: css, removeStyleTags: false, applyStyleTags: false, insertPreservedExtraCss: false }) + export default class MJMLRenderer { constructor (content, options = {}) { @@ -81,7 +89,7 @@ export default class MJMLRenderer { } postRender (MJMLDocument) { - const dom = require('./helpers/dom').default + const externalCSS = this.attributes.css.join('') let $ = dom.parseHTML(MJMLDocument) @@ -94,38 +102,12 @@ export default class MJMLRenderer { } }) - let finalMJMLDocument = dom.getHTML($) - finalMJMLDocument = removeCDATA(finalMJMLDocument) - - finalMJMLDocument = juice(finalMJMLDocument, { - extraCss: `${this.attributes.css.join('')}`, - removeStyleTags: false, - applyStyleTags: false, - insertPreservedExtraCss: false - }) - - if (this.options.beautify) { - const beautify = require('js-beautify').html - - finalMJMLDocument = beautify(finalMJMLDocument, { - indent_size: 2, - wrap_attributes_indent_size: 2 - }) - } - - if (this.options.minify) { - const minify = require('html-minifier').minify - - finalMJMLDocument = minify(finalMJMLDocument, { - collapseWhitespace: true, - removeEmptyAttributes: true, - minifyCSS: true - }) - } - - finalMJMLDocument = he.decode(finalMJMLDocument) - - return finalMJMLDocument + return [ removeCDATA, + curryRight(inlineExternal)(externalCSS), + this.options.beautify ? beautifyHTML : undefined, + this.options.minify ? minifyHTML : undefined, + he.decode ].filter(element => typeof element == 'function') + .reduce((res, fun) => fun(res), dom.getHTML($)) } } diff --git a/packages/mjml-core/test/input-output.spec.js b/packages/mjml-core/test/input-output.spec.js index b9a8289ef..bb0c09a9c 100644 --- a/packages/mjml-core/test/input-output.spec.js +++ b/packages/mjml-core/test/input-output.spec.js @@ -33,7 +33,7 @@ describe('MJML Renderer', () => {
-
`, { validationLevel: "skip" }).render() +
`, { level: "skip" }).render() ).to.throw(/EmptyMJMLError/) }) }) @@ -47,7 +47,7 @@ describe('MJML Renderer', () => {
-
`, { validationLevel: "skip" }).render() +
`, { level: "skip" }).render() ).to.not.contain('Mocked Component!') }) }) @@ -62,7 +62,7 @@ describe('MJML Renderer', () => {
-
`, { validationLevel: "skip" }).render() +
`, { level: "skip" }).render() ).to.contain('Mocked Component!') }) }) From 34559cadc10781f289e861da68dceb4ce3990ae0 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 17:05:42 +0200 Subject: [PATCH 48/87] link missing ? --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index dcea7701a..8710a7c97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,11 @@ node_js: - 6 script: - npm run lint + - cd packages/mjml-validator + - npm link mjml-core + - npm link - cd packages/mjml-core + - npm link mjml-validator + - npm link - npm install - npm test From b8e5b5863f6ed54ad009f83fda711158d5a398a3 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 17:13:12 +0200 Subject: [PATCH 49/87] build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8710a7c97..54450a2f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ script: - cd packages/mjml-validator - npm link mjml-core - npm link - - cd packages/mjml-core + - cd ../packages/mjml-core - npm link mjml-validator - npm link - npm install From 3cd84015215ba82675e5764e33cd6b01b72cdbb8 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 17:21:23 +0200 Subject: [PATCH 50/87] build again --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 54450a2f2..6909bf50f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ script: - cd packages/mjml-validator - npm link mjml-core - npm link - - cd ../packages/mjml-core + - cd ../mjml-core - npm link mjml-validator - npm link - npm install From 3544401f89a04d1e12518784b48580863ddc8678 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 17:33:23 +0200 Subject: [PATCH 51/87] Try to fix build... + test fix --- .travis.yml | 1 + packages/mjml-core/test/input-output.spec.js | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6909bf50f..2ff177819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ script: - cd packages/mjml-validator - npm link mjml-core - npm link + - npm install - cd ../mjml-core - npm link mjml-validator - npm link diff --git a/packages/mjml-core/test/input-output.spec.js b/packages/mjml-core/test/input-output.spec.js index bb0c09a9c..9205afada 100644 --- a/packages/mjml-core/test/input-output.spec.js +++ b/packages/mjml-core/test/input-output.spec.js @@ -29,9 +29,6 @@ describe('MJML Renderer', () => { expect(() => new MJMLRenderer(` - - - `, { level: "skip" }).render() ).to.throw(/EmptyMJMLError/) @@ -47,7 +44,7 @@ describe('MJML Renderer', () => {
-
`, { level: "skip" }).render() +
`, { level: "skip" }).render().html ).to.not.contain('Mocked Component!') }) }) @@ -62,7 +59,7 @@ describe('MJML Renderer', () => {
-
`, { level: "skip" }).render() +
`, { level: "skip" }).render().html ).to.contain('Mocked Component!') }) }) From 54a4c6d8c146b69c30c630fbff36bdd99c14d4aa Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Mon, 26 Sep 2016 18:03:42 +0200 Subject: [PATCH 52/87] list --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2ff177819..330b6e24c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,5 +23,6 @@ script: - cd ../mjml-core - npm link mjml-validator - npm link + - npm list - npm install - npm test From d4d81d13314a66bdc65edd4fec809175e824e224 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 11:55:18 +0200 Subject: [PATCH 53/87] test travis build --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 330b6e24c..6a799454c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,12 +17,10 @@ node_js: script: - npm run lint - cd packages/mjml-validator - - npm link mjml-core - npm link - npm install - cd ../mjml-core - npm link mjml-validator - - npm link - npm list - npm install - npm test From 252d15676ba3868daa6501cdfdc2da762b6971eb Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 12:05:31 +0200 Subject: [PATCH 54/87] travis build .. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6a799454c..4ef3d3279 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,6 @@ script: - npm install - cd ../mjml-core - npm link mjml-validator - - npm list - npm install + - npm list - npm test From 6346e8ac028bc5da53d450b7dd58d1a2efe72957 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 12:09:01 +0200 Subject: [PATCH 55/87] gulp build --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4ef3d3279..8264ca350 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ node_js: - 5 - 6 script: + - npm install + - gulp build - npm run lint - cd packages/mjml-validator - npm link From 435c7748f00084a700f082eb348f054c93708a3a Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 12:16:29 +0200 Subject: [PATCH 56/87] Link --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8264ca350..36741c736 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,6 @@ script: - npm install - cd ../mjml-core - npm link mjml-validator + - npm link - npm install - - npm list - npm test From fb2b772a650d6621c14eaf108f64effb6ae05cb1 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 12:28:01 +0200 Subject: [PATCH 57/87] link core again --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 36741c736..dcfddbb48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ script: - gulp build - npm run lint - cd packages/mjml-validator + - npm link mjml-core - npm link - npm install - cd ../mjml-core From 92c3f49e93bbc41b67603186328d08899415670e Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 13:17:37 +0200 Subject: [PATCH 58/87] deprecated stuff --- packages/mjml-core/src/index.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/mjml-core/src/index.js b/packages/mjml-core/src/index.js index 0cd4e89c0..c838b3639 100644 --- a/packages/mjml-core/src/index.js +++ b/packages/mjml-core/src/index.js @@ -1,5 +1,3 @@ -import warning from 'warning' - import MJMLRenderer from './MJMLRenderer' import elements, { registerMJElement } from './MJMLElementsCollection' import MJMLHeadElements, { registerMJHeadElement } from './MJMLHead' @@ -14,8 +12,3 @@ export const documentParser = content => { } export const version = () => '__MJML_VERSION__' export const mjml2html = (mjml, options = {}) => new MJMLRenderer(mjml, options).render() -export const registerElement = Component => { - warning(false, 'Please now use registerMJElement, registerElement is deprecated will no longer be supported soon') - - return registerMJElement(Component) -} From a8dcef29888bcd4318fa225592ebe0e0c5788530 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Tue, 27 Sep 2016 14:21:59 +0200 Subject: [PATCH 59/87] install before link ? --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dcfddbb48..3e25e06d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,6 @@ script: - npm install - cd ../mjml-core - npm link mjml-validator - - npm link - npm install + - npm link - npm test From 94fd24a6f10a5555c494907fe60685007aeb683f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Wed, 28 Sep 2016 09:47:26 +0200 Subject: [PATCH 60/87] Update dependencies, prepare mjml v3.0.0-beta.1 --- .eslintrc | 46 ++++++++++---------- package.json | 22 +++++----- packages/mjml-button/package.json | 8 ++-- packages/mjml-cli/package.json | 10 ++--- packages/mjml-column/package.json | 8 ++-- packages/mjml-container/package.json | 6 +-- packages/mjml-core/package.json | 22 +++++----- packages/mjml-divider/package.json | 8 ++-- packages/mjml-group/package.json | 8 ++-- packages/mjml-head-attributes/package.json | 4 +- packages/mjml-head-font/package.json | 4 +- packages/mjml-head-style/package.json | 2 +- packages/mjml-head-title/package.json | 2 +- packages/mjml-hero/package.json | 8 ++-- packages/mjml-html/package.json | 8 ++-- packages/mjml-image/package.json | 8 ++-- packages/mjml-invoice/package.json | 10 ++--- packages/mjml-list/package.json | 8 ++-- packages/mjml-location/package.json | 12 +++--- packages/mjml-navbar/package.json | 10 ++--- packages/mjml-navbar/src/InlineLinks.js | 4 +- packages/mjml-raw/package.json | 6 +-- packages/mjml-section/package.json | 8 ++-- packages/mjml-social/package.json | 8 ++-- packages/mjml-spacer/package.json | 6 +-- packages/mjml-table/package.json | 6 +-- packages/mjml-text/package.json | 8 ++-- packages/mjml-validator/package.json | 2 +- packages/mjml/package.json | 50 +++++++++++----------- 29 files changed, 157 insertions(+), 155 deletions(-) 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/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/package.json b/packages/mjml-button/package.json index cb777013b..9b0bce57d 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.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-cli/package.json b/packages/mjml-cli/package.json index d47663c83..52956c07f 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.1", "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.1" }, "devDependencies": { "chai": "^3.5.0", - "mocha": "^2.4.5" + "mocha": "^3.1.0" } } diff --git a/packages/mjml-column/package.json b/packages/mjml-column/package.json index f7ddc008f..af193aef3 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.10", + "version": "3.0.0-beta.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-container/package.json b/packages/mjml-container/package.json index cdd5bb26c..8d19de996 100644 --- a/packages/mjml-container/package.json +++ b/packages/mjml-container/package.json @@ -1,7 +1,7 @@ { "name": "mjml-container", "description": "mjml-container", - "version": "2.0.10", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "~2.3.2", - "react": "^15.3.0" + "mjml-core": "~3.0.0-beta.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index 190d071c3..2612e98d5 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -1,7 +1,7 @@ { "name": "mjml-core", "description": "mjml-core", - "version": "2.3.2", + "version": "3.0.0-beta.1", "main": "lib/index.js", "scripts": { "test": "mocha --compilers js:babel-register" @@ -21,24 +21,24 @@ "html-minifier": false }, "dependencies": { - "cheerio": "^0.20.0", + "cheerio": "^0.22.0", "classnames": "^2.2.5", "debug": "^2.2.0", "he": "^1.1.0", "hoist-non-react-statics": "^1.2.0", - "html-minifier": "^3.0.2", + "html-minifier": "^3.1.0", "immutable": "^3.8.1", - "jquery": "^3.1.0", - "js-beautify": "^1.6.3", - "juice": "^2.0.0", - "lodash": "^4.14.2", - "mjml-validator": "^3.0.0", - "react": "^15.3.0", - "react-dom": "^15.3.0", + "jquery": "^3.1.1", + "js-beautify": "^1.6.4", + "juice": "^3.0.0", + "lodash": "^4.16.2", + "mjml-validator": "~3.0.0-beta.1", + "react-dom": "^15.3.2", + "react": "^15.3.2", "warning": "^3.0.0" }, "devDependencies": { "chai": "^3.5.0", - "mocha": "^3.0.2" + "mocha": "^3.1.0" } } diff --git a/packages/mjml-divider/package.json b/packages/mjml-divider/package.json index 48fb48da2..9171e1630 100644 --- a/packages/mjml-divider/package.json +++ b/packages/mjml-divider/package.json @@ -1,7 +1,7 @@ { "name": "mjml-divider", "description": "mjml-divider", - "version": "2.0.11", + "version": "3.0.0-beta.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-group/package.json b/packages/mjml-group/package.json index 8b147269e..8a11b967c 100644 --- a/packages/mjml-group/package.json +++ b/packages/mjml-group/package.json @@ -1,7 +1,7 @@ { "name": "mjml-group", "description": "mjml-group", - "version": "2.0.4", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,8 +13,8 @@ }, "homepage": "https://github.com/mjmlio/mjml", "dependencies": { - "lodash": "^4.14.2", - "mjml-core": "~2.3.2", - "react": "^15.3.0" + "lodash": "^4.16.2", + "mjml-core": "~3.0.0-beta.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-head-attributes/package.json b/packages/mjml-head-attributes/package.json index 1fc26d138..3a3847ddf 100644 --- a/packages/mjml-head-attributes/package.json +++ b/packages/mjml-head-attributes/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-attributes", "description": "mjml-head-attributes", - "version": "2.0.7", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,6 +13,6 @@ }, "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.14.2" + "lodash": "^4.16.2" } } diff --git a/packages/mjml-head-font/package.json b/packages/mjml-head-font/package.json index ab15677aa..b8586ecf3 100644 --- a/packages/mjml-head-font/package.json +++ b/packages/mjml-head-font/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-font", "description": "mjml-head-font", - "version": "2.0.1", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,6 +13,6 @@ }, "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.15.0" + "lodash": "^4.16.2" } } diff --git a/packages/mjml-head-style/package.json b/packages/mjml-head-style/package.json index 0d312e6c4..6bc813f16 100644 --- a/packages/mjml-head-style/package.json +++ b/packages/mjml-head-style/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-style", "description": "mjml-head-style", - "version": "2.0.0", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml-head-title/package.json b/packages/mjml-head-title/package.json index 331e7e9c2..c964bd878 100644 --- a/packages/mjml-head-title/package.json +++ b/packages/mjml-head-title/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-title", "description": "mjml-head-title", - "version": "2.0.1", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml-hero/package.json b/packages/mjml-hero/package.json index 4b60a3661..10d513ff1 100644 --- a/packages/mjml-hero/package.json +++ b/packages/mjml-hero/package.json @@ -1,7 +1,7 @@ { "name": "mjml-hero", "description": "mjml-hero", - "version": "2.0.10", + "version": "3.0.0-beta.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-html/package.json b/packages/mjml-html/package.json index 71d6bdc20..4eff5b6fb 100644 --- a/packages/mjml-html/package.json +++ b/packages/mjml-html/package.json @@ -1,7 +1,7 @@ { "name": "mjml-html", "description": "mjml-html", - "version": "2.0.9", + "version": "3.0.0-beta.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-image/package.json b/packages/mjml-image/package.json index aca992f13..3e6811a4c 100644 --- a/packages/mjml-image/package.json +++ b/packages/mjml-image/package.json @@ -1,7 +1,7 @@ { "name": "mjml-image", "description": "mjml-image", - "version": "2.0.10", + "version": "3.0.0-beta.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-invoice/package.json b/packages/mjml-invoice/package.json index 88c017fbc..b6bd46059 100644 --- a/packages/mjml-invoice/package.json +++ b/packages/mjml-invoice/package.json @@ -1,7 +1,7 @@ { "name": "mjml-invoice", "description": "mjml-invoice", - "version": "2.0.11", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,10 +13,10 @@ }, "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.14.2", - "mjml-core": "~2.3.2", - "mjml-table": "~2.0.9", + "lodash": "^4.16.2", + "mjml-core": "~3.0.0-beta.1", + "mjml-table": "~3.0.0-beta.1", "numeral": "^1.5.3", - "react": "^15.3.0" + "react": "^15.3.2" } } diff --git a/packages/mjml-list/package.json b/packages/mjml-list/package.json index d6afe1b3b..a4920bc1b 100644 --- a/packages/mjml-list/package.json +++ b/packages/mjml-list/package.json @@ -1,7 +1,7 @@ { "name": "mjml-list", "description": "mjml-list", - "version": "2.0.9", + "version": "3.0.0-beta.1", "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.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-location/package.json b/packages/mjml-location/package.json index f421a1612..441616a0a 100644 --- a/packages/mjml-location/package.json +++ b/packages/mjml-location/package.json @@ -1,7 +1,7 @@ { "name": "mjml-location", "description": "mjml-location", - "version": "2.0.10", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,10 +13,10 @@ }, "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.14.2", - "mjml-core": "~2.3.2", - "mjml-image": "~2.0.9", - "mjml-text": "~2.0.9", - "react": "^15.3.0" + "lodash": "^4.16.2", + "mjml-core": "~3.0.0-beta.1", + "mjml-image": "~3.0.0-beta.1", + "mjml-text": "~3.0.0-beta.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-navbar/package.json b/packages/mjml-navbar/package.json index 7713c21d2..ee6719677 100644 --- a/packages/mjml-navbar/package.json +++ b/packages/mjml-navbar/package.json @@ -1,7 +1,7 @@ { "name": "mjml-navbar", "description": "mjml-navbar", - "version": "2.0.8", + "version": "3.0.0-beta.1", "main": "lib/index.js", "repository": { "type": "git", @@ -13,9 +13,9 @@ }, "homepage": "https://mjml.io", "dependencies": { - "lodash": "^4.14.2", - "mjml-core": "^2.3.2", - "mjml-section": "^2.0.10", - "react": "^15.3.0" + "lodash": "^4.16.2", + "mjml-core": "~3.0.0-beta.1", + "mjml-section": "~3.0.0-beta.1", + "react": "^15.3.2" } } diff --git a/packages/mjml-navbar/src/InlineLinks.js b/packages/mjml-navbar/src/InlineLinks.js index 3716e4718..a13304317 100644 --- a/packages/mjml-navbar/src/InlineLinks.js +++ b/packages/mjml-navbar/src/InlineLinks.js @@ -53,9 +53,9 @@ const postRender = $ => { .each(function () { $(this) .prepend(``) + `) .append(` + __content__ From aeeac793707df231362358bab34ec85ffab3d048 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Wed, 5 Oct 2016 16:08:14 +0200 Subject: [PATCH 75/87] default value for padding section/button like mjml2 (oops) --- packages/mjml-button/src/index.js | 4 ++-- packages/mjml-section/src/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mjml-button/src/index.js b/packages/mjml-button/src/index.js index 1fe9ccf1c..fc64481d4 100644 --- a/packages/mjml-button/src/index.js +++ b/packages/mjml-button/src/index.js @@ -26,8 +26,8 @@ const defaultMJMLDefinition = { "align": "center", "vertical-align": "middle", "href": null, - "inner-padding": "15px 25px", - "padding": "15px 25px", + "inner-padding": "10px 25px", + 'padding': '10px 25px', "padding-top": null, "padding-bottom": null, "padding-left": null, diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 903c7dc77..92d4ead17 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -18,7 +18,7 @@ const defaultMJMLDefinition = { 'border-right': null, 'border-top': null, 'full-width': null, - 'padding': '10px 25px', + 'padding': '20px 0', 'padding-top': null, 'padding-bottom': null, 'padding-left': null, From a192309236957861492f0888611386184adff2da Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 6 Oct 2016 10:07:54 +0200 Subject: [PATCH 76/87] =?UTF-8?q?Missing=20attrs,=20reverse=20column=20ord?= =?UTF-8?q?er=20in=20mobile/desktop=20=F0=9F=98=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mjml-column/src/index.js | 1 + packages/mjml-hero/src/Hero.js | 1 + packages/mjml-section/src/index.js | 2 ++ packages/mjml-social/src/index.js | 1 + packages/mjml-text/src/index.js | 2 ++ packages/mjml/test.js | 8 +++++++- 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 8d39fefb1..65ace271e 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -68,6 +68,7 @@ class Column extends Component { return merge({}, baseStyles, { div: { display: 'inline-block', + direction: 'ltr', fontSize: '13px', textAlign: 'left', width: this.getMobileWidth() diff --git a/packages/mjml-hero/src/Hero.js b/packages/mjml-hero/src/Hero.js index 345ecbc3e..bb3ebf84e 100644 --- a/packages/mjml-hero/src/Hero.js +++ b/packages/mjml-hero/src/Hero.js @@ -8,6 +8,7 @@ const defaultMJMLDefinition = { attributes: { 'mode': 'fixed-height', 'height': '0px', + 'background-url': null, 'background-width': '0px', 'background-height': '0px', 'background-position': 'center center', diff --git a/packages/mjml-section/src/index.js b/packages/mjml-section/src/index.js index 92d4ead17..5d52c1be6 100644 --- a/packages/mjml-section/src/index.js +++ b/packages/mjml-section/src/index.js @@ -17,6 +17,7 @@ const defaultMJMLDefinition = { 'border-radius': null, 'border-right': null, 'border-top': null, + 'direction': 'ltr', 'full-width': null, 'padding': '20px 0', 'padding-top': null, @@ -123,6 +124,7 @@ class Section extends Component { borderRadius: defaultUnit(mjAttribute('border-radius'), "px"), borderRight: mjAttribute('border-right'), borderTop: mjAttribute('border-top'), + direction: mjAttribute('direction'), fontSize: '0px', padding: defaultUnit(mjAttribute('padding'), 'px'), paddingBottom: defaultUnit(mjAttribute('padding-bottom'), 'px'), diff --git a/packages/mjml-social/src/index.js b/packages/mjml-social/src/index.js index 56b07ccac..c02d93f8e 100644 --- a/packages/mjml-social/src/index.js +++ b/packages/mjml-social/src/index.js @@ -23,6 +23,7 @@ const defaultMJMLDefinition = { 'google-href': '[[SHORT_PERMALINK]]', 'google-icon-color': '#dc4e41', 'icon-size': '20px', + 'inner-padding': null, 'instagram-content': 'Share', 'instagram-href': '[[SHORT_PERMALINK]]', 'instagram-icon-color': '#3f729b', diff --git a/packages/mjml-text/src/index.js b/packages/mjml-text/src/index.js index 7025ad2e6..413c4776a 100644 --- a/packages/mjml-text/src/index.js +++ b/packages/mjml-text/src/index.js @@ -21,6 +21,8 @@ const defaultMJMLDefinition = { 'padding-right': null, 'padding-top': null, 'padding': '10px 25px', + 'text-decoration': null, + 'text-transform': null, 'vertical-align': null } } diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 08e5b134d..2b57b908d 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -322,7 +322,13 @@ const inputMJML = ` ` try { - const azeaze = console.log(mjml.mjml2html(inputMJML, { beautify: true, level: "soft" })) + const { html, errors } = mjml.mjml2html(inputMJML, { beautify: false, level: "soft" }) + + if (errors) { + console.log(errors.map(e => e.formattedMessage).join('\n')) + } + + console.log(html) } catch(e) { if (e.getMessages) { console.log(e.getMessages()) From 7e5cc0fcbc35a51995fa0545b2bbe1cb8f26bd80 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 6 Oct 2016 11:13:37 +0200 Subject: [PATCH 77/87] vertical align removed by mistake + safe width --- packages/mjml-column/src/index.js | 1 + packages/mjml-image/src/index.js | 2 +- packages/mjml/test.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/mjml-column/src/index.js b/packages/mjml-column/src/index.js index 65ace271e..4cc9c4cd1 100644 --- a/packages/mjml-column/src/index.js +++ b/packages/mjml-column/src/index.js @@ -71,6 +71,7 @@ class Column extends Component { direction: 'ltr', fontSize: '13px', textAlign: 'left', + verticalAlign: mjAttribute('vertical-align'), width: this.getMobileWidth() }, table: { diff --git a/packages/mjml-image/src/index.js b/packages/mjml-image/src/index.js index 1178faceb..d212e4a4a 100644 --- a/packages/mjml-image/src/index.js +++ b/packages/mjml-image/src/index.js @@ -52,7 +52,7 @@ class Image extends Component { const { mjAttribute, getPadding } = this.props const parentWidth = mjAttribute('parentWidth') - const width = min([parseInt(mjAttribute('width')), parseInt(parentWidth)]) + const width = mjAttribute('width') ? min([parseInt(mjAttribute('width')), parseInt(parentWidth)]) : parseInt(parentWidth) const paddingRight = getPadding('right') const paddingLeft = getPadding('left') diff --git a/packages/mjml/test.js b/packages/mjml/test.js index 2b57b908d..e3d5a166e 100644 --- a/packages/mjml/test.js +++ b/packages/mjml/test.js @@ -322,7 +322,7 @@ const inputMJML = ` ` try { - const { html, errors } = mjml.mjml2html(inputMJML, { beautify: false, level: "soft" }) + const { html, errors } = mjml.mjml2html(inputMJML, { beautify: true, level: "soft" }) if (errors) { console.log(errors.map(e => e.formattedMessage).join('\n')) From 9d2d2c6f052357bc6b991b935b1a240cd8484f49 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 6 Oct 2016 16:00:50 +0200 Subject: [PATCH 78/87] should fix invoice --- packages/mjml-invoice/src/Invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mjml-invoice/src/Invoice.js b/packages/mjml-invoice/src/Invoice.js index 3cff8520b..2a0a7ea7a 100644 --- a/packages/mjml-invoice/src/Invoice.js +++ b/packages/mjml-invoice/src/Invoice.js @@ -135,7 +135,7 @@ class Invoice extends Component { - {children} + {React.Children.map(children, child => React.cloneElement(child, { columnElement: true }))} From ea4d53d45483e3fc1792d91552045bc64e3450d4 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Thu, 6 Oct 2016 16:05:29 +0200 Subject: [PATCH 79/87] missing attributes --- packages/mjml-invoice/src/Invoice.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mjml-invoice/src/Invoice.js b/packages/mjml-invoice/src/Invoice.js index 2a0a7ea7a..b2653ffa2 100644 --- a/packages/mjml-invoice/src/Invoice.js +++ b/packages/mjml-invoice/src/Invoice.js @@ -11,6 +11,7 @@ const defaultMJMLDefinition = { attributes: { 'border': '1px solid #ecedee', 'color': '#b9b9b9', + 'container-background-color': null, 'font-family': 'Roboto, Ubuntu, Helvetica, Arial, sans-serif', 'font-size': '13px', 'format': null, From f3828a585ec66c79c2281d5f3d3cced949bdc422 Mon Sep 17 00:00:00 2001 From: Nicolas Garnier Date: Thu, 6 Oct 2016 17:53:29 +0200 Subject: [PATCH 80/87] Documenting direction --- packages/mjml-section/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/mjml-section/README.md b/packages/mjml-section/README.md index 45b5cbfff..df18adb0b 100644 --- a/packages/mjml-section/README.md +++ b/packages/mjml-section/README.md @@ -25,6 +25,10 @@ The `full-width` property will be used to manage the background width. By default, it will be 600px. With the `full-width` property on, it will be changed to 100%. + + attribute | unit | description | default value --------------------|-------------|--------------------------------|--------------- full-width | string | make the section full-width | n/a @@ -45,3 +49,4 @@ padding-top | px | section top offset | n/a padding-bottom | px | section bottom offset | n/a padding-left | px | section left offset | n/a padding-right | px | section right offset | n/a +direction | string | ltr / rtl | ltr From 5a0ae7a41ad1f3a919deb2480051e5cac5b7dc89 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles Date: Fri, 7 Oct 2016 11:22:58 +0200 Subject: [PATCH 81/87] export validator --- packages/mjml-core/src/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/mjml-core/src/index.js b/packages/mjml-core/src/index.js index c838b3639..ce16028da 100644 --- a/packages/mjml-core/src/index.js +++ b/packages/mjml-core/src/index.js @@ -1,4 +1,5 @@ import MJMLRenderer from './MJMLRenderer' +import mjmlValidator from 'mjml-validator' import elements, { registerMJElement } from './MJMLElementsCollection' import MJMLHeadElements, { registerMJHeadElement } from './MJMLHead' import * as helpers from './helpers' @@ -11,4 +12,5 @@ export const documentParser = content => { return documentParser(content) } export const version = () => '__MJML_VERSION__' +export const MJMLValidator = mjmlValidator export const mjml2html = (mjml, options = {}) => new MJMLRenderer(mjml, options).render() From 1ea40afc2814a9c0e56d685875848f09579c5e59 Mon Sep 17 00:00:00 2001 From: Nicolas Garnier Date: Fri, 7 Oct 2016 12:21:28 +0200 Subject: [PATCH 82/87] Update doc + link --- packages/mjml-head-style/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mjml-head-style/README.md b/packages/mjml-head-style/README.md index 3c6ed727b..22f346596 100644 --- a/packages/mjml-head-style/README.md +++ b/packages/mjml-head-style/README.md @@ -1,6 +1,6 @@ ## mjml-style -This tag allow you to specify CSS style for your MJML document. Style will be inlined on the final HTML document +This tag allows you to use CSS styles for the HTML in your MJML document. This CSS style will be inlined on the final HTML document. ```xml @@ -26,7 +26,7 @@ This tag allow you to specify CSS style for your MJML document. Style will be in ```

- + sexy

From 23a7c3ed394572bbddee497b1d588620ac9fc586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Fri, 7 Oct 2016 15:13:00 +0200 Subject: [PATCH 83/87] Prepare 3.0.0-beta.3 --- packages/mjml-button/package.json | 4 +- packages/mjml-cli/package.json | 4 +- packages/mjml-column/package.json | 4 +- packages/mjml-container/package.json | 4 +- packages/mjml-core/package.json | 4 +- packages/mjml-divider/package.json | 4 +- packages/mjml-group/package.json | 4 +- packages/mjml-head-attributes/package.json | 2 +- packages/mjml-head-font/package.json | 2 +- packages/mjml-head-style/package.json | 2 +- packages/mjml-head-title/package.json | 2 +- packages/mjml-hero/package.json | 4 +- packages/mjml-html/package.json | 4 +- packages/mjml-image/package.json | 4 +- packages/mjml-invoice/package.json | 6 +-- packages/mjml-list/package.json | 4 +- packages/mjml-location/package.json | 8 ++-- packages/mjml-navbar/package.json | 6 +-- packages/mjml-raw/package.json | 4 +- packages/mjml-section/package.json | 4 +- packages/mjml-social/package.json | 4 +- packages/mjml-spacer/package.json | 4 +- packages/mjml-table/package.json | 4 +- packages/mjml-text/package.json | 4 +- packages/mjml-validator/package.json | 2 +- packages/mjml/package.json | 50 +++++++++++----------- 26 files changed, 74 insertions(+), 74 deletions(-) diff --git a/packages/mjml-button/package.json b/packages/mjml-button/package.json index 5a4b4a6a0..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": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-cli/package.json b/packages/mjml-cli/package.json index 5b5de02f5..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": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "bin/mjml", "bin": { "mjml-cli": "bin/mjml" @@ -20,7 +20,7 @@ "fs-promise": "^0.5.0", "glob": "^7.1.0", "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2" + "mjml-core": "~3.0.0-beta.3" }, "devDependencies": { "chai": "^3.5.0", diff --git a/packages/mjml-column/package.json b/packages/mjml-column/package.json index 82684bb77..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": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -15,7 +15,7 @@ "dependencies": { "classnames": "^2.2.5", "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-container/package.json b/packages/mjml-container/package.json index 65c4bea1a..3368f1088 100644 --- a/packages/mjml-container/package.json +++ b/packages/mjml-container/package.json @@ -1,7 +1,7 @@ { "name": "mjml-container", "description": "mjml-container", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-core/package.json b/packages/mjml-core/package.json index 98e6b9d7e..a0bd6f899 100644 --- a/packages/mjml-core/package.json +++ b/packages/mjml-core/package.json @@ -1,7 +1,7 @@ { "name": "mjml-core", "description": "mjml-core", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "scripts": { "test": "mocha --compilers js:babel-register" @@ -32,7 +32,7 @@ "js-beautify": "^1.6.4", "juice": "^3.0.0", "lodash": "^4.16.2", - "mjml-validator": "~3.0.0-beta.2", + "mjml-validator": "~3.0.0-beta.3", "react-dom": "^15.3.2", "react": "^15.3.2", "warning": "^3.0.0" diff --git a/packages/mjml-divider/package.json b/packages/mjml-divider/package.json index 08abeb949..5c3d78f4b 100644 --- a/packages/mjml-divider/package.json +++ b/packages/mjml-divider/package.json @@ -1,7 +1,7 @@ { "name": "mjml-divider", "description": "mjml-divider", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-group/package.json b/packages/mjml-group/package.json index 27f24a6a3..e7006d8c6 100644 --- a/packages/mjml-group/package.json +++ b/packages/mjml-group/package.json @@ -1,7 +1,7 @@ { "name": "mjml-group", "description": "mjml-group", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://github.com/mjmlio/mjml", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-head-attributes/package.json b/packages/mjml-head-attributes/package.json index 5d5416f5a..a3f1d22db 100644 --- a/packages/mjml-head-attributes/package.json +++ b/packages/mjml-head-attributes/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-attributes", "description": "mjml-head-attributes", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml-head-font/package.json b/packages/mjml-head-font/package.json index dc0ad8b6d..a126a6c99 100644 --- a/packages/mjml-head-font/package.json +++ b/packages/mjml-head-font/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-font", "description": "mjml-head-font", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml-head-style/package.json b/packages/mjml-head-style/package.json index f2a36d4c8..6ab6c0866 100644 --- a/packages/mjml-head-style/package.json +++ b/packages/mjml-head-style/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-style", "description": "mjml-head-style", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml-head-title/package.json b/packages/mjml-head-title/package.json index 5e19da398..177dd6983 100644 --- a/packages/mjml-head-title/package.json +++ b/packages/mjml-head-title/package.json @@ -1,7 +1,7 @@ { "name": "mjml-head-title", "description": "mjml-head-title", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml-hero/package.json b/packages/mjml-hero/package.json index 38d401a88..ea1bebce0 100644 --- a/packages/mjml-hero/package.json +++ b/packages/mjml-hero/package.json @@ -1,7 +1,7 @@ { "name": "mjml-hero", "description": "mjml-hero", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-html/package.json b/packages/mjml-html/package.json index a3da405dd..e0eadf12e 100644 --- a/packages/mjml-html/package.json +++ b/packages/mjml-html/package.json @@ -1,7 +1,7 @@ { "name": "mjml-html", "description": "mjml-html", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-image/package.json b/packages/mjml-image/package.json index ccfd3aa3b..01560a246 100644 --- a/packages/mjml-image/package.json +++ b/packages/mjml-image/package.json @@ -1,7 +1,7 @@ { "name": "mjml-image", "description": "mjml-image", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-invoice/package.json b/packages/mjml-invoice/package.json index 219bf0b8b..1323f8af5 100644 --- a/packages/mjml-invoice/package.json +++ b/packages/mjml-invoice/package.json @@ -1,7 +1,7 @@ { "name": "mjml-invoice", "description": "mjml-invoice", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,8 +14,8 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", - "mjml-table": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", + "mjml-table": "~3.0.0-beta.3", "numeral": "^1.5.3", "react": "^15.3.2" } diff --git a/packages/mjml-list/package.json b/packages/mjml-list/package.json index 81e708de9..a7577c671 100644 --- a/packages/mjml-list/package.json +++ b/packages/mjml-list/package.json @@ -1,7 +1,7 @@ { "name": "mjml-list", "description": "mjml-list", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-location/package.json b/packages/mjml-location/package.json index d5a990a99..4cb1864fd 100644 --- a/packages/mjml-location/package.json +++ b/packages/mjml-location/package.json @@ -1,7 +1,7 @@ { "name": "mjml-location", "description": "mjml-location", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,9 +14,9 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", - "mjml-image": "~3.0.0-beta.2", - "mjml-text": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", + "mjml-image": "~3.0.0-beta.3", + "mjml-text": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-navbar/package.json b/packages/mjml-navbar/package.json index 7a5767b96..5c10a11da 100644 --- a/packages/mjml-navbar/package.json +++ b/packages/mjml-navbar/package.json @@ -1,7 +1,7 @@ { "name": "mjml-navbar", "description": "mjml-navbar", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,8 +14,8 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", - "mjml-section": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", + "mjml-section": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-raw/package.json b/packages/mjml-raw/package.json index d8c9ed10e..e6e392825 100644 --- a/packages/mjml-raw/package.json +++ b/packages/mjml-raw/package.json @@ -1,7 +1,7 @@ { "name": "mjml-raw", "description": "mjml-raw", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-section/package.json b/packages/mjml-section/package.json index 9b7adc28e..ec9393a2f 100644 --- a/packages/mjml-section/package.json +++ b/packages/mjml-section/package.json @@ -1,7 +1,7 @@ { "name": "mjml-section", "description": "mjml-section", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-social/package.json b/packages/mjml-social/package.json index da22d1d64..a90122655 100644 --- a/packages/mjml-social/package.json +++ b/packages/mjml-social/package.json @@ -1,7 +1,7 @@ { "name": "mjml-social", "description": "mjml-social", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-spacer/package.json b/packages/mjml-spacer/package.json index a36afc5ab..3fbd89895 100644 --- a/packages/mjml-spacer/package.json +++ b/packages/mjml-spacer/package.json @@ -1,7 +1,7 @@ { "name": "mjml-spacer", "description": "mjml-spacer", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-table/package.json b/packages/mjml-table/package.json index 22ec07d3c..5e49eb1a8 100644 --- a/packages/mjml-table/package.json +++ b/packages/mjml-table/package.json @@ -1,7 +1,7 @@ { "name": "mjml-table", "description": "mjml-table", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -13,7 +13,7 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-text/package.json b/packages/mjml-text/package.json index 2575c9073..702b89690 100644 --- a/packages/mjml-text/package.json +++ b/packages/mjml-text/package.json @@ -1,7 +1,7 @@ { "name": "mjml-text", "description": "mjml-text", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", @@ -14,7 +14,7 @@ "homepage": "https://mjml.io", "dependencies": { "lodash": "^4.16.2", - "mjml-core": "~3.0.0-beta.2", + "mjml-core": "~3.0.0-beta.3", "react": "^15.3.2" } } diff --git a/packages/mjml-validator/package.json b/packages/mjml-validator/package.json index 51af13ff8..fb0a846e9 100644 --- a/packages/mjml-validator/package.json +++ b/packages/mjml-validator/package.json @@ -1,7 +1,7 @@ { "name": "mjml-validator", "description": "mjml-validator", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/mjml/package.json b/packages/mjml/package.json index 0f4c41b60..a6dc3d44a 100644 --- a/packages/mjml/package.json +++ b/packages/mjml/package.json @@ -1,7 +1,7 @@ { "name": "mjml", "description": "MJML: the only framework that makes responsive-email easy", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "main": "lib/index.js", "bin": { "mjml": "bin/mjml" @@ -16,29 +16,29 @@ }, "homepage": "https://mjml.io", "dependencies": { - "mjml-button": "~3.0.0-beta.2", - "mjml-cli": "~3.0.0-beta.2", - "mjml-column": "~3.0.0-beta.2", - "mjml-container": "~3.0.0-beta.2", - "mjml-core": "~3.0.0-beta.2", - "mjml-divider": "~3.0.0-beta.2", - "mjml-group": "~3.0.0-beta.2", - "mjml-head-attributes": "~3.0.0-beta.2", - "mjml-head-font": "~3.0.0-beta.2", - "mjml-head-title": "~3.0.0-beta.2", - "mjml-head-style": "~3.0.0-beta.2", - "mjml-hero": "~3.0.0-beta.2", - "mjml-html": "~3.0.0-beta.2", - "mjml-image": "~3.0.0-beta.2", - "mjml-invoice": "~3.0.0-beta.2", - "mjml-list": "~3.0.0-beta.2", - "mjml-location": "~3.0.0-beta.2", - "mjml-navbar": "~3.0.0-beta.2", - "mjml-raw": "~3.0.0-beta.2", - "mjml-section": "~3.0.0-beta.2", - "mjml-social": "~3.0.0-beta.2", - "mjml-spacer": "~3.0.0-beta.2", - "mjml-table": "~3.0.0-beta.2", - "mjml-text": "~3.0.0-beta.2" + "mjml-button": "~3.0.0-beta.3", + "mjml-cli": "~3.0.0-beta.3", + "mjml-column": "~3.0.0-beta.3", + "mjml-container": "~3.0.0-beta.3", + "mjml-core": "~3.0.0-beta.3", + "mjml-divider": "~3.0.0-beta.3", + "mjml-group": "~3.0.0-beta.3", + "mjml-head-attributes": "~3.0.0-beta.3", + "mjml-head-font": "~3.0.0-beta.3", + "mjml-head-title": "~3.0.0-beta.3", + "mjml-head-style": "~3.0.0-beta.3", + "mjml-hero": "~3.0.0-beta.3", + "mjml-html": "~3.0.0-beta.3", + "mjml-image": "~3.0.0-beta.3", + "mjml-invoice": "~3.0.0-beta.3", + "mjml-list": "~3.0.0-beta.3", + "mjml-location": "~3.0.0-beta.3", + "mjml-navbar": "~3.0.0-beta.3", + "mjml-raw": "~3.0.0-beta.3", + "mjml-section": "~3.0.0-beta.3", + "mjml-social": "~3.0.0-beta.3", + "mjml-spacer": "~3.0.0-beta.3", + "mjml-table": "~3.0.0-beta.3", + "mjml-text": "~3.0.0-beta.3" } } From 2d354e207faab68ff14f34cf86cf4db47467c251 Mon Sep 17 00:00:00 2001 From: Nicolas Garnier Date: Wed, 12 Oct 2016 17:08:48 +0200 Subject: [PATCH 84/87] Update create.md --- doc/create.md | 196 +------------------------------------------------- 1 file changed, 2 insertions(+), 194 deletions(-) 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(`<!--[if mso | IE]> - <table border="0" cellpadding="0" cellspacing="0" width="600" align="center" style="width:600}px;"><tr><td> - <![endif]-->`) - $('.title').append(`<!--[if mso | IE]> - </td></tr></table> - <![endif]-->`) - - 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: <mj-title color="blue">content</mj-title> - */ - 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 ( - <MjText style={ css }> - { content } - </MjText> - ) - } -} - -``` - -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(` - <mjml> - <mj-body> - <mj-title>Hello world!</mj-title> - </mj-body> - </mjml> -``` - -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. From 8ae319aa53b33c4637497c529c8c0d3c4b896d43 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles <maxime.brazeilles@gmail.com> Date: Fri, 14 Oct 2016 17:26:44 +0200 Subject: [PATCH 85/87] disable packages for browser ( lazy require ), add missing trad + validate in cli + remove component in mjml cli --- packages/mjml-cli/bin/mjml | 13 ++--- packages/mjml-cli/src/client.js | 44 ++++++++------ packages/mjml-cli/src/createComponent.js | 73 ------------------------ packages/mjml-core/src/MJMLRenderer.js | 26 ++++++--- packages/mjml-hero/src/Hero.js | 4 ++ 5 files changed, 55 insertions(+), 105 deletions(-) delete mode 100644 packages/mjml-cli/src/createComponent.js diff --git a/packages/mjml-cli/bin/mjml b/packages/mjml-cli/bin/mjml index 8f90fbab6..74966645a 100755 --- a/packages/mjml-cli/bin/mjml +++ b/packages/mjml-cli/bin/mjml @@ -25,12 +25,15 @@ binary .option('-o, --output <file>', '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 <name>', 'Initialize an MJML component') + .option('-f, --format <format>', 'Output format for MJML validation', /^(json|text)$/i, 'text') + .option('--validate <file>', '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 @@ -42,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/src/client.js b/packages/mjml-cli/src/client.js index f4922d267..0be95eaf0 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 @@ -56,7 +53,6 @@ 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) @@ -130,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} Cannot read ${input}`) + }) +} + /* * 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: <mj-${lowerName} color="blue">content</mj-${lowerName}> - */ - color: mjAttribute('color') - } - }) - } - - render () { - const css = this.getStyles() - const content = 'Hello World!' - - return ( - <MJMLText style={css}> - {content} - </MJMLText> - ) - } - -} - -${name}.tagName = tagName -${name}.defaultMJMLDefinition = defaultMJMLDefinition -${name}.endingTag = endingTag -${name}.baseStyles = baseStyles - -export default ${name} - -` -} diff --git a/packages/mjml-core/src/MJMLRenderer.js b/packages/mjml-core/src/MJMLRenderer.js index 8c143aea5..7dbcd9eac 100644 --- a/packages/mjml-core/src/MJMLRenderer.js +++ b/packages/mjml-core/src/MJMLRenderer.js @@ -5,15 +5,14 @@ import cloneDeep from 'lodash/cloneDeep' import configParser from './parsers/config' import curryRight from 'lodash/curryRight' import documentParser from './parsers/document' +import defaults from 'lodash/defaults' import defaultContainer from './configs/defaultContainer' import defaultFonts from './configs/listFontsImports' import dom from './helpers/dom' import he from 'he' import importFonts from './helpers/importFonts' import includeExternal from './includeExternal' -import juice from 'juice' import { html as beautify } from 'js-beautify' -import { minify } from 'html-minifier' import MJMLValidator from 'mjml-validator' import MJMLElementsCollection, { postRenders } from './MJMLElementsCollection' import isBrowser from './helpers/isBrowser' @@ -22,9 +21,17 @@ import ReactDOMServer from 'react-dom/server' const debug = require('debug')('mjml-engine/mjml2html') -const minifyHTML = htmlDocument => minify(htmlDocument, { collapseWhitespace: true, removeEmptyAttributes: true, minifyCSS: true }) +const minifyHTML = htmlDocument => { + const { minify } = require('html-minifier') + + return minify(htmlDocument, { collapseWhitespace: true, removeEmptyAttributes: true, minifyCSS: true }) +} const beautifyHTML = htmlDocument => beautify(htmlDocument, { indent_size: 2, wrap_attributes_indent_size: 2 }) -const inlineExternal = (htmlDocument, css) => juice(htmlDocument, { extraCss: css, removeStyleTags: false, applyStyleTags: false, insertPreservedExtraCss: false }) +const inlineExternal = (htmlDocument, css) => { + const juice = require('juice') + + return juice(htmlDocument, { extraCss: css, removeStyleTags: false, applyStyleTags: false, insertPreservedExtraCss: false }) +} export default class MJMLRenderer { @@ -42,8 +49,7 @@ export default class MJMLRenderer { } this.content = content - this.options = options - this.options["level"] = this.options["level"] || "soft" + this.options = defaults(options, { level: "soft", disableMjStyle: false, disableMjInclude: false, disableMinify: false }) if (typeof this.content === 'string') { this.parseDocument() @@ -51,7 +57,9 @@ export default class MJMLRenderer { } parseDocument () { - this.content = includeExternal(this.content) + if (!this.options.disableMjInclude) { + this.content = includeExternal(this.content) + } debug('Start parsing document') this.content = documentParser(this.content, this.attributes, this.options) @@ -109,9 +117,9 @@ export default class MJMLRenderer { }) return [ removeCDATA, - curryRight(inlineExternal)(externalCSS), + !this.options.disableMjStyle ? curryRight(inlineExternal)(externalCSS) : undefined, this.options.beautify ? beautifyHTML : undefined, - this.options.minify ? minifyHTML : undefined, + !this.options.disableMinify && this.options.minify ? minifyHTML : undefined, he.decode ].filter(element => typeof element == 'function') .reduce((res, fun) => fun(res), dom.getHTML($)) } diff --git a/packages/mjml-hero/src/Hero.js b/packages/mjml-hero/src/Hero.js index bb3ebf84e..7b5a85b3a 100644 --- a/packages/mjml-hero/src/Hero.js +++ b/packages/mjml-hero/src/Hero.js @@ -13,6 +13,10 @@ const defaultMJMLDefinition = { 'background-height': '0px', 'background-position': 'center center', 'padding': '0px', + 'padding-bottom': null, + 'padding-left': null, + 'padding-right': null, + 'padding-top': null, 'background-color': '#ffffff' } } From a84a72c4d113d0f79da91b188240ec3325fdf914 Mon Sep 17 00:00:00 2001 From: Maxime Brazeilles <maxime.brazeilles@gmail.com> Date: Mon, 17 Oct 2016 11:44:18 +0200 Subject: [PATCH 86/87] better catch --- packages/mjml-cli/src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mjml-cli/src/client.js b/packages/mjml-cli/src/client.js index 0be95eaf0..60a281074 100644 --- a/packages/mjml-cli/src/client.js +++ b/packages/mjml-cli/src/client.js @@ -145,7 +145,7 @@ export const validate = (input, { format }) => { process.stdout.write(outputFormat(report)) }) .catch(e => { - return error(`Error: ${e} Cannot read ${input}`) + return error(`Error: ${e}`) }) } From 7a64863e8f8a8132b0642c3a3a68038f2dc63591 Mon Sep 17 00:00:00 2001 From: Nicolas Garnier <ngarnier@users.noreply.github.com> Date: Mon, 17 Oct 2016 16:22:02 +0200 Subject: [PATCH 87/87] Update validChildren.js --- packages/mjml-validator/src/rules/validChildren.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mjml-validator/src/rules/validChildren.js b/packages/mjml-validator/src/rules/validChildren.js index 880a49360..ef2014513 100644 --- a/packages/mjml-validator/src/rules/validChildren.js +++ b/packages/mjml-validator/src/rules/validChildren.js @@ -27,6 +27,6 @@ export const validChildren = (element) => { return null; } - return ruleError(`${ChildComponent.tagName} cannot be used inside ${tagName}, only in: ${ChildComponent.parentTag.join(', ')}`, child) + return ruleError(`${ChildComponent.tagName} cannot be used inside ${tagName}, only inside: ${ChildComponent.parentTag.join(', ')}`, child) })) }