Closed jakeFeldman closed 3 years ago
Hmm interesting, I've had problems in the past using VueJS (https://github.com/artf/grapesjs-mjml/issues/155) but I haven't seen this error before.
Does the block manager get rendered correctly? also could you try the unminified build aswell and see if that work:
import grapesjsMJML from 'grapesjs-mjml/dist/grapesjs-mjml'
Hey @DRoet thanks for the quick reply. The block manager gets rendered properly for my setup (i.e. no panel but attaching to a custom div)
Using the existing setup with the unminified build I get a new warning/error. The outcome is the same but seems to narrow it down more.
Invalid target position: Component not draggable, acceptable by [[data-gjs-type=mj-body], [data-gjs-type=mj-wrapper]]
I put together a repo https://github.com/jakeFeldman/grapesjs-mjml-demo for testing if that helps. I'll try to pull the repo down and see if I can isolate whats happening.
Could you check on the latest release (0.4.0)? we now use the official mjml-browser
build which should offer better web compatibility.
The import grapesjsMJML from 'grapesjs-mjml/dist/grapesjs-mjml'
workaround is also no longer needed (when it comes to VueJS)
Hello @DRoet thanks for following up. I updated to version 0.4.0 and I still received the same error/warning.
If I use the default blocks panel I get this warning
Invalid target position: Component not draggable, acceptable by [[data-gjs-type=mj-body], [data-gjs-type=mj-wrapper]]
If I use my custom blocks panel I get the original error.
Invalid target position: Target collection not found, Target is not droppable, accepts [undefined], Component not draggable, acceptable by [undefined]
Try adding
<mjml>
<mj-body>
<mj-section>
</mj-section>
</mj-body>
</mjml>
It can be done thru editor.addComponents or fromElement
Thanks @edward-vo. I'll give it a try and see if that resolves my issue!
@jakeFeldman - did this resolve the issue ?
@buzztnt @edward-vo Adding the suggested mjml tags using editor.addComponents did allow me to add to the editor. We can close this issue!
I'm seeing the same issue in our setup now. The previous set up worked prior to upgrading grapesjs
from 0.18.4
to 0.19.5
, but no longer works. I am getting this warning when I attempt to drag any block item onto the canvas:
{
"errors": [
"Target collection not found",
"Component not draggable, acceptable by [[data-gjs-type=mj-body], [data-gjs-type=mj-wrapper]]"
],
"model": {
"tagName": "mj-section",
"type": "mj-section"
},
"context": "sorter",
"level": "warning"
}
defaults.js
:
import grapesJSMJML from 'grapesjs-mjml';
const defaults = {
container: '#gjs',
height: '600px',
width: 'auto',
fromElement: true,
plugins: [grapesJSMJML],
pluginsOpts: {
[grapesJSMJML]: {
cmdBtnMoveLabel: 'Move',
cmdBtnUndoLabel: 'Undo',
cmdBtnRedoLabel: 'Redo',
cmdBtnDesktopLabel: 'Desktop',
cmdBtnTabletLabel: 'Tablet',
cmdBtnMobileLabel: 'Mobile',
expTplBtnTitle: 'View Code',
fullScrBtnTitle: 'FullScreen',
swichtVwBtnTitle: 'View Components',
defaultTemplate: '', // Default template in case the canvas is empty
categoryLabel: 'Basic',
},
},
storageManager: {
id: '', // 'gjs-', // Prefix identifier that will be used on parameters
type: 'remote', // Type of the storage
autosave: true, // Store data automatically
autoload: true, // Autoload stored data on init
stepsBeforeSave: 1, // If autosave enabled, indicates how many changes are necessary before store method is triggered
},
colorPicker: {
appendTo: '#picker-container',
},
blockManager: {
appendTo: '#mjml-editor-blocks',
},
deviceManager: {
devices: [
{
name: 'Desktop',
width: '', // default size
},
{
name: 'Mobile',
width: '320px', // this value will be used on canvas width
widthMedia: '480px', // this value will be used in CSS @media
},
],
},
traitManager: {
appendTo: '.styles-container',
},
styleManager: {
appendTo: '.styles-container',
sectors: [
{
name: 'Dimension',
open: false,
// Use built-in properties
buildProps: ['width', 'min-height', 'padding'],
// Use `properties` to define/override single property
properties: [
{
// Type of the input,
// options: integer | radio | select | color | slider | file | composite | stack
type: 'integer',
name: 'The width', // Label for the property
property: 'width', // CSS property (if buildProps contains it will be extended)
units: ['px', '%'], // Units, available only for 'integer' types
defaults: 'auto', // Default value
min: 0, // Min value, available only for 'integer' types
},
],
},
],
},
panels: {
defaults: [
{
id: 'layers',
el: '.panel__right',
// Make the panel resizable
resizable: {
maxDim: 350,
minDim: 200,
tc: 0, // Top handler
cl: 1, // Left handler
cr: 0, // Right handler
bc: 0, // Bottom handler
// Being a flex child we need to change `flex-basis` property
// instead of the `width` (default)
keyWidth: 'flex-basis',
},
},
{
id: 'panel-devices',
el: '.panel__devices',
buttons: [
{
id: 'device-desktop',
className: 'fa fa-desktop',
command: 'set-device-desktop',
active: true,
togglable: false,
},
{
id: 'device-mobile',
className: 'fa fa-mobile',
command: 'set-device-mobile',
togglable: false,
},
],
},
{
id: 'panel-options',
el: '#panel__options',
buttons: [
{
id: 'undo',
className: 'fa fa-undo',
command: 'undo',
},
{
id: 'redo',
className: 'fa fa-repeat',
command: 'redo',
},
{
id: 'mjml-import',
className: 'fa fa-upload',
command: 'mjml-import',
},
],
},
{
id: 'basic-actions',
el: '.panel__basic-actions',
buttons: [
{
id: 'visibility',
active: true, // active by default
className: 'fa fa-square-o',
command: 'sw-visibility', // Built-in command
},
{
id: 'preview',
className: 'fa fa-eye',
command: 'preview',
context: 'preview',
attributes: { title: 'Preview' },
},
{
id: 'export',
className: 'fa fa-download',
command: 'export-template',
context: 'export-template', // For grouping context of buttons from the same panel
},
{
id: 'show-json',
className: 'fa fa-code',
context: 'show-json',
command(editor) {
editor.Modal.setTitle('Components JSON')
.setContent(
`<textarea style="width:100%; height: 450px;">
${JSON.stringify(editor.getComponents())}
</textarea>`,
)
.open();
},
},
],
},
],
},
};
export default defaults;
Ged.js
(i.e., grapesJS Editor
)
import GrapesJS from 'grapesjs';
import ReactDOMServer from 'react-dom/server';
import React from 'react';
import defaultsDeep from 'lodash/defaultsDeep';
import defaults from './defaults';
/* eslint-disable */
class Ged {
constructor(id, { variables, linkVariables }) {
const accessToken = localStorage.getItem('<localStorageKeyHere>');
const config = defaultsDeep(
{
storageManager: {
options:
{
remote:
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
urlStore: `${REACT_APP_API_URL}/api/email_templates/${id}/builder_store`,
urlLoad: `${REACT_APP_API_URL}/api/email_templates/${id}/builder_load`,
contentTypeJson: true,
credentials: 'same-origin',
onStore: (data, editor) => {
const pagesHtml = editor.Pages.getAll().map(page => {
const component = page.getMainComponent();
return {
html: editor.getHtml({ component }),
css: editor.getCss({ component }),
}
});
return {
...data, ...pagesHtml[0]
};
},
onLoad: result => {
return {...result, pages : [{ component: result.html }]};
},
},
}
},
richTextEditor: {
actions: this.getActions(variables),
},
},
defaults,
);
if (Ged.editor) {
Ged.editor.destroy();
}
Ged.editor = GrapesJS.init(config);
Ged.editor.Components.addType('wrapper', {
model: {
defaults: {
tagName: 'div', // replace body to div
},
// And/or skip wrapper in the HTML output
toHTML: function(opts) {
return this.getInnerHTML(opts);
}
}
});
this.removeDevicePanel();
this.addDeviceCommands();
this.updateImage();
this.addVariables(linkVariables);
this.addBlocks();
this.removeBlocks();
}
removeBlocks() {
const { editor } = Ged;
editor.BlockManager.remove('mj-hero');
editor.BlockManager.remove('mj-wrapper');
}
addBlocks() {
const { editor } = Ged;
editor.BlockManager.add('a', {
label: 'Link',
content: '<a class="link">Select to change the URL</a>',
attributes: { class: 'fa fa-link' },
});
editor.DomComponents.addType('link', {
model: {
defaults: {
draggable: '[data-gjs-type=mj-text]',
},
},
});
editor.DomComponents.addType('mj-text', {
model: {
defaults: {
droppable: ['a'],
},
},
});
}
updateImage() {
const comps = Ged.editor.DomComponents;
const originalImage = comps.getType('mj-image');
comps.addType('mj-image', {
model: {
defaults: {
traits: ['href', 'rel', 'alt', 'title', 'src'],
void: false,
},
},
});
}
getActions(variables) {
const { editor } = Ged;
const selectDropdown = (
<select className="gjs-field">
<option value="" disabled selected>
Variables
</option>
{variables.map((option) => (
<option key={option.key} value={`{{ ${option.key} }}`}>
{option.display_name}
</option>
))}
</select>
);
const actions = [
'bold',
'italic',
'underline',
'strikethrough',
'link',
{
name: 'vars',
icon: ReactDOMServer.renderToString(selectDropdown),
// Bind the 'result' on 'change' listener
event: 'change',
result: (rte, action) => rte.insertHTML(action.btn.firstChild.value),
// Reset the select on change
update: (rte, action) => {
action.btn.firstChild.value = '';
},
},
];
return actions;
}
removeDevicePanel() {
const { editor } = Ged;
editor.Panels.removePanel('devices-c');
}
addDeviceCommands() {
const { editor } = Ged;
editor.Commands.add('set-device-desktop', {
run: (editor) => editor.setDevice('Desktop'),
});
editor.Commands.add('set-device-mobile', {
run: (editor) => editor.setDevice('Mobile'),
});
}
addVariables(variables) {
const { editor } = Ged;
const buttonOptions = variables.map((option) => ({
value: `{{ ${option.key}}}`,
name: option.display_name,
}));
editor.DomComponents.addType('mj-button', {
model: {
defaults: {
traits: [
{
type: 'select',
label: 'Link',
name: 'href',
options: buttonOptions,
},
],
},
},
});
}
}
export default Ged;
Builder.js
:
import React from 'react';
import isEmpty from 'lodash/isEmpty';
import 'grapesjs/dist/css/grapes.min.css';
import './geditor.less';
import Ged from './Ged';
const { useEffect, useState } = React;
// slim example from https://github.com/thanhtunguet/grapesjs-react/issues/12
function Builder(props) {
const { id, variables, linkVariables } = props;
const [editor, setEditor] = useState(null);
useEffect(() => {
if (!editor && !isEmpty(variables) && !isEmpty(linkVariables)) {
const newEditor = new Ged(id, { variables, linkVariables });
setEditor(newEditor);
}
}, []);
return (
<div id="mjml-editor">
<div className="mjml-editor-row" style={{ height: '100%' }}>
<div id="mjml-editor-layers" className="mjml-editor-column" style={{ flexBasis: '200px' }}>
<div id="panel__options" />
<br />
<br />
<div id="mjml-editor-blocks" />
</div>
<div className="mjml-editor-column mjml-editor-clm">
<div className="panel__top">
<div className="panel__basic-actions" />
<div className="panel__devices" />
<div className="panel__switcher" />
</div>
<div className="editor-row">
<div className="editor-canvas">
<div id="gjs" style={{ overflow: 'hidden', height: '100%' }}>
<mjml>
<mj-body>
<mj-section></mj-section>
</mj-body>
</mjml>
</div>
<div id="picker-container" className="gjs-editor-cont" />
</div>
<div className="panel__right">
<div className="layers-container" />
<div className="styles-container" style={{ overflow: 'auto' }} />
<div className="traits-container" />
</div>
</div>
</div>
</div>
</div>
);
}
export default Builder;
mjml
components through editor.addComponents
. That is, removing the <mjml>
components from here:Builder.js
:
<div id="gjs" style={{ overflow: 'hidden', height: '100%' }}>
<mjml>
<mj-body>
<mj-section></mj-section>
</mj-body>
</mjml>
</div>
Ged.js
:
...
Ged.editor = GrapesJS.init(config);
Ged.editor.Components.addType('wrapper', {
model: {
defaults: {
tagName: 'div', // replace body to div
},
// And/or skip wrapper in the HTML output
toHTML: function(opts) {
return this.getInnerHTML(opts);
}
}
});
...
Builder.js
:
...
<div id="gjs" style={{ overflow: 'hidden', height: '100%' }} />
...
Ged.js
:
...
Ged.editor = GrapesJS.init(config);
Ged.editor.Components.addType('wrapper', {
model: {
defaults: {
tagName: 'div', // replace body to div
},
// And/or skip wrapper in the HTML output
toHTML: function(opts) {
return this.getInnerHTML(opts);
}
}
});
Ged.editor.addComponents('<mjml><mj-body><mj-section></mj-section></mj-body></mjml>')
...
Something I'm noticing is that:
mjml
tags within my div#gjs
container JSX code, the mjml
tags are not being included in the DOM:
<div id="gjs" style={{ overflow: 'hidden', height: '100%' }}>
<mjml>
<mj-body>
</mj-body>
</mjml>
</div>
Ged.editor.addComponents('<mjml><mj-body><mj-section></mj-section></mj-body></mjml>')
approach is also not working:Here is how ChatGPT 4 helped me:
useEffect(() => {
if (editorContainerRef.current !== null) {
const editor = grapesjs.init({
container: editorContainerRef.current,
plugins: [grapesjsMjml],
pluginsOpts: {
[grapesjsMjml]: {},
},
storageManager: false,
});
// Set the initial content of the canvas
const defaultContent = `
<mjml>
<mj-body>
</mj-body>
</mjml>`;
editor.setComponents(defaultContent);
return () => {
editor.destroy();
};
}
}, [editorContainerRef]);
// Set the initial content of the canvas const defaultContent = `
`; editor.setComponents(defaultContent);
This helped me too. Thank you so much
But if we remove storagManager: false, the blocks are not adding. Why?
Hey guys,
I have the same error on my grapesjs mjml installation.
Here it's my code
`const NewEmailEditor = () => { const editorRef = useRef(null)
useEffect(() => {
const editor = grapesjs.init({
fromElement: true,
container: '#email-editor',
plugins: [grapesJSMJML],
pluginsOpts: {
[grapesJSMJML]: {},
},
height: '100vh',
})
const defaultContent = '
<mjml>
<mj-body>
</mj-body>
</mjml>'
editor.setComponents(defaultContent)
}, [])
return <div id="email-editor" />
}
export default NewEmailEditor`
I can see that the HTML is correct (meaning that it has the mjml body, section etc) however when I try to add a component I'm getting error
{ "errors": [ "Target collection not found", "Component not draggable, acceptable by [[data-gjs-type=\"mj-body\"], [data-gjs-type=\"mj-wrapper\"]]" ], "model": { "tagName": "mj-section", "type": "mj-section" }, "context": "sorter", "level": "warning" }
Hello and thanks for grapesjs!
I'm running into an issue using the MJML plugin with grapesjs inside a react app.
The canvas loads but I'm not able to drop any blocks into the canvas area. Not using the the mjml plugin the canvas loads and allows for blocks to be dropped into the canvas area.
I get the following warning when using grapesjs-mjml plugin:
Here is my setup, maybe I'm missing something simple.