@contentful/experiences-core 1.29.0-dev-20250123T1244-eb6dcac.0 → 1.30.0-beta.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/dist/entity/EntityStore.d.ts +5 -2
- package/dist/index.js +125 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -10,13 +10,15 @@ type EntityStoreArgs = {
|
|
|
10
10
|
locale: string;
|
|
11
11
|
};
|
|
12
12
|
declare class EntityStore extends EntityStoreBase {
|
|
13
|
-
private
|
|
13
|
+
private _experienceEntryFields;
|
|
14
|
+
private _experienceEntryId;
|
|
14
15
|
private _unboundValues;
|
|
15
16
|
private _usedComponentsWithDeepReferences;
|
|
16
17
|
constructor(json: string);
|
|
17
18
|
constructor({ experienceEntry, entities, locale }: EntityStoreArgs);
|
|
18
19
|
getCurrentLocale(): string;
|
|
19
20
|
get experienceEntryFields(): ExperienceFields | undefined;
|
|
21
|
+
get experienceEntryId(): string | undefined;
|
|
20
22
|
get schemaVersion(): "2023-09-28" | undefined;
|
|
21
23
|
get breakpoints(): {
|
|
22
24
|
id: string;
|
|
@@ -54,7 +56,8 @@ declare class EntityStore extends EntityStoreBase {
|
|
|
54
56
|
[k: string]: Asset<contentful.ChainModifiers, string>;
|
|
55
57
|
};
|
|
56
58
|
locale: string;
|
|
57
|
-
|
|
59
|
+
_experienceEntryFields: ExperienceFields | undefined;
|
|
60
|
+
_experienceEntryId: string | undefined;
|
|
58
61
|
_unboundValues: Record<string, {
|
|
59
62
|
value?: string | number | boolean | Record<any, any> | undefined;
|
|
60
63
|
}> | undefined;
|
package/dist/index.js
CHANGED
|
@@ -570,6 +570,7 @@ const builtInStyles = {
|
|
|
570
570
|
defaultValue: '',
|
|
571
571
|
validations: {
|
|
572
572
|
format: 'URL',
|
|
573
|
+
bindingSourceType: ['entry', 'experience', 'manual'],
|
|
573
574
|
},
|
|
574
575
|
description: 'hyperlink for section or container',
|
|
575
576
|
},
|
|
@@ -615,6 +616,9 @@ const optionalBuiltInStyles = {
|
|
|
615
616
|
displayName: 'Image',
|
|
616
617
|
type: 'Media',
|
|
617
618
|
description: 'Image to display',
|
|
619
|
+
validations: {
|
|
620
|
+
bindingSourceType: ['entry', 'asset', 'manual'],
|
|
621
|
+
},
|
|
618
622
|
},
|
|
619
623
|
cfImageOptions: {
|
|
620
624
|
displayName: 'Image options',
|
|
@@ -637,6 +641,9 @@ const optionalBuiltInStyles = {
|
|
|
637
641
|
displayName: 'Background image',
|
|
638
642
|
type: 'Media',
|
|
639
643
|
description: 'Background image for component',
|
|
644
|
+
validations: {
|
|
645
|
+
bindingSourceType: ['entry', 'asset', 'manual'],
|
|
646
|
+
},
|
|
640
647
|
},
|
|
641
648
|
cfBackgroundImageOptions: {
|
|
642
649
|
displayName: 'Background image options',
|
|
@@ -1556,7 +1563,7 @@ const detachExperienceStyles = (experience) => {
|
|
|
1556
1563
|
}, {});
|
|
1557
1564
|
// getting the breakpoint ids
|
|
1558
1565
|
const breakpointIds = Object.keys(mediaQueriesTemplate);
|
|
1559
|
-
const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, patternWrapper, }) => {
|
|
1566
|
+
const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, patternWrapper, wrappingPatternIds, }) => {
|
|
1560
1567
|
// traversing the tree
|
|
1561
1568
|
const queue = [];
|
|
1562
1569
|
queue.push(...componentTree.children);
|
|
@@ -1573,6 +1580,10 @@ const detachExperienceStyles = (experience) => {
|
|
|
1573
1580
|
usedComponents,
|
|
1574
1581
|
});
|
|
1575
1582
|
if (isPatternNode) {
|
|
1583
|
+
// When detecting a circular dependency among patterns, stop to avoid an infinite loop
|
|
1584
|
+
if (wrappingPatternIds.has(currentNode.definitionId)) {
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1576
1587
|
const patternEntry = usedComponents.find((component) => component.sys.id === currentNode.definitionId);
|
|
1577
1588
|
if (!patternEntry || !('fields' in patternEntry)) {
|
|
1578
1589
|
continue;
|
|
@@ -1613,6 +1624,7 @@ const detachExperienceStyles = (experience) => {
|
|
|
1613
1624
|
componentVariablesOverwrites: currentNode.variables,
|
|
1614
1625
|
// pass top-level pattern node to store instance-specific child styles for rendering
|
|
1615
1626
|
patternWrapper: currentNode,
|
|
1627
|
+
wrappingPatternIds: new Set([...wrappingPatternIds, currentNode.definitionId]),
|
|
1616
1628
|
});
|
|
1617
1629
|
continue;
|
|
1618
1630
|
}
|
|
@@ -1769,6 +1781,7 @@ const detachExperienceStyles = (experience) => {
|
|
|
1769
1781
|
dataSource: experience.entityStore?.dataSource ?? {},
|
|
1770
1782
|
unboundValues: experience.entityStore?.unboundValues ?? {},
|
|
1771
1783
|
componentSettings: experience.entityStore?.experienceEntryFields?.componentSettings,
|
|
1784
|
+
wrappingPatternIds: new Set(experience.entityStore?.experienceEntryId ? [experience.entityStore.experienceEntryId] : []),
|
|
1772
1785
|
});
|
|
1773
1786
|
// once the whole tree was traversed, for each breakpoint, I aggregate the styles
|
|
1774
1787
|
// for each generated className into one css string
|
|
@@ -3131,64 +3144,76 @@ class EditorModeEntityStore extends EditorEntityStore {
|
|
|
3131
3144
|
}
|
|
3132
3145
|
}
|
|
3133
3146
|
|
|
3134
|
-
const
|
|
3135
|
-
const
|
|
3147
|
+
const resolveDeepUsedComponents = ({ experienceEntryFields, parentComponents, }) => {
|
|
3148
|
+
const totalUsedComponents = [];
|
|
3136
3149
|
const usedComponents = experienceEntryFields?.usedComponents;
|
|
3137
3150
|
if (!usedComponents || usedComponents.length === 0) {
|
|
3138
3151
|
return [];
|
|
3139
3152
|
}
|
|
3140
3153
|
for (const component of usedComponents) {
|
|
3141
3154
|
if ('fields' in component) {
|
|
3142
|
-
|
|
3143
|
-
|
|
3155
|
+
totalUsedComponents.push(component);
|
|
3156
|
+
if (parentComponents.has(component.sys.id)) {
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3159
|
+
totalUsedComponents.push(...resolveDeepUsedComponents({
|
|
3160
|
+
experienceEntryFields: component.fields,
|
|
3161
|
+
parentComponents: new Set([...parentComponents, component.sys.id]),
|
|
3162
|
+
}));
|
|
3144
3163
|
}
|
|
3145
3164
|
}
|
|
3146
|
-
return
|
|
3165
|
+
return totalUsedComponents;
|
|
3147
3166
|
};
|
|
3148
3167
|
|
|
3149
3168
|
class EntityStore extends EntityStoreBase {
|
|
3150
3169
|
constructor(options) {
|
|
3151
3170
|
if (typeof options === 'string') {
|
|
3152
|
-
|
|
3153
|
-
|
|
3171
|
+
// For SSR/SSG, the entity store is created server-side and passed to the client as a serialised JSON.
|
|
3172
|
+
// So the properties in data.entityStore are equal to the attributes of this class (see `toJSON()`)
|
|
3173
|
+
const serializedAttributes = JSON.parse(options).entityStore;
|
|
3154
3174
|
super({
|
|
3155
3175
|
entities: [
|
|
3156
|
-
...Object.values(entryMap),
|
|
3157
|
-
...Object.values(assetMap),
|
|
3176
|
+
...Object.values(serializedAttributes.entryMap),
|
|
3177
|
+
...Object.values(serializedAttributes.assetMap),
|
|
3158
3178
|
],
|
|
3159
|
-
locale,
|
|
3179
|
+
locale: serializedAttributes.locale,
|
|
3160
3180
|
});
|
|
3161
|
-
this.
|
|
3162
|
-
this.
|
|
3163
|
-
this.
|
|
3181
|
+
this._experienceEntryFields = serializedAttributes._experienceEntryFields;
|
|
3182
|
+
this._experienceEntryId = serializedAttributes._experienceEntryId;
|
|
3183
|
+
this._unboundValues = serializedAttributes._unboundValues;
|
|
3164
3184
|
}
|
|
3165
3185
|
else {
|
|
3166
3186
|
const { experienceEntry, entities, locale } = options;
|
|
3167
|
-
|
|
3168
|
-
if (isExperienceEntry(experienceEntry)) {
|
|
3169
|
-
this._experienceEntry = experienceEntry.fields;
|
|
3170
|
-
this._unboundValues = experienceEntry.fields.unboundValues;
|
|
3171
|
-
this._usedComponentsWithDeepReferences = gatherUsedComponentsWithDeepRefernces(this._experienceEntry);
|
|
3172
|
-
}
|
|
3173
|
-
else {
|
|
3187
|
+
if (!isExperienceEntry(experienceEntry)) {
|
|
3174
3188
|
throw new Error('Provided entry is not experience entry');
|
|
3175
3189
|
}
|
|
3190
|
+
super({ entities, locale });
|
|
3191
|
+
this._experienceEntryFields = experienceEntry.fields;
|
|
3192
|
+
this._experienceEntryId = experienceEntry.sys.id;
|
|
3193
|
+
this._unboundValues = experienceEntry.fields.unboundValues;
|
|
3176
3194
|
}
|
|
3195
|
+
this._usedComponentsWithDeepReferences = resolveDeepUsedComponents({
|
|
3196
|
+
experienceEntryFields: this._experienceEntryFields,
|
|
3197
|
+
parentComponents: new Set([this._experienceEntryId]),
|
|
3198
|
+
});
|
|
3177
3199
|
}
|
|
3178
3200
|
getCurrentLocale() {
|
|
3179
3201
|
return this.locale;
|
|
3180
3202
|
}
|
|
3181
3203
|
get experienceEntryFields() {
|
|
3182
|
-
return this.
|
|
3204
|
+
return this._experienceEntryFields;
|
|
3205
|
+
}
|
|
3206
|
+
get experienceEntryId() {
|
|
3207
|
+
return this._experienceEntryId;
|
|
3183
3208
|
}
|
|
3184
3209
|
get schemaVersion() {
|
|
3185
|
-
return this.
|
|
3210
|
+
return this._experienceEntryFields?.componentTree.schemaVersion;
|
|
3186
3211
|
}
|
|
3187
3212
|
get breakpoints() {
|
|
3188
|
-
return this.
|
|
3213
|
+
return this._experienceEntryFields?.componentTree.breakpoints ?? [];
|
|
3189
3214
|
}
|
|
3190
3215
|
get dataSource() {
|
|
3191
|
-
return this.
|
|
3216
|
+
return this._experienceEntryFields?.dataSource ?? {};
|
|
3192
3217
|
}
|
|
3193
3218
|
get unboundValues() {
|
|
3194
3219
|
return this._unboundValues ?? {};
|
|
@@ -3219,7 +3244,8 @@ class EntityStore extends EntityStoreBase {
|
|
|
3219
3244
|
}
|
|
3220
3245
|
toJSON() {
|
|
3221
3246
|
return {
|
|
3222
|
-
|
|
3247
|
+
_experienceEntryFields: this._experienceEntryFields,
|
|
3248
|
+
_experienceEntryId: this._experienceEntryId,
|
|
3223
3249
|
_unboundValues: this._unboundValues,
|
|
3224
3250
|
...super.toJSON(),
|
|
3225
3251
|
};
|
|
@@ -3544,22 +3570,22 @@ const fetchReferencedEntities = async ({ client, experienceEntry, locale, }) =>
|
|
|
3544
3570
|
throw new Error('Failed to fetch experience entities. Provided "experienceEntry" does not match experience entry schema');
|
|
3545
3571
|
}
|
|
3546
3572
|
const deepReferences = gatherDeepReferencesFromExperienceEntry(experienceEntry);
|
|
3547
|
-
const entryIds =
|
|
3548
|
-
const assetIds =
|
|
3573
|
+
const entryIds = new Set();
|
|
3574
|
+
const assetIds = new Set();
|
|
3549
3575
|
for (const dataBinding of Object.values(experienceEntry.fields.dataSource)) {
|
|
3550
3576
|
if (!('sys' in dataBinding)) {
|
|
3551
3577
|
continue;
|
|
3552
3578
|
}
|
|
3553
3579
|
if (dataBinding.sys.linkType === 'Entry') {
|
|
3554
|
-
entryIds.
|
|
3580
|
+
entryIds.add(dataBinding.sys.id);
|
|
3555
3581
|
}
|
|
3556
3582
|
if (dataBinding.sys.linkType === 'Asset') {
|
|
3557
|
-
assetIds.
|
|
3583
|
+
assetIds.add(dataBinding.sys.id);
|
|
3558
3584
|
}
|
|
3559
3585
|
}
|
|
3560
3586
|
const [entriesResponse, assetsResponse] = await Promise.all([
|
|
3561
|
-
fetchAllEntries({ client, ids: entryIds, locale }),
|
|
3562
|
-
fetchAllAssets({ client, ids: assetIds, locale }),
|
|
3587
|
+
fetchAllEntries({ client, ids: [...entryIds], locale }),
|
|
3588
|
+
fetchAllAssets({ client, ids: [...assetIds], locale }),
|
|
3563
3589
|
]);
|
|
3564
3590
|
const { autoFetchedReferentAssets, autoFetchedReferentEntries } = gatherAutoFetchedReferentsFromIncludes(deepReferences, entriesResponse);
|
|
3565
3591
|
// Using client getEntries resolves all linked entry references, so we do not need to resolve entries in usedComponents
|
|
@@ -3580,6 +3606,68 @@ const fetchReferencedEntities = async ({ client, experienceEntry, locale, }) =>
|
|
|
3580
3606
|
};
|
|
3581
3607
|
};
|
|
3582
3608
|
|
|
3609
|
+
/**
|
|
3610
|
+
* The CMA client will automatically replace links with entry references.
|
|
3611
|
+
* As we're including all referenced pattern entries in usedComponents, this can lead
|
|
3612
|
+
* to a circular reference. This function replaces those with plain links inplace (!).
|
|
3613
|
+
*
|
|
3614
|
+
* @see https://github.com/contentful/contentful.js/issues/377
|
|
3615
|
+
*/
|
|
3616
|
+
const removeCircularPatternReferences = (experienceEntry, _parentIds) => {
|
|
3617
|
+
const parentIds = _parentIds ?? new Set([experienceEntry.sys.id]);
|
|
3618
|
+
const usedComponents = experienceEntry.fields.usedComponents;
|
|
3619
|
+
const newUsedComponents = usedComponents?.reduce((acc, linkOrEntry) => {
|
|
3620
|
+
if (!('fields' in linkOrEntry)) {
|
|
3621
|
+
// It is a link, we're good
|
|
3622
|
+
return [...acc, linkOrEntry];
|
|
3623
|
+
}
|
|
3624
|
+
const entry = linkOrEntry;
|
|
3625
|
+
if (parentIds.has(entry.sys.id)) {
|
|
3626
|
+
// It is an entry that already occurred -> turn it into a link to remove the circularity
|
|
3627
|
+
const link = {
|
|
3628
|
+
sys: {
|
|
3629
|
+
id: entry.sys.id,
|
|
3630
|
+
linkType: 'Entry',
|
|
3631
|
+
type: 'Link',
|
|
3632
|
+
},
|
|
3633
|
+
};
|
|
3634
|
+
return [...acc, link];
|
|
3635
|
+
}
|
|
3636
|
+
// Remove circularity for its usedComponents as well (inplace)
|
|
3637
|
+
removeCircularPatternReferences(entry, new Set([...parentIds, entry.sys.id]));
|
|
3638
|
+
return [...acc, entry];
|
|
3639
|
+
}, []);
|
|
3640
|
+
// @ts-expect-error - type of usedComponents doesn't yet allow a mixed list of both links and entries
|
|
3641
|
+
experienceEntry.fields.usedComponents = newUsedComponents;
|
|
3642
|
+
};
|
|
3643
|
+
/**
|
|
3644
|
+
* The CMA client will automatically replace links with entry references if they are available.
|
|
3645
|
+
* While we're not fetching the data sources, a self reference would be replaced as the entry is
|
|
3646
|
+
* fetched. Any circuar reference in the object breaks SSR where we have to stringify the JSON.
|
|
3647
|
+
* This would fail if the object contains circular references.
|
|
3648
|
+
*/
|
|
3649
|
+
const removeSelfReferencingDataSource = (experienceEntry) => {
|
|
3650
|
+
const dataSources = experienceEntry.fields.dataSource;
|
|
3651
|
+
const newDataSource = Object.entries(dataSources).reduce((acc, [key, linkOrEntry]) => {
|
|
3652
|
+
if ('fields' in linkOrEntry && linkOrEntry.sys.id === experienceEntry.sys.id) {
|
|
3653
|
+
const entry = linkOrEntry;
|
|
3654
|
+
acc[key] = {
|
|
3655
|
+
sys: {
|
|
3656
|
+
id: entry.sys.id,
|
|
3657
|
+
linkType: 'Entry',
|
|
3658
|
+
type: 'Link',
|
|
3659
|
+
},
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
else {
|
|
3663
|
+
const link = linkOrEntry;
|
|
3664
|
+
acc[key] = link;
|
|
3665
|
+
}
|
|
3666
|
+
return acc;
|
|
3667
|
+
}, {});
|
|
3668
|
+
experienceEntry.fields.dataSource = newDataSource;
|
|
3669
|
+
};
|
|
3670
|
+
|
|
3583
3671
|
const errorMessagesWhileFetching$1 = {
|
|
3584
3672
|
experience: 'Failed to fetch experience',
|
|
3585
3673
|
experienceReferences: 'Failed to fetch entities, referenced in experience',
|
|
@@ -3593,7 +3681,7 @@ const handleError$1 = (generalMessage, error) => {
|
|
|
3593
3681
|
* @param {FetchBySlugParams} options - options to fetch the experience
|
|
3594
3682
|
*/
|
|
3595
3683
|
async function fetchBySlug({ client, experienceTypeId, slug, localeCode, isEditorMode, }) {
|
|
3596
|
-
//Be a no-op if in editor mode
|
|
3684
|
+
// Be a no-op if in editor mode
|
|
3597
3685
|
if (isEditorMode)
|
|
3598
3686
|
return;
|
|
3599
3687
|
let experienceEntry = undefined;
|
|
@@ -3609,6 +3697,8 @@ async function fetchBySlug({ client, experienceTypeId, slug, localeCode, isEdito
|
|
|
3609
3697
|
if (!experienceEntry) {
|
|
3610
3698
|
throw new Error(`No experience entry with slug: ${slug} exists`);
|
|
3611
3699
|
}
|
|
3700
|
+
removeCircularPatternReferences(experienceEntry);
|
|
3701
|
+
removeSelfReferencingDataSource(experienceEntry);
|
|
3612
3702
|
try {
|
|
3613
3703
|
const { entries, assets } = await fetchReferencedEntities({
|
|
3614
3704
|
client,
|
|
@@ -3661,6 +3751,8 @@ async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMod
|
|
|
3661
3751
|
if (!experienceEntry) {
|
|
3662
3752
|
throw new Error(`No experience entry with id: ${id} exists`);
|
|
3663
3753
|
}
|
|
3754
|
+
removeCircularPatternReferences(experienceEntry);
|
|
3755
|
+
removeSelfReferencingDataSource(experienceEntry);
|
|
3664
3756
|
try {
|
|
3665
3757
|
const { entries, assets } = await fetchReferencedEntities({
|
|
3666
3758
|
client,
|