@stackbit/cms-core 0.7.6-develop.1 → 0.8.0-develop.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/.tsbuildinfo +1 -1
- package/dist/content-store-utils.d.ts +44 -0
- package/dist/content-store-utils.d.ts.map +1 -1
- package/dist/content-store-utils.js +213 -1
- package/dist/content-store-utils.js.map +1 -1
- package/dist/content-store.d.ts +14 -1
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +70 -6
- package/dist/content-store.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/types/content-store-document-fields.d.ts +105 -0
- package/dist/types/content-store-document-fields.d.ts.map +1 -1
- package/dist/types/content-store-documents.d.ts +4 -1
- package/dist/types/content-store-documents.d.ts.map +1 -1
- package/dist/types/content-store-types.d.ts +10 -10
- package/dist/types/content-store-types.d.ts.map +1 -1
- package/dist/types/custom-actions.d.ts +108 -0
- package/dist/types/custom-actions.d.ts.map +1 -0
- package/dist/types/custom-actions.js +3 -0
- package/dist/types/custom-actions.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/csi-to-store-docs-converter.d.ts +2 -1
- package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
- package/dist/utils/csi-to-store-docs-converter.js +144 -11
- package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
- package/dist/utils/custom-actions.d.ts +53 -0
- package/dist/utils/custom-actions.d.ts.map +1 -0
- package/dist/utils/custom-actions.js +564 -0
- package/dist/utils/custom-actions.js.map +1 -0
- package/dist/utils/document-hooks.d.ts +9 -0
- package/dist/utils/document-hooks.d.ts.map +1 -1
- package/dist/utils/document-hooks.js +3 -2
- package/dist/utils/document-hooks.js.map +1 -1
- package/dist/utils/search-utils.d.ts +1 -1
- package/dist/utils/search-utils.d.ts.map +1 -1
- package/dist/utils/site-map.d.ts +4 -4
- package/dist/utils/site-map.d.ts.map +1 -1
- package/dist/utils/site-map.js +2 -11
- package/dist/utils/site-map.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 +77 -29
- package/dist/utils/store-to-api-docs-converter.js.map +1 -1
- package/dist/utils/store-to-csi-docs-converter.d.ts +3 -0
- package/dist/utils/store-to-csi-docs-converter.d.ts.map +1 -1
- package/dist/utils/store-to-csi-docs-converter.js +2 -1
- package/dist/utils/store-to-csi-docs-converter.js.map +1 -1
- package/dist/utils/tree-views.d.ts +10 -0
- package/dist/utils/tree-views.d.ts.map +1 -0
- package/dist/utils/tree-views.js +64 -0
- package/dist/utils/tree-views.js.map +1 -0
- package/package.json +5 -5
- package/src/content-store-utils.ts +247 -0
- package/src/content-store.ts +90 -7
- package/src/index.ts +0 -1
- package/src/types/content-store-document-fields.ts +55 -2
- package/src/types/content-store-documents.ts +4 -1
- package/src/types/content-store-types.ts +10 -10
- package/src/types/custom-actions.ts +154 -0
- package/src/types/index.ts +1 -0
- package/src/utils/csi-to-store-docs-converter.ts +220 -12
- package/src/utils/custom-actions.ts +707 -0
- package/src/utils/document-hooks.ts +3 -3
- package/src/utils/search-utils.ts +1 -1
- package/src/utils/site-map.ts +4 -14
- package/src/utils/store-to-api-docs-converter.ts +88 -33
- package/src/utils/store-to-csi-docs-converter.ts +4 -4
- package/src/utils/tree-views.ts +72 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSanitizedTreeViews = void 0;
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const content_store_utils_1 = require("../content-store-utils");
|
|
9
|
+
async function getSanitizedTreeViews({ configDelegate, stackbitConfig, contentSourceDataById, logger }) {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
const treeViews = (_b = (await ((_a = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.treeViews) === null || _a === void 0 ? void 0 : _a.call(stackbitConfig, configDelegate)))) !== null && _b !== void 0 ? _b : [];
|
|
12
|
+
const mapTreeViews = (treeViews) => treeViews
|
|
13
|
+
.map((treeView) => {
|
|
14
|
+
var _a, _b, _c, _d, _e, _f;
|
|
15
|
+
if ('document' in treeView && treeView.document) {
|
|
16
|
+
const contentSourceId = (0, content_store_utils_1.getContentSourceId)(treeView.document.srcType, treeView.document.srcProjectId);
|
|
17
|
+
const document = (_a = contentSourceDataById[contentSourceId]) === null || _a === void 0 ? void 0 : _a.documentMap[treeView.document.id];
|
|
18
|
+
// explicit check because developers can skip TS check and just not define required properties of document object
|
|
19
|
+
if (document) {
|
|
20
|
+
treeView = {
|
|
21
|
+
...treeView,
|
|
22
|
+
stableId: (_b = treeView.stableId) !== null && _b !== void 0 ? _b : document.srcObjectId,
|
|
23
|
+
label: (_c = treeView.label) !== null && _c !== void 0 ? _c : document.getPreview({ delegate: configDelegate }).previewTitle,
|
|
24
|
+
document: {
|
|
25
|
+
srcType: treeView.document.srcType,
|
|
26
|
+
srcProjectId: treeView.document.srcProjectId,
|
|
27
|
+
id: treeView.document.id
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
logger.warn(`One of required properties is missing in tree view document, document skipped from tree view,
|
|
33
|
+
srcType: ${(_d = treeView.document) === null || _d === void 0 ? void 0 : _d.srcType}, srcProjectId: ${(_e = treeView.document) === null || _e === void 0 ? void 0 : _e.srcProjectId}, id: ${(_f = treeView.document) === null || _f === void 0 ? void 0 : _f.id}.`);
|
|
34
|
+
// don't early return because treeView.children can be defined properly
|
|
35
|
+
if (treeView.children) {
|
|
36
|
+
treeView = lodash_1.default.omit(treeView, 'document');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if ('children' in treeView && treeView.children) {
|
|
44
|
+
// required check because of
|
|
45
|
+
// 1. typecast during omit in previous if
|
|
46
|
+
// 2. developer can ignore TS check and just not define stableId and label
|
|
47
|
+
if (treeView.stableId && treeView.label) {
|
|
48
|
+
treeView = {
|
|
49
|
+
...treeView,
|
|
50
|
+
children: mapTreeViews(treeView.children)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
logger.warn('one of required properties (stableId or label) is missing in tree view, children are skipped from tree view');
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return treeView;
|
|
59
|
+
})
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
return mapTreeViews(treeViews);
|
|
62
|
+
}
|
|
63
|
+
exports.getSanitizedTreeViews = getSanitizedTreeViews;
|
|
64
|
+
//# sourceMappingURL=tree-views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-views.js","sourceRoot":"","sources":["../../src/utils/tree-views.ts"],"names":[],"mappings":";;;;;;AAAA,oDAAuB;AAIvB,gEAA4D;AAErD,KAAK,UAAU,qBAAqB,CAAC,EACxC,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,MAAM,EAMT;;IACG,MAAM,SAAS,GAAG,MAAA,CAAC,MAAM,CAAA,MAAA,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,SAAS,+CAAzB,cAAc,EAAc,cAAc,CAAC,CAAA,CAAC,mCAAI,EAAE,CAAC;IAE5E,MAAM,YAAY,GAAG,CAAC,SAAkC,EAA2B,EAAE,CACjF,SAAS;SACJ,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;;QACd,IAAI,UAAU,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE;YAC7C,MAAM,eAAe,GAAG,IAAA,wCAAkB,EAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YACtG,MAAM,QAAQ,GAAG,MAAA,qBAAqB,CAAC,eAAe,CAAC,0CAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAE3F,iHAAiH;YACjH,IAAI,QAAQ,EAAE;gBACV,QAAQ,GAAG;oBACP,GAAG,QAAQ;oBACX,QAAQ,EAAE,MAAA,QAAQ,CAAC,QAAQ,mCAAI,QAAQ,CAAC,WAAW;oBACnD,KAAK,EAAE,MAAA,QAAQ,CAAC,KAAK,mCAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,YAAY;oBACvF,QAAQ,EAAE;wBACN,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO;wBAClC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY;wBAC5C,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE;qBAC3B;iBACJ,CAAC;aACL;iBAAM;gBACH,MAAM,CAAC,IAAI,CACP;uCACW,MAAA,QAAQ,CAAC,QAAQ,0CAAE,OAAO,mBAAmB,MAAA,QAAQ,CAAC,QAAQ,0CAAE,YAAY,SAAS,MAAA,QAAQ,CAAC,QAAQ,0CAAE,EAAE,GAAG,CAC3H,CAAC;gBACF,uEAAuE;gBACvE,IAAI,QAAQ,CAAC,QAAQ,EAAE;oBACnB,QAAQ,GAAG,gBAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAA8B,CAAC;iBACxE;qBAAM;oBACH,OAAO,SAAS,CAAC;iBACpB;aACJ;SACJ;QAED,IAAI,UAAU,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE;YAC7C,4BAA4B;YAC5B,yCAAyC;YACzC,0EAA0E;YAC1E,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,EAAE;gBACrC,QAAQ,GAAG;oBACP,GAAG,QAAQ;oBACX,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;iBAC5C,CAAC;aACL;iBAAM;gBACH,MAAM,CAAC,IAAI,CAAC,6GAA6G,CAAC,CAAC;gBAC3H,OAAO,SAAS,CAAC;aACpB;SACJ;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAA4B,CAAC;IAEpD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAjED,sDAiEC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackbit/cms-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-develop.0",
|
|
4
4
|
"description": "stackbit-dev",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"@babel/parser": "^7.11.5",
|
|
26
26
|
"@babel/traverse": "^7.11.5",
|
|
27
27
|
"@iarna/toml": "^2.2.3",
|
|
28
|
-
"@stackbit/sdk": "0.
|
|
29
|
-
"@stackbit/types": "0.
|
|
30
|
-
"@stackbit/utils": "0.
|
|
28
|
+
"@stackbit/sdk": "0.7.0-develop.0",
|
|
29
|
+
"@stackbit/types": "0.9.0-develop.0",
|
|
30
|
+
"@stackbit/utils": "0.3.0-develop.0",
|
|
31
31
|
"chalk": "^4.0.1",
|
|
32
32
|
"esm": "^3.2.25",
|
|
33
33
|
"fs-extra": "^8.1.0",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"slugify": "^1.6.5",
|
|
43
43
|
"uuid": "^9.0.0"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "817724f480726a53c0b2708a23d5c752965a87b4"
|
|
46
46
|
}
|
|
@@ -14,6 +14,23 @@ export function getContentSourceId(contentSourceType: string, srcProjectId: stri
|
|
|
14
14
|
return contentSourceType + ':' + srcProjectId;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export function getContentSourceDataByTypeAndProjectIdOrThrow(
|
|
18
|
+
srcType: string,
|
|
19
|
+
srcProjectId: string,
|
|
20
|
+
contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>
|
|
21
|
+
) {
|
|
22
|
+
const contentSourceId = getContentSourceId(srcType, srcProjectId);
|
|
23
|
+
return getContentSourceDataByIdOrThrow(contentSourceId, contentSourceDataById);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getContentSourceDataByIdOrThrow(contentSourceId: string, contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>) {
|
|
27
|
+
const contentSourceData = contentSourceDataById[contentSourceId];
|
|
28
|
+
if (!contentSourceData) {
|
|
29
|
+
throw new Error(`Content source not found: '${contentSourceId}'.`);
|
|
30
|
+
}
|
|
31
|
+
return contentSourceData;
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
export function findContentSourcesDataForTypeOrId({
|
|
18
35
|
contentSourceDataById,
|
|
19
36
|
srcType,
|
|
@@ -30,6 +47,10 @@ export function findContentSourcesDataForTypeOrId({
|
|
|
30
47
|
});
|
|
31
48
|
}
|
|
32
49
|
|
|
50
|
+
export function getUserContextForSrcTypeThunk(user?: ContentStoreTypes.User) {
|
|
51
|
+
return (srcType: string) => getUserContextForSrcType(srcType, user);
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
export function getUserContextForSrcType(srcType: string, user?: ContentStoreTypes.User): CSITypes.User | undefined {
|
|
34
55
|
if (!user) {
|
|
35
56
|
return undefined;
|
|
@@ -184,6 +205,232 @@ export function getModelFieldForFieldAtPath(
|
|
|
184
205
|
return getField(childDocField, childModelField, childFieldPath);
|
|
185
206
|
}
|
|
186
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Returns a model field and a document field at the specified field path.
|
|
210
|
+
*
|
|
211
|
+
* If some fields along the fieldPath are localized, the fieldPath must
|
|
212
|
+
* container the locale of the field under the "locales" property. The locales
|
|
213
|
+
* along the field path don't have to be the same.
|
|
214
|
+
* @example
|
|
215
|
+
* fieldPath: ['fields', 'button', 'locales', 'en', 'fields', 'title', 'locales', 'es']
|
|
216
|
+
*
|
|
217
|
+
* If the provided fieldPath points to a list item, the returned model field
|
|
218
|
+
* and document field will belong to a list item. In this case, the model field
|
|
219
|
+
* will contain only field-specific properties and the document field will be
|
|
220
|
+
* localized.
|
|
221
|
+
* @example
|
|
222
|
+
* fieldPath: ['fields', 'buttons', 'items', 2]
|
|
223
|
+
*
|
|
224
|
+
* The "fullFieldPath" flag specifies if the fieldPath includes container specifiers
|
|
225
|
+
* such as "fields", "items" and "locales".
|
|
226
|
+
* @example
|
|
227
|
+
* fullFieldPath: false => fieldPath: ['sections', 1, 'title', 'es']
|
|
228
|
+
* fullFieldPath: true => fieldPath: ['fields', 'sections', 'items', 1, 'fields', 'title', 'locales', 'es']
|
|
229
|
+
*
|
|
230
|
+
* @param document
|
|
231
|
+
* @param model
|
|
232
|
+
* @param fieldPath
|
|
233
|
+
* @param modelMap
|
|
234
|
+
* @param fullFieldPath
|
|
235
|
+
*/
|
|
236
|
+
export function getModelAndDocumentFieldForLocalizedFieldPath({
|
|
237
|
+
document,
|
|
238
|
+
model,
|
|
239
|
+
fieldPath,
|
|
240
|
+
modelMap,
|
|
241
|
+
fullFieldPath
|
|
242
|
+
}: {
|
|
243
|
+
document: ContentStoreTypes.Document;
|
|
244
|
+
model: Model;
|
|
245
|
+
fieldPath: (string | number)[];
|
|
246
|
+
modelMap: Record<string, Model>;
|
|
247
|
+
fullFieldPath?: boolean;
|
|
248
|
+
}): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems } {
|
|
249
|
+
const origFieldPath = fieldPath;
|
|
250
|
+
if (fullFieldPath) {
|
|
251
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
252
|
+
throw new Error('fieldPath must start with "fields" specifier');
|
|
253
|
+
}
|
|
254
|
+
fieldPath = _.tail(fieldPath);
|
|
255
|
+
}
|
|
256
|
+
if (_.isEmpty(fieldPath)) {
|
|
257
|
+
throw new Error('the fieldPath can not be empty');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getFieldPathPrefixOfTail(fieldPath: (string | number)[]) {
|
|
261
|
+
return origFieldPath.slice(0, origFieldPath.length - fieldPath.length);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function getField(
|
|
265
|
+
docField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems,
|
|
266
|
+
modelField: Field | FieldListItems,
|
|
267
|
+
fieldPath: (string | number)[]
|
|
268
|
+
): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems } {
|
|
269
|
+
if (docField.localized) {
|
|
270
|
+
if (fullFieldPath) {
|
|
271
|
+
if (_.head(fieldPath) !== 'locales') {
|
|
272
|
+
throw new Error('fieldPath must contain "locales" specifier for localized field');
|
|
273
|
+
}
|
|
274
|
+
fieldPath = _.tail(fieldPath);
|
|
275
|
+
}
|
|
276
|
+
const locale = _.head(fieldPath);
|
|
277
|
+
if (typeof locale !== 'string') {
|
|
278
|
+
throw new Error('fieldPath must contain string locale');
|
|
279
|
+
}
|
|
280
|
+
fieldPath = _.tail(fieldPath);
|
|
281
|
+
const localizedDocField = getDocumentFieldForLocale(docField, locale);
|
|
282
|
+
if (!localizedDocField) {
|
|
283
|
+
throw new Error(`locale for field was not found`);
|
|
284
|
+
}
|
|
285
|
+
docField = localizedDocField;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
switch (docField.type) {
|
|
289
|
+
case 'object': {
|
|
290
|
+
if (docField.isUnset) {
|
|
291
|
+
throw new Error(`field is not set`);
|
|
292
|
+
}
|
|
293
|
+
if (fullFieldPath) {
|
|
294
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
295
|
+
throw new Error('fieldPath must contain "fields" specifier for "object" field');
|
|
296
|
+
}
|
|
297
|
+
fieldPath = _.tail(fieldPath);
|
|
298
|
+
}
|
|
299
|
+
const fieldName = _.head(fieldPath);
|
|
300
|
+
if (typeof fieldName === 'undefined') {
|
|
301
|
+
throw new Error('the field path of object must contain a field name');
|
|
302
|
+
}
|
|
303
|
+
fieldPath = _.tail(fieldPath);
|
|
304
|
+
|
|
305
|
+
const childDocField = docField.fields[fieldName];
|
|
306
|
+
if (!childDocField) {
|
|
307
|
+
throw new Error(`document field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
|
|
308
|
+
}
|
|
309
|
+
if (modelField.type !== 'object') {
|
|
310
|
+
throw new Error(`model field type '${modelField.type}' doesn't match document field type '${docField.type}'`);
|
|
311
|
+
}
|
|
312
|
+
const childModelField = _.find(modelField.fields, (field) => field.name === fieldName);
|
|
313
|
+
if (!childModelField) {
|
|
314
|
+
throw new Error(`model field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
|
|
315
|
+
}
|
|
316
|
+
if (fieldPath.length === 0) {
|
|
317
|
+
return {
|
|
318
|
+
modelField: childModelField,
|
|
319
|
+
documentField: childDocField
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return getField(childDocField, childModelField, fieldPath);
|
|
323
|
+
}
|
|
324
|
+
case 'model': {
|
|
325
|
+
if (docField.isUnset) {
|
|
326
|
+
throw new Error(`field is not set`);
|
|
327
|
+
}
|
|
328
|
+
if (fullFieldPath) {
|
|
329
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
330
|
+
throw new Error('fieldPath must contain "fields" specifier for "model" field');
|
|
331
|
+
}
|
|
332
|
+
fieldPath = _.tail(fieldPath);
|
|
333
|
+
}
|
|
334
|
+
const fieldName = _.head(fieldPath);
|
|
335
|
+
if (typeof fieldName === 'undefined') {
|
|
336
|
+
throw new Error('the field path of model must contain a field name');
|
|
337
|
+
}
|
|
338
|
+
fieldPath = _.tail(fieldPath);
|
|
339
|
+
|
|
340
|
+
const modelName = docField.srcModelName;
|
|
341
|
+
const childModel = modelMap[modelName];
|
|
342
|
+
if (!childModel) {
|
|
343
|
+
throw new Error(`model ${modelName} doesn't exist`);
|
|
344
|
+
}
|
|
345
|
+
const childModelField = _.find(childModel.fields, (field) => field.name === fieldName);
|
|
346
|
+
if (!childModelField) {
|
|
347
|
+
throw new Error(`model field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
|
|
348
|
+
}
|
|
349
|
+
const childDocField = docField.fields[fieldName];
|
|
350
|
+
if (!childDocField) {
|
|
351
|
+
throw new Error(`document field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
|
|
352
|
+
}
|
|
353
|
+
if (fieldPath.length === 0) {
|
|
354
|
+
return {
|
|
355
|
+
modelField: childModelField,
|
|
356
|
+
documentField: childDocField
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return getField(childDocField, childModelField, fieldPath);
|
|
360
|
+
}
|
|
361
|
+
case 'list': {
|
|
362
|
+
if (fullFieldPath) {
|
|
363
|
+
if (_.head(fieldPath) !== 'items') {
|
|
364
|
+
throw new Error('fieldPath must contain "items" specifier for "list" field');
|
|
365
|
+
}
|
|
366
|
+
fieldPath = _.tail(fieldPath);
|
|
367
|
+
}
|
|
368
|
+
const itemIndex = _.head(fieldPath) as number;
|
|
369
|
+
if (typeof itemIndex === 'undefined') {
|
|
370
|
+
throw new Error('the field path of list must contain an item index');
|
|
371
|
+
}
|
|
372
|
+
fieldPath = _.tail(fieldPath);
|
|
373
|
+
|
|
374
|
+
const listItem = docField.items && docField.items[itemIndex as number];
|
|
375
|
+
if (!listItem) {
|
|
376
|
+
throw new Error(`list item doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
|
|
377
|
+
}
|
|
378
|
+
if (modelField.type !== 'list') {
|
|
379
|
+
throw new Error(`model field type '${modelField.type}' doesn't match document field type '${docField.type}'`);
|
|
380
|
+
}
|
|
381
|
+
const listItemsModel = modelField.items;
|
|
382
|
+
if (fieldPath.length === 0) {
|
|
383
|
+
return {
|
|
384
|
+
modelField: listItemsModel,
|
|
385
|
+
documentField: listItem
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (!Array.isArray(listItemsModel)) {
|
|
389
|
+
return getField(listItem, listItemsModel, fieldPath);
|
|
390
|
+
} else {
|
|
391
|
+
const fieldListItems = (listItemsModel as FieldListItems[]).find((listItemsModel) => listItemsModel.type === listItem.type);
|
|
392
|
+
if (!fieldListItems) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`cannot find list item model for document field of type '${listItem.type}' at field path: '${getFieldPathPrefixOfTail(
|
|
395
|
+
fieldPath
|
|
396
|
+
).join('.')}'`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return getField(listItem, fieldListItems, fieldPath);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
default:
|
|
403
|
+
if (!_.isEmpty(fieldPath)) {
|
|
404
|
+
throw new Error('illegal fieldPath');
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
modelField,
|
|
408
|
+
documentField: docField
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const fieldName = _.head(fieldPath);
|
|
414
|
+
const childFieldPath = _.tail(fieldPath);
|
|
415
|
+
|
|
416
|
+
if (typeof fieldName !== 'string') {
|
|
417
|
+
throw new Error('the first fieldPath item must be string');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const childDocField: ContentStoreTypes.DocumentField | undefined = document.fields[fieldName];
|
|
421
|
+
const childModelField: Field | undefined = _.find(model.fields, { name: fieldName });
|
|
422
|
+
|
|
423
|
+
if (!childDocField || !childModelField) {
|
|
424
|
+
throw new Error(`field ${fieldName} doesn't exist`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (childFieldPath.length === 0) {
|
|
428
|
+
return { modelField: childModelField, documentField: childDocField };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return getField(childDocField, childModelField, childFieldPath);
|
|
432
|
+
}
|
|
433
|
+
|
|
187
434
|
export function groupModelsByContentSource({ models }: { models: CSITypes.ModelWithSource[] }): Record<string, Record<string, Record<string, Model>>> {
|
|
188
435
|
const modelMapByContentSource: Record<string, Record<string, Record<string, Model>>> = {};
|
|
189
436
|
for (const model of models) {
|
package/src/content-store.ts
CHANGED
|
@@ -22,11 +22,12 @@ import { append, DeferredPromise, deferredPromise, deferWhileRunning, mapPromise
|
|
|
22
22
|
|
|
23
23
|
import * as ContentStoreTypes from './types';
|
|
24
24
|
import { Timer } from './utils/timer';
|
|
25
|
-
import { SearchFilter } from './types
|
|
25
|
+
import { SearchFilter } from './types';
|
|
26
26
|
import { searchDocuments } from './utils/search-utils';
|
|
27
27
|
import { mapCSIAssetsToStoreAssets, mapCSIDocumentsToStoreDocuments } from './utils/csi-to-store-docs-converter';
|
|
28
28
|
import {
|
|
29
29
|
getContentSourceId,
|
|
30
|
+
getContentSourceDataByIdOrThrow,
|
|
30
31
|
getContentSourceIdForContentSource,
|
|
31
32
|
getCSIDocumentsAndAssetsFromContentSourceDataByIds,
|
|
32
33
|
getModelFieldForFieldAtPath,
|
|
@@ -58,6 +59,7 @@ import { createConfigDelegate, getCreateConfigDelegateThunk } from './utils/conf
|
|
|
58
59
|
import { GitService } from './services';
|
|
59
60
|
import { getAssetSourcesForClient } from './utils/asset-sources-utils';
|
|
60
61
|
import { deleteDocumentHooked, publishDocumentHooked, updateDocumentHooked } from './utils/document-hooks';
|
|
62
|
+
import { resolveCustomActionsById, getGlobalAndBulkAPIActions, runCustomAction, stripModelActions } from './utils/custom-actions';
|
|
61
63
|
import {
|
|
62
64
|
logCreateDocument,
|
|
63
65
|
logDeleteDocument,
|
|
@@ -67,6 +69,7 @@ import {
|
|
|
67
69
|
logUploadAssets,
|
|
68
70
|
pluralize
|
|
69
71
|
} from './utils/user-log-utils';
|
|
72
|
+
import { getSanitizedTreeViews } from './utils/tree-views';
|
|
70
73
|
|
|
71
74
|
export type HandleConfigAssets = <T extends Model>({ models, presets }: { models?: T[]; presets?: PresetMap }) => Promise<{ models: T[]; presets: PresetMap }>;
|
|
72
75
|
|
|
@@ -81,6 +84,7 @@ export interface ContentSourceOptions {
|
|
|
81
84
|
userCommandSpawner?: UserCommandSpawner; //TODO remove
|
|
82
85
|
onSchemaChangeCallback: () => void;
|
|
83
86
|
onContentChangeCallback: (contentChanges: ContentStoreTypes.ContentChangeResult) => void;
|
|
87
|
+
onActionStateChangeCallback: (actionResult: ContentStoreTypes.CustomActionStateChange) => void;
|
|
84
88
|
handleConfigAssets: HandleConfigAssets;
|
|
85
89
|
devAppRestartNeeded?: () => void;
|
|
86
90
|
}
|
|
@@ -126,6 +130,7 @@ export class ContentStore {
|
|
|
126
130
|
private readonly git: GitService;
|
|
127
131
|
private readonly onSchemaChangeCallback: () => void;
|
|
128
132
|
private readonly onContentChangeCallback: (contentChanges: ContentStoreTypes.ContentChangeResult) => void;
|
|
133
|
+
private readonly onActionStateChangeCallback: (actionStateChange: ContentStoreTypes.CustomActionStateChange) => void;
|
|
129
134
|
private readonly handleConfigAssets: HandleConfigAssets;
|
|
130
135
|
private readonly devAppRestartNeeded?: () => void;
|
|
131
136
|
private contentSources: BackCompatContentSourceInterface[] = [];
|
|
@@ -140,6 +145,8 @@ export class ContentStore {
|
|
|
140
145
|
private siteMapEntryGroups: SiteMapEntryGroups = {};
|
|
141
146
|
private processingContentSourcesPromise: DeferredPromise<void> | null = null;
|
|
142
147
|
private contentStoreEventQueue: ContentStoreEventQueue = [];
|
|
148
|
+
private treeViews: CSITypes.TreeViewNode[] = [];
|
|
149
|
+
private customActionMap: ContentStoreTypes.ContentStoreCustomActionMap = {};
|
|
143
150
|
|
|
144
151
|
constructor(options: ContentSourceOptions) {
|
|
145
152
|
this.logger = options.logger.createLogger({ label: 'content-store' });
|
|
@@ -152,6 +159,7 @@ export class ContentStore {
|
|
|
152
159
|
this.userCommandSpawner = options.userCommandSpawner;
|
|
153
160
|
this.onSchemaChangeCallback = options.onSchemaChangeCallback;
|
|
154
161
|
this.onContentChangeCallback = options.onContentChangeCallback;
|
|
162
|
+
this.onActionStateChangeCallback = options.onActionStateChangeCallback;
|
|
155
163
|
this.handleConfigAssets = options.handleConfigAssets;
|
|
156
164
|
this.contentUpdatesWatchTimer = new Timer({ timerCallback: () => this.handleTimerTimeout(), logger: this.logger });
|
|
157
165
|
this.devAppRestartNeeded = options.devAppRestartNeeded;
|
|
@@ -461,8 +469,20 @@ export class ContentStore {
|
|
|
461
469
|
contentSourceRawDataArr: contentSourceRawDataArr
|
|
462
470
|
});
|
|
463
471
|
|
|
472
|
+
const configDelegate = createConfigDelegate({
|
|
473
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
474
|
+
logger: this.userLogger
|
|
475
|
+
});
|
|
476
|
+
|
|
464
477
|
// generate create site map entries
|
|
465
478
|
this.siteMapEntryGroups = await getSiteMapEntriesFromStackbitConfig({
|
|
479
|
+
stackbitConfig: this.stackbitConfig,
|
|
480
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
481
|
+
configDelegate
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
this.treeViews = await getSanitizedTreeViews({
|
|
485
|
+
configDelegate,
|
|
466
486
|
stackbitConfig: this.stackbitConfig,
|
|
467
487
|
contentSourceDataById: this.contentSourceDataById,
|
|
468
488
|
logger: this.userLogger
|
|
@@ -543,12 +563,22 @@ export class ContentStore {
|
|
|
543
563
|
});
|
|
544
564
|
} else {
|
|
545
565
|
this.logger.debug('processContentStoreEvents => content changes', { ...contentChangeResultCounts(contentChanges), presetsUpdated });
|
|
566
|
+
const configDelegate = createConfigDelegate({
|
|
567
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
568
|
+
logger: this.userLogger
|
|
569
|
+
});
|
|
546
570
|
// If the schema wasn't changed, update SiteMapEntries with the changed content.
|
|
547
571
|
this.siteMapEntryGroups = await updateSiteMapEntriesWithContentChanges({
|
|
548
572
|
siteMapEntryGroups: this.siteMapEntryGroups,
|
|
549
573
|
contentChanges: contentChanges,
|
|
550
574
|
stackbitConfig: this.stackbitConfig,
|
|
551
575
|
contentSourceDataById: this.contentSourceDataById,
|
|
576
|
+
configDelegate
|
|
577
|
+
});
|
|
578
|
+
this.treeViews = await getSanitizedTreeViews({
|
|
579
|
+
configDelegate,
|
|
580
|
+
stackbitConfig: this.stackbitConfig,
|
|
581
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
552
582
|
logger: this.userLogger
|
|
553
583
|
});
|
|
554
584
|
// If presets were updated, call onSchemaChangeCallback to notify the Studio.
|
|
@@ -1016,6 +1046,7 @@ export class ContentStore {
|
|
|
1016
1046
|
modelMap: contentSourceData.modelMap,
|
|
1017
1047
|
defaultLocaleCode: contentSourceData.defaultLocaleCode,
|
|
1018
1048
|
assetSources: this.stackbitConfig?.assetSources ?? [],
|
|
1049
|
+
customActionMap: this.customActionMap,
|
|
1019
1050
|
createConfigDelegate: getCreateConfigDelegateThunk({
|
|
1020
1051
|
getContentSourceDataById: () => this.contentSourceDataById,
|
|
1021
1052
|
logger: this.userLogger
|
|
@@ -1254,6 +1285,7 @@ export class ContentStore {
|
|
|
1254
1285
|
modelMap: modelMap,
|
|
1255
1286
|
defaultLocaleCode: csData.defaultLocaleCode,
|
|
1256
1287
|
assetSources: this.stackbitConfig?.assetSources ?? [],
|
|
1288
|
+
customActionMap: this.customActionMap,
|
|
1257
1289
|
createConfigDelegate: getCreateConfigDelegateThunk({
|
|
1258
1290
|
getContentSourceDataById: () => this.contentSourceDataById,
|
|
1259
1291
|
logger: this.userLogger
|
|
@@ -1302,9 +1334,10 @@ export class ContentStore {
|
|
|
1302
1334
|
const contentSourceType = contentSourceData.instance.getContentSourceType();
|
|
1303
1335
|
const srcProjectId = contentSourceData.instance.getProjectId();
|
|
1304
1336
|
const filteredModels = _.omitBy(contentSourceData.modelMap, (model) => model.name === StackbitPresetModelName);
|
|
1337
|
+
const mappedModels = stripModelActions({ modelMap: filteredModels });
|
|
1305
1338
|
// if `projectId` is number (even as string) e.g., '1234', _.set() will create an array of length 1235 and insert the item at the end.
|
|
1306
1339
|
// _.setWith(..., Object) ensures the values are always created as object keys, not as array indexes.
|
|
1307
|
-
_.setWith(result, [contentSourceType, srcProjectId],
|
|
1340
|
+
_.setWith(result, [contentSourceType, srcProjectId], mappedModels, Object);
|
|
1308
1341
|
_.setWith(result, [contentSourceType, srcProjectId, '__image_model'], IMAGE_MODEL, Object);
|
|
1309
1342
|
return result;
|
|
1310
1343
|
},
|
|
@@ -1327,6 +1360,56 @@ export class ContentStore {
|
|
|
1327
1360
|
);
|
|
1328
1361
|
}
|
|
1329
1362
|
|
|
1363
|
+
async getGlobalActions({
|
|
1364
|
+
pageUrl,
|
|
1365
|
+
user,
|
|
1366
|
+
locale,
|
|
1367
|
+
currentPageDocument
|
|
1368
|
+
}: {
|
|
1369
|
+
pageUrl?: string;
|
|
1370
|
+
user?: ContentStoreTypes.User;
|
|
1371
|
+
locale?: string;
|
|
1372
|
+
currentPageDocument?: ContentStoreTypes.APICustomActionDocumentSpecifier;
|
|
1373
|
+
}): Promise<(ContentStoreTypes.APICustomActionGlobal | ContentStoreTypes.APICustomActionBulk)[]> {
|
|
1374
|
+
return getGlobalAndBulkAPIActions({
|
|
1375
|
+
stackbitConfig: this.stackbitConfig,
|
|
1376
|
+
customActionMap: this.customActionMap,
|
|
1377
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
1378
|
+
userLogger: this.userLogger,
|
|
1379
|
+
locale,
|
|
1380
|
+
pageUrl,
|
|
1381
|
+
user,
|
|
1382
|
+
currentPageDocument
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
async getCustomActions(getActionRequest: ContentStoreTypes.APIGetCustomActionRequest): Promise<ContentStoreTypes.APICustomAction[]> {
|
|
1387
|
+
return resolveCustomActionsById({
|
|
1388
|
+
getActionRequest,
|
|
1389
|
+
customActionMap: this.customActionMap,
|
|
1390
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
1391
|
+
userLogger: this.userLogger
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
async runCustomAction(runActionRequest: ContentStoreTypes.APIRunCustomActionRequest): Promise<void> {
|
|
1396
|
+
this.onActionStateChangeCallback({
|
|
1397
|
+
actionId: runActionRequest.actionId,
|
|
1398
|
+
actionName: runActionRequest.actionName,
|
|
1399
|
+
actionType: runActionRequest.actionType,
|
|
1400
|
+
state: 'running'
|
|
1401
|
+
});
|
|
1402
|
+
runCustomAction({
|
|
1403
|
+
runActionRequest: runActionRequest,
|
|
1404
|
+
contentSourceDataById: this.contentSourceDataById,
|
|
1405
|
+
customActionMap: this.customActionMap,
|
|
1406
|
+
userLogger: this.userLogger,
|
|
1407
|
+
stackbitConfig: this.stackbitConfig
|
|
1408
|
+
}).then((actionStateChange) => {
|
|
1409
|
+
this.onActionStateChangeCallback(actionStateChange);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1330
1413
|
getPresets({ locale }: { locale?: string } = {}): Record<string, any> {
|
|
1331
1414
|
if (!this.presets || !locale) {
|
|
1332
1415
|
return this.presets ?? {};
|
|
@@ -1474,6 +1557,10 @@ export class ContentStore {
|
|
|
1474
1557
|
return _.isEmpty(locale) ? siteMapEntries : siteMapEntries.filter((siteMapEntry) => !siteMapEntry.locale || siteMapEntry.locale === locale);
|
|
1475
1558
|
}
|
|
1476
1559
|
|
|
1560
|
+
getTreeViews(): CSITypes.TreeViewNode[] {
|
|
1561
|
+
return this.treeViews;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1477
1564
|
getSiteMapEntriesForDocument({
|
|
1478
1565
|
srcType,
|
|
1479
1566
|
srcProjectId,
|
|
@@ -2532,11 +2619,7 @@ export class ContentStore {
|
|
|
2532
2619
|
}
|
|
2533
2620
|
|
|
2534
2621
|
private getContentSourceDataByIdOrThrow(contentSourceId: string): ContentSourceData {
|
|
2535
|
-
|
|
2536
|
-
if (!contentSourceData) {
|
|
2537
|
-
throw new Error(`Connection failed for content source: '${contentSourceId}'.`);
|
|
2538
|
-
}
|
|
2539
|
-
return contentSourceData;
|
|
2622
|
+
return getContentSourceDataByIdOrThrow(contentSourceId, this.contentSourceDataById);
|
|
2540
2623
|
}
|
|
2541
2624
|
|
|
2542
2625
|
onWebhook({ srcType, srcProjectId, data, headers }: { srcType: string; srcProjectId: string; data: unknown; headers: Record<string, string> }) {
|