@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.
- package/dist/content-store-types.d.ts +27 -3
- package/dist/content-store-types.d.ts.map +1 -1
- package/dist/content-store-utils.d.ts +12 -1
- package/dist/content-store-utils.d.ts.map +1 -1
- package/dist/content-store-utils.js +44 -1
- package/dist/content-store-utils.js.map +1 -1
- package/dist/content-store.d.ts +6 -16
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +71 -126
- package/dist/content-store.js.map +1 -1
- package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
- package/dist/utils/csi-to-store-docs-converter.js +13 -6
- package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
- package/dist/utils/store-to-api-docs-converter.js +1 -1
- package/dist/utils/store-to-api-docs-converter.js.map +1 -1
- package/dist/utils/store-to-csi-docs-converter.d.ts +4 -0
- package/dist/utils/store-to-csi-docs-converter.d.ts.map +1 -0
- package/dist/utils/store-to-csi-docs-converter.js +211 -0
- package/dist/utils/store-to-csi-docs-converter.js.map +1 -0
- package/package.json +5 -5
- package/src/content-store-types.ts +41 -4
- package/src/content-store-utils.ts +61 -3
- package/src/content-store.ts +99 -183
- package/src/utils/csi-to-store-docs-converter.ts +21 -12
- package/src/utils/store-to-api-docs-converter.ts +3 -3
- package/src/utils/store-to-csi-docs-converter.ts +222 -0
- package/dist/utils/preset-utils.d.ts +0 -4
- package/dist/utils/preset-utils.d.ts.map +0 -1
- package/dist/utils/preset-utils.js +0 -37
- package/dist/utils/preset-utils.js.map +0 -1
- package/src/utils/preset-utils.ts +0 -33
package/src/content-store.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
|
214
|
+
if (presetsChanged) {
|
|
248
215
|
this.logger.debug('identified change in stackbit preset files');
|
|
249
216
|
schemaChanged = true;
|
|
250
|
-
this.presets = await this.
|
|
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
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
1672
|
-
|
|
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 {
|
|
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
|
-
...(
|
|
197
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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;
|