@codeparticle/strapi-plugin-grapejs 1.3.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/README.md +48 -0
- package/admin/src/components/InputGrape/assets-view-override.js +87 -0
- package/admin/src/components/InputGrape/components.js +123 -0
- package/admin/src/components/InputGrape/edit-code-btn.js +70 -0
- package/admin/src/components/InputGrape/index.js +361 -0
- package/admin/src/components/InputGrape/storage.js +18 -0
- package/admin/src/components/InputGrapeNewsletter/index.js +9 -0
- package/admin/src/containers/App/index.js +25 -0
- package/admin/src/containers/HomePage/index.js +18 -0
- package/admin/src/index.js +52 -0
- package/admin/src/lifecycles.js +3 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/translations/ar.json +1 -0
- package/admin/src/translations/cs.json +1 -0
- package/admin/src/translations/de.json +1 -0
- package/admin/src/translations/en.json +1 -0
- package/admin/src/translations/es.json +1 -0
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/translations/index.js +43 -0
- package/admin/src/translations/it.json +1 -0
- package/admin/src/translations/ko.json +1 -0
- package/admin/src/translations/ms.json +1 -0
- package/admin/src/translations/nl.json +1 -0
- package/admin/src/translations/pl.json +1 -0
- package/admin/src/translations/pt-BR.json +1 -0
- package/admin/src/translations/pt.json +1 -0
- package/admin/src/translations/ru.json +1 -0
- package/admin/src/translations/sk.json +1 -0
- package/admin/src/translations/tr.json +1 -0
- package/admin/src/translations/vi.json +1 -0
- package/admin/src/translations/zh-Hans.json +1 -0
- package/admin/src/translations/zh.json +1 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/package.json +72 -0
- package/pnpm-workspace.yaml +6 -0
- package/server/controllers/grape.js +7 -0
- package/server/controllers/index.js +7 -0
- package/server/index.js +13 -0
- package/server/register.js +29 -0
- package/server/routes/index.js +11 -0
- package/server/services/grape.js +1 -0
- package/server/services/index.js +7 -0
- package/strapi-admin.js +1 -0
- package/strapi-server.js +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# README #
|
|
2
|
+
|
|
3
|
+
To run this plugin it must be installed into a strapi application.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Please refer to https://codeparticle.atlassian.net/wiki/spaces/CP/pages/2528313373/Plugin+GrapeJS.
|
|
8
|
+
|
|
9
|
+
## Dev
|
|
10
|
+
|
|
11
|
+
You need to create a new strapi project or just use an existing example one:
|
|
12
|
+
```
|
|
13
|
+
pnpm run dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Publishing
|
|
17
|
+
|
|
18
|
+
Publishing should already be setup. Just follow these steps to publish the project:
|
|
19
|
+
|
|
20
|
+
- After code merged to `main/master`
|
|
21
|
+
- Checkout the `main/master` branch
|
|
22
|
+
- Run `pnpm version [patch|minor|major]`
|
|
23
|
+
- Push to remote with `git push --tags` to trigger the tag pipeline
|
|
24
|
+
|
|
25
|
+
## Modifying GrapeJS editor when it is initialized
|
|
26
|
+
|
|
27
|
+
Create config.js file in `admin/src/config.js`, then add the default code exported from that file in strapi-admin and set the `window.onGrapeInit` function, which takes as an argument the `editor`, which is the grapeJS editor object.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
```js
|
|
31
|
+
// File where we can initialize front end code for GrapeJS plugin
|
|
32
|
+
|
|
33
|
+
// Refer to https://grapesjs.com/docs for editor docs
|
|
34
|
+
window.onGrapeInit = (editor) => {
|
|
35
|
+
const { BlockManager } = editor;
|
|
36
|
+
|
|
37
|
+
// 'my-first-block' is the ID of the block
|
|
38
|
+
BlockManager.add('my-first-block', {
|
|
39
|
+
label: 'Simple block',
|
|
40
|
+
content: '<div class="my-block">This is a simple block</div>',
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Default code of the config.js file in strapi-admin
|
|
45
|
+
export const LOGIN_LOGO = null;
|
|
46
|
+
export const SHOW_TUTORIALS = true;
|
|
47
|
+
export const SETTINGS_BASE_URL = '/settings';
|
|
48
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import debounce from 'lodash/debounce';
|
|
2
|
+
|
|
3
|
+
const INPUT_DEBOUNCE_TIME = 300;
|
|
4
|
+
|
|
5
|
+
export const assetsViewOverride = (editor, onSearch) => {
|
|
6
|
+
// Render so AssetsView() can be defined after the timeout
|
|
7
|
+
editor.AssetManager.render();
|
|
8
|
+
|
|
9
|
+
setTimeout(() => {
|
|
10
|
+
const view = editor.AssetManager.AssetsView();
|
|
11
|
+
|
|
12
|
+
// Overring the template to add a search input to the html
|
|
13
|
+
// eslint-disable-next-line func-names
|
|
14
|
+
view.template = function template({ pfx, ppfx, em }) {
|
|
15
|
+
let form = '';
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line react/no-this-in-sfc
|
|
18
|
+
if (this.config.showUrlInput) {
|
|
19
|
+
form = `
|
|
20
|
+
<form class="${pfx}add-asset">
|
|
21
|
+
<div class="${ppfx}field ${pfx}add-field">
|
|
22
|
+
<input placeholder="${em && em.t('assetManager.inputPlh')}"/>
|
|
23
|
+
</div>
|
|
24
|
+
<button class="${ppfx}btn-prim">${em && em.t('assetManager.addButton')}</button>
|
|
25
|
+
<div style="clear:both"></div>
|
|
26
|
+
</form>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `
|
|
31
|
+
<div class="${pfx}assets-cont">
|
|
32
|
+
<div class="${pfx}assets-header">
|
|
33
|
+
${form}
|
|
34
|
+
<div class="gjs-am-assets-search-container">
|
|
35
|
+
Search
|
|
36
|
+
<div class="gjs-field">
|
|
37
|
+
<input class="${pfx}assets-search" />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="${pfx}searching" style="display: none;">Searching...</div>
|
|
42
|
+
<div class="${pfx}assets" data-el="assets"></div>
|
|
43
|
+
<div style="clear:both"></div>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Override render to add the oninput event listener on the search input
|
|
49
|
+
// eslint-disable-next-line func-names
|
|
50
|
+
view.render = function render() {
|
|
51
|
+
const fuRendered = this.options.fu.render().el;
|
|
52
|
+
this.$el.empty();
|
|
53
|
+
this.$el.append(fuRendered).append(this.template(this));
|
|
54
|
+
this.el.className = `${this.ppfx}asset-manager`;
|
|
55
|
+
this.renderAssets();
|
|
56
|
+
this.rendered = 1;
|
|
57
|
+
|
|
58
|
+
const searchInput = this.$el.find('.gjs-am-assets-search')[0];
|
|
59
|
+
|
|
60
|
+
if (searchInput) {
|
|
61
|
+
const onInput = debounce(({ target: { value } }) => {
|
|
62
|
+
const searchingElement = this.$el.find('.gjs-am-searching')[0];
|
|
63
|
+
const assetsElement = this.$el.find('.gjs-am-assets')[0];
|
|
64
|
+
|
|
65
|
+
this.searching = true;
|
|
66
|
+
this.searchValue = value;
|
|
67
|
+
searchingElement.style.display = 'block';
|
|
68
|
+
assetsElement.style.display = 'none';
|
|
69
|
+
|
|
70
|
+
const onFinishSearch = () => {
|
|
71
|
+
this.searching = false;
|
|
72
|
+
searchingElement.style.display = 'none';
|
|
73
|
+
assetsElement.style.display = 'flex';
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
onSearch(value).then(onFinishSearch).catch(onFinishSearch);
|
|
77
|
+
}, INPUT_DEBOUNCE_TIME);
|
|
78
|
+
|
|
79
|
+
searchInput.oninput = onInput;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return this;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
view.render();
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
const Wrapper = styled.div`
|
|
4
|
+
margin-bottom: 23px;
|
|
5
|
+
|
|
6
|
+
& > label {
|
|
7
|
+
color: ${({ theme }) => theme.colors.neutral1000};
|
|
8
|
+
display: block;
|
|
9
|
+
font-size: 0.75rem;
|
|
10
|
+
font-weight: 600;
|
|
11
|
+
line-height: 1.33;
|
|
12
|
+
margin-bottom: 4px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.gjs-cv-canvas {
|
|
16
|
+
width: 75%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.gjs {
|
|
20
|
+
border: 1px solid #E3E9F3;
|
|
21
|
+
border-radius: 2px;
|
|
22
|
+
height: 300px;
|
|
23
|
+
|
|
24
|
+
&.fullscreen {
|
|
25
|
+
bottom: 0;
|
|
26
|
+
height: auto !important;
|
|
27
|
+
left: 0;
|
|
28
|
+
position: fixed;
|
|
29
|
+
right: 0;
|
|
30
|
+
top: 0;
|
|
31
|
+
width: auto !important;
|
|
32
|
+
z-index: 999999;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.gjs-am-add-asset .gjs-btn-prim {
|
|
36
|
+
font-size: 0.9rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.gjs-pn-options {
|
|
40
|
+
right: 25%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.gjs-pn-views {
|
|
44
|
+
width: 25%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.gjs-pn-views-container {
|
|
48
|
+
height: calc(100% - 42px);
|
|
49
|
+
margin-top: 42px;
|
|
50
|
+
padding-top: 0;
|
|
51
|
+
width: 25%;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.gjs-pn-btn {
|
|
55
|
+
font-size: 18px !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.fa {
|
|
59
|
+
font-family: FontAwesome !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.gjs-pn-btn {
|
|
63
|
+
align-items: center;
|
|
64
|
+
display: flex;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.gjs-layer-move {
|
|
69
|
+
padding: 5px 7px 5px 5px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.gjs-layer-caret {
|
|
73
|
+
padding: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.gjs-layer-vis {
|
|
77
|
+
padding-top: 9px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.gjs-block {
|
|
81
|
+
display: flex;
|
|
82
|
+
font-size: 1em;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
padding: 14px !important;
|
|
85
|
+
|
|
86
|
+
&::before {
|
|
87
|
+
flex: 1;
|
|
88
|
+
font-size: 3em !important;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&.fa::before {
|
|
92
|
+
align-items: center;
|
|
93
|
+
display: flex;
|
|
94
|
+
font: normal normal normal 2.5em FontAwesome !important;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
margin-bottom: 10px;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.sp-container {
|
|
101
|
+
position: fixed !important;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.gjs-am-assets {
|
|
105
|
+
height: 230px !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.gjs-am-assets-search-container {
|
|
109
|
+
align-items: center;
|
|
110
|
+
display: flex;
|
|
111
|
+
margin-top: 10px;
|
|
112
|
+
|
|
113
|
+
.gjs-field {
|
|
114
|
+
flex: 1;
|
|
115
|
+
margin-left: 10px;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
Wrapper,
|
|
123
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const addEditCodeBtn = (editor) => {
|
|
2
|
+
const pfx = editor.getConfig().stylePrefix;
|
|
3
|
+
const modal = editor.Modal;
|
|
4
|
+
const cmdm = editor.Commands;
|
|
5
|
+
const codeViewer = editor.CodeManager.getViewer('CodeMirror').clone();
|
|
6
|
+
const pnm = editor.Panels;
|
|
7
|
+
const container = document.createElement('div');
|
|
8
|
+
const btnEdit = document.createElement('button');
|
|
9
|
+
|
|
10
|
+
codeViewer.set({
|
|
11
|
+
codeName: 'htmlmixed',
|
|
12
|
+
readOnly: 0,
|
|
13
|
+
theme: 'hopscotch',
|
|
14
|
+
autoBeautify: true,
|
|
15
|
+
autoCloseTags: true,
|
|
16
|
+
autoCloseBrackets: true,
|
|
17
|
+
lineWrapping: true,
|
|
18
|
+
styleActiveLine: true,
|
|
19
|
+
smartIndent: true,
|
|
20
|
+
indentWithTabs: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
btnEdit.innerHTML = 'Edit';
|
|
24
|
+
btnEdit.className = `${pfx}btn-prim ${pfx}btn-import`;
|
|
25
|
+
btnEdit.onclick = (e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const code = codeViewer.editor.getValue();
|
|
28
|
+
editor.DomComponents.getWrapper().set('content', '');
|
|
29
|
+
editor.setComponents(code.trim());
|
|
30
|
+
modal.close();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
cmdm.add('html-edit', {
|
|
34
|
+
run: (e, sender) => {
|
|
35
|
+
if (sender) {
|
|
36
|
+
sender.set('active', 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let viewer = codeViewer.editor;
|
|
40
|
+
modal.setTitle('Edit code');
|
|
41
|
+
|
|
42
|
+
if (!viewer) {
|
|
43
|
+
const txtarea = document.createElement('textarea');
|
|
44
|
+
container.appendChild(txtarea);
|
|
45
|
+
container.appendChild(btnEdit);
|
|
46
|
+
codeViewer.init(txtarea);
|
|
47
|
+
viewer = codeViewer.editor;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const InnerHtml = editor.getHtml();
|
|
51
|
+
const Css = editor.getCss();
|
|
52
|
+
modal.setContent('');
|
|
53
|
+
modal.setContent(container);
|
|
54
|
+
codeViewer.setContent(`${InnerHtml}\n<style>\n${Css}\n</style>`);
|
|
55
|
+
modal.open();
|
|
56
|
+
viewer.refresh();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
pnm.addButton('options', [
|
|
61
|
+
{
|
|
62
|
+
id: 'edit',
|
|
63
|
+
className: 'fa fa-edit',
|
|
64
|
+
command: 'html-edit',
|
|
65
|
+
attributes: {
|
|
66
|
+
title: 'Edit',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
};
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import grapesjs from 'grapesjs';
|
|
2
|
+
import 'grapesjs/dist/css/grapes.min.css';
|
|
3
|
+
import defaultPlugin from 'grapesjs-preset-webpage';
|
|
4
|
+
import blocksPlugin from 'grapesjs-blocks-basic';
|
|
5
|
+
import formsPlugin from 'grapesjs-plugin-forms';
|
|
6
|
+
import React, {
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { useIntl } from 'react-intl';
|
|
13
|
+
import { request, useCMEditViewDataManager } from '@strapi/helper-plugin';
|
|
14
|
+
import { useStableUniqueId } from 'react-stable-uniqueid';
|
|
15
|
+
import eotFont from 'grapesjs/dist/fonts/main-fonts.eot';
|
|
16
|
+
import woffFont from 'grapesjs/dist/fonts/main-fonts.woff';
|
|
17
|
+
import svgFont from 'grapesjs/dist/fonts/main-fonts.svg';
|
|
18
|
+
import ttfFont from 'grapesjs/dist/fonts/main-fonts.ttf';
|
|
19
|
+
import { Wrapper } from './components';
|
|
20
|
+
import { addEditCodeBtn } from './edit-code-btn';
|
|
21
|
+
import { assetsViewOverride } from './assets-view-override';
|
|
22
|
+
|
|
23
|
+
// Load grapejs font face (It usually loads from the grapejs.min.css, but with latest strapi it isnt working anymore)
|
|
24
|
+
const style = document.createElement('style');
|
|
25
|
+
style.innerHTML = `
|
|
26
|
+
@font-face {
|
|
27
|
+
font-family: 'font3336';
|
|
28
|
+
src: url('${eotFont}.eot?v=20');
|
|
29
|
+
src:
|
|
30
|
+
url('${woffFont}?v=20') format('woff'),
|
|
31
|
+
url('${ttfFont}.ttf?v=20') format('truetype'),
|
|
32
|
+
url('${svgFont}.svg?v=20') format('svg'),
|
|
33
|
+
url('${eotFont}.eot?v=20') format('embedded-opentype');
|
|
34
|
+
font-weight: normal;
|
|
35
|
+
font-style: normal;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
document.head.appendChild(style);
|
|
39
|
+
|
|
40
|
+
const getFullUrl = path => (/^http/.test(path) ? path : `${strapi.backendURL}${path}`);
|
|
41
|
+
|
|
42
|
+
let imageFetchListeners = [];
|
|
43
|
+
let globalImagesFetching = false;
|
|
44
|
+
|
|
45
|
+
const textCleanCanvas = 'Are you sure to clean the canvas?';
|
|
46
|
+
|
|
47
|
+
const getSaveData = (value, valueData) => {
|
|
48
|
+
// Parse initial value
|
|
49
|
+
const parsedValue = value || '';
|
|
50
|
+
// Remove <div>, </div>, and get all content before <style>
|
|
51
|
+
let html = parsedValue.replace('<div>', '');
|
|
52
|
+
const lastIndexOfStyle = parsedValue.lastIndexOf('<style>');
|
|
53
|
+
html = html.slice(0, lastIndexOfStyle);
|
|
54
|
+
// Get all content after <style> and remove </style>
|
|
55
|
+
const css = parsedValue.slice(lastIndexOfStyle).replace('</div>', '').replace('<style>', '').replace('</style>', '');
|
|
56
|
+
|
|
57
|
+
const isValueDataString = typeof valueData === 'string';
|
|
58
|
+
let savedData = isValueDataString ? {} : (valueData || {});
|
|
59
|
+
|
|
60
|
+
if (isValueDataString) {
|
|
61
|
+
try {
|
|
62
|
+
savedData = JSON.parse(valueData || '{}');
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// ignore error
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
components: savedData?.components || html.trim(),
|
|
70
|
+
style: savedData?.style || css.trim(),
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const InputGrapeJs = ({
|
|
75
|
+
name,
|
|
76
|
+
label,
|
|
77
|
+
intlLabel,
|
|
78
|
+
onChange,
|
|
79
|
+
plugin = defaultPlugin,
|
|
80
|
+
}) => {
|
|
81
|
+
const {
|
|
82
|
+
modifiedData: {
|
|
83
|
+
[`${name}_data`]: valueData,
|
|
84
|
+
value,
|
|
85
|
+
},
|
|
86
|
+
} = useCMEditViewDataManager();
|
|
87
|
+
|
|
88
|
+
const { formatMessage } = useIntl();
|
|
89
|
+
|
|
90
|
+
if (window.FontAwesome) {
|
|
91
|
+
// Fixes for FontAwesome icons
|
|
92
|
+
window.FontAwesome.config.autoReplaceSvg = 'nest';
|
|
93
|
+
window.FontAwesome.noAuto();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const baseUrl = `${strapi.backendURL}/upload`;
|
|
97
|
+
const getUrl = `${baseUrl}/files`;
|
|
98
|
+
|
|
99
|
+
const mounted = useRef(true);
|
|
100
|
+
const [editor, setEditor] = useState();
|
|
101
|
+
const containerId = useStableUniqueId('gjs-');
|
|
102
|
+
const lastSearchValue = useRef('');
|
|
103
|
+
const editorRef = useRef(editor);
|
|
104
|
+
editorRef.current = editor;
|
|
105
|
+
const valueRef = useRef(value);
|
|
106
|
+
valueRef.current = value;
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (editorRef.current) {
|
|
110
|
+
const editorData = JSON.stringify({
|
|
111
|
+
components: editorRef.current.getComponents(),
|
|
112
|
+
style: editorRef.current.getStyle(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (editorData !== valueData) {
|
|
116
|
+
const savedData = getSaveData(value, valueData);
|
|
117
|
+
|
|
118
|
+
editorRef.current.setComponents(savedData.components);
|
|
119
|
+
editorRef.current.setStyle(savedData.style);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}, [value, valueData]);
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const savedData = getSaveData(value, valueData);
|
|
126
|
+
|
|
127
|
+
// Initialize grapeJS
|
|
128
|
+
const newEditor = grapesjs.init({
|
|
129
|
+
canvas: {
|
|
130
|
+
styles: window.getGrapeJSStyles ? window.getGrapeJSStyles() : [],
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
height: '300px',
|
|
134
|
+
container: `#${containerId}`,
|
|
135
|
+
|
|
136
|
+
components: savedData.components,
|
|
137
|
+
style: savedData.style,
|
|
138
|
+
styleManager: {},
|
|
139
|
+
|
|
140
|
+
plugins: [plugin, blocksPlugin, formsPlugin, ...(window.getGrapeJSPlugins ? window.getGrapeJSPlugins() : [])],
|
|
141
|
+
pluginsOpts: {
|
|
142
|
+
[plugin]: {},
|
|
143
|
+
[blocksPlugin]: {},
|
|
144
|
+
[formsPlugin]: {},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Disable storage manager so edits dont persist for all input types
|
|
148
|
+
storageManager: { type: null },
|
|
149
|
+
|
|
150
|
+
assetManager: {
|
|
151
|
+
// custom: true,
|
|
152
|
+
// Custom upload method so we save images on Media Library from strapi
|
|
153
|
+
upload: baseUrl,
|
|
154
|
+
uploadName: 'files',
|
|
155
|
+
uploadFile: (e) => {
|
|
156
|
+
const files = Array.from(e.dataTransfer ? e.dataTransfer.files : e.target.files);
|
|
157
|
+
|
|
158
|
+
return Promise.all(files.map(async (file) => {
|
|
159
|
+
const formData = new FormData();
|
|
160
|
+
|
|
161
|
+
formData.append('files', file);
|
|
162
|
+
|
|
163
|
+
const response = await request(
|
|
164
|
+
'/upload',
|
|
165
|
+
{
|
|
166
|
+
body: formData,
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: {},
|
|
169
|
+
},
|
|
170
|
+
false,
|
|
171
|
+
false,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Add image to editor after upload
|
|
175
|
+
response.forEach(({ mime, name: imgName, url }) => {
|
|
176
|
+
if (`${mime}${imgName}${url}`.includes(lastSearchValue.current.toLowerCase())) {
|
|
177
|
+
newEditor.AssetManager.add(getFullUrl(url));
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}));
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const assetManager = newEditor.AssetManager;
|
|
186
|
+
const commands = newEditor.Commands;
|
|
187
|
+
const pnm = newEditor.Panels;
|
|
188
|
+
const optionsPanel = pnm.getPanel('options');
|
|
189
|
+
const cmdClear = 'canvas-clear';
|
|
190
|
+
|
|
191
|
+
// Remove default clear button because newsletter plugin doesn't add one, only webpage plugin does
|
|
192
|
+
optionsPanel.buttons.remove(cmdClear);
|
|
193
|
+
|
|
194
|
+
optionsPanel.buttons.add({
|
|
195
|
+
id: cmdClear,
|
|
196
|
+
className: 'fa fa-trash',
|
|
197
|
+
command: e => e.runCommand(cmdClear),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// eslint-disable-next-line no-restricted-globals, no-alert
|
|
201
|
+
commands.add(cmdClear, e => confirm(textCleanCanvas) && e.runCommand('core:canvas-clear'));
|
|
202
|
+
|
|
203
|
+
// Override fullscreen command (original one makes whole browser full screen)
|
|
204
|
+
commands.add('fullscreen', {
|
|
205
|
+
run: () => {
|
|
206
|
+
newEditor.getContainer().classList.add('fullscreen');
|
|
207
|
+
newEditor.refresh();
|
|
208
|
+
},
|
|
209
|
+
stop: () => {
|
|
210
|
+
newEditor.getContainer().classList.remove('fullscreen');
|
|
211
|
+
newEditor.refresh();
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const addFetchedImages = (images) => {
|
|
216
|
+
if (mounted.current) {
|
|
217
|
+
const existingModels = assetManager.getAll().models.slice();
|
|
218
|
+
|
|
219
|
+
existingModels.forEach(({ id }) => assetManager.remove(id));
|
|
220
|
+
|
|
221
|
+
// Map images and make sure there are no duplicates
|
|
222
|
+
images.reverse().forEach(({
|
|
223
|
+
created_at: createdAt,
|
|
224
|
+
height,
|
|
225
|
+
id,
|
|
226
|
+
mime,
|
|
227
|
+
name: imageName,
|
|
228
|
+
url,
|
|
229
|
+
width,
|
|
230
|
+
}) => {
|
|
231
|
+
assetManager.add({
|
|
232
|
+
createdAt,
|
|
233
|
+
height,
|
|
234
|
+
id,
|
|
235
|
+
mime,
|
|
236
|
+
name: imageName,
|
|
237
|
+
src: getFullUrl(url),
|
|
238
|
+
width,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const onSearch = (q = '') => new Promise((resolve, reject) => {
|
|
245
|
+
lastSearchValue.current = q;
|
|
246
|
+
|
|
247
|
+
request(getUrl, {
|
|
248
|
+
params: {
|
|
249
|
+
pageSize: 50,
|
|
250
|
+
_sort: 'updated_at:DESC',
|
|
251
|
+
'filters[$and][0][name][$contains]': q,
|
|
252
|
+
},
|
|
253
|
+
}).then(({ results: images }) => {
|
|
254
|
+
globalImagesFetching = false;
|
|
255
|
+
addFetchedImages(images);
|
|
256
|
+
|
|
257
|
+
imageFetchListeners.forEach(listener => listener(images, q));
|
|
258
|
+
imageFetchListeners = [];
|
|
259
|
+
|
|
260
|
+
resolve(images);
|
|
261
|
+
}).catch((err) => {
|
|
262
|
+
console.log('Error fetching images', err);
|
|
263
|
+
globalImagesFetching = false;
|
|
264
|
+
|
|
265
|
+
reject(err);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Fetch images from Media Library
|
|
270
|
+
if (!globalImagesFetching) {
|
|
271
|
+
globalImagesFetching = true;
|
|
272
|
+
onSearch('');
|
|
273
|
+
} else {
|
|
274
|
+
// If images already fetching, just listen for callback
|
|
275
|
+
imageFetchListeners.push(addFetchedImages);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
addEditCodeBtn(newEditor);
|
|
279
|
+
assetsViewOverride(newEditor, onSearch);
|
|
280
|
+
setEditor(newEditor);
|
|
281
|
+
|
|
282
|
+
if (window.onGrapeInit) {
|
|
283
|
+
window.onGrapeInit(newEditor);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return () => {
|
|
287
|
+
mounted.current = false;
|
|
288
|
+
};
|
|
289
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
290
|
+
}, []);
|
|
291
|
+
|
|
292
|
+
// Update strapi value when template is updated
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
if (!editor) {
|
|
295
|
+
return () => {};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const onUpdate = () => {
|
|
299
|
+
const html = editor.getHtml();
|
|
300
|
+
const css = editor.getCss({ avoidProtected: true });
|
|
301
|
+
let newValue = '';
|
|
302
|
+
|
|
303
|
+
if (html.length || css.length) {
|
|
304
|
+
newValue = `\
|
|
305
|
+
<div>
|
|
306
|
+
${html.replace(/ draggable="true"/g, '')/* Remove draggable attribute from text elements */}
|
|
307
|
+
<style>
|
|
308
|
+
${css}
|
|
309
|
+
</style>
|
|
310
|
+
</div>\
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (valueRef.current === newValue) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
onChange({
|
|
319
|
+
target: {
|
|
320
|
+
name,
|
|
321
|
+
value: newValue,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const editorData = JSON.stringify({
|
|
326
|
+
components: editor.getComponents(),
|
|
327
|
+
style: editor.getStyle(),
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
onChange({
|
|
331
|
+
target: {
|
|
332
|
+
name: `${name}_data`,
|
|
333
|
+
value: editorData,
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
editor.on('update', onUpdate);
|
|
339
|
+
|
|
340
|
+
return () => editor.off('update', onUpdate);
|
|
341
|
+
}, [editor, name, onChange]);
|
|
342
|
+
|
|
343
|
+
// Don't submit the form when editing inputs inside the GrapeJS editor
|
|
344
|
+
const preventFormSubmission = useCallback((e) => {
|
|
345
|
+
if (e.which === 13) {
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
}
|
|
348
|
+
}, []);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<Wrapper onKeyDown={preventFormSubmission}>
|
|
352
|
+
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
|
|
353
|
+
<label htmlFor={name}>
|
|
354
|
+
{intlLabel ? formatMessage(intlLabel) : label}
|
|
355
|
+
</label>
|
|
356
|
+
<div className="gjs" id={containerId} />
|
|
357
|
+
</Wrapper>
|
|
358
|
+
);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export default InputGrapeJs;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Cache config for Media
|
|
2
|
+
const MEDIA_CACHE_KEY = 'gjs-media';
|
|
3
|
+
|
|
4
|
+
export const storeMedia = (images) => {
|
|
5
|
+
localStorage.setItem(MEDIA_CACHE_KEY, JSON.stringify(images));
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const loadMedia = () => {
|
|
9
|
+
let images = [];
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
images = JSON.parse(localStorage.getItem(MEDIA_CACHE_KEY) || '[]');
|
|
13
|
+
} catch (err) {
|
|
14
|
+
// Nothing
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return images;
|
|
18
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import defaultPlugin from 'grapesjs-preset-newsletter';
|
|
3
|
+
import InputGrape from '../InputGrape';
|
|
4
|
+
|
|
5
|
+
const InputGrapeNewsletter = (props) => (
|
|
6
|
+
<InputGrape {...props} plugin={defaultPlugin} />
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export default InputGrapeNewsletter;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* This component is the skeleton around the actual pages, and should only
|
|
4
|
+
* contain code that should be seen on all pages. (e.g. navigation bar)
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Switch, Route } from 'react-router-dom';
|
|
10
|
+
import { NotFound } from '@strapi/helper-plugin';
|
|
11
|
+
// Utils
|
|
12
|
+
import pluginId from '../../pluginId';
|
|
13
|
+
// Containers
|
|
14
|
+
import HomePage from '../HomePage';
|
|
15
|
+
|
|
16
|
+
const App = () => (
|
|
17
|
+
<div>
|
|
18
|
+
<Switch>
|
|
19
|
+
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
|
20
|
+
<Route component={NotFound} />
|
|
21
|
+
</Switch>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export default App;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* HomePage
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo } from 'react';
|
|
8
|
+
// import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const HomePage = () => (
|
|
12
|
+
<div>
|
|
13
|
+
<h1>{pluginId}'s HomePage</h1>
|
|
14
|
+
<p>Happy coding</p>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default memo(HomePage);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
|
2
|
+
import pluginPkg from '../../package.json';
|
|
3
|
+
import pluginId from './pluginId';
|
|
4
|
+
import trads from './translations';
|
|
5
|
+
import InputGrape from './components/InputGrape';
|
|
6
|
+
import InputGrapeNewsletter from './components/InputGrapeNewsletter';
|
|
7
|
+
|
|
8
|
+
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
|
|
9
|
+
const name = pluginPkg.strapi.name;
|
|
10
|
+
const icon = pluginPkg.strapi.icon;
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
register(app) {
|
|
14
|
+
const plugin = {
|
|
15
|
+
description: pluginDescription,
|
|
16
|
+
icon,
|
|
17
|
+
name,
|
|
18
|
+
id: pluginId,
|
|
19
|
+
trads,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
InputGrape.baseType = 'richtext';
|
|
23
|
+
InputGrapeNewsletter.baseType = 'richtext';
|
|
24
|
+
app.addComponents({ name: 'PluginGrapeInput', Component: InputGrape });
|
|
25
|
+
app.addComponents({ name: 'PluginGrapeNewsletterInput', Component: InputGrapeNewsletter });
|
|
26
|
+
|
|
27
|
+
app.registerPlugin(plugin);
|
|
28
|
+
},
|
|
29
|
+
async registerTrads({ locales }) {
|
|
30
|
+
const importedTrads = await Promise.all(
|
|
31
|
+
locales.map((locale) => {
|
|
32
|
+
return import(
|
|
33
|
+
/* webpackChunkName: "grapejs-trads" */ `./translations/${locale}.json`
|
|
34
|
+
)
|
|
35
|
+
.then(({ default: data }) => {
|
|
36
|
+
return {
|
|
37
|
+
data: prefixPluginTranslations(data, pluginId),
|
|
38
|
+
locale,
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
.catch(() => {
|
|
42
|
+
return {
|
|
43
|
+
data: {},
|
|
44
|
+
locale,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return Promise.resolve(importedTrads);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import ar from './ar.json';
|
|
2
|
+
import cs from './cs.json';
|
|
3
|
+
import de from './de.json';
|
|
4
|
+
import en from './en.json';
|
|
5
|
+
import es from './es.json';
|
|
6
|
+
import fr from './fr.json';
|
|
7
|
+
import it from './it.json';
|
|
8
|
+
import ko from './ko.json';
|
|
9
|
+
import ms from './ms.json';
|
|
10
|
+
import nl from './nl.json';
|
|
11
|
+
import pl from './pl.json';
|
|
12
|
+
import ptBR from './pt-BR.json';
|
|
13
|
+
import pt from './pt.json';
|
|
14
|
+
import ru from './ru.json';
|
|
15
|
+
import tr from './tr.json';
|
|
16
|
+
import vi from './vi.json';
|
|
17
|
+
import zhHans from './zh-Hans.json';
|
|
18
|
+
import zh from './zh.json';
|
|
19
|
+
import sk from './sk.json';
|
|
20
|
+
|
|
21
|
+
const trads = {
|
|
22
|
+
ar,
|
|
23
|
+
cs,
|
|
24
|
+
de,
|
|
25
|
+
en,
|
|
26
|
+
es,
|
|
27
|
+
fr,
|
|
28
|
+
it,
|
|
29
|
+
ko,
|
|
30
|
+
ms,
|
|
31
|
+
nl,
|
|
32
|
+
pl,
|
|
33
|
+
'pt-BR': ptBR,
|
|
34
|
+
pt,
|
|
35
|
+
ru,
|
|
36
|
+
tr,
|
|
37
|
+
vi,
|
|
38
|
+
'zh-Hans': zhHans,
|
|
39
|
+
zh,
|
|
40
|
+
sk,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default trads;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codeparticle/strapi-plugin-grapejs",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Integrated GrapeJS as a content editor",
|
|
5
|
+
"strapi": {
|
|
6
|
+
"name": "grapejs",
|
|
7
|
+
"icon": "plug",
|
|
8
|
+
"description": "Integrated GrapeJS as a content editor",
|
|
9
|
+
"kind": "plugin"
|
|
10
|
+
},
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "CodeParticle",
|
|
13
|
+
"email": "",
|
|
14
|
+
"url": ""
|
|
15
|
+
},
|
|
16
|
+
"maintainers": [
|
|
17
|
+
{
|
|
18
|
+
"name": "CodeParticle",
|
|
19
|
+
"email": "",
|
|
20
|
+
"url": ""
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": "22.x",
|
|
25
|
+
"pnpm": "10.x"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"files": [
|
|
29
|
+
"README.md",
|
|
30
|
+
"admin",
|
|
31
|
+
"package.json",
|
|
32
|
+
"pnpm-workspace.yaml",
|
|
33
|
+
"server",
|
|
34
|
+
"strapi-admin.js",
|
|
35
|
+
"strapi-server.js"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@strapi/helper-plugin": "4.9.2",
|
|
39
|
+
"@strapi/strapi": "4.9.2",
|
|
40
|
+
"grapesjs": "^0.19.4",
|
|
41
|
+
"grapesjs-blocks-basic": "^1.0.2",
|
|
42
|
+
"grapesjs-plugin-forms": "^2.0.6",
|
|
43
|
+
"grapesjs-preset-newsletter": "^1.0.2",
|
|
44
|
+
"grapesjs-preset-webpage": "^1.0.3",
|
|
45
|
+
"lodash": "4.17.19",
|
|
46
|
+
"react": "^17.0.2",
|
|
47
|
+
"react-router-dom": "^5.0.0",
|
|
48
|
+
"react-stable-uniqueid": "^1.2.2",
|
|
49
|
+
"styled-components": "^5.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@commitlint/config-conventional": "^7.6.0",
|
|
53
|
+
"babel-eslint": "^10.1.0",
|
|
54
|
+
"commitlint": "^7.6.1",
|
|
55
|
+
"eslint": "7.32.0",
|
|
56
|
+
"eslint-config-airbnb": "18.2.1",
|
|
57
|
+
"eslint-config-airbnb-base": "14.2.1",
|
|
58
|
+
"eslint-config-prettier": "6.15.0",
|
|
59
|
+
"eslint-plugin-import": "2.26.0",
|
|
60
|
+
"eslint-plugin-jsx-a11y": "6.5.1",
|
|
61
|
+
"eslint-plugin-node": "11.1.0",
|
|
62
|
+
"eslint-plugin-react": "7.29.4",
|
|
63
|
+
"eslint-plugin-react-hooks": "4.5.0"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"dev": "pnpm run link-plugin && cd example-app && pnpm install && pnpm run develop --watch-admin",
|
|
67
|
+
"link-plugin": "mkdir -p ./example-app/src/plugins && if [ ! -d \"$(pwd)/example-app/src/plugins/grapejs\" ]; then ln -s $(pwd) $(pwd)/example-app/src/plugins/grapejs; fi",
|
|
68
|
+
"lint": "eslint --ignore-path .gitignore --ignore-path .eslintignore .",
|
|
69
|
+
"lint-example-app": "cd example-app && pnpm run lint",
|
|
70
|
+
"postversion": "git push && git push --tags"
|
|
71
|
+
}
|
|
72
|
+
}
|
package/server/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const services = require('./services');
|
|
4
|
+
const routes = require('./routes');
|
|
5
|
+
const controllers = require('./controllers');
|
|
6
|
+
const register = require('./register');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
controllers,
|
|
10
|
+
routes,
|
|
11
|
+
services,
|
|
12
|
+
register
|
|
13
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
|
|
3
|
+
module.exports = ({ strapi }) => {
|
|
4
|
+
addDataAttribute(strapi);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const addDataAttribute = (strapi) => {
|
|
8
|
+
Object.values(strapi.contentTypes).forEach(({ attributes, uid }) => {
|
|
9
|
+
Object.entries(attributes).forEach(([key, entry]) => {
|
|
10
|
+
if (entry.customInput === 'PluginGrapeInput' || entry.customInput === 'PluginGrapeNewsletterInput') {
|
|
11
|
+
const attrKey = `${key}_data`;
|
|
12
|
+
|
|
13
|
+
if (attributes[attrKey]) {
|
|
14
|
+
console.warn(`Grapejs plugin could not initialize data attribute on model ${uid} because there is already an attribute with key ${attrKey}`);
|
|
15
|
+
} else {
|
|
16
|
+
_.set(attributes, attrKey, {
|
|
17
|
+
type: 'json',
|
|
18
|
+
writable: true,
|
|
19
|
+
configurable: false,
|
|
20
|
+
visible: false,
|
|
21
|
+
ignoreInFindMany: true,
|
|
22
|
+
ignoreInUserApi: true,
|
|
23
|
+
ignoreInLocalizationsPopulate: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = () => ({});
|
package/strapi-admin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./admin/src').default;
|
package/strapi-server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./server');
|