diff --git a/client/package-lock.json b/client/package-lock.json
index 0f6ab9a2..580edded 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -2180,6 +2180,11 @@
"safe-buffer": "5.1.1"
}
},
+ "ckeditor": {
+ "version": "4.11.1",
+ "resolved": "https://registry.npmjs.org/ckeditor/-/ckeditor-4.11.1.tgz",
+ "integrity": "sha512-UhHe02cc/wWJquDQZysEgh0ohLMEMU56zDx+s8prDdjylY/aBDY2xdIiIpbgCBTXdjhrEPIAPyiDS9g3RxYXig=="
+ },
"ckeditor5": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/ckeditor5/-/ckeditor5-11.1.1.tgz",
@@ -4909,6 +4914,19 @@
}
}
},
+ "grapesjs-plugin-ckeditor": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/grapesjs-plugin-ckeditor/-/grapesjs-plugin-ckeditor-0.0.9.tgz",
+ "integrity": "sha512-QXyAcSwgi09pzigGVS/NsHag5Skuw4zTkVGmEiBN/Qi8KU12/cQBG/OjAcjAB3/ZpToyPoglI33Ydjgj2nJuxQ=="
+ },
+ "grapesjs-preset-newsletter": {
+ "version": "0.2.20",
+ "resolved": "https://registry.npmjs.org/grapesjs-preset-newsletter/-/grapesjs-preset-newsletter-0.2.20.tgz",
+ "integrity": "sha512-rffUeuznf9Saig+kIUddmGfhWwbLjxdaqAYf6Hoge4b0sfT8knOS4mQXJBdRsSROfzuRhFe6ybRHm4yC32lHxA==",
+ "requires": {
+ "juice": "4.3.2"
+ }
+ },
"har-validator": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
diff --git a/client/package.json b/client/package.json
index 38d56f04..43940f59 100644
--- a/client/package.json
+++ b/client/package.json
@@ -39,6 +39,7 @@
"datatables.net-bs": "^1.10.15",
"grapesjs": "^0.14.40",
"grapesjs-mjml": "0.0.27",
+ "grapesjs-preset-newsletter": "^0.2.20",
"i18next": "^8.4.3",
"i18next-xhr-backend": "^1.4.2",
"immutable": "^3.8.1",
diff --git a/client/src/lib/sandboxed-grapesjs-root.js b/client/src/lib/sandboxed-grapesjs-root.js
index b92c6d08..2a4451e5 100644
--- a/client/src/lib/sandboxed-grapesjs-root.js
+++ b/client/src/lib/sandboxed-grapesjs-root.js
@@ -31,16 +31,26 @@ import mjml2html from "mjml4-in-browser";
import 'grapesjs/dist/css/grapes.min.css';
import grapesjs from 'grapesjs';
-import "grapesjs-mjml";
+
+import 'grapesjs-mjml';
+
+import 'grapesjs-preset-newsletter';
+import 'grapesjs-preset-newsletter/dist/grapesjs-preset-newsletter.css';
import "./sandboxed-grapesjs.scss";
import axios from './axios';
+import {GrapesJSSourceType} from "./sandboxed-grapesjs-shared";
-grapesjs.plugins.add('mailtrain', (editor, opts = {}) => {
- const panelManager = editor.Panels;
- panelManager.removeButton('options','fullscreen');
- panelManager.removeButton('options','export-template');
+
+grapesjs.plugins.add('mailtrain-remove-buttons', (editor, opts = {}) => {
+ // This needs to be done in on-load and after gjs plugin because grapesjs-preset-newsletter tries to set titles to all buttons (including those we remove)
+ // see https://github.com/artf/grapesjs-preset-newsletter/blob/e0a91636973a5a1481e9d7929e57a8869b1db72e/src/index.js#L248
+ editor.on('load', () => {
+ const panelManager = editor.Panels;
+ panelManager.removeButton('options','fullscreen');
+ panelManager.removeButton('options','export-template');
+ });
});
@@ -60,16 +70,19 @@ export class GrapesJSSandbox extends Component {
entityTypeId: PropTypes.string,
entityId: PropTypes.number,
initialSource: PropTypes.string,
- initialStyle: PropTypes.string
+ initialStyle: PropTypes.string,
+ sourceType: PropTypes.string
}
async exportState(method, params) {
+ const props = this.props;
+
const editor = this.editor;
// If exportState comes during text editing (via RichTextEditor), we need to cancel the editing, so that the
// text being edited is stored in the model
const sel = editor.getSelected();
- if (sel && sel.view && sel.disableEditing) {
+ if (sel && sel.view && sel.view.disableEditing) {
sel.view.disableEditing();
}
@@ -82,11 +95,21 @@ export class GrapesJSSandbox extends Component {
let html;
- const preMjml = '';
- const postMjml = '';
- const mjml = preMjml + source + postMjml;
+ if (props.sourceType === GrapesJSSourceType.MJML) {
+ const preMjml = '';
+ const postMjml = '';
+ const mjml = preMjml + source + postMjml;
+
+ const mjmlRes = mjml2html(mjml);
+ html = mjmlRes.html;
+
+ } else if (props.sourceType === GrapesJSSourceType.HTML) {
+ const commandManager = editor.Commands;
+
+ const cmdGetCode = commandManager.get('gjs-get-inlined-html');
+ html = cmdGetCode.run(editor);
+ }
- const mjmlRes = mjml2html(mjml);
return {
html,
@@ -124,19 +147,7 @@ export class GrapesJSSandbox extends Component {
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
- const source = props.initialSource ?
- base(props.initialSource, trustedUrlBase, sandboxUrlBase, publicUrlBase) :
- ' \n' +
- ' \n' +
- ' \n' +
- ' My Company\n' +
- ' \n' +
- ' \n' +
- ' ';
-
- const css = props.initialStyle && base(props.initialStyle, trustedUrlBase, sandboxUrlBase, publicUrlBase);
-
- this.editor = grapesjs.init({
+ const config = {
noticeOnUnload: false,
container: this.canvasNode,
height: '100%',
@@ -157,19 +168,458 @@ export class GrapesJSSandbox extends Component {
clearProperties: true,
},
fromElement: false,
- components: source,
- style: css,
+ components: '',
+ style: '',
plugins: [
- 'mailtrain',
- 'gjs-mjml'
],
pluginsOpts: {
- 'gjs-mjml': {
- preMjml: '',
- postMjml: ''
- }
}
- });
+ };
+
+ let defaultSource, defaultStyle;
+
+ if (props.sourceType === GrapesJSSourceType.MJML) {
+ defaultSource =
+ '\n' +
+ ' \n' +
+ ' \n' +
+ ' Lorem Ipsum...\n' +
+ ' \n' +
+ ' \n' +
+ '';
+
+ defaultStyle = '';
+
+ config.plugins.push('gjs-mjml');
+ config.pluginsOpts['gjs-mjml'] = {
+ preMjml: '',
+ postMjml: ''
+ };
+
+ } else if (props.sourceType === GrapesJSSourceType.HTML) {
+ defaultSource =
+ '
\n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' View in browser\n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' GrapesJS Newsletter Builder\n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' Build your newsletters faster than ever\n' +
+ ' \n' +
+ ' \n' +
+ ' Import, build, test and export responsive newsletter templates faster than ever using the GrapesJS Newsletter Builder.\n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' Built-in Blocks\n' +
+ ' \n' +
+ ' Drag and drop built-in blocks from the right panel and style them in a matter of seconds\n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' Toggle images\n' +
+ ' \n' +
+ ' Build a good looking newsletter even without images enabled by the email clients\n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' Test it\n' +
+ ' \n' +
+ ' You can send email tests directly from the editor and check how are looking on your email clients\n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' Responsive\n' +
+ ' \n' +
+ ' Using the device manager you\'ll always send a fully responsive contents\n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ ' \n' +
+ ' \n' +
+ ' | \n' +
+ '
\n' +
+ '
';
+
+ defaultStyle =
+ '.link {\n' +
+ ' color: rgb(217, 131, 166);\n' +
+ ' }\n' +
+ ' .row{\n' +
+ ' vertical-align:top;\n' +
+ ' }\n' +
+ ' .main-body{\n' +
+ ' min-height:150px;\n' +
+ ' padding: 5px;\n' +
+ ' width:100%;\n' +
+ ' height:100%;\n' +
+ ' background-color:rgb(234, 236, 237);\n' +
+ ' }\n' +
+ ' .c926{\n' +
+ ' color:rgb(158, 83, 129);\n' +
+ ' width:100%;\n' +
+ ' font-size:50px;\n' +
+ ' }\n' +
+ ' .cell.c849{\n' +
+ ' width:11%;\n' +
+ ' }\n' +
+ ' .c1144{\n' +
+ ' padding: 10px;\n' +
+ ' font-size:17px;\n' +
+ ' font-weight: 300;\n' +
+ ' }\n' +
+ ' .card{\n' +
+ ' min-height:150px;\n' +
+ ' padding: 5px;\n' +
+ ' margin-bottom:20px;\n' +
+ ' height:0px;\n' +
+ ' }\n' +
+ ' .card-cell{\n' +
+ ' background-color:rgb(255, 255, 255);\n' +
+ ' overflow:hidden;\n' +
+ ' border-radius: 3px;\n' +
+ ' padding: 0;\n' +
+ ' text-align:center;\n' +
+ ' }\n' +
+ ' .card.sector{\n' +
+ ' background-color:rgb(255, 255, 255);\n' +
+ ' border-radius: 3px;\n' +
+ ' border-collapse:separate;\n' +
+ ' }\n' +
+ ' .c1271{\n' +
+ ' width:100%;\n' +
+ ' margin: 0 0 15px 0;\n' +
+ ' font-size:50px;\n' +
+ ' color:rgb(120, 197, 214);\n' +
+ ' line-height:250px;\n' +
+ ' text-align:center;\n' +
+ ' }\n' +
+ ' .table100{\n' +
+ ' width:100%;\n' +
+ ' }\n' +
+ ' .c1357{\n' +
+ ' min-height:150px;\n' +
+ ' padding: 5px;\n' +
+ ' margin: auto;\n' +
+ ' height:0px;\n' +
+ ' }\n' +
+ ' .darkerfont{\n' +
+ ' color:rgb(65, 69, 72);\n' +
+ ' }\n' +
+ ' .button{\n' +
+ ' font-size:12px;\n' +
+ ' padding: 10px 20px;\n' +
+ ' background-color:rgb(217, 131, 166);\n' +
+ ' color:rgb(255, 255, 255);\n' +
+ ' text-align:center;\n' +
+ ' border-radius: 3px;\n' +
+ ' font-weight:300;\n' +
+ ' }\n' +
+ ' .table100.c1437{\n' +
+ ' text-align:left;\n' +
+ ' }\n' +
+ ' .cell.cell-bottom{\n' +
+ ' text-align:center;\n' +
+ ' height:51px;\n' +
+ ' }\n' +
+ ' .card-title{\n' +
+ ' font-size:25px;\n' +
+ ' font-weight:300;\n' +
+ ' color:rgb(68, 68, 68);\n' +
+ ' }\n' +
+ ' .card-content{\n' +
+ ' font-size:13px;\n' +
+ ' line-height:20px;\n' +
+ ' color:rgb(111, 119, 125);\n' +
+ ' padding: 10px 20px 0 20px;\n' +
+ ' vertical-align:top;\n' +
+ ' }\n' +
+ ' .container{\n' +
+ ' font-family: Helvetica, serif;\n' +
+ ' min-height:150px;\n' +
+ ' padding: 5px;\n' +
+ ' margin:auto;\n' +
+ ' height:0px;\n' +
+ ' width:90%;\n' +
+ ' max-width:550px;\n' +
+ ' }\n' +
+ ' .cell.c856{\n' +
+ ' vertical-align:middle;\n' +
+ ' }\n' +
+ ' .container-cell{\n' +
+ ' vertical-align:top;\n' +
+ ' font-size:medium;\n' +
+ ' padding-bottom:50px;\n' +
+ ' }\n' +
+ ' .c1790{\n' +
+ ' min-height:150px;\n' +
+ ' padding: 5px;\n' +
+ ' margin:auto;\n' +
+ ' height:0px;\n' +
+ ' }\n' +
+ ' .table100.c1790{\n' +
+ ' min-height:30px;\n' +
+ ' border-collapse:separate;\n' +
+ ' margin: 0 0 10px 0;\n' +
+ ' }\n' +
+ ' .browser-link{\n' +
+ ' font-size:12px;\n' +
+ ' }\n' +
+ ' .top-cell{\n' +
+ ' text-align:right;\n' +
+ ' color:rgb(152, 156, 165);\n' +
+ ' }\n' +
+ ' .table100.c1357{\n' +
+ ' margin: 0;\n' +
+ ' border-collapse:collapse;\n' +
+ ' }\n' +
+ ' .c1769{\n' +
+ ' width:30%;\n' +
+ ' }\n' +
+ ' .c1776{\n' +
+ ' width:70%;\n' +
+ ' }\n' +
+ ' .c1766{\n' +
+ ' margin: 0 auto 10px 0;\n' +
+ ' padding: 5px;\n' +
+ ' width:100%;\n' +
+ ' min-height:30px;\n' +
+ ' }\n' +
+ ' .cell.c1769{\n' +
+ ' width:11%;\n' +
+ ' }\n' +
+ ' .cell.c1776{\n' +
+ ' vertical-align:middle;\n' +
+ ' }\n' +
+ ' .c1542{\n' +
+ ' margin: 0 auto 10px auto;\n' +
+ ' padding:5px;\n' +
+ ' width:100%;\n' +
+ ' }\n' +
+ ' .card-footer{\n' +
+ ' padding: 20px 0;\n' +
+ ' text-align:center;\n' +
+ ' }\n' +
+ ' .c2280{\n' +
+ ' height:150px;\n' +
+ ' margin:0 auto 10px auto;\n' +
+ ' padding:5px 5px 5px 5px;\n' +
+ ' width:100%;\n' +
+ ' }\n' +
+ ' .c2421{\n' +
+ ' padding:10px;\n' +
+ ' }\n' +
+ ' .c2577{\n' +
+ ' padding:10px;\n' +
+ ' }\n' +
+ ' .footer{\n' +
+ ' margin-top: 50px;\n' +
+ ' color:rgb(152, 156, 165);\n' +
+ ' text-align:center;\n' +
+ ' font-size:11px;\n' +
+ ' padding: 5px;\n' +
+ ' }\n' +
+ ' .quote {\n' +
+ ' font-style: italic;\n' +
+ ' }\n' +
+ ' .list-item{\n' +
+ ' height:auto;\n' +
+ ' width:100%;\n' +
+ ' margin: 0 auto 10px auto;\n' +
+ ' padding: 5px;\n' +
+ ' }\n' +
+ ' .list-item-cell{\n' +
+ ' background-color:rgb(255, 255, 255);\n' +
+ ' border-radius: 3px;\n' +
+ ' overflow: hidden;\n' +
+ ' padding: 0;\n' +
+ ' }\n' +
+ ' .list-cell-left{\n' +
+ ' width:30%;\n' +
+ ' padding: 0;\n' +
+ ' }\n' +
+ ' .list-cell-right{\n' +
+ ' width:70%;\n' +
+ ' color:rgb(111, 119, 125);\n' +
+ ' font-size:13px;\n' +
+ ' line-height:20px;\n' +
+ ' padding: 10px 20px 0px 20px;\n' +
+ ' }\n' +
+ ' .list-item-content{\n' +
+ ' border-collapse: collapse;\n' +
+ ' margin: 0 auto;\n' +
+ ' padding: 5px;\n' +
+ ' height:150px;\n' +
+ ' width:100%;\n' +
+ ' }\n' +
+ ' .list-item-image{\n' +
+ ' color:rgb(217, 131, 166);\n' +
+ ' font-size:45px;\n' +
+ ' width: 100%;\n' +
+ ' }\n' +
+ ' .grid-item-image{\n' +
+ ' line-height:150px;\n' +
+ ' font-size:50px;\n' +
+ ' color:rgb(120, 197, 214);\n' +
+ ' margin-bottom:15px;\n' +
+ ' width:100%;\n' +
+ ' }\n' +
+ ' .grid-item-row {\n' +
+ ' margin: 0 auto 10px;\n' +
+ ' padding: 5px 0;\n' +
+ ' width: 100%;\n' +
+ ' }\n' +
+ ' .grid-item-card {\n' +
+ ' width:100%;\n' +
+ ' padding: 5px 0;\n' +
+ ' margin-bottom: 10px;\n' +
+ ' }\n' +
+ ' .grid-item-card-cell{\n' +
+ ' background-color:rgb(255, 255, 255);\n' +
+ ' overflow: hidden;\n' +
+ ' border-radius: 3px;\n' +
+ ' text-align:center;\n' +
+ ' padding: 0;\n' +
+ ' }\n' +
+ ' .grid-item-card-content{\n' +
+ ' font-size:13px;\n' +
+ ' color:rgb(111, 119, 125);\n' +
+ ' padding: 0 10px 20px 10px;\n' +
+ ' width:100%;\n' +
+ ' line-height:20px;\n' +
+ ' }\n' +
+ ' .grid-item-cell2-l{\n' +
+ ' vertical-align:top;\n' +
+ ' padding-right:10px;\n' +
+ ' width:50%;\n' +
+ ' }\n' +
+ ' .grid-item-cell2-r{\n' +
+ ' vertical-align:top;\n' +
+ ' padding-left:10px;\n' +
+ ' width:50%;\n' +
+ ' }';
+
+ config.plugins.push('gjs-preset-newsletter');
+ }
+
+ config.components = props.initialSource ? base(props.initialSource, trustedUrlBase, sandboxUrlBase, publicUrlBase) : defaultSource;
+ config.style = props.initialStyle ? base(props.initialStyle, trustedUrlBase, sandboxUrlBase, publicUrlBase) : defaultStyle;
+
+ config.plugins.push('mailtrain-remove-buttons');
+
+ this.editor = grapesjs.init(config);
}
render() {
@@ -182,7 +632,6 @@ export class GrapesJSSandbox extends Component {
}
-
export default function() {
parentRPC.init();
diff --git a/client/src/lib/sandboxed-grapesjs-shared.js b/client/src/lib/sandboxed-grapesjs-shared.js
new file mode 100644
index 00000000..339580c4
--- /dev/null
+++ b/client/src/lib/sandboxed-grapesjs-shared.js
@@ -0,0 +1,6 @@
+'use strict';
+
+export const GrapesJSSourceType = {
+ MJML: 'mjml',
+ HTML: 'html'
+};
diff --git a/client/src/lib/sandboxed-grapesjs.js b/client/src/lib/sandboxed-grapesjs.js
index fdca6651..1dabee78 100644
--- a/client/src/lib/sandboxed-grapesjs.js
+++ b/client/src/lib/sandboxed-grapesjs.js
@@ -10,6 +10,7 @@ import styles
import {UntrustedContentHost} from './untrusted';
import {Icon} from "./bootstrap-components";
import {getTrustedUrl} from "./urls";
+import {GrapesJSSourceType} from "./sandboxed-grapesjs-shared";
@translate(null, { withRef: true })
export class GrapesJSHost extends Component {
@@ -26,6 +27,7 @@ export class GrapesJSHost extends Component {
entity: PropTypes.object,
initialSource: PropTypes.string,
initialStyle: PropTypes.string,
+ sourceType: PropTypes.string,
title: PropTypes.string,
onFullscreenAsync: PropTypes.func
}
@@ -49,7 +51,8 @@ export class GrapesJSHost extends Component {
entityTypeId: this.props.entityTypeId,
entityId: this.props.entity.id,
initialSource: this.props.initialSource,
- initialStyle: this.props.initialStyle
+ initialStyle: this.props.initialStyle,
+ sourceType: this.props.sourceType
};
const tokenData = {
diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js
index 847028f1..7d71a29c 100644
--- a/client/src/templates/helpers.js
+++ b/client/src/templates/helpers.js
@@ -26,6 +26,7 @@ import {
import {Trans} from "react-i18next";
import styles from "../lib/styles.scss";
+import {GrapesJSSourceType} from "../lib/sandboxed-grapesjs-shared";
export const ResourceType = {
TEMPLATE: 'template',
@@ -175,9 +176,26 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
validate: state => {}
};
+
+ const grapesJSSourceTypes = [
+ {key: GrapesJSSourceType.MJML, label: t('MJML')},
+ {key: GrapesJSSourceType.HTML, label: t('HTML')}
+ ];
+
+ const grapesJSSourceTypeLabels = {};
+ for ({key, label} of grapesJSSourceTypes) {
+ grapesJSSourceTypeLabels[key] = label;
+ }
+
templateTypes.grapesjs = {
typeName: t('GrapeJS'),
- getTypeForm: (owner, isEdit) => null,
+ getTypeForm: (owner, isEdit) => {
+ if (isEdit) {
+ return {grapesJSSourceTypeLabels[(owner.getFormValue(prefix + 'grapesJSSourceType'))]};
+ } else {
+ return ;
+ }
+ },
getHTMLEditor: owner =>
@@ -199,9 +218,11 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
});
},
initData: () => ({
+ [prefix + 'grapesJSSourceType']: GrapesJSSourceType.MJML,
[prefix + 'grapesJSData']: {}
}),
afterLoad: data => {
+ data[prefix + 'grapesJSSourceType'] = data[prefix + 'data'].sourceType;
data[prefix + 'grapesJSData'] = {
source: data[prefix + 'data'].source,
style: data[prefix + 'data'].style
@@ -209,6 +230,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
},
beforeSave: data => {
data[prefix + 'data'] = {
+ sourceType: data[prefix + 'grapesJSSourceType'],
source: data[prefix + 'grapesJSData'].source,
style: data[prefix + 'grapesJSData'].style
};