apostrophe 3.3.0 → 3.5.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 +76 -0
- package/deploy-test-count +1 -1
- package/index.js +76 -11
- package/lib/moog-require.js +18 -3
- package/lib/moog.js +19 -8
- package/modules/@apostrophecms/admin-bar/index.js +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +44 -24
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +3 -2
- package/modules/@apostrophecms/area/index.js +38 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +3 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +4 -4
- package/modules/@apostrophecms/asset/index.js +9 -9
- package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +5 -2
- package/modules/@apostrophecms/attachment/index.js +63 -1
- package/modules/@apostrophecms/db/index.js +0 -44
- package/modules/@apostrophecms/doc/index.js +70 -46
- package/modules/@apostrophecms/doc-type/index.js +14 -15
- package/modules/@apostrophecms/express/index.js +2 -2
- package/modules/@apostrophecms/global/index.js +13 -18
- package/modules/@apostrophecms/i18n/i18n/en.json +13 -0
- package/modules/@apostrophecms/i18n/index.js +20 -3
- package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nCrossDomainSession.js +3 -21
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +153 -121
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +22 -12
- package/modules/@apostrophecms/image-widget/views/widget.html +1 -0
- package/modules/@apostrophecms/login/index.js +38 -4
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +3 -0
- package/modules/@apostrophecms/migration/index.js +2 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +1 -1
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +3 -1
- package/modules/@apostrophecms/module/index.js +2 -2
- package/modules/@apostrophecms/module/lib/events.js +10 -0
- package/modules/@apostrophecms/page/index.js +57 -104
- package/modules/@apostrophecms/page-type/index.js +53 -15
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +10 -14
- package/modules/@apostrophecms/piece-type/index.js +21 -31
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +4 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +14 -6
- 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 +5 -7
- 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 +26 -28
- package/modules/@apostrophecms/schema/lib/joinr.js +1 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +8 -5
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +9 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +53 -11
- package/modules/@apostrophecms/search/index.js +3 -3
- package/modules/@apostrophecms/submitted-draft/index.js +1 -1
- package/modules/@apostrophecms/task/index.js +15 -7
- package/modules/@apostrophecms/template/index.js +1 -1
- package/modules/@apostrophecms/ui/index.js +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +16 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/localized-v-tooltip.js +1 -1
- 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 +6 -2
- package/modules/@apostrophecms/util/ui/src/util.js +7 -0
- package/package.json +4 -2
- package/scripts/lint-i18n.js +2 -2
- package/test/bootstrapping.js +3 -63
- package/test/draft-published.js +4 -6
- package/test/events.js +16 -1
- package/test/events2.js +1 -1
- package/test/login.js +183 -0
- package/test/moog.js +47 -0
- package/test/pieces.js +51 -2
- package/test/subdir-project/app.js +3 -0
- package/test/subdir-project.js +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.5.0 - 2021-09-23
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* Pinned dependency on `vue-material-design-icons` to fix `apos-build.js` build error in production.
|
|
8
|
+
* 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
|
+
* Fixes moog error messages to reflect the recommended pattern of customization functions only taking `self` as an argument.
|
|
10
|
+
* Rich Text widgets now instantiate with a valid element from the `styles` option rather than always starting with an unclassed `<p>` tag.
|
|
11
|
+
* 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.
|
|
12
|
+
* 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.
|
|
13
|
+
* `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.
|
|
14
|
+
* 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.
|
|
15
|
+
* 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).
|
|
16
|
+
* `options.testModule` works properly when implementing unit tests for an npm module that is namespaced.
|
|
17
|
+
|
|
18
|
+
### Changes
|
|
19
|
+
|
|
20
|
+
* 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.
|
|
21
|
+
|
|
22
|
+
### Adds
|
|
23
|
+
|
|
24
|
+
* Rich Text widget's styles support a `def` property for specifying the default style the editor should instantiate with.
|
|
25
|
+
* A more helpful error message if a field of type `area` is missing its `options` property.
|
|
26
|
+
|
|
27
|
+
## 3.4.1 - 2021-09-13
|
|
28
|
+
|
|
29
|
+
No changes. Publishing to correctly mark the latest 3.x release as "latest" in npm.
|
|
30
|
+
|
|
31
|
+
## 3.4.0 - 2021-09-13
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
|
|
35
|
+
* 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.
|
|
36
|
+
* 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.
|
|
37
|
+
|
|
38
|
+
### Fixes
|
|
39
|
+
|
|
40
|
+
* The `apos.attachment.each` method, intended for migrations, now respects its `criteria` argument. This was necessary to the above security fix.
|
|
41
|
+
* Removes a lodash wrapper around `@apostrophecms/express` `bodyParser.json` options that prevented adding custom options to the body parser.
|
|
42
|
+
* Uses `req.clone` consistently when creating a new `req` object with a different mode or locale for localization purposes, etc.
|
|
43
|
+
* Fixes bug in the "select all" relationship chooser UI where it selected unpublished items.
|
|
44
|
+
* Fixes bug in "next" and "previous" query builders.
|
|
45
|
+
* Cutting and pasting widgets now works between locales that do not share a hostname, provided that you switch locales after cutting (it does not work between tabs that are already open on separate hostnames).
|
|
46
|
+
* The `req.session` object now exists in task `req` objects, for better compatibility. It has no actual persistence.
|
|
47
|
+
* Unlocalized piece types, such as users, may now be selected as part of a relationship when browsing.
|
|
48
|
+
* Unpublished localized piece types may not be selected via the autocomplete feature of the relationship input field, which formerly ignored this requirement, although the browse button enforced it.
|
|
49
|
+
* The server-side JavaScript and REST APIs to delete pieces now work properly for pieces that are not subject to either localization or draft/published workflow at all the (`localize: false` option). UI for this is under discussion, this is just a bug fix for the back end feature which already existed.
|
|
50
|
+
* Starting in version 3.3.1, a newly added image widget did not display its image until the page was refreshed. This has been fixed.
|
|
51
|
+
* A bug that prevented Undo operations from working properly and resulted in duplicate widget _id properties has been fixed.
|
|
52
|
+
* A bug that caused problems for Undo operations in nested widgets, i.e. layout or multicolumn widgets, has been fixed.
|
|
53
|
+
* Duplicate widget _id properties within the same document are now prevented on the server side at save time.
|
|
54
|
+
* Existing duplicate widget _id properties are corrected by a one-time migration.
|
|
55
|
+
|
|
56
|
+
### Adds
|
|
57
|
+
|
|
58
|
+
* Adds a linter to warn in dev mode when a module name include a period.
|
|
59
|
+
* Lints module names for `apostrophe-` prefixes even if they don't have a module directory (e.g., only in `app.js`).
|
|
60
|
+
* Starts all `warnDev` messages with a line break and warning symbol (⚠️) to stand out in the console.
|
|
61
|
+
* `apos.util.onReady` aliases `apos.util.onReadyAndRefresh` for brevity. The `apos.util.onReadyAndRefresh` method name will be deprecated in the next major version.
|
|
62
|
+
* Adds a developer setting that applies a margin between parent and child areas, allowing developers to change the default spacing in nested areas.
|
|
63
|
+
|
|
64
|
+
### Changes
|
|
65
|
+
|
|
66
|
+
* Removes the temporary `trace` method from the `@apostrophecms/db` module.
|
|
67
|
+
* 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.
|
|
68
|
+
* Only autofocuses rich text editors when they are empty.
|
|
69
|
+
* Nested areas now have a vertical margin applied when editing, allowing easier access to the parent area's controls.
|
|
70
|
+
|
|
71
|
+
## 3.3.1 - 2021-09-01
|
|
72
|
+
|
|
73
|
+
### Fixes
|
|
74
|
+
|
|
75
|
+
* In some situations it was possible for a relationship with just one selected document to list that document several times in the returned result, resulting in very large responses.
|
|
76
|
+
* Permissions roles UI localized correctly.
|
|
77
|
+
* Do not crash on startup if users have a relationship to another type. This was caused by the code that checks whether any users exist to present a warning to developers. That code was running too early for relationships to work due to event timing issues.
|
|
78
|
+
|
|
3
79
|
## 3.3.0 - 2021-08-30
|
|
4
80
|
|
|
5
81
|
### Fixes
|
package/deploy-test-count
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
6
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const argv = require('boring')({ end: true });
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const npmResolve = require('resolve');
|
|
6
6
|
let defaults = require('./defaults.js');
|
|
7
|
+
const { stripIndent } = require('common-tags');
|
|
7
8
|
|
|
8
9
|
// **Awaiting the Apostrophe function is optional**
|
|
9
10
|
//
|
|
@@ -102,13 +103,25 @@ module.exports = async function(options) {
|
|
|
102
103
|
// are out there and what icons they need without
|
|
103
104
|
// actually instantiating them
|
|
104
105
|
self.modulesToBeInstantiated = modulesToBeInstantiated;
|
|
106
|
+
self.eventAliases = {};
|
|
107
|
+
self.aliasEvent('modulesReady', 'modulesRegistered');
|
|
108
|
+
self.aliasEvent('afterInit', 'ready');
|
|
105
109
|
|
|
106
110
|
defineModules();
|
|
107
111
|
|
|
108
112
|
await instantiateModules();
|
|
109
113
|
lintModules();
|
|
110
|
-
await self.emit('
|
|
111
|
-
|
|
114
|
+
await self.emit('modulesRegistered'); // formerly modulesReady
|
|
115
|
+
self.apos.schema.validateAllSchemas();
|
|
116
|
+
self.apos.schema.registerAllSchemas();
|
|
117
|
+
await self.apos.migration.migrate(); // emits before and after events, inside the lock
|
|
118
|
+
await self.apos.global.insertIfMissing();
|
|
119
|
+
await self.apos.page.implementParkAllInDefaultLocale();
|
|
120
|
+
await self.apos.doc.replicate(); // emits beforeReplicate and afterReplicate events
|
|
121
|
+
// Replicate will have created the parked pages across locales if needed, but we may
|
|
122
|
+
// still need to reset parked properties
|
|
123
|
+
await self.apos.page.implementParkAllInOtherLocales();
|
|
124
|
+
await self.emit('ready'); // formerly afterInit
|
|
112
125
|
if (self.taskRan) {
|
|
113
126
|
process.exit(0);
|
|
114
127
|
} else {
|
|
@@ -241,6 +254,8 @@ module.exports = async function(options) {
|
|
|
241
254
|
// when options.testModule is true. There must be a
|
|
242
255
|
// test/ or tests/ subdir of the module containing
|
|
243
256
|
// a test.js file that runs under mocha via devDependencies.
|
|
257
|
+
// If `options.testModule` is a string it will be used as a
|
|
258
|
+
// namespace for the test module.
|
|
244
259
|
|
|
245
260
|
function testModule() {
|
|
246
261
|
if (!options.testModule) {
|
|
@@ -264,9 +279,17 @@ module.exports = async function(options) {
|
|
|
264
279
|
if (testDir === moduleDir) {
|
|
265
280
|
throw new Error('Test file must be in test/ or tests/ subdirectory of module');
|
|
266
281
|
}
|
|
282
|
+
|
|
283
|
+
const pkgName = require(`${moduleDir}/package.json`).name;
|
|
284
|
+
let pkgNamespace = '';
|
|
285
|
+
if (pkgName.includes('/')) {
|
|
286
|
+
const parts = pkgName.split('/');
|
|
287
|
+
pkgNamespace = '/' + parts.slice(0, parts.length - 1).join('/');
|
|
288
|
+
}
|
|
289
|
+
|
|
267
290
|
if (!fs.existsSync(testDir + '/node_modules')) {
|
|
268
|
-
fs.mkdirSync(testDir + '/node_modules');
|
|
269
|
-
fs.symlinkSync(moduleDir, testDir + '/node_modules/' +
|
|
291
|
+
fs.mkdirSync(testDir + '/node_modules' + pkgNamespace, { recursive: true });
|
|
292
|
+
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, 'dir');
|
|
270
293
|
}
|
|
271
294
|
|
|
272
295
|
// Not quite superfluous: it'll return self.root, but
|
|
@@ -336,19 +359,24 @@ module.exports = async function(options) {
|
|
|
336
359
|
validSteps.push(step.name);
|
|
337
360
|
}
|
|
338
361
|
}
|
|
362
|
+
|
|
363
|
+
if (!fs.existsSync(self.localModules)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
339
367
|
const dirs = fs.readdirSync(self.localModules);
|
|
340
368
|
for (const dir of dirs) {
|
|
341
369
|
if (dir.match(/^@/)) {
|
|
342
370
|
const nsDirs = fs.readdirSync(`${self.localModules}/${dir}`);
|
|
343
371
|
for (let nsDir of nsDirs) {
|
|
344
372
|
nsDir = `${dir}/${nsDir}`;
|
|
345
|
-
|
|
373
|
+
testDir(nsDir);
|
|
346
374
|
}
|
|
347
375
|
} else {
|
|
348
|
-
|
|
376
|
+
testDir(dir);
|
|
349
377
|
}
|
|
350
378
|
}
|
|
351
|
-
function
|
|
379
|
+
function testDir(name) {
|
|
352
380
|
// Projects that have different theme modules activated at different times
|
|
353
381
|
// are a frequent source of false positives for this warning, so ignore
|
|
354
382
|
// seemingly unused modules with "theme" in the name
|
|
@@ -362,14 +390,25 @@ module.exports = async function(options) {
|
|
|
362
390
|
// index.js might not exist, that's fine for our purposes
|
|
363
391
|
}
|
|
364
392
|
if (name.match(/^apostrophe-/)) {
|
|
365
|
-
warn(
|
|
393
|
+
warn(
|
|
394
|
+
'namespace-apostrophe-modules',
|
|
395
|
+
stripIndent`
|
|
396
|
+
You have a ${self.localModules}/${name} folder.
|
|
397
|
+
You are probably trying to configure an official Apostrophe module, but those
|
|
398
|
+
are namespaced now. Your directory should be renamed
|
|
399
|
+
${self.localModules}/${name.replace(/^apostrophe-/, '@apostrophecms/')}
|
|
400
|
+
|
|
401
|
+
If you get this warning for your own, original module, do not use the
|
|
402
|
+
"apostrophe-" prefix. It is reserved.
|
|
403
|
+
`
|
|
404
|
+
);
|
|
366
405
|
} else {
|
|
367
406
|
warn('orphan-modules', `You have a ${self.localModules}/${name} folder, but that module is not activated in app.js and it is not a base class of any other active module. Right now that code doesn't do anything.`);
|
|
368
407
|
}
|
|
369
408
|
}
|
|
370
409
|
function warn(name, message) {
|
|
371
|
-
if (self.
|
|
372
|
-
self.
|
|
410
|
+
if (self.util) {
|
|
411
|
+
self.util.warnDevOnce(name, message);
|
|
373
412
|
} else {
|
|
374
413
|
// apos.util not in play, this can be the case in our bootstrap tests
|
|
375
414
|
if (self.argv[`ignore-${name}`]) {
|
|
@@ -382,6 +421,30 @@ module.exports = async function(options) {
|
|
|
382
421
|
}
|
|
383
422
|
|
|
384
423
|
for (const [ name, module ] of Object.entries(self.modules)) {
|
|
424
|
+
if (name.match(/^apostrophe-/)) {
|
|
425
|
+
self.util.warnDevOnce(
|
|
426
|
+
'namespace-apostrophe-modules',
|
|
427
|
+
stripIndent`
|
|
428
|
+
You have configured an ${name} module.
|
|
429
|
+
You are probably trying to configure an official Apostrophe module, but those
|
|
430
|
+
are namespaced now. Your module should be renamed ${name.replace(/^apostrophe-/, '@apostrophecms/')}
|
|
431
|
+
|
|
432
|
+
If you get this warning for your own original module, do not use the
|
|
433
|
+
"apostrophe-" prefix. It is reserved.
|
|
434
|
+
`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const moduleNameRegex = /\./;
|
|
438
|
+
if (name.match(moduleNameRegex)) {
|
|
439
|
+
self.util.warnDevOnce(
|
|
440
|
+
'module-name-periods',
|
|
441
|
+
stripIndent`
|
|
442
|
+
You have configured a module named ${name}.
|
|
443
|
+
Modules names may not include periods. Please change this to avoid bugs.
|
|
444
|
+
`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
385
448
|
if (module.options.extends && ((typeof module.options.extends) === 'string')) {
|
|
386
449
|
lint(`The module ${name} contains an "extends" option. This is probably a\nmistake. In Apostrophe "extend" is used to extend other modules.`);
|
|
387
450
|
}
|
|
@@ -437,7 +500,9 @@ module.exports = async function(options) {
|
|
|
437
500
|
}
|
|
438
501
|
|
|
439
502
|
function lint(s) {
|
|
440
|
-
self.util.warnDev(
|
|
503
|
+
self.util.warnDev(stripIndent`
|
|
504
|
+
It looks like you may have made a mistake in your code:\n${s}
|
|
505
|
+
`);
|
|
441
506
|
}
|
|
442
507
|
}
|
|
443
508
|
|
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;
|
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,26 @@ 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
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
206
217
|
that[`${cascade}Groups`] = {
|
|
207
218
|
...groups,
|
|
208
219
|
...klona(properties.group)
|
|
@@ -289,14 +300,14 @@ module.exports = function(options) {
|
|
|
289
300
|
};
|
|
290
301
|
}
|
|
291
302
|
if ((typeof step[keyword]) !== 'function') {
|
|
292
|
-
throw stepError(step, `${keyword} must be a function that takes (self
|
|
303
|
+
throw stepError(step, `${keyword} must be a function that takes (self) and returns an object`);
|
|
293
304
|
}
|
|
294
305
|
_.merge(context, step[keyword](that, options));
|
|
295
306
|
}
|
|
296
307
|
const extend = getExtendKey(keyword);
|
|
297
308
|
if (step[extend]) {
|
|
298
309
|
if ((typeof step[extend]) !== 'function') {
|
|
299
|
-
throw stepError(step, `${extend} must be a function that takes (self
|
|
310
|
+
throw stepError(step, `${extend} must be a function that takes (self) and returns an object`);
|
|
300
311
|
}
|
|
301
312
|
const extensions = step[extend](that, options);
|
|
302
313
|
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
|
},
|
|
@@ -136,7 +140,8 @@ export default {
|
|
|
136
140
|
const result = await apos.http.post(`${apos.i18n.action}/locale`, {
|
|
137
141
|
body: {
|
|
138
142
|
contextDocId: apos.adminBar.context && apos.adminBar.context._id,
|
|
139
|
-
locale: name
|
|
143
|
+
locale: name,
|
|
144
|
+
clipboard: localStorage.getItem('aposWidgetClipboard')
|
|
140
145
|
}
|
|
141
146
|
});
|
|
142
147
|
|
|
@@ -148,7 +153,7 @@ export default {
|
|
|
148
153
|
}
|
|
149
154
|
} else {
|
|
150
155
|
const currentLocale = apos.i18n.locales[apos.locale];
|
|
151
|
-
|
|
156
|
+
this.$refs.menu.hide();
|
|
152
157
|
const toLocalize = await apos.confirm(
|
|
153
158
|
{
|
|
154
159
|
icon: false,
|
|
@@ -206,6 +211,13 @@ export default {
|
|
|
206
211
|
&::after {
|
|
207
212
|
right: 0;
|
|
208
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
|
+
}
|
|
209
221
|
}
|
|
210
222
|
|
|
211
223
|
.apos-locales-picker {
|
|
@@ -213,15 +225,14 @@ export default {
|
|
|
213
225
|
}
|
|
214
226
|
|
|
215
227
|
.apos-locales-filter {
|
|
228
|
+
@include type-large;
|
|
216
229
|
box-sizing: border-box;
|
|
217
230
|
width: 100%;
|
|
218
|
-
padding:
|
|
219
|
-
font-size: 14px;
|
|
231
|
+
padding: 20px 45px 20px 20px;
|
|
220
232
|
border-top: 0;
|
|
221
233
|
border-right: 0;
|
|
222
234
|
border-bottom: 1px solid var(--a-base-9);
|
|
223
235
|
border-left: 0;
|
|
224
|
-
color: var(--a-text-primary);
|
|
225
236
|
border-top-right-radius: var(--a-border-radius);
|
|
226
237
|
border-top-left-radius: var(--a-border-radius);
|
|
227
238
|
|
|
@@ -241,8 +252,7 @@ export default {
|
|
|
241
252
|
max-height: 350px;
|
|
242
253
|
overflow-y: scroll;
|
|
243
254
|
padding-left: 0;
|
|
244
|
-
margin
|
|
245
|
-
margin-bottom: 0;
|
|
255
|
+
margin: $spacing-base 0;
|
|
246
256
|
font-weight: var(--a-weight-base);
|
|
247
257
|
}
|
|
248
258
|
|
|
@@ -263,7 +273,7 @@ export default {
|
|
|
263
273
|
.apos-check {
|
|
264
274
|
position: absolute;
|
|
265
275
|
top: 50%;
|
|
266
|
-
left:
|
|
276
|
+
left: 18px;
|
|
267
277
|
transform: translateY(-50%);
|
|
268
278
|
color: var(--a-primary);
|
|
269
279
|
stroke: var(--a-primary);
|
|
@@ -278,11 +288,12 @@ export default {
|
|
|
278
288
|
.apos-locale-localized {
|
|
279
289
|
position: relative;
|
|
280
290
|
top: -1px;
|
|
291
|
+
left: 5px;
|
|
281
292
|
display: inline-block;
|
|
282
|
-
|
|
283
|
-
|
|
293
|
+
width: 3px;
|
|
294
|
+
height: 3px;
|
|
284
295
|
border: 1px solid var(--a-base-5);
|
|
285
|
-
border-radius:
|
|
296
|
+
border-radius: 50%;
|
|
286
297
|
|
|
287
298
|
&.apos-state-is-localized {
|
|
288
299
|
background-color: var(--a-success);
|
|
@@ -292,7 +303,7 @@ export default {
|
|
|
292
303
|
}
|
|
293
304
|
|
|
294
305
|
.apos-available-locales {
|
|
295
|
-
padding:
|
|
306
|
+
padding: $spacing-double;
|
|
296
307
|
border-top: 1px solid var(--a-base-9);
|
|
297
308
|
}
|
|
298
309
|
|
|
@@ -306,4 +317,13 @@ export default {
|
|
|
306
317
|
margin-right: 10px;
|
|
307
318
|
margin-bottom: 5px;
|
|
308
319
|
}
|
|
320
|
+
|
|
321
|
+
.apos-available-description {
|
|
322
|
+
margin-top: 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.apos-locale-name {
|
|
326
|
+
text-transform: uppercase;
|
|
327
|
+
}
|
|
328
|
+
|
|
309
329
|
</style>
|
|
@@ -48,7 +48,7 @@ export default {
|
|
|
48
48
|
// If the URL references a draft, go into draft mode but then clean up the URL
|
|
49
49
|
const draftMode = query.aposMode || 'published';
|
|
50
50
|
if (draftMode === 'draft') {
|
|
51
|
-
const newQuery = { ...
|
|
51
|
+
const newQuery = { ...query };
|
|
52
52
|
delete newQuery.aposMode;
|
|
53
53
|
history.replaceState(null, '', apos.http.addQueryToUrl(location.href, newQuery));
|
|
54
54
|
}
|
|
@@ -442,6 +442,7 @@ export default {
|
|
|
442
442
|
this.rememberLastBaseContext();
|
|
443
443
|
},
|
|
444
444
|
onContextEdited(patch) {
|
|
445
|
+
patch = klona(patch);
|
|
445
446
|
this.patchesSinceLoaded.push(patch);
|
|
446
447
|
this.patchesSinceSave.push(patch);
|
|
447
448
|
this.undone = [];
|
|
@@ -512,7 +513,7 @@ export default {
|
|
|
512
513
|
|
|
513
514
|
if (refreshable) {
|
|
514
515
|
refreshable.innerHTML = content;
|
|
515
|
-
if (!this.original) {
|
|
516
|
+
if (this.editMode && (!this.original)) {
|
|
516
517
|
// the first time we enter edit mode on the page, we need to
|
|
517
518
|
// establish a baseline for undo/redo. Use our
|
|
518
519
|
// "@ notation" PATCH feature. Sort the areas by DOM depth
|
|
@@ -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
|
|
@@ -26,6 +27,7 @@ module.exports = {
|
|
|
26
27
|
self.richTextWidgetTypes = [];
|
|
27
28
|
self.widgetManagers = {};
|
|
28
29
|
self.enableBrowserData();
|
|
30
|
+
self.addDeduplicateWidgetIdsMigration();
|
|
29
31
|
},
|
|
30
32
|
apiRoutes(self) {
|
|
31
33
|
return {
|
|
@@ -52,6 +54,9 @@ module.exports = {
|
|
|
52
54
|
widget = await sanitize(widget);
|
|
53
55
|
widget._edit = true;
|
|
54
56
|
widget._docId = _docId;
|
|
57
|
+
// So that carrying out relationship loading again can yield results
|
|
58
|
+
// (the idsStorage must be populated as if we were saving)
|
|
59
|
+
self.apos.schema.prepareForStorage(req, widget);
|
|
55
60
|
await load();
|
|
56
61
|
return render();
|
|
57
62
|
async function sanitize(widget) {
|
|
@@ -71,7 +76,7 @@ module.exports = {
|
|
|
71
76
|
},
|
|
72
77
|
handlers(self) {
|
|
73
78
|
return {
|
|
74
|
-
'apostrophe:
|
|
79
|
+
'apostrophe:modulesRegistered': {
|
|
75
80
|
getRichTextWidgetTypes() {
|
|
76
81
|
_.each(self.widgetManagers, function (manager, name) {
|
|
77
82
|
if (manager.getRichText) {
|
|
@@ -127,6 +132,14 @@ module.exports = {
|
|
|
127
132
|
const field = self.apos.schema.getFieldById(area._fieldId);
|
|
128
133
|
|
|
129
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
|
+
}
|
|
130
143
|
_.each(options.widgets, function (options, name) {
|
|
131
144
|
const manager = self.widgetManagers[name];
|
|
132
145
|
if (manager) {
|
|
@@ -564,6 +577,30 @@ module.exports = {
|
|
|
564
577
|
widgetManagers,
|
|
565
578
|
action: self.action
|
|
566
579
|
};
|
|
580
|
+
},
|
|
581
|
+
async addDeduplicateWidgetIdsMigration() {
|
|
582
|
+
self.apos.migration.add('deduplicate-widget-ids', () => {
|
|
583
|
+
// Make them globally unique because that is easiest to
|
|
584
|
+
// definitely get correct for this one-time migration, although
|
|
585
|
+
// there is no guarantee that widget ids are unique between
|
|
586
|
+
// separate documents going forward. The guarantee is that they
|
|
587
|
+
// will be unique within documents
|
|
588
|
+
const seen = new Set();
|
|
589
|
+
return self.apos.migration.eachWidget({}, async (doc, widget, dotPath) => {
|
|
590
|
+
if ((!widget._id) || seen.has(widget._id)) {
|
|
591
|
+
const _id = self.apos.util.generateId();
|
|
592
|
+
return self.apos.doc.db.updateOne({
|
|
593
|
+
_id: doc._id
|
|
594
|
+
}, {
|
|
595
|
+
$set: {
|
|
596
|
+
[`${dotPath}._id`]: _id
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
} else {
|
|
600
|
+
seen.add(widget._id);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
});
|
|
567
604
|
}
|
|
568
605
|
};
|
|
569
606
|
},
|
|
@@ -384,9 +384,9 @@ export default {
|
|
|
384
384
|
}
|
|
385
385
|
if (!this.focused) {
|
|
386
386
|
this.state.labels.show = false;
|
|
387
|
+
this.state.add.top.show = false;
|
|
388
|
+
this.state.add.bottom.show = false;
|
|
387
389
|
}
|
|
388
|
-
this.state.add.top.show = false;
|
|
389
|
-
this.state.add.bottom.show = false;
|
|
390
390
|
},
|
|
391
391
|
|
|
392
392
|
focus(e) {
|
|
@@ -396,8 +396,8 @@ export default {
|
|
|
396
396
|
this.focused = true;
|
|
397
397
|
this.state.container.focus = true;
|
|
398
398
|
this.state.controls.show = true;
|
|
399
|
-
this.state.add.top.show =
|
|
400
|
-
this.state.add.bottom.show =
|
|
399
|
+
this.state.add.top.show = true;
|
|
400
|
+
this.state.add.bottom.show = true;
|
|
401
401
|
this.state.labels.show = true;
|
|
402
402
|
document.addEventListener('click', this.unfocus);
|
|
403
403
|
},
|