everbee-landing-page-builder 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.js +20 -0
- package/.storybook/preview.js +13 -0
- package/package.json +55 -0
- package/rollup.config.js +42 -0
- package/src/components/Editor.js +552 -0
- package/src/components/PageBuilder.js +5 -0
- package/src/index.js +1 -0
- package/src/stories/PageBuilder.stories.js +9 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
|
|
2
|
+
const config = {
|
|
3
|
+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
|
4
|
+
addons: [
|
|
5
|
+
"@storybook/addon-webpack5-compiler-swc",
|
|
6
|
+
"@storybook/addon-onboarding",
|
|
7
|
+
"@storybook/addon-links",
|
|
8
|
+
"@storybook/addon-essentials",
|
|
9
|
+
"@chromatic-com/storybook",
|
|
10
|
+
"@storybook/addon-interactions",
|
|
11
|
+
],
|
|
12
|
+
framework: {
|
|
13
|
+
name: "@storybook/react-webpack5",
|
|
14
|
+
options: {},
|
|
15
|
+
},
|
|
16
|
+
docs: {
|
|
17
|
+
autodocs: "tag",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "everbee-landing-page-builder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"storybook": "storybook dev -p 6006",
|
|
9
|
+
"build-storybook": "storybook build",
|
|
10
|
+
"build-lib": "rollup -c"
|
|
11
|
+
},
|
|
12
|
+
"author": "abhay-everbee",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@babel/preset-react": "^7.24.1",
|
|
16
|
+
"@chromatic-com/storybook": "^1.3.1",
|
|
17
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
18
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
19
|
+
"@storybook/addon-essentials": "^8.0.6",
|
|
20
|
+
"@storybook/addon-interactions": "^8.0.6",
|
|
21
|
+
"@storybook/addon-links": "^8.0.6",
|
|
22
|
+
"@storybook/addon-onboarding": "^8.0.6",
|
|
23
|
+
"@storybook/addon-webpack5-compiler-swc": "^1.0.2",
|
|
24
|
+
"@storybook/blocks": "^8.0.6",
|
|
25
|
+
"@storybook/react": "^8.0.6",
|
|
26
|
+
"@storybook/react-webpack5": "^8.0.6",
|
|
27
|
+
"@storybook/test": "^8.0.6",
|
|
28
|
+
"grapesjs": "^0.21.9",
|
|
29
|
+
"grapesjs-blocks-basic": "^1.0.2",
|
|
30
|
+
"grapesjs-custom-code": "^1.0.2",
|
|
31
|
+
"grapesjs-navbar": "^1.0.2",
|
|
32
|
+
"grapesjs-parser-postcss": "^1.0.3",
|
|
33
|
+
"grapesjs-plugin-export": "^1.0.12",
|
|
34
|
+
"grapesjs-plugin-forms": "^2.0.6",
|
|
35
|
+
"grapesjs-preset-webpage": "^1.0.3",
|
|
36
|
+
"grapesjs-style-bg": "^2.0.2",
|
|
37
|
+
"grapesjs-tabs": "^1.0.6",
|
|
38
|
+
"grapesjs-tooltip": "^0.1.8",
|
|
39
|
+
"grapesjs-tui-image-editor": "^1.0.2",
|
|
40
|
+
"grapesjs-typed": "^2.0.1",
|
|
41
|
+
"prop-types": "^15.8.1",
|
|
42
|
+
"react": "^18.2.0",
|
|
43
|
+
"react-dom": "^18.2.0",
|
|
44
|
+
"rollup": "^2.79.1",
|
|
45
|
+
"rollup-plugin-babel": "^4.4.0",
|
|
46
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
47
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
48
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
49
|
+
"storybook": "^8.0.6"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": "^18.2.0",
|
|
53
|
+
"react-dom": "^18.2.0"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import babel from "rollup-plugin-babel";
|
|
2
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
3
|
+
import external from "rollup-plugin-peer-deps-external";
|
|
4
|
+
import { terser } from "rollup-plugin-terser";
|
|
5
|
+
import postcss from 'rollup-plugin-postcss';
|
|
6
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
{
|
|
10
|
+
context: 'window',
|
|
11
|
+
input: "./src/index.js",
|
|
12
|
+
external: ['dist/css/grapes.min.css'],
|
|
13
|
+
output: [
|
|
14
|
+
{
|
|
15
|
+
file: "dist/index.js",
|
|
16
|
+
format: "cjs",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
file: "dist/index.es.js",
|
|
20
|
+
format: "es",
|
|
21
|
+
exports: "named",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
plugins: [
|
|
25
|
+
babel({
|
|
26
|
+
exclude: "node_modules/**",
|
|
27
|
+
presets: ["@babel/preset-react"],
|
|
28
|
+
}),
|
|
29
|
+
external(),
|
|
30
|
+
resolve(),
|
|
31
|
+
terser(),
|
|
32
|
+
postcss({
|
|
33
|
+
// Include any postcss plugins or options here
|
|
34
|
+
extract: true, // Extract CSS to a separate file
|
|
35
|
+
minimize: true, // Minimize CSS output
|
|
36
|
+
modules: true, // Enable CSS modules if needed
|
|
37
|
+
// Add any other postcss plugins or options here as needed
|
|
38
|
+
}),
|
|
39
|
+
commonjs()
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import "grapesjs/dist/css/grapes.min.css";
|
|
3
|
+
import grapesjs from "grapesjs";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import grapesjsPresetWebpage from "grapesjs-preset-webpage";
|
|
6
|
+
import blocksBasic from "grapesjs-blocks-basic";
|
|
7
|
+
import tuiImageEditor from "grapesjs-tui-image-editor";
|
|
8
|
+
import navbarEditor from "grapesjs-navbar";
|
|
9
|
+
import typed from "grapesjs-typed";
|
|
10
|
+
import gjsForms from "grapesjs-plugin-forms";
|
|
11
|
+
|
|
12
|
+
const LandingPageEditor = ({ cssContent = "", htmlContent = "" }) => {
|
|
13
|
+
// const [htmlComponents, setHtmlComponents] = useState(false);
|
|
14
|
+
const [editorState, setEditorState] = useState(null);
|
|
15
|
+
const script = function () {
|
|
16
|
+
alert("This is a custom component test");
|
|
17
|
+
// `this` is bound to the component element
|
|
18
|
+
console.log("the element", this);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const mergeTags = editor => {
|
|
22
|
+
const newType = 'var-placeholder';
|
|
23
|
+
// Define a component with `textable` property
|
|
24
|
+
editor.DomComponents.addType(newType, {
|
|
25
|
+
model: {
|
|
26
|
+
defaults: {
|
|
27
|
+
textable: 1,
|
|
28
|
+
placeholder: 'VARIABLE-1',
|
|
29
|
+
},
|
|
30
|
+
toHTML() {
|
|
31
|
+
return `{{ ${this.get('placeholder')} }}`;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
// The view below it's just an example of creating a different UX
|
|
35
|
+
view: {
|
|
36
|
+
tagName: 'span',
|
|
37
|
+
events: {
|
|
38
|
+
'change': 'updatePlh',
|
|
39
|
+
},
|
|
40
|
+
// Update the model once the select is changed
|
|
41
|
+
updatePlh(ev) {
|
|
42
|
+
this.model.set({ placeholder: ev.target.value });
|
|
43
|
+
this.updateProps();
|
|
44
|
+
},
|
|
45
|
+
// When we blur from a TextComponent, all its children components are
|
|
46
|
+
// flattened via innerHTML and parsed by the editor. So to keep the state
|
|
47
|
+
// of our props in sync with the model so we need to expose props in the HTML
|
|
48
|
+
updateProps() {
|
|
49
|
+
const { el, model } = this;
|
|
50
|
+
el.setAttribute('data-gjs-placeholder', model.get('placeholder'));
|
|
51
|
+
},
|
|
52
|
+
onRender() {
|
|
53
|
+
const { model, el } = this;
|
|
54
|
+
const currentPlh = model.get('placeholder');
|
|
55
|
+
const select = document.createElement('select');
|
|
56
|
+
const options = [ 'VARIABLE-1', 'VARIABLE-2', 'VARIABLE-3' ];
|
|
57
|
+
select.innerHTML = options.map(item => `<option value="${item}" ${item === currentPlh ? 'selected' : ''}>
|
|
58
|
+
${item}
|
|
59
|
+
</option>`).join('');
|
|
60
|
+
while (el.firstChild) el.removeChild(el.firstChild);
|
|
61
|
+
el.appendChild(select);
|
|
62
|
+
select.setAttribute('style', 'padding: 5px; border-radius: 3px; border: none; -webkit-appearance: none;border: 2px solid black; background: aliceblue; border-radius:9px;');
|
|
63
|
+
this.updateProps();
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Use the component in blocks
|
|
69
|
+
editor.BlockManager.add(newType, {
|
|
70
|
+
label: 'Merge Tags',
|
|
71
|
+
content: { type: newType },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// const extendedTextType = 'extended-cmp'
|
|
75
|
+
// editor.Components.addType(extendedTextType, {
|
|
76
|
+
// extend: "text",
|
|
77
|
+
// model: {
|
|
78
|
+
// defaults: {
|
|
79
|
+
// tagName: 'p',
|
|
80
|
+
// style: {padding: "25px"},
|
|
81
|
+
// droppable: true,
|
|
82
|
+
// components: "Hello extended text component World!!"
|
|
83
|
+
// },
|
|
84
|
+
// },
|
|
85
|
+
// });
|
|
86
|
+
// editor.Blocks.add(extendedTextType, {
|
|
87
|
+
// label: 'Extended Text component',
|
|
88
|
+
// content: { type: extendedTextType },
|
|
89
|
+
// select: true,
|
|
90
|
+
// });
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
const myNewComponentTypes = editor => {
|
|
95
|
+
editor.DomComponents.addType('my-input-type', {
|
|
96
|
+
// Make the editor understand when to bind `my-input-type`
|
|
97
|
+
isComponent: el => el.tagName === 'INPUT',
|
|
98
|
+
|
|
99
|
+
// Model definition
|
|
100
|
+
model: {
|
|
101
|
+
// Default properties
|
|
102
|
+
defaults: {
|
|
103
|
+
tagName: 'input',
|
|
104
|
+
//draggable: 'form, form *', // Can be dropped only inside `form` elements
|
|
105
|
+
//droppable: false, // Can't drop other elements inside
|
|
106
|
+
attributes: { // Default attributes
|
|
107
|
+
type: 'text',
|
|
108
|
+
name: 'default-name',
|
|
109
|
+
placeholder: 'Insert text here',
|
|
110
|
+
},
|
|
111
|
+
traits: [
|
|
112
|
+
'name',
|
|
113
|
+
'placeholder',
|
|
114
|
+
{ type: 'checkbox', name: 'required' },
|
|
115
|
+
'id'
|
|
116
|
+
],
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Define a new custom component
|
|
122
|
+
editor.Components.addType("img-text-col", {
|
|
123
|
+
model: {
|
|
124
|
+
defaults: {
|
|
125
|
+
attributes: { class: 'cmp-css' },
|
|
126
|
+
components: `
|
|
127
|
+
<div type='wrapper'>
|
|
128
|
+
<div class='gjs-row'>
|
|
129
|
+
<div class='gjs-cell' style='text-align:center'>
|
|
130
|
+
I'm a text node
|
|
131
|
+
<span>Component with styles<span>
|
|
132
|
+
<div class="cmp-css-a">Component A</div>
|
|
133
|
+
<div class="cmp-css-b">Component B</div>
|
|
134
|
+
<input type='my-input-type' />
|
|
135
|
+
</div>
|
|
136
|
+
<div class='gjs-cell'><img alt="Image here"/></div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
`,
|
|
140
|
+
styles: `
|
|
141
|
+
.cmp-css { color: red }
|
|
142
|
+
.cmp-css-a { color: green }
|
|
143
|
+
.cmp-css-b { color: blue }
|
|
144
|
+
.gjs-row {display:flex;}
|
|
145
|
+
.gjs-cell {min-height:75px; flex-grow:1; flex-basis:100%; border:1px solid green; padding: 2px; }
|
|
146
|
+
@media (max-width: 992px) {
|
|
147
|
+
.cmp-css{ color: darkred; }
|
|
148
|
+
.cmp-css-a { color: darkgreen }
|
|
149
|
+
.cmp-css-b { color: darkblue }
|
|
150
|
+
}
|
|
151
|
+
`,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const btnScript = function(props) {
|
|
159
|
+
console.log('My lib options: ');
|
|
160
|
+
|
|
161
|
+
//console.log('My lib options: ',props);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
const btnss = (editor) => {
|
|
166
|
+
var newType = 'my-button';
|
|
167
|
+
editor.DomComponents.addType(newType, {
|
|
168
|
+
// ...
|
|
169
|
+
model: {
|
|
170
|
+
defaults: {
|
|
171
|
+
attributes: {itemsCounter: 0},
|
|
172
|
+
script: btnScript,
|
|
173
|
+
// Define traits, in order to change your properties
|
|
174
|
+
// traits: [
|
|
175
|
+
// {
|
|
176
|
+
// type: 'number',
|
|
177
|
+
// name: 'itemsCounter',
|
|
178
|
+
// changeProp: true,
|
|
179
|
+
// min: 0, // Minimum number value
|
|
180
|
+
// max: 100, // Maximum number value
|
|
181
|
+
// step: 1, // Number of steps
|
|
182
|
+
// }
|
|
183
|
+
// ],
|
|
184
|
+
},
|
|
185
|
+
toHTML() {
|
|
186
|
+
console.log('this',this);
|
|
187
|
+
return `<div>${this.get('itemsCounter')}</div>`;
|
|
188
|
+
},
|
|
189
|
+
'script-props': ['itemsCounter'],
|
|
190
|
+
},
|
|
191
|
+
view: {
|
|
192
|
+
// Be default, the tag of the element is the same of the model
|
|
193
|
+
tagName: 'div',
|
|
194
|
+
|
|
195
|
+
// Add easily component specific listeners with `events`
|
|
196
|
+
// Being component specific (eg. you can't attach here listeners to window)
|
|
197
|
+
// you don't need to care about removing them when the component is removed,
|
|
198
|
+
// they will be managed automatically by the editor
|
|
199
|
+
events: {
|
|
200
|
+
click: 'clickOnElement',
|
|
201
|
+
// You can also make use of event delegation
|
|
202
|
+
// and listen to events bubbled from some inner element
|
|
203
|
+
'dblclick .inner-el': 'innerElClick',
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
innerElClick(ev) {
|
|
207
|
+
ev.stopPropagation();
|
|
208
|
+
// ...
|
|
209
|
+
|
|
210
|
+
// If you need you can access the model from any function in the view
|
|
211
|
+
this.model.components('Update inner components');
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// On init you can create listeners, like in the model, or start some other
|
|
215
|
+
// function at the beginning
|
|
216
|
+
init({ model }) {
|
|
217
|
+
var itemCount = 0;
|
|
218
|
+
// Do something in view on model property change
|
|
219
|
+
this.listenTo(model, 'change:itemsCounter', this.handlePropChange);
|
|
220
|
+
this.on('change:attributes:itemsCounter', this.handlePropChange);
|
|
221
|
+
|
|
222
|
+
// If you attach listeners on outside objects remember to unbind
|
|
223
|
+
// them in `removed` function in order to avoid memory leaks
|
|
224
|
+
//this.onDocClick = this.onDocClick.bind(this);
|
|
225
|
+
document.addEventListener('click', this.onDocClick)
|
|
226
|
+
},
|
|
227
|
+
handlePropChange(e) {
|
|
228
|
+
console.log('handlePropChange',e);
|
|
229
|
+
const btn = this.el.querySelector('#add_to_cart');
|
|
230
|
+
btn.innerHTML = `+ ${e.get('itemsCounter')}`;
|
|
231
|
+
|
|
232
|
+
},
|
|
233
|
+
// Callback triggered when the element is removed from the canvas
|
|
234
|
+
removed() {
|
|
235
|
+
document.removeEventListener('click', this.onDocClick)
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// Do something with the content once the element is rendered.
|
|
239
|
+
// The DOM element is passed as `el` in the argument object,
|
|
240
|
+
// but you can access it from any function via `this.el`
|
|
241
|
+
async onRender({ el, model }) {
|
|
242
|
+
var itemsCounter = this.model.attributes.itemsCounter;
|
|
243
|
+
const btn = document.createElement('button');
|
|
244
|
+
const label = document.createElement('button');
|
|
245
|
+
label.innerHTML = `${itemsCounter}`;
|
|
246
|
+
label.id='add_to_cart'
|
|
247
|
+
btn.value = '+';
|
|
248
|
+
btn.innerHTML = '+';
|
|
249
|
+
btn.addEventListener('click', () => {
|
|
250
|
+
var itemsCounter = this.model.attributes.itemsCounter;
|
|
251
|
+
this.model.attributes.itemsCounter = itemsCounter + 1;
|
|
252
|
+
const btn = this.el.querySelector('#add_to_cart');
|
|
253
|
+
btn.innerHTML = ` ${this.model.attributes.itemsCounter}`;
|
|
254
|
+
|
|
255
|
+
//const component = editor.getSelected();
|
|
256
|
+
// console.log(component.getTrait('itemsCounter').props());
|
|
257
|
+
// const component = editor.getSelected(); // Component selected in canvas
|
|
258
|
+
// const traits = component.get('traits');
|
|
259
|
+
//var trait = component.getTrait('itemsCounter');
|
|
260
|
+
//console.log('trait',trait);
|
|
261
|
+
//trait.set('itemsCounter', parseInt(this.model.attributes.itemsCounter) + 1);
|
|
262
|
+
//trait.set({name: 'itemsCounter', itemsCounter: parseInt(this.model.attributes.itemsCounter) + 1});
|
|
263
|
+
//console.log('after update', this.model.getTrait('itemsCounter'));
|
|
264
|
+
|
|
265
|
+
//this.model.dispatchEvent(new CustomEvent('change'));
|
|
266
|
+
// trait.attributes.itemsCounter = parseInt(this.model.attributes.itemsCounter) + 1;
|
|
267
|
+
});
|
|
268
|
+
el.appendChild(btn);
|
|
269
|
+
el.appendChild(label);
|
|
270
|
+
|
|
271
|
+
const btn1 = document.createElement('button');
|
|
272
|
+
btn1.id='remove_to_cart'
|
|
273
|
+
btn1.value = '-';
|
|
274
|
+
btn1.innerHTML = '-';;
|
|
275
|
+
btn1.addEventListener('click', () => {
|
|
276
|
+
var itemsCounter = this.model.attributes.itemsCounter;
|
|
277
|
+
this.model.attributes.itemsCounter = itemsCounter - 1;
|
|
278
|
+
const btn = this.el.querySelector('#add_to_cart');
|
|
279
|
+
btn.innerHTML = ` ${this.model.attributes.itemsCounter}`;
|
|
280
|
+
});
|
|
281
|
+
el.appendChild(btn1);
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
fetch("https://email.everbee.com/api/v1/shops/best_products?category=newest", {
|
|
285
|
+
method: "GET",headers: {
|
|
286
|
+
'Authorization-Token': 'b86e5a9b-8678-41fe-b3b9-0d156ad45938',
|
|
287
|
+
}, }).then((response) => response.json())
|
|
288
|
+
.then((json) =>{
|
|
289
|
+
console.log(json);
|
|
290
|
+
json.products.map((product,index)=>{
|
|
291
|
+
var child = document.createElement('img');
|
|
292
|
+
child.title = `Product ${index}`;
|
|
293
|
+
child.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3R5bGU9ImZpbGw6IHJnYmEoMCwwLDAsMC4xNSk7IHRyYW5zZm9ybTogc2NhbGUoMC43NSkiPgogICAgICAgIDxwYXRoIGQ9Ik04LjUgMTMuNWwyLjUgMyAzLjUtNC41IDQuNSA2SDVtMTYgMVY1YTIgMiAwIDAgMC0yLTJINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMnoiPjwvcGF0aD4KICAgICAgPC9zdmc+'
|
|
294
|
+
el.appendChild(child);
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// Example of async content
|
|
300
|
+
// async onRender({ el, model }) {
|
|
301
|
+
// const asyncContent = await fetchSomething({
|
|
302
|
+
// someDataFromModel: model.get('someData'),
|
|
303
|
+
// });
|
|
304
|
+
// // Remember, these changes exist only inside the editor canvas
|
|
305
|
+
// // None of the DOM change is stored in your template data,
|
|
306
|
+
// // if you need to store something, update the model properties
|
|
307
|
+
// el.appendChild(asyncContent);
|
|
308
|
+
// }
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Use the component in blocks
|
|
313
|
+
editor.BlockManager.add(newType, {
|
|
314
|
+
label: 'ADD to CART',
|
|
315
|
+
asd: '11',
|
|
316
|
+
content: { type: newType, itemsCounter: 0 },
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
console.log("useEff");
|
|
323
|
+
const editor = grapesjs.init({
|
|
324
|
+
richTextEditor: {
|
|
325
|
+
actions: [
|
|
326
|
+
'bold',
|
|
327
|
+
'italic',
|
|
328
|
+
'underline',
|
|
329
|
+
'strikethrough',
|
|
330
|
+
'link',
|
|
331
|
+
'wrap',
|
|
332
|
+
{
|
|
333
|
+
name: 'custom-vars',
|
|
334
|
+
icon: `<select class="gjs-rte-action" style='min-width:100px;'>
|
|
335
|
+
<option value="">- Select -</option>
|
|
336
|
+
<option value="{{firstname}}">FirstName</option>
|
|
337
|
+
<option value="{{lastname}}">LastName</option>
|
|
338
|
+
<option value="{{age}}">Age</option>
|
|
339
|
+
</select>`,
|
|
340
|
+
// Bind the 'result' on 'change' listener
|
|
341
|
+
event: 'change',
|
|
342
|
+
result: (rte, action) => rte.insertHTML(action.btn.firstChild.value),
|
|
343
|
+
// Reset the select on change
|
|
344
|
+
update: (rte, action) => {
|
|
345
|
+
action.btn.firstChild.value = ''
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
container: "#gjs",
|
|
351
|
+
components: htmlContent,
|
|
352
|
+
style: cssContent,
|
|
353
|
+
height: "700px",
|
|
354
|
+
width: "100%",
|
|
355
|
+
plugins: [
|
|
356
|
+
blocksBasic,
|
|
357
|
+
grapesjsPresetWebpage,
|
|
358
|
+
tuiImageEditor,
|
|
359
|
+
navbarEditor,
|
|
360
|
+
typed,
|
|
361
|
+
gjsForms,
|
|
362
|
+
myNewComponentTypes,
|
|
363
|
+
mergeTags,
|
|
364
|
+
btnss
|
|
365
|
+
],
|
|
366
|
+
storageManager: false,
|
|
367
|
+
deviceManager: {
|
|
368
|
+
devices: [
|
|
369
|
+
{
|
|
370
|
+
id: "desktop",
|
|
371
|
+
name: "Desktop",
|
|
372
|
+
width: "",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: "tablet",
|
|
376
|
+
name: "Tablet",
|
|
377
|
+
width: "768px",
|
|
378
|
+
widthMedia: "992px",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: "mobilePortrait",
|
|
382
|
+
name: "Mobile portrait",
|
|
383
|
+
width: "320px",
|
|
384
|
+
widthMedia: "575px",
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
pluginsOpts: {
|
|
389
|
+
[blocksBasic]: { flexGrid: true, labelImage: "null" },
|
|
390
|
+
[grapesjsPresetWebpage]: {},
|
|
391
|
+
[tuiImageEditor]: {
|
|
392
|
+
script: [
|
|
393
|
+
// 'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.7/fabric.min.js',
|
|
394
|
+
"https://uicdn.toast.com/tui.code-snippet/v1.5.2/tui-code-snippet.min.js",
|
|
395
|
+
"https://uicdn.toast.com/tui-color-picker/v2.2.7/tui-color-picker.min.js",
|
|
396
|
+
"https://uicdn.toast.com/tui-image-editor/v3.15.2/tui-image-editor.min.js",
|
|
397
|
+
],
|
|
398
|
+
style: [
|
|
399
|
+
"https://uicdn.toast.com/tui-color-picker/v2.2.7/tui-color-picker.min.css",
|
|
400
|
+
"https://uicdn.toast.com/tui-image-editor/v3.15.2/tui-image-editor.min.css",
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
gjsForms: { category: "Forms" },
|
|
404
|
+
[typed]: {
|
|
405
|
+
block: {
|
|
406
|
+
category: "Extra",
|
|
407
|
+
content: {
|
|
408
|
+
type: "typed",
|
|
409
|
+
"type-speed": 40,
|
|
410
|
+
strings: ["Text row one", "Text row two", "Text row three"],
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
[grapesjsPresetWebpage]: {
|
|
415
|
+
modalImportTitle: "Import Template",
|
|
416
|
+
modalImportLabel:
|
|
417
|
+
'<div style="margin-bottom: 10px; font-size: 13px;">Paste here your HTML/CSS and click Import</div>',
|
|
418
|
+
modalImportContent: function (editor) {
|
|
419
|
+
return editor.getHtml() + "<style>" + editor.getCss() + "</style>";
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
// Define a new custom component
|
|
427
|
+
editor.Components.addType("comp-with-js", {
|
|
428
|
+
model: {
|
|
429
|
+
defaults: {
|
|
430
|
+
script,
|
|
431
|
+
// Add some style, just to make the component visible
|
|
432
|
+
style: {
|
|
433
|
+
width: "100px",
|
|
434
|
+
height: "100px",
|
|
435
|
+
background: "green",
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Create a block for the component, so we can drop it easily
|
|
442
|
+
editor.Blocks.add("test-block", {
|
|
443
|
+
label: "Custom Component test",
|
|
444
|
+
attributes: { class: "fa fa-text" },
|
|
445
|
+
content: [
|
|
446
|
+
{ type: "comp-with-js" },
|
|
447
|
+
`<div style="color:red">Hello this is a custom component. <div>Hello this is a custom component</div> </div>`,
|
|
448
|
+
],
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
editor.Blocks.add("img-text-col-block", {
|
|
452
|
+
label: "Custom Div",
|
|
453
|
+
attributes: { class: "fa fa-text" },
|
|
454
|
+
content: [
|
|
455
|
+
{ type: 'img-text-col'},
|
|
456
|
+
//`<div type='wrapper'><div class='gjs-row'> <div class='gjs-cell'> I'm a text node <input/> </div> <div class='gjs-cell'><img alt="Image here"/></div> </div></div>`
|
|
457
|
+
],
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
// editor.addComponents(`<div>
|
|
462
|
+
// <span title="foo" >Hello world!!!</span>
|
|
463
|
+
// <span title="foo" type='var-placeholder'>{{ VARIABLE-1 }}</span>
|
|
464
|
+
// </div>`);
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
setEditorState(editor);
|
|
468
|
+
return () => {};
|
|
469
|
+
}, []);
|
|
470
|
+
|
|
471
|
+
const downloadEditor = () => {
|
|
472
|
+
const jsonData = {
|
|
473
|
+
html: editorState.getHtml(),
|
|
474
|
+
css: editorState.getCss(),
|
|
475
|
+
components: editorState.getComponents(),
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const html = editorState.getHtml();
|
|
479
|
+
console.log("everything", jsonData);
|
|
480
|
+
console.log("html editor", html);
|
|
481
|
+
};
|
|
482
|
+
var htmlContent1 = {
|
|
483
|
+
"html": "<body id=\"igmq\"><div id=\"i4af\">Hi {{ VARIABLE-1 }},<br/>How are you<br/><br/>Thanks,<br/>Dave</div></body>",
|
|
484
|
+
"css": "* { box-sizing: border-box; } body {margin: 0;}#i4af{padding:10px;}",
|
|
485
|
+
"components": [
|
|
486
|
+
{
|
|
487
|
+
"type": "text",
|
|
488
|
+
"attributes": {
|
|
489
|
+
"id": "i4af"
|
|
490
|
+
},
|
|
491
|
+
"components": [
|
|
492
|
+
{
|
|
493
|
+
"type": "textnode",
|
|
494
|
+
"content": "Hi "
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"tagName": "span",
|
|
498
|
+
"type": "var-placeholder",
|
|
499
|
+
"attributes": {
|
|
500
|
+
"id": "ix3w"
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
"type": "textnode",
|
|
505
|
+
"content": ","
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"tagName": "br",
|
|
509
|
+
"void": true
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"type": "textnode",
|
|
513
|
+
"content": "How are you"
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
"tagName": "br",
|
|
517
|
+
"void": true
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"tagName": "br",
|
|
521
|
+
"void": true
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"type": "textnode",
|
|
525
|
+
"content": "Thanks,"
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
"tagName": "br",
|
|
529
|
+
"void": true
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
"type": "textnode",
|
|
533
|
+
"content": "Dave"
|
|
534
|
+
}
|
|
535
|
+
]
|
|
536
|
+
}
|
|
537
|
+
]
|
|
538
|
+
}
|
|
539
|
+
const importHTML = () => {
|
|
540
|
+
editorState?.setComponents(htmlContent1);
|
|
541
|
+
};
|
|
542
|
+
return (
|
|
543
|
+
<>
|
|
544
|
+
<div id="gjs"></div>
|
|
545
|
+
<div id="blocks"></div>
|
|
546
|
+
<button onClick={downloadEditor}>Console details</button>
|
|
547
|
+
<button onClick={importHTML(htmlContent)}>import html</button>
|
|
548
|
+
</>
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
export default LandingPageEditor;
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/PageBuilder'
|