diff --git a/README.md b/README.md
index 5cee922..2f3a8a5 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,6 @@ Supported MJML components (using default mjml-browser parser):
|`block`|Add custom block options, based on block id.|`(blockId) => ({})`|
|`codeViewerTheme`|Code viewer theme.|`hopscotch`|
|`customComponents`|List of components which will be added to default one |`[]` |
-|`fonts`|Custom fonts on exported HTML header [more info](https://github.com/mjmlio/mjml#inside-nodejs)|`{}`|
|`importPlaceholder`|Placeholder MJML template for the import modal|`''`|
|`imagePlaceholderSrc`|Image placeholder source|`'https://via.placeholder.com/350x250/78c5d6/fff'`|
|`i18n`|I18n object containing language [more info](https://grapesjs.com/docs/modules/I18n.html#configuration)|`{}`|
@@ -141,46 +140,6 @@ grapesJS.init({
});
```
-#### fonts usage:
-
-```js
-import 'grapesjs/dist/css/grapes.min.css'
-import grapesJS from 'grapesjs'
-import grapesJSMJML from 'grapesjs-mjml'
-
-const editor = grapesJS.init({
- fromElement: true,
- container: '#gjs',
- plugins: [grapesJSMJML],
- pluginsOpts: {
- [grapesJSMJML]: {
- // The font imports are included on HTML
/, '');
const start = content.indexOf('') + 6;
const end = content.indexOf('');
@@ -70,7 +85,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
return {
attributes,
content: componentEl.innerHTML,
- style: styles.join(' ')
+ style: styles.join(' '),
};
},
@@ -86,6 +101,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
// In case mjmlResult.attributes removes necessary stuff
this.updateStatus();
+ this.postRender();
return this;
},
@@ -95,8 +111,8 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
const modelStyle = model.get('style') || {};
const stylable = model.get('stylable') as string[];
const styles = Object.keys(modelStyle)
- .filter(prop => stylable.indexOf(prop) > -1)
- .map(prop => `${prop}:${modelStyle[prop]};`);
+ .filter((prop) => stylable.indexOf(prop) > -1)
+ .map((prop) => `${prop}:${modelStyle[prop]};`);
const styleResult = `${attributes.style} ${styles.join(' ')} ${el.getAttribute('style')}`;
el.setAttribute('style', styleResult);
// #290 Fix double borders
diff --git a/src/components/Image.ts b/src/components/Image.ts
index 57ee274..5c89ffa 100644
--- a/src/components/Image.ts
+++ b/src/components/Image.ts
@@ -20,22 +20,46 @@ export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPlugin
name: getName(editor, 'image'),
draggable: componentsToQuery([typeSection, typeColumn, typeHero]),
stylable: [
- 'width', 'height',
- 'padding', 'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
- 'border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius',
- 'border', 'border-width', 'border-style', 'border-color',
- 'container-background-color', 'align',
+ 'width',
+ 'height',
+ 'padding',
+ 'padding-top',
+ 'padding-left',
+ 'padding-right',
+ 'padding-bottom',
+ 'border-radius',
+ 'border-top-left-radius',
+ 'border-top-right-radius',
+ 'border-bottom-left-radius',
+ 'border-bottom-right-radius',
+ 'border',
+ 'border-width',
+ 'border-style',
+ 'border-color',
+ 'container-background-color',
+ 'align',
],
'style-default': {
'padding-top': '10px',
'padding-bottom': '10px',
'padding-right': '25px',
'padding-left': '25px',
- 'align': 'center',
+ align: 'center',
},
traits: ['href', 'rel', 'alt', 'title'],
void: false,
},
+
+ getStylesToAttributes() {
+ const style = coreMjmlModel.getStylesToAttributes.call(this);
+
+ // Fix #339
+ if (style.width === 'auto') {
+ delete style.width;
+ }
+
+ return style;
+ },
},
view: {
diff --git a/src/components/NavBar.ts b/src/components/NavBar.ts
index c559901..b473ffa 100644
--- a/src/components/NavBar.ts
+++ b/src/components/NavBar.ts
@@ -31,8 +31,8 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
options: [
{ value: 'hamburger', name: 'ON' },
{ value: '', name: 'OFF' },
- ]
- }
+ ],
+ },
],
},
},
@@ -52,8 +52,12 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
getTemplateFromMjml() {
const mjmlTmpl = this.getMjmlTemplate();
const innerMjml = this.getInnerMjmlTemplate();
- const htmlOutput = mjmlConvert(opt.mjmlParser, `${mjmlTmpl.start}
- ${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`, opt.fonts);
+ const htmlOutput = mjmlConvert(
+ opt.mjmlParser,
+ `${mjmlTmpl.start}
+ ${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`,
+ opt.fonts,
+ );
const html = htmlOutput.html;
// I need styles for hamburger
@@ -64,7 +68,6 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
styles.push(item.innerHTML);
});
-
const content = html.replace(//, '');
const start = content.indexOf('') + 6;
const end = content.indexOf('');
@@ -83,7 +86,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
return {
attributes,
content: componentEl.innerHTML,
- style: styles.join(' ')
+ style: styles.join(' '),
};
},
@@ -96,6 +99,8 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }:
this.getChildrenContainer().innerHTML = this.model.get('content')!;
this.renderChildren();
this.renderStyle();
+ this.postRender();
+
return this;
},
diff --git a/src/components/index.ts b/src/components/index.ts
index 74c1102..8362a62 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,4 +1,4 @@
-import type { Editor, PluginOptions } from 'grapesjs';
+import type { Editor } from 'grapesjs';
import { mjmlConvert, debounce, componentsToQuery } from './utils';
import loadMjml from './mjml';
import loadHead from './Head';
@@ -20,7 +20,7 @@ import loadNavBar from './NavBar';
import loadNavBarLink from './NavBarLink';
import loadHero from './Hero';
import loadRaw from './Raw';
-import { RequiredPluginOptions } from '..';
+import { RequiredPluginOptions, PluginOptions } from '..';
export type ComponentPluginOptions = {
/**
@@ -33,16 +33,15 @@ export type ComponentPluginOptions = {
coreMjmlView: any;
opt: Required;
sandboxEl: HTMLDivElement;
- componentsToQuery: typeof componentsToQuery,
-}
+ componentsToQuery: typeof componentsToQuery;
+};
export default (editor: Editor, opt: RequiredPluginOptions) => {
- const { Components } = editor;
+ const { Components } = editor;
// @ts-ignore
const ComponentsView = Components.ComponentsView;
const sandboxEl = document.createElement('div');
-
// MJML Core model
let coreMjmlModel = {
init() {
@@ -65,12 +64,15 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
this.setStyle(this.get('attributes'), opts);
},
- handleStyleChange(m: any, v: any, opts: any) {
- const style = this.getStyle();
+ getStylesToAttributes() {
+ const style = this.getStyle() || {};
delete style.__p;
- this.set('attributes', style, opts);
+ return style;
},
+ handleStyleChange(m: any, v: any, opts: any) {
+ this.set('attributes', this.getStylesToAttributes(), opts);
+ },
getMjmlAttributes() {
const attr = this.get('attributes') || {};
@@ -80,7 +82,6 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
return attr;
},
-
/**
* This will avoid rendering default attributes
* @return {Object}
@@ -102,7 +103,6 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
return attr;
},
-
/**
* Have to change a few things for the MJML's xml (no id, style, class)
*/
@@ -135,10 +135,9 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
isHidden() {
return this.getStyle().display === 'none';
- }
+ },
} as any;
-
/**
* MJML Core View.
* MJML is designed to compile from a valid MJML document therefore any time we update some component
@@ -168,7 +167,6 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
this.debouncedRender = debounce(this.render.bind(this), 0);
},
-
rerender() {
this.render(null, null, {}, 1);
},
@@ -194,8 +192,7 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
for (let prop in attr) {
const val = attr[prop];
- strAttr += typeof val !== 'undefined' && val !== '' ?
- ' ' + prop + '="' + val + '"' : '';
+ strAttr += typeof val !== 'undefined' && val !== '' ? ' ' + prop + '="' + val + '"' : '';
}
return {
@@ -228,7 +225,6 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
return this.getTemplateFromEl(sandboxEl);
},
-
/**
* Render children components
* @private
@@ -239,12 +235,14 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
// This trick will help perfs by caching children
if (!appendChildren) {
- this.childrenView = this.childrenView || new ComponentsView({
- collection: this.model.get('components'),
- // @ts-ignore
- config: this.config,
- componentTypes: this.opts.componentTypes,
- });
+ this.childrenView =
+ this.childrenView ||
+ new ComponentsView({
+ collection: this.model.get('components'),
+ // @ts-ignore
+ config: this.config,
+ componentTypes: this.opts.componentTypes,
+ });
this.childNodes = this.childrenView.render(container).el.childNodes;
} else {
this.childrenView.parentEl = container;
@@ -268,20 +266,20 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
this.checkVisibility();
},
-
render(p: any, c: any, opts: any, appendChildren: boolean) {
this.renderAttributes();
this.el.innerHTML = this.getTemplateFromMjml();
this.renderChildren(appendChildren);
this.childNodes = this.getChildrenContainer().childNodes;
this.renderStyle();
+ this.postRender();
return this;
- }
+ },
} as any;
// MJML Internal view (for elements inside mj-columns)
- const compOpts = { coreMjmlModel, coreMjmlView, opt, sandboxEl, componentsToQuery};
+ const compOpts = { coreMjmlModel, coreMjmlView, opt, sandboxEl, componentsToQuery };
// Avoid the tag from the default wrapper
editor.Components.addType('wrapper', {
@@ -291,8 +289,8 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
},
toHTML(opts: any) {
return this.getInnerHTML(opts)!;
- }
- }
+ },
+ },
});
[
@@ -317,6 +315,5 @@ export default (editor: Editor, opt: RequiredPluginOptions) => {
loadHero,
loadRaw,
...opt.customComponents,
- ]
- .forEach(module => module(editor, compOpts));
+ ].forEach((module) => module(editor, compOpts));
};
diff --git a/src/index.ts b/src/index.ts
index 8c17574..4667b95 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,144 +1,35 @@
-import type { Editor, Plugin } from 'grapesjs';
+import type { Plugin } from 'grapesjs';
import loadBlocks from './blocks';
-import loadComponents, { ComponentPluginOptions } from './components';
-import mjml2html, { MjmlParser } from './components/parser';
import loadCommands from './commands';
+import loadComponents from './components';
+import mjml2html from './components/parser';
+import en from './locale/en';
import loadPanels from './panels';
import loadStyle from './style';
-import en from './locale/en';
-
-export type PluginOptions = {
- /**
- * Which blocks to add.
- * @default (all)
- */
- blocks?: string[];
-
- /**
- * Add custom block options, based on block id.
- * @default (blockId) => ({})
- * @example (blockId) => (blockId === 'mj-hero' ? { attributes: {...} } : {})
- */
- block?: (blockId: string) => ({});
-
- /**
- * Code viewer theme.
- * @default 'hopscotch'
- */
- codeViewerTheme?: string;
-
- /**
- * Add custom MJML components
- *
- * @default []
- */
- customComponents?: ((editor: Editor, componentOptions: ComponentPluginOptions) => void)[],
-
- /**
- * Placeholder MJML template for the import modal
- * @default ''
- */
- importPlaceholder?: string;
-
- /**
- * Image placeholder source for mj-image block
- * @default ''
- */
- imagePlaceholderSrc?: string;
-
- /**
- * Custom MJML parser.
- * @default mjml-browser instance
- */
- mjmlParser?: MjmlParser;
-
- /**
- * Overwrite default export command
- * @default true
- */
- overwriteExport?: boolean;
-
- /**
- * String before the MJML in export code
- * @default ''
- */
- preMjml?: string;
+import { PluginOptions } from './types';
- /**
- * String after the MJML in export code
- * @default ''
- */
- postMjml?: string;
-
- /**
- * Clean all previous blocks if true
- * @default true
- */
- resetBlocks?: boolean;
-
- /**
- * Reset the Style Manager and add new properties for MJML
- * @default true
- */
- resetStyleManager?: boolean;
-
- /**
- * Clean all previous devices and set a new one for mobile
- * @default true
- */
- resetDevices?: boolean;
-
- /**
- * Hide the default selector manager
- * @default true
- */
- hideSelector?: boolean;
-
- /**
- * Experimental: use XML parser instead of HTML.
- * This should allow importing void MJML elements (without closing tags) like .
- * @default false
- * @experimental
- */
- useXmlParser?: boolean;
-
- /**
- * Column padding (this way it's easier to select columns)
- * @default '10px 0'
- */
- columnsPadding?: string;
-
- /**
- * I18n object containing languages, [more info](https://grapesjs.com/docs/modules/I18n.html#configuration).
- * @default {}
- */
- i18n?: Record;
-
- /**
- * Custom fonts on exported HTML header, [more info](https://github.com/mjmlio/mjml#inside-nodejs).
- * @default {}
- * @example
- * {
- * Montserrat: 'https://fonts.googleapis.com/css?family=Montserrat',
- * 'Open Sans': 'https://fonts.googleapis.com/css?family=Open+Sans'
- * }
- */
- fonts?: Record;
-
- /**
- * Load custom preset theme.
- * @default true
- */
- useCustomTheme?: boolean;
-};
+export * from './types';
export type RequiredPluginOptions = Required;
const plugin: Plugin = (editor, opt = {}) => {
const opts: RequiredPluginOptions = {
blocks: [
- 'mj-1-column', 'mj-2-columns', 'mj-3-columns', 'mj-text', 'mj-button', 'mj-image', 'mj-divider', 'mj-social-group',
- 'mj-social-element', 'mj-spacer', 'mj-navbar', 'mj-navbar-link', 'mj-hero', 'mj-wrapper', 'mj-raw'
+ 'mj-1-column',
+ 'mj-2-columns',
+ 'mj-3-columns',
+ 'mj-text',
+ 'mj-button',
+ 'mj-image',
+ 'mj-divider',
+ 'mj-social-group',
+ 'mj-social-element',
+ 'mj-spacer',
+ 'mj-navbar',
+ 'mj-navbar-link',
+ 'mj-hero',
+ 'mj-wrapper',
+ 'mj-raw',
],
block: () => ({}),
codeViewerTheme: 'hopscotch',
@@ -226,13 +117,7 @@ const plugin: Plugin = (editor, opt = {}) => {
...opts.i18n,
});
- [
- loadBlocks,
- loadComponents,
- loadCommands,
- loadPanels,
- loadStyle,
- ].forEach(module => module(editor, opts));
+ [loadBlocks, loadComponents, loadCommands, loadPanels, loadStyle].forEach((module) => module(editor, opts));
};
export default plugin;
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..599637b
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,133 @@
+import type { Editor } from 'grapesjs';
+import type { MJMLParsingOptions } from 'mjml-core';
+import { ComponentPluginOptions } from './components';
+import { MjmlParser } from './components/parser';
+
+export interface CommandOptionsMjmlToHtml extends MJMLParsingOptions {
+ mjml?: string;
+}
+
+export type PluginOptions = {
+ /**
+ * Which blocks to add.
+ * @default (all)
+ */
+ blocks?: string[];
+
+ /**
+ * Add custom block options, based on block id.
+ * @default (blockId) => ({})
+ * @example (blockId) => (blockId === 'mj-hero' ? { attributes: {...} } : {})
+ */
+ block?: (blockId: string) => {};
+
+ /**
+ * Code viewer theme.
+ * @default 'hopscotch'
+ */
+ codeViewerTheme?: string;
+
+ /**
+ * Add custom MJML components
+ *
+ * @default []
+ */
+ customComponents?: ((editor: Editor, componentOptions: ComponentPluginOptions) => void)[];
+
+ /**
+ * Placeholder MJML template for the import modal
+ * @default ''
+ */
+ importPlaceholder?: string;
+
+ /**
+ * Image placeholder source for mj-image block
+ * @default ''
+ */
+ imagePlaceholderSrc?: string;
+
+ /**
+ * Custom MJML parser.
+ * @default mjml-browser instance
+ */
+ mjmlParser?: MjmlParser;
+
+ /**
+ * Overwrite default export command
+ * @default true
+ */
+ overwriteExport?: boolean;
+
+ /**
+ * String before the MJML in export code
+ * @default ''
+ */
+ preMjml?: string;
+
+ /**
+ * String after the MJML in export code
+ * @default ''
+ */
+ postMjml?: string;
+
+ /**
+ * Clean all previous blocks if true
+ * @default true
+ */
+ resetBlocks?: boolean;
+
+ /**
+ * Reset the Style Manager and add new properties for MJML
+ * @default true
+ */
+ resetStyleManager?: boolean;
+
+ /**
+ * Clean all previous devices and set a new one for mobile
+ * @default true
+ */
+ resetDevices?: boolean;
+
+ /**
+ * Hide the default selector manager
+ * @default true
+ */
+ hideSelector?: boolean;
+
+ /**
+ * Experimental: use XML parser instead of HTML.
+ * This should allow importing void MJML elements (without closing tags) like .
+ * @default false
+ * @experimental
+ */
+ useXmlParser?: boolean;
+
+ /**
+ * Column padding (this way it's easier to select columns)
+ * @default '10px 0'
+ */
+ columnsPadding?: string;
+
+ /**
+ * I18n object containing languages, [more info](https://grapesjs.com/docs/modules/I18n.html#configuration).
+ * @default {}
+ */
+ i18n?: Record;
+
+ /**
+ * Custom fonts on exported HTML header, [more info](https://github.com/mjmlio/mjml#inside-nodejs).
+ * @default {}
+ * @example
+ * {
+ * Montserrat: 'https://fonts.googleapis.com/css?family=Montserrat',
+ * 'Open Sans': 'https://fonts.googleapis.com/css?family=Open+Sans'
+ * }
+ */
+ fonts?: Record;
+
+ /**
+ * Load custom preset theme.
+ * @default true
+ */
+ useCustomTheme?: boolean;
+};