@servicetitan/dte-unlayer 0.0.1
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/dist/editor.d.ts +20 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +47 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/loadScript.d.ts +2 -0
- package/dist/loadScript.d.ts.map +1 -0
- package/dist/loadScript.js +24 -0
- package/dist/loadScript.js.map +1 -0
- package/dist/store.d.ts +32 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +291 -0
- package/dist/store.js.map +1 -0
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +37 -0
- package/dist/tools.js.map +1 -0
- package/dist/unlayer-interface.d.ts +55 -0
- package/dist/unlayer-interface.d.ts.map +1 -0
- package/dist/unlayer-interface.js +2 -0
- package/dist/unlayer-interface.js.map +1 -0
- package/dist/unlayer.d.ts +41 -0
- package/dist/unlayer.d.ts.map +1 -0
- package/dist/unlayer.js +73 -0
- package/dist/unlayer.js.map +1 -0
- package/package.json +19 -0
- package/src/editor.tsx +87 -0
- package/src/index.ts +4 -0
- package/src/loadScript.ts +27 -0
- package/src/store.ts +217 -0
- package/src/tools.ts +53 -0
- package/src/unlayer-interface.tsx +61 -0
- package/src/unlayer.tsx +133 -0
package/dist/unlayer.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export var DesignUpdatedEventType;
|
|
2
|
+
(function (DesignUpdatedEventType) {
|
|
3
|
+
DesignUpdatedEventType["ContentRemoved"] = "content:removed";
|
|
4
|
+
DesignUpdatedEventType["ContentAdded"] = "content:added";
|
|
5
|
+
})(DesignUpdatedEventType || (DesignUpdatedEventType = {}));
|
|
6
|
+
export const createUnlayerEditor = (container, { customCSS, customJS, latest, mergeTags, noCoreTools, packageUrl, tools, }) => {
|
|
7
|
+
const customScripts = [
|
|
8
|
+
'/core.bundle.js',
|
|
9
|
+
...tools.map(tool => `/${tool.path}`),
|
|
10
|
+
tools.map(tool => `window.dteStore.registerTool(unlayer, '${tool.key}');`).join('\n'),
|
|
11
|
+
...(customJS ? (Array.isArray(customJS) ? customJS : [customJS]) : []),
|
|
12
|
+
'/anvilfonts.bundle.js',
|
|
13
|
+
'window.dteStore.sendData("ready")',
|
|
14
|
+
].map(url => (url.startsWith('/') ? `${packageUrl}${url}` : url));
|
|
15
|
+
/**
|
|
16
|
+
* Unlayer supports only passing id of container, but when we use shadowDOM (MFE),
|
|
17
|
+
* getElementById can't find element in it
|
|
18
|
+
* so making this dirty hack to let unlayer find container in DOM
|
|
19
|
+
*/
|
|
20
|
+
const id = container.id;
|
|
21
|
+
const currentFind = document.getElementById;
|
|
22
|
+
document.getElementById = function (elementId) {
|
|
23
|
+
if (id === elementId) {
|
|
24
|
+
return container;
|
|
25
|
+
}
|
|
26
|
+
return currentFind.call(document, id);
|
|
27
|
+
};
|
|
28
|
+
const result = window.unlayer.createEditor({
|
|
29
|
+
displayMode: 'web',
|
|
30
|
+
devices: ['desktop'],
|
|
31
|
+
features: {
|
|
32
|
+
preview: false,
|
|
33
|
+
userUploads: false,
|
|
34
|
+
stockImages: false,
|
|
35
|
+
},
|
|
36
|
+
mergeTags: mergeTags === null || mergeTags === void 0 ? void 0 : mergeTags.reduce((out, tag) => {
|
|
37
|
+
out[tag.id] = { name: tag.name, value: tag.propertyPath };
|
|
38
|
+
return out;
|
|
39
|
+
}, {}),
|
|
40
|
+
projectId: 5713,
|
|
41
|
+
version: latest ? undefined : '1.2.9',
|
|
42
|
+
tools: Object.assign({ button: { enabled: false }, html: { enabled: false }, menu: { enabled: false }, form: { enabled: false }, image: { enabled: false } }, (noCoreTools
|
|
43
|
+
? {
|
|
44
|
+
columns: { enabled: false },
|
|
45
|
+
text: { enabled: false },
|
|
46
|
+
heading: { enabled: false },
|
|
47
|
+
divider: { enabled: false },
|
|
48
|
+
}
|
|
49
|
+
: {})),
|
|
50
|
+
editor: {
|
|
51
|
+
autoSelectOnDrop: true,
|
|
52
|
+
},
|
|
53
|
+
customJS: customScripts,
|
|
54
|
+
customCSS: [customCSS],
|
|
55
|
+
id,
|
|
56
|
+
fonts: {
|
|
57
|
+
showDefaultFonts: true,
|
|
58
|
+
customFonts: [
|
|
59
|
+
{ label: 'Sofia Pro', value: 'Sofia Pro' },
|
|
60
|
+
{ label: 'Nunito Sans', value: 'Nunito Sans' },
|
|
61
|
+
{ label: 'Montserrat', value: 'Montserrat' },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
tabs: {
|
|
65
|
+
content: {},
|
|
66
|
+
body: {},
|
|
67
|
+
blocks: { enabled: false },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
document.getElementById = currentFind;
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=unlayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unlayer.js","sourceRoot":"","sources":["../src/unlayer.tsx"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@servicetitan/dte-unlayer",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"typings": "./dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"cli": {
|
|
15
|
+
"webpack": false
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/editor.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CSSProperties,
|
|
3
|
+
forwardRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { UnlayerStore, UnlayerToolsInfo } from './store';
|
|
11
|
+
import { UnlayerRef, CreateUnlayerEditorProps } from './unlayer-interface';
|
|
12
|
+
|
|
13
|
+
interface UnlayerEditorProps {
|
|
14
|
+
id?: string;
|
|
15
|
+
design: any;
|
|
16
|
+
opts: CreateUnlayerEditorProps;
|
|
17
|
+
minHeight?: number;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
onReady?(): void;
|
|
20
|
+
onChange?(info: UnlayerToolsInfo): void;
|
|
21
|
+
onImage?(file: any): Promise<{ url: string }>;
|
|
22
|
+
onMessage?(type: string, data: any): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useUnlayerRef = () => useRef<UnlayerRef | null>(null);
|
|
26
|
+
|
|
27
|
+
export const UnlayerEditor = forwardRef<UnlayerRef, UnlayerEditorProps>((props, ref) => {
|
|
28
|
+
const [status, setStatus] = useState(0);
|
|
29
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
30
|
+
const store = useMemo(
|
|
31
|
+
() => new UnlayerStore(props.opts),
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
[]
|
|
34
|
+
);
|
|
35
|
+
const isReady = status === 2;
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (containerRef.current) {
|
|
39
|
+
store
|
|
40
|
+
.init(containerRef.current)
|
|
41
|
+
.then(() => setStatus(2))
|
|
42
|
+
.catch(() => setStatus(1));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return () => store.destroy();
|
|
46
|
+
}, [store]);
|
|
47
|
+
useImperativeHandle(ref, () => store.unlayerRef, [store]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (isReady && props.design) {
|
|
51
|
+
store.setDesign(props.design);
|
|
52
|
+
}
|
|
53
|
+
}, [isReady, props.design, store]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
store.setOnChange(props.onChange);
|
|
57
|
+
|
|
58
|
+
return () => store.setOnChange();
|
|
59
|
+
}, [props.onChange, store]);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
store.setOnReady(props.onReady);
|
|
63
|
+
|
|
64
|
+
return () => store.setOnReady();
|
|
65
|
+
}, [props.onReady, store]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
store.setOnImage(props.onImage);
|
|
69
|
+
|
|
70
|
+
return () => store.setOnImage();
|
|
71
|
+
}, [props.onImage, store]);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
store.setOnMessage(props.onMessage);
|
|
75
|
+
|
|
76
|
+
return () => store.setOnMessage();
|
|
77
|
+
}, [props.onMessage, store]);
|
|
78
|
+
|
|
79
|
+
const { minHeight = 800, style = {} } = props;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div style={{ minHeight, display: 'flex' }}>
|
|
83
|
+
{status === 1 && <p className="c-red-500">error loading editor</p>}
|
|
84
|
+
<div id={props.id ?? 'editor'} style={style} ref={containerRef} />
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const isScriptInjected = (scriptUrl: string) => {
|
|
2
|
+
const scripts = document.querySelectorAll('script');
|
|
3
|
+
let injected = false;
|
|
4
|
+
|
|
5
|
+
scripts.forEach(script => {
|
|
6
|
+
if (script.src.includes(scriptUrl)) {
|
|
7
|
+
injected = true;
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return injected;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const loadScript = (scriptUrl: string): Promise<void> => {
|
|
15
|
+
return new Promise<void>(resolve => {
|
|
16
|
+
if (isScriptInjected(scriptUrl)) {
|
|
17
|
+
resolve();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const embedScript = document.createElement('script');
|
|
21
|
+
embedScript.setAttribute('src', scriptUrl);
|
|
22
|
+
embedScript.onload = () => {
|
|
23
|
+
resolve();
|
|
24
|
+
};
|
|
25
|
+
document.head.appendChild(embedScript);
|
|
26
|
+
});
|
|
27
|
+
};
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { loadScript } from './loadScript';
|
|
2
|
+
import { unlayerIterateCustomTools } from './tools';
|
|
3
|
+
import {
|
|
4
|
+
createUnlayerEditor,
|
|
5
|
+
DesignUpdatedEventType,
|
|
6
|
+
EventDesignUpdated,
|
|
7
|
+
Unlayer,
|
|
8
|
+
} from './unlayer';
|
|
9
|
+
import { CreateUnlayerEditorProps, UnlayerDesignFormat, UnlayerRef } from './unlayer-interface';
|
|
10
|
+
|
|
11
|
+
const defaultScriptUrl = 'https://editor.unlayer.com/embed.js?2';
|
|
12
|
+
|
|
13
|
+
export interface UnlayerToolsInfo {
|
|
14
|
+
notSupported: string[];
|
|
15
|
+
required: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class UnlayerStore {
|
|
19
|
+
readonly unlayerRef: UnlayerRef;
|
|
20
|
+
|
|
21
|
+
private editor: Unlayer | undefined;
|
|
22
|
+
private isInit = false;
|
|
23
|
+
private iframe?: HTMLIFrameElement;
|
|
24
|
+
|
|
25
|
+
private onMessageCB?: (type: string, data: any) => void;
|
|
26
|
+
private onChangeCB?: (info: UnlayerToolsInfo) => void;
|
|
27
|
+
private onReadyCB?: (info: UnlayerToolsInfo) => void;
|
|
28
|
+
private onImageCB?: (file: any) => Promise<{ url: string }>;
|
|
29
|
+
|
|
30
|
+
private currentComponents: Record<string, number> = {};
|
|
31
|
+
|
|
32
|
+
constructor(readonly props: CreateUnlayerEditorProps) {
|
|
33
|
+
this.unlayerRef = {
|
|
34
|
+
loadDesign: design => {
|
|
35
|
+
this.editor?.loadDesign(design);
|
|
36
|
+
},
|
|
37
|
+
saveDesign: cb => {
|
|
38
|
+
this.editor?.saveDesign(cb);
|
|
39
|
+
},
|
|
40
|
+
exportHtml: cb => {
|
|
41
|
+
this.editor?.exportHtml(data => {
|
|
42
|
+
if (this.props.altTools?.length) {
|
|
43
|
+
unlayerIterateCustomTools(data.design, tool => {
|
|
44
|
+
if (tool.slug.includes(':')) {
|
|
45
|
+
tool.slug = tool.slug.split(':')[0];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
for (const key of Object.keys(data.design.counters ?? {})) {
|
|
50
|
+
if (key.startsWith('u_content_custom_') && key.includes(':')) {
|
|
51
|
+
data.design.counters[key.split(':')[0]] = data.design.counters[key];
|
|
52
|
+
delete data.design.counters[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cb(data);
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
init = async (container: HTMLDivElement) => {
|
|
64
|
+
if (this.isInit) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.isInit = true;
|
|
69
|
+
|
|
70
|
+
window.addEventListener('message', this.onPostMessage);
|
|
71
|
+
|
|
72
|
+
await loadScript(defaultScriptUrl);
|
|
73
|
+
|
|
74
|
+
this.editor = createUnlayerEditor(container, this.props);
|
|
75
|
+
this.editor.addEventListener('design:loaded', this.onDesignLoaded);
|
|
76
|
+
this.editor.addEventListener('design:updated', this.onDesignUpdated);
|
|
77
|
+
this.editor.registerCallback('image', this.uploadImage);
|
|
78
|
+
|
|
79
|
+
this.iframe = container.querySelector(`iframe`) ?? undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
destroy = () => {
|
|
83
|
+
window.removeEventListener('message', this.onPostMessage);
|
|
84
|
+
|
|
85
|
+
if (this.editor) {
|
|
86
|
+
this.editor.removeEventListener('design:loaded', this.onDesignLoaded);
|
|
87
|
+
this.editor.removeEventListener('design:updated', this.onDesignUpdated);
|
|
88
|
+
this.editor.unregisterCallback('image', this.uploadImage);
|
|
89
|
+
|
|
90
|
+
this.editor = undefined;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
setDesign = (design: UnlayerDesignFormat) => {
|
|
95
|
+
if (!this.editor) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.currentComponents = {};
|
|
100
|
+
unlayerIterateCustomTools(design, tool => {
|
|
101
|
+
if (!this.currentComponents[tool.slug]) {
|
|
102
|
+
this.currentComponents[tool.slug] = 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.currentComponents[tool.slug]++;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.editor.loadDesign(design);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
setOnChange = (onChange?: (info: UnlayerToolsInfo) => void) => {
|
|
112
|
+
this.onChangeCB = onChange;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
setOnReady = (onReady?: (info: UnlayerToolsInfo) => void) => {
|
|
116
|
+
this.onReadyCB = onReady;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
setOnImage = (onImage?: UnlayerStore['onImageCB']) => {
|
|
120
|
+
this.onImageCB = onImage;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
setOnMessage = (onMessage?: UnlayerStore['onMessageCB']) => {
|
|
124
|
+
this.onMessageCB = onMessage;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
private onDesignLoaded = () => {
|
|
128
|
+
this.editor?.setBodyValues({
|
|
129
|
+
backgroundColor: 'rgba(0,0,0,0)',
|
|
130
|
+
contentWidth: '100%',
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
private onDesignUpdated = (data: EventDesignUpdated) => {
|
|
135
|
+
if (data.type === DesignUpdatedEventType.ContentRemoved) {
|
|
136
|
+
const tool = data.item.type === 'custom' ? data.item.slug : undefined;
|
|
137
|
+
|
|
138
|
+
if (tool && this.currentComponents[tool]) {
|
|
139
|
+
this.currentComponents[tool]--;
|
|
140
|
+
|
|
141
|
+
if (!this.currentComponents[tool]) {
|
|
142
|
+
delete this.currentComponents[tool];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else if (data.type === DesignUpdatedEventType.ContentAdded) {
|
|
146
|
+
const tool = data.item.type === 'custom' ? data.item.slug : undefined;
|
|
147
|
+
|
|
148
|
+
if (tool && this.currentComponents[tool]) {
|
|
149
|
+
if (!this.currentComponents[tool]) {
|
|
150
|
+
this.currentComponents[tool] = 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.currentComponents[tool]++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.onChangeCB?.(this.getToolsInfo());
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
private onPostMessage = (e: MessageEvent) => {
|
|
161
|
+
if (!e.data?.['-dte-message-from']) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const type = e.data?.['-dte-message-from']?.type;
|
|
166
|
+
const data = e.data?.['-dte-message-from']?.data;
|
|
167
|
+
|
|
168
|
+
if (!type) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (type === 'ready') {
|
|
173
|
+
this.onReadyCB?.(this.getToolsInfo());
|
|
174
|
+
this.sendMessage(
|
|
175
|
+
'--config',
|
|
176
|
+
JSON.parse(
|
|
177
|
+
JSON.stringify({
|
|
178
|
+
altTools: this.props.altTools ?? [],
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.onMessageCB?.(type, data);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
private sendMessage = (type: string, data?: any) => {
|
|
188
|
+
this.iframe?.contentWindow?.postMessage(
|
|
189
|
+
{
|
|
190
|
+
'-dte-message-to': {
|
|
191
|
+
type,
|
|
192
|
+
data,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
'*'
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
private getToolsInfo = () => {
|
|
200
|
+
return {
|
|
201
|
+
notSupported: Object.keys(this.currentComponents).filter(
|
|
202
|
+
key => !this.props.tools.some(tool => tool.key === key)
|
|
203
|
+
),
|
|
204
|
+
required: this.props.tools
|
|
205
|
+
.filter(tool => tool.isRequired && !this.currentComponents[tool.key])
|
|
206
|
+
.map(tool => tool.key),
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
private uploadImage = (file: any, done: (result: any) => void) => {
|
|
211
|
+
if (!this.onImageCB) {
|
|
212
|
+
throw new Error('image upload is not implemented');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.onImageCB(file).then(({ url }) => done({ progress: 100, url }));
|
|
216
|
+
};
|
|
217
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { UnlayerDesignFormat, UnlayerDesignTool } from './unlayer-interface';
|
|
2
|
+
|
|
3
|
+
export const unlayerIterateTools = (
|
|
4
|
+
design: UnlayerDesignFormat,
|
|
5
|
+
cb: (tool: UnlayerDesignTool) => boolean | undefined | void
|
|
6
|
+
) => {
|
|
7
|
+
for (const row of design?.body?.rows ?? []) {
|
|
8
|
+
for (const column of row.columns) {
|
|
9
|
+
for (const content of column.contents) {
|
|
10
|
+
if (cb(content) === false) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const unlayerIterateCustomTools = (
|
|
19
|
+
design: UnlayerDesignFormat,
|
|
20
|
+
cb: (tool: UnlayerDesignTool) => boolean | undefined | void
|
|
21
|
+
) => {
|
|
22
|
+
unlayerIterateTools(design, tool => {
|
|
23
|
+
if (tool.type === 'custom') {
|
|
24
|
+
return cb(tool);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const unlayerGetAllCustomTools = (design: UnlayerDesignFormat): string[] => {
|
|
30
|
+
const allTools = new Set<string>();
|
|
31
|
+
|
|
32
|
+
unlayerIterateCustomTools(design, tool => {
|
|
33
|
+
allTools.add(tool.slug);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return Array.from(allTools);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const unlayerFindTool = (
|
|
40
|
+
design: UnlayerDesignFormat,
|
|
41
|
+
cb: (tool: UnlayerDesignTool) => boolean | void
|
|
42
|
+
): UnlayerDesignTool | undefined => {
|
|
43
|
+
let out: UnlayerDesignTool | undefined;
|
|
44
|
+
|
|
45
|
+
unlayerIterateTools(design, tool => {
|
|
46
|
+
if (cb(tool)) {
|
|
47
|
+
out = tool;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return out;
|
|
53
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface UnlayerDesignTool {
|
|
2
|
+
type: string;
|
|
3
|
+
slug: string;
|
|
4
|
+
values: any;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface UnlayerEditorToolInfo {
|
|
8
|
+
key: string;
|
|
9
|
+
path: string;
|
|
10
|
+
isRequired?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UnlayerDesignFormat {
|
|
14
|
+
body: {
|
|
15
|
+
rows: {
|
|
16
|
+
cells: number[];
|
|
17
|
+
columns: {
|
|
18
|
+
contents: UnlayerDesignTool[];
|
|
19
|
+
}[];
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
counters: Record<string, number>;
|
|
23
|
+
schemaVersion: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UnlayerExport {
|
|
27
|
+
html: string;
|
|
28
|
+
chunks: any;
|
|
29
|
+
design: UnlayerDesignFormat;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UnlayerRef {
|
|
33
|
+
loadDesign(design: any): void;
|
|
34
|
+
saveDesign(cb: (design: any) => void): void;
|
|
35
|
+
exportHtml(cb: (data: UnlayerExport) => void): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UnlayerAltTool {
|
|
39
|
+
id: string;
|
|
40
|
+
tool: string;
|
|
41
|
+
label: string;
|
|
42
|
+
values: any;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface UnlayerEditorMergeTagInfo {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
propertyPath: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CreateUnlayerEditorProps {
|
|
52
|
+
packageUrl: string;
|
|
53
|
+
tools: UnlayerEditorToolInfo[];
|
|
54
|
+
altTools?: UnlayerAltTool[];
|
|
55
|
+
customCSS?: string;
|
|
56
|
+
customJS?: string | string[] | undefined;
|
|
57
|
+
mergeTags?: UnlayerEditorMergeTagInfo[] | undefined;
|
|
58
|
+
noCoreTools?: boolean;
|
|
59
|
+
latest?: boolean;
|
|
60
|
+
blocks?: boolean;
|
|
61
|
+
}
|
package/src/unlayer.tsx
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { CreateUnlayerEditorProps } from './unlayer-interface';
|
|
2
|
+
|
|
3
|
+
export interface UnlayerExport {
|
|
4
|
+
html: string;
|
|
5
|
+
chunks: any;
|
|
6
|
+
design: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Unlayer {
|
|
10
|
+
addEventListener(event: string, cb: (arg: any) => void): void;
|
|
11
|
+
removeEventListener(event: string, cb: (arg: any) => void): void;
|
|
12
|
+
registerCallback(type: string, cb: (...args: any[]) => void): void;
|
|
13
|
+
unregisterCallback(type: string, cb: (...args: any[]) => void): void;
|
|
14
|
+
registerProvider(type: string, cb: (...args: any[]) => void): void;
|
|
15
|
+
unregisterProvider(type: string, cb: (...args: any[]) => void): void;
|
|
16
|
+
loadDesign(design: any): void;
|
|
17
|
+
saveDesign(callback: (design: any) => void): void;
|
|
18
|
+
exportHtml(callback: (data: UnlayerExport) => void): void;
|
|
19
|
+
setMergeTags(tags: any): void;
|
|
20
|
+
createViewer(opts: { render: (values: any) => string }): any;
|
|
21
|
+
registerTool(tool: any): void;
|
|
22
|
+
setBodyValues(values: any): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum DesignUpdatedEventType {
|
|
26
|
+
ContentRemoved = 'content:removed',
|
|
27
|
+
ContentAdded = 'content:added',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EventDesignUpdated {
|
|
31
|
+
type: DesignUpdatedEventType;
|
|
32
|
+
item: {
|
|
33
|
+
type: string;
|
|
34
|
+
slug?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UnlayerEditorMergeTagInfo {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
propertyPath: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const createUnlayerEditor = (
|
|
45
|
+
container: HTMLDivElement,
|
|
46
|
+
{
|
|
47
|
+
customCSS,
|
|
48
|
+
customJS,
|
|
49
|
+
latest,
|
|
50
|
+
mergeTags,
|
|
51
|
+
noCoreTools,
|
|
52
|
+
packageUrl,
|
|
53
|
+
tools,
|
|
54
|
+
}: CreateUnlayerEditorProps
|
|
55
|
+
): Unlayer => {
|
|
56
|
+
const customScripts = [
|
|
57
|
+
'/core.bundle.js',
|
|
58
|
+
...tools.map(tool => `/${tool.path}`),
|
|
59
|
+
tools.map(tool => `window.dteStore.registerTool(unlayer, '${tool.key}');`).join('\n'),
|
|
60
|
+
...(customJS ? (Array.isArray(customJS) ? customJS : [customJS]) : []),
|
|
61
|
+
'/anvilfonts.bundle.js',
|
|
62
|
+
'window.dteStore.sendData("ready")',
|
|
63
|
+
].map(url => (url.startsWith('/') ? `${packageUrl}${url}` : url));
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Unlayer supports only passing id of container, but when we use shadowDOM (MFE),
|
|
67
|
+
* getElementById can't find element in it
|
|
68
|
+
* so making this dirty hack to let unlayer find container in DOM
|
|
69
|
+
*/
|
|
70
|
+
const id = container.id;
|
|
71
|
+
const currentFind = document.getElementById;
|
|
72
|
+
document.getElementById = function (elementId: string) {
|
|
73
|
+
if (id === elementId) {
|
|
74
|
+
return container;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return currentFind.call(document, id);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const result = (window as any).unlayer.createEditor({
|
|
81
|
+
displayMode: 'web',
|
|
82
|
+
devices: ['desktop'],
|
|
83
|
+
features: {
|
|
84
|
+
preview: false,
|
|
85
|
+
userUploads: false,
|
|
86
|
+
stockImages: false,
|
|
87
|
+
},
|
|
88
|
+
mergeTags: mergeTags?.reduce((out, tag) => {
|
|
89
|
+
out[tag.id] = { name: tag.name, value: tag.propertyPath };
|
|
90
|
+
|
|
91
|
+
return out;
|
|
92
|
+
}, {} as Record<string, { name: string; value: string }>),
|
|
93
|
+
projectId: 5713,
|
|
94
|
+
version: latest ? undefined : '1.2.9',
|
|
95
|
+
tools: {
|
|
96
|
+
button: { enabled: false },
|
|
97
|
+
html: { enabled: false },
|
|
98
|
+
menu: { enabled: false },
|
|
99
|
+
form: { enabled: false },
|
|
100
|
+
image: { enabled: false },
|
|
101
|
+
...(noCoreTools
|
|
102
|
+
? {
|
|
103
|
+
columns: { enabled: false },
|
|
104
|
+
text: { enabled: false },
|
|
105
|
+
heading: { enabled: false },
|
|
106
|
+
divider: { enabled: false },
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
109
|
+
},
|
|
110
|
+
editor: {
|
|
111
|
+
autoSelectOnDrop: true,
|
|
112
|
+
},
|
|
113
|
+
customJS: customScripts,
|
|
114
|
+
customCSS: [customCSS],
|
|
115
|
+
id,
|
|
116
|
+
fonts: {
|
|
117
|
+
showDefaultFonts: true,
|
|
118
|
+
customFonts: [
|
|
119
|
+
{ label: 'Sofia Pro', value: 'Sofia Pro' },
|
|
120
|
+
{ label: 'Nunito Sans', value: 'Nunito Sans' },
|
|
121
|
+
{ label: 'Montserrat', value: 'Montserrat' },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
tabs: {
|
|
125
|
+
content: {},
|
|
126
|
+
body: {},
|
|
127
|
+
blocks: { enabled: false },
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
document.getElementById = currentFind;
|
|
132
|
+
return result;
|
|
133
|
+
};
|