@stackbit/cms-core 0.1.13-presets.0 → 0.1.14-alpha.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.
Files changed (31) hide show
  1. package/dist/content-store-types.d.ts +27 -3
  2. package/dist/content-store-types.d.ts.map +1 -1
  3. package/dist/content-store-utils.d.ts +12 -1
  4. package/dist/content-store-utils.d.ts.map +1 -1
  5. package/dist/content-store-utils.js +44 -1
  6. package/dist/content-store-utils.js.map +1 -1
  7. package/dist/content-store.d.ts +6 -16
  8. package/dist/content-store.d.ts.map +1 -1
  9. package/dist/content-store.js +71 -126
  10. package/dist/content-store.js.map +1 -1
  11. package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
  12. package/dist/utils/csi-to-store-docs-converter.js +13 -6
  13. package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
  14. package/dist/utils/store-to-api-docs-converter.js +1 -1
  15. package/dist/utils/store-to-api-docs-converter.js.map +1 -1
  16. package/dist/utils/store-to-csi-docs-converter.d.ts +4 -0
  17. package/dist/utils/store-to-csi-docs-converter.d.ts.map +1 -0
  18. package/dist/utils/store-to-csi-docs-converter.js +211 -0
  19. package/dist/utils/store-to-csi-docs-converter.js.map +1 -0
  20. package/package.json +5 -5
  21. package/src/content-store-types.ts +41 -4
  22. package/src/content-store-utils.ts +61 -3
  23. package/src/content-store.ts +99 -183
  24. package/src/utils/csi-to-store-docs-converter.ts +21 -12
  25. package/src/utils/store-to-api-docs-converter.ts +3 -3
  26. package/src/utils/store-to-csi-docs-converter.ts +222 -0
  27. package/dist/utils/preset-utils.d.ts +0 -4
  28. package/dist/utils/preset-utils.d.ts.map +0 -1
  29. package/dist/utils/preset-utils.js +0 -37
  30. package/dist/utils/preset-utils.js.map +0 -1
  31. package/src/utils/preset-utils.ts +0 -33
@@ -3,21 +3,20 @@ import path from 'path';
3
3
  import sanitizeFilename from 'sanitize-filename';
4
4
 
5
5
  import * as CSITypes from '@stackbit/types';
6
- import { getLocalizedFieldForLocale, ModelExtension, UserCommandSpawner } from '@stackbit/types';
6
+ import { DocumentWithSource, getLocalizedFieldForLocale, ModelExtension, ModelWithSource, UserCommandSpawner } from '@stackbit/types';
7
7
  import {
8
8
  Config,
9
- Field,
10
- Model,
9
+ extendModelsWithPresetsIds,
10
+ getPresetDirs,
11
+ getYamlModelDirs,
11
12
  ImageModel,
12
- Preset,
13
- PresetMap,
14
13
  loadPresets,
15
- getYamlModelDirs,
16
- getPresetDirs,
17
14
  loadYamlModelsFromFiles,
15
+ mergeConfigModelsWithExternalModels,
18
16
  mergeConfigModelsWithModelsFromFiles,
19
- extendModelsWithPresetsIds,
20
- mergeConfigModelsWithExternalModels
17
+ Model,
18
+ Preset,
19
+ PresetMap
21
20
  } from '@stackbit/sdk';
22
21
  import { deferWhileRunning, mapPromise, reducePromise } from '@stackbit/utils';
23
22
 
@@ -26,20 +25,22 @@ import { Timer } from './utils/timer';
26
25
  import { SearchFilter } from './types/search-filter';
27
26
  import { searchDocuments } from './utils/search-utils';
28
27
  import { mapCSIAssetsToStoreAssets, mapCSIDocumentsToStoreDocuments } from './utils/csi-to-store-docs-converter';
28
+ import { mapStoreDocumentsToCSIDocumentsWithSource } from './utils/store-to-csi-docs-converter';
29
29
  import {
30
30
  getContentSourceId,
31
31
  getContentSourceIdForContentSource,
32
+ getDocumentFieldLabelValueForSiteMapEntry,
33
+ getCSIDocumentsAndAssetsFromContentSourceDataByIds,
32
34
  getModelFieldForFieldAtPath,
33
35
  getUserContextForSrcType,
34
- groupModelsByContentSource,
35
- groupDocumentsByContentSource
36
+ groupDocumentsByContentSource,
37
+ groupModelsByContentSource
36
38
  } from './content-store-utils';
37
39
  import { mapAssetsToLocalizedApiImages, mapDocumentsToLocalizedApiObjects, mapStoreAssetsToAPIAssets } from './utils/store-to-api-docs-converter';
38
40
  import { convertOperationField, createDocumentRecursively, getCreateDocumentThunk } from './utils/create-update-csi-docs';
39
41
  import { mergeObjectWithDocument } from './utils/duplicate-document';
40
42
  import { normalizeModels, validateModels } from './utils/model-utils';
41
43
  import { IMAGE_MODEL } from './common/common-schema';
42
- import { getPresetFromDocument } from './utils/preset-utils';
43
44
 
44
45
  export type HandleConfigAssets = <T extends Model>({ models, presets }: { models?: T[]; presets?: PresetMap }) => Promise<{ models: T[]; presets: PresetMap }>;
45
46
 
@@ -55,42 +56,8 @@ export interface ContentSourceOptions {
55
56
  devAppRestartNeeded?: () => void;
56
57
  }
57
58
 
58
- interface ContentSourceData {
59
- id: string;
60
- instance: CSITypes.ContentSourceInterface;
61
- srcType: string;
62
- srcProjectId: string;
63
- locales?: CSITypes.Locale[];
64
- defaultLocaleCode?: string;
65
- /* Array of extended and validated Models */
66
- models: Model[];
67
- /* Map of extended and validated Models by model name */
68
- modelMap: Record<string, Model>;
69
- /* Array of original Models (as provided by content source) */
70
- csiModels: CSITypes.Model[];
71
- /* Map of original Models (as provided by content source) by model name */
72
- csiModelMap: Record<string, CSITypes.Model>;
73
- /* Array of original content source Documents */
74
- csiDocuments: CSITypes.Document[];
75
- /* Map of original content source Documents by document ID */
76
- csiDocumentMap: Record<string, CSITypes.Document>;
77
- /* Array of converted content-store Documents */
78
- documents: ContentStoreTypes.Document[];
79
- /* Map of converted content-store Documents by document ID */
80
- documentMap: Record<string, ContentStoreTypes.Document>;
81
- /* Array of original content source Assets */
82
- csiAssets: CSITypes.Asset[];
83
- /* Map of original content source Assets by asset ID */
84
- csiAssetMap: Record<string, CSITypes.Asset>;
85
- /* Array of converted content-store Assets */
86
- assets: ContentStoreTypes.Asset[];
87
- /* Map of converted content-store Assets by asset ID */
88
- assetMap: Record<string, ContentStoreTypes.Asset>;
89
- }
90
-
91
- type ContentSourceRawData = Omit<ContentSourceData, 'models' | 'modelMap' | 'documents' | 'documentMap' | 'presetMap'>;
92
-
93
- const StackbitPresetModelName = 'stackbitPreset';
59
+ type ContentSourceData = ContentStoreTypes.ContentSourceData;
60
+ type ContentSourceRawData = Omit<ContentSourceData, 'models' | 'modelMap' | 'documents' | 'documentMap'>;
94
61
 
95
62
  export class ContentStore {
96
63
  private readonly logger: ContentStoreTypes.Logger;
@@ -104,7 +71,6 @@ export class ContentStore {
104
71
  private readonly devAppRestartNeeded?: () => void;
105
72
  private contentSources: CSITypes.ContentSourceInterface[] = [];
106
73
  private contentSourceDataById: Record<string, ContentSourceData> = {};
107
- private presetsContentSource?: CSITypes.ContentSourceInterface;
108
74
  private contentUpdatesWatchTimer: Timer;
109
75
  private stackbitConfig: Config | null = null;
110
76
  private yamlModels: Model[] = [];
@@ -167,6 +133,7 @@ export class ContentStore {
167
133
  this.yamlModels = await this.loadYamlModels({ stackbitConfig });
168
134
  this.configModels = this.mergeConfigModels(stackbitConfig.models ?? [], this.yamlModels);
169
135
  }
136
+ this.presets = await this.loadPresets({ stackbitConfig });
170
137
  }
171
138
 
172
139
  await this.loadContentSourcesAndProcessData({ init: true });
@@ -244,10 +211,10 @@ export class ContentStore {
244
211
  // Check if any of the preset files were changed. If presets were changed, reload them.
245
212
  const presetDirs = getPresetDirs(this.stackbitConfig);
246
213
  const presetsChanged = updatedFiles.find((updatedFile) => _.some(presetDirs, (presetDir) => updatedFile.startsWith(presetDir)));
247
- if (presetsChanged && !this.canManagePresets()) {
214
+ if (presetsChanged) {
248
215
  this.logger.debug('identified change in stackbit preset files');
249
216
  schemaChanged = true;
250
- this.presets = await this.loadPresetsFromConfig({ stackbitConfig: this.stackbitConfig });
217
+ this.presets = await this.loadPresets({ stackbitConfig: this.stackbitConfig });
251
218
  }
252
219
  }
253
220
 
@@ -317,7 +284,7 @@ export class ContentStore {
317
284
  return configModelsResult.models;
318
285
  }
319
286
 
320
- private async loadPresetsFromConfig({ stackbitConfig }: { stackbitConfig: Config }): Promise<PresetMap> {
287
+ private async loadPresets({ stackbitConfig }: { stackbitConfig: Config }): Promise<Record<string, Preset>> {
321
288
  const contentSources = stackbitConfig?.contentSources ?? [];
322
289
  const singleContentSource = contentSources.length === 1 ? contentSources[0] : null;
323
290
  const presetResult = await loadPresets({
@@ -336,19 +303,6 @@ export class ContentStore {
336
303
  return presets;
337
304
  }
338
305
 
339
- private async loadPresetsFromContentSource(contentSourceData: ContentSourceRawData): Promise<PresetMap> {
340
- const presets = _.reduce(contentSourceData.csiDocuments, (result: Record<string, Preset>, document) => {
341
- if (document.modelName === StackbitPresetModelName) {
342
- const preset = getPresetFromDocument(document, contentSourceData.csiAssetMap);
343
- if (preset) {
344
- result[document.id] = preset;
345
- }
346
- }
347
- return result;
348
- }, {});
349
- return presets;
350
- }
351
-
352
306
  /**
353
307
  * This function reloads the data of the specified content-sources, while
354
308
  * reusing the cached data of the rest of the content-sources, then processes
@@ -384,21 +338,6 @@ export class ContentStore {
384
338
 
385
339
  const contentSourceRawDataArr = await Promise.all(promises);
386
340
 
387
- // find first content source that supports presets
388
- for (let i = 0; i < contentSources.length; i++) {
389
- const contentSourceDataRaw = contentSourceRawDataArr[i];
390
- if (contentSourceDataRaw?.csiModelMap?.[StackbitPresetModelName]) {
391
- this.presetsContentSource = contentSources[i];
392
- this.presets = await this.loadPresetsFromContentSource(contentSourceDataRaw);
393
- break;
394
- }
395
- }
396
-
397
- // fallback to loading presets from config as usual
398
- if (this.stackbitConfig && !this.presetsContentSource && _.isEmpty(this.presets)) {
399
- this.presets = await this.loadPresetsFromConfig({ stackbitConfig: this.stackbitConfig });
400
- }
401
-
402
341
  // update all content sources at once to prevent race conditions
403
342
  this.contentSourceDataById = await this.processData({
404
343
  stackbitConfig: this.stackbitConfig,
@@ -503,9 +442,6 @@ export class ContentStore {
503
442
  private onContentChange(contentSourceId: string, contentChangeEvent: CSITypes.ContentChangeEvent): ContentStoreTypes.ContentChangeResult {
504
443
  // TODO: prevent content change process for contentSourceId if loading content is in progress
505
444
 
506
- // certain content changes, like preset changes are interpreted as schema changes
507
- let schemaChanged = false;
508
-
509
445
  this.logger.debug('onContentChange', {
510
446
  contentSourceId,
511
447
  documentCount: contentChangeEvent.documents.length,
@@ -616,22 +552,14 @@ export class ContentStore {
616
552
  // the indexes of mapped documents and documents from changeEvent are the same
617
553
  const document = documents[idx]!;
618
554
  const csiDocument = contentChangeEvent.documents[idx]!;
619
- if (csiDocument.modelName === StackbitPresetModelName) {
620
- const preset = getPresetFromDocument(csiDocument, contentSourceData.csiAssetMap);
621
- if (preset) {
622
- this.presets[csiDocument.id] = preset;
623
- schemaChanged = true;
624
- }
555
+ const dataIndex = contentSourceData.documents.findIndex((existingDoc) => existingDoc.srcObjectId === document.srcObjectId);
556
+ if (dataIndex === -1) {
557
+ contentSourceData.documents.push(document);
558
+ contentSourceData.csiDocuments.push(csiDocument);
625
559
  } else {
626
- const dataIndex = contentSourceData.documents.findIndex((existingDoc) => existingDoc.srcObjectId === document.srcObjectId);
627
- if (dataIndex === -1) {
628
- contentSourceData.documents.push(document);
629
- contentSourceData.csiDocuments.push(csiDocument);
630
- } else {
631
- // the indexes of documents and csiDocuments are always the same as they are always updated at the same time
632
- contentSourceData.documents.splice(dataIndex, 1, document);
633
- contentSourceData.csiDocuments.splice(dataIndex, 1, csiDocument);
634
- }
560
+ // the indexes of documents and csiDocuments are always the same as they are always updated at the same time
561
+ contentSourceData.documents.splice(dataIndex, 1, document);
562
+ contentSourceData.csiDocuments.splice(dataIndex, 1, csiDocument);
635
563
  }
636
564
  result.updatedDocuments.push({
637
565
  srcType: contentSourceData.srcType,
@@ -660,10 +588,6 @@ export class ContentStore {
660
588
  });
661
589
  }
662
590
 
663
- if (schemaChanged) {
664
- this.onSchemaChangeCallback?.();
665
- }
666
-
667
591
  return result;
668
592
  }
669
593
 
@@ -733,7 +657,9 @@ export class ContentStore {
733
657
  message += `, srcProjectId: '${configModel.srcProjectId}'`;
734
658
  }
735
659
  const matchesModelsMessage = matchedCSIModels.map((model) => `srcType: '${model.srcType}', srcProjectId: '${model.srcProjectId}'`).join('; ');
736
- this.userLogger.warn(`model ${message} defined in stackbit config matches more that 1 model in the following content sources: ${matchesModelsMessage}`);
660
+ this.userLogger.warn(
661
+ `model ${message} defined in stackbit config matches more that 1 model in the following content sources: ${matchesModelsMessage}`
662
+ );
737
663
  }
738
664
 
739
665
  const modelsWithSource = contentSourceRawDataArr.reduce((accum: CSITypes.ModelWithSource[], csData) => {
@@ -787,9 +713,8 @@ export class ContentStore {
787
713
  (csData): ContentSourceData => {
788
714
  const modelMap = _.get(modelMapByContentSource, [csData.srcType, csData.srcProjectId], {});
789
715
  const mappedCSIDocuments = _.get(documentMapByContentSource, [csData.srcType, csData.srcProjectId], []);
790
- const filteredCSIDocuments = mappedCSIDocuments.filter(document => document.modelName !== StackbitPresetModelName);
791
716
  const documents = mapCSIDocumentsToStoreDocuments({
792
- csiDocuments: filteredCSIDocuments,
717
+ csiDocuments: mappedCSIDocuments,
793
718
  contentSourceInstance: csData.instance,
794
719
  defaultLocaleCode: csData.defaultLocaleCode,
795
720
  modelMap: modelMap
@@ -845,10 +770,6 @@ export class ContentStore {
845
770
  return contentSourceData.instance.getProjectEnvironment();
846
771
  }
847
772
 
848
- canManagePresets() {
849
- return Boolean(this.presetsContentSource);
850
- }
851
-
852
773
  async hasAccess({
853
774
  srcType,
854
775
  srcProjectId,
@@ -956,6 +877,68 @@ export class ContentStore {
956
877
  };
957
878
  }
958
879
 
880
+ getSiteMapEntries({ locale }: { locale?: string } = {}): CSITypes.SiteMapEntry[] {
881
+ if (!this.stackbitConfig?.siteMap) {
882
+ return [];
883
+ }
884
+
885
+ // TODO: cache siteMap in processData
886
+ const siteMapOptions = _.reduce(
887
+ this.contentSourceDataById,
888
+ (accum: { models: ModelWithSource[]; documents: DocumentWithSource[] }, contentSourceData) => {
889
+ return {
890
+ models: accum.models.concat(
891
+ contentSourceData.models.map((model) => ({
892
+ srcType: contentSourceData.srcType,
893
+ srcProjectId: contentSourceData.srcProjectId,
894
+ ...model
895
+ }))
896
+ ),
897
+ documents: accum.documents.concat(mapStoreDocumentsToCSIDocumentsWithSource(contentSourceData.documents))
898
+ };
899
+ },
900
+ { models: [], documents: [] }
901
+ );
902
+
903
+ const siteMapEntries = this.stackbitConfig.siteMap(siteMapOptions).reduce((accum: CSITypes.SiteMapEntry[], siteMapEntry) => {
904
+ // The site map entries are provided by user, sanitize them and filter out illegal entries
905
+ if (!siteMapEntry) {
906
+ return accum;
907
+ }
908
+
909
+ if (typeof siteMapEntry.urlPath !== 'string') {
910
+ return accum;
911
+ }
912
+
913
+ if ('document' in siteMapEntry) {
914
+ const doc = siteMapEntry.document;
915
+ if (!doc.srcType || !doc.srcProjectId || !doc.modelName || !doc.id) {
916
+ return accum;
917
+ }
918
+ }
919
+
920
+ if (!siteMapEntry.label) {
921
+ const fieldLabelValue = getDocumentFieldLabelValueForSiteMapEntry({ siteMapEntry, locale, contentSourceDataById: this.contentSourceDataById });
922
+ siteMapEntry = {
923
+ ...siteMapEntry,
924
+ label: fieldLabelValue ?? siteMapEntry.urlPath
925
+ };
926
+ }
927
+
928
+ if (!siteMapEntry.stableId) {
929
+ siteMapEntry = {
930
+ ...siteMapEntry,
931
+ stableId: 'document' in siteMapEntry ? siteMapEntry.document.id : siteMapEntry.urlPath
932
+ };
933
+ }
934
+
935
+ accum.push(siteMapEntry);
936
+ return accum;
937
+ }, []);
938
+
939
+ return _.isEmpty(locale) ? siteMapEntries : siteMapEntries.filter((siteMapEntry) => !siteMapEntry.locale || siteMapEntry.locale === locale);
940
+ }
941
+
959
942
  getDocument({
960
943
  srcDocumentId,
961
944
  srcProjectId,
@@ -976,7 +959,7 @@ export class ContentStore {
976
959
  (documents: ContentStoreTypes.Document[], contentSourceData) => {
977
960
  const currentDocuments = _.isEmpty(locale)
978
961
  ? contentSourceData.documents
979
- : contentSourceData.documents.filter(document => !document.locale || document.locale === locale)
962
+ : contentSourceData.documents.filter((document) => !document.locale || document.locale === locale);
980
963
  return documents.concat(currentDocuments);
981
964
  },
982
965
  []
@@ -995,7 +978,7 @@ export class ContentStore {
995
978
  (assets: ContentStoreTypes.Asset[], contentSourceData) => {
996
979
  const currentAssets = _.isEmpty(locale)
997
980
  ? contentSourceData.assets
998
- : contentSourceData.assets.filter(asset => !asset.locale || asset.locale === locale)
981
+ : contentSourceData.assets.filter((asset) => !asset.locale || asset.locale === locale);
999
982
  return assets.concat(currentAssets);
1000
983
  },
1001
984
  []
@@ -1008,10 +991,10 @@ export class ContentStore {
1008
991
  this.contentSourceDataById,
1009
992
  (objects: ContentStoreTypes.APIObject[], contentSourceData) => {
1010
993
  const documents = hasExplicitLocale
1011
- ? contentSourceData.documents.filter(document => !document.locale || document.locale === locale)
994
+ ? contentSourceData.documents.filter((document) => !document.locale || document.locale === locale)
1012
995
  : contentSourceData.documents;
1013
996
  const assets = hasExplicitLocale
1014
- ? contentSourceData.assets.filter(asset => !asset.locale || asset.locale === locale)
997
+ ? contentSourceData.assets.filter((asset) => !asset.locale || asset.locale === locale)
1015
998
  : contentSourceData.assets;
1016
999
  const currentLocale = locale ?? contentSourceData.defaultLocaleCode;
1017
1000
  const documentObjects = mapDocumentsToLocalizedApiObjects(documents, currentLocale);
@@ -1171,51 +1154,6 @@ export class ContentStore {
1171
1154
  return { srcDocumentId: updatedDocument.id };
1172
1155
  }
1173
1156
 
1174
- async createPreset({ preset, thumbnailAsset, user }: { preset: Preset, thumbnailAsset: ContentStoreTypes.UploadAssetData, user?: ContentStoreTypes.User }): Promise<{ srcDocumentId: string }> {
1175
- if (!this.presetsContentSource) {
1176
- throw new Error('No content source available for preset saving');
1177
- }
1178
- let thumbnail: string | undefined;
1179
- if (thumbnailAsset) {
1180
- const assets = await this.uploadAssets({
1181
- srcType: this.presetsContentSource.getContentSourceType(),
1182
- srcProjectId: this.presetsContentSource.getProjectId(),
1183
- assets: [thumbnailAsset],
1184
- user
1185
- });
1186
- thumbnail = assets[0]?.objectId;
1187
- }
1188
- const document = await this.createDocument({
1189
- srcType: this.presetsContentSource.getContentSourceType(),
1190
- srcProjectId: this.presetsContentSource.getProjectId(),
1191
- modelName: StackbitPresetModelName,
1192
- object: {
1193
- label: preset.label,
1194
- thumbnail,
1195
- data: _.omit(preset, 'thumbnail')
1196
- },
1197
- user
1198
- });
1199
- return { srcDocumentId: document.srcDocumentId };
1200
- }
1201
-
1202
- async deletePreset({ presetId, user }: { presetId: string, user?: ContentStoreTypes.User }) {
1203
- if (!this.presetsContentSource) {
1204
- throw new Error('No content source available for preset deleting');
1205
- }
1206
- await this.deleteDocument({
1207
- srcType: this.presetsContentSource.getContentSourceType(),
1208
- srcProjectId: this.presetsContentSource.getProjectId(),
1209
- srcDocumentId: presetId,
1210
- user
1211
- });
1212
-
1213
- // we delete presets immediately because some CMSs don't notify us
1214
- // when documents have been deleted.
1215
- delete this.presets[presetId];
1216
- this.onSchemaChangeCallback?.();
1217
- }
1218
-
1219
1157
  async uploadAndLinkAsset({
1220
1158
  srcType,
1221
1159
  srcProjectId,
@@ -1664,12 +1602,12 @@ export class ContentStore {
1664
1602
 
1665
1603
  contentSourceIds.forEach((contentSourceId) => {
1666
1604
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
1667
-
1605
+
1668
1606
  _.set(schema, [contentSourceData.srcType, contentSourceData.srcProjectId], contentSourceData.modelMap);
1669
1607
 
1670
1608
  const contentSourceDocuments = _.isEmpty(locale)
1671
- ? contentSourceData.documents
1672
- : contentSourceData.documents.filter(document => !document.locale || document.locale === locale)
1609
+ ? contentSourceData.documents
1610
+ : contentSourceData.documents.filter((document) => !document.locale || document.locale === locale);
1673
1611
  documents.push(...contentSourceDocuments);
1674
1612
 
1675
1613
  if (contentSourceData.defaultLocaleCode) {
@@ -1720,28 +1658,6 @@ export class ContentStore {
1720
1658
  }
1721
1659
  }
1722
1660
 
1723
- function getCSIDocumentsAndAssetsFromContentSourceDataByIds(
1724
- contentSourceData: ContentSourceData,
1725
- objects: { srcObjectId: string }[]
1726
- ): {
1727
- documents: CSITypes.Document[];
1728
- assets: CSITypes.Asset[];
1729
- } {
1730
- const documents: CSITypes.Document[] = [];
1731
- const assets: CSITypes.Asset[] = [];
1732
- for (const object of objects) {
1733
- if (object.srcObjectId in contentSourceData.csiDocumentMap) {
1734
- documents.push(contentSourceData.csiDocumentMap[object.srcObjectId]!);
1735
- } else if (object.srcObjectId in contentSourceData.csiAssetMap) {
1736
- assets.push(contentSourceData.csiAssetMap[object.srcObjectId]!);
1737
- }
1738
- }
1739
- return {
1740
- documents,
1741
- assets
1742
- };
1743
- }
1744
-
1745
1661
  function internalValidateContent(
1746
1662
  documents: CSITypes.Document[],
1747
1663
  assets: CSITypes.Asset[],
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
- import { Field, FieldListProps, FieldModelProps, FieldObjectProps, FieldSpecificProps, Model, ImageModel } from '@stackbit/sdk';
3
- import { isLocalizedField } from '@stackbit/types';
2
+ import { Model, ImageModel } from '@stackbit/sdk';
3
+ import { Field, FieldSpecificProps, FieldList, FieldModelProps, FieldObjectProps, isLocalizedField } from '@stackbit/types';
4
4
  import * as CSITypes from '@stackbit/types';
5
5
 
6
6
  import * as ContentStoreTypes from '../content-store-types';
@@ -163,6 +163,7 @@ function mapCSIFieldsToStoreFields({
163
163
  const docField = mapCSIFieldToStoreField({
164
164
  csiDocumentField,
165
165
  modelField,
166
+ localized: modelField.localized,
166
167
  context
167
168
  });
168
169
  // Override document field types with specific model field types.
@@ -172,9 +173,6 @@ function mapCSIFieldsToStoreFields({
172
173
  docField.type = modelField.type;
173
174
  }
174
175
  docField.label = modelField.label;
175
- if ('localized' in modelField) {
176
- docField.localized = modelField.localized;
177
- }
178
176
  result[modelField.name] = docField;
179
177
  return result;
180
178
  }, {});
@@ -183,18 +181,24 @@ function mapCSIFieldsToStoreFields({
183
181
  function mapCSIFieldToStoreField({
184
182
  csiDocumentField,
185
183
  modelField,
184
+ localized,
186
185
  context
187
186
  }: {
188
187
  csiDocumentField: CSITypes.DocumentField | undefined;
189
- modelField: FieldSpecificProps;
188
+ modelField: Field | FieldSpecificProps;
189
+ localized?: boolean;
190
190
  context: MapContext;
191
191
  }): ContentStoreTypes.DocumentField {
192
192
  if (!csiDocumentField) {
193
193
  const isUnset = ['object', 'model', 'reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
194
194
  return {
195
195
  type: modelField.type,
196
- ...(isUnset ? { isUnset } : null),
197
- ...(modelField.type === 'list' ? { items: [] } : null)
196
+ ...(localized
197
+ ? { localized, locales: {} }
198
+ : {
199
+ ...(isUnset ? { isUnset } : null),
200
+ ...(modelField.type === 'list' ? { items: [] } : null)
201
+ })
198
202
  } as ContentStoreTypes.DocumentField;
199
203
  }
200
204
  // TODO: check if need to add "options" to "enum" and subtype/min/max to "number"
@@ -204,7 +208,8 @@ function mapCSIFieldToStoreField({
204
208
  case 'model':
205
209
  return mapModelField(csiDocumentField as CSITypes.DocumentModelField, modelField, context);
206
210
  case 'list':
207
- return mapListField(csiDocumentField as CSITypes.DocumentListField, modelField, context);
211
+ // list can not be in list, so modelField must be FieldList
212
+ return mapListField(csiDocumentField as CSITypes.DocumentListField, modelField as FieldList, context);
208
213
  case 'richText':
209
214
  return mapRichTextField(csiDocumentField as CSITypes.DocumentRichTextField);
210
215
  case 'markdown':
@@ -282,7 +287,7 @@ function mapModelField(csiDocumentField: CSITypes.DocumentModelField, modelField
282
287
  };
283
288
  }
284
289
 
285
- function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField: FieldListProps, context: MapContext): ContentStoreTypes.DocumentListField {
290
+ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField: FieldList, context: MapContext): ContentStoreTypes.DocumentListField {
286
291
  if (!isLocalizedField(csiDocumentField)) {
287
292
  return {
288
293
  type: csiDocumentField.type,
@@ -290,8 +295,10 @@ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField:
290
295
  mapCSIFieldToStoreField({
291
296
  csiDocumentField: item,
292
297
  modelField: modelField.items ?? { type: 'string' },
298
+ // list items can not be localized, only the list itself can be localized
299
+ localized: false,
293
300
  context
294
- })
301
+ }) as ContentStoreTypes.DocumentListFieldItems
295
302
  )
296
303
  };
297
304
  }
@@ -305,8 +312,10 @@ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField:
305
312
  mapCSIFieldToStoreField({
306
313
  csiDocumentField: item,
307
314
  modelField: modelField.items ?? { type: 'string' },
315
+ // list items can not be localized, only the list itself can be localized
316
+ localized: false,
308
317
  context
309
- })
318
+ }) as ContentStoreTypes.DocumentListFieldItems
310
319
  )
311
320
  };
312
321
  })
@@ -149,7 +149,7 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
149
149
  const { type, refType, localized, locales, ...base } = docField;
150
150
  const localeProps = locales && locale ? locales[locale] : undefined;
151
151
  // if reference field isUnset === true, it behaves like a regular object
152
- if (!localeProps || localeProps.isUnset) {
152
+ if (!localeProps) {
153
153
  return {
154
154
  type: 'object',
155
155
  isUnset: true,
@@ -186,14 +186,14 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
186
186
  return {
187
187
  ...base,
188
188
  ...localeProps,
189
- items: (localeProps?.items ?? []).map((field) => toLocalizedAPIField(field, locale, true)),
189
+ items: (localeProps?.items ?? []).map((field) => toLocalizedAPIField(field, locale, true) as ContentStoreTypes.DocumentListFieldItemsAPI),
190
190
  ...localeFields(localized)
191
191
  };
192
192
  }
193
193
  return {
194
194
  ...docField,
195
195
  ...localeFields(docField.localized),
196
- items: docField.items.map((field) => toLocalizedAPIField(field, locale, true))
196
+ items: docField.items.map((field) => toLocalizedAPIField(field, locale, true) as ContentStoreTypes.DocumentListFieldItemsAPI)
197
197
  };
198
198
  default:
199
199
  const _exhaustiveCheck: never = docField;