@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.
@@ -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":"AAwBA,MAAM,CAAN,IAAY,sBAGX;AAHD,WAAY,sBAAsB;IAC9B,4DAAkC,CAAA;IAClC,wDAA8B,CAAA;AAClC,CAAC,EAHW,sBAAsB,KAAtB,sBAAsB,QAGjC;AAgBD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAC/B,SAAyB,EACzB,EACI,SAAS,EACT,QAAQ,EACR,MAAM,EACN,SAAS,EACT,WAAW,EACX,UAAU,EACV,KAAK,GACkB,EACpB,EAAE;IACT,MAAM,aAAa,GAAG;QAClB,iBAAiB;QACjB,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACrC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,0CAA0C,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACrF,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,uBAAuB;QACvB,mCAAmC;KACtC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAElE;;;;OAIG;IACH,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;IACxB,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC;IAC5C,QAAQ,CAAC,cAAc,GAAG,UAAU,SAAiB;QACjD,IAAI,EAAE,KAAK,SAAS,EAAE;YAClB,OAAO,SAAS,CAAC;SACpB;QAED,OAAO,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC;IAEF,MAAM,MAAM,GAAI,MAAc,CAAC,OAAO,CAAC,YAAY,CAAC;QAChD,WAAW,EAAE,KAAK;QAClB,OAAO,EAAE,CAAC,SAAS,CAAC;QACpB,QAAQ,EAAE;YACN,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,KAAK;YAClB,WAAW,EAAE,KAAK;SACrB;QACD,SAAS,EAAE,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACtC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;YAE1D,OAAO,GAAG,CAAC;QACf,CAAC,EAAE,EAAqD,CAAC;QACzD,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;QACrC,KAAK,kBACD,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAC1B,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EACxB,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EACxB,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EACxB,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,IACtB,CAAC,WAAW;YACX,CAAC,CAAC;gBACI,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;gBAC3B,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;gBACxB,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;gBAC3B,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;aAC9B;YACH,CAAC,CAAC,EAAE,CAAC,CACZ;QACD,MAAM,EAAE;YACJ,gBAAgB,EAAE,IAAI;SACzB;QACD,QAAQ,EAAE,aAAa;QACvB,SAAS,EAAE,CAAC,SAAS,CAAC;QACtB,EAAE;QACF,KAAK,EAAE;YACH,gBAAgB,EAAE,IAAI;YACtB,WAAW,EAAE;gBACT,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;gBAC1C,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;gBAC9C,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;aAC/C;SACJ;QACD,IAAI,EAAE;YACF,OAAO,EAAE,EAAE;YACX,IAAI,EAAE,EAAE;YACR,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;SAC7B;KACJ,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,GAAG,WAAW,CAAC;IACtC,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC"}
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,4 @@
1
+ export * from './editor';
2
+ export * from './tools';
3
+ export * from './unlayer-interface';
4
+ export * from './loadScript';
@@ -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
+ }
@@ -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
+ };