@contentful/experiences-core 1.9.0-dev-20240628T2230-7a4f71f.0 → 1.9.0-dev-20240628T2235-7a4f71f.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/package.json +3 -3
- package/dist/communication/sendMessage.d.ts +0 -5
- package/dist/constants.d.ts +0 -112
- package/dist/constants.js +0 -150
- package/dist/constants.js.map +0 -1
- package/dist/deep-binding/DeepReference.d.ts +0 -27
- package/dist/definitions/components.d.ts +0 -9
- package/dist/definitions/styles.d.ts +0 -12
- package/dist/entity/EditorEntityStore.d.ts +0 -34
- package/dist/entity/EditorModeEntityStore.d.ts +0 -29
- package/dist/entity/EntityStore.d.ts +0 -69
- package/dist/entity/EntityStoreBase.d.ts +0 -49
- package/dist/enums.d.ts +0 -6
- package/dist/exports.d.ts +0 -3
- package/dist/exports.js +0 -2
- package/dist/exports.js.map +0 -1
- package/dist/fetchers/createExperience.d.ts +0 -20
- package/dist/fetchers/fetchById.d.ts +0 -20
- package/dist/fetchers/fetchBySlug.d.ts +0 -20
- package/dist/index.d.ts +0 -27
- package/dist/index.js +0 -3370
- package/dist/index.js.map +0 -1
- package/dist/registries/breakpointsRegistry.d.ts +0 -20
- package/dist/registries/designTokenRegistry.d.ts +0 -13
- package/dist/types.d.ts +0 -451
- package/dist/utils/breakpoints.d.ts +0 -12
- package/dist/utils/components.d.ts +0 -5
- package/dist/utils/domValues.d.ts +0 -15
- package/dist/utils/isLink.d.ts +0 -5
- package/dist/utils/isLinkToAsset.d.ts +0 -5
- package/dist/utils/pathSchema.d.ts +0 -31
- package/dist/utils/resolveHyperlinkPattern.d.ts +0 -17
- package/dist/utils/styleUtils/ssrStyles.d.ts +0 -51
- package/dist/utils/styleUtils/stylesUtils.d.ts +0 -21
- package/dist/utils/supportedModes.d.ts +0 -5
- package/dist/utils/transformers/transformBoundContentValue.d.ts +0 -8
- package/dist/utils/typeguards.d.ts +0 -6
- package/dist/utils/utils.d.ts +0 -46
- package/dist/utils/validations.d.ts +0 -10
package/dist/index.js
DELETED
|
@@ -1,3370 +0,0 @@
|
|
|
1
|
-
import md5 from 'md5';
|
|
2
|
-
import { z, ZodIssueCode } from 'zod';
|
|
3
|
-
import { BLOCKS } from '@contentful/rich-text-types';
|
|
4
|
-
import { uniqBy } from 'lodash-es';
|
|
5
|
-
|
|
6
|
-
const INCOMING_EVENTS = {
|
|
7
|
-
RequestEditorMode: 'requestEditorMode',
|
|
8
|
-
ExperienceUpdated: 'componentTreeUpdated',
|
|
9
|
-
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
10
|
-
ComponentDragCanceled: 'componentDragCanceled',
|
|
11
|
-
ComponentDragStarted: 'componentDragStarted',
|
|
12
|
-
ComponentDragEnded: 'componentDragEnded',
|
|
13
|
-
ComponentMoveEnded: 'componentMoveEnded',
|
|
14
|
-
CanvasResized: 'canvasResized',
|
|
15
|
-
SelectComponent: 'selectComponent',
|
|
16
|
-
HoverComponent: 'hoverComponent',
|
|
17
|
-
UpdatedEntity: 'updatedEntity',
|
|
18
|
-
AssembliesAdded: 'assembliesAdded',
|
|
19
|
-
AssembliesRegistered: 'assembliesRegistered',
|
|
20
|
-
MouseMove: 'mouseMove',
|
|
21
|
-
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
22
|
-
};
|
|
23
|
-
const CONTENTFUL_COMPONENT_CATEGORY = 'contentful-component';
|
|
24
|
-
const CONTENTFUL_DEFAULT_CATEGORY = 'Contentful';
|
|
25
|
-
const CONTENTFUL_COMPONENTS = {
|
|
26
|
-
section: {
|
|
27
|
-
id: 'contentful-section',
|
|
28
|
-
name: 'Section',
|
|
29
|
-
},
|
|
30
|
-
container: {
|
|
31
|
-
id: 'contentful-container',
|
|
32
|
-
name: 'Container',
|
|
33
|
-
},
|
|
34
|
-
columns: {
|
|
35
|
-
id: 'contentful-columns',
|
|
36
|
-
name: 'Columns',
|
|
37
|
-
},
|
|
38
|
-
singleColumn: {
|
|
39
|
-
id: 'contentful-single-column',
|
|
40
|
-
name: 'Column',
|
|
41
|
-
},
|
|
42
|
-
button: {
|
|
43
|
-
id: 'contentful-button',
|
|
44
|
-
name: 'Button',
|
|
45
|
-
},
|
|
46
|
-
heading: {
|
|
47
|
-
id: 'contentful-heading',
|
|
48
|
-
name: 'Heading',
|
|
49
|
-
},
|
|
50
|
-
image: {
|
|
51
|
-
id: 'contentful-image',
|
|
52
|
-
name: 'Image',
|
|
53
|
-
},
|
|
54
|
-
richText: {
|
|
55
|
-
id: 'contentful-richText',
|
|
56
|
-
name: 'Rich Text',
|
|
57
|
-
},
|
|
58
|
-
text: {
|
|
59
|
-
id: 'contentful-text',
|
|
60
|
-
name: 'Text',
|
|
61
|
-
},
|
|
62
|
-
divider: {
|
|
63
|
-
id: 'contentful-divider',
|
|
64
|
-
name: 'Divider',
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
|
|
68
|
-
const CF_STYLE_ATTRIBUTES = [
|
|
69
|
-
'cfHorizontalAlignment',
|
|
70
|
-
'cfVerticalAlignment',
|
|
71
|
-
'cfMargin',
|
|
72
|
-
'cfPadding',
|
|
73
|
-
'cfBackgroundColor',
|
|
74
|
-
'cfWidth',
|
|
75
|
-
'cfMaxWidth',
|
|
76
|
-
'cfHeight',
|
|
77
|
-
'cfImageAsset',
|
|
78
|
-
'cfImageOptions',
|
|
79
|
-
'cfBackgroundImageUrl',
|
|
80
|
-
'cfBackgroundImageOptions',
|
|
81
|
-
'cfFlexDirection',
|
|
82
|
-
'cfFlexWrap',
|
|
83
|
-
'cfBorder',
|
|
84
|
-
'cfBorderRadius',
|
|
85
|
-
'cfGap',
|
|
86
|
-
'cfFontSize',
|
|
87
|
-
'cfFontWeight',
|
|
88
|
-
'cfLineHeight',
|
|
89
|
-
'cfLetterSpacing',
|
|
90
|
-
'cfTextColor',
|
|
91
|
-
'cfTextAlign',
|
|
92
|
-
'cfTextTransform',
|
|
93
|
-
'cfTextBold',
|
|
94
|
-
'cfTextItalic',
|
|
95
|
-
'cfTextUnderline',
|
|
96
|
-
// For backwards compatibility
|
|
97
|
-
// we need to keep those in this constant array
|
|
98
|
-
// so that omit() in <VisualEditorBlock> and <CompositionBlock>
|
|
99
|
-
// can filter them out and not pass as props
|
|
100
|
-
'cfBackgroundImageScaling',
|
|
101
|
-
'cfBackgroundImageAlignment',
|
|
102
|
-
'cfBackgroundImageAlignmentVertical',
|
|
103
|
-
'cfBackgroundImageAlignmentHorizontal',
|
|
104
|
-
];
|
|
105
|
-
const EMPTY_CONTAINER_HEIGHT = '80px';
|
|
106
|
-
const DEFAULT_IMAGE_WIDTH = '500px';
|
|
107
|
-
var PostMessageMethods;
|
|
108
|
-
(function (PostMessageMethods) {
|
|
109
|
-
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
110
|
-
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
111
|
-
})(PostMessageMethods || (PostMessageMethods = {}));
|
|
112
|
-
const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
|
|
113
|
-
|
|
114
|
-
const structureComponents = new Set([
|
|
115
|
-
CONTENTFUL_COMPONENTS.section.id,
|
|
116
|
-
CONTENTFUL_COMPONENTS.columns.id,
|
|
117
|
-
CONTENTFUL_COMPONENTS.container.id,
|
|
118
|
-
CONTENTFUL_COMPONENTS.singleColumn.id,
|
|
119
|
-
]);
|
|
120
|
-
const isContentfulStructureComponent = (componentId) => structureComponents.has(componentId ?? '');
|
|
121
|
-
const isComponentAllowedOnRoot = (componentId) => isContentfulStructureComponent(componentId) || componentId === CONTENTFUL_COMPONENTS.divider.id;
|
|
122
|
-
const isStructureWithRelativeHeight = (componentId, height) => {
|
|
123
|
-
return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const findOutermostCoordinates = (first, second) => {
|
|
127
|
-
return {
|
|
128
|
-
top: Math.min(first.top, second.top),
|
|
129
|
-
right: Math.max(first.right, second.right),
|
|
130
|
-
bottom: Math.max(first.bottom, second.bottom),
|
|
131
|
-
left: Math.min(first.left, second.left),
|
|
132
|
-
};
|
|
133
|
-
};
|
|
134
|
-
const getElementCoordinates = (element) => {
|
|
135
|
-
const rect = element.getBoundingClientRect();
|
|
136
|
-
/**
|
|
137
|
-
* If element does not have children, or element has it's own width or height,
|
|
138
|
-
* return the element's coordinates.
|
|
139
|
-
*/
|
|
140
|
-
if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
|
|
141
|
-
return rect;
|
|
142
|
-
}
|
|
143
|
-
const rects = [];
|
|
144
|
-
/**
|
|
145
|
-
* If element has children, or element does not have it's own width and height,
|
|
146
|
-
* we find the cordinates of the children, and assume the outermost coordinates of the children
|
|
147
|
-
* as the coordinate of the element.
|
|
148
|
-
*
|
|
149
|
-
* E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
|
|
150
|
-
* The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
|
|
151
|
-
*/
|
|
152
|
-
for (const child of element.children) {
|
|
153
|
-
const childRect = getElementCoordinates(child);
|
|
154
|
-
if (childRect.width !== 0 || childRect.height !== 0) {
|
|
155
|
-
const { top, right, bottom, left } = childRect;
|
|
156
|
-
rects.push({ top, right, bottom, left });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (rects.length === 0) {
|
|
160
|
-
return rect;
|
|
161
|
-
}
|
|
162
|
-
const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
|
|
163
|
-
return DOMRect.fromRect({
|
|
164
|
-
x: left,
|
|
165
|
-
y: top,
|
|
166
|
-
height: bottom - top,
|
|
167
|
-
width: right - left,
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
class ParseError extends Error {
|
|
172
|
-
constructor(message) {
|
|
173
|
-
super(message);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const isValidJsonObject = (s) => {
|
|
177
|
-
try {
|
|
178
|
-
const result = JSON.parse(s);
|
|
179
|
-
if ('object' !== typeof result) {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
catch (e) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const doesMismatchMessageSchema = (event) => {
|
|
189
|
-
try {
|
|
190
|
-
tryParseMessage(event);
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
if (e instanceof ParseError) {
|
|
195
|
-
return e.message;
|
|
196
|
-
}
|
|
197
|
-
throw e;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
const tryParseMessage = (event) => {
|
|
201
|
-
if (!event.data) {
|
|
202
|
-
throw new ParseError('Field event.data is missing');
|
|
203
|
-
}
|
|
204
|
-
if ('string' !== typeof event.data) {
|
|
205
|
-
throw new ParseError(`Field event.data must be a string, instead of '${typeof event.data}'`);
|
|
206
|
-
}
|
|
207
|
-
if (!isValidJsonObject(event.data)) {
|
|
208
|
-
throw new ParseError('Field event.data must be a valid JSON object serialized as string');
|
|
209
|
-
}
|
|
210
|
-
const eventData = JSON.parse(event.data);
|
|
211
|
-
if (!eventData.source) {
|
|
212
|
-
throw new ParseError(`Field eventData.source must be equal to 'composability-app'`);
|
|
213
|
-
}
|
|
214
|
-
if ('composability-app' !== eventData.source) {
|
|
215
|
-
throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
|
|
216
|
-
}
|
|
217
|
-
// check eventData.eventType
|
|
218
|
-
const supportedEventTypes = Object.values(INCOMING_EVENTS);
|
|
219
|
-
if (!supportedEventTypes.includes(eventData.eventType)) {
|
|
220
|
-
// Expected message: This message is handled in the EntityStore to store fetched entities
|
|
221
|
-
if (eventData.eventType !== PostMessageMethods.REQUESTED_ENTITIES) {
|
|
222
|
-
throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return eventData;
|
|
226
|
-
};
|
|
227
|
-
const validateExperienceBuilderConfig = ({ locale, isEditorMode, }) => {
|
|
228
|
-
if (isEditorMode) {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (!locale) {
|
|
232
|
-
throw new Error('Parameter "locale" is required for experience builder initialization outside of editor mode');
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Keep this for backwards compatilibity - deleting this would be a breaking change
|
|
237
|
-
// because existing components on a users experience will have the width value as fill
|
|
238
|
-
// rather than 100%
|
|
239
|
-
const transformFill = (value) => (value === 'fill' ? '100%' : value);
|
|
240
|
-
const transformGridColumn = (span) => {
|
|
241
|
-
if (!span) {
|
|
242
|
-
return {};
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
gridColumn: `span ${span}`,
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
const transformBorderStyle = (value) => {
|
|
249
|
-
if (!value)
|
|
250
|
-
return {};
|
|
251
|
-
const parts = value.split(' ');
|
|
252
|
-
// Just accept the passed value
|
|
253
|
-
if (parts.length < 3)
|
|
254
|
-
return { border: value };
|
|
255
|
-
const [borderSize, borderStyle, ...borderColorParts] = parts;
|
|
256
|
-
const borderColor = borderColorParts.join(' ');
|
|
257
|
-
return {
|
|
258
|
-
border: `${borderSize} ${borderStyle} ${borderColor}`,
|
|
259
|
-
};
|
|
260
|
-
};
|
|
261
|
-
const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection = 'column') => cfFlexDirection === 'row'
|
|
262
|
-
? {
|
|
263
|
-
alignItems: cfHorizontalAlignment,
|
|
264
|
-
justifyContent: cfVerticalAlignment === 'center' ? `safe ${cfVerticalAlignment}` : cfVerticalAlignment,
|
|
265
|
-
}
|
|
266
|
-
: {
|
|
267
|
-
alignItems: cfVerticalAlignment,
|
|
268
|
-
justifyContent: cfHorizontalAlignment === 'center'
|
|
269
|
-
? `safe ${cfHorizontalAlignment}`
|
|
270
|
-
: cfHorizontalAlignment,
|
|
271
|
-
};
|
|
272
|
-
const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
|
|
273
|
-
const matchBackgroundSize = (scaling) => {
|
|
274
|
-
if ('fill' === scaling)
|
|
275
|
-
return 'cover';
|
|
276
|
-
if ('fit' === scaling)
|
|
277
|
-
return 'contain';
|
|
278
|
-
};
|
|
279
|
-
const matchBackgroundPosition = (alignment) => {
|
|
280
|
-
if (!alignment || 'string' !== typeof alignment) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
|
|
284
|
-
// Special case for handling single values
|
|
285
|
-
// for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
|
|
286
|
-
if (horizontalAlignment && !verticalAlignment) {
|
|
287
|
-
const singleValue = horizontalAlignment;
|
|
288
|
-
switch (singleValue) {
|
|
289
|
-
case 'left':
|
|
290
|
-
horizontalAlignment = 'left';
|
|
291
|
-
verticalAlignment = 'center';
|
|
292
|
-
break;
|
|
293
|
-
case 'right':
|
|
294
|
-
horizontalAlignment = 'right';
|
|
295
|
-
verticalAlignment = 'center';
|
|
296
|
-
break;
|
|
297
|
-
case 'center':
|
|
298
|
-
horizontalAlignment = 'center';
|
|
299
|
-
verticalAlignment = 'center';
|
|
300
|
-
break;
|
|
301
|
-
case 'top':
|
|
302
|
-
horizontalAlignment = 'center';
|
|
303
|
-
verticalAlignment = 'top';
|
|
304
|
-
break;
|
|
305
|
-
case 'bottom':
|
|
306
|
-
horizontalAlignment = 'center';
|
|
307
|
-
verticalAlignment = 'bottom';
|
|
308
|
-
break;
|
|
309
|
-
// just fall down to the normal validation logic for horiz and vert
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
|
|
313
|
-
const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
|
|
314
|
-
horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
|
|
315
|
-
verticalAlignment = isVerticalValid ? verticalAlignment : 'top';
|
|
316
|
-
return `${horizontalAlignment} ${verticalAlignment}`;
|
|
317
|
-
};
|
|
318
|
-
if (!cfBackgroundImageUrl) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
let backgroundImage;
|
|
322
|
-
let backgroundImageSet;
|
|
323
|
-
if (typeof cfBackgroundImageUrl === 'string') {
|
|
324
|
-
backgroundImage = `url(${cfBackgroundImageUrl})`;
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
|
|
328
|
-
backgroundImage = `url(${cfBackgroundImageUrl.url})`;
|
|
329
|
-
backgroundImageSet = `image-set(${imgSet})`;
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
backgroundImage,
|
|
333
|
-
backgroundImage2: backgroundImageSet,
|
|
334
|
-
backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
|
|
335
|
-
backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
|
|
336
|
-
backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const toCSSAttribute = (key) => {
|
|
341
|
-
let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
|
342
|
-
// Remove the number from the end of the key to allow for overrides on style properties
|
|
343
|
-
val = val.replace(/\d+$/, '');
|
|
344
|
-
return val;
|
|
345
|
-
};
|
|
346
|
-
const buildStyleTag = ({ styles, nodeId }) => {
|
|
347
|
-
const stylesStr = Object.entries(styles)
|
|
348
|
-
.filter(([, value]) => value !== undefined)
|
|
349
|
-
.reduce((acc, [key, value]) => `${acc}
|
|
350
|
-
${toCSSAttribute(key)}: ${value};`, '');
|
|
351
|
-
const className = `cfstyles-${nodeId ? nodeId : md5(stylesStr)}`;
|
|
352
|
-
const styleRule = `.${className}{ ${stylesStr} }`;
|
|
353
|
-
return [className, styleRule];
|
|
354
|
-
};
|
|
355
|
-
const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection, cfFlexWrap, cfMargin, cfPadding, cfBackgroundColor, cfWidth, cfHeight, cfMaxWidth, cfBorder, cfBorderRadius, cfGap, cfBackgroundImageUrl, cfBackgroundImageOptions, cfFontSize, cfFontWeight, cfImageOptions, cfLineHeight, cfLetterSpacing, cfTextColor, cfTextAlign, cfTextTransform, cfTextBold, cfTextItalic, cfTextUnderline, cfColumnSpan, }) => {
|
|
356
|
-
return {
|
|
357
|
-
margin: cfMargin,
|
|
358
|
-
padding: cfPadding,
|
|
359
|
-
backgroundColor: cfBackgroundColor,
|
|
360
|
-
width: transformFill(cfWidth || cfImageOptions?.width),
|
|
361
|
-
height: transformFill(cfHeight || cfImageOptions?.height),
|
|
362
|
-
maxWidth: cfMaxWidth,
|
|
363
|
-
...transformGridColumn(cfColumnSpan),
|
|
364
|
-
...transformBorderStyle(cfBorder),
|
|
365
|
-
borderRadius: cfBorderRadius,
|
|
366
|
-
gap: cfGap,
|
|
367
|
-
...transformAlignment(cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection),
|
|
368
|
-
flexDirection: cfFlexDirection,
|
|
369
|
-
flexWrap: cfFlexWrap,
|
|
370
|
-
...transformBackgroundImage(cfBackgroundImageUrl, cfBackgroundImageOptions),
|
|
371
|
-
fontSize: cfFontSize,
|
|
372
|
-
fontWeight: cfTextBold ? 'bold' : cfFontWeight,
|
|
373
|
-
fontStyle: cfTextItalic ? 'italic' : 'normal',
|
|
374
|
-
lineHeight: cfLineHeight,
|
|
375
|
-
letterSpacing: cfLetterSpacing,
|
|
376
|
-
color: cfTextColor,
|
|
377
|
-
textAlign: cfTextAlign,
|
|
378
|
-
textTransform: cfTextTransform,
|
|
379
|
-
textDecoration: cfTextUnderline ? 'underline' : 'none',
|
|
380
|
-
boxSizing: 'border-box',
|
|
381
|
-
objectFit: cfImageOptions?.objectFit,
|
|
382
|
-
objectPosition: cfImageOptions?.objectPosition,
|
|
383
|
-
};
|
|
384
|
-
};
|
|
385
|
-
/**
|
|
386
|
-
* Container/section default behavior:
|
|
387
|
-
* Default height => height: EMPTY_CONTAINER_HEIGHT
|
|
388
|
-
* If a container component has children => height: 'fit-content'
|
|
389
|
-
*/
|
|
390
|
-
const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
|
|
391
|
-
if (!blockId || !isContentfulStructureComponent(blockId) || value !== 'auto') {
|
|
392
|
-
return value;
|
|
393
|
-
}
|
|
394
|
-
if (children.length) {
|
|
395
|
-
return '100%';
|
|
396
|
-
}
|
|
397
|
-
return EMPTY_CONTAINER_HEIGHT;
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
// These styles get added to every component, user custom or contentful provided
|
|
401
|
-
const builtInStyles = {
|
|
402
|
-
cfVerticalAlignment: {
|
|
403
|
-
validations: {
|
|
404
|
-
in: [
|
|
405
|
-
{
|
|
406
|
-
value: 'start',
|
|
407
|
-
displayName: 'Align left',
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
value: 'center',
|
|
411
|
-
displayName: 'Align center',
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
value: 'end',
|
|
415
|
-
displayName: 'Align right',
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
},
|
|
419
|
-
type: 'Text',
|
|
420
|
-
group: 'style',
|
|
421
|
-
description: 'The horizontal alignment of the section',
|
|
422
|
-
defaultValue: 'center',
|
|
423
|
-
displayName: 'Vertical alignment',
|
|
424
|
-
},
|
|
425
|
-
cfHorizontalAlignment: {
|
|
426
|
-
validations: {
|
|
427
|
-
in: [
|
|
428
|
-
{
|
|
429
|
-
value: 'start',
|
|
430
|
-
displayName: 'Align top',
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
value: 'center',
|
|
434
|
-
displayName: 'Align center',
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
value: 'end',
|
|
438
|
-
displayName: 'Align bottom',
|
|
439
|
-
},
|
|
440
|
-
],
|
|
441
|
-
},
|
|
442
|
-
type: 'Text',
|
|
443
|
-
group: 'style',
|
|
444
|
-
description: 'The horizontal alignment of the section',
|
|
445
|
-
defaultValue: 'center',
|
|
446
|
-
displayName: 'Horizontal alignment',
|
|
447
|
-
},
|
|
448
|
-
cfMargin: {
|
|
449
|
-
displayName: 'Margin',
|
|
450
|
-
type: 'Text',
|
|
451
|
-
group: 'style',
|
|
452
|
-
description: 'The margin of the section',
|
|
453
|
-
defaultValue: '0 0 0 0',
|
|
454
|
-
},
|
|
455
|
-
cfPadding: {
|
|
456
|
-
displayName: 'Padding',
|
|
457
|
-
type: 'Text',
|
|
458
|
-
group: 'style',
|
|
459
|
-
description: 'The padding of the section',
|
|
460
|
-
defaultValue: '0 0 0 0',
|
|
461
|
-
},
|
|
462
|
-
cfBackgroundColor: {
|
|
463
|
-
displayName: 'Background color',
|
|
464
|
-
type: 'Text',
|
|
465
|
-
group: 'style',
|
|
466
|
-
description: 'The background color of the section',
|
|
467
|
-
},
|
|
468
|
-
cfWidth: {
|
|
469
|
-
displayName: 'Width',
|
|
470
|
-
type: 'Text',
|
|
471
|
-
group: 'style',
|
|
472
|
-
description: 'The width of the section',
|
|
473
|
-
defaultValue: '100%',
|
|
474
|
-
},
|
|
475
|
-
cfHeight: {
|
|
476
|
-
displayName: 'Height',
|
|
477
|
-
type: 'Text',
|
|
478
|
-
group: 'style',
|
|
479
|
-
description: 'The height of the section',
|
|
480
|
-
defaultValue: 'fit-content',
|
|
481
|
-
},
|
|
482
|
-
cfMaxWidth: {
|
|
483
|
-
displayName: 'Max width',
|
|
484
|
-
type: 'Text',
|
|
485
|
-
group: 'style',
|
|
486
|
-
description: 'The max-width of the section',
|
|
487
|
-
defaultValue: 'none',
|
|
488
|
-
},
|
|
489
|
-
cfFlexDirection: {
|
|
490
|
-
displayName: 'Direction',
|
|
491
|
-
type: 'Text',
|
|
492
|
-
group: 'style',
|
|
493
|
-
description: 'The orientation of the section',
|
|
494
|
-
defaultValue: 'column',
|
|
495
|
-
},
|
|
496
|
-
cfFlexWrap: {
|
|
497
|
-
displayName: 'Wrap objects',
|
|
498
|
-
type: 'Text',
|
|
499
|
-
group: 'style',
|
|
500
|
-
description: 'Wrap objects',
|
|
501
|
-
defaultValue: 'nowrap',
|
|
502
|
-
},
|
|
503
|
-
cfBorder: {
|
|
504
|
-
displayName: 'Border',
|
|
505
|
-
type: 'Text',
|
|
506
|
-
group: 'style',
|
|
507
|
-
description: 'The border of the section',
|
|
508
|
-
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
509
|
-
},
|
|
510
|
-
cfGap: {
|
|
511
|
-
displayName: 'Gap',
|
|
512
|
-
type: 'Text',
|
|
513
|
-
group: 'style',
|
|
514
|
-
description: 'The spacing between the elements of the section',
|
|
515
|
-
defaultValue: '0px',
|
|
516
|
-
},
|
|
517
|
-
cfHyperlink: {
|
|
518
|
-
displayName: 'Hyperlink',
|
|
519
|
-
type: 'Hyperlink',
|
|
520
|
-
defaultValue: '',
|
|
521
|
-
validations: {
|
|
522
|
-
format: 'URL',
|
|
523
|
-
},
|
|
524
|
-
description: 'hyperlink for section or container',
|
|
525
|
-
},
|
|
526
|
-
cfOpenInNewTab: {
|
|
527
|
-
displayName: 'Hyperlink behaviour',
|
|
528
|
-
type: 'Boolean',
|
|
529
|
-
defaultValue: false,
|
|
530
|
-
description: 'To open hyperlink in new Tab or not',
|
|
531
|
-
},
|
|
532
|
-
};
|
|
533
|
-
const optionalBuiltInStyles = {
|
|
534
|
-
cfFontSize: {
|
|
535
|
-
displayName: 'Font Size',
|
|
536
|
-
type: 'Text',
|
|
537
|
-
group: 'style',
|
|
538
|
-
description: 'The font size of the element',
|
|
539
|
-
defaultValue: '16px',
|
|
540
|
-
},
|
|
541
|
-
cfFontWeight: {
|
|
542
|
-
validations: {
|
|
543
|
-
in: [
|
|
544
|
-
{
|
|
545
|
-
value: '400',
|
|
546
|
-
displayName: 'Normal',
|
|
547
|
-
},
|
|
548
|
-
{
|
|
549
|
-
value: '500',
|
|
550
|
-
displayName: 'Medium',
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
value: '600',
|
|
554
|
-
displayName: 'Semi Bold',
|
|
555
|
-
},
|
|
556
|
-
],
|
|
557
|
-
},
|
|
558
|
-
displayName: 'Font Weight',
|
|
559
|
-
type: 'Text',
|
|
560
|
-
group: 'style',
|
|
561
|
-
description: 'The font weight of the element',
|
|
562
|
-
defaultValue: '400',
|
|
563
|
-
},
|
|
564
|
-
cfImageAsset: {
|
|
565
|
-
displayName: 'Image',
|
|
566
|
-
type: 'Media',
|
|
567
|
-
description: 'Image to display',
|
|
568
|
-
},
|
|
569
|
-
cfImageOptions: {
|
|
570
|
-
displayName: 'Image options',
|
|
571
|
-
type: 'Object',
|
|
572
|
-
group: 'style',
|
|
573
|
-
defaultValue: {
|
|
574
|
-
width: DEFAULT_IMAGE_WIDTH,
|
|
575
|
-
height: '100%',
|
|
576
|
-
targetSize: DEFAULT_IMAGE_WIDTH,
|
|
577
|
-
},
|
|
578
|
-
},
|
|
579
|
-
cfBackgroundImageUrl: {
|
|
580
|
-
displayName: 'Background image',
|
|
581
|
-
type: 'Media',
|
|
582
|
-
description: 'Background image for component',
|
|
583
|
-
},
|
|
584
|
-
cfBackgroundImageOptions: {
|
|
585
|
-
displayName: 'Background image options',
|
|
586
|
-
type: 'Object',
|
|
587
|
-
group: 'style',
|
|
588
|
-
defaultValue: {
|
|
589
|
-
scaling: 'fill',
|
|
590
|
-
alignment: 'left top',
|
|
591
|
-
targetSize: '2000px',
|
|
592
|
-
},
|
|
593
|
-
},
|
|
594
|
-
cfBorderRadius: {
|
|
595
|
-
displayName: 'Border Radius',
|
|
596
|
-
type: 'Text',
|
|
597
|
-
group: 'style',
|
|
598
|
-
description: 'The border radius of the section',
|
|
599
|
-
defaultValue: '0px',
|
|
600
|
-
},
|
|
601
|
-
cfLineHeight: {
|
|
602
|
-
displayName: 'Line Height',
|
|
603
|
-
type: 'Text',
|
|
604
|
-
group: 'style',
|
|
605
|
-
description: 'The line height of the element',
|
|
606
|
-
defaultValue: '20px',
|
|
607
|
-
},
|
|
608
|
-
cfLetterSpacing: {
|
|
609
|
-
displayName: 'Letter Spacing',
|
|
610
|
-
type: 'Text',
|
|
611
|
-
group: 'style',
|
|
612
|
-
description: 'The letter spacing of the element',
|
|
613
|
-
defaultValue: '0px',
|
|
614
|
-
},
|
|
615
|
-
cfTextColor: {
|
|
616
|
-
displayName: 'Text Color',
|
|
617
|
-
type: 'Text',
|
|
618
|
-
group: 'style',
|
|
619
|
-
description: 'The text color of the element',
|
|
620
|
-
defaultValue: 'rgba(0, 0, 0, 1)',
|
|
621
|
-
},
|
|
622
|
-
cfTextAlign: {
|
|
623
|
-
validations: {
|
|
624
|
-
in: [
|
|
625
|
-
{
|
|
626
|
-
value: 'left',
|
|
627
|
-
displayName: 'Align left',
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
value: 'center',
|
|
631
|
-
displayName: 'Align center',
|
|
632
|
-
},
|
|
633
|
-
{
|
|
634
|
-
value: 'right',
|
|
635
|
-
displayName: 'Align right',
|
|
636
|
-
},
|
|
637
|
-
],
|
|
638
|
-
},
|
|
639
|
-
displayName: 'Text Align',
|
|
640
|
-
type: 'Text',
|
|
641
|
-
group: 'style',
|
|
642
|
-
description: 'The text alignment of the element',
|
|
643
|
-
defaultValue: 'left',
|
|
644
|
-
},
|
|
645
|
-
cfTextTransform: {
|
|
646
|
-
validations: {
|
|
647
|
-
in: [
|
|
648
|
-
{
|
|
649
|
-
value: 'none',
|
|
650
|
-
displayName: 'Normal',
|
|
651
|
-
},
|
|
652
|
-
{
|
|
653
|
-
value: 'capitalize',
|
|
654
|
-
displayName: 'Capitalize',
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
value: 'uppercase',
|
|
658
|
-
displayName: 'Uppercase',
|
|
659
|
-
},
|
|
660
|
-
{
|
|
661
|
-
value: 'lowercase',
|
|
662
|
-
displayName: 'Lowercase',
|
|
663
|
-
},
|
|
664
|
-
],
|
|
665
|
-
},
|
|
666
|
-
displayName: 'Text Transform',
|
|
667
|
-
type: 'Text',
|
|
668
|
-
group: 'style',
|
|
669
|
-
description: 'The text transform of the element',
|
|
670
|
-
defaultValue: 'none',
|
|
671
|
-
},
|
|
672
|
-
cfTextBold: {
|
|
673
|
-
displayName: 'Bold',
|
|
674
|
-
type: 'Boolean',
|
|
675
|
-
group: 'style',
|
|
676
|
-
description: 'The text bold of the element',
|
|
677
|
-
defaultValue: false,
|
|
678
|
-
},
|
|
679
|
-
cfTextItalic: {
|
|
680
|
-
displayName: 'Italic',
|
|
681
|
-
type: 'Boolean',
|
|
682
|
-
group: 'style',
|
|
683
|
-
description: 'The text italic of the element',
|
|
684
|
-
defaultValue: false,
|
|
685
|
-
},
|
|
686
|
-
cfTextUnderline: {
|
|
687
|
-
displayName: 'Underline',
|
|
688
|
-
type: 'Boolean',
|
|
689
|
-
group: 'style',
|
|
690
|
-
description: 'The text underline of the element',
|
|
691
|
-
defaultValue: false,
|
|
692
|
-
},
|
|
693
|
-
};
|
|
694
|
-
const sectionBuiltInStyles = {
|
|
695
|
-
...builtInStyles,
|
|
696
|
-
cfBorderRadius: optionalBuiltInStyles.cfBorderRadius,
|
|
697
|
-
cfBackgroundImageUrl: optionalBuiltInStyles.cfBackgroundImageUrl,
|
|
698
|
-
cfBackgroundImageOptions: optionalBuiltInStyles.cfBackgroundImageOptions,
|
|
699
|
-
};
|
|
700
|
-
const containerBuiltInStyles = {
|
|
701
|
-
...builtInStyles,
|
|
702
|
-
cfBorderRadius: optionalBuiltInStyles.cfBorderRadius,
|
|
703
|
-
cfBackgroundImageUrl: optionalBuiltInStyles.cfBackgroundImageUrl,
|
|
704
|
-
cfBackgroundImageOptions: optionalBuiltInStyles.cfBackgroundImageOptions,
|
|
705
|
-
cfMaxWidth: {
|
|
706
|
-
displayName: 'Max Width',
|
|
707
|
-
type: 'Text',
|
|
708
|
-
group: 'style',
|
|
709
|
-
description: 'The max-width of the section',
|
|
710
|
-
defaultValue: '1192px',
|
|
711
|
-
},
|
|
712
|
-
};
|
|
713
|
-
const dividerBuiltInStyles = {
|
|
714
|
-
cfBorderRadius: optionalBuiltInStyles.cfBorderRadius,
|
|
715
|
-
cfMargin: {
|
|
716
|
-
displayName: 'Margin',
|
|
717
|
-
type: 'Text',
|
|
718
|
-
group: 'style',
|
|
719
|
-
description: 'The margin of the divider',
|
|
720
|
-
defaultValue: '20px 0px 20px 0px',
|
|
721
|
-
},
|
|
722
|
-
cfWidth: {
|
|
723
|
-
displayName: 'Width',
|
|
724
|
-
type: 'Text',
|
|
725
|
-
group: 'style',
|
|
726
|
-
description: 'The width of the divider',
|
|
727
|
-
defaultValue: '100%',
|
|
728
|
-
},
|
|
729
|
-
cfHeight: {
|
|
730
|
-
displayName: 'Height',
|
|
731
|
-
type: 'Text',
|
|
732
|
-
group: 'style',
|
|
733
|
-
description: 'The height of the divider',
|
|
734
|
-
defaultValue: '2px',
|
|
735
|
-
},
|
|
736
|
-
cfMaxWidth: {
|
|
737
|
-
displayName: 'Max width',
|
|
738
|
-
type: 'Text',
|
|
739
|
-
group: 'style',
|
|
740
|
-
description: 'The max-width of the divider',
|
|
741
|
-
defaultValue: 'none',
|
|
742
|
-
},
|
|
743
|
-
cfBackgroundColor: {
|
|
744
|
-
displayName: 'Background color',
|
|
745
|
-
type: 'Text',
|
|
746
|
-
group: 'style',
|
|
747
|
-
description: 'The background color of the divider',
|
|
748
|
-
defaultValue: 'rgba(0, 0, 0, 1)',
|
|
749
|
-
},
|
|
750
|
-
};
|
|
751
|
-
const singleColumnBuiltInStyles = {
|
|
752
|
-
cfBorderRadius: optionalBuiltInStyles.cfBorderRadius,
|
|
753
|
-
cfBackgroundImageUrl: optionalBuiltInStyles.cfBackgroundImageUrl,
|
|
754
|
-
cfBackgroundImageOptions: optionalBuiltInStyles.cfBackgroundImageOptions,
|
|
755
|
-
cfVerticalAlignment: {
|
|
756
|
-
validations: {
|
|
757
|
-
in: [
|
|
758
|
-
{
|
|
759
|
-
value: 'start',
|
|
760
|
-
displayName: 'Align left',
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
value: 'center',
|
|
764
|
-
displayName: 'Align center',
|
|
765
|
-
},
|
|
766
|
-
{
|
|
767
|
-
value: 'end',
|
|
768
|
-
displayName: 'Align right',
|
|
769
|
-
},
|
|
770
|
-
],
|
|
771
|
-
},
|
|
772
|
-
type: 'Text',
|
|
773
|
-
group: 'style',
|
|
774
|
-
description: 'The horizontal alignment of the column',
|
|
775
|
-
defaultValue: 'center',
|
|
776
|
-
displayName: 'Vertical alignment',
|
|
777
|
-
},
|
|
778
|
-
cfHorizontalAlignment: {
|
|
779
|
-
validations: {
|
|
780
|
-
in: [
|
|
781
|
-
{
|
|
782
|
-
value: 'start',
|
|
783
|
-
displayName: 'Align top',
|
|
784
|
-
},
|
|
785
|
-
{
|
|
786
|
-
value: 'center',
|
|
787
|
-
displayName: 'Align center',
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
value: 'end',
|
|
791
|
-
displayName: 'Align bottom',
|
|
792
|
-
},
|
|
793
|
-
],
|
|
794
|
-
},
|
|
795
|
-
type: 'Text',
|
|
796
|
-
group: 'style',
|
|
797
|
-
description: 'The horizontal alignment of the column',
|
|
798
|
-
defaultValue: 'center',
|
|
799
|
-
displayName: 'Horizontal alignment',
|
|
800
|
-
},
|
|
801
|
-
cfPadding: {
|
|
802
|
-
displayName: 'Padding',
|
|
803
|
-
type: 'Text',
|
|
804
|
-
group: 'style',
|
|
805
|
-
description: 'The padding of the column',
|
|
806
|
-
defaultValue: '0 0 0 0',
|
|
807
|
-
},
|
|
808
|
-
cfBackgroundColor: {
|
|
809
|
-
displayName: 'Background color',
|
|
810
|
-
type: 'Text',
|
|
811
|
-
group: 'style',
|
|
812
|
-
description: 'The background color of the column',
|
|
813
|
-
},
|
|
814
|
-
cfFlexDirection: {
|
|
815
|
-
displayName: 'Direction',
|
|
816
|
-
type: 'Text',
|
|
817
|
-
group: 'style',
|
|
818
|
-
description: 'The orientation of the column',
|
|
819
|
-
defaultValue: 'column',
|
|
820
|
-
},
|
|
821
|
-
cfFlexWrap: {
|
|
822
|
-
displayName: 'Wrap objects',
|
|
823
|
-
type: 'Text',
|
|
824
|
-
group: 'style',
|
|
825
|
-
description: 'Wrap objects',
|
|
826
|
-
defaultValue: 'nowrap',
|
|
827
|
-
},
|
|
828
|
-
cfBorder: {
|
|
829
|
-
displayName: 'Border',
|
|
830
|
-
type: 'Text',
|
|
831
|
-
group: 'style',
|
|
832
|
-
description: 'The border of the column',
|
|
833
|
-
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
834
|
-
},
|
|
835
|
-
cfGap: {
|
|
836
|
-
displayName: 'Gap',
|
|
837
|
-
type: 'Text',
|
|
838
|
-
group: 'style',
|
|
839
|
-
description: 'The spacing between the elements of the column',
|
|
840
|
-
defaultValue: '0px',
|
|
841
|
-
},
|
|
842
|
-
cfColumnSpan: {
|
|
843
|
-
type: 'Text',
|
|
844
|
-
defaultValue: '6',
|
|
845
|
-
group: 'style',
|
|
846
|
-
},
|
|
847
|
-
cfColumnSpanLock: {
|
|
848
|
-
type: 'Boolean',
|
|
849
|
-
defaultValue: false,
|
|
850
|
-
group: 'style',
|
|
851
|
-
},
|
|
852
|
-
};
|
|
853
|
-
const columnsBuiltInStyles = {
|
|
854
|
-
cfBorderRadius: optionalBuiltInStyles.cfBorderRadius,
|
|
855
|
-
cfBackgroundImageUrl: optionalBuiltInStyles.cfBackgroundImageUrl,
|
|
856
|
-
cfBackgroundImageOptions: optionalBuiltInStyles.cfBackgroundImageOptions,
|
|
857
|
-
cfMargin: {
|
|
858
|
-
displayName: 'Margin',
|
|
859
|
-
type: 'Text',
|
|
860
|
-
group: 'style',
|
|
861
|
-
description: 'The margin of the columns',
|
|
862
|
-
defaultValue: '0 0 0 0',
|
|
863
|
-
},
|
|
864
|
-
cfWidth: {
|
|
865
|
-
displayName: 'Width',
|
|
866
|
-
type: 'Text',
|
|
867
|
-
group: 'style',
|
|
868
|
-
description: 'The width of the columns',
|
|
869
|
-
defaultValue: '100%',
|
|
870
|
-
},
|
|
871
|
-
cfMaxWidth: {
|
|
872
|
-
displayName: 'Max width',
|
|
873
|
-
type: 'Text',
|
|
874
|
-
group: 'style',
|
|
875
|
-
description: 'The max-width of the columns',
|
|
876
|
-
defaultValue: '1192px',
|
|
877
|
-
},
|
|
878
|
-
cfPadding: {
|
|
879
|
-
displayName: 'Padding',
|
|
880
|
-
type: 'Text',
|
|
881
|
-
group: 'style',
|
|
882
|
-
description: 'The padding of the columns',
|
|
883
|
-
defaultValue: '10px 10px 10px 10px',
|
|
884
|
-
},
|
|
885
|
-
cfBackgroundColor: {
|
|
886
|
-
displayName: 'Background color',
|
|
887
|
-
type: 'Text',
|
|
888
|
-
group: 'style',
|
|
889
|
-
description: 'The background color of the columns',
|
|
890
|
-
},
|
|
891
|
-
cfBorder: {
|
|
892
|
-
displayName: 'Border',
|
|
893
|
-
type: 'Text',
|
|
894
|
-
group: 'style',
|
|
895
|
-
description: 'The border of the columns',
|
|
896
|
-
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
897
|
-
},
|
|
898
|
-
cfGap: {
|
|
899
|
-
displayName: 'Gap',
|
|
900
|
-
type: 'Text',
|
|
901
|
-
group: 'style',
|
|
902
|
-
description: 'The spacing between the elements of the columns',
|
|
903
|
-
defaultValue: '10px 10px',
|
|
904
|
-
},
|
|
905
|
-
cfColumns: {
|
|
906
|
-
type: 'Text',
|
|
907
|
-
defaultValue: '[6,6]',
|
|
908
|
-
group: 'style',
|
|
909
|
-
},
|
|
910
|
-
cfWrapColumns: {
|
|
911
|
-
type: 'Boolean',
|
|
912
|
-
defaultValue: false,
|
|
913
|
-
group: 'style',
|
|
914
|
-
},
|
|
915
|
-
cfWrapColumnsCount: {
|
|
916
|
-
type: 'Text',
|
|
917
|
-
defaultValue: '2',
|
|
918
|
-
group: 'style',
|
|
919
|
-
},
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
const sectionDefinition = {
|
|
923
|
-
id: CONTENTFUL_COMPONENTS.section.id,
|
|
924
|
-
name: CONTENTFUL_COMPONENTS.section.name,
|
|
925
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
926
|
-
children: true,
|
|
927
|
-
variables: sectionBuiltInStyles,
|
|
928
|
-
tooltip: {
|
|
929
|
-
description: 'Create a new full width section of your experience by dragging this component onto the canvas. Other components and patterns can be added into a section.',
|
|
930
|
-
},
|
|
931
|
-
};
|
|
932
|
-
const containerDefinition = {
|
|
933
|
-
id: CONTENTFUL_COMPONENTS.container.id,
|
|
934
|
-
name: CONTENTFUL_COMPONENTS.container.name,
|
|
935
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
936
|
-
children: true,
|
|
937
|
-
variables: containerBuiltInStyles,
|
|
938
|
-
tooltip: {
|
|
939
|
-
description: 'Create a new area or pattern within your page layout by dragging a container onto the canvas. Other components and patterns can be added into a container.',
|
|
940
|
-
},
|
|
941
|
-
};
|
|
942
|
-
const columnsDefinition = {
|
|
943
|
-
id: CONTENTFUL_COMPONENTS.columns.id,
|
|
944
|
-
name: CONTENTFUL_COMPONENTS.columns.name,
|
|
945
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
946
|
-
children: true,
|
|
947
|
-
variables: columnsBuiltInStyles,
|
|
948
|
-
tooltip: {
|
|
949
|
-
description: 'Add columns to a container to create your desired layout and ensure that the experience is responsive across different screen sizes.',
|
|
950
|
-
},
|
|
951
|
-
};
|
|
952
|
-
const singleColumnDefinition = {
|
|
953
|
-
id: CONTENTFUL_COMPONENTS.singleColumn.id,
|
|
954
|
-
name: CONTENTFUL_COMPONENTS.singleColumn.name,
|
|
955
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
956
|
-
children: true,
|
|
957
|
-
variables: singleColumnBuiltInStyles,
|
|
958
|
-
};
|
|
959
|
-
const dividerDefinition = {
|
|
960
|
-
id: CONTENTFUL_COMPONENTS.divider.id,
|
|
961
|
-
name: CONTENTFUL_COMPONENTS.divider.name,
|
|
962
|
-
category: CONTENTFUL_DEFAULT_CATEGORY,
|
|
963
|
-
children: false,
|
|
964
|
-
variables: dividerBuiltInStyles,
|
|
965
|
-
tooltip: {
|
|
966
|
-
description: 'Drop onto the canvas to add a divider.',
|
|
967
|
-
},
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
let designTokensRegistry = {};
|
|
971
|
-
// This function is used to ensure that the composite values are valid since composite values are optional.
|
|
972
|
-
// Therefore only border and in the future text related design tokens are/will be checked in this funciton.
|
|
973
|
-
// Ensuring values for simple key-value design tokens are not neccessary since they are required via typescript.
|
|
974
|
-
const ensureValidCompositeValues = (designTokenDefinition) => {
|
|
975
|
-
// TODO: add validation logic when text related design tokens are added
|
|
976
|
-
// Border validation
|
|
977
|
-
if (designTokenDefinition.border) {
|
|
978
|
-
for (const borderKey in designTokenDefinition.border) {
|
|
979
|
-
const borderValue = designTokenDefinition.border[borderKey];
|
|
980
|
-
designTokenDefinition.border[borderKey] = {
|
|
981
|
-
width: borderValue.width || '1px',
|
|
982
|
-
style: borderValue.style || 'solid',
|
|
983
|
-
color: borderValue.color || '#000000',
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return designTokenDefinition;
|
|
988
|
-
};
|
|
989
|
-
/**
|
|
990
|
-
* Register design tokens styling
|
|
991
|
-
* @param designTokenDefinition - {[key:string]: Record<string, string>}
|
|
992
|
-
* @returns void
|
|
993
|
-
*/
|
|
994
|
-
const defineDesignTokens = (designTokenDefinition) => {
|
|
995
|
-
Object.assign(designTokensRegistry, ensureValidCompositeValues(designTokenDefinition));
|
|
996
|
-
};
|
|
997
|
-
const templateStringRegex = /\${(.+?)}/g;
|
|
998
|
-
const getDesignTokenRegistration = (breakpointValue, variableName) => {
|
|
999
|
-
if (!breakpointValue)
|
|
1000
|
-
return breakpointValue;
|
|
1001
|
-
let resolvedValue = '';
|
|
1002
|
-
for (const part of breakpointValue.split(' ')) {
|
|
1003
|
-
const tokenValue = templateStringRegex.test(part)
|
|
1004
|
-
? resolveSimpleDesignToken(part, variableName)
|
|
1005
|
-
: part;
|
|
1006
|
-
resolvedValue += `${tokenValue} `;
|
|
1007
|
-
}
|
|
1008
|
-
// Not trimming would end up with a trailing space that breaks the check in `calculateNodeDefaultHeight`
|
|
1009
|
-
return resolvedValue.trim();
|
|
1010
|
-
};
|
|
1011
|
-
const resolveSimpleDesignToken = (templateString, variableName) => {
|
|
1012
|
-
const nonTemplateValue = templateString.replace(templateStringRegex, '$1');
|
|
1013
|
-
const [tokenCategory, tokenName] = nonTemplateValue.split('.');
|
|
1014
|
-
const tokenValues = designTokensRegistry[tokenCategory];
|
|
1015
|
-
if (tokenValues && tokenValues[tokenName]) {
|
|
1016
|
-
if (variableName === 'cfBorder') {
|
|
1017
|
-
const { width, style, color } = tokenValues[tokenName];
|
|
1018
|
-
return `${width} ${style} ${color}`;
|
|
1019
|
-
}
|
|
1020
|
-
return tokenValues[tokenName];
|
|
1021
|
-
}
|
|
1022
|
-
if (builtInStyles[variableName]) {
|
|
1023
|
-
return builtInStyles[variableName].defaultValue;
|
|
1024
|
-
}
|
|
1025
|
-
if (optionalBuiltInStyles[variableName]) {
|
|
1026
|
-
return optionalBuiltInStyles[variableName].defaultValue;
|
|
1027
|
-
}
|
|
1028
|
-
return '0px';
|
|
1029
|
-
};
|
|
1030
|
-
// Used in unit tests to reset the design token registry
|
|
1031
|
-
const resetDesignTokenRegistry = () => {
|
|
1032
|
-
designTokensRegistry = {};
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
// If more than one version is supported, use z.union
|
|
1036
|
-
const SchemaVersions = z.literal('2023-09-28');
|
|
1037
|
-
// Keep deprecated versions here just for reference
|
|
1038
|
-
z.union([
|
|
1039
|
-
z.literal('2023-08-23'),
|
|
1040
|
-
z.literal('2023-07-26'),
|
|
1041
|
-
z.literal('2023-06-27'),
|
|
1042
|
-
]);
|
|
1043
|
-
|
|
1044
|
-
const DefinitionPropertyTypeSchema = z.enum([
|
|
1045
|
-
'Text',
|
|
1046
|
-
'RichText',
|
|
1047
|
-
'Number',
|
|
1048
|
-
'Date',
|
|
1049
|
-
'Boolean',
|
|
1050
|
-
'Location',
|
|
1051
|
-
'Media',
|
|
1052
|
-
'Object',
|
|
1053
|
-
'Hyperlink',
|
|
1054
|
-
]);
|
|
1055
|
-
const DefinitionPropertyKeySchema = z
|
|
1056
|
-
.string()
|
|
1057
|
-
.regex(/^[a-zA-Z0-9-_]{1,32}$/, { message: 'Property needs to match: /^[a-zA-Z0-9-_]{1,32}$/' });
|
|
1058
|
-
z.object({
|
|
1059
|
-
id: DefinitionPropertyKeySchema,
|
|
1060
|
-
variables: z.record(DefinitionPropertyKeySchema, z.object({
|
|
1061
|
-
// TODO - extend with definition of validations and defaultValue
|
|
1062
|
-
displayName: z.string().optional(),
|
|
1063
|
-
type: DefinitionPropertyTypeSchema,
|
|
1064
|
-
description: z.string().optional(),
|
|
1065
|
-
group: z.string().optional(),
|
|
1066
|
-
})),
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
const uuidKeySchema = z
|
|
1070
|
-
.string()
|
|
1071
|
-
.regex(/^[a-zA-Z0-9-_]{1,21}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,21}$/' });
|
|
1072
|
-
/**
|
|
1073
|
-
* Property keys for imported components have a limit of 32 characters (to be implemented) while
|
|
1074
|
-
* property keys for patterns have a limit of 54 characters (<32-char-variabl-name>_<21-char-nanoid-id>).
|
|
1075
|
-
* Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
|
|
1076
|
-
*/
|
|
1077
|
-
const propertyKeySchema = z
|
|
1078
|
-
.string()
|
|
1079
|
-
.regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
|
|
1080
|
-
const DataSourceSchema = z.record(uuidKeySchema, z.object({
|
|
1081
|
-
sys: z.object({
|
|
1082
|
-
type: z.literal('Link'),
|
|
1083
|
-
id: z.string(),
|
|
1084
|
-
linkType: z.enum(['Entry', 'Asset']),
|
|
1085
|
-
}),
|
|
1086
|
-
}));
|
|
1087
|
-
const PrimitiveValueSchema = z.union([
|
|
1088
|
-
z.string(),
|
|
1089
|
-
z.boolean(),
|
|
1090
|
-
z.number(),
|
|
1091
|
-
z.record(z.any(), z.any()),
|
|
1092
|
-
z.undefined(),
|
|
1093
|
-
]);
|
|
1094
|
-
const ValuesByBreakpointSchema = z.record(z.lazy(() => PrimitiveValueSchema));
|
|
1095
|
-
const DesignValueSchema = z
|
|
1096
|
-
.object({
|
|
1097
|
-
type: z.literal('DesignValue'),
|
|
1098
|
-
valuesByBreakpoint: ValuesByBreakpointSchema,
|
|
1099
|
-
})
|
|
1100
|
-
.strict();
|
|
1101
|
-
const BoundValueSchema = z
|
|
1102
|
-
.object({
|
|
1103
|
-
type: z.literal('BoundValue'),
|
|
1104
|
-
path: z.string(),
|
|
1105
|
-
})
|
|
1106
|
-
.strict();
|
|
1107
|
-
const HyperlinkValueSchema = z
|
|
1108
|
-
.object({
|
|
1109
|
-
type: z.literal('HyperlinkValue'),
|
|
1110
|
-
linkTargetKey: z.string(),
|
|
1111
|
-
overrides: z.object({}).optional(),
|
|
1112
|
-
})
|
|
1113
|
-
.strict();
|
|
1114
|
-
const UnboundValueSchema = z
|
|
1115
|
-
.object({
|
|
1116
|
-
type: z.literal('UnboundValue'),
|
|
1117
|
-
key: z.string(),
|
|
1118
|
-
})
|
|
1119
|
-
.strict();
|
|
1120
|
-
const ComponentValueSchema = z
|
|
1121
|
-
.object({
|
|
1122
|
-
type: z.literal('ComponentValue'),
|
|
1123
|
-
key: z.string(),
|
|
1124
|
-
})
|
|
1125
|
-
.strict();
|
|
1126
|
-
const ComponentPropertyValueSchema = z.discriminatedUnion('type', [
|
|
1127
|
-
DesignValueSchema,
|
|
1128
|
-
BoundValueSchema,
|
|
1129
|
-
UnboundValueSchema,
|
|
1130
|
-
HyperlinkValueSchema,
|
|
1131
|
-
ComponentValueSchema,
|
|
1132
|
-
]);
|
|
1133
|
-
const BreakpointSchema = z
|
|
1134
|
-
.object({
|
|
1135
|
-
id: propertyKeySchema,
|
|
1136
|
-
query: z.string().regex(/^\*$|^<[0-9*]+px$/),
|
|
1137
|
-
previewSize: z.string(),
|
|
1138
|
-
displayName: z.string(),
|
|
1139
|
-
displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
|
|
1140
|
-
})
|
|
1141
|
-
.strict();
|
|
1142
|
-
const UnboundValuesSchema = z.record(uuidKeySchema, z.object({
|
|
1143
|
-
value: PrimitiveValueSchema,
|
|
1144
|
-
}));
|
|
1145
|
-
// Use helper schema to define a recursive schema with its type correctly below
|
|
1146
|
-
const BaseComponentTreeNodeSchema = z.object({
|
|
1147
|
-
definitionId: DefinitionPropertyKeySchema,
|
|
1148
|
-
displayName: z.string().optional(),
|
|
1149
|
-
slotId: z.string().optional(),
|
|
1150
|
-
variables: z.record(propertyKeySchema, ComponentPropertyValueSchema),
|
|
1151
|
-
});
|
|
1152
|
-
const ComponentTreeNodeSchema = BaseComponentTreeNodeSchema.extend({
|
|
1153
|
-
children: z.lazy(() => ComponentTreeNodeSchema.array()),
|
|
1154
|
-
});
|
|
1155
|
-
const ComponentSettingsSchema = z.object({
|
|
1156
|
-
variableDefinitions: z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
|
|
1157
|
-
z.object({
|
|
1158
|
-
displayName: z.string().optional(),
|
|
1159
|
-
type: DefinitionPropertyTypeSchema,
|
|
1160
|
-
defaultValue: PrimitiveValueSchema.or(ComponentPropertyValueSchema).optional(),
|
|
1161
|
-
description: z.string().optional(),
|
|
1162
|
-
group: z.string().optional(),
|
|
1163
|
-
validations: z
|
|
1164
|
-
.object({
|
|
1165
|
-
required: z.boolean().optional(),
|
|
1166
|
-
format: z.literal('URL').optional(),
|
|
1167
|
-
in: z
|
|
1168
|
-
.array(z.object({
|
|
1169
|
-
value: z.union([z.string(), z.number()]),
|
|
1170
|
-
displayName: z.string().optional(),
|
|
1171
|
-
}))
|
|
1172
|
-
.optional(),
|
|
1173
|
-
})
|
|
1174
|
-
.optional(),
|
|
1175
|
-
})),
|
|
1176
|
-
});
|
|
1177
|
-
const UsedComponentsSchema = z.array(z.object({
|
|
1178
|
-
sys: z.object({
|
|
1179
|
-
type: z.literal('Link'),
|
|
1180
|
-
id: z.string(),
|
|
1181
|
-
linkType: z.literal('Entry'),
|
|
1182
|
-
}),
|
|
1183
|
-
}));
|
|
1184
|
-
const breakpointsRefinement = (value, ctx) => {
|
|
1185
|
-
if (!value.length || value[0].query !== '*') {
|
|
1186
|
-
ctx.addIssue({
|
|
1187
|
-
code: z.ZodIssueCode.custom,
|
|
1188
|
-
message: `The first breakpoint should include the following attributes: { "query": "*" }`,
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
|
|
1192
|
-
// check if the current breakpoint id is found in the rest of the array
|
|
1193
|
-
const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
|
|
1194
|
-
return breakpointIndex !== currentBreakpointIndex;
|
|
1195
|
-
});
|
|
1196
|
-
if (hasDuplicateIds) {
|
|
1197
|
-
ctx.addIssue({
|
|
1198
|
-
code: z.ZodIssueCode.custom,
|
|
1199
|
-
message: `Breakpoint IDs must be unique`,
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
// Extract the queries boundary by removing the special characters around it
|
|
1203
|
-
const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
|
|
1204
|
-
// sort updates queries array in place so we need to create a copy
|
|
1205
|
-
const originalQueries = [...queries];
|
|
1206
|
-
queries.sort((q1, q2) => {
|
|
1207
|
-
if (q1 === '*') {
|
|
1208
|
-
return -1;
|
|
1209
|
-
}
|
|
1210
|
-
if (q2 === '*') {
|
|
1211
|
-
return 1;
|
|
1212
|
-
}
|
|
1213
|
-
return q1 > q2 ? -1 : 1;
|
|
1214
|
-
});
|
|
1215
|
-
if (originalQueries.join('') !== queries.join('')) {
|
|
1216
|
-
ctx.addIssue({
|
|
1217
|
-
code: z.ZodIssueCode.custom,
|
|
1218
|
-
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
};
|
|
1222
|
-
const componentSettingsRefinement = (value, ctx) => {
|
|
1223
|
-
const { componentSettings, usedComponents } = value;
|
|
1224
|
-
if (!componentSettings || !usedComponents) {
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
const localeKey = Object.keys(componentSettings ?? {})[0];
|
|
1228
|
-
if (componentSettings[localeKey] !== undefined && usedComponents[localeKey] !== undefined) {
|
|
1229
|
-
ctx.addIssue({
|
|
1230
|
-
code: z.ZodIssueCode.custom,
|
|
1231
|
-
message: `'componentSettings' field cannot be used in conjunction with 'usedComponents' field`,
|
|
1232
|
-
path: ['componentSettings', localeKey],
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
};
|
|
1236
|
-
const ComponentTreeSchema = z
|
|
1237
|
-
.object({
|
|
1238
|
-
breakpoints: z.array(BreakpointSchema).superRefine(breakpointsRefinement),
|
|
1239
|
-
children: z.array(ComponentTreeNodeSchema),
|
|
1240
|
-
schemaVersion: SchemaVersions,
|
|
1241
|
-
})
|
|
1242
|
-
.strict();
|
|
1243
|
-
const localeWrapper = (fieldSchema) => z.record(z.string(), fieldSchema);
|
|
1244
|
-
z
|
|
1245
|
-
.object({
|
|
1246
|
-
componentTree: localeWrapper(ComponentTreeSchema),
|
|
1247
|
-
dataSource: localeWrapper(DataSourceSchema),
|
|
1248
|
-
unboundValues: localeWrapper(UnboundValuesSchema),
|
|
1249
|
-
usedComponents: localeWrapper(UsedComponentsSchema).optional(),
|
|
1250
|
-
componentSettings: localeWrapper(ComponentSettingsSchema).optional(),
|
|
1251
|
-
})
|
|
1252
|
-
.superRefine(componentSettingsRefinement);
|
|
1253
|
-
|
|
1254
|
-
var CodeNames;
|
|
1255
|
-
(function (CodeNames) {
|
|
1256
|
-
CodeNames["Type"] = "type";
|
|
1257
|
-
CodeNames["Required"] = "required";
|
|
1258
|
-
CodeNames["Unexpected"] = "unexpected";
|
|
1259
|
-
CodeNames["Regex"] = "regex";
|
|
1260
|
-
CodeNames["In"] = "in";
|
|
1261
|
-
CodeNames["Size"] = "size";
|
|
1262
|
-
CodeNames["Custom"] = "custom";
|
|
1263
|
-
})(CodeNames || (CodeNames = {}));
|
|
1264
|
-
const convertInvalidType = (issue) => {
|
|
1265
|
-
const name = issue.received === 'undefined' ? CodeNames.Required : CodeNames.Type;
|
|
1266
|
-
const details = issue.received === 'undefined'
|
|
1267
|
-
? `The property "${issue.path.slice(-1)}" is required here`
|
|
1268
|
-
: `The type of "${issue.path.slice(-1)}" is incorrect, expected type: ${issue.expected}`;
|
|
1269
|
-
return {
|
|
1270
|
-
details: details,
|
|
1271
|
-
name: name,
|
|
1272
|
-
path: issue.path,
|
|
1273
|
-
value: issue.received.toString(),
|
|
1274
|
-
};
|
|
1275
|
-
};
|
|
1276
|
-
const convertUnrecognizedKeys = (issue) => {
|
|
1277
|
-
const missingProperties = issue.keys.map((k) => `"${k}"`).join(', ');
|
|
1278
|
-
return {
|
|
1279
|
-
details: issue.keys.length > 1
|
|
1280
|
-
? `The properties ${missingProperties} are not expected`
|
|
1281
|
-
: `The property ${missingProperties} is not expected`,
|
|
1282
|
-
name: CodeNames.Unexpected,
|
|
1283
|
-
path: issue.path,
|
|
1284
|
-
};
|
|
1285
|
-
};
|
|
1286
|
-
const convertInvalidString = (issue) => {
|
|
1287
|
-
return {
|
|
1288
|
-
details: issue.message || 'Invalid string',
|
|
1289
|
-
name: issue.validation === 'regex' ? CodeNames.Regex : CodeNames.Unexpected,
|
|
1290
|
-
path: issue.path,
|
|
1291
|
-
};
|
|
1292
|
-
};
|
|
1293
|
-
const convertInvalidEnumValue = (issue) => {
|
|
1294
|
-
return {
|
|
1295
|
-
details: issue.message || 'Value must be one of expected values',
|
|
1296
|
-
name: CodeNames.In,
|
|
1297
|
-
path: issue.path,
|
|
1298
|
-
value: issue.received.toString(),
|
|
1299
|
-
expected: issue.options,
|
|
1300
|
-
};
|
|
1301
|
-
};
|
|
1302
|
-
const convertInvalidLiteral = (issue) => {
|
|
1303
|
-
return {
|
|
1304
|
-
details: issue.message || 'Value must be one of expected values',
|
|
1305
|
-
name: CodeNames.In,
|
|
1306
|
-
path: issue.path,
|
|
1307
|
-
value: issue.received,
|
|
1308
|
-
expected: [issue.expected],
|
|
1309
|
-
};
|
|
1310
|
-
};
|
|
1311
|
-
const convertTooBig = (issue) => {
|
|
1312
|
-
return {
|
|
1313
|
-
details: issue.message || `Size should be at most ${issue.maximum}`,
|
|
1314
|
-
name: CodeNames.Size,
|
|
1315
|
-
path: issue.path,
|
|
1316
|
-
max: issue.maximum,
|
|
1317
|
-
};
|
|
1318
|
-
};
|
|
1319
|
-
const convertTooSmall = (issue) => {
|
|
1320
|
-
return {
|
|
1321
|
-
details: issue.message || `Size should be at least ${issue.minimum}`,
|
|
1322
|
-
name: CodeNames.Size,
|
|
1323
|
-
path: issue.path,
|
|
1324
|
-
min: issue.minimum,
|
|
1325
|
-
};
|
|
1326
|
-
};
|
|
1327
|
-
const defaultConversion = (issue) => {
|
|
1328
|
-
return {
|
|
1329
|
-
details: issue.message || 'An unexpected error occurred',
|
|
1330
|
-
name: CodeNames.Custom,
|
|
1331
|
-
path: issue.path.map(String),
|
|
1332
|
-
};
|
|
1333
|
-
};
|
|
1334
|
-
const zodToContentfulError = (issue) => {
|
|
1335
|
-
switch (issue.code) {
|
|
1336
|
-
case ZodIssueCode.invalid_type:
|
|
1337
|
-
return convertInvalidType(issue);
|
|
1338
|
-
case ZodIssueCode.unrecognized_keys:
|
|
1339
|
-
return convertUnrecognizedKeys(issue);
|
|
1340
|
-
case ZodIssueCode.invalid_enum_value:
|
|
1341
|
-
return convertInvalidEnumValue(issue);
|
|
1342
|
-
case ZodIssueCode.invalid_string:
|
|
1343
|
-
return convertInvalidString(issue);
|
|
1344
|
-
case ZodIssueCode.too_small:
|
|
1345
|
-
return convertTooSmall(issue);
|
|
1346
|
-
case ZodIssueCode.too_big:
|
|
1347
|
-
return convertTooBig(issue);
|
|
1348
|
-
case ZodIssueCode.invalid_literal:
|
|
1349
|
-
return convertInvalidLiteral(issue);
|
|
1350
|
-
default:
|
|
1351
|
-
return defaultConversion(issue);
|
|
1352
|
-
}
|
|
1353
|
-
};
|
|
1354
|
-
const validateBreakpointsDefinition = (breakpoints) => {
|
|
1355
|
-
const result = z
|
|
1356
|
-
.array(BreakpointSchema)
|
|
1357
|
-
.superRefine(breakpointsRefinement)
|
|
1358
|
-
.safeParse(breakpoints);
|
|
1359
|
-
if (!result.success) {
|
|
1360
|
-
return {
|
|
1361
|
-
success: false,
|
|
1362
|
-
errors: result.error.issues.map(zodToContentfulError),
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
return { success: true };
|
|
1366
|
-
};
|
|
1367
|
-
|
|
1368
|
-
let breakpointsRegistry = [];
|
|
1369
|
-
/**
|
|
1370
|
-
* Register custom breakpoints
|
|
1371
|
-
* @param breakpoints - [{[key:string]: string}]
|
|
1372
|
-
* @returns void
|
|
1373
|
-
*/
|
|
1374
|
-
const defineBreakpoints = (breakpoints) => {
|
|
1375
|
-
Object.assign(breakpointsRegistry, breakpoints);
|
|
1376
|
-
};
|
|
1377
|
-
const runBreakpointsValidation = () => {
|
|
1378
|
-
if (!breakpointsRegistry.length)
|
|
1379
|
-
return;
|
|
1380
|
-
const validation = validateBreakpointsDefinition(breakpointsRegistry);
|
|
1381
|
-
if (!validation.success) {
|
|
1382
|
-
throw new Error(`Invalid breakpoints definition. Failed with errors: \n${JSON.stringify(validation.errors, null, 2)}`);
|
|
1383
|
-
}
|
|
1384
|
-
};
|
|
1385
|
-
// Used in the tests to get a breakpoint registration
|
|
1386
|
-
const getBreakpointRegistration = (id) => breakpointsRegistry.find((breakpoint) => breakpoint.id === id);
|
|
1387
|
-
// Used in the tests to reset the registry
|
|
1388
|
-
const resetBreakpointsRegistry = () => {
|
|
1389
|
-
breakpointsRegistry = [];
|
|
1390
|
-
};
|
|
1391
|
-
|
|
1392
|
-
const detachExperienceStyles = (experience) => {
|
|
1393
|
-
const experienceTreeRoot = experience.entityStore?.experienceEntryFields
|
|
1394
|
-
?.componentTree;
|
|
1395
|
-
if (!experienceTreeRoot) {
|
|
1396
|
-
return;
|
|
1397
|
-
}
|
|
1398
|
-
const mapOfDesignVariableKeys = flattenDesignTokenRegistry(designTokensRegistry);
|
|
1399
|
-
// getting breakpoints from the entry componentTree field
|
|
1400
|
-
/**
|
|
1401
|
-
* breakpoints [
|
|
1402
|
-
{
|
|
1403
|
-
id: 'desktop',
|
|
1404
|
-
query: '*',
|
|
1405
|
-
displayName: 'All Sizes',
|
|
1406
|
-
previewSize: '100%'
|
|
1407
|
-
},
|
|
1408
|
-
{
|
|
1409
|
-
id: 'tablet',
|
|
1410
|
-
query: '<992px',
|
|
1411
|
-
displayName: 'Tablet',
|
|
1412
|
-
previewSize: '820px'
|
|
1413
|
-
},
|
|
1414
|
-
{
|
|
1415
|
-
id: 'mobile',
|
|
1416
|
-
query: '<576px',
|
|
1417
|
-
displayName: 'Mobile',
|
|
1418
|
-
previewSize: '390px'
|
|
1419
|
-
}
|
|
1420
|
-
]
|
|
1421
|
-
*/
|
|
1422
|
-
const { breakpoints } = experienceTreeRoot;
|
|
1423
|
-
// creating the structure which I thought would work best for aggregation
|
|
1424
|
-
const mediaQueriesTemplate = breakpoints.reduce((mediaQueryTemplate, breakpoint) => {
|
|
1425
|
-
return {
|
|
1426
|
-
...mediaQueryTemplate,
|
|
1427
|
-
[breakpoint.id]: {
|
|
1428
|
-
condition: breakpoint.query,
|
|
1429
|
-
cssByClassName: {},
|
|
1430
|
-
},
|
|
1431
|
-
};
|
|
1432
|
-
}, {});
|
|
1433
|
-
// getting the breakpoint ids
|
|
1434
|
-
const breakpointIds = Object.keys(mediaQueriesTemplate);
|
|
1435
|
-
const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, }) => {
|
|
1436
|
-
// traversing the tree
|
|
1437
|
-
const queue = [];
|
|
1438
|
-
queue.push(...componentTree.children);
|
|
1439
|
-
let currentNode = undefined;
|
|
1440
|
-
// for each tree node
|
|
1441
|
-
while (queue.length) {
|
|
1442
|
-
currentNode = queue.shift();
|
|
1443
|
-
if (!currentNode) {
|
|
1444
|
-
break;
|
|
1445
|
-
}
|
|
1446
|
-
const usedComponents = experience.entityStore?.experienceEntryFields?.usedComponents ?? [];
|
|
1447
|
-
const isPatternNode = checkIsAssemblyNode({
|
|
1448
|
-
componentId: currentNode.definitionId,
|
|
1449
|
-
usedComponents,
|
|
1450
|
-
});
|
|
1451
|
-
if (isPatternNode) {
|
|
1452
|
-
const patternEntry = usedComponents.find((component) => component.sys.id === currentNode.definitionId);
|
|
1453
|
-
if (!patternEntry || !('fields' in patternEntry)) {
|
|
1454
|
-
continue;
|
|
1455
|
-
}
|
|
1456
|
-
const defaultPatternDivStyles = Object.fromEntries(Object.entries(buildCfStyles({}))
|
|
1457
|
-
.filter(([, value]) => value !== undefined)
|
|
1458
|
-
.map(([key, value]) => [toCSSAttribute(key), value]));
|
|
1459
|
-
// I create a hash of the object above because that would ensure hash stability
|
|
1460
|
-
const styleHash = md5(JSON.stringify(defaultPatternDivStyles));
|
|
1461
|
-
// and prefix the className to make sure the value can be processed
|
|
1462
|
-
const className = `cf-${styleHash}`;
|
|
1463
|
-
for (const breakpointId of breakpointIds) {
|
|
1464
|
-
if (!mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
|
|
1465
|
-
mediaQueriesTemplate[breakpointId].cssByClassName[className] =
|
|
1466
|
-
toCSSString(defaultPatternDivStyles);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
currentNode.variables.cfSsrClassName = {
|
|
1470
|
-
type: 'DesignValue',
|
|
1471
|
-
valuesByBreakpoint: {
|
|
1472
|
-
[breakpointIds[0]]: className,
|
|
1473
|
-
},
|
|
1474
|
-
};
|
|
1475
|
-
// the node of a used pattern contains only the definitionId (id of the patter entry)
|
|
1476
|
-
// as well as the variables overwrites
|
|
1477
|
-
// the layout of a pattern is stored in it's entry
|
|
1478
|
-
iterateOverTreeAndExtractStyles({
|
|
1479
|
-
// that is why we pass it here to iterate of the pattern tree
|
|
1480
|
-
componentTree: patternEntry.fields.componentTree,
|
|
1481
|
-
// but we pass the data source of the experience entry cause that's where the binding is stored
|
|
1482
|
-
dataSource,
|
|
1483
|
-
// unbound values of a pattern store the default values of pattern variables
|
|
1484
|
-
unboundValues: patternEntry.fields.unboundValues,
|
|
1485
|
-
// this is where we can map the pattern variable to it's default value
|
|
1486
|
-
componentSettings: patternEntry.fields.componentSettings,
|
|
1487
|
-
// and this is where the over-writes for the default values are stored
|
|
1488
|
-
// yes, I know, it's a bit confusing
|
|
1489
|
-
componentVariablesOverwrites: currentNode.variables,
|
|
1490
|
-
});
|
|
1491
|
-
continue;
|
|
1492
|
-
}
|
|
1493
|
-
/** Variables value is stored in `valuesByBreakpoint` object
|
|
1494
|
-
* {
|
|
1495
|
-
cfVerticalAlignment: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'center' } },
|
|
1496
|
-
cfHorizontalAlignment: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'center' } },
|
|
1497
|
-
cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0 0 0 0' } },
|
|
1498
|
-
cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0 0 0 0' } },
|
|
1499
|
-
cfBackgroundColor: {
|
|
1500
|
-
type: 'DesignValue',
|
|
1501
|
-
valuesByBreakpoint: { desktop: 'rgba(246, 246, 246, 1)' }
|
|
1502
|
-
},
|
|
1503
|
-
cfWidth: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'fill' } },
|
|
1504
|
-
cfHeight: {
|
|
1505
|
-
type: 'DesignValue',
|
|
1506
|
-
valuesByBreakpoint: { desktop: 'fit-content' }
|
|
1507
|
-
},
|
|
1508
|
-
cfMaxWidth: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'none' } },
|
|
1509
|
-
cfFlexDirection: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'column' } },
|
|
1510
|
-
cfFlexWrap: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'nowrap' } },
|
|
1511
|
-
cfBorder: {
|
|
1512
|
-
type: 'DesignValue',
|
|
1513
|
-
valuesByBreakpoint: { desktop: '0px solid rgba(0, 0, 0, 0)' }
|
|
1514
|
-
},
|
|
1515
|
-
cfBorderRadius: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0px' } },
|
|
1516
|
-
cfGap: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0px 0px' } },
|
|
1517
|
-
cfHyperlink: { type: 'UnboundValue', key: 'VNc49Qyepd6IzN7rmKUyS' },
|
|
1518
|
-
cfOpenInNewTab: { type: 'UnboundValue', key: 'ZA5YqB2fmREQ4pTKqY5hX' },
|
|
1519
|
-
cfBackgroundImageUrl: { type: 'UnboundValue', key: 'FeskH0WbYD5_RQVXX-1T8' },
|
|
1520
|
-
cfBackgroundImageOptions: { type: 'DesignValue', valuesByBreakpoint: { desktop: [Object] } }
|
|
1521
|
-
}
|
|
1522
|
-
*/
|
|
1523
|
-
// so first, I convert it into a map to help me make it easier to access the values
|
|
1524
|
-
const propsByBreakpoint = indexByBreakpoint({
|
|
1525
|
-
variables: currentNode.variables,
|
|
1526
|
-
breakpointIds,
|
|
1527
|
-
unboundValues: unboundValues,
|
|
1528
|
-
dataSource: dataSource,
|
|
1529
|
-
componentSettings,
|
|
1530
|
-
componentVariablesOverwrites,
|
|
1531
|
-
getBoundEntityById: (id) => {
|
|
1532
|
-
return experience.entityStore?.entities.find((entity) => entity.sys.id === id);
|
|
1533
|
-
},
|
|
1534
|
-
});
|
|
1535
|
-
/**
|
|
1536
|
-
* propsByBreakpoint {
|
|
1537
|
-
desktop: {
|
|
1538
|
-
cfVerticalAlignment: 'center',
|
|
1539
|
-
cfHorizontalAlignment: 'center',
|
|
1540
|
-
cfMargin: '0 0 0 0',
|
|
1541
|
-
cfPadding: '0 0 0 0',
|
|
1542
|
-
cfBackgroundColor: 'rgba(246, 246, 246, 1)',
|
|
1543
|
-
cfWidth: 'fill',
|
|
1544
|
-
cfHeight: 'fit-content',
|
|
1545
|
-
cfMaxWidth: 'none',
|
|
1546
|
-
cfFlexDirection: 'column',
|
|
1547
|
-
cfFlexWrap: 'nowrap',
|
|
1548
|
-
cfBorder: '0px solid rgba(0, 0, 0, 0)',
|
|
1549
|
-
cfBorderRadius: '0px',
|
|
1550
|
-
cfGap: '0px 0px',
|
|
1551
|
-
cfBackgroundImageOptions: { scaling: 'fill', alignment: 'left top', targetSize: '2000px' }
|
|
1552
|
-
},
|
|
1553
|
-
tablet: {},
|
|
1554
|
-
mobile: {}
|
|
1555
|
-
}
|
|
1556
|
-
*/
|
|
1557
|
-
const currentNodeClassNames = [];
|
|
1558
|
-
// then for each breakpoint
|
|
1559
|
-
for (const breakpointId of breakpointIds) {
|
|
1560
|
-
const propsByBreakpointWithResolvedDesignTokens = Object.entries(propsByBreakpoint[breakpointId]).reduce((acc, [variableName, variableValue]) => {
|
|
1561
|
-
return {
|
|
1562
|
-
...acc,
|
|
1563
|
-
[variableName]: maybePopulateDesignTokenValue(variableName, variableValue, mapOfDesignVariableKeys),
|
|
1564
|
-
};
|
|
1565
|
-
}, {});
|
|
1566
|
-
// We convert cryptic prop keys to css variables
|
|
1567
|
-
// Eg: cfMargin to margin
|
|
1568
|
-
const stylesForBreakpoint = buildCfStyles(propsByBreakpointWithResolvedDesignTokens);
|
|
1569
|
-
const stylesForBreakpointWithoutUndefined = Object.fromEntries(Object.entries(stylesForBreakpoint)
|
|
1570
|
-
.filter(([, value]) => value !== undefined)
|
|
1571
|
-
.map(([key, value]) => [toCSSAttribute(key), value]));
|
|
1572
|
-
/**
|
|
1573
|
-
* stylesForBreakpoint {
|
|
1574
|
-
margin: '0 0 0 0',
|
|
1575
|
-
padding: '0 0 0 0',
|
|
1576
|
-
'background-color': 'rgba(246, 246, 246, 1)',
|
|
1577
|
-
width: '100%',
|
|
1578
|
-
height: 'fit-content',
|
|
1579
|
-
'max-width': 'none',
|
|
1580
|
-
border: '0px solid rgba(0, 0, 0, 0)',
|
|
1581
|
-
'border-radius': '0px',
|
|
1582
|
-
gap: '0px 0px',
|
|
1583
|
-
'align-items': 'center',
|
|
1584
|
-
'justify-content': 'safe center',
|
|
1585
|
-
'flex-direction': 'column',
|
|
1586
|
-
'flex-wrap': 'nowrap',
|
|
1587
|
-
'font-style': 'normal',
|
|
1588
|
-
'text-decoration': 'none',
|
|
1589
|
-
'box-sizing': 'border-box'
|
|
1590
|
-
}
|
|
1591
|
-
*/
|
|
1592
|
-
// I create a hash of the object above because that would ensure hash stability
|
|
1593
|
-
const styleHash = md5(JSON.stringify(stylesForBreakpointWithoutUndefined));
|
|
1594
|
-
// and prefix the className to make sure the value can be processed
|
|
1595
|
-
const className = `cf-${styleHash}`;
|
|
1596
|
-
// I save the generated hashes into an array to later save it in the tree node
|
|
1597
|
-
// as cfSsrClassName prop
|
|
1598
|
-
// making sure to avoid the duplicates in case styles for > 1 breakpoints are the same
|
|
1599
|
-
if (!currentNodeClassNames.includes(className)) {
|
|
1600
|
-
currentNodeClassNames.push(className);
|
|
1601
|
-
}
|
|
1602
|
-
// if there is already the similar hash - no need to over-write it
|
|
1603
|
-
if (mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
|
|
1604
|
-
continue;
|
|
1605
|
-
}
|
|
1606
|
-
// otherwise, save it to the stylesheet
|
|
1607
|
-
mediaQueriesTemplate[breakpointId].cssByClassName[className] = toCSSString(stylesForBreakpointWithoutUndefined);
|
|
1608
|
-
}
|
|
1609
|
-
// all generated classNames are saved in the tree node
|
|
1610
|
-
// to be handled by the sdk later
|
|
1611
|
-
// each node will get N classNames, where N is the number of breakpoints
|
|
1612
|
-
// browsers process classNames in the order they are defined
|
|
1613
|
-
// meaning that in case of className1 className2 className3
|
|
1614
|
-
// className3 will win over className2 and className1
|
|
1615
|
-
// making sure that we respect the order of breakpoints from
|
|
1616
|
-
// we can achieve "desktop first" or "mobile first" approach to style over-writes
|
|
1617
|
-
currentNode.variables.cfSsrClassName = {
|
|
1618
|
-
type: 'DesignValue',
|
|
1619
|
-
valuesByBreakpoint: {
|
|
1620
|
-
[breakpointIds[0]]: currentNodeClassNames.join(' '),
|
|
1621
|
-
},
|
|
1622
|
-
};
|
|
1623
|
-
queue.push(...currentNode.children);
|
|
1624
|
-
}
|
|
1625
|
-
};
|
|
1626
|
-
iterateOverTreeAndExtractStyles({
|
|
1627
|
-
componentTree: experienceTreeRoot,
|
|
1628
|
-
dataSource: experience.entityStore?.dataSource ?? {},
|
|
1629
|
-
unboundValues: experience.entityStore?.unboundValues ?? {},
|
|
1630
|
-
componentSettings: experience.entityStore?.experienceEntryFields?.componentSettings,
|
|
1631
|
-
});
|
|
1632
|
-
// once the whole tree was traversed, for each breakpoint, I aggregate the styles
|
|
1633
|
-
// for each generated className into one css string
|
|
1634
|
-
const styleSheet = Object.entries(mediaQueriesTemplate).reduce((acc, [, breakpointPayload]) => {
|
|
1635
|
-
return `${acc}${toMediaQuery(breakpointPayload)}`;
|
|
1636
|
-
}, '');
|
|
1637
|
-
return styleSheet;
|
|
1638
|
-
};
|
|
1639
|
-
const isCfStyleAttribute = (variableName) => {
|
|
1640
|
-
return CF_STYLE_ATTRIBUTES.includes(variableName);
|
|
1641
|
-
};
|
|
1642
|
-
const maybePopulateDesignTokenValue = (variableName, variableValue, mapOfDesignVariableKeys) => {
|
|
1643
|
-
// TODO: refactor to reuse fn from core package
|
|
1644
|
-
if (typeof variableValue !== 'string') {
|
|
1645
|
-
return variableValue;
|
|
1646
|
-
}
|
|
1647
|
-
if (!isCfStyleAttribute(variableName)) {
|
|
1648
|
-
return variableValue;
|
|
1649
|
-
}
|
|
1650
|
-
const resolveSimpleDesignToken = (variableName, variableValue) => {
|
|
1651
|
-
const nonTemplateDesignTokenValue = variableValue.replace(templateStringRegex, '$1');
|
|
1652
|
-
const tokenValue = mapOfDesignVariableKeys[nonTemplateDesignTokenValue];
|
|
1653
|
-
if (!tokenValue) {
|
|
1654
|
-
if (builtInStyles[variableName]) {
|
|
1655
|
-
return builtInStyles[variableName].defaultValue;
|
|
1656
|
-
}
|
|
1657
|
-
if (optionalBuiltInStyles[variableName]) {
|
|
1658
|
-
return optionalBuiltInStyles[variableName].defaultValue;
|
|
1659
|
-
}
|
|
1660
|
-
return '0px';
|
|
1661
|
-
}
|
|
1662
|
-
if (variableName === 'cfBorder' || variableName.startsWith('cfBorder_')) {
|
|
1663
|
-
if (typeof tokenValue === 'object') {
|
|
1664
|
-
const { width, style, color } = tokenValue;
|
|
1665
|
-
return `${width} ${style} ${color}`;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
return tokenValue;
|
|
1669
|
-
};
|
|
1670
|
-
const templateStringRegex = /\${(.+?)}/g;
|
|
1671
|
-
const parts = variableValue.split(' ');
|
|
1672
|
-
let resolvedValue = '';
|
|
1673
|
-
for (const part of parts) {
|
|
1674
|
-
const tokenValue = templateStringRegex.test(part)
|
|
1675
|
-
? resolveSimpleDesignToken(variableName, part)
|
|
1676
|
-
: part;
|
|
1677
|
-
resolvedValue += `${tokenValue} `;
|
|
1678
|
-
}
|
|
1679
|
-
// Not trimming would end up with a trailing space that breaks the check in `calculateNodeDefaultHeight`
|
|
1680
|
-
return resolvedValue.trim();
|
|
1681
|
-
};
|
|
1682
|
-
const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataSource = {}, unboundValues = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, }) => {
|
|
1683
|
-
if (variableData.type === 'UnboundValue') {
|
|
1684
|
-
const uuid = variableData.key;
|
|
1685
|
-
return unboundValues[uuid]?.value;
|
|
1686
|
-
}
|
|
1687
|
-
if (variableData.type === 'ComponentValue') {
|
|
1688
|
-
const variableDefinitionKey = variableData.key;
|
|
1689
|
-
const variableDefinition = componentSettings.variableDefinitions[variableDefinitionKey];
|
|
1690
|
-
// @ts-expect-error TODO: fix the types as it thinks taht `defaultValue` is of type string
|
|
1691
|
-
const defaultValueKey = variableDefinition.defaultValue?.key;
|
|
1692
|
-
const defaultValue = unboundValues[defaultValueKey].value;
|
|
1693
|
-
const userSetValue = componentVariablesOverwrites?.[variableDefinitionKey];
|
|
1694
|
-
if (!userSetValue) {
|
|
1695
|
-
return defaultValue;
|
|
1696
|
-
}
|
|
1697
|
-
// at this point userSetValue will either be type of 'DesignValue' or 'BoundValue'
|
|
1698
|
-
// so we recursively run resolution again to resolve it
|
|
1699
|
-
const resolvedValue = resolveBackgroundImageBinding({
|
|
1700
|
-
variableData: userSetValue,
|
|
1701
|
-
getBoundEntityById,
|
|
1702
|
-
dataSource,
|
|
1703
|
-
unboundValues,
|
|
1704
|
-
componentVariablesOverwrites,
|
|
1705
|
-
componentSettings,
|
|
1706
|
-
});
|
|
1707
|
-
return resolvedValue || defaultValue;
|
|
1708
|
-
}
|
|
1709
|
-
if (variableData.type === 'BoundValue') {
|
|
1710
|
-
// '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale'
|
|
1711
|
-
const [, uuid] = variableData.path.split('/');
|
|
1712
|
-
const binding = dataSource[uuid];
|
|
1713
|
-
const boundEntity = getBoundEntityById(binding.sys.id);
|
|
1714
|
-
if (!boundEntity) {
|
|
1715
|
-
return;
|
|
1716
|
-
}
|
|
1717
|
-
if (boundEntity.sys.type === 'Asset') {
|
|
1718
|
-
return boundEntity.fields.file?.url;
|
|
1719
|
-
}
|
|
1720
|
-
else {
|
|
1721
|
-
// '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale'
|
|
1722
|
-
// becomes
|
|
1723
|
-
// '/fields/assetReference/~locale/fields/file/~locale'
|
|
1724
|
-
const pathWithoutUUID = variableData.path.split(uuid)[1];
|
|
1725
|
-
// '/fields/assetReference/~locale/fields/file/~locale'
|
|
1726
|
-
// becomes
|
|
1727
|
-
// '/fields/assetReference/'
|
|
1728
|
-
const pathToReferencedAsset = pathWithoutUUID.split('~locale')[0];
|
|
1729
|
-
// '/fields/assetReference/'
|
|
1730
|
-
// becomes
|
|
1731
|
-
// '[fields, assetReference]'
|
|
1732
|
-
const [, fieldName] = pathToReferencedAsset.substring(1).split('/') ?? undefined;
|
|
1733
|
-
const referenceToAsset = boundEntity.fields[fieldName];
|
|
1734
|
-
if (!referenceToAsset) {
|
|
1735
|
-
return;
|
|
1736
|
-
}
|
|
1737
|
-
if (referenceToAsset.sys?.linkType === 'Asset') {
|
|
1738
|
-
const referencedAsset = getBoundEntityById(referenceToAsset.sys.id);
|
|
1739
|
-
if (!referencedAsset) {
|
|
1740
|
-
return;
|
|
1741
|
-
}
|
|
1742
|
-
return referencedAsset.fields.file?.url;
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
};
|
|
1747
|
-
const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unboundValues = {}, dataSource = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, }) => {
|
|
1748
|
-
const variableValuesByBreakpoints = breakpointIds.reduce((acc, breakpointId) => {
|
|
1749
|
-
return {
|
|
1750
|
-
...acc,
|
|
1751
|
-
[breakpointId]: {},
|
|
1752
|
-
};
|
|
1753
|
-
}, {});
|
|
1754
|
-
const defaultBreakpoint = breakpointIds[0];
|
|
1755
|
-
for (const [variableName, variableData] of Object.entries(variables)) {
|
|
1756
|
-
// handling the special case - cfBackgroundImageUrl variable, which can be bound or unbound
|
|
1757
|
-
// so, we need to resolve it here and pass it down as a css property to be convereted into the CSS
|
|
1758
|
-
// I used .startsWith() cause it can be part of a pattern node
|
|
1759
|
-
if (variableName === 'cfBackgroundImageUrl' ||
|
|
1760
|
-
variableName.startsWith('cfBackgroundImageUrl_')) {
|
|
1761
|
-
const imageUrl = resolveBackgroundImageBinding({
|
|
1762
|
-
variableData,
|
|
1763
|
-
getBoundEntityById,
|
|
1764
|
-
unboundValues,
|
|
1765
|
-
dataSource,
|
|
1766
|
-
componentSettings,
|
|
1767
|
-
componentVariablesOverwrites,
|
|
1768
|
-
});
|
|
1769
|
-
if (imageUrl) {
|
|
1770
|
-
variableValuesByBreakpoints[defaultBreakpoint][variableName] = imageUrl;
|
|
1771
|
-
}
|
|
1772
|
-
continue;
|
|
1773
|
-
}
|
|
1774
|
-
if (variableData.type !== 'DesignValue') {
|
|
1775
|
-
continue;
|
|
1776
|
-
}
|
|
1777
|
-
for (const [breakpointId, variableValue] of Object.entries(variableData.valuesByBreakpoint)) {
|
|
1778
|
-
if (!variableValue) {
|
|
1779
|
-
continue;
|
|
1780
|
-
}
|
|
1781
|
-
variableValuesByBreakpoints[breakpointId] = {
|
|
1782
|
-
...variableValuesByBreakpoints[breakpointId],
|
|
1783
|
-
[variableName]: variableValue,
|
|
1784
|
-
};
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
return variableValuesByBreakpoints;
|
|
1788
|
-
};
|
|
1789
|
-
/**
|
|
1790
|
-
* Flattens the object from
|
|
1791
|
-
* {
|
|
1792
|
-
* color: {
|
|
1793
|
-
* [key]: [value]
|
|
1794
|
-
* }
|
|
1795
|
-
* }
|
|
1796
|
-
*
|
|
1797
|
-
* to
|
|
1798
|
-
*
|
|
1799
|
-
* {
|
|
1800
|
-
* 'color.key': [value]
|
|
1801
|
-
* }
|
|
1802
|
-
*/
|
|
1803
|
-
const flattenDesignTokenRegistry = (designTokenRegistry) => {
|
|
1804
|
-
return Object.entries(designTokenRegistry).reduce((acc, [categoryName, tokenCategory]) => {
|
|
1805
|
-
const tokensWithCategory = Object.entries(tokenCategory).reduce((acc, [tokenName, tokenValue]) => {
|
|
1806
|
-
return {
|
|
1807
|
-
...acc,
|
|
1808
|
-
[`${categoryName}.${tokenName}`]: tokenValue,
|
|
1809
|
-
};
|
|
1810
|
-
}, {});
|
|
1811
|
-
return {
|
|
1812
|
-
...acc,
|
|
1813
|
-
...tokensWithCategory,
|
|
1814
|
-
};
|
|
1815
|
-
}, {});
|
|
1816
|
-
};
|
|
1817
|
-
// Replaces camelCase with kebab-case
|
|
1818
|
-
// converts the <key, value> object into a css string
|
|
1819
|
-
const toCSSString = (breakpointStyles) => {
|
|
1820
|
-
return Object.entries(breakpointStyles)
|
|
1821
|
-
.map(([key, value]) => `${key}:${value};`)
|
|
1822
|
-
.join('');
|
|
1823
|
-
};
|
|
1824
|
-
const toMediaQuery = (breakpointPayload) => {
|
|
1825
|
-
const mediaQueryStyles = Object.entries(breakpointPayload.cssByClassName).reduce((acc, [className, css]) => {
|
|
1826
|
-
return `${acc}.${className}{${css}}`;
|
|
1827
|
-
}, ``);
|
|
1828
|
-
if (breakpointPayload.condition === '*') {
|
|
1829
|
-
return mediaQueryStyles;
|
|
1830
|
-
}
|
|
1831
|
-
const [evaluation, pixelValue] = [
|
|
1832
|
-
breakpointPayload.condition[0],
|
|
1833
|
-
breakpointPayload.condition.substring(1),
|
|
1834
|
-
];
|
|
1835
|
-
const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
|
|
1836
|
-
return `@media(${mediaQueryRule}:${pixelValue}){${mediaQueryStyles}}`;
|
|
1837
|
-
};
|
|
1838
|
-
|
|
1839
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1840
|
-
function get(obj, path) {
|
|
1841
|
-
if (!path.length) {
|
|
1842
|
-
return obj;
|
|
1843
|
-
}
|
|
1844
|
-
try {
|
|
1845
|
-
const [currentPath, ...nextPath] = path;
|
|
1846
|
-
return get(obj[currentPath], nextPath);
|
|
1847
|
-
}
|
|
1848
|
-
catch (err) {
|
|
1849
|
-
return undefined;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
const getBoundValue = (entryOrAsset, path) => {
|
|
1854
|
-
const value = get(entryOrAsset, path.split('/').slice(2, -1));
|
|
1855
|
-
return value && typeof value == 'object' && value.url
|
|
1856
|
-
? value.url
|
|
1857
|
-
: value;
|
|
1858
|
-
};
|
|
1859
|
-
|
|
1860
|
-
const transformRichText = (entryOrAsset, path) => {
|
|
1861
|
-
const value = getBoundValue(entryOrAsset, path);
|
|
1862
|
-
if (typeof value === 'string') {
|
|
1863
|
-
return {
|
|
1864
|
-
data: {},
|
|
1865
|
-
content: [
|
|
1866
|
-
{
|
|
1867
|
-
nodeType: BLOCKS.PARAGRAPH,
|
|
1868
|
-
data: {},
|
|
1869
|
-
content: [
|
|
1870
|
-
{
|
|
1871
|
-
data: {},
|
|
1872
|
-
nodeType: 'text',
|
|
1873
|
-
value: value,
|
|
1874
|
-
marks: [],
|
|
1875
|
-
},
|
|
1876
|
-
],
|
|
1877
|
-
},
|
|
1878
|
-
],
|
|
1879
|
-
nodeType: BLOCKS.DOCUMENT,
|
|
1880
|
-
};
|
|
1881
|
-
}
|
|
1882
|
-
if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
|
|
1883
|
-
return value;
|
|
1884
|
-
}
|
|
1885
|
-
return undefined;
|
|
1886
|
-
};
|
|
1887
|
-
|
|
1888
|
-
function getOptimizedImageUrl(url, width, quality, format) {
|
|
1889
|
-
if (url.startsWith('//')) {
|
|
1890
|
-
url = 'https:' + url;
|
|
1891
|
-
}
|
|
1892
|
-
const params = new URLSearchParams();
|
|
1893
|
-
if (width) {
|
|
1894
|
-
params.append('w', width.toString());
|
|
1895
|
-
}
|
|
1896
|
-
if (quality && quality > 0 && quality < 100) {
|
|
1897
|
-
params.append('q', quality.toString());
|
|
1898
|
-
}
|
|
1899
|
-
if (format) {
|
|
1900
|
-
params.append('fm', format);
|
|
1901
|
-
}
|
|
1902
|
-
const queryString = params.toString();
|
|
1903
|
-
return `${url}${queryString ? '?' + queryString : ''}`;
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
function validateParams(file, quality, format) {
|
|
1907
|
-
if (!file.details.image) {
|
|
1908
|
-
throw Error('No image in file asset to transform');
|
|
1909
|
-
}
|
|
1910
|
-
if (quality < 0 || quality > 100) {
|
|
1911
|
-
throw Error('Quality must be between 0 and 100');
|
|
1912
|
-
}
|
|
1913
|
-
if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
|
|
1914
|
-
throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
|
|
1915
|
-
}
|
|
1916
|
-
return true;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
const MAX_WIDTH_ALLOWED$1 = 2000;
|
|
1920
|
-
const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', format) => {
|
|
1921
|
-
const qualityNumber = Number(quality.replace('%', ''));
|
|
1922
|
-
if (!validateParams(file, qualityNumber, format)) ;
|
|
1923
|
-
if (!validateParams(file, qualityNumber, format)) ;
|
|
1924
|
-
const url = file.url;
|
|
1925
|
-
const { width1x, width2x } = getWidths(widthStyle, file);
|
|
1926
|
-
const imageUrl1x = getOptimizedImageUrl(url, width1x, qualityNumber, format);
|
|
1927
|
-
const imageUrl2x = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
1928
|
-
const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
|
|
1929
|
-
const returnedUrl = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
1930
|
-
const optimizedBackgroundImageAsset = {
|
|
1931
|
-
url: returnedUrl,
|
|
1932
|
-
srcSet,
|
|
1933
|
-
file,
|
|
1934
|
-
};
|
|
1935
|
-
return optimizedBackgroundImageAsset;
|
|
1936
|
-
function getWidths(widthStyle, file) {
|
|
1937
|
-
let width1x = 0;
|
|
1938
|
-
let width2x = 0;
|
|
1939
|
-
const intrinsicImageWidth = file.details.image.width;
|
|
1940
|
-
if (widthStyle.endsWith('px')) {
|
|
1941
|
-
width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
|
|
1942
|
-
}
|
|
1943
|
-
else {
|
|
1944
|
-
width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
|
|
1945
|
-
}
|
|
1946
|
-
width2x = Math.min(width1x * 2, intrinsicImageWidth);
|
|
1947
|
-
return { width1x, width2x };
|
|
1948
|
-
}
|
|
1949
|
-
};
|
|
1950
|
-
|
|
1951
|
-
const MAX_WIDTH_ALLOWED = 4000;
|
|
1952
|
-
const getOptimizedImageAsset = ({ file, sizes, loading, quality = '100%', format, }) => {
|
|
1953
|
-
const qualityNumber = Number(quality.replace('%', ''));
|
|
1954
|
-
if (!validateParams(file, qualityNumber, format)) ;
|
|
1955
|
-
const url = file.url;
|
|
1956
|
-
const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
|
|
1957
|
-
const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
|
|
1958
|
-
const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
|
|
1959
|
-
const srcSet = sizes
|
|
1960
|
-
? widthParts.map((width) => `${getOptimizedImageUrl(url, width, qualityNumber, format)} ${width}w`)
|
|
1961
|
-
: [];
|
|
1962
|
-
const intrinsicImageWidth = file.details.image.width;
|
|
1963
|
-
if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
|
|
1964
|
-
srcSet.push(`${getOptimizedImageUrl(url, undefined, qualityNumber, format)} ${intrinsicImageWidth}w`);
|
|
1965
|
-
}
|
|
1966
|
-
const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, qualityNumber, format);
|
|
1967
|
-
const optimizedImageAsset = {
|
|
1968
|
-
url: returnedUrl,
|
|
1969
|
-
srcSet,
|
|
1970
|
-
sizes,
|
|
1971
|
-
file,
|
|
1972
|
-
loading,
|
|
1973
|
-
};
|
|
1974
|
-
return optimizedImageAsset;
|
|
1975
|
-
};
|
|
1976
|
-
|
|
1977
|
-
const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
|
|
1978
|
-
let value;
|
|
1979
|
-
// If it is not a deep path and not pointing to the file of the asset,
|
|
1980
|
-
// it is just pointing to a normal field and therefore we just resolve the value as normal field
|
|
1981
|
-
if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
|
|
1982
|
-
return getBoundValue(asset, path);
|
|
1983
|
-
}
|
|
1984
|
-
//TODO: this will be better served by injectable type transformers instead of if statement
|
|
1985
|
-
if (variableName === 'cfImageAsset') {
|
|
1986
|
-
const optionsVariableName = 'cfImageOptions';
|
|
1987
|
-
const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
|
|
1988
|
-
? variables[optionsVariableName].valuesByBreakpoint
|
|
1989
|
-
: {}, optionsVariableName);
|
|
1990
|
-
if (!options) {
|
|
1991
|
-
console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
|
|
1992
|
-
return;
|
|
1993
|
-
}
|
|
1994
|
-
try {
|
|
1995
|
-
value = getOptimizedImageAsset({
|
|
1996
|
-
file: asset.fields.file,
|
|
1997
|
-
loading: options.loading,
|
|
1998
|
-
sizes: options.targetSize,
|
|
1999
|
-
quality: options.quality,
|
|
2000
|
-
format: options.format,
|
|
2001
|
-
});
|
|
2002
|
-
return value;
|
|
2003
|
-
}
|
|
2004
|
-
catch (error) {
|
|
2005
|
-
console.error('Error transforming image asset', error);
|
|
2006
|
-
}
|
|
2007
|
-
return;
|
|
2008
|
-
}
|
|
2009
|
-
if (variableName === 'cfBackgroundImageUrl') {
|
|
2010
|
-
const width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth');
|
|
2011
|
-
const optionsVariableName = 'cfBackgroundImageOptions';
|
|
2012
|
-
const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
|
|
2013
|
-
? variables[optionsVariableName].valuesByBreakpoint
|
|
2014
|
-
: {}, optionsVariableName);
|
|
2015
|
-
if (!options) {
|
|
2016
|
-
console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
|
|
2017
|
-
return;
|
|
2018
|
-
}
|
|
2019
|
-
try {
|
|
2020
|
-
value = getOptimizedBackgroundImageAsset(asset.fields.file, width, options.quality, options.format);
|
|
2021
|
-
return value;
|
|
2022
|
-
}
|
|
2023
|
-
catch (error) {
|
|
2024
|
-
console.error('Error transforming image asset', error);
|
|
2025
|
-
}
|
|
2026
|
-
return;
|
|
2027
|
-
}
|
|
2028
|
-
return asset.fields.file?.url;
|
|
2029
|
-
};
|
|
2030
|
-
|
|
2031
|
-
const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableDefinition, path) => {
|
|
2032
|
-
const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
|
|
2033
|
-
if (!entityOrAsset)
|
|
2034
|
-
return;
|
|
2035
|
-
switch (variableDefinition.type) {
|
|
2036
|
-
case 'Media':
|
|
2037
|
-
// If we bound a normal entry field to the media veriable we just return the bound value
|
|
2038
|
-
if (entityOrAsset.sys.type === 'Entry') {
|
|
2039
|
-
return getBoundValue(entityOrAsset, path);
|
|
2040
|
-
}
|
|
2041
|
-
return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
|
|
2042
|
-
case 'RichText':
|
|
2043
|
-
return transformRichText(entityOrAsset, path);
|
|
2044
|
-
default:
|
|
2045
|
-
return getBoundValue(entityOrAsset, path);
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
|
|
2049
|
-
const getDataFromTree = (tree) => {
|
|
2050
|
-
let dataSource = {};
|
|
2051
|
-
let unboundValues = {};
|
|
2052
|
-
const queue = [...tree.root.children];
|
|
2053
|
-
while (queue.length) {
|
|
2054
|
-
const node = queue.shift();
|
|
2055
|
-
if (!node) {
|
|
2056
|
-
continue;
|
|
2057
|
-
}
|
|
2058
|
-
dataSource = { ...dataSource, ...node.data.dataSource };
|
|
2059
|
-
unboundValues = { ...unboundValues, ...node.data.unboundValues };
|
|
2060
|
-
if (node.children.length) {
|
|
2061
|
-
queue.push(...node.children);
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
return {
|
|
2065
|
-
dataSource,
|
|
2066
|
-
unboundValues,
|
|
2067
|
-
};
|
|
2068
|
-
};
|
|
2069
|
-
/**
|
|
2070
|
-
* Gets calculates the index to drop the dragged component based on the mouse position
|
|
2071
|
-
* @returns {InsertionData} a object containing a node that will become a parent for dragged component and index at which it must be inserted
|
|
2072
|
-
*/
|
|
2073
|
-
const getInsertionData = ({ dropReceiverParentNode, dropReceiverNode, flexDirection, isMouseAtTopBorder, isMouseAtBottomBorder, isMouseInLeftHalf, isMouseInUpperHalf, isOverTopIndicator, isOverBottomIndicator, }) => {
|
|
2074
|
-
const APPEND_INSIDE = dropReceiverNode.children.length;
|
|
2075
|
-
const PREPEND_INSIDE = 0;
|
|
2076
|
-
if (isMouseAtTopBorder || isMouseAtBottomBorder) {
|
|
2077
|
-
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2078
|
-
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2079
|
-
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2080
|
-
return {
|
|
2081
|
-
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2082
|
-
node: dropReceiverParentNode,
|
|
2083
|
-
index: isMouseAtBottomBorder ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2084
|
-
};
|
|
2085
|
-
}
|
|
2086
|
-
// if over one of the section indicators
|
|
2087
|
-
if (isOverTopIndicator || isOverBottomIndicator) {
|
|
2088
|
-
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2089
|
-
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2090
|
-
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2091
|
-
return {
|
|
2092
|
-
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2093
|
-
node: dropReceiverParentNode,
|
|
2094
|
-
index: isOverBottomIndicator ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2095
|
-
};
|
|
2096
|
-
}
|
|
2097
|
-
if (flexDirection === undefined || flexDirection === 'row') {
|
|
2098
|
-
return {
|
|
2099
|
-
node: dropReceiverNode,
|
|
2100
|
-
index: isMouseInLeftHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2101
|
-
};
|
|
2102
|
-
}
|
|
2103
|
-
else {
|
|
2104
|
-
return {
|
|
2105
|
-
node: dropReceiverNode,
|
|
2106
|
-
index: isMouseInUpperHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2107
|
-
};
|
|
2108
|
-
}
|
|
2109
|
-
};
|
|
2110
|
-
const generateRandomId = (letterCount) => {
|
|
2111
|
-
const LETTERS = 'abcdefghijklmnopqvwxyzABCDEFGHIJKLMNOPQVWXYZ';
|
|
2112
|
-
const NUMS = '0123456789';
|
|
2113
|
-
const ALNUM = NUMS + LETTERS;
|
|
2114
|
-
const times = (n, callback) => Array.from({ length: n }, callback);
|
|
2115
|
-
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
2116
|
-
return times(letterCount, () => ALNUM[random(0, ALNUM.length - 1)]).join('');
|
|
2117
|
-
};
|
|
2118
|
-
const checkIsAssemblyNode = ({ componentId, usedComponents, }) => {
|
|
2119
|
-
if (!usedComponents?.length)
|
|
2120
|
-
return false;
|
|
2121
|
-
return usedComponents.some((usedComponent) => usedComponent.sys.id === componentId);
|
|
2122
|
-
};
|
|
2123
|
-
/** @deprecated use `checkIsAssemblyNode` instead. Will be removed with SDK v5. */
|
|
2124
|
-
const checkIsAssembly = checkIsAssemblyNode;
|
|
2125
|
-
/**
|
|
2126
|
-
* This check assumes that the entry is already ensured to be an experience, i.e. the
|
|
2127
|
-
* content type of the entry is an experience type with the necessary annotations.
|
|
2128
|
-
**/
|
|
2129
|
-
const checkIsAssemblyEntry = (entry) => {
|
|
2130
|
-
return Boolean(entry.fields?.componentSettings);
|
|
2131
|
-
};
|
|
2132
|
-
const checkIsAssemblyDefinition = (component) => component?.category === ASSEMBLY_DEFAULT_CATEGORY;
|
|
2133
|
-
|
|
2134
|
-
const isExperienceEntry = (entry) => {
|
|
2135
|
-
return (entry?.sys?.type === 'Entry' &&
|
|
2136
|
-
!!entry.fields?.title &&
|
|
2137
|
-
!!entry.fields?.slug &&
|
|
2138
|
-
!!entry.fields?.componentTree &&
|
|
2139
|
-
Array.isArray(entry.fields.componentTree.breakpoints) &&
|
|
2140
|
-
Array.isArray(entry.fields.componentTree.children) &&
|
|
2141
|
-
typeof entry.fields.componentTree.schemaVersion === 'string');
|
|
2142
|
-
};
|
|
2143
|
-
|
|
2144
|
-
const supportedModes = ['delivery', 'preview', 'editor'];
|
|
2145
|
-
|
|
2146
|
-
const MEDIA_QUERY_REGEXP = /(<|>)(\d{1,})(px|cm|mm|in|pt|pc)$/;
|
|
2147
|
-
const toCSSMediaQuery = ({ query }) => {
|
|
2148
|
-
if (query === '*')
|
|
2149
|
-
return undefined;
|
|
2150
|
-
const match = query.match(MEDIA_QUERY_REGEXP);
|
|
2151
|
-
if (!match)
|
|
2152
|
-
return undefined;
|
|
2153
|
-
const [, operator, value, unit] = match;
|
|
2154
|
-
if (operator === '<') {
|
|
2155
|
-
const maxScreenWidth = Number(value) - 1;
|
|
2156
|
-
return `(max-width: ${maxScreenWidth}${unit})`;
|
|
2157
|
-
}
|
|
2158
|
-
else if (operator === '>') {
|
|
2159
|
-
const minScreenWidth = Number(value) + 1;
|
|
2160
|
-
return `(min-width: ${minScreenWidth}${unit})`;
|
|
2161
|
-
}
|
|
2162
|
-
return undefined;
|
|
2163
|
-
};
|
|
2164
|
-
// Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
|
|
2165
|
-
const findLast = (array, predicate) => {
|
|
2166
|
-
return array.reverse().find(predicate);
|
|
2167
|
-
};
|
|
2168
|
-
// Initialise media query matchers. This won't include the always matching fallback breakpoint.
|
|
2169
|
-
const mediaQueryMatcher = (breakpoints) => {
|
|
2170
|
-
const mediaQueryMatches = {};
|
|
2171
|
-
const mediaQueryMatchers = breakpoints
|
|
2172
|
-
.map((breakpoint) => {
|
|
2173
|
-
const cssMediaQuery = toCSSMediaQuery(breakpoint);
|
|
2174
|
-
if (!cssMediaQuery)
|
|
2175
|
-
return undefined;
|
|
2176
|
-
if (typeof window === 'undefined')
|
|
2177
|
-
return undefined;
|
|
2178
|
-
const mediaQueryMatcher = window.matchMedia(cssMediaQuery);
|
|
2179
|
-
mediaQueryMatches[breakpoint.id] = mediaQueryMatcher.matches;
|
|
2180
|
-
return { id: breakpoint.id, signal: mediaQueryMatcher };
|
|
2181
|
-
})
|
|
2182
|
-
.filter((matcher) => !!matcher);
|
|
2183
|
-
return [mediaQueryMatchers, mediaQueryMatches];
|
|
2184
|
-
};
|
|
2185
|
-
const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
|
|
2186
|
-
// The breakpoints are ordered (desktop-first: descending by screen width)
|
|
2187
|
-
const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
|
|
2188
|
-
id,
|
|
2189
|
-
index,
|
|
2190
|
-
// The fallback breakpoint with wildcard query will always match
|
|
2191
|
-
isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
|
|
2192
|
-
}));
|
|
2193
|
-
// Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
|
|
2194
|
-
const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
|
|
2195
|
-
return mostSpecificIndex ?? fallbackBreakpointIndex;
|
|
2196
|
-
};
|
|
2197
|
-
const getFallbackBreakpointIndex = (breakpoints) => {
|
|
2198
|
-
// We assume that there will be a single breakpoint which uses the wildcard query.
|
|
2199
|
-
// If there is none, we just take the first one in the list.
|
|
2200
|
-
return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
|
|
2201
|
-
};
|
|
2202
|
-
const builtInStylesWithDesignTokens = [
|
|
2203
|
-
'cfMargin',
|
|
2204
|
-
'cfPadding',
|
|
2205
|
-
'cfGap',
|
|
2206
|
-
'cfWidth',
|
|
2207
|
-
'cfHeight',
|
|
2208
|
-
'cfBackgroundColor',
|
|
2209
|
-
'cfBorder',
|
|
2210
|
-
'cfBorderRadius',
|
|
2211
|
-
'cfFontSize',
|
|
2212
|
-
'cfLineHeight',
|
|
2213
|
-
'cfLetterSpacing',
|
|
2214
|
-
'cfTextColor',
|
|
2215
|
-
'cfMaxWidth',
|
|
2216
|
-
];
|
|
2217
|
-
const getValueForBreakpoint = (valuesByBreakpoint, breakpoints, activeBreakpointIndex, variableName) => {
|
|
2218
|
-
const eventuallyResolveDesignTokens = (value) => {
|
|
2219
|
-
// For some built-in design propertier, we support design tokens
|
|
2220
|
-
if (builtInStylesWithDesignTokens.includes(variableName)) {
|
|
2221
|
-
return getDesignTokenRegistration(value, variableName);
|
|
2222
|
-
}
|
|
2223
|
-
// For all other properties, we just return the breakpoint-specific value
|
|
2224
|
-
return value;
|
|
2225
|
-
};
|
|
2226
|
-
if (valuesByBreakpoint instanceof Object) {
|
|
2227
|
-
// Assume that the values are sorted by media query to apply the cascading CSS logic
|
|
2228
|
-
for (let index = activeBreakpointIndex; index >= 0; index--) {
|
|
2229
|
-
const breakpointId = breakpoints[index].id;
|
|
2230
|
-
if (valuesByBreakpoint[breakpointId]) {
|
|
2231
|
-
// If the value is defined, we use it and stop the breakpoints cascade
|
|
2232
|
-
return eventuallyResolveDesignTokens(valuesByBreakpoint[breakpointId]);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
// If no breakpoint matched, we search and apply the fallback breakpoint
|
|
2236
|
-
const fallbackBreakpointIndex = getFallbackBreakpointIndex(breakpoints);
|
|
2237
|
-
const fallbackBreakpointId = breakpoints[fallbackBreakpointIndex].id;
|
|
2238
|
-
return eventuallyResolveDesignTokens(valuesByBreakpoint[fallbackBreakpointId]);
|
|
2239
|
-
}
|
|
2240
|
-
else {
|
|
2241
|
-
// Old design properties did not support breakpoints, keep for backward compatibility
|
|
2242
|
-
return valuesByBreakpoint;
|
|
2243
|
-
}
|
|
2244
|
-
};
|
|
2245
|
-
|
|
2246
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2247
|
-
const isLinkToAsset = (variable) => {
|
|
2248
|
-
if (!variable)
|
|
2249
|
-
return false;
|
|
2250
|
-
if (typeof variable !== 'object')
|
|
2251
|
-
return false;
|
|
2252
|
-
return (variable.sys?.linkType === 'Asset' &&
|
|
2253
|
-
typeof variable.sys?.id === 'string' &&
|
|
2254
|
-
!!variable.sys?.id &&
|
|
2255
|
-
variable.sys?.type === 'Link');
|
|
2256
|
-
};
|
|
2257
|
-
|
|
2258
|
-
const isLink = (maybeLink) => {
|
|
2259
|
-
if (maybeLink === null)
|
|
2260
|
-
return false;
|
|
2261
|
-
if (typeof maybeLink !== 'object')
|
|
2262
|
-
return false;
|
|
2263
|
-
const link = maybeLink;
|
|
2264
|
-
return Boolean(link.sys?.id) && link.sys?.type === 'Link';
|
|
2265
|
-
};
|
|
2266
|
-
|
|
2267
|
-
/**
|
|
2268
|
-
* This module encapsulates format of the path to a deep reference.
|
|
2269
|
-
*/
|
|
2270
|
-
const parseDataSourcePathIntoFieldset = (path) => {
|
|
2271
|
-
const parsedPath = parseDeepPath(path);
|
|
2272
|
-
if (null === parsedPath) {
|
|
2273
|
-
throw new Error(`Cannot parse path '${path}' as deep path`);
|
|
2274
|
-
}
|
|
2275
|
-
return parsedPath.fields.map((field) => [null, field, '~locale']);
|
|
2276
|
-
};
|
|
2277
|
-
/**
|
|
2278
|
-
* Parse path into components, supports L1 references (one reference follow) atm.
|
|
2279
|
-
* @param path from data source. eg. `/uuid123/fields/image/~locale/fields/file/~locale`
|
|
2280
|
-
* eg. `/uuid123/fields/file/~locale/fields/title/~locale`
|
|
2281
|
-
* @returns
|
|
2282
|
-
*/
|
|
2283
|
-
const parseDataSourcePathWithL1DeepBindings = (path) => {
|
|
2284
|
-
const parsedPath = parseDeepPath(path);
|
|
2285
|
-
if (null === parsedPath) {
|
|
2286
|
-
throw new Error(`Cannot parse path '${path}' as deep path`);
|
|
2287
|
-
}
|
|
2288
|
-
return {
|
|
2289
|
-
key: parsedPath.key,
|
|
2290
|
-
field: parsedPath.fields[0],
|
|
2291
|
-
referentField: parsedPath.fields[1],
|
|
2292
|
-
};
|
|
2293
|
-
};
|
|
2294
|
-
/**
|
|
2295
|
-
* Detects if paths is valid deep-path, like:
|
|
2296
|
-
* - /gV6yKXp61hfYrR7rEyKxY/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
|
|
2297
|
-
* or regular, like:
|
|
2298
|
-
* - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
|
|
2299
|
-
* @returns
|
|
2300
|
-
*/
|
|
2301
|
-
const isDeepPath = (deepPathCandidate) => {
|
|
2302
|
-
const deepPathParsed = parseDeepPath(deepPathCandidate);
|
|
2303
|
-
if (!deepPathParsed) {
|
|
2304
|
-
return false;
|
|
2305
|
-
}
|
|
2306
|
-
return deepPathParsed.fields.length > 1;
|
|
2307
|
-
};
|
|
2308
|
-
const parseDeepPath = (deepPathCandidate) => {
|
|
2309
|
-
// ALGORITHM:
|
|
2310
|
-
// We start with deep path in form:
|
|
2311
|
-
// /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
|
|
2312
|
-
// First turn string into array of segments
|
|
2313
|
-
// ['', 'uuid123', 'fields', 'mainStory', '~locale', 'fields', 'cover', '~locale', 'fields', 'title', '~locale']
|
|
2314
|
-
// Then group segments into intermediate represenatation - chunks, where each non-initial chunk starts with 'fields'
|
|
2315
|
-
// [
|
|
2316
|
-
// [ "", "uuid123" ],
|
|
2317
|
-
// [ "fields", "mainStory", "~locale" ],
|
|
2318
|
-
// [ "fields", "cover", "~locale" ],
|
|
2319
|
-
// [ "fields", "title", "~locale" ]
|
|
2320
|
-
// ]
|
|
2321
|
-
// Then check "initial" chunk for corretness
|
|
2322
|
-
// Then check all "field-leading" chunks for correctness
|
|
2323
|
-
const isValidInitialChunk = (initialChunk) => {
|
|
2324
|
-
// must have start with '' and have at least 2 segments, second non-empty
|
|
2325
|
-
// eg. /-_432uuid123123
|
|
2326
|
-
return /^\/([^/^~]+)$/.test(initialChunk.join('/'));
|
|
2327
|
-
};
|
|
2328
|
-
const isValidFieldChunk = (fieldChunk) => {
|
|
2329
|
-
// must start with 'fields' and have at least 3 segments, second non-empty and last segment must be '~locale'
|
|
2330
|
-
// eg. fields/-32234mainStory/~locale
|
|
2331
|
-
return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
|
|
2332
|
-
};
|
|
2333
|
-
const deepPathSegments = deepPathCandidate.split('/');
|
|
2334
|
-
const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
|
|
2335
|
-
if (chunks.length <= 1) {
|
|
2336
|
-
return null; // malformed path, even regular paths have at least 2 chunks
|
|
2337
|
-
}
|
|
2338
|
-
else if (chunks.length === 2) {
|
|
2339
|
-
return null; // deep paths have at least 3 chunks
|
|
2340
|
-
}
|
|
2341
|
-
// With 3+ chunks we can now check for deep path correctness
|
|
2342
|
-
const [initialChunk, ...fieldChunks] = chunks;
|
|
2343
|
-
if (!isValidInitialChunk(initialChunk)) {
|
|
2344
|
-
return null;
|
|
2345
|
-
}
|
|
2346
|
-
if (!fieldChunks.every(isValidFieldChunk)) {
|
|
2347
|
-
return null;
|
|
2348
|
-
}
|
|
2349
|
-
return {
|
|
2350
|
-
key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
|
|
2351
|
-
fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
|
|
2352
|
-
};
|
|
2353
|
-
};
|
|
2354
|
-
const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
|
|
2355
|
-
const chunks = [];
|
|
2356
|
-
let currentChunk = [];
|
|
2357
|
-
const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
|
|
2358
|
-
const excludeEmptyChunks = (chunk) => chunk.length > 0;
|
|
2359
|
-
for (let i = 0; i < segments.length; i++) {
|
|
2360
|
-
const isInitialElement = i === 0;
|
|
2361
|
-
const segment = segments[i];
|
|
2362
|
-
if (isInitialElement) {
|
|
2363
|
-
currentChunk = [segment];
|
|
2364
|
-
}
|
|
2365
|
-
else if (isSegmentBeginningOfChunk(segment)) {
|
|
2366
|
-
chunks.push(currentChunk);
|
|
2367
|
-
currentChunk = [segment];
|
|
2368
|
-
}
|
|
2369
|
-
else {
|
|
2370
|
-
currentChunk.push(segment);
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
chunks.push(currentChunk);
|
|
2374
|
-
return chunks.filter(excludeEmptyChunks);
|
|
2375
|
-
};
|
|
2376
|
-
const lastPathNamedSegmentEq = (path, expectedName) => {
|
|
2377
|
-
// `/key123/fields/featureImage/~locale/fields/file/~locale`
|
|
2378
|
-
// ['', 'key123', 'fields', 'featureImage', '~locale', 'fields', 'file', '~locale']
|
|
2379
|
-
const segments = path.split('/');
|
|
2380
|
-
if (segments.length < 2) {
|
|
2381
|
-
console.warn(`[experiences-sdk-react] Attempting to check whether last named segment of the path (${path}) equals to '${expectedName}', but the path doesn't have enough segments.`);
|
|
2382
|
-
return false;
|
|
2383
|
-
}
|
|
2384
|
-
const secondLast = segments[segments.length - 2]; // skipping trailing '~locale'
|
|
2385
|
-
return secondLast === expectedName;
|
|
2386
|
-
};
|
|
2387
|
-
|
|
2388
|
-
const resolveHyperlinkPattern = (pattern, entry, locale) => {
|
|
2389
|
-
if (!entry || !locale)
|
|
2390
|
-
return null;
|
|
2391
|
-
const variables = {
|
|
2392
|
-
entry,
|
|
2393
|
-
locale,
|
|
2394
|
-
};
|
|
2395
|
-
return buildTemplate({ template: pattern, context: variables });
|
|
2396
|
-
};
|
|
2397
|
-
function getValue(obj, path) {
|
|
2398
|
-
return path
|
|
2399
|
-
.replace(/\[/g, '.')
|
|
2400
|
-
.replace(/\]/g, '')
|
|
2401
|
-
.split('.')
|
|
2402
|
-
.reduce((o, k) => (o || {})[k], obj);
|
|
2403
|
-
}
|
|
2404
|
-
function addLocale(str, locale) {
|
|
2405
|
-
const fieldsIndicator = 'fields';
|
|
2406
|
-
const fieldsIndex = str.indexOf(fieldsIndicator);
|
|
2407
|
-
if (fieldsIndex !== -1) {
|
|
2408
|
-
const dotIndex = str.indexOf('.', fieldsIndex + fieldsIndicator.length + 1); // +1 for '.'
|
|
2409
|
-
if (dotIndex !== -1) {
|
|
2410
|
-
return str.slice(0, dotIndex + 1) + locale + '.' + str.slice(dotIndex + 1);
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
return str;
|
|
2414
|
-
}
|
|
2415
|
-
function getTemplateValue(ctx, path) {
|
|
2416
|
-
const pathWithLocale = addLocale(path, ctx.locale);
|
|
2417
|
-
const retrievedValue = getValue(ctx, pathWithLocale);
|
|
2418
|
-
return typeof retrievedValue === 'object' && retrievedValue !== null
|
|
2419
|
-
? retrievedValue[ctx.locale]
|
|
2420
|
-
: retrievedValue;
|
|
2421
|
-
}
|
|
2422
|
-
function buildTemplate({ template, context, }) {
|
|
2423
|
-
const localeVariable = /{\s*locale\s*}/g;
|
|
2424
|
-
// e.g. "{ page.sys.id }"
|
|
2425
|
-
const variables = /{\s*([\S]+?)\s*}/g;
|
|
2426
|
-
return (template
|
|
2427
|
-
// first replace the locale pattern
|
|
2428
|
-
.replace(localeVariable, context.locale)
|
|
2429
|
-
// then resolve the remaining variables
|
|
2430
|
-
.replace(variables, (_, path) => {
|
|
2431
|
-
const fallback = path + '_NOT_FOUND';
|
|
2432
|
-
const value = getTemplateValue(context, path) ?? fallback;
|
|
2433
|
-
// using _.result didn't gave proper results so we run our own version of it
|
|
2434
|
-
return String(typeof value === 'function' ? value() : value);
|
|
2435
|
-
}));
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
const sendMessage = (eventType, data) => {
|
|
2439
|
-
if (typeof window === 'undefined') {
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
console.debug(`[experiences-sdk-react::sendMessage] Sending message [${eventType}]`, {
|
|
2443
|
-
source: 'customer-app',
|
|
2444
|
-
eventType,
|
|
2445
|
-
payload: data,
|
|
2446
|
-
});
|
|
2447
|
-
window.parent?.postMessage({
|
|
2448
|
-
source: 'customer-app',
|
|
2449
|
-
eventType,
|
|
2450
|
-
payload: data,
|
|
2451
|
-
}, '*');
|
|
2452
|
-
};
|
|
2453
|
-
|
|
2454
|
-
/**
|
|
2455
|
-
* Base Store for entities
|
|
2456
|
-
* Can be extended for the different loading behaviours (editor, production, ..)
|
|
2457
|
-
*/
|
|
2458
|
-
class EntityStoreBase {
|
|
2459
|
-
constructor({ entities, locale }) {
|
|
2460
|
-
this.entryMap = new Map();
|
|
2461
|
-
this.assetMap = new Map();
|
|
2462
|
-
this.locale = locale;
|
|
2463
|
-
for (const entity of entities) {
|
|
2464
|
-
this.addEntity(entity);
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
get entities() {
|
|
2468
|
-
return [...this.entryMap.values(), ...this.assetMap.values()];
|
|
2469
|
-
}
|
|
2470
|
-
updateEntity(entity) {
|
|
2471
|
-
this.addEntity(entity);
|
|
2472
|
-
}
|
|
2473
|
-
getEntryOrAsset(linkOrEntryOrAsset, path) {
|
|
2474
|
-
if (isDeepPath(path)) {
|
|
2475
|
-
return this.getDeepEntry(linkOrEntryOrAsset, path);
|
|
2476
|
-
}
|
|
2477
|
-
let entity;
|
|
2478
|
-
if (isLink(linkOrEntryOrAsset)) {
|
|
2479
|
-
const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
|
|
2480
|
-
? this.entryMap.get(linkOrEntryOrAsset.sys.id)
|
|
2481
|
-
: this.assetMap.get(linkOrEntryOrAsset.sys.id);
|
|
2482
|
-
if (!resolvedEntity || resolvedEntity.sys.type !== linkOrEntryOrAsset.sys.linkType) {
|
|
2483
|
-
console.warn(`Experience references unresolved entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
|
|
2484
|
-
return;
|
|
2485
|
-
}
|
|
2486
|
-
entity = resolvedEntity;
|
|
2487
|
-
}
|
|
2488
|
-
else {
|
|
2489
|
-
// We already have the complete entity in preview & delivery (resolved by the CMA client)
|
|
2490
|
-
entity = linkOrEntryOrAsset;
|
|
2491
|
-
}
|
|
2492
|
-
return entity;
|
|
2493
|
-
}
|
|
2494
|
-
/**
|
|
2495
|
-
* @deprecated in the base class this should be simply an abstract method
|
|
2496
|
-
* @param entityLink
|
|
2497
|
-
* @param path
|
|
2498
|
-
* @returns
|
|
2499
|
-
*/
|
|
2500
|
-
getValue(entityLink, path) {
|
|
2501
|
-
const entity = this.getEntity(entityLink.sys.linkType, entityLink.sys.id);
|
|
2502
|
-
if (!entity) {
|
|
2503
|
-
// TODO: move to `debug` utils once it is extracted
|
|
2504
|
-
console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
|
|
2505
|
-
return;
|
|
2506
|
-
}
|
|
2507
|
-
return get(entity, path);
|
|
2508
|
-
}
|
|
2509
|
-
getEntityFromLink(link) {
|
|
2510
|
-
const resolvedEntity = link.sys.linkType === 'Entry'
|
|
2511
|
-
? this.entryMap.get(link.sys.id)
|
|
2512
|
-
: this.assetMap.get(link.sys.id);
|
|
2513
|
-
if (!resolvedEntity || resolvedEntity.sys.type !== link.sys.linkType) {
|
|
2514
|
-
console.warn(`Experience references unresolved entity: ${JSON.stringify(link)}`);
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
return resolvedEntity;
|
|
2518
|
-
}
|
|
2519
|
-
getEntitiesFromMap(type, ids) {
|
|
2520
|
-
const resolved = [];
|
|
2521
|
-
const missing = [];
|
|
2522
|
-
for (const id of ids) {
|
|
2523
|
-
const entity = this.getEntity(type, id);
|
|
2524
|
-
if (entity) {
|
|
2525
|
-
resolved.push(entity);
|
|
2526
|
-
}
|
|
2527
|
-
else {
|
|
2528
|
-
missing.push(id);
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
return {
|
|
2532
|
-
resolved,
|
|
2533
|
-
missing,
|
|
2534
|
-
};
|
|
2535
|
-
}
|
|
2536
|
-
addEntity(entity) {
|
|
2537
|
-
if (this.isAsset(entity)) {
|
|
2538
|
-
this.assetMap.set(entity.sys.id, entity);
|
|
2539
|
-
}
|
|
2540
|
-
else {
|
|
2541
|
-
this.entryMap.set(entity.sys.id, entity);
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
async fetchAsset(id) {
|
|
2545
|
-
const { resolved, missing } = this.getEntitiesFromMap('Asset', [id]);
|
|
2546
|
-
if (missing.length) {
|
|
2547
|
-
// TODO: move to `debug` utils once it is extracted
|
|
2548
|
-
console.warn(`Asset "${id}" is not in the store`);
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
return resolved[0];
|
|
2552
|
-
}
|
|
2553
|
-
async fetchAssets(ids) {
|
|
2554
|
-
const { resolved, missing } = this.getEntitiesFromMap('Asset', ids);
|
|
2555
|
-
if (missing.length) {
|
|
2556
|
-
throw new Error(`Missing assets in the store (${missing.join(',')})`);
|
|
2557
|
-
}
|
|
2558
|
-
return resolved;
|
|
2559
|
-
}
|
|
2560
|
-
async fetchEntry(id) {
|
|
2561
|
-
const { resolved, missing } = this.getEntitiesFromMap('Entry', [id]);
|
|
2562
|
-
if (missing.length) {
|
|
2563
|
-
// TODO: move to `debug` utils once it is extracted
|
|
2564
|
-
console.warn(`Entry "${id}" is not in the store`);
|
|
2565
|
-
return;
|
|
2566
|
-
}
|
|
2567
|
-
return resolved[0];
|
|
2568
|
-
}
|
|
2569
|
-
async fetchEntries(ids) {
|
|
2570
|
-
const { resolved, missing } = this.getEntitiesFromMap('Entry', ids);
|
|
2571
|
-
if (missing.length) {
|
|
2572
|
-
throw new Error(`Missing assets in the store (${missing.join(',')})`);
|
|
2573
|
-
}
|
|
2574
|
-
return resolved;
|
|
2575
|
-
}
|
|
2576
|
-
getDeepEntry(linkOrEntryOrAsset, path) {
|
|
2577
|
-
const resolveFieldset = (unresolvedFieldset, headEntry) => {
|
|
2578
|
-
const resolvedFieldset = [];
|
|
2579
|
-
let entityToResolveFieldsFrom = headEntry;
|
|
2580
|
-
for (let i = 0; i < unresolvedFieldset.length; i++) {
|
|
2581
|
-
const isLeaf = i === unresolvedFieldset.length - 1; // with last row, we are not expecting a link, but a value
|
|
2582
|
-
const row = unresolvedFieldset[i];
|
|
2583
|
-
const [, field, _localeQualifier] = row;
|
|
2584
|
-
if (!entityToResolveFieldsFrom) {
|
|
2585
|
-
throw new Error(`Logic Error: Cannot resolve field ${field} of a fieldset as there is no entity to resolve it from.`);
|
|
2586
|
-
}
|
|
2587
|
-
if (isLeaf) {
|
|
2588
|
-
resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
|
|
2589
|
-
break;
|
|
2590
|
-
}
|
|
2591
|
-
const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
|
|
2592
|
-
if (undefined === fieldValue) {
|
|
2593
|
-
return {
|
|
2594
|
-
resolvedFieldset,
|
|
2595
|
-
isFullyResolved: false,
|
|
2596
|
-
reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
|
|
2597
|
-
};
|
|
2598
|
-
}
|
|
2599
|
-
else if (isLink(fieldValue)) {
|
|
2600
|
-
const entity = this.getEntityFromLink(fieldValue);
|
|
2601
|
-
if (entity === undefined) {
|
|
2602
|
-
return {
|
|
2603
|
-
resolvedFieldset,
|
|
2604
|
-
isFullyResolved: false,
|
|
2605
|
-
reason: `Field reference Link (sys.id=${fieldValue.sys.id}) not found in the EntityStore, waiting...`,
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
|
|
2609
|
-
entityToResolveFieldsFrom = entity; // we move up
|
|
2610
|
-
}
|
|
2611
|
-
else {
|
|
2612
|
-
// TODO: Eg. when someone changed the schema and the field is not a link anymore, what should we return then?
|
|
2613
|
-
throw new Error(`LogicError: Invalid value of a field we consider a reference field. Cannot resolve field ${field} of a fieldset as it is not a link, neither undefined.`);
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
return {
|
|
2617
|
-
resolvedFieldset,
|
|
2618
|
-
isFullyResolved: true,
|
|
2619
|
-
};
|
|
2620
|
-
};
|
|
2621
|
-
const headEntity = isLink(linkOrEntryOrAsset)
|
|
2622
|
-
? this.getEntityFromLink(linkOrEntryOrAsset)
|
|
2623
|
-
: linkOrEntryOrAsset;
|
|
2624
|
-
if (undefined === headEntity) {
|
|
2625
|
-
return;
|
|
2626
|
-
}
|
|
2627
|
-
const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
|
|
2628
|
-
// The purpose here is to take this intermediate representation of the deep-path
|
|
2629
|
-
// and to follow the links to the leaf-entity and field
|
|
2630
|
-
// in case we can't follow till the end, we should signal that there was null-reference in the path
|
|
2631
|
-
const { resolvedFieldset, isFullyResolved, reason } = resolveFieldset(unresolvedFieldset, headEntity);
|
|
2632
|
-
if (!isFullyResolved) {
|
|
2633
|
-
reason &&
|
|
2634
|
-
console.debug(`[exp-builder.sdk::EntityStoreBased::getValueDeep()] Deep path wasn't resolved till leaf node, falling back to undefined, because: ${reason}`);
|
|
2635
|
-
return;
|
|
2636
|
-
}
|
|
2637
|
-
const [leafEntity] = resolvedFieldset[resolvedFieldset.length - 1];
|
|
2638
|
-
return leafEntity;
|
|
2639
|
-
}
|
|
2640
|
-
isAsset(entity) {
|
|
2641
|
-
return entity.sys.type === 'Asset';
|
|
2642
|
-
}
|
|
2643
|
-
getEntity(type, id) {
|
|
2644
|
-
if (type === 'Asset') {
|
|
2645
|
-
return this.assetMap.get(id);
|
|
2646
|
-
}
|
|
2647
|
-
return this.entryMap.get(id);
|
|
2648
|
-
}
|
|
2649
|
-
toJSON() {
|
|
2650
|
-
return {
|
|
2651
|
-
entryMap: Object.fromEntries(this.entryMap),
|
|
2652
|
-
assetMap: Object.fromEntries(this.assetMap),
|
|
2653
|
-
locale: this.locale,
|
|
2654
|
-
};
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
|
-
/**
|
|
2659
|
-
* EntityStore which resolves entries and assets from the editor
|
|
2660
|
-
* over the sendMessage and subscribe functions.
|
|
2661
|
-
*/
|
|
2662
|
-
class EditorEntityStore extends EntityStoreBase {
|
|
2663
|
-
constructor({ entities, locale, sendMessage, subscribe, timeoutDuration = 3000, }) {
|
|
2664
|
-
super({ entities, locale });
|
|
2665
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2666
|
-
this.requestCache = new Map();
|
|
2667
|
-
this.cacheIdSeperator = ',';
|
|
2668
|
-
this.sendMessage = sendMessage;
|
|
2669
|
-
this.subscribe = subscribe;
|
|
2670
|
-
this.timeoutDuration = timeoutDuration;
|
|
2671
|
-
}
|
|
2672
|
-
cleanupPromise(referenceId) {
|
|
2673
|
-
setTimeout(() => {
|
|
2674
|
-
this.requestCache.delete(referenceId);
|
|
2675
|
-
}, 300);
|
|
2676
|
-
}
|
|
2677
|
-
getCacheId(id) {
|
|
2678
|
-
return id.length === 1 ? id[0] : id.join(this.cacheIdSeperator);
|
|
2679
|
-
}
|
|
2680
|
-
async fetchEntity(type, ids, skipCache = false) {
|
|
2681
|
-
let missing;
|
|
2682
|
-
if (!skipCache) {
|
|
2683
|
-
const { missing: missingFromCache, resolved } = this.getEntitiesFromMap(type, ids);
|
|
2684
|
-
if (missingFromCache.length === 0) {
|
|
2685
|
-
// everything is already in cache
|
|
2686
|
-
return resolved;
|
|
2687
|
-
}
|
|
2688
|
-
missing = missingFromCache;
|
|
2689
|
-
}
|
|
2690
|
-
else {
|
|
2691
|
-
missing = [...ids];
|
|
2692
|
-
}
|
|
2693
|
-
const cacheId = this.getCacheId(missing);
|
|
2694
|
-
const openRequest = this.requestCache.get(cacheId);
|
|
2695
|
-
if (openRequest) {
|
|
2696
|
-
return openRequest;
|
|
2697
|
-
}
|
|
2698
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2699
|
-
const newPromise = new Promise((resolve, reject) => {
|
|
2700
|
-
const unsubscribe = this.subscribe(PostMessageMethods.REQUESTED_ENTITIES, (message) => {
|
|
2701
|
-
const messageIds = [
|
|
2702
|
-
...message.entities.map((entity) => entity.sys.id),
|
|
2703
|
-
...(message.missingEntityIds ?? []),
|
|
2704
|
-
];
|
|
2705
|
-
if (missing.every((id) => messageIds.find((entityId) => entityId === id))) {
|
|
2706
|
-
clearTimeout(timeout);
|
|
2707
|
-
resolve(message.entities);
|
|
2708
|
-
this.cleanupPromise(cacheId);
|
|
2709
|
-
ids.forEach((id) => this.cleanupPromise(id));
|
|
2710
|
-
unsubscribe();
|
|
2711
|
-
}
|
|
2712
|
-
else {
|
|
2713
|
-
console.warn('Unexpected entities received in REQUESTED_ENTITIES. Ignoring this response.');
|
|
2714
|
-
}
|
|
2715
|
-
});
|
|
2716
|
-
const timeout = setTimeout(() => {
|
|
2717
|
-
reject(new Error(`Request for entities timed out ${this.timeoutDuration}ms} for ${cacheId}`));
|
|
2718
|
-
this.cleanupPromise(cacheId);
|
|
2719
|
-
ids.forEach((id) => this.cleanupPromise(id));
|
|
2720
|
-
unsubscribe();
|
|
2721
|
-
}, this.timeoutDuration);
|
|
2722
|
-
this.sendMessage(PostMessageMethods.REQUEST_ENTITIES, {
|
|
2723
|
-
entityIds: missing,
|
|
2724
|
-
entityType: type,
|
|
2725
|
-
locale: this.locale,
|
|
2726
|
-
});
|
|
2727
|
-
});
|
|
2728
|
-
this.requestCache.set(cacheId, newPromise);
|
|
2729
|
-
ids.forEach((cid) => {
|
|
2730
|
-
this.requestCache.set(cid, newPromise);
|
|
2731
|
-
});
|
|
2732
|
-
const result = (await newPromise);
|
|
2733
|
-
result.forEach((value) => {
|
|
2734
|
-
this.addEntity(value);
|
|
2735
|
-
});
|
|
2736
|
-
return this.getEntitiesFromMap(type, ids).resolved;
|
|
2737
|
-
}
|
|
2738
|
-
async fetchAsset(id, skipCache = false) {
|
|
2739
|
-
try {
|
|
2740
|
-
return (await this.fetchAssets([id], skipCache))[0];
|
|
2741
|
-
}
|
|
2742
|
-
catch (err) {
|
|
2743
|
-
// TODO: move to debug utils once it is extracted
|
|
2744
|
-
console.warn(`Failed to request asset ${id}`);
|
|
2745
|
-
return undefined;
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
fetchAssets(ids, skipCache = false) {
|
|
2749
|
-
return this.fetchEntity('Asset', ids, skipCache);
|
|
2750
|
-
}
|
|
2751
|
-
async fetchEntry(id, skipCache = false) {
|
|
2752
|
-
try {
|
|
2753
|
-
return (await this.fetchEntries([id], skipCache))[0];
|
|
2754
|
-
}
|
|
2755
|
-
catch (err) {
|
|
2756
|
-
// TODO: move to debug utils once it is extracted
|
|
2757
|
-
console.warn(`Failed to request entry ${id}`, err);
|
|
2758
|
-
return undefined;
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
fetchEntries(ids, skipCache = false) {
|
|
2762
|
-
return this.fetchEntity('Entry', ids, skipCache);
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
function transformAssetFileToUrl(fieldValue) {
|
|
2767
|
-
return fieldValue && typeof fieldValue == 'object' && fieldValue.url
|
|
2768
|
-
? fieldValue.url
|
|
2769
|
-
: fieldValue;
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
// The default of 3s in the EditorEntityStore is sometimes timing out and
|
|
2773
|
-
// leads to not rendering bound content and assemblies.
|
|
2774
|
-
const REQUEST_TIMEOUT = 10000;
|
|
2775
|
-
class EditorModeEntityStore extends EditorEntityStore {
|
|
2776
|
-
constructor({ entities, locale }) {
|
|
2777
|
-
console.debug(`[experiences-sdk-react] Initializing editor entity store with ${entities.length} entities for locale ${locale}.`, { entities });
|
|
2778
|
-
const subscribe = (method, cb) => {
|
|
2779
|
-
const handleMessage = (event) => {
|
|
2780
|
-
const data = JSON.parse(event.data);
|
|
2781
|
-
if (typeof data !== 'object' || !data)
|
|
2782
|
-
return;
|
|
2783
|
-
if (data.source !== 'composability-app')
|
|
2784
|
-
return;
|
|
2785
|
-
if (data.eventType === method) {
|
|
2786
|
-
cb(data.payload);
|
|
2787
|
-
}
|
|
2788
|
-
};
|
|
2789
|
-
if (typeof window !== 'undefined') {
|
|
2790
|
-
window.addEventListener('message', handleMessage);
|
|
2791
|
-
}
|
|
2792
|
-
return () => {
|
|
2793
|
-
if (typeof window !== 'undefined') {
|
|
2794
|
-
window.removeEventListener('message', handleMessage);
|
|
2795
|
-
}
|
|
2796
|
-
};
|
|
2797
|
-
};
|
|
2798
|
-
super({ entities, sendMessage, subscribe, locale, timeoutDuration: REQUEST_TIMEOUT });
|
|
2799
|
-
this.locale = locale;
|
|
2800
|
-
}
|
|
2801
|
-
/**
|
|
2802
|
-
* This function collects and returns the list of requested entries and assets. Additionally, it checks
|
|
2803
|
-
* upfront whether any async fetching logic is actually happening. If not, it returns a plain `false` value, so we
|
|
2804
|
-
* can detect this early and avoid unnecessary re-renders.
|
|
2805
|
-
* @param entityLinks
|
|
2806
|
-
* @returns false if no async fetching is happening, otherwise a promise that resolves when all entities are fetched
|
|
2807
|
-
*/
|
|
2808
|
-
async fetchEntities({ missingEntryIds, missingAssetIds, skipCache = false, }) {
|
|
2809
|
-
// Entries and assets will be stored in entryMap and assetMap
|
|
2810
|
-
await Promise.all([
|
|
2811
|
-
this.fetchEntries(missingEntryIds, skipCache),
|
|
2812
|
-
this.fetchAssets(missingAssetIds, skipCache),
|
|
2813
|
-
]);
|
|
2814
|
-
}
|
|
2815
|
-
getMissingEntityIds(entityLinks) {
|
|
2816
|
-
const entryLinks = entityLinks.filter((link) => link.sys?.linkType === 'Entry');
|
|
2817
|
-
const assetLinks = entityLinks.filter((link) => link.sys?.linkType === 'Asset');
|
|
2818
|
-
const uniqueEntryIds = [...new Set(entryLinks.map((link) => link.sys.id))];
|
|
2819
|
-
const uniqueAssetIds = [...new Set(assetLinks.map((link) => link.sys.id))];
|
|
2820
|
-
const { missing: missingEntryIds } = this.getEntitiesFromMap('Entry', uniqueEntryIds);
|
|
2821
|
-
const { missing: missingAssetIds } = this.getEntitiesFromMap('Asset', uniqueAssetIds);
|
|
2822
|
-
return { missingEntryIds, missingAssetIds };
|
|
2823
|
-
}
|
|
2824
|
-
getValue(entityLinkOrEntity, path) {
|
|
2825
|
-
const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
|
|
2826
|
-
if (!entity) {
|
|
2827
|
-
return;
|
|
2828
|
-
}
|
|
2829
|
-
const fieldValue = get(entity, path);
|
|
2830
|
-
// walk around to render asset files
|
|
2831
|
-
return fieldValue && typeof fieldValue == 'object' && fieldValue.url
|
|
2832
|
-
? fieldValue.url
|
|
2833
|
-
: fieldValue;
|
|
2834
|
-
}
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
class EntityStore extends EntityStoreBase {
|
|
2838
|
-
constructor(options) {
|
|
2839
|
-
if (typeof options === 'string') {
|
|
2840
|
-
const data = JSON.parse(options);
|
|
2841
|
-
const { _experienceEntry, _unboundValues, locale, entryMap, assetMap } = data.entityStore;
|
|
2842
|
-
super({
|
|
2843
|
-
entities: [
|
|
2844
|
-
...Object.values(entryMap),
|
|
2845
|
-
...Object.values(assetMap),
|
|
2846
|
-
],
|
|
2847
|
-
locale,
|
|
2848
|
-
});
|
|
2849
|
-
this._experienceEntry = _experienceEntry;
|
|
2850
|
-
this._unboundValues = _unboundValues;
|
|
2851
|
-
}
|
|
2852
|
-
else {
|
|
2853
|
-
const { experienceEntry, entities, locale } = options;
|
|
2854
|
-
super({ entities, locale });
|
|
2855
|
-
if (isExperienceEntry(experienceEntry)) {
|
|
2856
|
-
this._experienceEntry = experienceEntry.fields;
|
|
2857
|
-
this._unboundValues = experienceEntry.fields.unboundValues;
|
|
2858
|
-
}
|
|
2859
|
-
else {
|
|
2860
|
-
throw new Error('Provided entry is not experience entry');
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
getCurrentLocale() {
|
|
2865
|
-
return this.locale;
|
|
2866
|
-
}
|
|
2867
|
-
get experienceEntryFields() {
|
|
2868
|
-
return this._experienceEntry;
|
|
2869
|
-
}
|
|
2870
|
-
get schemaVersion() {
|
|
2871
|
-
return this._experienceEntry?.componentTree.schemaVersion;
|
|
2872
|
-
}
|
|
2873
|
-
get breakpoints() {
|
|
2874
|
-
return this._experienceEntry?.componentTree.breakpoints ?? [];
|
|
2875
|
-
}
|
|
2876
|
-
get dataSource() {
|
|
2877
|
-
return this._experienceEntry?.dataSource ?? {};
|
|
2878
|
-
}
|
|
2879
|
-
get unboundValues() {
|
|
2880
|
-
return this._unboundValues ?? {};
|
|
2881
|
-
}
|
|
2882
|
-
get usedComponents() {
|
|
2883
|
-
return this._experienceEntry?.usedComponents ?? [];
|
|
2884
|
-
}
|
|
2885
|
-
/**
|
|
2886
|
-
* Extend the existing set of unbound values with the ones from the assembly definition.
|
|
2887
|
-
* When creating a new assembly out of a container, the unbound value keys are copied and
|
|
2888
|
-
* thus the existing and the added ones have colliding keys. In the case of overlapping value
|
|
2889
|
-
* keys, the ones from the experience overrule the ones from the assembly definition as
|
|
2890
|
-
* the latter one is certainly just a default value while the other one is from the actual instance.
|
|
2891
|
-
* @param unboundValues set of unbound values defined in the assembly definition
|
|
2892
|
-
*/
|
|
2893
|
-
addAssemblyUnboundValues(unboundValues) {
|
|
2894
|
-
this._unboundValues = { ...unboundValues, ...(this._unboundValues ?? {}) };
|
|
2895
|
-
}
|
|
2896
|
-
getValue(entityLinkOrEntity, path) {
|
|
2897
|
-
const entity = isLink(entityLinkOrEntity)
|
|
2898
|
-
? this.getEntityFromLink(entityLinkOrEntity)
|
|
2899
|
-
: entityLinkOrEntity;
|
|
2900
|
-
if (entity === undefined) {
|
|
2901
|
-
return;
|
|
2902
|
-
}
|
|
2903
|
-
const fieldValue = get(entity, path);
|
|
2904
|
-
return transformAssetFileToUrl(fieldValue);
|
|
2905
|
-
}
|
|
2906
|
-
toJSON() {
|
|
2907
|
-
return {
|
|
2908
|
-
_experienceEntry: this._experienceEntry,
|
|
2909
|
-
_unboundValues: this._unboundValues,
|
|
2910
|
-
...super.toJSON(),
|
|
2911
|
-
};
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
2914
|
-
|
|
2915
|
-
var VisualEditorMode;
|
|
2916
|
-
(function (VisualEditorMode) {
|
|
2917
|
-
VisualEditorMode["LazyLoad"] = "lazyLoad";
|
|
2918
|
-
VisualEditorMode["InjectScript"] = "injectScript";
|
|
2919
|
-
})(VisualEditorMode || (VisualEditorMode = {}));
|
|
2920
|
-
|
|
2921
|
-
function createExperience(options) {
|
|
2922
|
-
if (typeof options === 'string') {
|
|
2923
|
-
const entityStore = new EntityStore(options);
|
|
2924
|
-
return {
|
|
2925
|
-
entityStore,
|
|
2926
|
-
};
|
|
2927
|
-
}
|
|
2928
|
-
else {
|
|
2929
|
-
const { experienceEntry, referencedAssets, referencedEntries, locale } = options;
|
|
2930
|
-
if (!isExperienceEntry(experienceEntry)) {
|
|
2931
|
-
throw new Error('Provided entry is not experience entry');
|
|
2932
|
-
}
|
|
2933
|
-
const entityStore = new EntityStore({
|
|
2934
|
-
experienceEntry,
|
|
2935
|
-
entities: [...referencedEntries, ...referencedAssets],
|
|
2936
|
-
locale,
|
|
2937
|
-
});
|
|
2938
|
-
return {
|
|
2939
|
-
entityStore,
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
const fetchExperienceEntry = async ({ client, experienceTypeId, locale, identifier, }) => {
|
|
2945
|
-
if (!client) {
|
|
2946
|
-
throw new Error('Failed to fetch experience entities. Required "client" parameter was not provided');
|
|
2947
|
-
}
|
|
2948
|
-
if (!locale) {
|
|
2949
|
-
throw new Error('Failed to fetch experience entities. Required "locale" parameter was not provided');
|
|
2950
|
-
}
|
|
2951
|
-
if (!experienceTypeId) {
|
|
2952
|
-
throw new Error('Failed to fetch experience entities. Required "experienceTypeId" parameter was not provided');
|
|
2953
|
-
}
|
|
2954
|
-
if (!identifier.slug && !identifier.id) {
|
|
2955
|
-
throw new Error(`Failed to fetch experience entities. At least one identifier must be provided. Received: ${JSON.stringify(identifier)}`);
|
|
2956
|
-
}
|
|
2957
|
-
const filter = identifier.slug ? { 'fields.slug': identifier.slug } : { 'sys.id': identifier.id };
|
|
2958
|
-
const entries = await client.getEntries({
|
|
2959
|
-
content_type: experienceTypeId,
|
|
2960
|
-
locale,
|
|
2961
|
-
...filter,
|
|
2962
|
-
});
|
|
2963
|
-
if (entries.items.length > 1) {
|
|
2964
|
-
throw new Error(`More than one experience with identifier: ${JSON.stringify(identifier)} was found`);
|
|
2965
|
-
}
|
|
2966
|
-
return entries.items[0];
|
|
2967
|
-
};
|
|
2968
|
-
|
|
2969
|
-
function treeVisit(initialNode, onNode) {
|
|
2970
|
-
// returns last used index
|
|
2971
|
-
const _treeVisit = (currentNode, currentIndex, currentDepth) => {
|
|
2972
|
-
// Copy children in case of onNode removing it as we pass the node by reference
|
|
2973
|
-
const children = [...currentNode.children];
|
|
2974
|
-
onNode(currentNode, currentIndex, currentDepth);
|
|
2975
|
-
let nextAvailableIndex = currentIndex + 1;
|
|
2976
|
-
const lastUsedIndex = currentIndex;
|
|
2977
|
-
for (const child of children) {
|
|
2978
|
-
const lastUsedIndex = _treeVisit(child, nextAvailableIndex, currentDepth + 1);
|
|
2979
|
-
nextAvailableIndex = lastUsedIndex + 1;
|
|
2980
|
-
}
|
|
2981
|
-
return lastUsedIndex;
|
|
2982
|
-
};
|
|
2983
|
-
_treeVisit(initialNode, 0, 0);
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
class DeepReference {
|
|
2987
|
-
constructor({ path, dataSource }) {
|
|
2988
|
-
const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
|
|
2989
|
-
this.originalPath = path;
|
|
2990
|
-
this.entityId = dataSource[key].sys.id;
|
|
2991
|
-
this.entityLink = dataSource[key];
|
|
2992
|
-
this.field = field;
|
|
2993
|
-
this.referentField = referentField;
|
|
2994
|
-
}
|
|
2995
|
-
get headEntityId() {
|
|
2996
|
-
return this.entityId;
|
|
2997
|
-
}
|
|
2998
|
-
/**
|
|
2999
|
-
* Extracts referent from the path, using EntityStore as source of
|
|
3000
|
-
* entities during the resolution path.
|
|
3001
|
-
*/
|
|
3002
|
-
extractReferent(entityStore) {
|
|
3003
|
-
const headEntity = entityStore.getEntityFromLink(this.entityLink);
|
|
3004
|
-
const maybeReferentLink = headEntity?.fields[this.field];
|
|
3005
|
-
if (undefined === maybeReferentLink) {
|
|
3006
|
-
// field references nothing (or even field doesn't exist)
|
|
3007
|
-
return undefined;
|
|
3008
|
-
}
|
|
3009
|
-
if (!isLink(maybeReferentLink)) {
|
|
3010
|
-
// Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
|
|
3011
|
-
// Under normal circumstance we expect field to be a Link, but it could be an "impostor"
|
|
3012
|
-
// eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
|
|
3013
|
-
return undefined;
|
|
3014
|
-
}
|
|
3015
|
-
return maybeReferentLink;
|
|
3016
|
-
}
|
|
3017
|
-
static from(opt) {
|
|
3018
|
-
return new DeepReference(opt);
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
function gatherDeepReferencesFromExperienceEntry(experienceEntry) {
|
|
3022
|
-
const deepReferences = [];
|
|
3023
|
-
const dataSource = experienceEntry.fields.dataSource;
|
|
3024
|
-
const { children } = experienceEntry.fields.componentTree;
|
|
3025
|
-
treeVisit({
|
|
3026
|
-
definitionId: 'root',
|
|
3027
|
-
variables: {},
|
|
3028
|
-
children,
|
|
3029
|
-
}, (node) => {
|
|
3030
|
-
if (!node.variables)
|
|
3031
|
-
return;
|
|
3032
|
-
for (const [, variableMapping] of Object.entries(node.variables)) {
|
|
3033
|
-
if (variableMapping.type !== 'BoundValue')
|
|
3034
|
-
continue;
|
|
3035
|
-
if (!isDeepPath(variableMapping.path))
|
|
3036
|
-
continue;
|
|
3037
|
-
deepReferences.push(DeepReference.from({
|
|
3038
|
-
path: variableMapping.path,
|
|
3039
|
-
dataSource,
|
|
3040
|
-
}));
|
|
3041
|
-
}
|
|
3042
|
-
});
|
|
3043
|
-
return deepReferences;
|
|
3044
|
-
}
|
|
3045
|
-
function gatherDeepReferencesFromTree(startingNode, dataSource) {
|
|
3046
|
-
const deepReferences = [];
|
|
3047
|
-
treeVisit(startingNode, (node) => {
|
|
3048
|
-
if (!node.data.props)
|
|
3049
|
-
return;
|
|
3050
|
-
for (const [, variableMapping] of Object.entries(node.data.props)) {
|
|
3051
|
-
if (variableMapping.type !== 'BoundValue')
|
|
3052
|
-
continue;
|
|
3053
|
-
if (!isDeepPath(variableMapping.path))
|
|
3054
|
-
continue;
|
|
3055
|
-
deepReferences.push(DeepReference.from({
|
|
3056
|
-
path: variableMapping.path,
|
|
3057
|
-
dataSource,
|
|
3058
|
-
}));
|
|
3059
|
-
}
|
|
3060
|
-
});
|
|
3061
|
-
return deepReferences;
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
/**
|
|
3065
|
-
* Traverses deep-references and extracts referents from valid deep-paths.
|
|
3066
|
-
* The referents are received from the CDA/CPA response `.includes` field.
|
|
3067
|
-
*
|
|
3068
|
-
* In case deep-paths not resolving till the end, eg.:
|
|
3069
|
-
* - non-link referents: are ignored
|
|
3070
|
-
* - unset references: are ignored
|
|
3071
|
-
*
|
|
3072
|
-
* Errors are thrown in case of deep-paths being correct,
|
|
3073
|
-
* but referents not found. Because if we don't throw now, the EntityStore will
|
|
3074
|
-
* be missing entities and upon rendering will not be able to render bindings.
|
|
3075
|
-
*/
|
|
3076
|
-
function gatherAutoFetchedReferentsFromIncludes(deepReferences, entriesResponse) {
|
|
3077
|
-
const autoFetchedReferentEntries = [];
|
|
3078
|
-
const autoFetchedReferentAssets = [];
|
|
3079
|
-
for (const reference of deepReferences) {
|
|
3080
|
-
const headEntry = entriesResponse.items.find((entry) => entry.sys.id === reference.headEntityId);
|
|
3081
|
-
if (!headEntry) {
|
|
3082
|
-
console.debug(`[experiences-sdk-core::fetchers] When resolving deep-references could not find headEntry with id '${reference.entityId}'`);
|
|
3083
|
-
continue;
|
|
3084
|
-
}
|
|
3085
|
-
const linkToReferent = headEntry.fields[reference.field];
|
|
3086
|
-
if (undefined === linkToReferent) {
|
|
3087
|
-
console.debug(`[experiences-sdk-core::fetchers] Empty reference in headEntity. Probably reference is simply not set.`);
|
|
3088
|
-
continue;
|
|
3089
|
-
}
|
|
3090
|
-
if (!isLink(linkToReferent)) {
|
|
3091
|
-
console.debug(`[experiences-sdk-core::fetchers] Non-link value in headEntity. Probably broken path '${reference.originalPath}'`);
|
|
3092
|
-
continue;
|
|
3093
|
-
}
|
|
3094
|
-
const linkType = linkToReferent.sys.linkType;
|
|
3095
|
-
if (!['Entry', 'Asset'].includes(linkType)) {
|
|
3096
|
-
console.debug(`[experiences-sdk-core::fetchers] Unhandled linkType :${JSON.stringify(linkToReferent)}`);
|
|
3097
|
-
continue;
|
|
3098
|
-
}
|
|
3099
|
-
const referentEntity = entriesResponse.includes?.[linkType]?.find((entity) => entity.sys.id === linkToReferent.sys.id);
|
|
3100
|
-
if (!referentEntity) {
|
|
3101
|
-
console.debug(`[experiences-sdk-core::fetchers] L2-referent ${linkType} was not found within .includes (${JSON.stringify({
|
|
3102
|
-
linkToReferent,
|
|
3103
|
-
})})`);
|
|
3104
|
-
continue;
|
|
3105
|
-
}
|
|
3106
|
-
linkType === 'Entry'
|
|
3107
|
-
? autoFetchedReferentEntries.push(referentEntity)
|
|
3108
|
-
: autoFetchedReferentAssets.push(referentEntity);
|
|
3109
|
-
} // for (reference of deepReferences)
|
|
3110
|
-
return { autoFetchedReferentAssets, autoFetchedReferentEntries };
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
const MIN_FETCH_LIMIT = 1;
|
|
3114
|
-
const fetchAllEntries = async ({ client, ids, locale, skip = 0, limit = 100, responseItems = [], responseIncludes = { Entry: [], Asset: [] }, }) => {
|
|
3115
|
-
try {
|
|
3116
|
-
if (!client) {
|
|
3117
|
-
throw new Error('Failed to fetch experience entities. Required "client" parameter was not provided');
|
|
3118
|
-
}
|
|
3119
|
-
if (!ids.length) {
|
|
3120
|
-
return {
|
|
3121
|
-
items: [],
|
|
3122
|
-
includes: {
|
|
3123
|
-
Entry: [],
|
|
3124
|
-
Asset: [],
|
|
3125
|
-
},
|
|
3126
|
-
};
|
|
3127
|
-
}
|
|
3128
|
-
const query = { 'sys.id[in]': ids, locale, limit, skip };
|
|
3129
|
-
const { items, includes, total: responseTotal, } = await client.withoutLinkResolution.getEntries({ ...query });
|
|
3130
|
-
responseItems.push(...items);
|
|
3131
|
-
responseIncludes?.Entry?.push(...(includes?.Entry || []));
|
|
3132
|
-
responseIncludes?.Asset?.push(...(includes?.Asset || []));
|
|
3133
|
-
// E.g Total entries = 99
|
|
3134
|
-
// First fetch => { skip: 0, limit: 50, total: 99 } => 50 Entries fetched in Page 0
|
|
3135
|
-
// Total Entries fetched = 50, 49 remaining
|
|
3136
|
-
// 0(skip) + 50(limit) < 99(total) => Fetch again
|
|
3137
|
-
// Second fetch => { skip: 50, limit: 50, total: 99 } => 49 Entries fetched in Page 1
|
|
3138
|
-
// Total Entries fetched = 50(Page 0) + 49(Page 1) = 99, 0 remaining
|
|
3139
|
-
// 50(skip) + 50(limit) > 99(total) => Stop fetching
|
|
3140
|
-
if (skip + limit < responseTotal) {
|
|
3141
|
-
return await fetchAllEntries({
|
|
3142
|
-
client,
|
|
3143
|
-
ids,
|
|
3144
|
-
locale,
|
|
3145
|
-
skip: skip + limit,
|
|
3146
|
-
limit,
|
|
3147
|
-
responseItems,
|
|
3148
|
-
responseIncludes,
|
|
3149
|
-
});
|
|
3150
|
-
}
|
|
3151
|
-
const dedupedEntries = uniqBy(responseIncludes?.Entry, (entry) => entry.sys.id);
|
|
3152
|
-
const dedupedAssets = uniqBy(responseIncludes?.Asset, (asset) => asset.sys.id);
|
|
3153
|
-
return {
|
|
3154
|
-
items: responseItems,
|
|
3155
|
-
includes: {
|
|
3156
|
-
Entry: dedupedEntries,
|
|
3157
|
-
Asset: dedupedAssets,
|
|
3158
|
-
},
|
|
3159
|
-
};
|
|
3160
|
-
}
|
|
3161
|
-
catch (error) {
|
|
3162
|
-
if (error instanceof Error &&
|
|
3163
|
-
error.message.includes('size too big') &&
|
|
3164
|
-
limit > MIN_FETCH_LIMIT) {
|
|
3165
|
-
const newLimit = Math.max(MIN_FETCH_LIMIT, Math.floor(limit / 2));
|
|
3166
|
-
return fetchAllEntries({
|
|
3167
|
-
client,
|
|
3168
|
-
ids,
|
|
3169
|
-
locale,
|
|
3170
|
-
skip,
|
|
3171
|
-
limit: newLimit,
|
|
3172
|
-
responseItems,
|
|
3173
|
-
});
|
|
3174
|
-
}
|
|
3175
|
-
throw error;
|
|
3176
|
-
}
|
|
3177
|
-
};
|
|
3178
|
-
const fetchAllAssets = async ({ client, ids, locale, skip = 0, limit = 100, responseItems = [], }) => {
|
|
3179
|
-
try {
|
|
3180
|
-
if (!client) {
|
|
3181
|
-
throw new Error('Failed to fetch experience entities. Required "client" parameter was not provided');
|
|
3182
|
-
}
|
|
3183
|
-
if (!ids.length) {
|
|
3184
|
-
return { items: [] };
|
|
3185
|
-
}
|
|
3186
|
-
const query = { 'sys.id[in]': ids, locale, limit, skip };
|
|
3187
|
-
const { items, total: responseTotal } = await client.getAssets({ ...query });
|
|
3188
|
-
responseItems.push(...items);
|
|
3189
|
-
if (skip + limit < responseTotal) {
|
|
3190
|
-
return await fetchAllAssets({
|
|
3191
|
-
client,
|
|
3192
|
-
ids,
|
|
3193
|
-
locale,
|
|
3194
|
-
skip: skip + limit,
|
|
3195
|
-
limit,
|
|
3196
|
-
responseItems,
|
|
3197
|
-
});
|
|
3198
|
-
}
|
|
3199
|
-
return {
|
|
3200
|
-
items: responseItems,
|
|
3201
|
-
};
|
|
3202
|
-
}
|
|
3203
|
-
catch (error) {
|
|
3204
|
-
if (error instanceof Error &&
|
|
3205
|
-
error.message.includes('size too big') &&
|
|
3206
|
-
limit > MIN_FETCH_LIMIT) {
|
|
3207
|
-
const newLimit = Math.max(MIN_FETCH_LIMIT, Math.floor(limit / 2));
|
|
3208
|
-
return fetchAllAssets({
|
|
3209
|
-
client,
|
|
3210
|
-
ids,
|
|
3211
|
-
locale,
|
|
3212
|
-
skip,
|
|
3213
|
-
limit: newLimit,
|
|
3214
|
-
responseItems,
|
|
3215
|
-
});
|
|
3216
|
-
}
|
|
3217
|
-
throw error;
|
|
3218
|
-
}
|
|
3219
|
-
};
|
|
3220
|
-
|
|
3221
|
-
const fetchReferencedEntities = async ({ client, experienceEntry, locale, }) => {
|
|
3222
|
-
if (!client) {
|
|
3223
|
-
throw new Error('Failed to fetch experience entities. Required "client" parameter was not provided');
|
|
3224
|
-
}
|
|
3225
|
-
if (!locale) {
|
|
3226
|
-
throw new Error('Failed to fetch experience entities. Required "locale" parameter was not provided');
|
|
3227
|
-
}
|
|
3228
|
-
if (!isExperienceEntry(experienceEntry)) {
|
|
3229
|
-
throw new Error('Failed to fetch experience entities. Provided "experienceEntry" does not match experience entry schema');
|
|
3230
|
-
}
|
|
3231
|
-
const deepReferences = gatherDeepReferencesFromExperienceEntry(experienceEntry);
|
|
3232
|
-
const entryIds = [];
|
|
3233
|
-
const assetIds = [];
|
|
3234
|
-
for (const dataBinding of Object.values(experienceEntry.fields.dataSource)) {
|
|
3235
|
-
if (!('sys' in dataBinding)) {
|
|
3236
|
-
continue;
|
|
3237
|
-
}
|
|
3238
|
-
if (dataBinding.sys.linkType === 'Entry') {
|
|
3239
|
-
entryIds.push(dataBinding.sys.id);
|
|
3240
|
-
}
|
|
3241
|
-
if (dataBinding.sys.linkType === 'Asset') {
|
|
3242
|
-
assetIds.push(dataBinding.sys.id);
|
|
3243
|
-
}
|
|
3244
|
-
}
|
|
3245
|
-
const [entriesResponse, assetsResponse] = await Promise.all([
|
|
3246
|
-
fetchAllEntries({ client, ids: entryIds, locale }),
|
|
3247
|
-
fetchAllAssets({ client, ids: assetIds, locale }),
|
|
3248
|
-
]);
|
|
3249
|
-
const { autoFetchedReferentAssets, autoFetchedReferentEntries } = gatherAutoFetchedReferentsFromIncludes(deepReferences, entriesResponse);
|
|
3250
|
-
// Using client getEntries resolves all linked entry references, so we do not need to resolve entries in usedComponents
|
|
3251
|
-
const allResolvedEntries = [
|
|
3252
|
-
...(entriesResponse?.items ?? []),
|
|
3253
|
-
...(experienceEntry.fields.usedComponents || []),
|
|
3254
|
-
...autoFetchedReferentEntries,
|
|
3255
|
-
];
|
|
3256
|
-
const allResolvedAssets = [
|
|
3257
|
-
...(assetsResponse.items ?? []),
|
|
3258
|
-
...autoFetchedReferentAssets,
|
|
3259
|
-
];
|
|
3260
|
-
return {
|
|
3261
|
-
entries: allResolvedEntries,
|
|
3262
|
-
assets: allResolvedAssets,
|
|
3263
|
-
};
|
|
3264
|
-
};
|
|
3265
|
-
|
|
3266
|
-
const errorMessagesWhileFetching$1 = {
|
|
3267
|
-
experience: 'Failed to fetch experience',
|
|
3268
|
-
experienceReferences: 'Failed to fetch entities, referenced in experience',
|
|
3269
|
-
};
|
|
3270
|
-
const handleError$1 = (generalMessage, error) => {
|
|
3271
|
-
const message = error instanceof Error ? error.message : `Unknown error: ${error}`;
|
|
3272
|
-
throw Error(message);
|
|
3273
|
-
};
|
|
3274
|
-
/**
|
|
3275
|
-
* Fetch experience entry using slug as the identifier
|
|
3276
|
-
* @param {string} experienceTypeId - id of the content type associated with the experience
|
|
3277
|
-
* @param {string} slug - slug of the experience (defined in entry settings)
|
|
3278
|
-
* @param {string} localeCode - locale code to fetch the experience. Falls back to the currently active locale in the state
|
|
3279
|
-
*/
|
|
3280
|
-
// Promise<Experience<EntityStore> | undefined> =>
|
|
3281
|
-
async function fetchBySlug({ client, experienceTypeId, slug, localeCode, }) {
|
|
3282
|
-
let experienceEntry = undefined;
|
|
3283
|
-
try {
|
|
3284
|
-
experienceEntry = await fetchExperienceEntry({
|
|
3285
|
-
client,
|
|
3286
|
-
experienceTypeId,
|
|
3287
|
-
locale: localeCode,
|
|
3288
|
-
identifier: {
|
|
3289
|
-
slug,
|
|
3290
|
-
},
|
|
3291
|
-
});
|
|
3292
|
-
if (!experienceEntry) {
|
|
3293
|
-
throw new Error(`No experience entry with slug: ${slug} exists`);
|
|
3294
|
-
}
|
|
3295
|
-
try {
|
|
3296
|
-
const { entries, assets } = await fetchReferencedEntities({
|
|
3297
|
-
client,
|
|
3298
|
-
experienceEntry,
|
|
3299
|
-
locale: localeCode,
|
|
3300
|
-
});
|
|
3301
|
-
const experience = createExperience({
|
|
3302
|
-
experienceEntry,
|
|
3303
|
-
referencedAssets: assets,
|
|
3304
|
-
referencedEntries: entries,
|
|
3305
|
-
locale: localeCode,
|
|
3306
|
-
});
|
|
3307
|
-
return experience;
|
|
3308
|
-
}
|
|
3309
|
-
catch (error) {
|
|
3310
|
-
handleError$1(errorMessagesWhileFetching$1.experienceReferences, error);
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
catch (error) {
|
|
3314
|
-
handleError$1(errorMessagesWhileFetching$1.experience, error);
|
|
3315
|
-
}
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
|
-
const errorMessagesWhileFetching = {
|
|
3319
|
-
experience: 'Failed to fetch experience',
|
|
3320
|
-
experienceReferences: 'Failed to fetch entities, referenced in experience',
|
|
3321
|
-
};
|
|
3322
|
-
const handleError = (generalMessage, error) => {
|
|
3323
|
-
const message = error instanceof Error ? error.message : `Unknown error: ${error}`;
|
|
3324
|
-
throw Error(message);
|
|
3325
|
-
};
|
|
3326
|
-
/**
|
|
3327
|
-
* Fetch experience entry using slug as the identifier
|
|
3328
|
-
* @param {string} experienceTypeId - id of the content type associated with the experience
|
|
3329
|
-
* @param {string} slug - slug of the experience (defined in entry settings)
|
|
3330
|
-
* @param {string} localeCode - locale code to fetch the experience. Falls back to the currently active locale in the state
|
|
3331
|
-
*/
|
|
3332
|
-
async function fetchById({ client, experienceTypeId, id, localeCode, }) {
|
|
3333
|
-
let experienceEntry = undefined;
|
|
3334
|
-
try {
|
|
3335
|
-
experienceEntry = await fetchExperienceEntry({
|
|
3336
|
-
client,
|
|
3337
|
-
experienceTypeId,
|
|
3338
|
-
locale: localeCode,
|
|
3339
|
-
identifier: {
|
|
3340
|
-
id,
|
|
3341
|
-
},
|
|
3342
|
-
});
|
|
3343
|
-
if (!experienceEntry) {
|
|
3344
|
-
throw new Error(`No experience entry with id: ${id} exists`);
|
|
3345
|
-
}
|
|
3346
|
-
try {
|
|
3347
|
-
const { entries, assets } = await fetchReferencedEntities({
|
|
3348
|
-
client,
|
|
3349
|
-
experienceEntry,
|
|
3350
|
-
locale: localeCode,
|
|
3351
|
-
});
|
|
3352
|
-
const experience = createExperience({
|
|
3353
|
-
experienceEntry,
|
|
3354
|
-
referencedAssets: assets,
|
|
3355
|
-
referencedEntries: entries,
|
|
3356
|
-
locale: localeCode,
|
|
3357
|
-
});
|
|
3358
|
-
return experience;
|
|
3359
|
-
}
|
|
3360
|
-
catch (error) {
|
|
3361
|
-
handleError(errorMessagesWhileFetching.experienceReferences, error);
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
catch (error) {
|
|
3365
|
-
handleError(errorMessagesWhileFetching.experience, error);
|
|
3366
|
-
}
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
|
-
export { DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, columnsDefinition, containerBuiltInStyles, containerDefinition, createExperience, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, dividerBuiltInStyles, dividerDefinition, doesMismatchMessageSchema, fetchById, fetchBySlug, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTemplateValue, getValueForBreakpoint, indexByBreakpoint, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isStructureWithRelativeHeight, lastPathNamedSegmentEq, maybePopulateDesignTokenValue, mediaQueryMatcher, optionalBuiltInStyles, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sectionBuiltInStyles, sectionDefinition, sendMessage, singleColumnBuiltInStyles, singleColumnDefinition, supportedModes, toCSSAttribute, toCSSString, toMediaQuery, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
|
|
3370
|
-
//# sourceMappingURL=index.js.map
|