@dotcms/client 0.0.1-alpha.38 → 0.0.1-alpha.39
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/.eslintrc.json +18 -0
- package/jest.config.ts +15 -0
- package/package.json +3 -15
- package/project.json +72 -0
- package/src/index.ts +30 -0
- package/src/lib/client/content/builders/collection/collection.spec.ts +515 -0
- package/src/lib/client/content/builders/collection/{collection.d.ts → collection.ts} +209 -19
- package/src/lib/client/content/{content-api.d.ts → content-api.ts} +14 -4
- package/src/lib/client/content/shared/{const.d.ts → const.ts} +5 -3
- package/src/lib/client/content/shared/{types.d.ts → types.ts} +19 -2
- package/src/lib/client/content/shared/{utils.d.ts → utils.ts} +9 -1
- package/src/lib/client/models/{index.d.ts → index.ts} +8 -1
- package/src/lib/client/models/{types.d.ts → types.ts} +1 -0
- package/src/lib/client/sdk-js-client.spec.ts +483 -0
- package/src/lib/client/{sdk-js-client.d.ts → sdk-js-client.ts} +181 -15
- package/src/lib/editor/listeners/listeners.spec.ts +119 -0
- package/src/lib/editor/listeners/listeners.ts +223 -0
- package/src/lib/editor/models/{client.model.d.ts → client.model.ts} +19 -16
- package/src/lib/editor/models/{editor.model.d.ts → editor.model.ts} +9 -5
- package/src/lib/editor/models/{listeners.model.d.ts → listeners.model.ts} +9 -6
- package/src/lib/editor/sdk-editor-vtl.ts +31 -0
- package/src/lib/editor/sdk-editor.spec.ts +116 -0
- package/src/lib/editor/sdk-editor.ts +105 -0
- package/src/lib/editor/utils/editor.utils.spec.ts +206 -0
- package/src/lib/editor/utils/{editor.utils.d.ts → editor.utils.ts} +121 -22
- package/src/lib/query-builder/lucene-syntax/{Equals.d.ts → Equals.ts} +45 -11
- package/src/lib/query-builder/lucene-syntax/{Field.d.ts → Field.ts} +13 -5
- package/src/lib/query-builder/lucene-syntax/{NotOperand.d.ts → NotOperand.ts} +13 -5
- package/src/lib/query-builder/lucene-syntax/{Operand.d.ts → Operand.ts} +21 -7
- package/src/lib/query-builder/sdk-query-builder.spec.ts +159 -0
- package/src/lib/query-builder/{sdk-query-builder.d.ts → sdk-query-builder.ts} +16 -5
- package/src/lib/query-builder/utils/{index.d.ts → index.ts} +49 -12
- package/src/lib/utils/graphql/transforms.spec.ts +150 -0
- package/src/lib/utils/graphql/transforms.ts +99 -0
- package/src/lib/utils/page/common-utils.spec.ts +37 -0
- package/src/lib/utils/page/common-utils.ts +64 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +13 -0
- package/tsconfig.spec.json +9 -0
- package/index.cjs.d.ts +0 -1
- package/index.cjs.default.js +0 -1
- package/index.cjs.js +0 -1953
- package/index.cjs.mjs +0 -2
- package/index.esm.d.ts +0 -1
- package/index.esm.js +0 -1944
- package/src/index.d.ts +0 -6
- package/src/lib/editor/listeners/listeners.d.ts +0 -50
- package/src/lib/editor/sdk-editor-vtl.d.ts +0 -6
- package/src/lib/editor/sdk-editor.d.ts +0 -54
- package/src/lib/utils/graphql/transforms.d.ts +0 -24
- package/src/lib/utils/page/common-utils.d.ts +0 -33
- /package/src/lib/query-builder/lucene-syntax/{index.d.ts → index.ts} +0 -0
- /package/src/lib/utils/{index.d.ts → index.ts} +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Content } from './content/content-api';
|
|
2
|
+
import { ErrorMessages } from './models';
|
|
3
|
+
import { DotcmsClientListener } from './models/types';
|
|
4
|
+
|
|
5
|
+
import { isInsideEditor } from '../editor/sdk-editor';
|
|
6
|
+
|
|
2
7
|
export type ClientOptions = Omit<RequestInit, 'body' | 'method'>;
|
|
8
|
+
|
|
3
9
|
export interface ClientConfig {
|
|
4
10
|
/**
|
|
5
11
|
* The URL of the dotCMS instance.
|
|
@@ -38,6 +44,7 @@ export interface ClientConfig {
|
|
|
38
44
|
*/
|
|
39
45
|
requestOptions?: ClientOptions;
|
|
40
46
|
}
|
|
47
|
+
|
|
41
48
|
export type PageApiOptions = {
|
|
42
49
|
/**
|
|
43
50
|
* The path of the page you want to retrieve.
|
|
@@ -90,6 +97,7 @@ export type PageApiOptions = {
|
|
|
90
97
|
*/
|
|
91
98
|
depth?: number;
|
|
92
99
|
};
|
|
100
|
+
|
|
93
101
|
type NavApiOptions = {
|
|
94
102
|
/**
|
|
95
103
|
* The root path to begin traversing the folder tree.
|
|
@@ -123,6 +131,15 @@ type NavApiOptions = {
|
|
|
123
131
|
*/
|
|
124
132
|
languageId?: number;
|
|
125
133
|
};
|
|
134
|
+
|
|
135
|
+
function getHostURL(url: string): URL | undefined {
|
|
136
|
+
try {
|
|
137
|
+
return new URL(url);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
126
143
|
/**
|
|
127
144
|
* `DotCmsClient` is a TypeScript class that provides methods to interact with the DotCMS REST API.
|
|
128
145
|
* DotCMS is a hybrid-headless CMS and digital experience platform.
|
|
@@ -169,13 +186,49 @@ type NavApiOptions = {
|
|
|
169
186
|
* client.editor.on('changes', (payload) => console.log('Changes detected:', payload));
|
|
170
187
|
* ```
|
|
171
188
|
*/
|
|
172
|
-
export
|
|
173
|
-
#private;
|
|
189
|
+
export class DotCmsClient {
|
|
174
190
|
static instance: DotCmsClient;
|
|
191
|
+
#config: ClientConfig;
|
|
192
|
+
#requestOptions!: ClientOptions;
|
|
193
|
+
#listeners: DotcmsClientListener[] = [];
|
|
194
|
+
|
|
175
195
|
dotcmsUrl?: string;
|
|
176
196
|
content: Content;
|
|
177
|
-
|
|
178
|
-
|
|
197
|
+
|
|
198
|
+
constructor(
|
|
199
|
+
config: ClientConfig = { dotcmsUrl: '', authToken: '', requestOptions: {}, siteId: '' }
|
|
200
|
+
) {
|
|
201
|
+
if (!config.dotcmsUrl) {
|
|
202
|
+
throw new Error("Invalid configuration - 'dotcmsUrl' is required");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.dotcmsUrl = getHostURL(config.dotcmsUrl)?.origin;
|
|
206
|
+
|
|
207
|
+
if (!this.dotcmsUrl) {
|
|
208
|
+
throw new Error("Invalid configuration - 'dotcmsUrl' must be a valid URL");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!config.authToken) {
|
|
212
|
+
throw new Error("Invalid configuration - 'authToken' is required");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.#config = {
|
|
216
|
+
...config,
|
|
217
|
+
dotcmsUrl: this.dotcmsUrl
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
this.#requestOptions = {
|
|
221
|
+
...this.#config.requestOptions,
|
|
222
|
+
headers: {
|
|
223
|
+
Authorization: `Bearer ${this.#config.authToken}`,
|
|
224
|
+
...this.#config.requestOptions?.headers
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
this.content = new Content(this.#requestOptions, this.#config.dotcmsUrl);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
page = {
|
|
179
232
|
/**
|
|
180
233
|
* `page.get` is an asynchronous method of the `DotCmsClient` class that retrieves all the elements of any Page in your dotCMS system in JSON format.
|
|
181
234
|
* It takes a `PageApiOptions` object as a parameter and returns a Promise that resolves to the response from the DotCMS API.
|
|
@@ -194,9 +247,51 @@ export declare class DotCmsClient {
|
|
|
194
247
|
* client.page.get({ path: '/about-us' }).then(response => console.log(response));
|
|
195
248
|
* ```
|
|
196
249
|
*/
|
|
197
|
-
get: (options: PageApiOptions)
|
|
250
|
+
get: async (options: PageApiOptions): Promise<unknown> => {
|
|
251
|
+
this.validatePageOptions(options);
|
|
252
|
+
|
|
253
|
+
const queryParamsObj: Record<string, string> = {};
|
|
254
|
+
for (const [key, value] of Object.entries(options)) {
|
|
255
|
+
if (value === undefined || key === 'path' || key === 'siteId') continue;
|
|
256
|
+
|
|
257
|
+
if (key === 'personaId') {
|
|
258
|
+
queryParamsObj['com.dotmarketing.persona.id'] = String(value);
|
|
259
|
+
} else if (key === 'mode' && value) {
|
|
260
|
+
queryParamsObj['mode'] = String(value);
|
|
261
|
+
} else {
|
|
262
|
+
queryParamsObj[key] = String(value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const queryHostId = options.siteId ?? this.#config.siteId ?? '';
|
|
267
|
+
|
|
268
|
+
if (queryHostId) {
|
|
269
|
+
queryParamsObj['host_id'] = queryHostId;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const queryParams = new URLSearchParams(queryParamsObj).toString();
|
|
273
|
+
|
|
274
|
+
const formattedPath = options.path.startsWith('/') ? options.path : `/${options.path}`;
|
|
275
|
+
const url = `${this.#config.dotcmsUrl}/api/v1/page/json${formattedPath}${
|
|
276
|
+
queryParams ? `?${queryParams}` : ''
|
|
277
|
+
}`;
|
|
278
|
+
|
|
279
|
+
const response = await fetch(url, this.#requestOptions);
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
const error = {
|
|
282
|
+
status: response.status,
|
|
283
|
+
message: ErrorMessages[response.status] || response.statusText
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
console.error(error);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return response.json().then((data) => data.entity);
|
|
291
|
+
}
|
|
198
292
|
};
|
|
199
|
-
|
|
293
|
+
|
|
294
|
+
editor = {
|
|
200
295
|
/**
|
|
201
296
|
* `editor.on` is an asynchronous method of the `DotCmsClient` class that allows you to react to actions issued by the UVE.
|
|
202
297
|
*
|
|
@@ -210,7 +305,22 @@ export declare class DotCmsClient {
|
|
|
210
305
|
* });
|
|
211
306
|
* ```
|
|
212
307
|
*/
|
|
213
|
-
on: (action: string, callbackFn: (payload: unknown) => void) =>
|
|
308
|
+
on: (action: string, callbackFn: (payload: unknown) => void) => {
|
|
309
|
+
if (!isInsideEditor()) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (action === 'changes') {
|
|
314
|
+
const messageCallback = (event: MessageEvent) => {
|
|
315
|
+
if (event.data.name === 'SET_PAGE_DATA') {
|
|
316
|
+
callbackFn(event.data.payload);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
window.addEventListener('message', messageCallback);
|
|
321
|
+
this.#listeners.push({ event: 'message', callback: messageCallback, action });
|
|
322
|
+
}
|
|
323
|
+
},
|
|
214
324
|
/**
|
|
215
325
|
* `editor.off` is a synchronous method of the `DotCmsClient` class that allows you to stop listening and reacting to an action issued by UVE.
|
|
216
326
|
*
|
|
@@ -221,9 +331,19 @@ export declare class DotCmsClient {
|
|
|
221
331
|
* client.editor.off('changes');
|
|
222
332
|
* ```
|
|
223
333
|
*/
|
|
224
|
-
off: (action: string) =>
|
|
334
|
+
off: (action: string) => {
|
|
335
|
+
const listenerIndex = this.#listeners.findIndex(
|
|
336
|
+
(listener) => listener.action === action
|
|
337
|
+
);
|
|
338
|
+
if (listenerIndex !== -1) {
|
|
339
|
+
const listener = this.#listeners[listenerIndex];
|
|
340
|
+
window.removeEventListener(listener.event, listener.callback);
|
|
341
|
+
this.#listeners.splice(listenerIndex, 1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
225
344
|
};
|
|
226
|
-
|
|
345
|
+
|
|
346
|
+
nav = {
|
|
227
347
|
/**
|
|
228
348
|
* `nav.get` is an asynchronous method of the `DotCmsClient` class that retrieves information about the dotCMS file and folder tree.
|
|
229
349
|
* It takes a `NavApiOptions` object as a parameter (with default values) and returns a Promise that resolves to the response from the DotCMS API.
|
|
@@ -240,8 +360,34 @@ export declare class DotCmsClient {
|
|
|
240
360
|
* client.nav.get({ path: '/about-us', depth: 2 }).then(response => console.log(response));
|
|
241
361
|
* ```
|
|
242
362
|
*/
|
|
243
|
-
get: (
|
|
363
|
+
get: async (
|
|
364
|
+
options: NavApiOptions = { depth: 0, path: '/', languageId: 1 }
|
|
365
|
+
): Promise<unknown> => {
|
|
366
|
+
this.validateNavOptions(options);
|
|
367
|
+
|
|
368
|
+
// Extract the 'path' from the options and prepare the rest as query parameters
|
|
369
|
+
const { path, ...queryParamsOptions } = options;
|
|
370
|
+
const queryParamsObj: Record<string, string> = {};
|
|
371
|
+
Object.entries(queryParamsOptions).forEach(([key, value]) => {
|
|
372
|
+
if (value !== undefined) {
|
|
373
|
+
queryParamsObj[key] = String(value);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const queryParams = new URLSearchParams(queryParamsObj).toString();
|
|
378
|
+
|
|
379
|
+
// Format the URL correctly depending on the 'path' value
|
|
380
|
+
const formattedPath = path === '/' ? '/' : `/${path}`;
|
|
381
|
+
const url = `${this.#config.dotcmsUrl}/api/v1/nav${formattedPath}${
|
|
382
|
+
queryParams ? `?${queryParams}` : ''
|
|
383
|
+
}`;
|
|
384
|
+
|
|
385
|
+
const response = await fetch(url, this.#requestOptions);
|
|
386
|
+
|
|
387
|
+
return response.json();
|
|
388
|
+
}
|
|
244
389
|
};
|
|
390
|
+
|
|
245
391
|
/**
|
|
246
392
|
* Initializes the DotCmsClient instance with the provided configuration.
|
|
247
393
|
* If an instance already exists, it returns the existing instance.
|
|
@@ -253,24 +399,44 @@ export declare class DotCmsClient {
|
|
|
253
399
|
* const client = DotCmsClient.init({ dotcmsUrl: 'https://demo.dotcms.com', authToken: 'your-auth-token' });
|
|
254
400
|
* ```
|
|
255
401
|
*/
|
|
256
|
-
static init(config: ClientConfig): DotCmsClient
|
|
402
|
+
static init(config: ClientConfig): DotCmsClient {
|
|
403
|
+
if (this.instance) {
|
|
404
|
+
console.warn(
|
|
405
|
+
'DotCmsClient has already been initialized. Please use the instance to interact with the DotCMS API.'
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return this.instance ?? (this.instance = new DotCmsClient(config));
|
|
410
|
+
}
|
|
411
|
+
|
|
257
412
|
/**
|
|
258
413
|
* Retrieves the DotCMS URL from the instance configuration.
|
|
259
414
|
*
|
|
260
415
|
* @returns {string} - The DotCMS URL.
|
|
261
416
|
*/
|
|
262
|
-
static get dotcmsUrl(): string
|
|
417
|
+
static get dotcmsUrl(): string {
|
|
418
|
+
return (this.instance && this.instance.#config.dotcmsUrl) || '';
|
|
419
|
+
}
|
|
420
|
+
|
|
263
421
|
/**
|
|
264
422
|
* Throws an error if the path is not valid.
|
|
265
423
|
*
|
|
266
424
|
* @returns {string} - The authentication token.
|
|
267
425
|
*/
|
|
268
|
-
private validatePageOptions
|
|
426
|
+
private validatePageOptions(options: PageApiOptions): void {
|
|
427
|
+
if (!options.path) {
|
|
428
|
+
throw new Error("The 'path' parameter is required for the Page API");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
269
432
|
/**
|
|
270
433
|
* Throws an error if the path is not valid.
|
|
271
434
|
*
|
|
272
435
|
* @returns {string} - The authentication token.
|
|
273
436
|
*/
|
|
274
|
-
private validateNavOptions
|
|
437
|
+
private validateNavOptions(options: NavApiOptions): void {
|
|
438
|
+
if (!options.path) {
|
|
439
|
+
throw new Error("The 'path' parameter is required for the Nav API");
|
|
440
|
+
}
|
|
441
|
+
}
|
|
275
442
|
}
|
|
276
|
-
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchPageDataFromInsideUVE,
|
|
3
|
+
listenEditorMessages,
|
|
4
|
+
listenHoveredContentlet,
|
|
5
|
+
preserveScrollOnIframe,
|
|
6
|
+
scrollHandler
|
|
7
|
+
} from './listeners';
|
|
8
|
+
|
|
9
|
+
import { CUSTOMER_ACTIONS, postMessageToEditor } from '../models/client.model';
|
|
10
|
+
|
|
11
|
+
jest.mock('../models/client.model', () => ({
|
|
12
|
+
postMessageToEditor: jest.fn(),
|
|
13
|
+
CUSTOMER_ACTIONS: {
|
|
14
|
+
NAVIGATION_UPDATE: 'set-url',
|
|
15
|
+
SET_BOUNDS: 'set-bounds',
|
|
16
|
+
SET_CONTENTLET: 'set-contentlet',
|
|
17
|
+
IFRAME_SCROLL: 'scroll',
|
|
18
|
+
PING_EDITOR: 'ping-editor',
|
|
19
|
+
CONTENT_CHANGE: 'content-change',
|
|
20
|
+
GET_PAGE_DATA: 'get-page-data',
|
|
21
|
+
NOOP: 'noop'
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const CONTAINER_MOCK = {
|
|
26
|
+
acceptTypes: 'text/html',
|
|
27
|
+
identifier: '123',
|
|
28
|
+
maxContentlets: '1',
|
|
29
|
+
uuid: '123-456'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getContentletTestElement = () => {
|
|
33
|
+
const div = document.createElement('div');
|
|
34
|
+
|
|
35
|
+
div.setAttribute('data-dot-object', 'contentlet');
|
|
36
|
+
div.setAttribute('data-dot-identifier', '123');
|
|
37
|
+
div.setAttribute('data-dot-title', 'title');
|
|
38
|
+
div.setAttribute('data-dot-inode', '123-456');
|
|
39
|
+
div.setAttribute('data-dot-type', 'CONTENT');
|
|
40
|
+
div.setAttribute('data-dot-basetype', 'WIDGET');
|
|
41
|
+
div.setAttribute('data-dot-widget-title', 'widgetTitle');
|
|
42
|
+
div.setAttribute('data-dot-on-number-of-pages', '1');
|
|
43
|
+
div.setAttribute('data-dot-container', JSON.stringify(CONTAINER_MOCK));
|
|
44
|
+
div.innerHTML = 'contentlet';
|
|
45
|
+
|
|
46
|
+
return div;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Observation: We must test the execution of methods as well.
|
|
51
|
+
*/
|
|
52
|
+
describe('listeners', () => {
|
|
53
|
+
it('should listen editor messages', () => {
|
|
54
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
55
|
+
listenEditorMessages();
|
|
56
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should listen to hovered contentlet', () => {
|
|
60
|
+
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
|
|
61
|
+
|
|
62
|
+
listenHoveredContentlet();
|
|
63
|
+
|
|
64
|
+
const target = getContentletTestElement();
|
|
65
|
+
const event = new MouseEvent('pointermove', {
|
|
66
|
+
bubbles: true,
|
|
67
|
+
cancelable: true
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
document.body.appendChild(target);
|
|
71
|
+
target.dispatchEvent(event);
|
|
72
|
+
|
|
73
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
|
|
74
|
+
expect(postMessageToEditor).toHaveBeenCalledWith({
|
|
75
|
+
action: CUSTOMER_ACTIONS.SET_CONTENTLET,
|
|
76
|
+
payload: {
|
|
77
|
+
x: expect.any(Number),
|
|
78
|
+
y: expect.any(Number),
|
|
79
|
+
width: expect.any(Number),
|
|
80
|
+
height: expect.any(Number),
|
|
81
|
+
payload: {
|
|
82
|
+
container: CONTAINER_MOCK,
|
|
83
|
+
contentlet: {
|
|
84
|
+
baseType: 'WIDGET',
|
|
85
|
+
identifier: '123',
|
|
86
|
+
inode: '123-456',
|
|
87
|
+
contentType: 'CONTENT',
|
|
88
|
+
title: 'title',
|
|
89
|
+
widgetTitle: 'widgetTitle',
|
|
90
|
+
onNumberOfPages: '1'
|
|
91
|
+
},
|
|
92
|
+
vtlFiles: null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle scroll', () => {
|
|
99
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
100
|
+
scrollHandler();
|
|
101
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should preserve scroll on iframe', () => {
|
|
105
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
106
|
+
preserveScrollOnIframe();
|
|
107
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('load', expect.any(Function));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should get page data post message to editor', () => {
|
|
111
|
+
fetchPageDataFromInsideUVE('some-url');
|
|
112
|
+
expect(postMessageToEditor).toHaveBeenCalledWith({
|
|
113
|
+
action: CUSTOMER_ACTIONS.GET_PAGE_DATA,
|
|
114
|
+
payload: {
|
|
115
|
+
pathname: 'some-url'
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { CUSTOMER_ACTIONS, postMessageToEditor } from '../models/client.model';
|
|
2
|
+
import { DotCMSPageEditorSubscription, NOTIFY_CUSTOMER } from '../models/listeners.model';
|
|
3
|
+
import {
|
|
4
|
+
findVTLData,
|
|
5
|
+
findDotElement,
|
|
6
|
+
getClosestContainerData,
|
|
7
|
+
getPageElementBound,
|
|
8
|
+
scrollIsInBottom
|
|
9
|
+
} from '../utils/editor.utils';
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface Window {
|
|
13
|
+
lastScrollYPosition: number;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Represents an array of DotCMSPageEditorSubscription objects.
|
|
19
|
+
* Used to store the subscriptions for the editor and unsubscribe later.
|
|
20
|
+
*/
|
|
21
|
+
export const subscriptions: DotCMSPageEditorSubscription[] = [];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sets the bounds of the containers in the editor.
|
|
25
|
+
* Retrieves the containers from the DOM and sends their position data to the editor.
|
|
26
|
+
* @private
|
|
27
|
+
* @memberof DotCMSPageEditor
|
|
28
|
+
*/
|
|
29
|
+
function setBounds(): void {
|
|
30
|
+
const containers = Array.from(
|
|
31
|
+
document.querySelectorAll('[data-dot-object="container"]')
|
|
32
|
+
) as HTMLDivElement[];
|
|
33
|
+
const positionData = getPageElementBound(containers);
|
|
34
|
+
|
|
35
|
+
postMessageToEditor({
|
|
36
|
+
action: CUSTOMER_ACTIONS.SET_BOUNDS,
|
|
37
|
+
payload: positionData
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Listens for editor messages and performs corresponding actions based on the received message.
|
|
43
|
+
*
|
|
44
|
+
* @private
|
|
45
|
+
* @memberof DotCMSPageEditor
|
|
46
|
+
*/
|
|
47
|
+
export function listenEditorMessages(): void {
|
|
48
|
+
const messageCallback = (event: MessageEvent) => {
|
|
49
|
+
switch (event.data) {
|
|
50
|
+
case NOTIFY_CUSTOMER.EMA_REQUEST_BOUNDS: {
|
|
51
|
+
setBounds();
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (event.data.name === NOTIFY_CUSTOMER.EMA_SCROLL_INSIDE_IFRAME) {
|
|
57
|
+
const direction = event.data.direction;
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
(window.scrollY === 0 && direction === 'up') ||
|
|
61
|
+
(scrollIsInBottom() && direction === 'down')
|
|
62
|
+
) {
|
|
63
|
+
// If the iframe scroll is at the top or bottom, do not send anything.
|
|
64
|
+
// This avoids losing the scrollend event.
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const scrollY = direction === 'up' ? -120 : 120;
|
|
69
|
+
window.scrollBy({ left: 0, top: scrollY, behavior: 'smooth' });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
window.addEventListener('message', messageCallback);
|
|
74
|
+
|
|
75
|
+
subscriptions.push({
|
|
76
|
+
type: 'listener',
|
|
77
|
+
event: 'message',
|
|
78
|
+
callback: messageCallback
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Listens for pointer move events and extracts information about the hovered contentlet.
|
|
84
|
+
*
|
|
85
|
+
* @private
|
|
86
|
+
* @memberof DotCMSPageEditor
|
|
87
|
+
*/
|
|
88
|
+
export function listenHoveredContentlet(): void {
|
|
89
|
+
const pointerMoveCallback = (event: PointerEvent) => {
|
|
90
|
+
const foundElement = findDotElement(event.target as HTMLElement);
|
|
91
|
+
|
|
92
|
+
if (!foundElement) return;
|
|
93
|
+
|
|
94
|
+
const { x, y, width, height } = foundElement.getBoundingClientRect();
|
|
95
|
+
|
|
96
|
+
const isContainer = foundElement.dataset?.['dotObject'] === 'container';
|
|
97
|
+
|
|
98
|
+
const contentletForEmptyContainer = {
|
|
99
|
+
identifier: 'TEMP_EMPTY_CONTENTLET',
|
|
100
|
+
title: 'TEMP_EMPTY_CONTENTLET',
|
|
101
|
+
contentType: 'TEMP_EMPTY_CONTENTLET_TYPE',
|
|
102
|
+
inode: 'TEMPY_EMPTY_CONTENTLET_INODE',
|
|
103
|
+
widgetTitle: 'TEMP_EMPTY_CONTENTLET',
|
|
104
|
+
baseType: 'TEMP_EMPTY_CONTENTLET',
|
|
105
|
+
onNumberOfPages: 1
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const contentlet = {
|
|
109
|
+
identifier: foundElement.dataset?.['dotIdentifier'],
|
|
110
|
+
title: foundElement.dataset?.['dotTitle'],
|
|
111
|
+
inode: foundElement.dataset?.['dotInode'],
|
|
112
|
+
contentType: foundElement.dataset?.['dotType'],
|
|
113
|
+
baseType: foundElement.dataset?.['dotBasetype'],
|
|
114
|
+
widgetTitle: foundElement.dataset?.['dotWidgetTitle'],
|
|
115
|
+
onNumberOfPages: foundElement.dataset?.['dotOnNumberOfPages']
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const vtlFiles = findVTLData(foundElement);
|
|
119
|
+
const contentletPayload = {
|
|
120
|
+
container:
|
|
121
|
+
// Here extract dot-container from contentlet if it is Headless
|
|
122
|
+
// or search in parent container if it is VTL
|
|
123
|
+
foundElement.dataset?.['dotContainer']
|
|
124
|
+
? JSON.parse(foundElement.dataset?.['dotContainer'])
|
|
125
|
+
: getClosestContainerData(foundElement),
|
|
126
|
+
contentlet: isContainer ? contentletForEmptyContainer : contentlet,
|
|
127
|
+
vtlFiles
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
postMessageToEditor({
|
|
131
|
+
action: CUSTOMER_ACTIONS.SET_CONTENTLET,
|
|
132
|
+
payload: {
|
|
133
|
+
x,
|
|
134
|
+
y,
|
|
135
|
+
width,
|
|
136
|
+
height,
|
|
137
|
+
payload: contentletPayload
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
document.addEventListener('pointermove', pointerMoveCallback);
|
|
143
|
+
|
|
144
|
+
subscriptions.push({
|
|
145
|
+
type: 'listener',
|
|
146
|
+
event: 'pointermove',
|
|
147
|
+
callback: pointerMoveCallback
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Attaches a scroll event listener to the window
|
|
153
|
+
* and sends a message to the editor when the window is scrolled.
|
|
154
|
+
*
|
|
155
|
+
* @private
|
|
156
|
+
* @memberof DotCMSPageEditor
|
|
157
|
+
*/
|
|
158
|
+
export function scrollHandler(): void {
|
|
159
|
+
const scrollCallback = () => {
|
|
160
|
+
postMessageToEditor({
|
|
161
|
+
action: CUSTOMER_ACTIONS.IFRAME_SCROLL
|
|
162
|
+
});
|
|
163
|
+
window.lastScrollYPosition = window.scrollY;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const scrollEndCallback = () => {
|
|
167
|
+
postMessageToEditor({
|
|
168
|
+
action: CUSTOMER_ACTIONS.IFRAME_SCROLL_END
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
window.addEventListener('scroll', scrollCallback);
|
|
173
|
+
window.addEventListener('scrollend', scrollEndCallback);
|
|
174
|
+
|
|
175
|
+
subscriptions.push({
|
|
176
|
+
type: 'listener',
|
|
177
|
+
event: 'scroll',
|
|
178
|
+
callback: scrollEndCallback
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
subscriptions.push({
|
|
182
|
+
type: 'listener',
|
|
183
|
+
event: 'scroll',
|
|
184
|
+
callback: scrollCallback
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Restores the scroll position of the window when an iframe is loaded.
|
|
190
|
+
* Only used in VTL Pages.
|
|
191
|
+
* @export
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* preserveScrollOnIframe();
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export function preserveScrollOnIframe(): void {
|
|
198
|
+
const preserveScrollCallback = () => {
|
|
199
|
+
window.scrollTo(0, window.lastScrollYPosition);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
window.addEventListener('load', preserveScrollCallback);
|
|
203
|
+
subscriptions.push({
|
|
204
|
+
type: 'listener',
|
|
205
|
+
event: 'scroll',
|
|
206
|
+
callback: preserveScrollCallback
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Sends a message to the editor to get the page data.
|
|
212
|
+
* @param {string} pathname - The pathname of the page.
|
|
213
|
+
* @private
|
|
214
|
+
* @memberof DotCMSPageEditor
|
|
215
|
+
*/
|
|
216
|
+
export function fetchPageDataFromInsideUVE(pathname: string) {
|
|
217
|
+
postMessageToEditor({
|
|
218
|
+
action: CUSTOMER_ACTIONS.GET_PAGE_DATA,
|
|
219
|
+
payload: {
|
|
220
|
+
pathname
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|