@stackbit/cms-core 0.1.10 → 0.1.12-locale.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.
@@ -0,0 +1,254 @@
1
+ import _ from 'lodash';
2
+
3
+ import * as ContentStoreTypes from '../content-store-types';
4
+ import { getDocumentFieldForLocale } from '../content-store-utils';
5
+ import { IMAGE_MODEL } from '../common/common-schema';
6
+
7
+ export function mergeObjectWithDocument({
8
+ object,
9
+ document,
10
+ locale,
11
+ documentMap,
12
+ seenReferences = [],
13
+ referenceBehavior,
14
+ duplicatableModels,
15
+ nonDuplicatableModels
16
+ }: {
17
+ object: Record<string, any> | undefined;
18
+ document: ContentStoreTypes.Document;
19
+ locale?: string;
20
+ documentMap: Record<string, ContentStoreTypes.Document>;
21
+ seenReferences?: string[];
22
+ referenceBehavior?: 'copyReference' | 'duplicateContents';
23
+ duplicatableModels?: string[];
24
+ nonDuplicatableModels?: string[];
25
+ }): Record<string, any> {
26
+ return mergeObjectWithDocumentFields({
27
+ object,
28
+ documentFields: document.fields,
29
+ locale,
30
+ documentMap,
31
+ seenReferences: seenReferences.concat(document.srcObjectId),
32
+ referenceBehavior,
33
+ duplicatableModels,
34
+ nonDuplicatableModels
35
+ });
36
+ }
37
+
38
+ type Context = {
39
+ locale?: string;
40
+ documentMap: Record<string, ContentStoreTypes.Document>;
41
+ seenReferences: string[];
42
+ referenceBehavior?: 'copyReference' | 'duplicateContents';
43
+ duplicatableModels?: string[];
44
+ nonDuplicatableModels?: string[];
45
+ };
46
+
47
+ function mergeObjectWithDocumentFields({
48
+ object,
49
+ documentFields,
50
+ ...context
51
+ }: {
52
+ object: Record<string, any> | undefined;
53
+ documentFields: Record<string, ContentStoreTypes.DocumentField>;
54
+ } & Context): Record<string, any> {
55
+ return _.reduce(
56
+ documentFields,
57
+ (object, documentField, fieldName) => {
58
+ const value = mergeObjectWithDocumentField({
59
+ value: object[fieldName],
60
+ documentField,
61
+ ...context
62
+ });
63
+ if (typeof value !== 'undefined') {
64
+ object[fieldName] = value;
65
+ }
66
+ return object;
67
+ },
68
+ Object.assign({}, object)
69
+ );
70
+ }
71
+
72
+ function mergeObjectWithDocumentField({
73
+ value,
74
+ documentField,
75
+ ...context
76
+ }: {
77
+ value: any;
78
+ documentField: ContentStoreTypes.DocumentField;
79
+ } & Context): any {
80
+ const locale = context.locale;
81
+ switch (documentField.type) {
82
+ case 'string':
83
+ case 'text':
84
+ case 'html':
85
+ case 'slug':
86
+ case 'url':
87
+ case 'color':
88
+ case 'boolean':
89
+ case 'number':
90
+ case 'date':
91
+ case 'datetime':
92
+ case 'enum':
93
+ case 'file':
94
+ case 'json':
95
+ case 'style':
96
+ case 'markdown':
97
+ case 'richText': {
98
+ // if a value was provided explicitly, use it to override the value
99
+ // of the matching field in the duplicated document.
100
+ if (typeof value !== 'undefined') {
101
+ return value;
102
+ }
103
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
104
+ if (localizedField) {
105
+ return localizedField.value;
106
+ }
107
+ break;
108
+ }
109
+ case 'image': {
110
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
111
+ if (localizedField && !localizedField.isUnset) {
112
+ return mergeObjectWithDocumentFields({
113
+ object: value,
114
+ documentFields: localizedField.fields,
115
+ ...context
116
+ });
117
+ }
118
+ break;
119
+ }
120
+ case 'object': {
121
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
122
+ if (localizedField && !localizedField.isUnset) {
123
+ return mergeObjectWithDocumentFields({
124
+ object: value,
125
+ documentFields: localizedField.fields,
126
+ ...context
127
+ });
128
+ }
129
+ break;
130
+ }
131
+ case 'model': {
132
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
133
+ if (localizedField && !localizedField.isUnset) {
134
+ if (value && value.$$type !== localizedField.srcModelName) {
135
+ // if the override object's $$type isn't equal to the type
136
+ // of the current object in the field, then use whatever
137
+ // was passed in the object.
138
+ break;
139
+ }
140
+ return {
141
+ $$type: localizedField.srcModelName,
142
+ ...mergeObjectWithDocumentFields({
143
+ object: value,
144
+ documentFields: localizedField.fields,
145
+ ...context
146
+ })
147
+ };
148
+ }
149
+ break;
150
+ }
151
+ case 'reference': {
152
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
153
+ if (localizedField && !localizedField.isUnset) {
154
+ if (value && value.$$ref) {
155
+ // if the override object has $$ref, use it
156
+ break;
157
+ }
158
+ if (localizedField.refType === 'asset') {
159
+ return {
160
+ $$ref: localizedField.refId
161
+ };
162
+ } else {
163
+ const document = context.documentMap[localizedField.refId];
164
+ if (!document) {
165
+ break;
166
+ }
167
+ const shouldDuplicateDocument = shouldDuplicate({
168
+ referenceField: localizedField,
169
+ modelName: document.srcModelName,
170
+ seenReferences: context.seenReferences,
171
+ referenceBehavior: context.referenceBehavior,
172
+ duplicatableModels: context.duplicatableModels,
173
+ nonDuplicatableModels: context.nonDuplicatableModels
174
+ });
175
+ if (shouldDuplicateDocument || (value && value.$$type)) {
176
+ if (value && value.$$type !== document.srcModelName) {
177
+ // if the override object has $$type different from
178
+ // the type of the document that is currently
179
+ // referenced in the field, then create and link a
180
+ // new document according to the provided object
181
+ break;
182
+ }
183
+ return {
184
+ $$type: document.srcModelName,
185
+ ...mergeObjectWithDocument({
186
+ object: value,
187
+ document,
188
+ locale: context.locale,
189
+ documentMap: context.documentMap,
190
+ seenReferences: context.seenReferences.concat(document.srcObjectId),
191
+ referenceBehavior: context.referenceBehavior,
192
+ duplicatableModels: context.duplicatableModels,
193
+ nonDuplicatableModels: context.nonDuplicatableModels
194
+ })
195
+ };
196
+ } else {
197
+ return {
198
+ $$ref: localizedField.refId
199
+ };
200
+ }
201
+ }
202
+ }
203
+ break;
204
+ }
205
+ case 'list': {
206
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
207
+ if (value) {
208
+ // do not try to merge provided list items with existing items
209
+ // because array items can not be matched by names like fields do.
210
+ break;
211
+ }
212
+ if (localizedField) {
213
+ return (localizedField.items ?? []).map((field) =>
214
+ mergeObjectWithDocumentField({
215
+ value: undefined,
216
+ documentField: field,
217
+ ...context
218
+ })
219
+ ).filter((value) => typeof value !== 'undefined') // if locale passed, it may return undefined for items localized to a different locale;
220
+ }
221
+ break;
222
+ }
223
+ default: {
224
+ const _exhaustiveCheck: never = documentField;
225
+ return _exhaustiveCheck;
226
+ }
227
+ }
228
+ // if the document value was not set and no merging occurred use the value
229
+ // that was provided to override the field in the duplicated document
230
+ return value;
231
+ }
232
+
233
+ function shouldDuplicate({
234
+ referenceField,
235
+ modelName,
236
+ seenReferences,
237
+ referenceBehavior,
238
+ nonDuplicatableModels,
239
+ duplicatableModels
240
+ }: {
241
+ referenceField: ContentStoreTypes.DocumentReferenceFieldNonLocalized & { isUnset?: false };
242
+ modelName: string;
243
+ seenReferences: string[];
244
+ referenceBehavior?: 'copyReference' | 'duplicateContents';
245
+ nonDuplicatableModels?: string[];
246
+ duplicatableModels?: string[];
247
+ }) {
248
+ return (
249
+ !seenReferences.includes(referenceField.refId) &&
250
+ modelName !== IMAGE_MODEL.name &&
251
+ ((referenceBehavior === 'duplicateContents' && !(nonDuplicatableModels ?? []).includes(modelName)) ||
252
+ (referenceBehavior === 'copyReference' && (duplicatableModels ?? []).includes(modelName)))
253
+ );
254
+ }
@@ -22,16 +22,20 @@ function toLocalizedAPIFields(docFields: Record<string, ContentStoreTypes.Docume
22
22
 
23
23
  function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?: string, isListItem = false): ContentStoreTypes.DocumentFieldAPI {
24
24
  type ToBoolean<T extends boolean | undefined> = T extends true ? true : false;
25
- function localeFields<T extends boolean | undefined>(
26
- localized: T,
27
- _locale: string | undefined
28
- ): null | { localized: ToBoolean<T>; locale: string | undefined } {
29
- return isListItem
30
- ? null
31
- : {
32
- localized: !!localized as ToBoolean<T>,
33
- locale: locale ?? _locale
34
- };
25
+ function localeFields<T extends boolean | undefined>(localized: T): null | { localized: ToBoolean<T>; locale?: string } {
26
+ const isLocalized = !!localized as ToBoolean<T>;
27
+ if (isListItem) {
28
+ return null;
29
+ }
30
+ if (!isLocalized) {
31
+ return {
32
+ localized: isLocalized
33
+ };
34
+ }
35
+ return {
36
+ localized: isLocalized,
37
+ locale: locale
38
+ };
35
39
  }
36
40
  switch (docField.type) {
37
41
  case 'string':
@@ -52,38 +56,52 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
52
56
  case 'richText':
53
57
  if (docField.localized) {
54
58
  const { localized, locales, ...base } = docField;
55
- const localeProps = locale ? locales[locale] : undefined;
59
+ const localeProps = locales && locale ? locales[locale] : undefined;
56
60
  return {
57
61
  ...base,
58
62
  ...(localeProps ?? { value: null }),
59
- ...localeFields(localized, locale),
63
+ ...localeFields(localized),
60
64
  ...(['file', 'json', 'markdown', 'richText'].includes(docField.type) && !localeProps ? { isUnset: true } : null)
61
65
  };
62
66
  }
63
67
  return {
64
68
  ...docField,
65
- ...localeFields(docField.localized, docField.locale)
69
+ ...localeFields(docField.localized)
66
70
  };
67
71
  case 'image':
68
72
  if (docField.localized) {
69
73
  const { localized, locales, ...base } = docField;
70
- const localeProps = locale ? locales[locale] : undefined;
74
+ const localeProps = locales && locale ? locales[locale] : undefined;
71
75
  return {
72
76
  ...base,
73
- ...(localeProps ?? { isUnset: true }),
74
- ...localeFields(localized, locale)
77
+ ...(localeProps
78
+ ? {
79
+ ...localeProps,
80
+ fields: toLocalizedAPIFields(localeProps.fields, locale) as ContentStoreTypes.ImageFieldsAPI
81
+ }
82
+ : { isUnset: true }),
83
+ ...localeFields(localized)
75
84
  };
76
85
  }
77
86
  return {
78
- ...docField,
79
- ...localeFields(docField.localized, docField.locale)
87
+ ...(!docField.isUnset
88
+ ? {
89
+ ...docField,
90
+ type: 'image',
91
+ fields: toLocalizedAPIFields(docField.fields, locale) as ContentStoreTypes.ImageFieldsAPI
92
+ }
93
+ : {
94
+ ...docField,
95
+ type: 'image'
96
+ }),
97
+ ...localeFields(docField.localized)
80
98
  };
81
99
  case 'object':
82
100
  case 'model':
83
101
  if (docField.localized) {
84
102
  if (docField.type === 'object') {
85
103
  const { localized, locales, ...base } = docField;
86
- const localeProps = locale ? locales[locale] : undefined;
104
+ const localeProps = locales && locale ? locales[locale] : undefined;
87
105
  return {
88
106
  ...base,
89
107
  ...(localeProps
@@ -92,11 +110,11 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
92
110
  fields: toLocalizedAPIFields(localeProps.fields, locale)
93
111
  }
94
112
  : { isUnset: true }),
95
- ...localeFields(localized, locale)
113
+ ...localeFields(localized)
96
114
  };
97
115
  } else {
98
116
  const { localized, locales, ...base } = docField;
99
- const localeProps = locale ? locales[locale] : undefined;
117
+ const localeProps = locales && locale ? locales[locale] : undefined;
100
118
  return {
101
119
  ...base,
102
120
  type: 'object',
@@ -106,7 +124,7 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
106
124
  fields: toLocalizedAPIFields(localeProps.fields, locale)
107
125
  }
108
126
  : { isUnset: true }),
109
- ...localeFields(localized, locale)
127
+ ...localeFields(localized)
110
128
  };
111
129
  }
112
130
  }
@@ -121,19 +139,19 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
121
139
  ...docField,
122
140
  type: 'object'
123
141
  }),
124
- ...localeFields(docField.localized, docField.locale)
142
+ ...localeFields(docField.localized)
125
143
  };
126
144
  case 'reference':
127
145
  if (docField.localized) {
128
146
  const { type, refType, localized, locales, ...base } = docField;
129
- const localeProps = locale ? locales[locale] : undefined;
147
+ const localeProps = locales && locale ? locales[locale] : undefined;
130
148
  // if reference field isUnset === true, it behaves like a regular object
131
149
  if (!localeProps || localeProps.isUnset) {
132
150
  return {
133
151
  type: 'object',
134
152
  isUnset: true,
135
153
  ...base,
136
- ...localeFields(localized, locale)
154
+ ...localeFields(localized)
137
155
  };
138
156
  }
139
157
  return {
@@ -141,7 +159,7 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
141
159
  refType: refType === 'asset' ? 'image' : 'object',
142
160
  ...base,
143
161
  ...localeProps,
144
- ...localeFields(localized, locale)
162
+ ...localeFields(localized)
145
163
  };
146
164
  }
147
165
  const { type, refType, ...base } = docField;
@@ -149,29 +167,29 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
149
167
  return {
150
168
  type: 'object',
151
169
  ...base,
152
- ...localeFields(docField.localized, docField.locale)
170
+ ...localeFields(docField.localized)
153
171
  };
154
172
  }
155
173
  return {
156
174
  type: 'unresolved_reference',
157
175
  refType: refType === 'asset' ? 'image' : 'object',
158
176
  ...base,
159
- ...localeFields(docField.localized, docField.locale)
177
+ ...localeFields(docField.localized)
160
178
  };
161
179
  case 'list':
162
180
  if (docField.localized) {
163
181
  const { localized, locales, ...base } = docField;
164
- const localeProps = locale ? locales[locale] : undefined;
182
+ const localeProps = locales && locale ? locales[locale] : undefined;
165
183
  return {
166
184
  ...base,
167
185
  ...localeProps,
168
186
  items: (localeProps?.items ?? []).map((field) => toLocalizedAPIField(field, locale, true)),
169
- ...localeFields(localized, locale)
187
+ ...localeFields(localized)
170
188
  };
171
189
  }
172
190
  return {
173
191
  ...docField,
174
- ...localeFields(docField.localized, docField.locale),
192
+ ...localeFields(docField.localized),
175
193
  items: docField.items.map((field) => toLocalizedAPIField(field, locale, true))
176
194
  };
177
195
  default:
@@ -207,16 +225,15 @@ function localizeAssetFields(assetFields: ContentStoreTypes.AssetFields, locale?
207
225
  };
208
226
  const titleFieldNonLocalized = getDocumentFieldForLocale(assetFields.title, locale);
209
227
  fields.title.value = titleFieldNonLocalized?.value;
210
- fields.title.locale = locale ?? titleFieldNonLocalized?.locale;
228
+ fields.title.locale = locale;
211
229
  const assetFileField = assetFields.file;
212
230
  if (assetFileField.localized) {
213
231
  if (locale) {
214
- fields.url.value = assetFileField.locales[locale]?.url ?? null;
232
+ fields.url.value = assetFileField.locales?.[locale]?.url ?? null;
215
233
  fields.url.locale = locale;
216
234
  }
217
235
  } else {
218
236
  fields.url.value = assetFileField.url;
219
- fields.url.locale = assetFileField.locale;
220
237
  }
221
238
  return fields;
222
239
  }
@@ -227,9 +244,9 @@ export function mapStoreAssetsToAPIAssets(assets: ContentStoreTypes.Asset[], loc
227
244
 
228
245
  function storeAssetToAPIAsset(asset: ContentStoreTypes.Asset, locale?: string): ContentStoreTypes.APIAsset {
229
246
  const assetTitleField = asset.fields.title;
230
- const localizedTitleField = assetTitleField.localized ? assetTitleField.locales[locale!]! : assetTitleField;
247
+ const localizedTitleField = assetTitleField.localized ? assetTitleField.locales?.[locale!]! : assetTitleField;
231
248
  const assetFileField = asset.fields.file;
232
- const localizedFileField = assetFileField.localized ? assetFileField.locales[locale!]! : assetFileField;
249
+ const localizedFileField = assetFileField.localized ? assetFileField.locales?.[locale!]! : assetFileField;
233
250
  return {
234
251
  objectId: asset.srcObjectId,
235
252
  createdAt: asset.createdAt,