@stackbit/cms-core 0.1.11 → 0.1.12-locale.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.
- package/dist/content-store-types.d.ts +8 -2
- package/dist/content-store-types.d.ts.map +1 -1
- package/dist/content-store-utils.js +1 -1
- package/dist/content-store-utils.js.map +1 -1
- package/dist/content-store.d.ts +11 -4
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +141 -39
- 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 +11 -0
- package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
- package/dist/utils/duplicate-document.d.ts +2 -2
- package/dist/utils/duplicate-document.d.ts.map +1 -1
- package/dist/utils/duplicate-document.js +7 -4
- package/dist/utils/duplicate-document.js.map +1 -1
- package/dist/utils/search-utils.d.ts +1 -0
- package/dist/utils/search-utils.d.ts.map +1 -1
- package/dist/utils/search-utils.js +7 -4
- package/dist/utils/search-utils.js.map +1 -1
- package/dist/utils/store-to-api-docs-converter.d.ts.map +1 -1
- package/dist/utils/store-to-api-docs-converter.js +48 -36
- package/dist/utils/store-to-api-docs-converter.js.map +1 -1
- package/package.json +4 -4
- package/src/content-store-types.ts +3 -2
- package/src/content-store-utils.ts +1 -1
- package/src/content-store.ts +150 -38
- package/src/utils/csi-to-store-docs-converter.ts +11 -0
- package/src/utils/duplicate-document.ts +14 -10
- package/src/utils/search-utils.ts +5 -2
- package/src/utils/store-to-api-docs-converter.ts +46 -37
package/src/content-store.ts
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import sanitizeFilename from 'sanitize-filename';
|
|
4
4
|
|
|
5
5
|
import * as CSITypes from '@stackbit/types';
|
|
6
|
-
import { getLocalizedFieldForLocale, UserCommandSpawner } from '@stackbit/types';
|
|
6
|
+
import { getLocalizedFieldForLocale, ModelExtension, UserCommandSpawner } from '@stackbit/types';
|
|
7
7
|
import {
|
|
8
8
|
Config,
|
|
9
9
|
Field,
|
|
@@ -105,6 +105,7 @@ export class ContentStore {
|
|
|
105
105
|
private stackbitConfig: Config | null = null;
|
|
106
106
|
private yamlModels: Model[] = [];
|
|
107
107
|
private configModels: Model[] = [];
|
|
108
|
+
private modelExtensions: ModelExtension[] | null = null;
|
|
108
109
|
private presets: PresetMap = {};
|
|
109
110
|
|
|
110
111
|
constructor(options: ContentSourceOptions) {
|
|
@@ -152,22 +153,36 @@ export class ContentStore {
|
|
|
152
153
|
|
|
153
154
|
async init({ stackbitConfig }: { stackbitConfig: Config | null }): Promise<void> {
|
|
154
155
|
this.logger.debug('init');
|
|
156
|
+
|
|
157
|
+
this.stackbitConfig = stackbitConfig;
|
|
158
|
+
|
|
155
159
|
if (stackbitConfig) {
|
|
156
|
-
|
|
160
|
+
if (stackbitConfig.modelExtensions) {
|
|
161
|
+
this.modelExtensions = stackbitConfig.modelExtensions;
|
|
162
|
+
} else {
|
|
163
|
+
this.yamlModels = await this.loadYamlModels({ stackbitConfig });
|
|
164
|
+
this.configModels = this.mergeConfigModels(stackbitConfig.models ?? [], this.yamlModels);
|
|
165
|
+
}
|
|
157
166
|
this.presets = await this.loadPresets({ stackbitConfig });
|
|
158
167
|
}
|
|
159
|
-
|
|
168
|
+
|
|
169
|
+
await this.loadContentSourcesAndProcessData({ init: true });
|
|
170
|
+
|
|
160
171
|
this.contentUpdatesWatchTimer.startTimer();
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
async onStackbitConfigChange({ stackbitConfig }: { stackbitConfig: Config | null }) {
|
|
164
175
|
this.logger.debug('onStackbitConfigChange');
|
|
165
|
-
await this.setStackbitConfig({ stackbitConfig });
|
|
166
|
-
}
|
|
167
176
|
|
|
168
|
-
private async setStackbitConfig({ stackbitConfig }: { stackbitConfig: Config | null }) {
|
|
169
177
|
this.stackbitConfig = stackbitConfig;
|
|
170
|
-
|
|
178
|
+
|
|
179
|
+
if (stackbitConfig) {
|
|
180
|
+
if (stackbitConfig.modelExtensions) {
|
|
181
|
+
this.modelExtensions = stackbitConfig.modelExtensions;
|
|
182
|
+
} else {
|
|
183
|
+
this.configModels = this.mergeConfigModels(stackbitConfig.models ?? [], this.yamlModels);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
171
186
|
|
|
172
187
|
await this.loadContentSourcesAndProcessData({ init: true });
|
|
173
188
|
}
|
|
@@ -202,12 +217,16 @@ export class ContentStore {
|
|
|
202
217
|
this.contentUpdatesWatchTimer.startTimer();
|
|
203
218
|
}
|
|
204
219
|
|
|
220
|
+
stop() {
|
|
221
|
+
this.contentUpdatesWatchTimer.stopTimer();
|
|
222
|
+
}
|
|
223
|
+
|
|
205
224
|
async onFilesChange(updatedFiles: string[]): Promise<{ schemaChanged?: boolean; contentChanges: ContentStoreTypes.ContentChangeResult }> {
|
|
206
225
|
this.logger.debug('onFilesChange');
|
|
207
226
|
|
|
208
227
|
let schemaChanged = false;
|
|
209
228
|
|
|
210
|
-
if (this.stackbitConfig) {
|
|
229
|
+
if (this.stackbitConfig && !this.stackbitConfig.modelExtensions) {
|
|
211
230
|
// Check if any of the yaml models files were changed. If yaml model files were changed,
|
|
212
231
|
// reload them and merge them with models defined in stackbit config.
|
|
213
232
|
const modelDirs = getYamlModelDirs(this.stackbitConfig);
|
|
@@ -216,7 +235,7 @@ export class ContentStore {
|
|
|
216
235
|
this.logger.debug('identified change in stackbit model files');
|
|
217
236
|
schemaChanged = true;
|
|
218
237
|
this.yamlModels = await this.loadYamlModels({ stackbitConfig: this.stackbitConfig });
|
|
219
|
-
this.configModels = this.mergeConfigModels(this.stackbitConfig, this.yamlModels);
|
|
238
|
+
this.configModels = this.mergeConfigModels(this.stackbitConfig.models ?? [], this.yamlModels);
|
|
220
239
|
}
|
|
221
240
|
|
|
222
241
|
// Check if any of the preset files were changed. If presets were changed, reload them.
|
|
@@ -287,8 +306,8 @@ export class ContentStore {
|
|
|
287
306
|
return yamlModelsResult.models;
|
|
288
307
|
}
|
|
289
308
|
|
|
290
|
-
private mergeConfigModels(
|
|
291
|
-
const configModelsResult = mergeConfigModelsWithModelsFromFiles(
|
|
309
|
+
private mergeConfigModels(configModels: Model[], modelsFromFiles: Model[]) {
|
|
310
|
+
const configModelsResult = mergeConfigModelsWithModelsFromFiles(configModels, modelsFromFiles);
|
|
292
311
|
for (const error of configModelsResult.errors) {
|
|
293
312
|
this.userLogger.warn(error.message);
|
|
294
313
|
}
|
|
@@ -352,7 +371,7 @@ export class ContentStore {
|
|
|
352
371
|
// update all content sources at once to prevent race conditions
|
|
353
372
|
this.contentSourceDataById = await this.processData({
|
|
354
373
|
stackbitConfig: this.stackbitConfig,
|
|
355
|
-
configModels: this.configModels,
|
|
374
|
+
configModels: (this.modelExtensions as Model[]) ?? this.configModels ?? [],
|
|
356
375
|
presets: this.presets,
|
|
357
376
|
contentSourceRawDataArr: contentSourceRawDataArr
|
|
358
377
|
});
|
|
@@ -613,9 +632,67 @@ export class ContentStore {
|
|
|
613
632
|
presets: Record<string, Preset>;
|
|
614
633
|
contentSourceRawDataArr: ContentSourceRawData[];
|
|
615
634
|
}): Promise<Record<string, ContentSourceData>> {
|
|
635
|
+
// Group models from all content sources by their names
|
|
636
|
+
|
|
637
|
+
const csiModelGroups = contentSourceRawDataArr.reduce((modelGroups: Record<string, CSITypes.ModelWithSource[]>, csData) => {
|
|
638
|
+
return csData.csiModels.reduce((modelGroups, model) => {
|
|
639
|
+
if (!(model.name in modelGroups)) {
|
|
640
|
+
modelGroups[model.name] = [];
|
|
641
|
+
}
|
|
642
|
+
modelGroups[model.name]!.push({
|
|
643
|
+
srcType: csData.srcType,
|
|
644
|
+
srcProjectId: csData.srcProjectId,
|
|
645
|
+
...model
|
|
646
|
+
});
|
|
647
|
+
return modelGroups;
|
|
648
|
+
}, modelGroups);
|
|
649
|
+
}, {});
|
|
650
|
+
|
|
651
|
+
// Match config models to the group of content source models with the same name.
|
|
652
|
+
// Then, match the config model to content source model by comparing srcType and
|
|
653
|
+
// srcProjectId. If after the comparison, there are more than one model left,
|
|
654
|
+
// log a warning and filter out that config model so it won't be merged with any
|
|
655
|
+
// of the content source models.
|
|
656
|
+
const modelMatchErrors: { configModel: ModelExtension; matchedCSIModels: CSITypes.ModelWithSource[] }[] = [];
|
|
657
|
+
const filteredConfigModels = (configModels as ModelExtension[]).filter((configModel) => {
|
|
658
|
+
const csiModels = csiModelGroups[configModel.name];
|
|
659
|
+
if (!csiModels) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const matchedCSIModels = csiModels.filter((model) => {
|
|
663
|
+
const matchesType = !configModel.srcType || model.srcType === configModel.srcType;
|
|
664
|
+
const matchesId = !configModel.srcProjectId || model.srcProjectId === configModel.srcProjectId;
|
|
665
|
+
return matchesType && matchesId;
|
|
666
|
+
});
|
|
667
|
+
if (matchedCSIModels.length === 0) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
if (matchedCSIModels.length === 1) {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
modelMatchErrors.push({
|
|
674
|
+
configModel,
|
|
675
|
+
matchedCSIModels
|
|
676
|
+
});
|
|
677
|
+
return false;
|
|
678
|
+
}) as Model[];
|
|
679
|
+
|
|
680
|
+
// Log model matching warnings using user logger
|
|
681
|
+
for (const { configModel, matchedCSIModels } of modelMatchErrors) {
|
|
682
|
+
let message = `name: '${configModel.name}'`;
|
|
683
|
+
if (configModel.srcType) {
|
|
684
|
+
message += `, srcType: '${configModel.srcType}'`;
|
|
685
|
+
}
|
|
686
|
+
if (configModel.srcProjectId) {
|
|
687
|
+
message += `, srcProjectId: '${configModel.srcProjectId}'`;
|
|
688
|
+
}
|
|
689
|
+
const matchesModelsMessage = matchedCSIModels.map((model) => `srcType: '${model.srcType}', srcProjectId: '${model.srcProjectId}'`).join('; ');
|
|
690
|
+
this.userLogger.warn(`model ${message} defined in stackbit config matches more that 1 model in the following content sources: ${matchesModelsMessage}`);
|
|
691
|
+
}
|
|
692
|
+
|
|
616
693
|
const modelsWithSource = contentSourceRawDataArr.reduce((accum: CSITypes.ModelWithSource[], csData) => {
|
|
617
694
|
const mergedModels = mergeConfigModelsWithExternalModels({
|
|
618
|
-
configModels:
|
|
695
|
+
configModels: filteredConfigModels,
|
|
619
696
|
externalModels: csData.csiModels
|
|
620
697
|
});
|
|
621
698
|
const modelsWithSource = mergedModels.map(
|
|
@@ -708,8 +785,11 @@ export class ContentStore {
|
|
|
708
785
|
);
|
|
709
786
|
}
|
|
710
787
|
|
|
711
|
-
getPresets(): Record<string, any> {
|
|
712
|
-
|
|
788
|
+
getPresets({ locale }: { locale?: string } = {}): Record<string, any> {
|
|
789
|
+
if (!this.presets || !locale) {
|
|
790
|
+
return this.presets ?? {};
|
|
791
|
+
}
|
|
792
|
+
return _.pickBy(this.presets, (preset) => !preset.locale || preset.locale === locale);
|
|
713
793
|
}
|
|
714
794
|
|
|
715
795
|
getContentSourceEnvironment({ srcProjectId, srcType }: { srcProjectId: string; srcType: string }): string {
|
|
@@ -839,11 +919,14 @@ export class ContentStore {
|
|
|
839
919
|
return contentSourceData.documentMap[srcDocumentId];
|
|
840
920
|
}
|
|
841
921
|
|
|
842
|
-
getDocuments(): ContentStoreTypes.Document[] {
|
|
922
|
+
getDocuments({ locale }: { locale?: string }): ContentStoreTypes.Document[] {
|
|
843
923
|
return _.reduce(
|
|
844
924
|
this.contentSourceDataById,
|
|
845
925
|
(documents: ContentStoreTypes.Document[], contentSourceData) => {
|
|
846
|
-
|
|
926
|
+
const currentDocuments = _.isEmpty(locale)
|
|
927
|
+
? contentSourceData.documents
|
|
928
|
+
: contentSourceData.documents.filter(document => !document.locale || document.locale === locale)
|
|
929
|
+
return documents.concat(currentDocuments);
|
|
847
930
|
},
|
|
848
931
|
[]
|
|
849
932
|
);
|
|
@@ -855,23 +938,33 @@ export class ContentStore {
|
|
|
855
938
|
return contentSourceData.assetMap[srcAssetId];
|
|
856
939
|
}
|
|
857
940
|
|
|
858
|
-
getAssets(): ContentStoreTypes.Asset[] {
|
|
941
|
+
getAssets({ locale }: { locale?: string }): ContentStoreTypes.Asset[] {
|
|
859
942
|
return _.reduce(
|
|
860
943
|
this.contentSourceDataById,
|
|
861
944
|
(assets: ContentStoreTypes.Asset[], contentSourceData) => {
|
|
862
|
-
|
|
945
|
+
const currentAssets = _.isEmpty(locale)
|
|
946
|
+
? contentSourceData.assets
|
|
947
|
+
: contentSourceData.assets.filter(asset => !asset.locale || asset.locale === locale)
|
|
948
|
+
return assets.concat(currentAssets);
|
|
863
949
|
},
|
|
864
950
|
[]
|
|
865
951
|
);
|
|
866
952
|
}
|
|
867
953
|
|
|
868
954
|
getLocalizedApiObjects({ locale }: { locale?: string }): ContentStoreTypes.APIObject[] {
|
|
955
|
+
const hasExplicitLocale = !_.isEmpty(locale);
|
|
869
956
|
return _.reduce(
|
|
870
957
|
this.contentSourceDataById,
|
|
871
958
|
(objects: ContentStoreTypes.APIObject[], contentSourceData) => {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
959
|
+
const documents = hasExplicitLocale
|
|
960
|
+
? contentSourceData.documents.filter(document => !document.locale || document.locale === locale)
|
|
961
|
+
: contentSourceData.documents;
|
|
962
|
+
const assets = hasExplicitLocale
|
|
963
|
+
? contentSourceData.assets.filter(asset => !asset.locale || asset.locale === locale)
|
|
964
|
+
: contentSourceData.assets;
|
|
965
|
+
const currentLocale = locale ?? contentSourceData.defaultLocaleCode;
|
|
966
|
+
const documentObjects = mapDocumentsToLocalizedApiObjects(documents, currentLocale);
|
|
967
|
+
const imageObjects = mapAssetsToLocalizedApiImages(assets, currentLocale);
|
|
875
968
|
return objects.concat(documentObjects, imageObjects);
|
|
876
969
|
},
|
|
877
970
|
[]
|
|
@@ -1418,7 +1511,7 @@ export class ContentStore {
|
|
|
1418
1511
|
locale = locale ?? contentSourceData.defaultLocaleCode;
|
|
1419
1512
|
const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
|
|
1420
1513
|
const userContext = getUserContextForSrcType(contentSourceData.srcType, user);
|
|
1421
|
-
const internalValidationErrors = internalValidateContent(documents, assets, contentSourceData);
|
|
1514
|
+
const internalValidationErrors = internalValidateContent(documents, assets, contentSourceData, locale);
|
|
1422
1515
|
const validationResult = await contentSourceData.instance.validateDocuments({ documents, assets, locale, userContext });
|
|
1423
1516
|
errors = errors.concat(
|
|
1424
1517
|
internalValidationErrors,
|
|
@@ -1466,22 +1559,34 @@ export class ContentStore {
|
|
|
1466
1559
|
items: ContentStoreTypes.Document[];
|
|
1467
1560
|
}> {
|
|
1468
1561
|
this.logger.debug('searchDocuments');
|
|
1469
|
-
|
|
1562
|
+
const locale = data.locale;
|
|
1470
1563
|
const objectsBySourceId = _.groupBy(data.models, (object) => getContentSourceId(object.srcType, object.srcProjectId));
|
|
1564
|
+
const contentSourceIds = Object.keys(objectsBySourceId);
|
|
1471
1565
|
const documents: ContentStoreTypes.Document[] = [];
|
|
1472
1566
|
const schema: Record<string, Record<string, Record<string, Model>>> = {};
|
|
1567
|
+
const defaultLocales: Record<string, string> = {};
|
|
1473
1568
|
|
|
1474
|
-
|
|
1569
|
+
contentSourceIds.forEach((contentSourceId) => {
|
|
1475
1570
|
const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
|
|
1476
|
-
|
|
1477
|
-
documents.push(...contentSourceData.documents);
|
|
1571
|
+
|
|
1478
1572
|
_.set(schema, [contentSourceData.srcType, contentSourceData.srcProjectId], contentSourceData.modelMap);
|
|
1573
|
+
|
|
1574
|
+
const contentSourceDocuments = _.isEmpty(locale)
|
|
1575
|
+
? contentSourceData.documents
|
|
1576
|
+
: contentSourceData.documents.filter(document => !document.locale || document.locale === locale)
|
|
1577
|
+
documents.push(...contentSourceDocuments);
|
|
1578
|
+
|
|
1579
|
+
if (contentSourceData.defaultLocaleCode) {
|
|
1580
|
+
defaultLocales[contentSourceId] = contentSourceData.defaultLocaleCode;
|
|
1581
|
+
}
|
|
1479
1582
|
});
|
|
1480
1583
|
|
|
1481
1584
|
return searchDocuments({
|
|
1482
1585
|
...data,
|
|
1483
1586
|
documents,
|
|
1484
|
-
schema
|
|
1587
|
+
schema,
|
|
1588
|
+
locale,
|
|
1589
|
+
defaultLocales
|
|
1485
1590
|
});
|
|
1486
1591
|
}
|
|
1487
1592
|
|
|
@@ -1544,13 +1649,16 @@ function getCSIDocumentsAndAssetsFromContentSourceDataByIds(
|
|
|
1544
1649
|
function internalValidateContent(
|
|
1545
1650
|
documents: CSITypes.Document[],
|
|
1546
1651
|
assets: CSITypes.Asset[],
|
|
1547
|
-
contentSourceData: ContentSourceData
|
|
1652
|
+
contentSourceData: ContentSourceData,
|
|
1653
|
+
locale?: string
|
|
1548
1654
|
): ContentStoreTypes.ValidationError[] {
|
|
1549
1655
|
const errors: ContentStoreTypes.ValidationError[] = [];
|
|
1550
1656
|
_.forEach(documents, (document) => {
|
|
1551
1657
|
_.forEach(document.fields, (documentField, fieldName) => {
|
|
1552
|
-
const localizedField = getLocalizedFieldForLocale(documentField)
|
|
1553
|
-
|
|
1658
|
+
const localizedField = getLocalizedFieldForLocale(documentField, locale);
|
|
1659
|
+
if (localizedField) {
|
|
1660
|
+
errors.push(...validateDocumentFields(document, localizedField, [fieldName], contentSourceData, locale));
|
|
1661
|
+
}
|
|
1554
1662
|
});
|
|
1555
1663
|
});
|
|
1556
1664
|
return errors;
|
|
@@ -1560,19 +1668,24 @@ function validateDocumentFields(
|
|
|
1560
1668
|
document: CSITypes.Document,
|
|
1561
1669
|
documentField: CSITypes.DocumentFieldNonLocalized,
|
|
1562
1670
|
fieldPath: (string | number)[],
|
|
1563
|
-
contentSourceData: ContentSourceData
|
|
1671
|
+
contentSourceData: ContentSourceData,
|
|
1672
|
+
locale?: string
|
|
1564
1673
|
): ContentStoreTypes.ValidationError[] {
|
|
1565
1674
|
const errors: ContentStoreTypes.ValidationError[] = [];
|
|
1566
1675
|
|
|
1567
1676
|
if (documentField.type === 'object') {
|
|
1568
1677
|
_.forEach(documentField.fields, (documentField, fieldName) => {
|
|
1569
|
-
const localizedField = getLocalizedFieldForLocale(documentField)
|
|
1570
|
-
|
|
1678
|
+
const localizedField = getLocalizedFieldForLocale(documentField, locale);
|
|
1679
|
+
if (localizedField) {
|
|
1680
|
+
errors.push(...validateDocumentFields(document, localizedField, fieldPath.concat(fieldName), contentSourceData, locale));
|
|
1681
|
+
}
|
|
1571
1682
|
});
|
|
1572
1683
|
} else if (documentField.type === 'model') {
|
|
1573
1684
|
_.forEach(documentField.fields, (documentField, fieldName) => {
|
|
1574
|
-
const localizedField = getLocalizedFieldForLocale(documentField)
|
|
1575
|
-
|
|
1685
|
+
const localizedField = getLocalizedFieldForLocale(documentField, locale);
|
|
1686
|
+
if (localizedField) {
|
|
1687
|
+
errors.push(...validateDocumentFields(document, localizedField, fieldPath.concat(fieldName), contentSourceData, locale));
|
|
1688
|
+
}
|
|
1576
1689
|
});
|
|
1577
1690
|
} else if (documentField.type === 'reference') {
|
|
1578
1691
|
const objRef = documentField.refType === 'asset' ? contentSourceData.assetMap[documentField.refId] : contentSourceData.documentMap[documentField.refId];
|
|
@@ -1588,8 +1701,7 @@ function validateDocumentFields(
|
|
|
1588
1701
|
}
|
|
1589
1702
|
} else if (documentField.type === 'list') {
|
|
1590
1703
|
_.forEach(documentField.items, (documentField, i) => {
|
|
1591
|
-
|
|
1592
|
-
errors.push(...validateDocumentFields(document, documentField, fieldPath.concat(i), contentSourceData));
|
|
1704
|
+
errors.push(...validateDocumentFields(document, documentField, fieldPath.concat(i), contentSourceData, locale));
|
|
1593
1705
|
});
|
|
1594
1706
|
}
|
|
1595
1707
|
|
|
@@ -48,6 +48,7 @@ function sourceAssetToStoreAsset({
|
|
|
48
48
|
createdBy: csiAsset.createdBy,
|
|
49
49
|
updatedAt: csiAsset.updatedAt,
|
|
50
50
|
updatedBy: csiAsset.updatedBy,
|
|
51
|
+
locale: csiAsset.locale,
|
|
51
52
|
fields: {
|
|
52
53
|
title: {
|
|
53
54
|
label: 'Title',
|
|
@@ -131,6 +132,7 @@ function mapCSIDocumentToStoreDocument({
|
|
|
131
132
|
createdBy: csiDocument.createdBy,
|
|
132
133
|
updatedAt: csiDocument.updatedAt,
|
|
133
134
|
updatedBy: csiDocument.updatedBy,
|
|
135
|
+
locale: csiDocument.locale,
|
|
134
136
|
fields: mapCSIFieldsToStoreFields({
|
|
135
137
|
csiDocumentFields: csiDocument.fields,
|
|
136
138
|
modelFields: model.fields ?? [],
|
|
@@ -163,7 +165,16 @@ function mapCSIFieldsToStoreFields({
|
|
|
163
165
|
modelField,
|
|
164
166
|
context
|
|
165
167
|
});
|
|
168
|
+
// Override document field types with specific model field types.
|
|
169
|
+
// For example when developer re-mapped content-source model "string"
|
|
170
|
+
// field to stackbit "color" field.
|
|
171
|
+
if (modelField.type === 'color' || modelField.type === 'style') {
|
|
172
|
+
docField.type = modelField.type;
|
|
173
|
+
}
|
|
166
174
|
docField.label = modelField.label;
|
|
175
|
+
if ('localized' in modelField) {
|
|
176
|
+
docField.localized = modelField.localized;
|
|
177
|
+
}
|
|
167
178
|
result[modelField.name] = docField;
|
|
168
179
|
return result;
|
|
169
180
|
}, {});
|
|
@@ -14,7 +14,7 @@ export function mergeObjectWithDocument({
|
|
|
14
14
|
duplicatableModels,
|
|
15
15
|
nonDuplicatableModels
|
|
16
16
|
}: {
|
|
17
|
-
object: Record<string,
|
|
17
|
+
object: Record<string, unknown> | undefined;
|
|
18
18
|
document: ContentStoreTypes.Document;
|
|
19
19
|
locale?: string;
|
|
20
20
|
documentMap: Record<string, ContentStoreTypes.Document>;
|
|
@@ -22,7 +22,7 @@ export function mergeObjectWithDocument({
|
|
|
22
22
|
referenceBehavior?: 'copyReference' | 'duplicateContents';
|
|
23
23
|
duplicatableModels?: string[];
|
|
24
24
|
nonDuplicatableModels?: string[];
|
|
25
|
-
}): Record<string,
|
|
25
|
+
}): Record<string, unknown> {
|
|
26
26
|
return mergeObjectWithDocumentFields({
|
|
27
27
|
object,
|
|
28
28
|
documentFields: document.fields,
|
|
@@ -49,9 +49,9 @@ function mergeObjectWithDocumentFields({
|
|
|
49
49
|
documentFields,
|
|
50
50
|
...context
|
|
51
51
|
}: {
|
|
52
|
-
object: Record<string,
|
|
52
|
+
object: Record<string, unknown> | undefined;
|
|
53
53
|
documentFields: Record<string, ContentStoreTypes.DocumentField>;
|
|
54
|
-
} & Context): Record<string,
|
|
54
|
+
} & Context): Record<string, unknown> {
|
|
55
55
|
return _.reduce(
|
|
56
56
|
documentFields,
|
|
57
57
|
(object, documentField, fieldName) => {
|
|
@@ -74,9 +74,9 @@ function mergeObjectWithDocumentField({
|
|
|
74
74
|
documentField,
|
|
75
75
|
...context
|
|
76
76
|
}: {
|
|
77
|
-
value:
|
|
77
|
+
value: unknown;
|
|
78
78
|
documentField: ContentStoreTypes.DocumentField;
|
|
79
|
-
} & Context):
|
|
79
|
+
} & Context): unknown {
|
|
80
80
|
const locale = context.locale;
|
|
81
81
|
switch (documentField.type) {
|
|
82
82
|
case 'string':
|
|
@@ -108,7 +108,7 @@ function mergeObjectWithDocumentField({
|
|
|
108
108
|
}
|
|
109
109
|
case 'image': {
|
|
110
110
|
const localizedField = getDocumentFieldForLocale(documentField, locale);
|
|
111
|
-
if (localizedField && !localizedField.isUnset) {
|
|
111
|
+
if (localizedField && !localizedField.isUnset && isPlainObjectOrUndefined(value)) {
|
|
112
112
|
return mergeObjectWithDocumentFields({
|
|
113
113
|
object: value,
|
|
114
114
|
documentFields: localizedField.fields,
|
|
@@ -119,7 +119,7 @@ function mergeObjectWithDocumentField({
|
|
|
119
119
|
}
|
|
120
120
|
case 'object': {
|
|
121
121
|
const localizedField = getDocumentFieldForLocale(documentField, locale);
|
|
122
|
-
if (localizedField && !localizedField.isUnset) {
|
|
122
|
+
if (localizedField && !localizedField.isUnset && isPlainObjectOrUndefined(value)) {
|
|
123
123
|
return mergeObjectWithDocumentFields({
|
|
124
124
|
object: value,
|
|
125
125
|
documentFields: localizedField.fields,
|
|
@@ -130,7 +130,7 @@ function mergeObjectWithDocumentField({
|
|
|
130
130
|
}
|
|
131
131
|
case 'model': {
|
|
132
132
|
const localizedField = getDocumentFieldForLocale(documentField, locale);
|
|
133
|
-
if (localizedField && !localizedField.isUnset) {
|
|
133
|
+
if (localizedField && !localizedField.isUnset && isPlainObjectOrUndefined(value)) {
|
|
134
134
|
if (value && value.$$type !== localizedField.srcModelName) {
|
|
135
135
|
// if the override object's $$type isn't equal to the type
|
|
136
136
|
// of the current object in the field, then use whatever
|
|
@@ -150,7 +150,7 @@ function mergeObjectWithDocumentField({
|
|
|
150
150
|
}
|
|
151
151
|
case 'reference': {
|
|
152
152
|
const localizedField = getDocumentFieldForLocale(documentField, locale);
|
|
153
|
-
if (localizedField && !localizedField.isUnset) {
|
|
153
|
+
if (localizedField && !localizedField.isUnset && isPlainObjectOrUndefined(value)) {
|
|
154
154
|
if (value && value.$$ref) {
|
|
155
155
|
// if the override object has $$ref, use it
|
|
156
156
|
break;
|
|
@@ -230,6 +230,10 @@ function mergeObjectWithDocumentField({
|
|
|
230
230
|
return value;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
function isPlainObjectOrUndefined(value: unknown): value is Record<string, unknown> | undefined {
|
|
234
|
+
return typeof value === 'undefined' || _.isPlainObject(value);
|
|
235
|
+
}
|
|
236
|
+
|
|
233
237
|
function shouldDuplicate({
|
|
234
238
|
referenceField,
|
|
235
239
|
modelName,
|
|
@@ -5,6 +5,7 @@ import { getLocalizedFieldForLocale } from '@stackbit/types';
|
|
|
5
5
|
|
|
6
6
|
import { SearchFilter, SearchFilterItem } from '../types/search-filter';
|
|
7
7
|
import { ContentStoreTypes } from '..';
|
|
8
|
+
import { getContentSourceId } from '../content-store-utils';
|
|
8
9
|
|
|
9
10
|
const META_FIELD = {
|
|
10
11
|
createdAt: {
|
|
@@ -28,6 +29,7 @@ export const searchDocuments = (data: {
|
|
|
28
29
|
documents: ContentStoreTypes.Document[];
|
|
29
30
|
schema: Schema;
|
|
30
31
|
locale?: string;
|
|
32
|
+
defaultLocales?: Record<string, string>
|
|
31
33
|
}): {
|
|
32
34
|
total: number;
|
|
33
35
|
items: ContentStoreTypes.Document[];
|
|
@@ -39,6 +41,7 @@ export const searchDocuments = (data: {
|
|
|
39
41
|
let allDocuments = 0;
|
|
40
42
|
|
|
41
43
|
const matchedDocuments = documents.filter((document) => {
|
|
44
|
+
const contentSourceId = getContentSourceId(document.srcType, document.srcProjectId);
|
|
42
45
|
const isIncludedModel = _.find(data.models, {
|
|
43
46
|
srcType: document.srcType,
|
|
44
47
|
srcProjectId: document.srcProjectId,
|
|
@@ -51,7 +54,7 @@ export const searchDocuments = (data: {
|
|
|
51
54
|
allDocuments += 1;
|
|
52
55
|
|
|
53
56
|
if (query) {
|
|
54
|
-
const matches = isDocumentMatchesPattern(document, query, data.locale);
|
|
57
|
+
const matches = isDocumentMatchesPattern(document, query, data.locale ?? data.defaultLocales?.[contentSourceId]);
|
|
55
58
|
if (!matches) {
|
|
56
59
|
return false;
|
|
57
60
|
}
|
|
@@ -61,7 +64,7 @@ export const searchDocuments = (data: {
|
|
|
61
64
|
// only 'and' supported for now; later we can add e.g. 'or'
|
|
62
65
|
const matches = data.filter.and.every((filter) => {
|
|
63
66
|
const field = getFieldForFilter(document, filter);
|
|
64
|
-
return isFieldMatchesFilter({ field, filter, document, schema, locale: data.locale });
|
|
67
|
+
return isFieldMatchesFilter({ field, filter, document, schema, locale: data.locale ?? data.defaultLocales?.[contentSourceId] });
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
if (!matches) {
|