apostrophe 3.4.0 → 3.7.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/CHANGELOG.md +85 -0
- package/README.md +1 -1
- package/deploy-test-count +1 -1
- package/index.js +125 -5
- package/lib/moog-require.js +41 -3
- package/lib/moog.js +20 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +42 -23
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +25 -13
- package/modules/@apostrophecms/area/index.js +9 -0
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
- package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +3 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +6 -6
- package/modules/@apostrophecms/asset/index.js +8 -8
- package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +5 -2
- package/modules/@apostrophecms/doc/index.js +13 -3
- package/modules/@apostrophecms/doc-type/index.js +1 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +11 -2
- package/modules/@apostrophecms/i18n/i18n/es.json +383 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +380 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +381 -0
- package/modules/@apostrophecms/i18n/index.js +10 -1
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +153 -121
- package/modules/@apostrophecms/image/index.js +2 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +24 -13
- package/modules/@apostrophecms/login/index.js +36 -17
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +8 -0
- package/modules/@apostrophecms/migration/index.js +1 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +1 -1
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/module/index.js +1 -1
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +4 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +42 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +6 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +64 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +15 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +23 -0
- package/modules/@apostrophecms/schema/index.js +97 -20
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +8 -5
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
- package/modules/@apostrophecms/template/index.js +61 -36
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
- package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
- package/modules/@apostrophecms/ui/index.js +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +16 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +3 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_widgets.scss +3 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +2 -1
- package/modules/@apostrophecms/user/index.js +21 -0
- package/modules/@apostrophecms/util/index.js +2 -2
- package/modules/@apostrophecms/util/ui/src/http.js +12 -8
- package/modules/@apostrophecms/widget-type/index.js +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
- package/package.json +3 -3
- package/test/extra_node_modules/improve-global/index.js +7 -0
- package/test/extra_node_modules/improve-piece-type/index.js +7 -0
- package/test/improve-overrides.js +30 -0
- package/test/login.js +183 -0
- package/test/modules/@apostrophecms/global/index.js +8 -0
- package/test/modules/fragment-all/views/aux-test.html +7 -0
- package/test/modules/fragment-all/views/fragment.html +5 -0
- package/test/moog.js +47 -0
- package/test/package.json +5 -4
- package/test/reverse-relationship.js +170 -0
- package/test/subdir-project/app.js +3 -0
- package/test/subdir-project.js +26 -0
- package/test/templates.js +7 -1
- package/test-lib/test.js +23 -12
- package/test-lib/util.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,92 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.7.0 - 2021-10-28
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* 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.
|
|
8
|
+
|
|
9
|
+
* 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.
|
|
10
|
+
|
|
11
|
+
### Fixes
|
|
12
|
+
|
|
13
|
+
* Prevents double-escaping interpolated localization strings in the UI.
|
|
14
|
+
* Rich text editor style labels are now run through a localization method to get the translated strings from their l10n keys.
|
|
15
|
+
* Fixes README Node version requirement (Node 12+).
|
|
16
|
+
* 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.
|
|
17
|
+
* Users can now activate the built-in date and time editing popups of modern browsers when using the `date` and `time` schema field types.
|
|
18
|
+
* 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).
|
|
19
|
+
* 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.
|
|
20
|
+
* Developers can now `require` their project `app.js` in the Node.js REPL for debugging and inspection
|
|
21
|
+
* Ensure array field items have valid _id prop before storing. Thanks to Thanks to [Matthew Francis Brunetti](https://github.com/zenflow).
|
|
22
|
+
|
|
23
|
+
### Changes
|
|
24
|
+
|
|
25
|
+
* 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.
|
|
26
|
+
* 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.
|
|
27
|
+
* Adds deprecation notes to doc module `afterLoad` events, which are deprecated.
|
|
28
|
+
* Removes unused `afterLogin` method in the login module.
|
|
29
|
+
|
|
30
|
+
## 3.6.0 - 2021-10-13
|
|
31
|
+
|
|
32
|
+
### Adds
|
|
33
|
+
|
|
34
|
+
* 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.
|
|
35
|
+
* Adds 'no-search' modifier to relationship fields as a UI simplification option.
|
|
36
|
+
* Fields can now have their own `modifiers` array. This is combined with the schema modifiers, allowing for finer grained control of field rendering.
|
|
37
|
+
* Adds a Slovak localization file. Activate the `sk` locale to use this. Many thanks to [Michael Huna](https://github.com/Miselrkba) for the contribution.
|
|
38
|
+
* Adds a Spanish localization file. Activate the `es` locale to use this. Many thanks to [Eugenio Gonzalez](https://github.com/egonzalezg9) for the contribution.
|
|
39
|
+
* 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.
|
|
40
|
+
|
|
41
|
+
### Fixes
|
|
42
|
+
|
|
43
|
+
* Fixed missing translation for "New Piece" option on the "more" menu of the piece manager, seen when using it as a chooser.
|
|
44
|
+
* 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.
|
|
45
|
+
* 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.
|
|
46
|
+
* Array input component edit button label is now propertly localized.
|
|
47
|
+
* 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.
|
|
48
|
+
* Fragments now have access to `__t()`, `getOptions` and other features passed to regular templates.
|
|
49
|
+
* Fixes field group cascade merging, using the original group label if none is given in the new field group configuration.
|
|
50
|
+
* 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.
|
|
51
|
+
* 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.
|
|
52
|
+
* 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.
|
|
53
|
+
|
|
54
|
+
### Changes
|
|
55
|
+
|
|
56
|
+
* No longer logs a warning about no users if `testModule` is true on the app.
|
|
57
|
+
|
|
58
|
+
## 3.5.0 - 2021-09-23
|
|
59
|
+
|
|
60
|
+
* Pinned dependency on `vue-material-design-icons` to fix `apos-build.js` build error in production.
|
|
61
|
+
* 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.
|
|
62
|
+
* Fixes moog error messages to reflect the recommended pattern of customization functions only taking `self` as an argument.
|
|
63
|
+
* Rich Text widgets now instantiate with a valid element from the `styles` option rather than always starting with an unclassed `<p>` tag.
|
|
64
|
+
* Since version 3.2.0, apostrophe modules to be loaded via npm must appear as explicit npm dependencies of the project. This is a necessary security and stability improvement, but it was slightly too strict. Starting with this release, if the project has no `package.json` in its root directory, the `package.json` in the closest ancestor directory is consulted.
|
|
65
|
+
* Fixes a bug where having no project modules directory would throw an error. This is primarily a concern for module unit tests where there are no additional modules involved.
|
|
66
|
+
* `css-loader` now ignores `url()` in css files inside `assets` so that paths are left intact, i.e. `url(/images/file.svg)` will now find a static file at `/public/images/file.svg` (static assets in `/public` are served by `express.static`). Thanks to Matic Tersek.
|
|
67
|
+
* Restored support for clicking on a "foreign" area, i.e. an area displayed on the page whose content comes from a piece, in order to edit it in an appropriate way.
|
|
68
|
+
* Apostrophe module aliases and the data attached to them are now visible immediately to `ui/src/index.js` JavaScript code, i.e. you can write `apos.alias` where `alias` matches the `alias` option configured for that module. Previously one had to write `apos.modules['module-name']` or wait until next tick. However, note that most modules do not push any data to the browser when a user is not logged in. You can do so in a custom module by calling `self.enableBrowserData('public')` from `init` and implementing or extending the `getBrowserData(req)` method (note that page, piece and widget types already have one, so it is important to extend in those cases).
|
|
69
|
+
* `options.testModule` works properly when implementing unit tests for an npm module that is namespaced.
|
|
70
|
+
|
|
71
|
+
### Changes
|
|
72
|
+
|
|
73
|
+
* 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.
|
|
74
|
+
* AposButton's `block` modifier now less login-specific
|
|
75
|
+
|
|
76
|
+
### Adds
|
|
77
|
+
|
|
78
|
+
* Rich Text widget's styles support a `def` property for specifying the default style the editor should instantiate with.
|
|
79
|
+
* A more helpful error message if a field of type `area` is missing its `options` property.
|
|
80
|
+
|
|
81
|
+
## 3.4.1 - 2021-09-13
|
|
82
|
+
|
|
83
|
+
No changes. Publishing to correctly mark the latest 3.x release as "latest" in npm.
|
|
84
|
+
|
|
3
85
|
## 3.4.0 - 2021-09-13
|
|
4
86
|
|
|
5
87
|
### Security
|
|
6
88
|
|
|
89
|
+
* Changing a user's password or marking their account as disabled now immediately terminates any active sessions or bearer tokens for that user. Thanks to Daniel Elkabes for pointing out the issue. To ensure all sessions have the necessary data for this, all users logged in via sessions at the time of this upgrade will need to log in again.
|
|
7
90
|
* Users with permission to upload SVG files were previously able to do so even if they contained XSS attacks. In Apostrophe 3.x, the general public so far never has access to upload SVG files, so the risk is minor but could be used to phish access from an admin user by encouraging them to upload a specially crafted SVG file. While Apostrophe typically displays SVG files using the `img` tag, which ignores XSS vectors, an XSS attack might still be possible if the image were opened directly via the Apostrophe media library's convenience link for doing so. All SVG uploads are now sanitized via DOMPurify to remove XSS attack vectors. In addition, all existing SVG attachments not already validated are passed through DOMPurify during a one-time migration.
|
|
8
91
|
|
|
9
92
|
### Fixes
|
|
@@ -30,12 +113,14 @@
|
|
|
30
113
|
* Lints module names for `apostrophe-` prefixes even if they don't have a module directory (e.g., only in `app.js`).
|
|
31
114
|
* Starts all `warnDev` messages with a line break and warning symbol (⚠️) to stand out in the console.
|
|
32
115
|
* `apos.util.onReady` aliases `apos.util.onReadyAndRefresh` for brevity. The `apos.util.onReadyAndRefresh` method name will be deprecated in the next major version.
|
|
116
|
+
* Adds a developer setting that applies a margin between parent and child areas, allowing developers to change the default spacing in nested areas.
|
|
33
117
|
|
|
34
118
|
### Changes
|
|
35
119
|
|
|
36
120
|
* Removes the temporary `trace` method from the `@apostrophecms/db` module.
|
|
37
121
|
* Beginning with this release, the `apostrophe:modulesReady` event has been renamed `apostrophe:modulesRegistered`, and the `apostrophe:afterInit` event has been renamed `apostrophe:ready`. This better reflects their actual roles. The old event names are accepted for backwards compatibility. See the documentation for more information.
|
|
38
122
|
* Only autofocuses rich text editors when they are empty.
|
|
123
|
+
* Nested areas now have a vertical margin applied when editing, allowing easier access to the parent area's controls.
|
|
39
124
|
|
|
40
125
|
## 3.3.1 - 2021-09-01
|
|
41
126
|
|
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 |
|
|
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/deploy-test-count
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
6
|
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
|
-
//
|
|
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\//)) {
|
|
@@ -254,6 +341,8 @@ module.exports = async function(options) {
|
|
|
254
341
|
// when options.testModule is true. There must be a
|
|
255
342
|
// test/ or tests/ subdir of the module containing
|
|
256
343
|
// a test.js file that runs under mocha via devDependencies.
|
|
344
|
+
// If `options.testModule` is a string it will be used as a
|
|
345
|
+
// namespace for the test module.
|
|
257
346
|
|
|
258
347
|
function testModule() {
|
|
259
348
|
if (!options.testModule) {
|
|
@@ -277,9 +366,17 @@ module.exports = async function(options) {
|
|
|
277
366
|
if (testDir === moduleDir) {
|
|
278
367
|
throw new Error('Test file must be in test/ or tests/ subdirectory of module');
|
|
279
368
|
}
|
|
369
|
+
|
|
370
|
+
const pkgName = require(`${moduleDir}/package.json`).name;
|
|
371
|
+
let pkgNamespace = '';
|
|
372
|
+
if (pkgName.includes('/')) {
|
|
373
|
+
const parts = pkgName.split('/');
|
|
374
|
+
pkgNamespace = '/' + parts.slice(0, parts.length - 1).join('/');
|
|
375
|
+
}
|
|
376
|
+
|
|
280
377
|
if (!fs.existsSync(testDir + '/node_modules')) {
|
|
281
|
-
fs.mkdirSync(testDir + '/node_modules');
|
|
282
|
-
fs.symlinkSync(moduleDir, testDir + '/node_modules/' +
|
|
378
|
+
fs.mkdirSync(testDir + '/node_modules' + pkgNamespace, { recursive: true });
|
|
379
|
+
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, 'dir');
|
|
283
380
|
}
|
|
284
381
|
|
|
285
382
|
// Not quite superfluous: it'll return self.root, but
|
|
@@ -324,6 +421,10 @@ module.exports = async function(options) {
|
|
|
324
421
|
synth.define(name, options);
|
|
325
422
|
});
|
|
326
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
|
+
|
|
327
428
|
return synth;
|
|
328
429
|
}
|
|
329
430
|
|
|
@@ -349,6 +450,11 @@ module.exports = async function(options) {
|
|
|
349
450
|
validSteps.push(step.name);
|
|
350
451
|
}
|
|
351
452
|
}
|
|
453
|
+
|
|
454
|
+
if (!fs.existsSync(self.localModules)) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
352
458
|
const dirs = fs.readdirSync(self.localModules);
|
|
353
459
|
for (const dir of dirs) {
|
|
354
460
|
if (dir.match(/^@/)) {
|
|
@@ -499,3 +605,17 @@ module.exports.bundle = {
|
|
|
499
605
|
modules: abstractClasses.concat(_.keys(defaults.modules)),
|
|
500
606
|
directory: 'modules'
|
|
501
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
|
+
}
|
package/lib/moog-require.js
CHANGED
|
@@ -166,10 +166,25 @@ module.exports = function(options) {
|
|
|
166
166
|
// Even if the package exists in node_modules it might just be a
|
|
167
167
|
// sub-dependency due to npm/yarn flattening, which means we could be
|
|
168
168
|
// confused by an unrelated npm module with the same name as an Apostrophe
|
|
169
|
-
// module unless we verify it is a real project-level dependency
|
|
169
|
+
// module unless we verify it is a real project-level dependency. However
|
|
170
|
+
// if no package.json at all exists at project level we do search up the
|
|
171
|
+
// tree until we find one to accommodate patterns like `src/app.js`
|
|
170
172
|
if (!self.validPackages) {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
+
const initialFolder = path.dirname(self.root.filename);
|
|
174
|
+
let folder = initialFolder;
|
|
175
|
+
while (true) {
|
|
176
|
+
const file = `${folder}/package.json`;
|
|
177
|
+
if (fs.existsSync(file)) {
|
|
178
|
+
const info = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
179
|
+
self.validPackages = new Set([ ...Object.keys(info.dependencies || {}), ...Object.keys(info.devDependencies || {}) ]);
|
|
180
|
+
break;
|
|
181
|
+
} else {
|
|
182
|
+
folder = path.dirname(folder);
|
|
183
|
+
if (!folder.length) {
|
|
184
|
+
throw new Error(`package.json was not found in ${initialFolder} or any of its parent folders.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
173
188
|
}
|
|
174
189
|
if (!self.validPackages.has(type)) {
|
|
175
190
|
return null;
|
|
@@ -187,5 +202,28 @@ module.exports = function(options) {
|
|
|
187
202
|
return _.has(self.improvements, name);
|
|
188
203
|
};
|
|
189
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
|
+
|
|
190
228
|
return self;
|
|
191
229
|
};
|
package/lib/moog.js
CHANGED
|
@@ -142,8 +142,8 @@ module.exports = function(options) {
|
|
|
142
142
|
|
|
143
143
|
const upgradeHints = {
|
|
144
144
|
construct: 'in Apostrophe 3.x, "construct" has been replaced with "methods", "routes", "apiRoutes", etc.',
|
|
145
|
-
beforeConstruct: 'in Apostrophe 3.x, "beforeConstruct" has been replaced with "beforeSuperClass". It takes (self
|
|
146
|
-
afterConstruct: 'in Apostrophe 3.x, "afterConstruct" has been replaced with "init". It takes (self
|
|
145
|
+
beforeConstruct: 'in Apostrophe 3.x, "beforeConstruct" has been replaced with "beforeSuperClass". It takes (self) and should be solely concerned with modifying the options before the base class sees them. It must be synchronous. Check out the new fields section, you might not need beforeSuperClass.',
|
|
146
|
+
afterConstruct: 'in Apostrophe 3.x, "afterConstruct" has been replaced with "init". It takes (self) and may be an async function.'
|
|
147
147
|
};
|
|
148
148
|
|
|
149
149
|
for (const step of steps) {
|
|
@@ -194,15 +194,27 @@ module.exports = function(options) {
|
|
|
194
194
|
const groups = klona(that[`${cascade}Groups`]);
|
|
195
195
|
for (const value of Object.values(properties.group)) {
|
|
196
196
|
for (const field of value.fields || []) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
// Remove fields from existing groups if they're added to a new
|
|
198
|
+
// group.
|
|
199
|
+
for (const val of Object.values(groups)) {
|
|
200
|
+
if (val.fields) {
|
|
201
|
+
if (val.fields.includes(field)) {
|
|
202
|
+
val.fields = val.fields.filter(_field => _field !== field);
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
}
|
|
208
|
+
|
|
209
|
+
// Combine groups of the same name now that inherited groups are
|
|
210
|
+
// filtered
|
|
211
|
+
for (const [ key, value ] of Object.entries(properties.group)) {
|
|
212
|
+
if (groups[key] && Array.isArray(groups[key].fields)) {
|
|
213
|
+
value.fields = groups[key].fields.concat(value.fields);
|
|
214
|
+
value.label = value.label || groups[key].label;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
206
218
|
that[`${cascade}Groups`] = {
|
|
207
219
|
...groups,
|
|
208
220
|
...klona(properties.group)
|
|
@@ -289,14 +301,14 @@ module.exports = function(options) {
|
|
|
289
301
|
};
|
|
290
302
|
}
|
|
291
303
|
if ((typeof step[keyword]) !== 'function') {
|
|
292
|
-
throw stepError(step, `${keyword} must be a function that takes (self
|
|
304
|
+
throw stepError(step, `${keyword} must be a function that takes (self) and returns an object`);
|
|
293
305
|
}
|
|
294
306
|
_.merge(context, step[keyword](that, options));
|
|
295
307
|
}
|
|
296
308
|
const extend = getExtendKey(keyword);
|
|
297
309
|
if (step[extend]) {
|
|
298
310
|
if ((typeof step[extend]) !== 'function') {
|
|
299
|
-
throw stepError(step, `${extend} must be a function that takes (self
|
|
311
|
+
throw stepError(step, `${extend} must be a function that takes (self) and returns an object`);
|
|
300
312
|
}
|
|
301
313
|
const extensions = step[extend](that, options);
|
|
302
314
|
wrap(context, extensions);
|
|
@@ -25,13 +25,18 @@
|
|
|
25
25
|
@click="switchLocale(locale)"
|
|
26
26
|
>
|
|
27
27
|
<span class="apos-locale">
|
|
28
|
-
<
|
|
28
|
+
<AposIndicator
|
|
29
29
|
v-if="isActive(locale)"
|
|
30
|
+
icon="check-bold-icon"
|
|
31
|
+
fill-color="var(--a-primary)"
|
|
30
32
|
class="apos-check"
|
|
31
|
-
|
|
32
|
-
:
|
|
33
|
+
:icon-size="12"
|
|
34
|
+
:title="$t('apostrophe:currentLocale')"
|
|
33
35
|
/>
|
|
34
36
|
{{ locale.label }}
|
|
37
|
+
<span class="apos-locale-name">
|
|
38
|
+
({{ locale.name }})
|
|
39
|
+
</span>
|
|
35
40
|
<span
|
|
36
41
|
class="apos-locale-localized"
|
|
37
42
|
:class="{ 'apos-state-is-localized': isLocalized(locale) }"
|
|
@@ -43,24 +48,23 @@
|
|
|
43
48
|
<p class="apos-available-description">
|
|
44
49
|
{{ $t('apostrophe:documentExistsInLocales') }}
|
|
45
50
|
</p>
|
|
46
|
-
<
|
|
51
|
+
<AposButton
|
|
47
52
|
v-for="locale in availableLocales"
|
|
48
53
|
:key="locale.name"
|
|
49
54
|
class="apos-available-locale"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
:label="locale.label"
|
|
56
|
+
type="quiet"
|
|
57
|
+
:modifiers="['no-motion']"
|
|
58
|
+
@click="switchLocale(locale)"
|
|
59
|
+
/>
|
|
53
60
|
</div>
|
|
54
61
|
</div>
|
|
55
62
|
</AposContextMenu>
|
|
56
63
|
</template>
|
|
57
64
|
|
|
58
65
|
<script>
|
|
59
|
-
import CheckIcon from 'vue-material-design-icons/Check.vue';
|
|
60
|
-
|
|
61
66
|
export default {
|
|
62
67
|
name: 'TheAposAdminBarLocale',
|
|
63
|
-
components: { CheckIcon },
|
|
64
68
|
data() {
|
|
65
69
|
return {
|
|
66
70
|
search: '',
|
|
@@ -79,11 +83,11 @@ export default {
|
|
|
79
83
|
button() {
|
|
80
84
|
return {
|
|
81
85
|
label: {
|
|
82
|
-
key:
|
|
86
|
+
key: apos.i18n.locale,
|
|
83
87
|
localize: false
|
|
84
88
|
},
|
|
85
89
|
icon: 'chevron-down-icon',
|
|
86
|
-
modifiers: [ 'icon-right', 'no-motion' ],
|
|
90
|
+
modifiers: [ 'icon-right', 'no-motion', 'uppercase' ],
|
|
87
91
|
type: 'quiet'
|
|
88
92
|
};
|
|
89
93
|
},
|
|
@@ -149,7 +153,7 @@ export default {
|
|
|
149
153
|
}
|
|
150
154
|
} else {
|
|
151
155
|
const currentLocale = apos.i18n.locales[apos.locale];
|
|
152
|
-
|
|
156
|
+
this.$refs.menu.hide();
|
|
153
157
|
const toLocalize = await apos.confirm(
|
|
154
158
|
{
|
|
155
159
|
icon: false,
|
|
@@ -207,6 +211,13 @@ export default {
|
|
|
207
211
|
&::after {
|
|
208
212
|
right: 0;
|
|
209
213
|
}
|
|
214
|
+
|
|
215
|
+
&::v-deep .apos-button__label {
|
|
216
|
+
@include type-small;
|
|
217
|
+
color: var(--a-primary);
|
|
218
|
+
font-weight: var(--a-weight-bold);
|
|
219
|
+
letter-spacing: 1px;
|
|
220
|
+
}
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
.apos-locales-picker {
|
|
@@ -214,15 +225,14 @@ export default {
|
|
|
214
225
|
}
|
|
215
226
|
|
|
216
227
|
.apos-locales-filter {
|
|
228
|
+
@include type-large;
|
|
217
229
|
box-sizing: border-box;
|
|
218
230
|
width: 100%;
|
|
219
|
-
padding:
|
|
220
|
-
font-size: 14px;
|
|
231
|
+
padding: 20px 45px 20px 20px;
|
|
221
232
|
border-top: 0;
|
|
222
233
|
border-right: 0;
|
|
223
234
|
border-bottom: 1px solid var(--a-base-9);
|
|
224
235
|
border-left: 0;
|
|
225
|
-
color: var(--a-text-primary);
|
|
226
236
|
border-top-right-radius: var(--a-border-radius);
|
|
227
237
|
border-top-left-radius: var(--a-border-radius);
|
|
228
238
|
|
|
@@ -242,8 +252,7 @@ export default {
|
|
|
242
252
|
max-height: 350px;
|
|
243
253
|
overflow-y: scroll;
|
|
244
254
|
padding-left: 0;
|
|
245
|
-
margin
|
|
246
|
-
margin-bottom: 0;
|
|
255
|
+
margin: $spacing-base 0;
|
|
247
256
|
font-weight: var(--a-weight-base);
|
|
248
257
|
}
|
|
249
258
|
|
|
@@ -264,7 +273,7 @@ export default {
|
|
|
264
273
|
.apos-check {
|
|
265
274
|
position: absolute;
|
|
266
275
|
top: 50%;
|
|
267
|
-
left:
|
|
276
|
+
left: 18px;
|
|
268
277
|
transform: translateY(-50%);
|
|
269
278
|
color: var(--a-primary);
|
|
270
279
|
stroke: var(--a-primary);
|
|
@@ -279,11 +288,12 @@ export default {
|
|
|
279
288
|
.apos-locale-localized {
|
|
280
289
|
position: relative;
|
|
281
290
|
top: -1px;
|
|
291
|
+
left: 5px;
|
|
282
292
|
display: inline-block;
|
|
283
|
-
|
|
284
|
-
|
|
293
|
+
width: 3px;
|
|
294
|
+
height: 3px;
|
|
285
295
|
border: 1px solid var(--a-base-5);
|
|
286
|
-
border-radius:
|
|
296
|
+
border-radius: 50%;
|
|
287
297
|
|
|
288
298
|
&.apos-state-is-localized {
|
|
289
299
|
background-color: var(--a-success);
|
|
@@ -293,7 +303,7 @@ export default {
|
|
|
293
303
|
}
|
|
294
304
|
|
|
295
305
|
.apos-available-locales {
|
|
296
|
-
padding:
|
|
306
|
+
padding: $spacing-double;
|
|
297
307
|
border-top: 1px solid var(--a-base-9);
|
|
298
308
|
}
|
|
299
309
|
|
|
@@ -307,4 +317,13 @@ export default {
|
|
|
307
317
|
margin-right: 10px;
|
|
308
318
|
margin-bottom: 5px;
|
|
309
319
|
}
|
|
320
|
+
|
|
321
|
+
.apos-available-description {
|
|
322
|
+
margin-top: 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.apos-locale-name {
|
|
326
|
+
text-transform: uppercase;
|
|
327
|
+
}
|
|
328
|
+
|
|
310
329
|
</style>
|
|
@@ -259,20 +259,32 @@ export default {
|
|
|
259
259
|
// So that areas revert to being editable
|
|
260
260
|
await this.refresh();
|
|
261
261
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const deep = require('deep-get-set');
|
|
3
|
+
const { stripIndent } = require('common-tags');
|
|
3
4
|
|
|
4
5
|
// An area is a series of zero or more widgets, in which users can add
|
|
5
6
|
// and remove widgets and drag them to reorder them. This module implements
|
|
@@ -131,6 +132,14 @@ module.exports = {
|
|
|
131
132
|
const field = self.apos.schema.getFieldById(area._fieldId);
|
|
132
133
|
|
|
133
134
|
const options = field.options;
|
|
135
|
+
if (!options) {
|
|
136
|
+
throw new Error(stripIndent`
|
|
137
|
+
The area field ${field.name} has no options property.
|
|
138
|
+
|
|
139
|
+
You probably forgot to nest the widgets property
|
|
140
|
+
in an options property.
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
134
143
|
_.each(options.widgets, function (options, name) {
|
|
135
144
|
const manager = self.widgetManagers[name];
|
|
136
145
|
if (manager) {
|