@stackbit/cms-core 0.0.11 → 0.0.14

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,86 @@
1
+ export const FIELD_TYPES: string[];
2
+ export function isObjectField(field: any): boolean;
3
+ export function isModelField(field: any): boolean;
4
+ export function isModelsField(field: any): boolean;
5
+ export function isReferenceField(field: any): boolean;
6
+ export function isCustomModelField(field: any, modelsByName: any): any;
7
+ export function isListField(field: any): boolean;
8
+ export function isListOfObjectsField(field: any): boolean;
9
+ export function isListOfModelField(field: any): boolean;
10
+ export function isListOfModelsField(field: any): boolean;
11
+ export function isListOfCustomModelField(field: any): any;
12
+ export function isListOfReferenceField(field: any): boolean;
13
+ /**
14
+ * Gets a list field and returns its items field. If list field does not define
15
+ * items field, the default field is string:
16
+ *
17
+ * @example
18
+ * listItemField = getListItemsField({
19
+ * type: 'list',
20
+ * name: '...',
21
+ * items: { type: 'object', fields: [] }
22
+ * }
23
+ * listItemField => {
24
+ * type: 'object',
25
+ * name: '...',
26
+ * fields: []
27
+ * }
28
+ *
29
+ * // list field without `items`
30
+ * listItemField = getListItemsField({ type: 'list', name: '...' }
31
+ * listItemField => { type: 'string' }
32
+ *
33
+ * @param {Object} field
34
+ * @return {Object}
35
+ */
36
+ export function getListItemsField(field: Object): Object;
37
+ /**
38
+ * This function invokes the `iterator` function for every field of the `model`.
39
+ * It recursively traverses through fields of type `object` and `list` with
40
+ * items of type `object` and invokes the `iterator` on their child fields,
41
+ * and so on. The traversal is a depth-first and the `iterator` is invoked
42
+ * before traversing the field's child fields.
43
+ *
44
+ * The iterator is invoked with two parameters, `field` and `fieldPath`. The
45
+ * `field` is the currently iterated field, and `fieldPath` is an array of
46
+ * strings indicating the path of the `field` relative to the model.
47
+ *
48
+ * @example
49
+ * model = {
50
+ * fields: [
51
+ * { name: "title", type: "string" },
52
+ * {
53
+ * name: "banner",
54
+ * type: "object",
55
+ * fields: [
56
+ * { name: "logo", type: "image" }
57
+ * ]}
58
+ * {
59
+ * name: "actions",
60
+ * type: "list",
61
+ * items: {
62
+ * type: "object",
63
+ * fields: [
64
+ * {name: "label", type: "string"}
65
+ * ]
66
+ * }
67
+ * }
68
+ * ]
69
+ * }
70
+ * iterateModelFieldsRecursively(model, iterator);
71
+ * // will call the iterator with following field.name and fieldPath
72
+ * - 'title', ['fields', 'title']
73
+ * - 'banner', ['fields', 'banner']
74
+ * - 'logo', ['fields', 'banner', 'fields', 'logo']
75
+ * - 'actions', ['fields', 'actions']
76
+ * - 'label', ['fields', 'actions', 'items', 'fields', 'label']
77
+ *
78
+ * @param {Object} model The root model to iterate fields
79
+ * @param {Function} iterator The iterator function
80
+ * @param {Array} [fieldPath]
81
+ */
82
+ export function iterateModelFieldsRecursively(model: Object, iterator: Function, fieldPath?: any[] | undefined): void;
83
+ export function resolveLabelFieldForModel(model: any, modelLabelFieldPath: any, fields: any): any;
84
+ export function resolveLabelFieldFromFields(fields: any): any;
85
+ export function getUrlPath(stackbitModel: any): any;
86
+ export function getNormalizedModelType(stackbitModel: any): any;
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ const _ = require('lodash');
3
+ const FIELD_TYPES = [
4
+ 'string',
5
+ 'text',
6
+ 'markdown',
7
+ 'html',
8
+ 'url',
9
+ 'slug',
10
+ 'number',
11
+ 'boolean',
12
+ 'select',
13
+ 'enum',
14
+ 'date',
15
+ 'datetime',
16
+ 'color',
17
+ 'image',
18
+ 'file',
19
+ 'json',
20
+ 'reference',
21
+ 'model',
22
+ 'models',
23
+ 'object',
24
+ 'list',
25
+ 'array'
26
+ ];
27
+ module.exports = {
28
+ FIELD_TYPES,
29
+ isObjectField,
30
+ isModelField,
31
+ isModelsField,
32
+ isReferenceField,
33
+ isCustomModelField,
34
+ isListField,
35
+ isListOfObjectsField,
36
+ isListOfModelField,
37
+ isListOfModelsField,
38
+ isListOfCustomModelField,
39
+ isListOfReferenceField,
40
+ getListItemsField,
41
+ iterateModelFieldsRecursively,
42
+ resolveLabelFieldForModel,
43
+ resolveLabelFieldFromFields,
44
+ getUrlPath,
45
+ getNormalizedModelType
46
+ };
47
+ function resolveLabelFieldForModel(model, modelLabelFieldPath, fields) {
48
+ let labelField = _.get(model, modelLabelFieldPath, null);
49
+ if (labelField) {
50
+ return labelField;
51
+ }
52
+ return resolveLabelFieldFromFields(fields);
53
+ }
54
+ function resolveLabelFieldFromFields(fields) {
55
+ let labelField = null;
56
+ let titleField = _.find(fields, field => field.name === 'title' && _.includes(['string', 'text'], field.type));
57
+ if (!titleField) {
58
+ // get first string field
59
+ titleField = _.find(fields, { type: 'string' });
60
+ }
61
+ if (titleField) {
62
+ labelField = _.get(titleField, 'name');
63
+ }
64
+ return labelField;
65
+ }
66
+ function isObjectField(field) {
67
+ return field.type === 'object';
68
+ }
69
+ function isReferenceField(field) {
70
+ return field.type === 'reference';
71
+ }
72
+ function isCustomModelField(field, modelsByName) {
73
+ return !FIELD_TYPES.includes(field.type) && (!modelsByName || _.has(modelsByName, field.type));
74
+ }
75
+ function isModelField(field) {
76
+ return field.type === 'model';
77
+ }
78
+ function isModelsField(field) {
79
+ return field.type === 'models';
80
+ }
81
+ function isListField(field) {
82
+ return ['list', 'array'].includes(field.type);
83
+ }
84
+ function isListOfObjectsField(field) {
85
+ return isListField(field) && isObjectField(getListItemsField(field));
86
+ }
87
+ function isListOfModelField(field) {
88
+ return isListField(field) && isModelField(getListItemsField(field));
89
+ }
90
+ function isListOfModelsField(field) {
91
+ return isListField(field) && isModelsField(getListItemsField(field));
92
+ }
93
+ function isListOfCustomModelField(field) {
94
+ return isListField(field) && isCustomModelField(getListItemsField(field));
95
+ }
96
+ function isListOfReferenceField(field) {
97
+ return isListField(field) && isReferenceField(getListItemsField(field));
98
+ }
99
+ /**
100
+ * Gets a list field and returns its items field. If list field does not define
101
+ * items field, the default field is string:
102
+ *
103
+ * @example
104
+ * listItemField = getListItemsField({
105
+ * type: 'list',
106
+ * name: '...',
107
+ * items: { type: 'object', fields: [] }
108
+ * }
109
+ * listItemField => {
110
+ * type: 'object',
111
+ * name: '...',
112
+ * fields: []
113
+ * }
114
+ *
115
+ * // list field without `items`
116
+ * listItemField = getListItemsField({ type: 'list', name: '...' }
117
+ * listItemField => { type: 'string' }
118
+ *
119
+ * @param {Object} field
120
+ * @return {Object}
121
+ */
122
+ function getListItemsField(field) {
123
+ // items.type defaults to string
124
+ return _.defaults({}, _.get(field, 'items', {}), { type: 'string' });
125
+ }
126
+ /**
127
+ * This function invokes the `iterator` function for every field of the `model`.
128
+ * It recursively traverses through fields of type `object` and `list` with
129
+ * items of type `object` and invokes the `iterator` on their child fields,
130
+ * and so on. The traversal is a depth-first and the `iterator` is invoked
131
+ * before traversing the field's child fields.
132
+ *
133
+ * The iterator is invoked with two parameters, `field` and `fieldPath`. The
134
+ * `field` is the currently iterated field, and `fieldPath` is an array of
135
+ * strings indicating the path of the `field` relative to the model.
136
+ *
137
+ * @example
138
+ * model = {
139
+ * fields: [
140
+ * { name: "title", type: "string" },
141
+ * {
142
+ * name: "banner",
143
+ * type: "object",
144
+ * fields: [
145
+ * { name: "logo", type: "image" }
146
+ * ]}
147
+ * {
148
+ * name: "actions",
149
+ * type: "list",
150
+ * items: {
151
+ * type: "object",
152
+ * fields: [
153
+ * {name: "label", type: "string"}
154
+ * ]
155
+ * }
156
+ * }
157
+ * ]
158
+ * }
159
+ * iterateModelFieldsRecursively(model, iterator);
160
+ * // will call the iterator with following field.name and fieldPath
161
+ * - 'title', ['fields', 'title']
162
+ * - 'banner', ['fields', 'banner']
163
+ * - 'logo', ['fields', 'banner', 'fields', 'logo']
164
+ * - 'actions', ['fields', 'actions']
165
+ * - 'label', ['fields', 'actions', 'items', 'fields', 'label']
166
+ *
167
+ * @param {Object} model The root model to iterate fields
168
+ * @param {Function} iterator The iterator function
169
+ * @param {Array} [fieldPath]
170
+ */
171
+ function iterateModelFieldsRecursively(model, iterator, fieldPath = []) {
172
+ const fields = _.get(model, 'fields', []);
173
+ fieldPath = fieldPath.concat('fields');
174
+ _.forEach(fields, (field) => {
175
+ const childFieldPath = fieldPath.concat(field.name);
176
+ iterator(field, childFieldPath);
177
+ if (isObjectField(field)) {
178
+ iterateModelFieldsRecursively(field, iterator, childFieldPath);
179
+ }
180
+ else if (isListOfObjectsField(field)) {
181
+ iterateModelFieldsRecursively(getListItemsField(field), iterator, childFieldPath.concat('items'));
182
+ }
183
+ });
184
+ }
185
+ function getUrlPath(stackbitModel) {
186
+ const modelType = getNormalizedModelType(stackbitModel);
187
+ if (modelType === 'page') {
188
+ return _.get(stackbitModel, 'urlPath', '/{slug}');
189
+ }
190
+ return null;
191
+ }
192
+ function getNormalizedModelType(stackbitModel) {
193
+ return stackbitModel ? (stackbitModel.type === 'config' ? 'data' : stackbitModel.type) : 'object';
194
+ }
195
+ //# sourceMappingURL=schema-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-utils.js","sourceRoot":"","sources":["../../src/utils/schema-utils.js"],"names":[],"mappings":";AAAA,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE5B,MAAM,WAAW,GAAG;IAChB,QAAQ;IACR,MAAM;IACN,UAAU;IACV,MAAM;IACN,KAAK;IACL,MAAM;IACN,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,MAAM;IACN,MAAM;IACN,UAAU;IACV,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,WAAW;IACX,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,OAAO;CACV,CAAC;AAEF,MAAM,CAAC,OAAO,GAAG;IACb,WAAW;IACX,aAAa;IACb,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,kBAAkB;IAClB,WAAW;IACX,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,wBAAwB;IACxB,sBAAsB;IACtB,iBAAiB;IACjB,6BAA6B;IAC7B,yBAAyB;IACzB,2BAA2B;IAC3B,UAAU;IACV,sBAAsB;CACzB,CAAC;AAGF,SAAS,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM;IACjE,IAAI,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACzD,IAAI,UAAU,EAAE;QACZ,OAAO,UAAU,CAAC;KACrB;IACD,OAAO,2BAA2B,CAAC,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,2BAA2B,CAAC,MAAM;IACvC,IAAI,UAAU,GAAG,IAAI,CAAC;IACtB,IAAI,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/G,IAAI,CAAC,UAAU,EAAE;QACb,yBAAyB;QACzB,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,EAAC,IAAI,EAAE,QAAQ,EAAC,CAAC,CAAC;KACjD;IACD,IAAI,UAAU,EAAE;QACZ,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;KAC1C;IACD,OAAO,UAAU,CAAC;AACtB,CAAC;AAED,SAAS,aAAa,CAAC,KAAK;IACxB,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AACnC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAK;IAC3B,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC;AACtC,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAK,EAAE,YAAY;IAC3C,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AACnG,CAAC;AAED,SAAS,YAAY,CAAC,KAAK;IACvB,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAClC,CAAC;AAED,SAAS,aAAa,CAAC,KAAK;IACxB,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AACnC,CAAC;AAED,SAAS,WAAW,CAAC,KAAK;IACtB,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAK;IAC/B,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAK;IAC7B,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAK;IAC9B,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAK;IACnC,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAK;IACjC,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,gBAAgB,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAS,iBAAiB,CAAC,KAAK;IAC5B,gCAAgC;IAChC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,EAAC,IAAI,EAAE,QAAQ,EAAC,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,SAAS,6BAA6B,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,GAAG,EAAE;IAClE,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1C,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QACxB,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QAChC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE;YACtB,6BAA6B,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;SAClE;aAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE;YACpC,6BAA6B,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;SACrG;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,UAAU,CAAC,aAAa;IAC7B,MAAM,SAAS,GAAG,sBAAsB,CAAC,aAAa,CAAC,CAAC;IACxD,IAAI,SAAS,KAAK,MAAM,EAAE;QACtB,OAAO,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;KACrD;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,sBAAsB,CAAC,aAAa;IACzC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACtG,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbit/cms-core",
3
- "version": "0.0.11",
3
+ "version": "0.0.14",
4
4
  "description": "stackbit-dev",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "@babel/parser": "^7.11.5",
28
28
  "@babel/traverse": "^7.11.5",
29
29
  "@iarna/toml": "^2.2.3",
30
- "@stackbit/sdk": "^0.2.22",
30
+ "@stackbit/sdk": "^0.2.32",
31
31
  "chalk": "^4.0.1",
32
32
  "esm": "^3.2.25",
33
33
  "fs-extra": "^8.1.0",
@@ -39,5 +39,5 @@
39
39
  "moment": "^2.29.1",
40
40
  "parse5": "^6.0.1"
41
41
  },
42
- "gitHead": "73055add8b105838db1dd89ec245087539f5b53e"
42
+ "gitHead": "34568b6182f3380d40d8bab8970d24c749d53554"
43
43
  }
@@ -0,0 +1,212 @@
1
+ const _ = require('lodash');
2
+
3
+ const FIELD_TYPES = [
4
+ 'string',
5
+ 'text',
6
+ 'markdown',
7
+ 'html',
8
+ 'url',
9
+ 'slug',
10
+ 'number',
11
+ 'boolean',
12
+ 'select',
13
+ 'enum',
14
+ 'date',
15
+ 'datetime',
16
+ 'color',
17
+ 'image',
18
+ 'file',
19
+ 'json',
20
+ 'reference',
21
+ 'model',
22
+ 'models',
23
+ 'object',
24
+ 'list',
25
+ 'array'
26
+ ];
27
+
28
+ module.exports = {
29
+ FIELD_TYPES,
30
+ isObjectField,
31
+ isModelField,
32
+ isModelsField,
33
+ isReferenceField,
34
+ isCustomModelField,
35
+ isListField,
36
+ isListOfObjectsField,
37
+ isListOfModelField,
38
+ isListOfModelsField,
39
+ isListOfCustomModelField,
40
+ isListOfReferenceField,
41
+ getListItemsField,
42
+ iterateModelFieldsRecursively,
43
+ resolveLabelFieldForModel,
44
+ resolveLabelFieldFromFields,
45
+ getUrlPath,
46
+ getNormalizedModelType
47
+ };
48
+
49
+
50
+ function resolveLabelFieldForModel(model, modelLabelFieldPath, fields) {
51
+ let labelField = _.get(model, modelLabelFieldPath, null);
52
+ if (labelField) {
53
+ return labelField;
54
+ }
55
+ return resolveLabelFieldFromFields(fields);
56
+ }
57
+
58
+ function resolveLabelFieldFromFields(fields) {
59
+ let labelField = null;
60
+ let titleField = _.find(fields, field => field.name === 'title' && _.includes(['string', 'text'], field.type));
61
+ if (!titleField) {
62
+ // get first string field
63
+ titleField = _.find(fields, {type: 'string'});
64
+ }
65
+ if (titleField) {
66
+ labelField = _.get(titleField, 'name');
67
+ }
68
+ return labelField;
69
+ }
70
+
71
+ function isObjectField(field) {
72
+ return field.type === 'object';
73
+ }
74
+
75
+ function isReferenceField(field) {
76
+ return field.type === 'reference';
77
+ }
78
+
79
+ function isCustomModelField(field, modelsByName) {
80
+ return !FIELD_TYPES.includes(field.type) && (!modelsByName || _.has(modelsByName, field.type));
81
+ }
82
+
83
+ function isModelField(field) {
84
+ return field.type === 'model';
85
+ }
86
+
87
+ function isModelsField(field) {
88
+ return field.type === 'models';
89
+ }
90
+
91
+ function isListField(field) {
92
+ return ['list', 'array'].includes(field.type);
93
+ }
94
+
95
+ function isListOfObjectsField(field) {
96
+ return isListField(field) && isObjectField(getListItemsField(field));
97
+ }
98
+
99
+ function isListOfModelField(field) {
100
+ return isListField(field) && isModelField(getListItemsField(field));
101
+ }
102
+
103
+ function isListOfModelsField(field) {
104
+ return isListField(field) && isModelsField(getListItemsField(field));
105
+ }
106
+
107
+ function isListOfCustomModelField(field) {
108
+ return isListField(field) && isCustomModelField(getListItemsField(field));
109
+ }
110
+
111
+ function isListOfReferenceField(field) {
112
+ return isListField(field) && isReferenceField(getListItemsField(field));
113
+ }
114
+
115
+ /**
116
+ * Gets a list field and returns its items field. If list field does not define
117
+ * items field, the default field is string:
118
+ *
119
+ * @example
120
+ * listItemField = getListItemsField({
121
+ * type: 'list',
122
+ * name: '...',
123
+ * items: { type: 'object', fields: [] }
124
+ * }
125
+ * listItemField => {
126
+ * type: 'object',
127
+ * name: '...',
128
+ * fields: []
129
+ * }
130
+ *
131
+ * // list field without `items`
132
+ * listItemField = getListItemsField({ type: 'list', name: '...' }
133
+ * listItemField => { type: 'string' }
134
+ *
135
+ * @param {Object} field
136
+ * @return {Object}
137
+ */
138
+ function getListItemsField(field) {
139
+ // items.type defaults to string
140
+ return _.defaults({}, _.get(field, 'items', {}), {type: 'string'});
141
+ }
142
+
143
+ /**
144
+ * This function invokes the `iterator` function for every field of the `model`.
145
+ * It recursively traverses through fields of type `object` and `list` with
146
+ * items of type `object` and invokes the `iterator` on their child fields,
147
+ * and so on. The traversal is a depth-first and the `iterator` is invoked
148
+ * before traversing the field's child fields.
149
+ *
150
+ * The iterator is invoked with two parameters, `field` and `fieldPath`. The
151
+ * `field` is the currently iterated field, and `fieldPath` is an array of
152
+ * strings indicating the path of the `field` relative to the model.
153
+ *
154
+ * @example
155
+ * model = {
156
+ * fields: [
157
+ * { name: "title", type: "string" },
158
+ * {
159
+ * name: "banner",
160
+ * type: "object",
161
+ * fields: [
162
+ * { name: "logo", type: "image" }
163
+ * ]}
164
+ * {
165
+ * name: "actions",
166
+ * type: "list",
167
+ * items: {
168
+ * type: "object",
169
+ * fields: [
170
+ * {name: "label", type: "string"}
171
+ * ]
172
+ * }
173
+ * }
174
+ * ]
175
+ * }
176
+ * iterateModelFieldsRecursively(model, iterator);
177
+ * // will call the iterator with following field.name and fieldPath
178
+ * - 'title', ['fields', 'title']
179
+ * - 'banner', ['fields', 'banner']
180
+ * - 'logo', ['fields', 'banner', 'fields', 'logo']
181
+ * - 'actions', ['fields', 'actions']
182
+ * - 'label', ['fields', 'actions', 'items', 'fields', 'label']
183
+ *
184
+ * @param {Object} model The root model to iterate fields
185
+ * @param {Function} iterator The iterator function
186
+ * @param {Array} [fieldPath]
187
+ */
188
+ function iterateModelFieldsRecursively(model, iterator, fieldPath = []) {
189
+ const fields = _.get(model, 'fields', []);
190
+ fieldPath = fieldPath.concat('fields');
191
+ _.forEach(fields, (field) => {
192
+ const childFieldPath = fieldPath.concat(field.name);
193
+ iterator(field, childFieldPath);
194
+ if (isObjectField(field)) {
195
+ iterateModelFieldsRecursively(field, iterator, childFieldPath);
196
+ } else if (isListOfObjectsField(field)) {
197
+ iterateModelFieldsRecursively(getListItemsField(field), iterator, childFieldPath.concat('items'));
198
+ }
199
+ });
200
+ }
201
+
202
+ function getUrlPath(stackbitModel) {
203
+ const modelType = getNormalizedModelType(stackbitModel);
204
+ if (modelType === 'page') {
205
+ return _.get(stackbitModel, 'urlPath', '/{slug}');
206
+ }
207
+ return null;
208
+ }
209
+
210
+ function getNormalizedModelType(stackbitModel) {
211
+ return stackbitModel ? (stackbitModel.type === 'config' ? 'data' : stackbitModel.type) : 'object';
212
+ }