apostrophe 3.5.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +96 -3
  4. package/README.md +1 -1
  5. package/index.js +108 -3
  6. package/lib/moog-require.js +23 -0
  7. package/lib/moog.js +1 -0
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
  9. package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
  10. package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +2 -2
  12. package/modules/@apostrophecms/asset/index.js +77 -13
  13. package/modules/@apostrophecms/attachment/index.js +1 -0
  14. package/modules/@apostrophecms/db/index.js +5 -6
  15. package/modules/@apostrophecms/doc/index.js +2 -0
  16. package/modules/@apostrophecms/doc-type/index.js +39 -16
  17. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  18. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
  19. package/modules/@apostrophecms/i18n/i18n/en.json +19 -6
  20. package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
  21. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
  22. package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
  23. package/modules/@apostrophecms/i18n/index.js +10 -1
  24. package/modules/@apostrophecms/image/index.js +2 -1
  25. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  26. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -1
  27. package/modules/@apostrophecms/image-widget/index.js +2 -1
  28. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  29. package/modules/@apostrophecms/job/index.js +164 -212
  30. package/modules/@apostrophecms/login/index.js +1 -16
  31. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +5 -0
  32. package/modules/@apostrophecms/migration/index.js +1 -1
  33. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  34. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
  35. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  36. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  37. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  38. package/modules/@apostrophecms/notification/index.js +116 -8
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  40. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  41. package/modules/@apostrophecms/page/index.js +37 -30
  42. package/modules/@apostrophecms/permission/index.js +1 -1
  43. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
  44. package/modules/@apostrophecms/piece-type/index.js +178 -61
  45. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
  46. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  47. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  48. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
  49. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +1 -3
  50. package/modules/@apostrophecms/schema/index.js +97 -20
  51. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  52. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
  53. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  54. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
  55. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  56. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  57. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  58. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
  59. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
  60. package/modules/@apostrophecms/task/index.js +2 -2
  61. package/modules/@apostrophecms/template/index.js +63 -36
  62. package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
  63. package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
  64. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -2
  65. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  66. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  67. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  68. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  69. package/modules/@apostrophecms/util/index.js +2 -2
  70. package/modules/@apostrophecms/util/ui/src/http.js +12 -8
  71. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  72. package/modules/@apostrophecms/widget-type/index.js +1 -1
  73. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
  74. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  75. package/package.json +3 -3
  76. package/test/extra_node_modules/improve-global/index.js +7 -0
  77. package/test/extra_node_modules/improve-piece-type/index.js +7 -0
  78. package/test/improve-overrides.js +30 -0
  79. package/test/job.js +224 -0
  80. package/test/modules/@apostrophecms/global/index.js +8 -0
  81. package/test/modules/fragment-all/views/aux-test.html +7 -0
  82. package/test/modules/fragment-all/views/fragment.html +5 -0
  83. package/test/package.json +5 -4
  84. package/test/pieces.js +34 -0
  85. package/test/reverse-relationship.js +170 -0
  86. package/test/templates.js +7 -1
  87. package/test-lib/test.js +23 -12
  88. package/test-lib/util.js +33 -0
package/.eslintrc CHANGED
@@ -10,6 +10,10 @@
10
10
  "no-var": "error",
11
11
  "no-console": 0,
12
12
  "multiline-ternary": "off",
13
+ "no-unused-vars": [
14
+ "error",
15
+ { "varsIgnorePattern": "^_.", "args": "none" }
16
+ ],
13
17
  "vue/no-deprecated-destroyed-lifecycle": 0,
14
18
  "vue/v-on-event-hyphenation": 1,
15
19
  "vue/custom-event-name-casing": ["warn", "kebab-case"],
package/.scratch.md ADDED
@@ -0,0 +1,2 @@
1
+ - `/deep/ .apos-button` to `:deep(.apos-button)`
2
+ - Remove v-popover, v-tooltip, VueClickOutsideElement
package/CHANGELOG.md CHANGED
@@ -1,9 +1,102 @@
1
1
  # Changelog
2
2
 
3
- ## 3.5.0 - 2021-09-23
3
+ ## 3.8.1 - 2021-11-23
4
+
5
+ ### Fixes
6
+
7
+ * The search field of the pieces manager modal works properly. Thanks to [Miro Yovchev](https://github.com/myovchev) for pointing out the issue and providing a solution.
8
+ * Fixes a bug in `AposRichTextWidgetEditor.vue` when a rich text widget was specifically configured with an empty array as the `styles` option. In that case a new empty rich text widget will initiate with an empty paragraph tag.
9
+ * The`fieldsPresent` method that is used with the `presentFieldsOnly` option in doc-type was broken, looking for properties in strings and wasn't returning anything.
10
+
11
+ ## 3.8.0 - 2021-11-15
12
+
13
+ ### Adds
14
+
15
+ * Checkboxes for pieces are back, a main checkbox allows to select all page items. When all pieces on a page are checked, a banner where the user can select all pieces appears. A launder for mongo projections has been added.
16
+ * Registered `batchOperations` on a piece-type will now become buttons in the manager batch operations "more menu" (styled as a kebab icon). Batch operations should include a label, `messages` object, and `modalOptions` for the confirmation modal.
17
+ * `batchOperations` can be grouped into a single button with a menu using the `group` cascade subproperty.
18
+ * `batchOperations` can be conditional with an `if` conditional object. This allows developers to pass a single value or an array of values.
19
+ * Piece types can have `utilityOperations` configured as a top-level cascade property. These operations are made available in the piece manager as new buttons.
20
+ * Notifications may now include an `event` property, which the AposNotification component will emit on mount. The `event` property should be set to an object with `name` (the event name) and optionally `data` (data included with the event emission).
21
+ * Adds support for using the attachments query builder in REST API calls via the query string.
22
+ * Adds contextual menu for pieces, any module extending the piece-type one can add actions in this contextual menu.
23
+ * When clicking on a batch operation, it opens a confirmation modal using modal options from the batch operation, it also works for operations in grouped ones. operations name property has been renamed in action to work with AposContextMenu component.
24
+ * Beginning with this release, a module-specific static asset in your project such as `modules/mymodulename/public/images/bg.png` can always be referenced in your `.scss` and `.css` files as `/modules/mymodulename/images/bg.png`, even if assets are actually being deployed to S3, CDNs, etc. Note that `public` and `ui/public` module subdirectories have separate functions. See the documentation for more information.
25
+ * Adds AposFile.vue component to abstract file dropzone UI, uses it in AposInputAttachment, and uses it in the confirmation modal for pieces import.
26
+ * Optionally add `dimensionAttrs` option to image widget, which sets width & height attributes to optimize for Cumulative Layout Shift. Thank you to [Qiao Lin](https://github.com/qclin) for the contribution.
27
+
28
+ ### Fixes
29
+
30
+ * The `apos.util.attachmentUrl` method now works correctly. To facilitate that, `apos.uploadsUrl` is now populated browser-side at all times as the frontend logic originally expected. For backwards compatibility `apos.attachment.uploadsUrl` is still populated when logged in.
31
+ * Widget players are now prevented from being played twice by the implementing vue component.
32
+
33
+ ### Changes
34
+ * Removes Apostrophe 2 documentation and UI configuration from the `@apostrophecms/job` module. These options were not yet in use for A3.
35
+ * Renames methods and removes unsupported routes in the `@apostrophecms/job` module that were not yet in use. This was not done lightly, but specifically because of the minimal likelihood that they were in use in project code given the lack of UI support.
36
+ * The deprecated `cancel` route was removed and will likely be replaced at a later date.
37
+ * `run` was renamed `runBatch` as its purpose is specifically to run processes on a "batch selected" array of pieces or pages.
38
+ * `runNonBatch` was renamed to `run` as it is the more generic job-running method. It is likely that `runBatch` will eventually be refactored to use this method.
39
+ * The `good` and `bad` methods are renamed `success` and `failure`, respectively. The expected methods used in the `run` method were similarly renamed. They still increment job document properties called `good` and `bad`.
40
+ * Comments out the unused `batchSimpleRoute` methods in the page and piece-type modules to avoid usage before they are fully implemented.
41
+ * Optionally add `dimensionAttrs` option to image widget, which sets width & height attributes to optimize for Cumulative Layout Shift.
42
+ * Temporarily removes `npm audit` from our automated tests because of a sub-dependency of uploadfs that doesn't actually cause a security vulnerability for apostrophe.
43
+
44
+ ## 3.7.0 - 2021-10-28
45
+
46
+ ### Adds
47
+
48
+ * Schema select field choices can now be populated by a server side function, like an API call. Set the `choices` property to a method name of the calling module. That function should take a single argument of `req`, and return an array of objects with `label` and `value` properties. The function can be async and will be awaited.
49
+ * Apostrophe now has built-in support for the Node.js cluster module. If the `APOS_CLUSTER_PROCESSES` environment variable is set to a number, that number of child processes are forked, sharing the same listening port. If the variable is set to `0`, one process is forked for each CPU core, with a minimum of `2` to provide availability during restarts. If the variable is set to a negative number, that number is added to the number of CPU cores, e.g. `-1` is a good way to reserve one core for MongoDB if it is running on the same server. This is for production use only (`NODE_ENV=production`). If a child process fails it is restarted automatically.
4
50
 
5
51
  ### Fixes
6
52
 
53
+ * Prevents double-escaping interpolated localization strings in the UI.
54
+ * Rich text editor style labels are now run through a localization method to get the translated strings from their l10n keys.
55
+ * Fixes README Node version requirement (Node 12+).
56
+ * The text alignment buttons now work immediately in a new rich text widget. Previously they worked only after manually setting a style or refreshing the page. Thanks to Michelin for their support of this fix.
57
+ * Users can now activate the built-in date and time editing popups of modern browsers when using the `date` and `time` schema field types.
58
+ * Developers can now `require` their project `app.js` file in the Node.js REPL for debugging and inspection. Thanks to [Matthew Francis Brunetti](https://github.com/zenflow).
59
+ * If a static text phrase is unavailable in both the current locale and the default locale, Apostrophe will always fall back to the `en` locale as a last resort, which ensures the admin UI works if it has not been translated.
60
+ * Developers can now `require` their project `app.js` in the Node.js REPL for debugging and inspection
61
+ * Ensure array field items have valid _id prop before storing. Thanks to Thanks to [Matthew Francis Brunetti](https://github.com/zenflow).
62
+
63
+ ### Changes
64
+
65
+ * In 3.x, `relationship` fields have an optional `builders` property, which replaces `filters` from 2.x, and within that an optional `project` property, which replaces `projection` from 2.x (to match MongoDB's `cursor.project`). Prior to this release leaving the old syntax in place could lead to severe performance problems due to a lack of projections. Starting with this release the 2.x syntax results in an error at startup to help the developer correct their code.
66
+ * The `className` option from the widget options in a rich text area field is now also applied to the rich text editor itself, for a consistently WYSIWYG appearance when editing and when viewing. Thanks to [Max Mulatz](https://github.com/klappradla) for this contribution.
67
+ * Adds deprecation notes to doc module `afterLoad` events, which are deprecated.
68
+ * Removes unused `afterLogin` method in the login module.
69
+
70
+ ## 3.6.0 - 2021-10-13
71
+
72
+ ### Adds
73
+
74
+ * The `context-editing` apostrophe admin UI bus event can now take a boolean parameter, explicitly indicating whether the user is actively typing or performing a similar active manipulation of controls right now. If a boolean parameter is not passed, the existing 1100-millisecond debounced timeout is used.
75
+ * Adds 'no-search' modifier to relationship fields as a UI simplification option.
76
+ * Fields can now have their own `modifiers` array. This is combined with the schema modifiers, allowing for finer grained control of field rendering.
77
+ * Adds a Slovak localization file. Activate the `sk` locale to use this. Many thanks to [Michael Huna](https://github.com/Miselrkba) for the contribution.
78
+ * Adds a Spanish localization file. Activate the `es` locale to use this. Many thanks to [Eugenio Gonzalez](https://github.com/egonzalezg9) for the contribution.
79
+ * Adds a Brazilian Portuguese localization file. Activate the `pt-BR` locale to use this. Many thanks to [Pietro Rutzen](https://github.com/pietro-rutzen) for the contribution.
80
+
81
+ ### Fixes
82
+
83
+ * Fixed missing translation for "New Piece" option on the "more" menu of the piece manager, seen when using it as a chooser.
84
+ * Piece types with relationships to multiple other piece types may now be configured in any order, relative to the other piece types. This sometimes appeared to be a bug in reverse relationships.
85
+ * Code at the project level now overrides code found in modules that use `improve` for the same module name. For example, options set by the `@apostrophecms/seo-global` improvement that ships with `@apostrophecms/seo` can now be overridden at project level by `/modules/@apostrophecms/global/index.js` in the way one would expect.
86
+ * Array input component edit button label is now propertly localized.
87
+ * A memory leak on each request has been fixed, and performance improved, by avoiding the use of new Nunjucks environments for each request. Thanks to Miro Yovchev for pointing out the leak.
88
+ * Fragments now have access to `__t()`, `getOptions` and other features passed to regular templates.
89
+ * Fixes field group cascade merging, using the original group label if none is given in the new field group configuration.
90
+ * If a field is conditional (using an `if` option), is required, but the condition has not been met, it no longer throws a validation error.
91
+ * Passing `busy: true` to `apos.http.post` and related methods no longer produces an error if invoked when logged out, however note that there will likely never be a UI for this when logged out, so indicate busy state in your own way.
92
+ * Bugs in document modification detection have been fixed. These bugs caused edge cases where modifications were not detected and the "Update" button did not appear, and could cause false positives as well.
93
+
94
+ ### Changes
95
+
96
+ * No longer logs a warning about no users if `testModule` is true on the app.
97
+
98
+ ## 3.5.0 - 2021-09-23
99
+
7
100
  * Pinned dependency on `vue-material-design-icons` to fix `apos-build.js` build error in production.
8
101
  * The file size of uploaded media is visible again when selected in the editor, and media information such as upload date, dimensions and file size is now properly localized.
9
102
  * Fixes moog error messages to reflect the recommended pattern of customization functions only taking `self` as an argument.
@@ -18,6 +111,7 @@
18
111
  ### Changes
19
112
 
20
113
  * Cascade grouping (e.g., grouping fields) will now concatenate a group's field name array with the field name array of an existing group of the same name. Put simply, if a new piece module adds their custom fields to a `basics` group, that field will be added to the default `basics` group fields. Previously the new group would have replaced the old, leaving inherited fields in the "Ungrouped" section.
114
+ * AposButton's `block` modifier now less login-specific
21
115
 
22
116
  ### Adds
23
117
 
@@ -133,8 +227,7 @@ No changes. Publishing to correctly mark the latest 3.x release as "latest" in n
133
227
  can include the widget on the clipboard, a special Clipboard widget will appear in area's Add UI. This works across pages as well.
134
228
 
135
229
  ### Changes
136
- * Apostrophe's Global's UI (the @apostrophecms/global singleton has moved from the admin bar's content controls to
137
- the admin utility tray under a cog icon.
230
+ * Apostrophe's Global's UI (the @apostrophecms/global singleton has moved from the admin bar's content controls to the admin utility tray under a cog icon.
138
231
  * The context bar's document Edit button, which was a cog icon, has been rolled into the doc's context menu.
139
232
 
140
233
  ## 3.1.3 - 2021-07-16
package/README.md CHANGED
@@ -43,7 +43,7 @@ We recommend installing the following with [Homebrew](https://brew.sh/) on macOS
43
43
 
44
44
  | Software | Minimum Version | Notes
45
45
  | ------------- | ------------- | -----
46
- | Node.js | 10.x | Or better
46
+ | Node.js | 12.x | Or better
47
47
  | npm | 6.x | Or better
48
48
  | MongoDB | 3.6 | Or better
49
49
  | Imagemagick | Any | Faster image uploads, GIF support (optional)
package/index.js CHANGED
@@ -2,11 +2,43 @@ const path = require('path');
2
2
  const _ = require('lodash');
3
3
  const argv = require('boring')({ end: true });
4
4
  const fs = require('fs');
5
+ const { stripIndent } = require('common-tags');
6
+ const cluster = require('cluster');
7
+ const { cpus } = require('os');
8
+ const process = require('process');
5
9
  const npmResolve = require('resolve');
10
+
6
11
  let defaults = require('./defaults.js');
7
- const { stripIndent } = require('common-tags');
8
12
 
9
- // **Awaiting the Apostrophe function is optional**
13
+ // ## Top-level options
14
+ //
15
+ // `cluster`
16
+ //
17
+ // If set to `true`, Apostrophe will spawn as many processes as
18
+ // there are CPU cores on the server, or a minimum of 2, and balance
19
+ // incoming connections among them. This ensures availability while one
20
+ // process is restarting due to a crash and also increases scalability if
21
+ // the server has multiple CPU cores.
22
+ //
23
+ // If set to an object with a `processes` property, that many
24
+ // processes are started. If `processes` is 0 or a negative number,
25
+ // it is added to the number of CPU cores reported by the server.
26
+ // Notably, `-1` can be a good way to reserve one CPU core for MongoDB
27
+ // in a single-server deployment.
28
+ //
29
+ // However when in cluster mode no fewer than 2 processes will be
30
+ // started as there is no availability benefit without at least 2.
31
+ //
32
+ // If a child process exits with a failure status code it will be
33
+ // restarted. However, if it exits in less than 20 seconds after
34
+ // startup there will be a 20 second delay to avoid flooding logs
35
+ // and pinning the CPU.
36
+ //
37
+ // Alternatively the `APOS_CLUSTER_PROCESSES` environment variable
38
+ // can be set to a number, which will effectively set the cluster
39
+ // option to `cluster: { processes: n }`.
40
+ //
41
+ // ## Awaiting the Apostrophe function
10
42
  //
11
43
  // The apos function is async, but in typical cases you do not
12
44
  // need to await it. If you simply call it, Apostrophe will
@@ -21,8 +53,63 @@ const { stripIndent } = require('common-tags');
21
53
  // To avoid exiting on errors, pass the `exit: false` option.
22
54
  // This can option also can be used to allow awaiting a command line
23
55
  // task, as they also normally exit on completion.
56
+ //
57
+ // If `options.cluster` is truthy, the function quickly resolves to
58
+ // `null` in the primary process. In the child process it resolves as
59
+ // documented above.
24
60
 
25
61
  module.exports = async function(options) {
62
+ const guardTime = 20000;
63
+ if (process.env.APOS_CLUSTER_PROCESSES) {
64
+ options.cluster = {
65
+ processes: parseInt(process.env.APOS_CLUSTER_PROCESSES)
66
+ };
67
+ }
68
+ if (options.cluster && (process.env.NODE_ENV !== 'production')) {
69
+ console.log('NODE_ENV is not set to production, disabling cluster mode');
70
+ options.cluster = false;
71
+ }
72
+ if (options.cluster && !argv._.length) {
73
+ // For bc with node 14 and below we need to check both
74
+ if (cluster.isPrimary || cluster.isMaster) {
75
+ let processes = options.cluster.processes || cpus().length;
76
+ if (processes <= 0) {
77
+ processes = cpus().length + processes;
78
+ }
79
+ let capped = '';
80
+ if (processes > cpus().length) {
81
+ processes = cpus().length;
82
+ capped = ' (capped to number of CPU cores)';
83
+ }
84
+ if (processes < 2) {
85
+ processes = 2;
86
+ if (capped) {
87
+ capped = ' (less than 2 cores, capped to minimum of 2)';
88
+ } else {
89
+ capped = ' (using minimum of 2)';
90
+ }
91
+ }
92
+ console.log(`Starting ${processes} cluster child processes${capped}`);
93
+ for (let i = 0; i < processes; i++) {
94
+ clusterFork();
95
+ }
96
+ cluster.on('exit', (worker, code, signal) => {
97
+ if (code !== 0) {
98
+ if ((Date.now() - worker.bornAt) < guardTime) {
99
+ console.error(`Worker process ${worker.process.pid} failed in ${seconds(Date.now() - worker.bornAt)}, waiting ${seconds(guardTime)} before restart`);
100
+ setTimeout(() => {
101
+ respawn(worker);
102
+ }, guardTime);
103
+ } else {
104
+ respawn(worker);
105
+ }
106
+ }
107
+ });
108
+ return null;
109
+ } else {
110
+ console.log(`Cluster worker ${process.pid} started`);
111
+ }
112
+ }
26
113
 
27
114
  // The core is not a true moog object but it must look enough like one
28
115
  // to participate as an async event emitter
@@ -174,7 +261,7 @@ module.exports = async function(options) {
174
261
  function getRoot() {
175
262
  let _module = module;
176
263
  let m = _module;
177
- while (m.parent) {
264
+ while (m.parent && m.parent.filename) {
178
265
  // The test file is the root as far as we are concerned,
179
266
  // not mocha itself
180
267
  if (m.parent.filename.match(/\/node_modules\/mocha\//)) {
@@ -334,6 +421,10 @@ module.exports = async function(options) {
334
421
  synth.define(name, options);
335
422
  });
336
423
 
424
+ // Apostrophe prefers that any improvements to @apostrophecms/global
425
+ // be applied before any project level version of @apostrophecms/global
426
+ synth.applyImprovementsBeforeProjectLevel();
427
+
337
428
  return synth;
338
429
  }
339
430
 
@@ -514,3 +605,17 @@ module.exports.bundle = {
514
605
  modules: abstractClasses.concat(_.keys(defaults.modules)),
515
606
  directory: 'modules'
516
607
  };
608
+
609
+ function seconds(msec) {
610
+ return (Math.round(msec / 100) / 10) + ' seconds';
611
+ }
612
+
613
+ function clusterFork() {
614
+ const worker = cluster.fork();
615
+ worker.bornAt = Date.now();
616
+ }
617
+
618
+ function respawn(worker) {
619
+ console.error(`Respawning worker process ${worker.process.pid}`);
620
+ clusterFork();
621
+ }
@@ -202,5 +202,28 @@ module.exports = function(options) {
202
202
  return _.has(self.improvements, name);
203
203
  };
204
204
 
205
+ self.applyImprovementsBeforeProjectLevel = () => {
206
+ for (const [ name, definition ] of Object.entries(self.definitions)) {
207
+ // At this stage the complete definition of a type is a linked list of `extend`
208
+ // properties starting from what should be project level, unless there
209
+ // are improvements. Shuffle project level to be the first in the
210
+ // linked list
211
+ if (definition.__meta.name !== self.originalToMy(name)) {
212
+ let candidate = definition;
213
+ let predecessor = null;
214
+ while (candidate.extend && ((typeof candidate.extend) === 'object')) {
215
+ predecessor = candidate;
216
+ candidate = candidate.extend;
217
+ if (candidate.__meta.name === self.originalToMy(name)) {
218
+ predecessor.extend = candidate.extend;
219
+ candidate.extend = definition;
220
+ self.definitions[name] = candidate;
221
+ break;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ };
227
+
205
228
  return self;
206
229
  };
package/lib/moog.js CHANGED
@@ -211,6 +211,7 @@ module.exports = function(options) {
211
211
  for (const [ key, value ] of Object.entries(properties.group)) {
212
212
  if (groups[key] && Array.isArray(groups[key].fields)) {
213
213
  value.fields = groups[key].fields.concat(value.fields);
214
+ value.label = value.label || groups[key].label;
214
215
  }
215
216
  }
216
217
 
@@ -259,20 +259,32 @@ export default {
259
259
  // So that areas revert to being editable
260
260
  await this.refresh();
261
261
  },
262
- async onContextEditing() {
263
- // Accept a hint that someone is actively typing in a
264
- // rich text editor and a context-edited event is likely
265
- // coming; allows continuity of the "Saving..." indicator
266
- // so it doesn't flicker once a second as you type
267
- this.editing = true;
268
- if (this.editingTimeout) {
269
- clearTimeout(this.editingTimeout);
262
+ // Accept a hint that a user is actively typing and/or manipulating controls
263
+ // and it would best not to enable a save button or a "...Saved" indication yet
264
+ // to avoid a frenetic display and/or a situation where not everything is ready
265
+ // to be saved yet.
266
+ //
267
+ // If the event is emitted with a boolean value of `true`, the emitter takes
268
+ // responsibility for later emitting `false` to indicate active typing/manipulating
269
+ // is no longer in progress. If the event is emitted with no value then there is a
270
+ // 1100-millisecond, debounced timeout.
271
+
272
+ async onContextEditing(state) {
273
+ if ((typeof state) === 'boolean') {
274
+ this.editing = state;
275
+ } else {
276
+ if (!this.editing) {
277
+ this.editing = true;
278
+ }
279
+ if (this.editingTimeout) {
280
+ clearTimeout(this.editingTimeout);
281
+ }
282
+ this.editingTimeout = setTimeout(() => {
283
+ this.editing = false;
284
+ // Wait slightly longer than the rich text editor does
285
+ // before sending us a context-edited event
286
+ }, 1100);
270
287
  }
271
- this.editingTimeout = setTimeout(() => {
272
- this.editing = false;
273
- // Wait slightly longer than the rich text editor does
274
- // before sending us a context-edited event
275
- }, 1100);
276
288
  },
277
289
  async onPublish(e) {
278
290
  if (!this.canPublish) {
@@ -451,7 +463,11 @@ export default {
451
463
  }
452
464
  },
453
465
  async onContentChanged(e) {
454
- if (e.doc && (e.doc._id === this.context._id)) {
466
+
467
+ if (
468
+ (e.doc && (e.doc._id === this.context._id)) ||
469
+ (e.docIds && e.docIds.includes(this.context._id))
470
+ ) {
455
471
  if (e.action === 'delete') {
456
472
  if (!this.contextStack.length) {
457
473
  // With the current page gone, we need to move to safe ground
@@ -46,7 +46,7 @@ module.exports = function(self) {
46
46
  return { args };
47
47
  },
48
48
  async run(context, doc, name, _with) {
49
- const req = context.env.opts.req;
49
+ const req = context.ctx.__req;
50
50
  let area;
51
51
  if ((!doc) || ((typeof doc) !== 'object')) {
52
52
  throw usage('You must pass an existing doc or widget as the first argument.');
@@ -40,7 +40,7 @@ module.exports = function(self) {
40
40
  return { args };
41
41
  },
42
42
  async run(context, item, options, _with) {
43
- const req = context.env.opts.req;
43
+ const req = context.ctx.__req;
44
44
  if (!item) {
45
45
  self.apos.util.warn('a null widget was encountered.');
46
46
  return '';
@@ -615,8 +615,8 @@ export default {
615
615
  }
616
616
 
617
617
  .apos-area-widget-wrapper--foreign .apos-area-widget-inner .apos-area-widget__breadcrumbs {
618
- background-color: var(--a-primary);
619
- & ::v-deep .apos-button {
618
+ background-color: var(--a-background-inverted);
619
+ & ::v-deep .apos-button__content {
620
620
  color: var(--a-text-inverted);
621
621
  }
622
622
  }
@@ -73,6 +73,12 @@ module.exports = {
73
73
  await fs.remove(buildDir);
74
74
  await fs.mkdirp(buildDir);
75
75
 
76
+ // Static asset files in `public` subdirs of each module are copied
77
+ // to the same relative path `/public/apos-frontend/namespace/modules/modulename`.
78
+ // Inherited files are also copied, with the deepest subclass overriding in the
79
+ // event of a conflict
80
+ await moduleOverrides(`${bundleDir}/modules`, 'public');
81
+
76
82
  for (const [ name, options ] of Object.entries(self.builds)) {
77
83
  // If the option is not present always rebuild everything
78
84
  let rebuild = argv && !argv['check-apos-build'];
@@ -122,12 +128,17 @@ module.exports = {
122
128
  // just `public` and `apos`) by examining those specified as
123
129
  // targets for the various builds
124
130
  const scenes = [ ...new Set(Object.values(self.builds).map(options => options.scenes).flat()) ];
125
- let bundles = [];
131
+ let deployFiles = [];
126
132
  for (const scene of scenes) {
127
- bundles = [ ...bundles, ...merge(scene) ];
133
+ deployFiles = [ ...deployFiles, ...merge(scene) ];
128
134
  }
129
-
130
- await deploy(bundles);
135
+ // enumerate public assets and include them in deployment if appropriate
136
+ const publicAssets = glob.sync('modules/**/*', {
137
+ cwd: bundleDir,
138
+ mark: true
139
+ }).filter(match => !match.endsWith('/'));
140
+ deployFiles = [ ...deployFiles, ...publicAssets ];
141
+ await deploy(deployFiles);
131
142
 
132
143
  if (process.env.APOS_BUNDLE_ANALYZER) {
133
144
  return new Promise((resolve, reject) => {
@@ -162,7 +173,7 @@ module.exports = {
162
173
  for (const name of names) {
163
174
  const moduleDir = `${modulesDir}/${name}`;
164
175
  for (const dir of directories[name]) {
165
- const srcDir = `${dir}/ui/${source}`;
176
+ const srcDir = `${dir}/${source}`;
166
177
  if (fs.existsSync(srcDir)) {
167
178
  await fs.copy(srcDir, moduleDir);
168
179
  }
@@ -176,7 +187,7 @@ module.exports = {
176
187
  }));
177
188
  const modulesDir = `${buildDir}/${name}/modules`;
178
189
  const source = options.source || name;
179
- await moduleOverrides(modulesDir, source);
190
+ await moduleOverrides(modulesDir, `ui/${source}`);
180
191
 
181
192
  let iconImports, componentImports, tiptapExtensionImports, appImports, indexJsImports, indexSassImports;
182
193
  if (options.apos) {
@@ -233,7 +244,8 @@ module.exports = {
233
244
  // Remove previous build artifacts, as some pipelines won't build all artifacts
234
245
  // if there is no input, and we don't want stale output in the bundle
235
246
  fs.removeSync(`${bundleDir}/${outputFilename}`);
236
- fs.removeSync(`${bundleDir}/${outputFilename}`.replace(/\.js$/, '.css'));
247
+ const cssPath = `${bundleDir}/${outputFilename}`.replace(/\.js$/, '.css');
248
+ fs.removeSync(cssPath);
237
249
  await Promise.promisify(webpackModule)(require(`./lib/webpack/${name}/webpack.config`)(
238
250
  {
239
251
  importFile,
@@ -243,6 +255,11 @@ module.exports = {
243
255
  },
244
256
  self.apos
245
257
  ));
258
+ if (fs.existsSync(cssPath)) {
259
+ fs.writeFileSync(cssPath, self.filterCss(fs.readFileSync(cssPath, 'utf8'), {
260
+ modulesPrefix: `${self.getAssetBaseUrl()}/modules`
261
+ }));
262
+ }
246
263
  if (options.apos) {
247
264
  const now = Date.now().toString();
248
265
  fs.writeFileSync(`${bundleDir}/${name}-build-timestamp.txt`, now);
@@ -269,7 +286,9 @@ module.exports = {
269
286
  const publicImports = getImports(name, '*.css', { });
270
287
  fs.writeFileSync(`${bundleDir}/${name}-build.css`,
271
288
  publicImports.paths.map(path => {
272
- return fs.readFileSync(path, 'utf8');
289
+ return self.filterCss(fs.readFileSync(path, 'utf8'), {
290
+ modulesPrefix: `${self.getAssetBaseUrl()}/modules`
291
+ });
273
292
  }).join('\n')
274
293
  );
275
294
  }
@@ -358,7 +377,19 @@ module.exports = {
358
377
  return [ jsModules, jsNoModules, css ];
359
378
  }
360
379
 
361
- async function deploy(bundles) {
380
+ // If NODE_ENV is production, this function will copy the given
381
+ // array of asset files from `${bundleDir}/${file}` to
382
+ // the same relative location in the appropriate release subdirectory in
383
+ // `/public/apos-frontend/releases`, or in `/apos-frontend/releases` in
384
+ // uploadfs if `APOS_UPLOADFS_ASSETS` is present.
385
+ //
386
+ // If NODE_ENV is not production this function does nothing and
387
+ // the assets are served directly from `/public/apos-frontend/${file}`.
388
+ //
389
+ // The namespace (e.g. default) should be part of each filename given.
390
+ // A leading slash should NOT be passed.
391
+
392
+ async function deploy(files) {
362
393
  if (process.env.NODE_ENV !== 'production') {
363
394
  return;
364
395
  }
@@ -373,17 +404,23 @@ module.exports = {
373
404
  } else {
374
405
  // The right choice with Docker if uploadfs is just the local filesystem
375
406
  // mapped to a volume (a Docker build step can't access that)
376
- copyIn = fs.copyFile;
407
+ copyIn = fsCopyIn;
377
408
  releaseDir = `${self.apos.rootDir}/public/apos-frontend/releases/${releaseId}/${namespace}`;
378
409
  await fs.mkdirp(releaseDir);
379
410
  }
380
- for (const bundle of bundles) {
381
- const src = `${bundleDir}/${bundle}`;
382
- await copyIn(src, `${releaseDir}/${bundle}`);
411
+ for (const file of files) {
412
+ const src = `${bundleDir}/${file}`;
413
+ await copyIn(src, `${releaseDir}/${file}`);
383
414
  await fs.remove(src);
384
415
  }
385
416
  }
386
417
 
418
+ async function fsCopyIn(from, to) {
419
+ const base = path.dirname(to);
420
+ await fs.mkdirp(base);
421
+ return fs.copyFile(from, to);
422
+ }
423
+
387
424
  function getImports(folder, pattern, options) {
388
425
  let components = [];
389
426
  const seen = {};
@@ -650,6 +687,30 @@ module.exports = {
650
687
  if (!self.options.es5) {
651
688
  delete self.builds['src-es5'];
652
689
  }
690
+ },
691
+ // Filter the given css performing any necessary transformations,
692
+ // such as support for the /modules path regardless of where
693
+ // static assets are actually deployed
694
+ filterCss(css, { modulesPrefix }) {
695
+ return self.filterCssUrls(css, url => {
696
+ if (url.startsWith('/modules')) {
697
+ return url.replace('/modules', modulesPrefix);
698
+ }
699
+ return url;
700
+ });
701
+ },
702
+ // Run all URLs in CSS through a filter function
703
+ filterCssUrls(css, filter) {
704
+ css = css.replace(/url\(([^'"].*?)\)/g, function(s, url) {
705
+ return 'url(' + filter(url) + ')';
706
+ });
707
+ css = css.replace(/url\("([^"]+?)"\)/g, function(s, url) {
708
+ return 'url("' + filter(url) + '")';
709
+ });
710
+ css = css.replace(/url\('([^']+?)'\)/g, function(s, url) {
711
+ return 'url(\'' + filter(url) + '\')';
712
+ });
713
+ return css;
653
714
  }
654
715
  };
655
716
  },
@@ -667,6 +728,9 @@ module.exports = {
667
728
  }
668
729
  const script = fs.readFileSync(path.join(__dirname, '/lib/refresh-on-restart.js'), 'utf8');
669
730
  return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id">\n${script}</script>`);
731
+ },
732
+ url(path) {
733
+ return `${self.getAssetBaseUrl()}${path}`;
670
734
  }
671
735
  };
672
736
  },
@@ -1038,6 +1038,7 @@ module.exports = {
1038
1038
  action: self.action,
1039
1039
  fileGroups: self.fileGroups,
1040
1040
  name: self.name,
1041
+ // for bc
1041
1042
  uploadsUrl: self.uploadfs.getUrl(),
1042
1043
  croppable: self.croppable,
1043
1044
  sized: self.sized
@@ -1,4 +1,4 @@
1
- // This module establishes `apos.db`, the mongodb driver connection object.
1
+ // This module establishes `apos.db`, the MongoDB database object.
2
2
  //
3
3
  // ## Options
4
4
  //
@@ -20,8 +20,7 @@
20
20
  //
21
21
  // ### `client`
22
22
  //
23
- // An existing MongoDB connection (MongoClient) object. If present, a new
24
- // connection instance is created that reuses the same sockets,
23
+ // An existing MongoDB connection (MongoClient) object. If present, it is used
25
24
  // and `uri`, `host`, `connect`, etc. are ignored.
26
25
  //
27
26
  // ### `versionCheck`
@@ -40,7 +39,7 @@
40
39
  // allow other modules to drop related non-MongoDB resources at the
41
40
  // same time, if desired.
42
41
  //
43
- // Note that `apos.db` is the mongodb database object, not this module.
42
+ // Note that `apos.db` is the MongoDB database object, not this module.
44
43
  // You shouldn't need to talk to this module after startup, but you can
45
44
  // access it as `apos.modules['@apostrophecms/db']` if you wish. You can
46
45
  // also access `apos.dbClient` if you need the MongoClient object.
@@ -81,8 +80,8 @@ module.exports = {
81
80
  },
82
81
  methods(self) {
83
82
  return {
84
- // Open the database connection. Always use MongoClient with its
85
- // sensible defaults. Build a URI if we need to, so we can call it
83
+ // Open the database connection. Always uses MongoClient with its
84
+ // sensible defaults. Builds a URI if necessary, so we can call it
86
85
  // in a consistent way.
87
86
  //
88
87
  // One default we override: if the connection is lost, we keep