@kontent-ai/delivery-sdk 16.4.3 → 16.4.4
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/.npmignore +15 -15
- package/LICENSE.md +9 -9
- package/dist/bundles/kontent-delivery.umd.js +5077 -4620
- package/dist/bundles/kontent-delivery.umd.js.map +1 -1
- package/dist/bundles/kontent-delivery.umd.min.js +1 -1
- package/dist/bundles/kontent-delivery.umd.min.js.map +1 -1
- package/dist/bundles/report.json +1 -1
- package/dist/bundles/report.min.json +1 -1
- package/dist/bundles/stats.json +7195 -5946
- package/dist/bundles/stats.min.json +11923 -10797
- package/dist/cjs/sdk-info.generated.js +1 -1
- package/dist/es6/sdk-info.generated.js +1 -1
- package/dist/esnext/sdk-info.generated.js +1 -1
- package/lib/client/delivery-client.factory.ts +9 -9
- package/lib/client/delivery-client.ts +163 -163
- package/lib/client/idelivery-client.interface.ts +110 -110
- package/lib/client/index.ts +3 -3
- package/lib/config/delivery-configs.ts +98 -98
- package/lib/config/index.ts +1 -1
- package/lib/contracts/contracts.ts +265 -265
- package/lib/contracts/index.ts +1 -1
- package/lib/elements/element-models.ts +89 -89
- package/lib/elements/element-resolver.ts +3 -3
- package/lib/elements/element-type.ts +16 -16
- package/lib/elements/elements.ts +70 -70
- package/lib/elements/index.ts +4 -4
- package/lib/images/image-url-transformation-builder.factory.ts +5 -5
- package/lib/images/image-url-transformation-builder.ts +181 -181
- package/lib/images/image.models.ts +4 -4
- package/lib/images/index.ts +3 -3
- package/lib/index.ts +12 -12
- package/lib/mappers/element.mapper.ts +504 -504
- package/lib/mappers/generic-element.mapper.ts +20 -20
- package/lib/mappers/index.ts +8 -8
- package/lib/mappers/item.mapper.ts +181 -181
- package/lib/mappers/language.mapper.ts +24 -24
- package/lib/mappers/sync.mapper.ts +32 -32
- package/lib/mappers/taxonomy.mapper.ts +77 -77
- package/lib/mappers/type.mapper.ts +76 -76
- package/lib/mappers/used-in.mapper.ts +20 -20
- package/lib/models/common/base-responses.ts +13 -13
- package/lib/models/common/common-models.ts +118 -118
- package/lib/models/common/filters.ts +280 -280
- package/lib/models/common/headers.ts +5 -5
- package/lib/models/common/index.ts +7 -7
- package/lib/models/common/pagination.class.ts +7 -7
- package/lib/models/common/parameters.ts +189 -189
- package/lib/models/common/sort-order.ts +1 -1
- package/lib/models/content-type-models.ts +42 -42
- package/lib/models/element-models.ts +51 -51
- package/lib/models/index.ts +9 -9
- package/lib/models/item-models.ts +192 -192
- package/lib/models/language-models.ts +17 -17
- package/lib/models/responses.ts +137 -137
- package/lib/models/sync-models.ts +18 -18
- package/lib/models/taxonomy-models.ts +25 -25
- package/lib/query/common/base-item-listing-query.class.ts +274 -274
- package/lib/query/common/base-listing-query.class.ts +95 -95
- package/lib/query/common/base-query.class.ts +148 -148
- package/lib/query/element/element-query.class.ts +45 -45
- package/lib/query/index.ts +13 -13
- package/lib/query/item/multiple-items-query.class.ts +183 -183
- package/lib/query/item/single-item-query.class.ts +106 -106
- package/lib/query/items-feed/items-feed-query.class.ts +191 -191
- package/lib/query/language/languages-query.class.ts +91 -91
- package/lib/query/sync/initialize-sync-query.class.ts +83 -83
- package/lib/query/sync/sync-changes-query.class.ts +56 -56
- package/lib/query/taxonomy/taxonomies-query.class.ts +82 -82
- package/lib/query/taxonomy/taxonomy-query.class.ts +50 -50
- package/lib/query/type/multiple-type-query.class.ts +95 -95
- package/lib/query/type/single-type-query.class.ts +45 -45
- package/lib/query/used-in/used-in-query.class.ts +126 -126
- package/lib/sdk-info.generated.ts +1 -1
- package/lib/services/base-delivery-query.service.ts +375 -375
- package/lib/services/delivery-query.service.ts +336 -336
- package/lib/services/index.ts +2 -2
- package/lib/services/mapping.service.ts +223 -223
- package/lib/utilities/codename.helper.ts +10 -10
- package/lib/utilities/delivery-url.helper.ts +11 -11
- package/lib/utilities/enum.helper.ts +38 -38
- package/lib/utilities/index.ts +3 -3
- package/package.json +96 -96
- package/readme.md +531 -531
|
@@ -1,504 +1,504 @@
|
|
|
1
|
-
import { codenameHelper, deliveryUrlHelper, enumHelper } from '../utilities';
|
|
2
|
-
|
|
3
|
-
import { IDeliveryClientConfig } from '../config';
|
|
4
|
-
import { Contracts } from '../contracts';
|
|
5
|
-
import { ElementModels, Elements, ElementType } from '../elements';
|
|
6
|
-
import {
|
|
7
|
-
IContentItem,
|
|
8
|
-
IContentItemsContainer,
|
|
9
|
-
IContentItemWithRawDataContainer,
|
|
10
|
-
IContentItemWithRawElements,
|
|
11
|
-
ILink,
|
|
12
|
-
IMapElementsResult,
|
|
13
|
-
IRichTextImage
|
|
14
|
-
} from '../models';
|
|
15
|
-
|
|
16
|
-
interface IRichTextImageUrlRecord {
|
|
17
|
-
originalUrl: string;
|
|
18
|
-
newUrl: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class ElementMapper<TContentItemType extends IContentItem> {
|
|
22
|
-
constructor(private readonly config: IDeliveryClientConfig) {}
|
|
23
|
-
|
|
24
|
-
mapElements<TContentItem extends TContentItemType = TContentItemType>(data: {
|
|
25
|
-
dataToMap: IContentItemWithRawElements;
|
|
26
|
-
processedItems: IContentItemsContainer<TContentItem>;
|
|
27
|
-
processingStartedForCodenames: string[];
|
|
28
|
-
preparedItems: IContentItemWithRawDataContainer;
|
|
29
|
-
}): IMapElementsResult<TContentItem, TContentItemType> | undefined {
|
|
30
|
-
// return processed item to avoid infinite recursion
|
|
31
|
-
const processedItem =
|
|
32
|
-
data.processedItems[codenameHelper.escapeCodenameInCodenameIndexer(data.dataToMap.item.system.codename)];
|
|
33
|
-
if (processedItem) {
|
|
34
|
-
// item was already resolved
|
|
35
|
-
return {
|
|
36
|
-
item: processedItem,
|
|
37
|
-
processedItems: data.processedItems,
|
|
38
|
-
preparedItems: data.preparedItems,
|
|
39
|
-
processingStartedForCodenames: data.processingStartedForCodenames
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const preparedItem =
|
|
44
|
-
data.preparedItems[codenameHelper.escapeCodenameInCodenameIndexer(data.dataToMap.item.system.codename)];
|
|
45
|
-
|
|
46
|
-
const itemInstance = preparedItem?.item;
|
|
47
|
-
|
|
48
|
-
if (!itemInstance) {
|
|
49
|
-
// item is not present in response
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// mapp elements
|
|
54
|
-
const elementCodenames = Object.getOwnPropertyNames(data.dataToMap.rawItem.elements);
|
|
55
|
-
|
|
56
|
-
for (const elementCodename of elementCodenames) {
|
|
57
|
-
const elementWrapper: ElementModels.IElementWrapper = {
|
|
58
|
-
system: data.dataToMap.item.system,
|
|
59
|
-
rawElement: data.dataToMap.rawItem.elements[elementCodename],
|
|
60
|
-
element: elementCodename
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const mappedElement = this.mapElement({
|
|
64
|
-
elementWrapper: elementWrapper,
|
|
65
|
-
item: itemInstance,
|
|
66
|
-
preparedItems: data.preparedItems,
|
|
67
|
-
processingStartedForCodenames: data.processingStartedForCodenames,
|
|
68
|
-
processedItems: data.processedItems
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// set mapped elements
|
|
72
|
-
itemInstance.elements[elementCodename] = mappedElement;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
item: itemInstance as TContentItem,
|
|
77
|
-
processedItems: data.processedItems,
|
|
78
|
-
preparedItems: data.preparedItems,
|
|
79
|
-
processingStartedForCodenames: data.processingStartedForCodenames
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private mapElement(data: {
|
|
84
|
-
elementWrapper: ElementModels.IElementWrapper;
|
|
85
|
-
item: IContentItem;
|
|
86
|
-
processedItems: IContentItemsContainer<TContentItemType>;
|
|
87
|
-
processingStartedForCodenames: string[];
|
|
88
|
-
preparedItems: IContentItemWithRawDataContainer;
|
|
89
|
-
}): ElementModels.IElement<any> {
|
|
90
|
-
const elementType = enumHelper.getEnumFromValue<ElementType>(ElementType, data.elementWrapper.rawElement.type);
|
|
91
|
-
if (elementType) {
|
|
92
|
-
if (elementType === ElementType.ModularContent) {
|
|
93
|
-
return this.mapLinkedItemsElement({
|
|
94
|
-
elementWrapper: data.elementWrapper,
|
|
95
|
-
preparedItems: data.preparedItems,
|
|
96
|
-
processingStartedForCodenames: data.processingStartedForCodenames,
|
|
97
|
-
processedItems: data.processedItems
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (elementType === ElementType.Text) {
|
|
102
|
-
return this.mapTextElement(data.elementWrapper);
|
|
103
|
-
}
|
|
104
|
-
if (elementType === ElementType.Asset) {
|
|
105
|
-
return this.mapAssetsElement(data.elementWrapper);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (elementType === ElementType.Number) {
|
|
109
|
-
return this.mapNumberElement(data.elementWrapper);
|
|
110
|
-
}
|
|
111
|
-
if (elementType === ElementType.MultipleChoice) {
|
|
112
|
-
return this.mapMultipleChoiceElement(data.elementWrapper);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (elementType === ElementType.DateTime) {
|
|
116
|
-
return this.mapDateTimeElement(data.elementWrapper);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (elementType === ElementType.RichText) {
|
|
120
|
-
// add to parent items
|
|
121
|
-
return this.mapRichTextElement(
|
|
122
|
-
data.elementWrapper,
|
|
123
|
-
data.processedItems,
|
|
124
|
-
data.processingStartedForCodenames,
|
|
125
|
-
data.preparedItems
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (elementType === ElementType.UrlSlug) {
|
|
130
|
-
return this.mapUrlSlugElement(data.elementWrapper);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (elementType === ElementType.Taxonomy) {
|
|
134
|
-
return this.mapTaxonomyElement(data.elementWrapper);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (elementType === ElementType.Custom) {
|
|
138
|
-
return this.mapCustomElement(data.elementWrapper);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
console.warn(
|
|
142
|
-
`Could not map element '${data.elementWrapper.rawElement.name}' of type '${data.elementWrapper.rawElement.type}'. Returning unknown element instead.`
|
|
143
|
-
);
|
|
144
|
-
return this.mapUnknowElement(data.elementWrapper);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private mapRichTextElement(
|
|
148
|
-
elementWrapper: ElementModels.IElementWrapper,
|
|
149
|
-
processedItems: IContentItemsContainer<TContentItemType>,
|
|
150
|
-
processingStartedForCodenames: string[],
|
|
151
|
-
preparedItems: IContentItemWithRawDataContainer
|
|
152
|
-
): Elements.RichTextElement {
|
|
153
|
-
const rawElement = elementWrapper.rawElement as Contracts.IRichTextElementContract;
|
|
154
|
-
|
|
155
|
-
// get all linked items and linked items codenames nested in rich text
|
|
156
|
-
const richTextLinkedItems: IContentItem[] = [];
|
|
157
|
-
const richTextLinkedItemsCodenames: string[] = [];
|
|
158
|
-
|
|
159
|
-
// The Kontent Delivery API is not guaranteed to return rich-text modular_content array items in the same order in which they appear inside the `value` property.
|
|
160
|
-
// We extract the modular_content codenames in the rich-text value and sort the raw modular_content based on that order instead.
|
|
161
|
-
const rawModularContentCodenamesMatches = (rawElement.value as string).matchAll(
|
|
162
|
-
/<object[^>]+data-codename="(?<codename>[a-z0-9_]*)".*?>/g
|
|
163
|
-
);
|
|
164
|
-
const rawModularContentCodenamesSorted = Array.from(rawModularContentCodenamesMatches).reduce<string[]>(
|
|
165
|
-
(acc, match) => {
|
|
166
|
-
if (match.groups && match.groups.codename) {
|
|
167
|
-
acc.push(match.groups.codename);
|
|
168
|
-
}
|
|
169
|
-
return acc;
|
|
170
|
-
},
|
|
171
|
-
[] as string[]
|
|
172
|
-
);
|
|
173
|
-
const rawModularContentCodenames = [...rawElement.modular_content].sort(function (a, b) {
|
|
174
|
-
return rawModularContentCodenamesSorted.indexOf(a) - rawModularContentCodenamesSorted.indexOf(b);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
for (const codename of rawModularContentCodenames) {
|
|
178
|
-
richTextLinkedItemsCodenames.push(codename);
|
|
179
|
-
// get linked item and check if it exists (it might not be included in response due to 'Depth' parameter)
|
|
180
|
-
const preparedData = preparedItems[codename];
|
|
181
|
-
|
|
182
|
-
// first try to get existing item
|
|
183
|
-
if (this.canMapLinkedItems()) {
|
|
184
|
-
const existingLinkedItem = this.getOrSaveLinkedItemForElement(
|
|
185
|
-
codename,
|
|
186
|
-
rawElement,
|
|
187
|
-
processedItems,
|
|
188
|
-
processingStartedForCodenames,
|
|
189
|
-
preparedItems
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
if (existingLinkedItem) {
|
|
193
|
-
// item was found, add it to linked items
|
|
194
|
-
richTextLinkedItems.push(existingLinkedItem);
|
|
195
|
-
} else {
|
|
196
|
-
// item was not found or not yet resolved
|
|
197
|
-
if (preparedData) {
|
|
198
|
-
const mappedLinkedItemResult = this.mapElements({
|
|
199
|
-
dataToMap: preparedData,
|
|
200
|
-
preparedItems: preparedItems,
|
|
201
|
-
processingStartedForCodenames: processingStartedForCodenames,
|
|
202
|
-
processedItems: processedItems
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// add mapped linked item to result
|
|
206
|
-
if (mappedLinkedItemResult) {
|
|
207
|
-
richTextLinkedItems.push(mappedLinkedItemResult.item);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// get rich text images
|
|
215
|
-
const richTextImagesResult = this.getRichTextImages(rawElement.images);
|
|
216
|
-
|
|
217
|
-
// extract and map links & images
|
|
218
|
-
const links: ILink[] = this.mapRichTextLinks(rawElement.links);
|
|
219
|
-
const images: IRichTextImage[] = richTextImagesResult.richTextImages;
|
|
220
|
-
|
|
221
|
-
// replace asset urls in html
|
|
222
|
-
const richTextHtml: string = this.getRichTextHtml(rawElement.value, richTextImagesResult.imageUrlRecords);
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
images: images,
|
|
226
|
-
linkedItemCodenames: richTextLinkedItemsCodenames,
|
|
227
|
-
linkedItems: richTextLinkedItems,
|
|
228
|
-
links: links,
|
|
229
|
-
name: rawElement.name,
|
|
230
|
-
type: ElementType.RichText,
|
|
231
|
-
value: richTextHtml
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private mapDateTimeElement(elementWrapper: ElementModels.IElementWrapper): Elements.DateTimeElement {
|
|
236
|
-
const rawElement = elementWrapper.rawElement as Contracts.IDateTimeElementContract;
|
|
237
|
-
return {
|
|
238
|
-
...this.buildElement(elementWrapper, ElementType.DateTime, () => rawElement.value),
|
|
239
|
-
displayTimeZone: rawElement.display_timezone ?? null
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private mapMultipleChoiceElement(elementWrapper: ElementModels.IElementWrapper): Elements.MultipleChoiceElement {
|
|
244
|
-
return this.buildElement(elementWrapper, ElementType.MultipleChoice, () => elementWrapper.rawElement.value);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
private mapNumberElement(elementWrapper: ElementModels.IElementWrapper): Elements.NumberElement {
|
|
248
|
-
return this.buildElement(elementWrapper, ElementType.Number, () => {
|
|
249
|
-
if (elementWrapper.rawElement.value === 0) {
|
|
250
|
-
return 0;
|
|
251
|
-
} else if (elementWrapper.rawElement.value) {
|
|
252
|
-
return +elementWrapper.rawElement.value;
|
|
253
|
-
}
|
|
254
|
-
return null;
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private mapTextElement(elementWrapper: ElementModels.IElementWrapper): Elements.TextElement {
|
|
259
|
-
return this.buildElement(elementWrapper, ElementType.Text, () => elementWrapper.rawElement.value);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private mapAssetsElement(elementWrapper: ElementModels.IElementWrapper): Elements.AssetsElement {
|
|
263
|
-
return this.buildElement(elementWrapper, ElementType.Asset, () => {
|
|
264
|
-
const assetContracts = elementWrapper.rawElement.value as Contracts.IAssetContract[];
|
|
265
|
-
|
|
266
|
-
const assets: ElementModels.AssetModel[] = [];
|
|
267
|
-
|
|
268
|
-
for (const assetContract of assetContracts) {
|
|
269
|
-
let renditions: { [renditionPresetCodename: string]: ElementModels.Rendition } | null = null;
|
|
270
|
-
|
|
271
|
-
// get asset url (custom domain may be configured)
|
|
272
|
-
const assetUrl: string = this.config.assetsDomain
|
|
273
|
-
? deliveryUrlHelper.replaceAssetDomain(assetContract.url, this.config.assetsDomain)
|
|
274
|
-
: assetContract.url;
|
|
275
|
-
|
|
276
|
-
if (assetContract.renditions) {
|
|
277
|
-
renditions = {};
|
|
278
|
-
|
|
279
|
-
for (const renditionPresetKey of Object.keys(assetContract.renditions)) {
|
|
280
|
-
const rendition = assetContract.renditions[renditionPresetKey];
|
|
281
|
-
|
|
282
|
-
renditions[renditionPresetKey] = {
|
|
283
|
-
...rendition,
|
|
284
|
-
url: `${assetUrl}?${rendition.query}` // enhance rendition with absolute url
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const renditionToBeApplied: ElementModels.Rendition | null =
|
|
290
|
-
(this.config.defaultRenditionPreset && renditions?.[this.config.defaultRenditionPreset]) || null;
|
|
291
|
-
|
|
292
|
-
const finalUrl = renditionToBeApplied?.url ?? assetUrl;
|
|
293
|
-
|
|
294
|
-
const asset: ElementModels.AssetModel = {
|
|
295
|
-
...assetContract,
|
|
296
|
-
url: finalUrl, // use custom url of asset which may contain custom domain and applied rendition
|
|
297
|
-
renditions
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
assets.push(asset);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return assets;
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
private mapTaxonomyElement(elementWrapper: ElementModels.IElementWrapper): Elements.TaxonomyElement {
|
|
308
|
-
return {
|
|
309
|
-
...this.buildElement(elementWrapper, ElementType.Taxonomy, () => elementWrapper.rawElement.value),
|
|
310
|
-
taxonomyGroup: elementWrapper.rawElement.taxonomy_group ?? ''
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private mapUnknowElement(elementWrapper: ElementModels.IElementWrapper): Elements.UnknownElement {
|
|
315
|
-
return this.buildElement(elementWrapper, ElementType.Unknown, () => elementWrapper.rawElement.value);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private mapCustomElement(
|
|
319
|
-
elementWrapper: ElementModels.IElementWrapper
|
|
320
|
-
): Elements.CustomElement | ElementModels.IElement<string> {
|
|
321
|
-
// try to find element resolver
|
|
322
|
-
if (this.config.elementResolver) {
|
|
323
|
-
const elementResolverValue = this.config.elementResolver(elementWrapper);
|
|
324
|
-
|
|
325
|
-
if (elementResolverValue) {
|
|
326
|
-
return this.buildElement(elementWrapper, ElementType.Custom, () => elementResolverValue);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return this.buildElement(elementWrapper, ElementType.Custom, () => elementWrapper.rawElement.value);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private mapUrlSlugElement(elementWrapper: ElementModels.IElementWrapper): Elements.UrlSlugElement {
|
|
334
|
-
return this.buildElement(elementWrapper, ElementType.UrlSlug, () => elementWrapper.rawElement.value);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
private mapLinkedItemsElement(data: {
|
|
338
|
-
elementWrapper: ElementModels.IElementWrapper;
|
|
339
|
-
processedItems: IContentItemsContainer<TContentItemType>;
|
|
340
|
-
processingStartedForCodenames: string[];
|
|
341
|
-
preparedItems: IContentItemWithRawDataContainer;
|
|
342
|
-
}): Elements.LinkedItemsElement<any> {
|
|
343
|
-
// prepare linked items
|
|
344
|
-
const linkedItems: IContentItem[] = [];
|
|
345
|
-
|
|
346
|
-
// value = array of item codenames
|
|
347
|
-
const linkedItemCodenames = data.elementWrapper.rawElement.value as string[];
|
|
348
|
-
|
|
349
|
-
for (const codename of linkedItemCodenames) {
|
|
350
|
-
if (this.canMapLinkedItems()) {
|
|
351
|
-
const linkedItem = this.getOrSaveLinkedItemForElement(
|
|
352
|
-
codename,
|
|
353
|
-
data.elementWrapper.rawElement,
|
|
354
|
-
data.processedItems,
|
|
355
|
-
data.processingStartedForCodenames,
|
|
356
|
-
data.preparedItems
|
|
357
|
-
);
|
|
358
|
-
if (linkedItem) {
|
|
359
|
-
// add item to result
|
|
360
|
-
linkedItems.push(linkedItem);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
...this.buildElement(data.elementWrapper, ElementType.ModularContent, () => linkedItemCodenames),
|
|
367
|
-
linkedItems: linkedItems
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private getOrSaveLinkedItemForElement(
|
|
372
|
-
codename: string,
|
|
373
|
-
element: Contracts.IElementContract,
|
|
374
|
-
processedItems: IContentItemsContainer<TContentItemType>,
|
|
375
|
-
mappingStartedForCodenames: string[],
|
|
376
|
-
preparedItems: IContentItemWithRawDataContainer
|
|
377
|
-
): IContentItem | undefined {
|
|
378
|
-
const escapedCodename = codenameHelper.escapeCodenameInCodenameIndexer(codename);
|
|
379
|
-
|
|
380
|
-
// first check if item was already resolved and return it if it was
|
|
381
|
-
const processedItem = processedItems[escapedCodename];
|
|
382
|
-
|
|
383
|
-
if (processedItem) {
|
|
384
|
-
// item was already resolved
|
|
385
|
-
return processedItem;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const preparedItem = preparedItems[escapedCodename];
|
|
389
|
-
|
|
390
|
-
if (mappingStartedForCodenames.includes(codename)) {
|
|
391
|
-
return preparedItem?.item;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
mappingStartedForCodenames.push(codename);
|
|
395
|
-
|
|
396
|
-
// throw error if item is not in response and errors are not skipped
|
|
397
|
-
if (!preparedItem) {
|
|
398
|
-
return undefined;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
let mappedLinkedItem: TContentItemType | undefined;
|
|
402
|
-
|
|
403
|
-
// original resolving if item is still undefined
|
|
404
|
-
const mappedLinkedItemResult = this.mapElements({
|
|
405
|
-
dataToMap: preparedItem,
|
|
406
|
-
preparedItems: preparedItems,
|
|
407
|
-
processingStartedForCodenames: mappingStartedForCodenames,
|
|
408
|
-
processedItems: processedItems
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
if (mappedLinkedItemResult) {
|
|
412
|
-
mappedLinkedItem = mappedLinkedItemResult.item;
|
|
413
|
-
|
|
414
|
-
// add to processed items
|
|
415
|
-
processedItems[escapedCodename] = mappedLinkedItem;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return mappedLinkedItem;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private mapRichTextLinks(linksJson: Contracts.IRichTextElementLinkWrapperContract): ILink[] {
|
|
422
|
-
const links: ILink[] = [];
|
|
423
|
-
|
|
424
|
-
for (const linkId of Object.keys(linksJson)) {
|
|
425
|
-
const linkRaw = linksJson[linkId];
|
|
426
|
-
links.push({
|
|
427
|
-
codename: linkRaw.codename,
|
|
428
|
-
linkId: linkId,
|
|
429
|
-
urlSlug: linkRaw.url_slug,
|
|
430
|
-
type: linkRaw.type
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return links;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
private getRichTextHtml(richTextHtml: string, richTextImageRecords: IRichTextImageUrlRecord[]): string {
|
|
438
|
-
for (const richTextImageRecord of richTextImageRecords) {
|
|
439
|
-
// replace rich text image url if it differs
|
|
440
|
-
if (richTextImageRecord.newUrl !== richTextImageRecord.originalUrl) {
|
|
441
|
-
richTextHtml = richTextHtml.replace(
|
|
442
|
-
new RegExp(richTextImageRecord.originalUrl, 'g'),
|
|
443
|
-
richTextImageRecord.newUrl
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return richTextHtml;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
private getRichTextImages(imagesJson: Contracts.IRichTextElementImageWrapperContract): {
|
|
452
|
-
richTextImages: IRichTextImage[];
|
|
453
|
-
imageUrlRecords: IRichTextImageUrlRecord[];
|
|
454
|
-
} {
|
|
455
|
-
const images: IRichTextImage[] = [];
|
|
456
|
-
const imageUrlRecords: IRichTextImageUrlRecord[] = [];
|
|
457
|
-
|
|
458
|
-
for (const imageId of Object.keys(imagesJson)) {
|
|
459
|
-
const imageRaw = imagesJson[imageId];
|
|
460
|
-
|
|
461
|
-
// image may contain custom asset domain
|
|
462
|
-
const imageUrl: string = this.config.assetsDomain
|
|
463
|
-
? deliveryUrlHelper.replaceAssetDomain(imageRaw.url, this.config.assetsDomain)
|
|
464
|
-
: imageRaw.url;
|
|
465
|
-
|
|
466
|
-
images.push({
|
|
467
|
-
description: imageRaw.description ?? null,
|
|
468
|
-
imageId: imageRaw.image_id,
|
|
469
|
-
url: imageUrl,
|
|
470
|
-
height: imageRaw.height ?? null,
|
|
471
|
-
width: imageRaw.width ?? null
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
imageUrlRecords.push({
|
|
475
|
-
originalUrl: imageRaw.url,
|
|
476
|
-
newUrl: imageUrl
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return {
|
|
481
|
-
imageUrlRecords: imageUrlRecords,
|
|
482
|
-
richTextImages: images
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
private buildElement<TValue>(
|
|
487
|
-
elementWrapper: ElementModels.IElementWrapper,
|
|
488
|
-
type: ElementType,
|
|
489
|
-
valueFactory: () => TValue
|
|
490
|
-
): ElementModels.IElement<TValue> {
|
|
491
|
-
return {
|
|
492
|
-
name: elementWrapper.rawElement.name,
|
|
493
|
-
type: type,
|
|
494
|
-
value: valueFactory()
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private canMapLinkedItems(): boolean {
|
|
499
|
-
if (!this.config.linkedItemsReferenceHandler) {
|
|
500
|
-
return true;
|
|
501
|
-
}
|
|
502
|
-
return this.config.linkedItemsReferenceHandler === 'map';
|
|
503
|
-
}
|
|
504
|
-
}
|
|
1
|
+
import { codenameHelper, deliveryUrlHelper, enumHelper } from '../utilities';
|
|
2
|
+
|
|
3
|
+
import { IDeliveryClientConfig } from '../config';
|
|
4
|
+
import { Contracts } from '../contracts';
|
|
5
|
+
import { ElementModels, Elements, ElementType } from '../elements';
|
|
6
|
+
import {
|
|
7
|
+
IContentItem,
|
|
8
|
+
IContentItemsContainer,
|
|
9
|
+
IContentItemWithRawDataContainer,
|
|
10
|
+
IContentItemWithRawElements,
|
|
11
|
+
ILink,
|
|
12
|
+
IMapElementsResult,
|
|
13
|
+
IRichTextImage
|
|
14
|
+
} from '../models';
|
|
15
|
+
|
|
16
|
+
interface IRichTextImageUrlRecord {
|
|
17
|
+
originalUrl: string;
|
|
18
|
+
newUrl: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ElementMapper<TContentItemType extends IContentItem> {
|
|
22
|
+
constructor(private readonly config: IDeliveryClientConfig) {}
|
|
23
|
+
|
|
24
|
+
mapElements<TContentItem extends TContentItemType = TContentItemType>(data: {
|
|
25
|
+
dataToMap: IContentItemWithRawElements;
|
|
26
|
+
processedItems: IContentItemsContainer<TContentItem>;
|
|
27
|
+
processingStartedForCodenames: string[];
|
|
28
|
+
preparedItems: IContentItemWithRawDataContainer;
|
|
29
|
+
}): IMapElementsResult<TContentItem, TContentItemType> | undefined {
|
|
30
|
+
// return processed item to avoid infinite recursion
|
|
31
|
+
const processedItem =
|
|
32
|
+
data.processedItems[codenameHelper.escapeCodenameInCodenameIndexer(data.dataToMap.item.system.codename)];
|
|
33
|
+
if (processedItem) {
|
|
34
|
+
// item was already resolved
|
|
35
|
+
return {
|
|
36
|
+
item: processedItem,
|
|
37
|
+
processedItems: data.processedItems,
|
|
38
|
+
preparedItems: data.preparedItems,
|
|
39
|
+
processingStartedForCodenames: data.processingStartedForCodenames
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const preparedItem =
|
|
44
|
+
data.preparedItems[codenameHelper.escapeCodenameInCodenameIndexer(data.dataToMap.item.system.codename)];
|
|
45
|
+
|
|
46
|
+
const itemInstance = preparedItem?.item;
|
|
47
|
+
|
|
48
|
+
if (!itemInstance) {
|
|
49
|
+
// item is not present in response
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// mapp elements
|
|
54
|
+
const elementCodenames = Object.getOwnPropertyNames(data.dataToMap.rawItem.elements);
|
|
55
|
+
|
|
56
|
+
for (const elementCodename of elementCodenames) {
|
|
57
|
+
const elementWrapper: ElementModels.IElementWrapper = {
|
|
58
|
+
system: data.dataToMap.item.system,
|
|
59
|
+
rawElement: data.dataToMap.rawItem.elements[elementCodename],
|
|
60
|
+
element: elementCodename
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const mappedElement = this.mapElement({
|
|
64
|
+
elementWrapper: elementWrapper,
|
|
65
|
+
item: itemInstance,
|
|
66
|
+
preparedItems: data.preparedItems,
|
|
67
|
+
processingStartedForCodenames: data.processingStartedForCodenames,
|
|
68
|
+
processedItems: data.processedItems
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// set mapped elements
|
|
72
|
+
itemInstance.elements[elementCodename] = mappedElement;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
item: itemInstance as TContentItem,
|
|
77
|
+
processedItems: data.processedItems,
|
|
78
|
+
preparedItems: data.preparedItems,
|
|
79
|
+
processingStartedForCodenames: data.processingStartedForCodenames
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private mapElement(data: {
|
|
84
|
+
elementWrapper: ElementModels.IElementWrapper;
|
|
85
|
+
item: IContentItem;
|
|
86
|
+
processedItems: IContentItemsContainer<TContentItemType>;
|
|
87
|
+
processingStartedForCodenames: string[];
|
|
88
|
+
preparedItems: IContentItemWithRawDataContainer;
|
|
89
|
+
}): ElementModels.IElement<any> {
|
|
90
|
+
const elementType = enumHelper.getEnumFromValue<ElementType>(ElementType, data.elementWrapper.rawElement.type);
|
|
91
|
+
if (elementType) {
|
|
92
|
+
if (elementType === ElementType.ModularContent) {
|
|
93
|
+
return this.mapLinkedItemsElement({
|
|
94
|
+
elementWrapper: data.elementWrapper,
|
|
95
|
+
preparedItems: data.preparedItems,
|
|
96
|
+
processingStartedForCodenames: data.processingStartedForCodenames,
|
|
97
|
+
processedItems: data.processedItems
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (elementType === ElementType.Text) {
|
|
102
|
+
return this.mapTextElement(data.elementWrapper);
|
|
103
|
+
}
|
|
104
|
+
if (elementType === ElementType.Asset) {
|
|
105
|
+
return this.mapAssetsElement(data.elementWrapper);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (elementType === ElementType.Number) {
|
|
109
|
+
return this.mapNumberElement(data.elementWrapper);
|
|
110
|
+
}
|
|
111
|
+
if (elementType === ElementType.MultipleChoice) {
|
|
112
|
+
return this.mapMultipleChoiceElement(data.elementWrapper);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (elementType === ElementType.DateTime) {
|
|
116
|
+
return this.mapDateTimeElement(data.elementWrapper);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (elementType === ElementType.RichText) {
|
|
120
|
+
// add to parent items
|
|
121
|
+
return this.mapRichTextElement(
|
|
122
|
+
data.elementWrapper,
|
|
123
|
+
data.processedItems,
|
|
124
|
+
data.processingStartedForCodenames,
|
|
125
|
+
data.preparedItems
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (elementType === ElementType.UrlSlug) {
|
|
130
|
+
return this.mapUrlSlugElement(data.elementWrapper);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (elementType === ElementType.Taxonomy) {
|
|
134
|
+
return this.mapTaxonomyElement(data.elementWrapper);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (elementType === ElementType.Custom) {
|
|
138
|
+
return this.mapCustomElement(data.elementWrapper);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
console.warn(
|
|
142
|
+
`Could not map element '${data.elementWrapper.rawElement.name}' of type '${data.elementWrapper.rawElement.type}'. Returning unknown element instead.`
|
|
143
|
+
);
|
|
144
|
+
return this.mapUnknowElement(data.elementWrapper);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private mapRichTextElement(
|
|
148
|
+
elementWrapper: ElementModels.IElementWrapper,
|
|
149
|
+
processedItems: IContentItemsContainer<TContentItemType>,
|
|
150
|
+
processingStartedForCodenames: string[],
|
|
151
|
+
preparedItems: IContentItemWithRawDataContainer
|
|
152
|
+
): Elements.RichTextElement {
|
|
153
|
+
const rawElement = elementWrapper.rawElement as Contracts.IRichTextElementContract;
|
|
154
|
+
|
|
155
|
+
// get all linked items and linked items codenames nested in rich text
|
|
156
|
+
const richTextLinkedItems: IContentItem[] = [];
|
|
157
|
+
const richTextLinkedItemsCodenames: string[] = [];
|
|
158
|
+
|
|
159
|
+
// The Kontent Delivery API is not guaranteed to return rich-text modular_content array items in the same order in which they appear inside the `value` property.
|
|
160
|
+
// We extract the modular_content codenames in the rich-text value and sort the raw modular_content based on that order instead.
|
|
161
|
+
const rawModularContentCodenamesMatches = (rawElement.value as string).matchAll(
|
|
162
|
+
/<object[^>]+data-codename="(?<codename>[a-z0-9_]*)".*?>/g
|
|
163
|
+
);
|
|
164
|
+
const rawModularContentCodenamesSorted = Array.from(rawModularContentCodenamesMatches).reduce<string[]>(
|
|
165
|
+
(acc, match) => {
|
|
166
|
+
if (match.groups && match.groups.codename) {
|
|
167
|
+
acc.push(match.groups.codename);
|
|
168
|
+
}
|
|
169
|
+
return acc;
|
|
170
|
+
},
|
|
171
|
+
[] as string[]
|
|
172
|
+
);
|
|
173
|
+
const rawModularContentCodenames = [...rawElement.modular_content].sort(function (a, b) {
|
|
174
|
+
return rawModularContentCodenamesSorted.indexOf(a) - rawModularContentCodenamesSorted.indexOf(b);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
for (const codename of rawModularContentCodenames) {
|
|
178
|
+
richTextLinkedItemsCodenames.push(codename);
|
|
179
|
+
// get linked item and check if it exists (it might not be included in response due to 'Depth' parameter)
|
|
180
|
+
const preparedData = preparedItems[codename];
|
|
181
|
+
|
|
182
|
+
// first try to get existing item
|
|
183
|
+
if (this.canMapLinkedItems()) {
|
|
184
|
+
const existingLinkedItem = this.getOrSaveLinkedItemForElement(
|
|
185
|
+
codename,
|
|
186
|
+
rawElement,
|
|
187
|
+
processedItems,
|
|
188
|
+
processingStartedForCodenames,
|
|
189
|
+
preparedItems
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (existingLinkedItem) {
|
|
193
|
+
// item was found, add it to linked items
|
|
194
|
+
richTextLinkedItems.push(existingLinkedItem);
|
|
195
|
+
} else {
|
|
196
|
+
// item was not found or not yet resolved
|
|
197
|
+
if (preparedData) {
|
|
198
|
+
const mappedLinkedItemResult = this.mapElements({
|
|
199
|
+
dataToMap: preparedData,
|
|
200
|
+
preparedItems: preparedItems,
|
|
201
|
+
processingStartedForCodenames: processingStartedForCodenames,
|
|
202
|
+
processedItems: processedItems
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// add mapped linked item to result
|
|
206
|
+
if (mappedLinkedItemResult) {
|
|
207
|
+
richTextLinkedItems.push(mappedLinkedItemResult.item);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// get rich text images
|
|
215
|
+
const richTextImagesResult = this.getRichTextImages(rawElement.images);
|
|
216
|
+
|
|
217
|
+
// extract and map links & images
|
|
218
|
+
const links: ILink[] = this.mapRichTextLinks(rawElement.links);
|
|
219
|
+
const images: IRichTextImage[] = richTextImagesResult.richTextImages;
|
|
220
|
+
|
|
221
|
+
// replace asset urls in html
|
|
222
|
+
const richTextHtml: string = this.getRichTextHtml(rawElement.value, richTextImagesResult.imageUrlRecords);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
images: images,
|
|
226
|
+
linkedItemCodenames: richTextLinkedItemsCodenames,
|
|
227
|
+
linkedItems: richTextLinkedItems,
|
|
228
|
+
links: links,
|
|
229
|
+
name: rawElement.name,
|
|
230
|
+
type: ElementType.RichText,
|
|
231
|
+
value: richTextHtml
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private mapDateTimeElement(elementWrapper: ElementModels.IElementWrapper): Elements.DateTimeElement {
|
|
236
|
+
const rawElement = elementWrapper.rawElement as Contracts.IDateTimeElementContract;
|
|
237
|
+
return {
|
|
238
|
+
...this.buildElement(elementWrapper, ElementType.DateTime, () => rawElement.value),
|
|
239
|
+
displayTimeZone: rawElement.display_timezone ?? null
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private mapMultipleChoiceElement(elementWrapper: ElementModels.IElementWrapper): Elements.MultipleChoiceElement {
|
|
244
|
+
return this.buildElement(elementWrapper, ElementType.MultipleChoice, () => elementWrapper.rawElement.value);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private mapNumberElement(elementWrapper: ElementModels.IElementWrapper): Elements.NumberElement {
|
|
248
|
+
return this.buildElement(elementWrapper, ElementType.Number, () => {
|
|
249
|
+
if (elementWrapper.rawElement.value === 0) {
|
|
250
|
+
return 0;
|
|
251
|
+
} else if (elementWrapper.rawElement.value) {
|
|
252
|
+
return +elementWrapper.rawElement.value;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private mapTextElement(elementWrapper: ElementModels.IElementWrapper): Elements.TextElement {
|
|
259
|
+
return this.buildElement(elementWrapper, ElementType.Text, () => elementWrapper.rawElement.value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private mapAssetsElement(elementWrapper: ElementModels.IElementWrapper): Elements.AssetsElement {
|
|
263
|
+
return this.buildElement(elementWrapper, ElementType.Asset, () => {
|
|
264
|
+
const assetContracts = elementWrapper.rawElement.value as Contracts.IAssetContract[];
|
|
265
|
+
|
|
266
|
+
const assets: ElementModels.AssetModel[] = [];
|
|
267
|
+
|
|
268
|
+
for (const assetContract of assetContracts) {
|
|
269
|
+
let renditions: { [renditionPresetCodename: string]: ElementModels.Rendition } | null = null;
|
|
270
|
+
|
|
271
|
+
// get asset url (custom domain may be configured)
|
|
272
|
+
const assetUrl: string = this.config.assetsDomain
|
|
273
|
+
? deliveryUrlHelper.replaceAssetDomain(assetContract.url, this.config.assetsDomain)
|
|
274
|
+
: assetContract.url;
|
|
275
|
+
|
|
276
|
+
if (assetContract.renditions) {
|
|
277
|
+
renditions = {};
|
|
278
|
+
|
|
279
|
+
for (const renditionPresetKey of Object.keys(assetContract.renditions)) {
|
|
280
|
+
const rendition = assetContract.renditions[renditionPresetKey];
|
|
281
|
+
|
|
282
|
+
renditions[renditionPresetKey] = {
|
|
283
|
+
...rendition,
|
|
284
|
+
url: `${assetUrl}?${rendition.query}` // enhance rendition with absolute url
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const renditionToBeApplied: ElementModels.Rendition | null =
|
|
290
|
+
(this.config.defaultRenditionPreset && renditions?.[this.config.defaultRenditionPreset]) || null;
|
|
291
|
+
|
|
292
|
+
const finalUrl = renditionToBeApplied?.url ?? assetUrl;
|
|
293
|
+
|
|
294
|
+
const asset: ElementModels.AssetModel = {
|
|
295
|
+
...assetContract,
|
|
296
|
+
url: finalUrl, // use custom url of asset which may contain custom domain and applied rendition
|
|
297
|
+
renditions
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
assets.push(asset);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return assets;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private mapTaxonomyElement(elementWrapper: ElementModels.IElementWrapper): Elements.TaxonomyElement {
|
|
308
|
+
return {
|
|
309
|
+
...this.buildElement(elementWrapper, ElementType.Taxonomy, () => elementWrapper.rawElement.value),
|
|
310
|
+
taxonomyGroup: elementWrapper.rawElement.taxonomy_group ?? ''
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private mapUnknowElement(elementWrapper: ElementModels.IElementWrapper): Elements.UnknownElement {
|
|
315
|
+
return this.buildElement(elementWrapper, ElementType.Unknown, () => elementWrapper.rawElement.value);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private mapCustomElement(
|
|
319
|
+
elementWrapper: ElementModels.IElementWrapper
|
|
320
|
+
): Elements.CustomElement | ElementModels.IElement<string> {
|
|
321
|
+
// try to find element resolver
|
|
322
|
+
if (this.config.elementResolver) {
|
|
323
|
+
const elementResolverValue = this.config.elementResolver(elementWrapper);
|
|
324
|
+
|
|
325
|
+
if (elementResolverValue) {
|
|
326
|
+
return this.buildElement(elementWrapper, ElementType.Custom, () => elementResolverValue);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return this.buildElement(elementWrapper, ElementType.Custom, () => elementWrapper.rawElement.value);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private mapUrlSlugElement(elementWrapper: ElementModels.IElementWrapper): Elements.UrlSlugElement {
|
|
334
|
+
return this.buildElement(elementWrapper, ElementType.UrlSlug, () => elementWrapper.rawElement.value);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private mapLinkedItemsElement(data: {
|
|
338
|
+
elementWrapper: ElementModels.IElementWrapper;
|
|
339
|
+
processedItems: IContentItemsContainer<TContentItemType>;
|
|
340
|
+
processingStartedForCodenames: string[];
|
|
341
|
+
preparedItems: IContentItemWithRawDataContainer;
|
|
342
|
+
}): Elements.LinkedItemsElement<any> {
|
|
343
|
+
// prepare linked items
|
|
344
|
+
const linkedItems: IContentItem[] = [];
|
|
345
|
+
|
|
346
|
+
// value = array of item codenames
|
|
347
|
+
const linkedItemCodenames = data.elementWrapper.rawElement.value as string[];
|
|
348
|
+
|
|
349
|
+
for (const codename of linkedItemCodenames) {
|
|
350
|
+
if (this.canMapLinkedItems()) {
|
|
351
|
+
const linkedItem = this.getOrSaveLinkedItemForElement(
|
|
352
|
+
codename,
|
|
353
|
+
data.elementWrapper.rawElement,
|
|
354
|
+
data.processedItems,
|
|
355
|
+
data.processingStartedForCodenames,
|
|
356
|
+
data.preparedItems
|
|
357
|
+
);
|
|
358
|
+
if (linkedItem) {
|
|
359
|
+
// add item to result
|
|
360
|
+
linkedItems.push(linkedItem);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
...this.buildElement(data.elementWrapper, ElementType.ModularContent, () => linkedItemCodenames),
|
|
367
|
+
linkedItems: linkedItems
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private getOrSaveLinkedItemForElement(
|
|
372
|
+
codename: string,
|
|
373
|
+
element: Contracts.IElementContract,
|
|
374
|
+
processedItems: IContentItemsContainer<TContentItemType>,
|
|
375
|
+
mappingStartedForCodenames: string[],
|
|
376
|
+
preparedItems: IContentItemWithRawDataContainer
|
|
377
|
+
): IContentItem | undefined {
|
|
378
|
+
const escapedCodename = codenameHelper.escapeCodenameInCodenameIndexer(codename);
|
|
379
|
+
|
|
380
|
+
// first check if item was already resolved and return it if it was
|
|
381
|
+
const processedItem = processedItems[escapedCodename];
|
|
382
|
+
|
|
383
|
+
if (processedItem) {
|
|
384
|
+
// item was already resolved
|
|
385
|
+
return processedItem;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const preparedItem = preparedItems[escapedCodename];
|
|
389
|
+
|
|
390
|
+
if (mappingStartedForCodenames.includes(codename)) {
|
|
391
|
+
return preparedItem?.item;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
mappingStartedForCodenames.push(codename);
|
|
395
|
+
|
|
396
|
+
// throw error if item is not in response and errors are not skipped
|
|
397
|
+
if (!preparedItem) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let mappedLinkedItem: TContentItemType | undefined;
|
|
402
|
+
|
|
403
|
+
// original resolving if item is still undefined
|
|
404
|
+
const mappedLinkedItemResult = this.mapElements({
|
|
405
|
+
dataToMap: preparedItem,
|
|
406
|
+
preparedItems: preparedItems,
|
|
407
|
+
processingStartedForCodenames: mappingStartedForCodenames,
|
|
408
|
+
processedItems: processedItems
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (mappedLinkedItemResult) {
|
|
412
|
+
mappedLinkedItem = mappedLinkedItemResult.item;
|
|
413
|
+
|
|
414
|
+
// add to processed items
|
|
415
|
+
processedItems[escapedCodename] = mappedLinkedItem;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return mappedLinkedItem;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private mapRichTextLinks(linksJson: Contracts.IRichTextElementLinkWrapperContract): ILink[] {
|
|
422
|
+
const links: ILink[] = [];
|
|
423
|
+
|
|
424
|
+
for (const linkId of Object.keys(linksJson)) {
|
|
425
|
+
const linkRaw = linksJson[linkId];
|
|
426
|
+
links.push({
|
|
427
|
+
codename: linkRaw.codename,
|
|
428
|
+
linkId: linkId,
|
|
429
|
+
urlSlug: linkRaw.url_slug,
|
|
430
|
+
type: linkRaw.type
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return links;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private getRichTextHtml(richTextHtml: string, richTextImageRecords: IRichTextImageUrlRecord[]): string {
|
|
438
|
+
for (const richTextImageRecord of richTextImageRecords) {
|
|
439
|
+
// replace rich text image url if it differs
|
|
440
|
+
if (richTextImageRecord.newUrl !== richTextImageRecord.originalUrl) {
|
|
441
|
+
richTextHtml = richTextHtml.replace(
|
|
442
|
+
new RegExp(richTextImageRecord.originalUrl, 'g'),
|
|
443
|
+
richTextImageRecord.newUrl
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return richTextHtml;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private getRichTextImages(imagesJson: Contracts.IRichTextElementImageWrapperContract): {
|
|
452
|
+
richTextImages: IRichTextImage[];
|
|
453
|
+
imageUrlRecords: IRichTextImageUrlRecord[];
|
|
454
|
+
} {
|
|
455
|
+
const images: IRichTextImage[] = [];
|
|
456
|
+
const imageUrlRecords: IRichTextImageUrlRecord[] = [];
|
|
457
|
+
|
|
458
|
+
for (const imageId of Object.keys(imagesJson)) {
|
|
459
|
+
const imageRaw = imagesJson[imageId];
|
|
460
|
+
|
|
461
|
+
// image may contain custom asset domain
|
|
462
|
+
const imageUrl: string = this.config.assetsDomain
|
|
463
|
+
? deliveryUrlHelper.replaceAssetDomain(imageRaw.url, this.config.assetsDomain)
|
|
464
|
+
: imageRaw.url;
|
|
465
|
+
|
|
466
|
+
images.push({
|
|
467
|
+
description: imageRaw.description ?? null,
|
|
468
|
+
imageId: imageRaw.image_id,
|
|
469
|
+
url: imageUrl,
|
|
470
|
+
height: imageRaw.height ?? null,
|
|
471
|
+
width: imageRaw.width ?? null
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
imageUrlRecords.push({
|
|
475
|
+
originalUrl: imageRaw.url,
|
|
476
|
+
newUrl: imageUrl
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
imageUrlRecords: imageUrlRecords,
|
|
482
|
+
richTextImages: images
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private buildElement<TValue>(
|
|
487
|
+
elementWrapper: ElementModels.IElementWrapper,
|
|
488
|
+
type: ElementType,
|
|
489
|
+
valueFactory: () => TValue
|
|
490
|
+
): ElementModels.IElement<TValue> {
|
|
491
|
+
return {
|
|
492
|
+
name: elementWrapper.rawElement.name,
|
|
493
|
+
type: type,
|
|
494
|
+
value: valueFactory()
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private canMapLinkedItems(): boolean {
|
|
499
|
+
if (!this.config.linkedItemsReferenceHandler) {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
return this.config.linkedItemsReferenceHandler === 'map';
|
|
503
|
+
}
|
|
504
|
+
}
|