apostrophe 3.16.0 → 3.17.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.
Files changed (39) hide show
  1. package/.eslintignore +3 -1
  2. package/CHANGELOG.md +23 -1
  3. package/index.js +5 -4
  4. package/lib/moog.js +14 -1
  5. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +3 -1
  6. package/modules/@apostrophecms/asset/index.js +213 -101
  7. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +3 -3
  8. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +30 -12
  9. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.es5.js +1 -1
  10. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +6 -2
  11. package/modules/@apostrophecms/asset/lib/webpack/utils.js +266 -0
  12. package/modules/@apostrophecms/asset/views/scripts.html +1 -0
  13. package/modules/@apostrophecms/asset/views/stylesheets.html +1 -0
  14. package/modules/@apostrophecms/doc/index.js +64 -0
  15. package/modules/@apostrophecms/doc-type/index.js +35 -0
  16. package/modules/@apostrophecms/i18n/i18n/en.json +2 -0
  17. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +7 -0
  18. package/modules/@apostrophecms/login/index.js +4 -0
  19. package/modules/@apostrophecms/schema/index.js +40 -49
  20. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +67 -0
  21. package/modules/@apostrophecms/template/index.js +14 -5
  22. package/modules/@apostrophecms/template/lib/bundlesLoader.js +158 -0
  23. package/modules/@apostrophecms/template/views/outerLayoutBase.html +6 -0
  24. package/modules/@apostrophecms/widget-type/index.js +21 -0
  25. package/package.json +1 -1
  26. package/test/areas.js +2 -1
  27. package/test/assets.js +307 -3
  28. package/test/docs.js +67 -2
  29. package/test/modules/bundle/index.js +7 -0
  30. package/test/modules/bundle-page/index.js +32 -0
  31. package/test/modules/bundle-page/ui/src/extra.js +1 -0
  32. package/test/modules/bundle-page/ui/src/extra.scss +0 -0
  33. package/test/modules/bundle-page/views/index.html +9 -0
  34. package/test/modules/bundle-page/views/show.html +10 -0
  35. package/test/modules/bundle-widget/index.js +27 -0
  36. package/test/modules/bundle-widget/ui/src/extra2.js +1 -0
  37. package/test/modules/bundle-widget/views/widget.html +1 -0
  38. package/test/pieces.js +38 -12
  39. package/test/public/static-test.txt +0 -1
@@ -2,6 +2,7 @@ const path = require('path');
2
2
  const merge = require('webpack-merge').merge;
3
3
  const scssTask = require('./webpack.scss');
4
4
  const es5Task = require('./webpack.es5');
5
+ const srcBuildNames = [ 'src-build', 'src-es5-build' ];
5
6
 
6
7
  let BundleAnalyzerPlugin;
7
8
 
@@ -10,9 +11,11 @@ if (process.env.APOS_BUNDLE_ANALYZER) {
10
11
  }
11
12
 
12
13
  module.exports = ({
13
- importFile, modulesDir, outputPath, outputFilename, es5
14
+ importFile, modulesDir, outputPath, outputFilename, bundles = {}, es5
14
15
  }, apos) => {
16
+ const mainBundleName = outputFilename.replace('.js', '');
15
17
  const taskFns = [ scssTask ];
18
+
16
19
  if (es5) {
17
20
  taskFns.push(es5Task);
18
21
  }
@@ -20,16 +23,20 @@ module.exports = ({
20
23
  task(
21
24
  {
22
25
  importFile,
23
- modulesDir,
24
- outputFilename
26
+ modulesDir
25
27
  },
26
- apos
28
+ apos,
29
+ srcBuildNames
27
30
  )
28
31
  );
29
32
 
30
- let config = {
31
- entry: importFile,
32
- target: es5 ? 'es5' : 'web',
33
+ const moduleName = es5 ? 'nomodule' : 'module';
34
+ const config = {
35
+ entry: {
36
+ [mainBundleName]: importFile,
37
+ ...bundles
38
+ },
39
+ target: es5 ? [ 'web', 'es5' ] : 'web',
33
40
  mode: process.env.NODE_ENV || 'development',
34
41
  optimization: {
35
42
  minimize: process.env.NODE_ENV === 'production'
@@ -37,11 +44,17 @@ module.exports = ({
37
44
  devtool: 'source-map',
38
45
  output: {
39
46
  path: outputPath,
40
- filename: outputFilename
47
+ filename: ({ chunk }) => {
48
+ return srcBuildNames.includes(chunk.name)
49
+ ? '[name].js'
50
+ : `[name]-${moduleName}-bundle.js`;
51
+ }
41
52
  },
42
53
  resolveLoader: {
43
54
  extensions: [ '*', '.js' ],
44
- modules: [ 'node_modules' ]
55
+ // Make sure css-loader and postcss-loader can always be found, even
56
+ // if npm didn't hoist them
57
+ modules: [ 'node_modules', 'node_modules/apostrophe/node_modules' ]
45
58
  },
46
59
  resolve: {
47
60
  extensions: [ '*', '.js' ],
@@ -50,16 +63,21 @@ module.exports = ({
50
63
  Modules: path.resolve(modulesDir)
51
64
  },
52
65
  modules: [
66
+ 'node_modules',
53
67
  `${apos.npmRootDir}/node_modules`,
54
68
  // Make sure core-js and regenerator-runtime can always be found, even
55
69
  // if npm didn't hoist them
56
70
  `${apos.npmRootDir}/node_modules/apostrophe/node_modules`
57
- ]
71
+ ],
72
+ symlinks: false
58
73
  },
59
74
  stats: 'verbose',
60
75
  plugins: process.env.APOS_BUNDLE_ANALYZER ? [ new BundleAnalyzerPlugin() ] : []
61
76
  };
62
77
 
63
- config = merge(config, ...tasks);
64
- return config;
78
+ if (es5) {
79
+ config.output.chunkFormat = 'array-push';
80
+ }
81
+
82
+ return merge(config, ...tasks);
65
83
  };
@@ -1,6 +1,6 @@
1
1
  module.exports = (options, apos) => {
2
2
  return {
3
- target: 'es5',
3
+ target: [ 'web', 'es5' ],
4
4
  module: {
5
5
  rules: [
6
6
  {
@@ -1,6 +1,6 @@
1
1
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
2
2
 
3
- module.exports = (options, apos) => {
3
+ module.exports = (options, apos, srcBuildNames) => {
4
4
  return {
5
5
  module: {
6
6
  rules: [
@@ -40,7 +40,11 @@ module.exports = (options, apos) => {
40
40
  plugins: [
41
41
  new MiniCssExtractPlugin({
42
42
  // Should be automatic but we wind up with main.css if we try to go with that
43
- filename: options.outputFilename.replace('.js', '.css')
43
+ filename: ({ chunk }) => {
44
+ return srcBuildNames.includes(chunk.name)
45
+ ? '[name].css'
46
+ : '[name]-bundle.css';
47
+ }
44
48
  })
45
49
  ]
46
50
  };
@@ -0,0 +1,266 @@
1
+ const fs = require('fs-extra');
2
+
3
+ module.exports = {
4
+ checkModulesWebpackConfig(modules, t) {
5
+ const allowedProperties = [ 'extensions', 'bundles' ];
6
+ for (const mod of Object.values(modules)) {
7
+ const webpackConfig = mod.__meta.webpack[mod.__meta.name];
8
+
9
+ if (!webpackConfig) {
10
+ continue;
11
+ }
12
+
13
+ if (
14
+ typeof webpackConfig !== 'object' ||
15
+ webpackConfig === null ||
16
+ Array.isArray(webpackConfig) ||
17
+ Object.keys(webpackConfig).some((prop) => !allowedProperties.includes(prop))
18
+ ) {
19
+ const error = t('apostrophe:assetWebpackConfigWarning', {
20
+ module: mod.__meta.name
21
+ });
22
+
23
+ throw new Error(error);
24
+ }
25
+
26
+ if (webpackConfig && webpackConfig.bundles) {
27
+ const bundles = Object.values(webpackConfig.bundles);
28
+
29
+ bundles.forEach(bundle => {
30
+ const bundleProps = Object.keys(bundle);
31
+ if (
32
+ bundleProps.length > 1 ||
33
+ (bundleProps.length === 1 && !bundle.templates) ||
34
+ (bundle.templates && !Array.isArray(bundle.templates))
35
+ ) {
36
+ const error = t('apostrophe:assetWebpackBundlesWarning', {
37
+ module: mod.__meta.name
38
+ });
39
+
40
+ throw new Error(error);
41
+ }
42
+ });
43
+ }
44
+ }
45
+ },
46
+
47
+ async getWebpackExtensions ({
48
+ getMetadata, modulesToInstantiate
49
+ }) {
50
+ const modulesMeta = modulesToInstantiate
51
+ .map((name) => getMetadata(name));
52
+
53
+ const { extensions, foundBundles } = getModulesWebpackConfigs(
54
+ modulesMeta
55
+ );
56
+
57
+ const verifiedBundles = await verifyBundlesEntryPoints(foundBundles);
58
+
59
+ return {
60
+ extensions,
61
+ verifiedBundles
62
+ };
63
+ },
64
+
65
+ fillExtraBundles (verifiedBundles = {}) {
66
+ return Object.entries(verifiedBundles).reduce((acc, [ bundleName, { js, scss } ]) => {
67
+ return {
68
+ js: [
69
+ ...acc.js,
70
+ ...(js.length && !acc.js.includes(bundleName)) ? [ bundleName ] : []
71
+ ],
72
+ css: [
73
+ ...acc.css,
74
+ ...(scss.length && !acc.css.includes(bundleName)) ? [ bundleName ] : []
75
+ ]
76
+ };
77
+ }, {
78
+ js: [],
79
+ css: []
80
+ });
81
+ },
82
+
83
+ getBundlesNames (bundles, es5 = false) {
84
+ return Object.entries(bundles).reduce((acc, [ ext, bundlesNames ]) => {
85
+ const nameExtension = ext === 'css'
86
+ ? '-bundle'
87
+ : '-module-bundle';
88
+
89
+ const es5Bundles = es5 && ext === 'js'
90
+ ? bundlesNames.map((name) => `${name}-nomodule-bundle.${ext}`)
91
+ : [];
92
+
93
+ return [
94
+ ...acc,
95
+ ...bundlesNames.map((name) => `${name}${nameExtension}.${ext}`),
96
+ ...es5Bundles
97
+ ];
98
+ }, []);
99
+ },
100
+
101
+ writeBundlesImportFiles ({
102
+ name,
103
+ buildDir,
104
+ mainBundleName,
105
+ verifiedBundles,
106
+ getImportFileOutput,
107
+ writeImportFile
108
+ }) {
109
+ if (!name.includes('src')) {
110
+ return [];
111
+ }
112
+
113
+ const bundlesOutputs = Object.entries(verifiedBundles)
114
+ .map(([ bundleName, paths ]) => {
115
+ return {
116
+ bundleName,
117
+ importFile: `${buildDir}/${bundleName}-import.js`,
118
+ js: getImportFileOutput(paths.js, {
119
+ invokeApps: true,
120
+ enumerateImports: true,
121
+ requireDefaultExport: true
122
+ }),
123
+ scss: getImportFileOutput(paths.scss, {
124
+ enumerateImports: true,
125
+ importSuffix: 'Stylesheet'
126
+ })
127
+ };
128
+ });
129
+
130
+ for (const output of bundlesOutputs) {
131
+ writeImportFile({
132
+ importFile: output.importFile,
133
+ indexJs: output.js,
134
+ indexSass: output.scss
135
+ });
136
+ }
137
+
138
+ return bundlesOutputs.reduce((acc, { bundleName, importFile }) => {
139
+ return {
140
+ ...acc,
141
+ [bundleName]: {
142
+ import: importFile,
143
+ dependOn: mainBundleName
144
+ }
145
+ };
146
+ }, {});
147
+ }
148
+ };
149
+
150
+ function getModulesWebpackConfigs (modulesMeta) {
151
+ const { extensions, bundles } = modulesMeta.reduce((acc, meta) => {
152
+ const { webpack, __meta } = meta;
153
+
154
+ const configs = formatConfigs(__meta.chain, webpack);
155
+
156
+ if (!configs.length) {
157
+ return acc;
158
+ }
159
+
160
+ const moduleBundles = configs.reduce((acc, conf) => {
161
+ return {
162
+ ...acc,
163
+ ...conf.bundles
164
+ };
165
+ }, {});
166
+
167
+ return {
168
+ extensions: {
169
+ ...acc.extensions,
170
+ ...configs.reduce((acc, config) => ({
171
+ ...acc,
172
+ ...config.extensions
173
+ }), {})
174
+ },
175
+ bundles: {
176
+ ...acc.bundles,
177
+ ...moduleBundles
178
+ }
179
+ };
180
+ }, {
181
+ extensions: {},
182
+ bundles: {}
183
+ });
184
+
185
+ return {
186
+ extensions,
187
+ foundBundles: flattenBundles(bundles)
188
+ };
189
+ };
190
+
191
+ async function verifyBundlesEntryPoints (bundles) {
192
+ const checkPathsPromises = bundles.map(async ({ bundleName, modulePath }) => {
193
+ const jsPath = `${modulePath}/ui/src/${bundleName}.js`;
194
+ const scssPath = `${modulePath}/ui/src/${bundleName}.scss`;
195
+
196
+ const jsFileExists = await fs.pathExists(jsPath);
197
+ const scssFileExists = await fs.pathExists(scssPath);
198
+
199
+ return {
200
+ bundleName,
201
+ ...jsFileExists && { jsPath: jsPath },
202
+ ...scssFileExists && { scssPath: scssPath }
203
+ };
204
+ });
205
+
206
+ const bundlesPaths = await Promise.all(checkPathsPromises);
207
+
208
+ const packedFilesByBundle = bundlesPaths.reduce((acc, {
209
+ bundleName, jsPath, scssPath
210
+ }) => {
211
+ if (!jsPath && !scssPath) {
212
+ return acc;
213
+ }
214
+
215
+ return {
216
+ ...acc,
217
+ [bundleName]: {
218
+ js: [
219
+ ...acc[bundleName] ? acc[bundleName].js : [],
220
+ ...jsPath ? [ jsPath ] : []
221
+ ],
222
+ scss: [
223
+ ...acc[bundleName] ? acc[bundleName].scss : [],
224
+ ...scssPath ? [ scssPath ] : []
225
+ ]
226
+ }
227
+ };
228
+ }, {});
229
+
230
+ return packedFilesByBundle;
231
+ };
232
+
233
+ function formatConfigs (chain, webpackConfigs) {
234
+ return Object.entries(webpackConfigs)
235
+ .map(([ name, config ], i) => {
236
+
237
+ if (!config) {
238
+ return null;
239
+ }
240
+
241
+ const { bundles = {}, extensions = {} } = config;
242
+
243
+ return {
244
+ extensions,
245
+ bundles: {
246
+ [name]: {
247
+ bundleNames: Object.keys(bundles),
248
+ modulePath: chain[i].dirname
249
+ }
250
+ }
251
+ };
252
+ }).filter((config) => config);
253
+ }
254
+
255
+ function flattenBundles (bundles) {
256
+ return Object.values(bundles)
257
+ .reduce((acc, { bundleNames, modulePath }) => {
258
+ return [
259
+ ...acc,
260
+ ...bundleNames.map((bundleName) => ({
261
+ bundleName,
262
+ modulePath
263
+ }))
264
+ ];
265
+ }, []);
266
+ }
@@ -0,0 +1 @@
1
+ {{ data.placeholder }}
@@ -0,0 +1 @@
1
+ {{ data.placeholder }}
@@ -329,6 +329,10 @@ module.exports = {
329
329
  updatedAt: -1,
330
330
  aposLocale: 1
331
331
  }, {});
332
+ await self.db.createIndex({
333
+ relatedReverseIds: 1,
334
+ aposLocale: 1
335
+ }, {});
332
336
  await self.db.createIndex({ 'advisoryLock._id': 1 }, {});
333
337
  await self.createTextIndex();
334
338
  await self.db.createIndex({ parkedId: 1 }, {});
@@ -1093,6 +1097,66 @@ module.exports = {
1093
1097
  }
1094
1098
  });
1095
1099
  },
1100
+
1101
+ // Iterate through the document fields and call the provided handlers
1102
+ // for each item of an array, object and relationship field type.
1103
+ walkByMetaType(doc, handlers) {
1104
+ const defaultHandlers = {
1105
+ arrayItem: () => {},
1106
+ object: () => {},
1107
+ relationship: () => {}
1108
+ };
1109
+
1110
+ handlers = {
1111
+ ...defaultHandlers,
1112
+ ...handlers
1113
+ };
1114
+
1115
+ if (doc.metaType === 'doc') {
1116
+ const manager = self.getManager(doc.type);
1117
+ if (!manager) {
1118
+ return;
1119
+ }
1120
+ forSchema(manager.schema, doc);
1121
+ } else if (doc.metaType === 'widget') {
1122
+ const manager = self.apos.area.getWidgetManager(doc.type);
1123
+ if (!manager) {
1124
+ return;
1125
+ }
1126
+ forSchema(manager.schema, doc);
1127
+ }
1128
+
1129
+ function forSchema(schema, doc) {
1130
+ for (const field of schema) {
1131
+ if (field.type === 'area' && doc[field.name] && doc[field.name].items) {
1132
+ for (const widget of doc[field.name].items) {
1133
+ self.walkByMetaType(widget, {
1134
+ arrayItem: handlers.arrayItem,
1135
+ object: handlers.object,
1136
+ relationship: handlers.relationship
1137
+ });
1138
+ }
1139
+ } else if (field.type === 'array') {
1140
+ if (doc[field.name]) {
1141
+ doc[field.name].forEach(item => {
1142
+ handlers.arrayItem(field, item);
1143
+ forSchema(field.schema, item);
1144
+ });
1145
+ }
1146
+ } else if (field.type === 'object') {
1147
+ const value = doc[field.name];
1148
+ if (value) {
1149
+ handlers.object(field, value);
1150
+ forSchema(field.schema, value);
1151
+ }
1152
+ } else if (field.type === 'relationship') {
1153
+ if (Array.isArray(doc[field.name])) {
1154
+ handlers.relationship(field, doc);
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1159
+ },
1096
1160
  ...require('./lib/legacy-migrations')(self)
1097
1161
  };
1098
1162
  }
@@ -94,6 +94,29 @@ module.exports = {
94
94
  handlers(self) {
95
95
  return {
96
96
  beforeSave: {
97
+ async updateBacklinks(req, doc) {
98
+ const relatedDocsIds = self.getRelatedDocsIds(req, doc);
99
+
100
+ // Remove all references to the doc
101
+ await self.apos.doc.db.updateMany({
102
+ relatedReverseIds: { $in: [ doc.aposDocId ] },
103
+ aposLocale: { $in: [ doc.aposLocale, null ] }
104
+ }, {
105
+ $pull: { relatedReverseIds: doc.aposDocId }
106
+ });
107
+
108
+ if (!relatedDocsIds.length) {
109
+ return;
110
+ }
111
+
112
+ // Add doc reference to all related docs
113
+ await self.apos.doc.db.updateMany({
114
+ aposDocId: { $in: relatedDocsIds },
115
+ aposLocale: { $in: [ doc.aposLocale, null ] }
116
+ }, {
117
+ $push: { relatedReverseIds: doc.aposDocId }
118
+ });
119
+ },
97
120
  prepareForStorage(req, doc) {
98
121
  self.apos.schema.prepareForStorage(req, doc);
99
122
  },
@@ -253,6 +276,18 @@ module.exports = {
253
276
 
254
277
  methods(self) {
255
278
  return {
279
+ getRelatedDocsIds(req, doc) {
280
+ const relatedDocsIds = [];
281
+ const handlers = {
282
+ relationship: (field, doc) => {
283
+ relatedDocsIds.push(...doc[field.name].map(relatedDoc => self.apos.doc.toAposDocId(relatedDoc)));
284
+ }
285
+ };
286
+
287
+ self.apos.doc.walkByMetaType(doc, handlers);
288
+
289
+ return relatedDocsIds;
290
+ },
256
291
  sanitizeFieldList(choices) {
257
292
  if ((typeof choices) === 'string') {
258
293
  return choices.split(/\s*,\s*/);
@@ -30,6 +30,8 @@
30
30
  "areYouSure": "Are You Sure?",
31
31
  "assetTypeBuildComplete": "👍 {{ label }} is complete!",
32
32
  "assetTypeBuilding": "🧑‍💻 Building the {{ label }}...",
33
+ "assetWebpackConfigWarning": "⚠️ In the module {{ module }}, your webpack config is incorrect. It must be an object and should contain only two properties extensions and bundles.",
34
+ "assetWebpackBundlesWarning": "⚠️ In the module {{ module }} your webpack config is incorrect. Each bundle can only have one property 'templates' that must be an array of strings.",
33
35
  "back": "Back",
34
36
  "backToHome": "Back to Home",
35
37
  "basics": "Basics",
@@ -717,6 +717,13 @@ export default {
717
717
  ...getRelatedBySchema(value, field.schema)
718
718
  ];
719
719
  }
720
+ } else if (field.type === 'object') {
721
+ if (object[field.name]) {
722
+ related = [
723
+ ...related,
724
+ ...getRelatedBySchema(object[field.name], field.schema)
725
+ ];
726
+ }
720
727
  } else if (field.type === 'area') {
721
728
  for (const widget of (object[field.name]?.items || [])) {
722
729
  related = [
@@ -682,6 +682,10 @@ module.exports = {
682
682
 
683
683
  // Awaitable wrapper for req.login. An implementation detail of the login route
684
684
  async passportLogin(req, user) {
685
+ const cookieName = `${self.apos.shortName}.${loggedInCookieName}`;
686
+ if (req.cookies[cookieName] !== 'true') {
687
+ req.res.cookie(cookieName, 'true');
688
+ }
685
689
  const passportLogin = (user) => {
686
690
  return require('util').promisify(function(user, callback) {
687
691
  return req.login(user, callback);
@@ -790,6 +790,7 @@ module.exports = {
790
790
  self.addFieldType({
791
791
  name: 'object',
792
792
  async convert(req, field, data, destination) {
793
+ data = data[field.name];
793
794
  const schema = field.schema;
794
795
  const errors = [];
795
796
  const result = {
@@ -808,6 +809,8 @@ module.exports = {
808
809
  });
809
810
  }
810
811
  }
812
+ result.metaType = 'objectItem';
813
+ result.scopedObjectName = field.scopedObjectName;
811
814
  destination[field.name] = result;
812
815
  if (errors.length) {
813
816
  throw errors;
@@ -821,6 +824,11 @@ module.exports = {
821
824
  };
822
825
  self.register(metaType, type, field.schema);
823
826
  },
827
+ validate: function (field, options, warn, fail) {
828
+ for (const subField of field.schema || field.fields.add) {
829
+ self.validateField(subField, options);
830
+ }
831
+ },
824
832
  isEqual(req, field, one, two) {
825
833
  if (one && (!two)) {
826
834
  return false;
@@ -831,6 +839,15 @@ module.exports = {
831
839
  if (!(one || two)) {
832
840
  return true;
833
841
  }
842
+ if (one[field.name] && (!two[field.name])) {
843
+ return false;
844
+ }
845
+ if (two[field.name] && (!one[field.name])) {
846
+ return false;
847
+ }
848
+ if (!(one[field.name] || two[field.name])) {
849
+ return true;
850
+ }
834
851
  return self.isEqual(req, field.schema, one[field.name], two[field.name]);
835
852
  },
836
853
  def: {}
@@ -1952,59 +1969,31 @@ module.exports = {
1952
1969
  // Currently `req` does not impact this, but that may change.
1953
1970
 
1954
1971
  prepareForStorage(req, doc) {
1955
- if (doc.metaType === 'doc') {
1956
- const manager = self.apos.doc.getManager(doc.type);
1957
- if (!manager) {
1958
- return;
1959
- }
1960
- forSchema(manager.schema, doc);
1961
- } else if (doc.metaType === 'widget') {
1962
- const manager = self.apos.area.getWidgetManager(doc.type);
1963
- if (!manager) {
1964
- return;
1965
- }
1966
- forSchema(manager.schema, doc);
1967
- }
1968
- function forSchema(schema, doc) {
1969
- for (const field of schema) {
1970
- if (field.type === 'area') {
1971
- if (doc[field.name] && doc[field.name].items) {
1972
- for (const widget of doc[field.name].items) {
1973
- self.prepareForStorage(req, widget);
1974
- }
1975
- }
1976
- } else if (field.type === 'array') {
1977
- if (doc[field.name]) {
1978
- doc[field.name].forEach(item => {
1979
- item._id = item._id || self.apos.util.generateId();
1980
- item.metaType = 'arrayItem';
1981
- item.scopedArrayName = field.scopedArrayName;
1982
- forSchema(field.schema, item);
1983
- });
1984
- }
1985
- } else if (field.type === 'object') {
1986
- const value = doc[field.name];
1987
- if (value) {
1988
- value.metaType = 'object';
1989
- value.scopedObjectName = field.scopedObjectName;
1990
- forSchema(field.schema, value);
1991
- }
1992
- } else if (field.type === 'relationship') {
1993
- if (Array.isArray(doc[field.name])) {
1994
- doc[field.idsStorage] = doc[field.name].map(relatedDoc => self.apos.doc.toAposDocId(relatedDoc));
1995
- if (field.fieldsStorage) {
1996
- const fieldsById = doc[field.fieldsStorage] || {};
1997
- for (const relatedDoc of doc[field.name]) {
1998
- if (relatedDoc._fields) {
1999
- fieldsById[self.apos.doc.toAposDocId(relatedDoc)] = relatedDoc._fields;
2000
- }
2001
- }
2002
- doc[field.fieldsStorage] = fieldsById;
1972
+ const handlers = {
1973
+ arrayItem: (field, object) => {
1974
+ object._id = object._id || self.apos.util.generateId();
1975
+ object.metaType = 'arrayItem';
1976
+ object.scopedArrayName = field.scopedArrayName;
1977
+ },
1978
+ object: (field, object) => {
1979
+ object.metaType = 'object';
1980
+ object.scopedObjectName = field.scopedObjectName;
1981
+ },
1982
+ relationship: (field, doc) => {
1983
+ doc[field.idsStorage] = doc[field.name].map(relatedDoc => self.apos.doc.toAposDocId(relatedDoc));
1984
+ if (field.fieldsStorage) {
1985
+ const fieldsById = doc[field.fieldsStorage] || {};
1986
+ for (const relatedDoc of doc[field.name]) {
1987
+ if (relatedDoc._fields) {
1988
+ fieldsById[self.apos.doc.toAposDocId(relatedDoc)] = relatedDoc._fields;
2003
1989
  }
2004
1990
  }
1991
+ doc[field.fieldsStorage] = fieldsById;
2005
1992
  }
2006
1993
  }
2007
- }
1994
+ };
1995
+
1996
+ self.apos.doc.walkByMetaType(doc, handlers);
2008
1997
  },
2009
1998
 
2010
1999
  // Add a new field type. The `type` object may contain the following properties:
@@ -2540,6 +2529,8 @@ module.exports = {
2540
2529
  item._id = self.apos.util.generateId();
2541
2530
  self.regenerateIds(req, field.schema, item);
2542
2531
  }
2532
+ } else if (field.type === 'object') {
2533
+ this.regenerateIds(req, field.schema, doc[field.name] || {});
2543
2534
  } else if (field.type === 'area') {
2544
2535
  if (doc[field.name]) {
2545
2536
  doc[field.name]._id = self.apos.util.generateId();