diff --git a/README.md b/README.md index 9adf57e..23011c2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This plugin enables the usage of [MJML](https://mjml.io/) components inside the

GrapesJS


-Supported MJML components: +Supported MJML components (using default mjml-browser parser): `mj-mjml` `mj-head` `mj-body` @@ -41,10 +41,12 @@ Supported MJML components: |`blocks`|Which blocks to add|(all)| |`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)|`{}`| +|`mjmlParser`|Custom [mjml-browser](https://www.npmjs.com/package/mjml-browser) instance. Allows to extend MJML functionality or add custom MJML components |`(input: string \| MJMLJsonObject, opt: MJMLParsingOptions) => MJMLParseResults`| |`overwriteExport`|Overwrite default export command|`true`| |`preMjml`|String before the MJML in export code|`''`| |`postMjml`|String after the MJML in export code|`''`| @@ -181,6 +183,39 @@ editor.on('load', () => { }); ``` +### Using Independent mjml-browser Build + +In case, you have your own version of MJML with custom or extended components, it is possible +to override default [mjml parser](https://github.com/mjmlio/mjml/tree/master/packages/mjml-browser) +with custom one and create custom grapesJS components. + +For further info how to create MJML Component, you can +[visit components folder](https://github.com/GrapesJS/mjml/tree/master/src/components) +or you can go to [docs](https://grapesjs.com/docs/modules/Components.html#define-custom-component-type). + +```ts +import 'grapesjs/dist/css/grapes.min.css' +import grapesJS from 'grapesjs' +import grapesJSMJML from 'grapesjs-mjml' +import customMjmlParser from 'custom-mjml-parser'; + +import customImage from 'custom/components/path' + +grapesJS.init({ + fromElement: true, + container: '#gjs', + plugins: [grapesJSMJML], + pluginsOpts: { + [grapesJSMJML]: { + mjmlParser: customMjmlParser, + customComponents: [ + customImage, + ] + } + }, +}); +``` + ## Development Clone the repository diff --git a/package-lock.json b/package-lock.json index 4f2032a..69aa401 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2427,6 +2427,55 @@ "node": ">=0.10.0" } }, + "@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/mjml": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz", + "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==", + "requires": { + "@types/mjml-core": "*" + } + }, + "@types/mjml-core": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.7.4.tgz", + "integrity": "sha512-hajbYITLm/wJU99Of50Dmn/k4ok+mrhJs4qDdnveJsINdiNJhQd+03C6Kt09vF9biB23cEI4pPeLrJNYfIZf7g==" + }, + "@types/node": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", + "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, "node_modules/@jest/core/node_modules/fill-range/node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", diff --git a/package.json b/package.json index 2b68145..24fdd19 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "author": "Artur Arseniev", "license": "BSD-3-Clause", "dependencies": { + "@types/mjml": "^4.7.4", "mjml-browser": "^4.13.0" }, "babel": { diff --git a/src/commands/index.ts b/src/commands/index.ts index aa8a80f..0b4dca0 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -22,7 +22,7 @@ export default (editor: Editor, opts: RequiredPluginOptions) => { Commands.add(cmdGetMjmlToHtml, (ed, _, opt) => { const mjml = Commands.run(cmdGetMjml); - return mjmlConvert(mjml, opts.fonts, opt); + return mjmlConvert(opts.mjmlParser, mjml, opts.fonts, opt); }); openExportMjml(editor, opts, cmdOpenExport); diff --git a/src/components/Body.ts b/src/components/Body.ts index 550be38..5d4b089 100644 --- a/src/components/Body.ts +++ b/src/components/Body.ts @@ -1,5 +1,6 @@ // Specs: https://documentation.mjml.io/#mj-body import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { type as typeHero } from './Hero'; import { type as typeRaw } from './Raw'; import { type as typeSection } from './Section'; @@ -8,7 +9,7 @@ import { componentsToQuery, getName, isComponentType } from './utils'; export const type = 'mj-body'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { diff --git a/src/components/Button.ts b/src/components/Button.ts index cc2b440..09d0910 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mj-button import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeColumn } from './Column'; import { type as typeHero } from './Hero'; export const type = 'mj-button'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), extend: 'link', diff --git a/src/components/Column.ts b/src/components/Column.ts index f99725a..c0ff086 100644 --- a/src/components/Column.ts +++ b/src/components/Column.ts @@ -1,11 +1,13 @@ // Specs: https://documentation.mjml.io/#mj-column import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType, mjmlConvert } from './utils'; import { type as typeSection } from './Section'; + export const type = 'mj-column'; -export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: any) => { +export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: ComponentPluginOptions) => { const clmPadd = opt.columnsPadding; editor.Components.addType(type, { @@ -37,7 +39,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: getTemplateFromMjml() { const mjmlTmpl = this.getMjmlTemplate(); const innerMjml = this.getInnerMjmlTemplate(); - const htmlOutput = mjmlConvert(`${mjmlTmpl.start} + const htmlOutput = mjmlConvert(opt.mjmlParser, `${mjmlTmpl.start} ${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`, opt.fonts); const html = htmlOutput.html; diff --git a/src/components/Divider.ts b/src/components/Divider.ts index 126082c..d983b01 100644 --- a/src/components/Divider.ts +++ b/src/components/Divider.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mj-divider import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeColumn } from './Column'; import { type as typeHero } from './Hero'; export const type = 'mj-divider'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { diff --git a/src/components/Font.ts b/src/components/Font.ts index 9686d3b..f551b53 100644 --- a/src/components/Font.ts +++ b/src/components/Font.ts @@ -1,11 +1,12 @@ // Specs: https://documentation.mjml.io/#mj-font import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, isComponentType, mjmlConvert } from './utils'; import { type as typeHead } from './Head'; export const type = 'mj-font'; -export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: any) => { +export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { @@ -40,7 +41,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: getTemplateFromMjml() { let mjmlTmpl = this.getMjmlTemplate(); let innerMjml = this.getInnerMjmlTemplate(); - const htmlOutput = mjmlConvert(`${mjmlTmpl.start} + const htmlOutput = mjmlConvert(opt.mjmlParser, `${mjmlTmpl.start} ${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`, opt.fonts); let html = htmlOutput.html; let start = html.indexOf('') + 6; diff --git a/src/components/Group.ts b/src/components/Group.ts index 7463632..e79cfab 100644 --- a/src/components/Group.ts +++ b/src/components/Group.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mj-group import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeSection } from './Section'; import { type as typeColumn } from './Column'; export const type = 'mj-group'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { diff --git a/src/components/Hero.ts b/src/components/Hero.ts index 0439ff0..11bcc1b 100644 --- a/src/components/Hero.ts +++ b/src/components/Hero.ts @@ -1,5 +1,6 @@ // Specs: https://documentation.mjml.io/#mj-hero import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeBody } from './Body'; import { type as typeText } from './Text'; @@ -12,7 +13,7 @@ import { type as typeSpacer } from './Spacer'; export const type = 'mj-hero'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { diff --git a/src/components/Image.ts b/src/components/Image.ts index a3180af..57ee274 100644 --- a/src/components/Image.ts +++ b/src/components/Image.ts @@ -1,5 +1,6 @@ // Specs: https://documentation.mjml.io/#mj-image import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeSection } from './Section'; import { type as typeColumn } from './Column'; @@ -7,7 +8,7 @@ import { type as typeHero } from './Hero'; export const type = 'mj-image'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), extend: 'image', diff --git a/src/components/NavBar.ts b/src/components/NavBar.ts index 67b8224..c559901 100644 --- a/src/components/NavBar.ts +++ b/src/components/NavBar.ts @@ -1,5 +1,6 @@ // Specs https://documentation.mjml.io/#mj-navbar import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType, mjmlConvert } from './utils'; import { type as typeColumn } from './Column'; import { type as typeHero } from './Hero'; @@ -7,7 +8,7 @@ import { type as typeNavBarLink } from './NavBarLink'; export const type = 'mj-navbar'; -export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: any) => { +export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { @@ -51,7 +52,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: getTemplateFromMjml() { const mjmlTmpl = this.getMjmlTemplate(); const innerMjml = this.getInnerMjmlTemplate(); - const htmlOutput = mjmlConvert(`${mjmlTmpl.start} + const htmlOutput = mjmlConvert(opt.mjmlParser, `${mjmlTmpl.start} ${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`, opt.fonts); const html = htmlOutput.html; diff --git a/src/components/NavBarLink.ts b/src/components/NavBarLink.ts index 96e6602..3114f53 100644 --- a/src/components/NavBarLink.ts +++ b/src/components/NavBarLink.ts @@ -1,11 +1,12 @@ // Specs: https://documentation.mjml.io/#mj-navbar-link import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeNavBar } from './NavBar'; export const type = 'mj-navbar-link'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), extend: 'link', diff --git a/src/components/Raw.ts b/src/components/Raw.ts index 4666539..4b393c5 100644 --- a/src/components/Raw.ts +++ b/src/components/Raw.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mj-raw import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeBody } from './Body'; import { type as typeHead } from './Head'; export const type = 'mj-raw'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { diff --git a/src/components/Section.ts b/src/components/Section.ts index b1ff50b..72c173e 100644 --- a/src/components/Section.ts +++ b/src/components/Section.ts @@ -1,5 +1,6 @@ // Specs: https://documentation.mjml.io/#mj-section import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeBody } from './Body'; import { type as typeWrapper } from './Wrapper'; @@ -8,7 +9,7 @@ import { type as typeGroup } from './Group'; export const type = 'mj-section'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), diff --git a/src/components/Social.ts b/src/components/Social.ts index 17f6baf..90f3368 100644 --- a/src/components/Social.ts +++ b/src/components/Social.ts @@ -1,5 +1,6 @@ // Specs: https://documentation.mjml.io/#mjml-social import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeColumn } from './Column'; import { type as typeHero } from './Hero'; @@ -7,7 +8,7 @@ import { type as typeSocialElement } from './SocialElement'; export const type = 'mj-social'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), diff --git a/src/components/SocialElement.ts b/src/components/SocialElement.ts index 391c8c4..c590e3c 100644 --- a/src/components/SocialElement.ts +++ b/src/components/SocialElement.ts @@ -1,11 +1,12 @@ // Specs: https://documentation.mjml.io/#mjml-social import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeSocial } from './Social'; export const type = 'mj-social-element'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), diff --git a/src/components/Spacer.ts b/src/components/Spacer.ts index 94454f4..ab8326e 100644 --- a/src/components/Spacer.ts +++ b/src/components/Spacer.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mjml-spacer import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeColumn } from './Column'; import { type as typeHero } from './Hero'; export const type = 'mj-spacer'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), diff --git a/src/components/Style.ts b/src/components/Style.ts index 6242b7f..95f026a 100644 --- a/src/components/Style.ts +++ b/src/components/Style.ts @@ -1,11 +1,12 @@ // Specs: https://documentation.mjml.io/#mj-style import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, isComponentType, mjmlConvert } from './utils'; import { type as typeHead } from './Head'; export const type = 'mj-style'; -export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: any) => { +export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), @@ -35,7 +36,7 @@ export default (editor: Editor, { opt, coreMjmlModel, coreMjmlView, sandboxEl }: getTemplateFromMjml() { let mjmlTmpl = this.getMjmlTemplate(); let innerMjml = this.getInnerMjmlTemplate(); - const htmlOutput = mjmlConvert(`${mjmlTmpl.start} + const htmlOutput = mjmlConvert(opt.mjmlParser, `${mjmlTmpl.start} ${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`, opt.fonts); let html = htmlOutput.html; let start = html.indexOf('') + 6; diff --git a/src/components/Text.ts b/src/components/Text.ts index 40ee8b9..2cf11bc 100644 --- a/src/components/Text.ts +++ b/src/components/Text.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mjml-text import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeColumn } from './Column'; import { type as typeHero } from './Hero'; export const type = 'mj-text'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { extend: 'text', extendFnView: ['onActive'], diff --git a/src/components/Wrapper.ts b/src/components/Wrapper.ts index 4fe42a1..82c9257 100644 --- a/src/components/Wrapper.ts +++ b/src/components/Wrapper.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mjml-wrapper import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { componentsToQuery, getName, isComponentType } from './utils'; import { type as typeBody } from './Body'; import { type as typeSection } from './Section'; export const type = 'mj-wrapper'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), diff --git a/src/components/index.ts b/src/components/index.ts index 0b6c09c..74c1102 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,5 +1,5 @@ -import type { Editor } from 'grapesjs'; -import { mjmlConvert, debounce } from './utils'; +import type { Editor, PluginOptions } from 'grapesjs'; +import { mjmlConvert, debounce, componentsToQuery } from './utils'; import loadMjml from './mjml'; import loadHead from './Head'; import loadStyle from './Style'; @@ -22,6 +22,20 @@ import loadHero from './Hero'; import loadRaw from './Raw'; import { RequiredPluginOptions } from '..'; +export type ComponentPluginOptions = { + /** + * Core model, which can be extended + */ + coreMjmlModel: any; + /** + * Core view, which can be extended + */ + coreMjmlView: any; + opt: Required; + sandboxEl: HTMLDivElement; + componentsToQuery: typeof componentsToQuery, +} + export default (editor: Editor, opt: RequiredPluginOptions) => { const { Components } = editor; // @ts-ignore @@ -204,7 +218,7 @@ export default (editor: Editor, opt: RequiredPluginOptions) => { const mjmlTmpl = this.getMjmlTemplate(); const innerMjml = this.getInnerMjmlTemplate(); const mjml = `${mjmlTmpl.start}${innerMjml.start}${innerMjml.end}${mjmlTmpl.end}`; - const htmlOutput = mjmlConvert(mjml, opt.fonts); + const htmlOutput = mjmlConvert(opt.mjmlParser, mjml, opt.fonts); let html = htmlOutput.html; html = html.replace(//, ''); let start = html.indexOf('') + 6; @@ -266,9 +280,8 @@ export default (editor: Editor, opt: RequiredPluginOptions) => { } } as any; - // MJML Internal view (for elements inside mj-columns) - const compOpts = { coreMjmlModel, coreMjmlView, opt, sandboxEl }; + const compOpts = { coreMjmlModel, coreMjmlView, opt, sandboxEl, componentsToQuery}; // Avoid the tag from the default wrapper editor.Components.addType('wrapper', { @@ -303,6 +316,7 @@ export default (editor: Editor, opt: RequiredPluginOptions) => { loadNavBarLink, loadHero, loadRaw, + ...opt.customComponents, ] .forEach(module => module(editor, compOpts)); }; diff --git a/src/components/mjml.ts b/src/components/mjml.ts index a0af7ac..aa26f0f 100644 --- a/src/components/mjml.ts +++ b/src/components/mjml.ts @@ -1,12 +1,13 @@ // Specs: https://documentation.mjml.io/#mjml import type { Editor } from 'grapesjs'; +import { ComponentPluginOptions } from '.'; import { isComponentType, componentsToQuery } from './utils'; import { type as typeHead } from './Head'; import { type as typeBody } from './Body'; export const type = 'mjml'; -export default (editor: Editor, { coreMjmlModel, coreMjmlView }: any) => { +export default (editor: Editor, { coreMjmlModel, coreMjmlView }: ComponentPluginOptions) => { editor.Components.addType(type, { isComponent: isComponentType(type), model: { diff --git a/src/components/parser.ts b/src/components/parser.ts new file mode 100644 index 0000000..e18be4a --- /dev/null +++ b/src/components/parser.ts @@ -0,0 +1,15 @@ +// @ts-ignore +import mjml2html from 'mjml-browser'; +import mjml from "mjml-core"; + +/** + * MJML Parser. + * + * @see {@link https://github.com/mjmlio/mjml/tree/master/packages/mjml-core} + */ +export type MjmlParser = typeof mjml; + +/** + * MJML Parser instance. + */ +export default mjml2html as MjmlParser; diff --git a/src/components/utils.ts b/src/components/utils.ts index 1a20e00..35972d6 100644 --- a/src/components/utils.ts +++ b/src/components/utils.ts @@ -1,14 +1,14 @@ -// @ts-ignore -import mjml2html from 'mjml-browser'; import type { Editor } from 'grapesjs'; +import { MJMLParsingOptions } from "mjml-core"; +import { MjmlParser } from "./parser"; export const isComponentType = (type: string) => (el: Element) => (el.tagName || '').toLowerCase() === type; -export function mjmlConvert (mjml: string, fonts: Record, opts: Record = {}) { - const options = { +export function mjmlConvert (parser: MjmlParser, mjml: string, fonts: Record, opts: Partial = {}) { + const options: MJMLParsingOptions = { useMjmlConfigOptions: false, - mjmlConfigPath: null, - filePath: null, + mjmlConfigPath: undefined, + filePath: undefined, ...opts, }; @@ -18,7 +18,7 @@ export function mjmlConvert (mjml: string, fonts: Record, opts: options.fonts = fonts; } - return mjml2html(mjml, options); + return parser(mjml, options); } export const componentsToQuery = (cmps: string | string[]): string => { diff --git a/src/index.ts b/src/index.ts index a3092e2..8c17574 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ -import type { Plugin } from 'grapesjs'; +import type { Editor, Plugin } from 'grapesjs'; import loadBlocks from './blocks'; -import loadComponents from './components'; +import loadComponents, { ComponentPluginOptions } from './components'; +import mjml2html, { MjmlParser } from './components/parser'; import loadCommands from './commands'; import loadPanels from './panels'; import loadStyle from './style'; @@ -26,6 +27,13 @@ export type PluginOptions = { */ codeViewerTheme?: string; + /** + * Add custom MJML components + * + * @default [] + */ + customComponents?: ((editor: Editor, componentOptions: ComponentPluginOptions) => void)[], + /** * Placeholder MJML template for the import modal * @default '' @@ -38,6 +46,12 @@ export type PluginOptions = { */ imagePlaceholderSrc?: string; + /** + * Custom MJML parser. + * @default mjml-browser instance + */ + mjmlParser?: MjmlParser; + /** * Overwrite default export command * @default true @@ -128,8 +142,10 @@ const plugin: Plugin = (editor, opt = {}) => { ], block: () => ({}), codeViewerTheme: 'hopscotch', + customComponents: [], importPlaceholder: '', imagePlaceholderSrc: '', + mjmlParser: mjml2html, overwriteExport: true, preMjml: '', postMjml: '',