@stackbit/cms-core 0.4.5-develop.1 → 0.4.6-develop.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/content-store.d.ts +2 -2
  2. package/dist/content-store.d.ts.map +1 -1
  3. package/dist/content-store.js +30 -18
  4. package/dist/content-store.js.map +1 -1
  5. package/dist/types/content-store-document-fields.d.ts +3 -3
  6. package/dist/types/content-store-document-fields.d.ts.map +1 -1
  7. package/dist/utils/asset-sources-utils.d.ts +12 -0
  8. package/dist/utils/asset-sources-utils.d.ts.map +1 -0
  9. package/dist/utils/asset-sources-utils.js +101 -0
  10. package/dist/utils/asset-sources-utils.js.map +1 -0
  11. package/dist/utils/create-update-csi-docs.d.ts +4 -2
  12. package/dist/utils/create-update-csi-docs.d.ts.map +1 -1
  13. package/dist/utils/create-update-csi-docs.js +62 -11
  14. package/dist/utils/create-update-csi-docs.js.map +1 -1
  15. package/dist/utils/csi-to-store-docs-converter.d.ts +2 -1
  16. package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
  17. package/dist/utils/csi-to-store-docs-converter.js +95 -4
  18. package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
  19. package/dist/utils/duplicate-document.js +5 -7
  20. package/dist/utils/duplicate-document.js.map +1 -1
  21. package/dist/utils/store-to-csi-docs-converter.d.ts.map +1 -1
  22. package/dist/utils/store-to-csi-docs-converter.js +9 -8
  23. package/dist/utils/store-to-csi-docs-converter.js.map +1 -1
  24. package/package.json +5 -5
  25. package/src/content-store.ts +26 -16
  26. package/src/types/content-store-document-fields.ts +10 -5
  27. package/src/utils/asset-sources-utils.ts +106 -0
  28. package/src/utils/create-update-csi-docs.ts +79 -13
  29. package/src/utils/csi-to-store-docs-converter.ts +102 -2
  30. package/src/utils/duplicate-document.ts +5 -7
  31. package/src/utils/store-to-csi-docs-converter.ts +9 -8
@@ -0,0 +1,106 @@
1
+ import { Config } from '@stackbit/sdk';
2
+ import { AssetSource, DistributiveOmit, FieldImageProps } from '@stackbit/types';
3
+ import * as ContentStoreTypes from '../types';
4
+
5
+ export function getAssetSourcesForClient(stackbitConfig: Config | null): DistributiveOmit<AssetSource, 'transform' | 'preview'>[] {
6
+ if (!stackbitConfig) {
7
+ return [];
8
+ }
9
+ const assetSources = stackbitConfig.assetSources ?? [];
10
+ return assetSources.reduce((accum: DistributiveOmit<AssetSource, 'transform' | 'preview'>[], assetSource: AssetSource) => {
11
+ if (assetSource.type === 'iframe') {
12
+ if (!assetSource.name || !assetSource.url) {
13
+ return accum;
14
+ }
15
+ const { transform, preview, ...rest } = assetSource;
16
+ return accum.concat(rest);
17
+ } else {
18
+ const buildInAssetSources = ['cloudinary'];
19
+ const name = assetSource.name ?? (buildInAssetSources.includes(assetSource.type) ? assetSource.type : null);
20
+ if (!name) {
21
+ return accum;
22
+ }
23
+ const { transform, preview, ...rest } = assetSource;
24
+ return accum.concat({
25
+ ...rest,
26
+ name: name
27
+ });
28
+ }
29
+ return accum;
30
+ }, []);
31
+ }
32
+
33
+ export function transformAssetSourceDataForAssetSource(sourceData: any, assetSource: AssetSource): any {
34
+ if (typeof assetSource.transform === 'function') {
35
+ return assetSource.transform({ assetData: sourceData });
36
+ } else {
37
+ return sourceData;
38
+ }
39
+ }
40
+
41
+ export function getImageFieldsFromSourceData({
42
+ sourceData,
43
+ imageModelField,
44
+ assetSources
45
+ }: {
46
+ sourceData: any;
47
+ imageModelField: FieldImageProps;
48
+ assetSources: AssetSource[];
49
+ }): ContentStoreTypes.ImageFields {
50
+ const imageAssetSource = getAssetSourceBySourceName(assetSources, imageModelField.source);
51
+
52
+ if (imageAssetSource?.preview) {
53
+ const preview = typeof imageAssetSource.preview === 'function' ? imageAssetSource.preview({ assetData: sourceData }) : imageAssetSource.preview;
54
+ return {
55
+ title: {
56
+ type: 'string',
57
+ value: preview?.title ?? ''
58
+ },
59
+ url: {
60
+ type: 'string',
61
+ value: preview.image ?? ''
62
+ }
63
+ };
64
+ }
65
+
66
+ if (imageAssetSource?.type === 'cloudinary') {
67
+ return {
68
+ title: {
69
+ type: 'string',
70
+ value: sourceData?.public_id
71
+ },
72
+ url: {
73
+ type: 'string',
74
+ value: sourceData?.derived?.[0]?.secure_url ?? sourceData?.secure_url
75
+ }
76
+ };
77
+ }
78
+
79
+ return {
80
+ title: {
81
+ type: 'string',
82
+ value: ''
83
+ },
84
+ url: {
85
+ type: 'string',
86
+ value: typeof sourceData === 'string' ? sourceData : ''
87
+ }
88
+ };
89
+ }
90
+
91
+ export function getAssetSourceBySourceName(assetSources: AssetSource[], assetSourceName: string | undefined): AssetSource | undefined {
92
+ if (!assetSourceName) {
93
+ return undefined;
94
+ }
95
+ const assetSource = assetSources.find((assetSources) => assetSources.name === assetSourceName);
96
+ if (!assetSource) {
97
+ // for build-in asset sources, use the name of the source (field[type=image].source) as its type
98
+ if (assetSourceName === 'cloudinary') {
99
+ return {
100
+ type: assetSourceName,
101
+ name: assetSourceName
102
+ };
103
+ }
104
+ }
105
+ return assetSource;
106
+ }
@@ -2,12 +2,20 @@ import _ from 'lodash';
2
2
  import slugify from 'slugify';
3
3
 
4
4
  import { Model as SDKModel } from '@stackbit/sdk';
5
- import { Model as CSIModel, Field, FieldSpecificProps, UpdateOperationListFieldItem, isOneOfFieldTypes } from '@stackbit/types';
5
+ import {
6
+ Model as CSIModel,
7
+ Field,
8
+ FieldSpecificProps,
9
+ UpdateOperationListFieldItem,
10
+ isOneOfFieldTypes,
11
+ isModelFieldSpecificPropsOneOfFieldTypes
12
+ } from '@stackbit/types';
6
13
  import { fieldPathToString, mapPromise } from '@stackbit/utils';
7
14
  import * as CSITypes from '@stackbit/types';
8
15
 
9
16
  import * as ContentStoreTypes from '../types';
10
17
  import { getContentSourceId, getUserContextForSrcType, updateOperationValueFieldWithCrossReference } from '../content-store-utils';
18
+ import { getAssetSourceBySourceName, transformAssetSourceDataForAssetSource } from './asset-sources-utils';
11
19
 
12
20
  export type CreateDocumentCallback = ({
13
21
  updateOperationFields,
@@ -92,12 +100,14 @@ export async function createDocumentRecursively({
92
100
  modelName,
93
101
  contentSourceId,
94
102
  contentSourceDataById,
103
+ assetSources,
95
104
  createDocument
96
105
  }: {
97
106
  object?: Record<string, any>;
98
107
  modelName: string;
99
108
  contentSourceId: string;
100
109
  contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
110
+ assetSources: CSITypes.AssetSource[];
101
111
  createDocument: CreateDocumentCallback;
102
112
  }): Promise<{ documentId: string; newRefDocumentIds: string[] }> {
103
113
  const contentSourceData = contentSourceDataById[contentSourceId];
@@ -120,15 +130,21 @@ export async function createDocumentRecursively({
120
130
  if (object && slugField && (slugField in object || '_stackbit_slug' in object)) {
121
131
  const slugFieldValue = object[slugField] || object['_stackbit_slug'];
122
132
  object[slugField] = sanitizeSlug(slugFieldValue);
123
- if (!modelFields.find(field => field.name === slugField)) {
124
- modelFields = [...modelFields, {
125
- type: 'slug',
126
- name: slugField
127
- }];
128
- csiModelFields = [...csiModelFields, {
129
- type: 'slug',
130
- name: slugField
131
- }];
133
+ if (!modelFields.find((field) => field.name === slugField)) {
134
+ modelFields = [
135
+ ...modelFields,
136
+ {
137
+ type: 'slug',
138
+ name: slugField
139
+ }
140
+ ];
141
+ csiModelFields = [
142
+ ...csiModelFields,
143
+ {
144
+ type: 'slug',
145
+ name: slugField
146
+ }
147
+ ];
132
148
  }
133
149
  }
134
150
  }
@@ -142,6 +158,7 @@ export async function createDocumentRecursively({
142
158
  csiModelMap,
143
159
  contentSourceId,
144
160
  contentSourceDataById,
161
+ assetSources,
145
162
  createDocument
146
163
  });
147
164
 
@@ -161,7 +178,8 @@ function extractTokensFromString(input: string): string[] {
161
178
  }
162
179
 
163
180
  function sanitizeSlug(slug?: string) {
164
- return slug?.split('/')
181
+ return slug
182
+ ?.split('/')
165
183
  .map((part) => slugify(part, { lower: true }))
166
184
  .join('/');
167
185
  }
@@ -175,6 +193,7 @@ async function createObjectRecursively({
175
193
  csiModelMap,
176
194
  contentSourceId,
177
195
  contentSourceDataById,
196
+ assetSources,
178
197
  createDocument
179
198
  }: {
180
199
  object?: Record<string, any>;
@@ -185,6 +204,7 @@ async function createObjectRecursively({
185
204
  csiModelMap: Record<string, CSIModel>;
186
205
  contentSourceId: string;
187
206
  contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
207
+ assetSources: CSITypes.AssetSource[];
188
208
  createDocument: CreateDocumentCallback;
189
209
  }): Promise<{
190
210
  fields: Record<string, CSITypes.UpdateOperationField>;
@@ -233,6 +253,7 @@ async function createObjectRecursively({
233
253
  csiModelMap,
234
254
  contentSourceId,
235
255
  contentSourceDataById,
256
+ assetSources,
236
257
  createDocument
237
258
  });
238
259
  result.fields[fieldName] = fieldResult.field;
@@ -256,6 +277,7 @@ async function createUpdateOperationFieldRecursively({
256
277
  csiModelMap,
257
278
  contentSourceId,
258
279
  contentSourceDataById,
280
+ assetSources,
259
281
  createDocument
260
282
  }: {
261
283
  value: any;
@@ -266,6 +288,7 @@ async function createUpdateOperationFieldRecursively({
266
288
  csiModelMap: Record<string, CSIModel>;
267
289
  contentSourceId: string;
268
290
  contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
291
+ assetSources: CSITypes.AssetSource[];
269
292
  createDocument: CreateDocumentCallback;
270
293
  }): Promise<{ field: CSITypes.UpdateOperationField; newRefDocumentIds: string[] }> {
271
294
  if (csiModelField.type === 'object') {
@@ -281,6 +304,7 @@ async function createUpdateOperationFieldRecursively({
281
304
  csiModelMap,
282
305
  contentSourceId,
283
306
  contentSourceDataById,
307
+ assetSources,
284
308
  createDocument
285
309
  });
286
310
  return {
@@ -320,6 +344,7 @@ async function createUpdateOperationFieldRecursively({
320
344
  csiModelMap,
321
345
  contentSourceId,
322
346
  contentSourceDataById,
347
+ assetSources,
323
348
  createDocument
324
349
  });
325
350
  return {
@@ -332,15 +357,33 @@ async function createUpdateOperationFieldRecursively({
332
357
  };
333
358
  } else if (csiModelField.type === 'image') {
334
359
  let refId: string | undefined;
335
- if (csiModelField.source === 'cloudinary') {
360
+ const assetSource = getAssetSourceBySourceName(assetSources, csiModelField.source);
361
+ if (assetSource) {
362
+ // omit $$type for backwards compatibility with legacy presets
363
+ const sourceData = _.omit(value, ['$$type']);
336
364
  return {
337
365
  field: {
338
366
  type: 'image',
339
- value: _.omit(value, ['$$type']) // backwards compatibility with legacy presets
367
+ value: transformAssetSourceDataForAssetSource(sourceData, assetSource)
340
368
  },
341
369
  newRefDocumentIds: []
342
370
  };
343
371
  }
372
+ // - when setting images in git, the UpdateOperationField is:
373
+ // { type: 'image', value: 'stackbit_asset_id:static:images/elephants.jpg' }
374
+ // so the 'value' will be a string 'stackbit_asset_id:static:images/elephants.jpg'
375
+ // - when setting images in Contentful, the UpdateOperationField is:
376
+ // { type: 'image', value: '6rEF3N6lFlEscOq8U63gYg' }
377
+ // so the 'value' will be a string representing the Asset ID - '6rEF3N6lFlEscOq8U63gYg'
378
+ // - when creating images from presets or default values the asset ID can
379
+ // can be specified as $$ref or as plain value
380
+ // - when duplicating documents with images, the duplicated image field will be:
381
+ // { $$ref: '...' }
382
+ // TODO: A bug!
383
+ // - when the image is specified as an absolute URL - https://... or //...
384
+ // the absolute URL value will will be set as 'refId', with field
385
+ // type: 'reference'. There is currently no way to solve it because we
386
+ // use 'image' type both for referenced images and for literal images.
344
387
  if (_.isPlainObject(value)) {
345
388
  refId = value.$$ref ?? value.url;
346
389
  } else {
@@ -391,6 +434,7 @@ async function createUpdateOperationFieldRecursively({
391
434
  modelName,
392
435
  contentSourceId,
393
436
  contentSourceDataById,
437
+ assetSources,
394
438
  createDocument
395
439
  });
396
440
  return {
@@ -431,6 +475,7 @@ async function createUpdateOperationFieldRecursively({
431
475
  modelName,
432
476
  contentSourceId: getContentSourceId(refSrcType, refProjectId),
433
477
  contentSourceDataById,
478
+ assetSources,
434
479
  createDocument
435
480
  });
436
481
  _newRefDocumentIds.push(documentId, ...newRefDocumentIds);
@@ -462,6 +507,7 @@ async function createUpdateOperationFieldRecursively({
462
507
  csiModelMap,
463
508
  contentSourceId,
464
509
  contentSourceDataById,
510
+ assetSources,
465
511
  createDocument
466
512
  });
467
513
  if (result.field.type === 'list') {
@@ -491,6 +537,20 @@ async function createUpdateOperationFieldRecursively({
491
537
  },
492
538
  newRefDocumentIds: []
493
539
  };
540
+ } else if (isModelFieldSpecificPropsOneOfFieldTypes(csiModelField, ['string', 'text', 'json']) && modelField.type === 'image') {
541
+ if (modelField.source) {
542
+ const assetSource = getAssetSourceBySourceName(assetSources, modelField.source);
543
+ if (assetSource) {
544
+ value = transformAssetSourceDataForAssetSource(value, assetSource);
545
+ }
546
+ }
547
+ return {
548
+ field: {
549
+ type: csiModelField.type,
550
+ value: csiModelField.type !== 'json' && _.isPlainObject(value) ? JSON.stringify(value) : value
551
+ },
552
+ newRefDocumentIds: []
553
+ };
494
554
  }
495
555
  return {
496
556
  field: {
@@ -510,6 +570,7 @@ export async function convertOperationField({
510
570
  csiModelMap,
511
571
  contentSourceId,
512
572
  contentSourceDataById,
573
+ assetSources,
513
574
  createDocument
514
575
  }: {
515
576
  operationField: ContentStoreTypes.UpdateOperationField;
@@ -520,6 +581,7 @@ export async function convertOperationField({
520
581
  csiModelMap: Record<string, SDKModel>;
521
582
  contentSourceId: string;
522
583
  contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
584
+ assetSources: CSITypes.AssetSource[];
523
585
  createDocument: CreateDocumentCallback;
524
586
  }): Promise<CSITypes.UpdateOperationField> {
525
587
  switch (operationField.type) {
@@ -536,6 +598,7 @@ export async function convertOperationField({
536
598
  csiModelMap,
537
599
  contentSourceId,
538
600
  contentSourceDataById,
601
+ assetSources,
539
602
  createDocument
540
603
  });
541
604
  return {
@@ -558,6 +621,7 @@ export async function convertOperationField({
558
621
  csiModelMap,
559
622
  contentSourceId,
560
623
  contentSourceDataById,
624
+ assetSources,
561
625
  createDocument
562
626
  });
563
627
  return {
@@ -600,6 +664,7 @@ export async function convertOperationField({
600
664
  csiModelMap,
601
665
  contentSourceId,
602
666
  contentSourceDataById,
667
+ assetSources,
603
668
  createDocument
604
669
  });
605
670
  if (result.field.type === 'list') {
@@ -656,6 +721,7 @@ export async function convertOperationField({
656
721
  csiModelMap,
657
722
  contentSourceId,
658
723
  contentSourceDataById,
724
+ assetSources,
659
725
  createDocument
660
726
  });
661
727
  return result.field;
@@ -16,6 +16,7 @@ import * as CSITypes from '@stackbit/types';
16
16
  import * as ContentStoreTypes from '../types';
17
17
  import { IMAGE_MODEL } from '../common/common-schema';
18
18
  import { BackCompatContentSourceInterface } from './backward-compatibility';
19
+ import { getImageFieldsFromSourceData } from './asset-sources-utils';
19
20
 
20
21
  export function mapCSIAssetsToStoreAssets({
21
22
  csiAssets,
@@ -98,12 +99,14 @@ export function mapCSIDocumentsToStoreDocuments({
98
99
  contentSourceInstance,
99
100
  modelMap,
100
101
  defaultLocaleCode,
102
+ assetSources,
101
103
  createConfigDelegate
102
104
  }: {
103
105
  csiDocuments: CSITypes.Document[];
104
106
  contentSourceInstance: BackCompatContentSourceInterface;
105
107
  modelMap: Record<string, Model>;
106
108
  defaultLocaleCode?: string;
109
+ assetSources: CSITypes.AssetSource[];
107
110
  createConfigDelegate: () => CSITypes.ConfigDelegate;
108
111
  }): ContentStoreTypes.Document[] {
109
112
  const meta = getMetadataFromContentStore({ contentSourceInstance });
@@ -114,6 +117,7 @@ export function mapCSIDocumentsToStoreDocuments({
114
117
  modelMap,
115
118
  defaultLocaleCode,
116
119
  meta,
120
+ assetSources,
117
121
  createConfigDelegate
118
122
  })
119
123
  );
@@ -125,6 +129,7 @@ function mapCSIDocumentToStoreDocument({
125
129
  modelMap,
126
130
  defaultLocaleCode,
127
131
  meta,
132
+ assetSources,
128
133
  createConfigDelegate
129
134
  }: {
130
135
  csiDocument: CSITypes.Document;
@@ -132,6 +137,7 @@ function mapCSIDocumentToStoreDocument({
132
137
  modelMap: Record<string, Model>;
133
138
  defaultLocaleCode?: string;
134
139
  meta: { srcType: string; srcProjectId: string; srcProjectUrl: string; srcEnvironment: string };
140
+ assetSources: CSITypes.AssetSource[];
135
141
  createConfigDelegate: () => CSITypes.ConfigDelegate;
136
142
  }): ContentStoreTypes.Document {
137
143
  return omitByNil({
@@ -166,6 +172,7 @@ function mapCSIDocumentToStoreDocument({
166
172
  parentDocument: csiDocument,
167
173
  modelMap,
168
174
  defaultLocaleCode,
175
+ assetSources,
169
176
  createConfigDelegate
170
177
  }
171
178
  })
@@ -178,6 +185,7 @@ type MapContext = {
178
185
  parentDocument: CSITypes.Document;
179
186
  modelMap: Record<string, Model>;
180
187
  defaultLocaleCode?: string;
188
+ assetSources: CSITypes.AssetSource[];
181
189
  createConfigDelegate: () => CSITypes.ConfigDelegate;
182
190
  };
183
191
 
@@ -237,6 +245,7 @@ function mapCSIFieldToStoreField({
237
245
  case 'date':
238
246
  case 'datetime':
239
247
  case 'enum':
248
+ // TODO: 'json' and 'style' fields can be remapped from 'string' and 'text', in this case parse the JSON object
240
249
  case 'json':
241
250
  case 'style':
242
251
  case 'color':
@@ -248,10 +257,14 @@ function mapCSIFieldToStoreField({
248
257
  ...csiDocumentField,
249
258
  type: modelField.type
250
259
  } as ContentStoreTypes.DocumentField;
260
+ case 'image':
261
+ // The 'image' model field can be a 'reference' document field in CMSes like Sanity and Contentful.
262
+ if (csiDocumentField.type === 'reference') {
263
+ return csiDocumentField;
264
+ }
265
+ return mapImageField(csiDocumentField, modelField, context.assetSources);
251
266
  // Don't override types of the following document fields.
252
- // An 'image' model field can be a 'reference' document field in CMSes like Sanity and Contentful.
253
267
  // Rest of the fields must have the same type across document and model fields.
254
- case 'image':
255
268
  case 'file':
256
269
  case 'reference':
257
270
  return csiDocumentField as ContentStoreTypes.DocumentField;
@@ -275,6 +288,93 @@ function mapCSIFieldToStoreField({
275
288
  }
276
289
  }
277
290
 
291
+ function mapImageField(
292
+ csiDocumentField: CSITypes.DocumentField,
293
+ imageModelField: CSITypes.FieldImageProps,
294
+ assetSources: CSITypes.AssetSource[]
295
+ ): ContentStoreTypes.DocumentImageField {
296
+ // the image can be remapped from 'string', 'text' or 'json' fields
297
+ if (isDocumentFieldOneOfFieldTypes(csiDocumentField, ['string', 'text', 'json'])) {
298
+ try {
299
+ if (!isLocalizedField(csiDocumentField)) {
300
+ if (imageModelField.source) {
301
+ return omitByNil({
302
+ type: 'image',
303
+ source: imageModelField.source,
304
+ sourceData: csiDocumentField.value,
305
+ fields: getImageFieldsFromSourceData({
306
+ sourceData: csiDocumentField.value,
307
+ imageModelField: imageModelField,
308
+ assetSources
309
+ })
310
+ });
311
+ }
312
+ } else {
313
+ return omitByNil({
314
+ type: 'image',
315
+ source: imageModelField.source,
316
+ localized: true,
317
+ locales: _.mapValues(csiDocumentField.locales, (locale) => {
318
+ return {
319
+ locale: locale.locale,
320
+ sourceData: locale.value,
321
+ fields: getImageFieldsFromSourceData({
322
+ sourceData: locale.value,
323
+ imageModelField: imageModelField,
324
+ assetSources
325
+ })
326
+ };
327
+ })
328
+ });
329
+ }
330
+ } catch (e) {
331
+ return {
332
+ type: 'image',
333
+ ...(isLocalizedField(csiDocumentField) ? { localized: true, locales: {} } : { isUnset: true })
334
+ };
335
+ }
336
+ }
337
+ if (csiDocumentField.type !== 'image') {
338
+ return {
339
+ type: 'image',
340
+ ...(isLocalizedField(csiDocumentField) ? { localized: true, locales: {} } : { isUnset: true })
341
+ };
342
+ }
343
+ if (!isLocalizedField(csiDocumentField)) {
344
+ return omitByNil({
345
+ type: 'image',
346
+ source: csiDocumentField.source,
347
+ sourceData: csiDocumentField.sourceData,
348
+ fields:
349
+ csiDocumentField.fields ??
350
+ getImageFieldsFromSourceData({
351
+ sourceData: csiDocumentField.sourceData,
352
+ imageModelField: imageModelField,
353
+ assetSources
354
+ })
355
+ });
356
+ }
357
+ return omitByNil({
358
+ type: 'image',
359
+ source: csiDocumentField.source,
360
+ localized: true,
361
+ locales: _.mapValues(csiDocumentField.locales, (locale) => {
362
+ return {
363
+ locale: locale.locale,
364
+ sourceData: locale.sourceData,
365
+ fields:
366
+ // for backward compatibility use, fields if provided
367
+ locale.fields ??
368
+ getImageFieldsFromSourceData({
369
+ sourceData: locale.sourceData,
370
+ imageModelField: imageModelField,
371
+ assetSources
372
+ })
373
+ };
374
+ })
375
+ });
376
+ }
377
+
278
378
  function mapCrossReferenceField(csiDocumentField: CSITypes.DocumentField): ContentStoreTypes.DocumentCrossReferenceField {
279
379
  const unlocalizedUnset = {
280
380
  type: 'cross-reference',
@@ -114,20 +114,18 @@ function mergeObjectWithDocumentField({
114
114
  break;
115
115
  }
116
116
  case 'image': {
117
+ // if an image value was provided explicitly, use it to override the image
118
+ // of the matching field in the duplicated document.
117
119
  if (typeof value !== 'undefined') {
118
120
  return value;
119
121
  }
120
122
  const localizedField = getDocumentFieldForLocale(documentField, locale);
121
- if (localizedField && !localizedField.isUnset && isPlainObjectOrUndefined(value)) {
123
+ if (localizedField && !localizedField.isUnset) {
122
124
  if (localizedField?.sourceData) {
123
125
  return localizedField?.sourceData;
124
126
  }
125
- //TODO needs testing, looks like we need to use the url field instead of this
126
- return mergeObjectWithDocumentFields({
127
- object: value,
128
- documentFields: localizedField.fields,
129
- ...context
130
- });
127
+ const localizedUrl = getDocumentFieldForLocale(localizedField.fields.url, locale);
128
+ return localizedUrl?.value;
131
129
  }
132
130
  break;
133
131
  }
@@ -1,5 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import * as CSITypes from '@stackbit/types';
3
+ import { omitByNil } from '@stackbit/utils';
3
4
  import * as ContentStoreTypes from '../types';
4
5
 
5
6
  export function mapStoreDocumentsToCSIDocumentsWithSource(documents: ContentStoreTypes.Document[]): CSITypes.DocumentWithSource[] {
@@ -92,26 +93,26 @@ function mapStoreFieldToCSIField(documentField: ContentStoreTypes.DocumentField)
92
93
  if (_.isEmpty(documentField.locales)) {
93
94
  return;
94
95
  }
95
- return {
96
+ return omitByNil({
96
97
  type: 'image',
97
98
  localized: true,
98
99
  source: documentField.source,
99
- sourceData: documentField.sourceData,
100
- locales: _.mapValues(documentField.locales, (locale) => ({
100
+ locales: _.mapValues(documentField.locales, (locale) => omitByNil({
101
101
  locale: locale.locale,
102
- fields: mapStoreFieldsToCSIFields(locale.fields)
102
+ sourceData: locale.sourceData,
103
+ ...(locale.sourceData ? null : { fields: mapStoreFieldsToCSIFields(locale.fields) })
103
104
  }))
104
- } as CSITypes.DocumentImageFieldLocalized;
105
+ }) as CSITypes.DocumentImageFieldLocalized;
105
106
  }
106
107
  if (documentField.isUnset) {
107
108
  return;
108
109
  }
109
- return {
110
+ return omitByNil({
110
111
  type: 'image',
111
112
  source: documentField.source,
112
113
  sourceData: documentField.sourceData,
113
- fields: mapStoreFieldsToCSIFields(documentField.fields)
114
- } as CSITypes.DocumentImageFieldNonLocalized;
114
+ ...(documentField.sourceData ? null : { fields: mapStoreFieldsToCSIFields(documentField.fields) })
115
+ }) as CSITypes.DocumentImageFieldNonLocalized;
115
116
  }
116
117
  case 'object': {
117
118
  if (documentField.localized) {