apostrophe 3.9.0 → 3.12.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 +62 -0
- package/index.js +37 -1
- package/lib/moog.js +28 -3
- package/modules/@apostrophecms/admin-bar/ui/apos/apps/AposAdminBar.js +7 -1
- package/modules/@apostrophecms/any-page-type/index.js +2 -0
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +13 -1
- package/modules/@apostrophecms/asset/index.js +7 -2
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +2 -2
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +1 -1
- package/modules/@apostrophecms/busy/ui/apos/apps/AposBusy.js +3 -1
- package/modules/@apostrophecms/doc/index.js +2 -2
- package/modules/@apostrophecms/doc-type/index.js +12 -8
- package/modules/@apostrophecms/i18n/index.js +59 -12
- package/modules/@apostrophecms/login/index.js +23 -10
- package/modules/@apostrophecms/login/ui/apos/apps/AposLogin.js +3 -2
- package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +8 -5
- package/modules/@apostrophecms/module/index.js +12 -2
- package/modules/@apostrophecms/notification/ui/apos/apps/AposNotification.js +3 -1
- package/modules/@apostrophecms/page/index.js +4 -5
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/piece-type/index.js +1 -1
- package/modules/@apostrophecms/rich-text-widget/index.js +2 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +3 -1
- package/modules/@apostrophecms/schema/index.js +25 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +12 -5
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +7 -3
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +17 -0
- package/modules/@apostrophecms/util/index.js +3 -9
- package/modules/@apostrophecms/widget-type/index.js +9 -0
- package/package.json +10 -10
- package/test/modules/base-type/i18n/custom/en.json +4 -0
- package/test/modules/base-type/i18n/en.json +3 -0
- package/test/modules/nested-module-subdirs/example1/index.js +5 -0
- package/test/modules/nested-module-subdirs/modules.js +7 -0
- package/test/modules/subtype/i18n/custom/en.json +4 -0
- package/test/modules/subtype/index.js +7 -0
- package/test/moog.js +48 -0
- package/test/static-i18n.js +28 -0
- package/test/with-nested-module-subdirs.js +32 -0
- package/test/without-nested-module-subdirs.js +31 -0
- package/test-lib/util.js +4 -2
- package/.github/pull_request_template.md +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.12.0 - 2022-01-21
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* It is now best practice to deliver namespaced i18n strings as JSON files in module-level subdirectories of `i18n/` named to match the namespace, e.g. `i18n/ourTeam` if the namespace is `ourTeam`. This allows base class modules to deliver phrases to any namespace without conflicting with those introduced at project level. The `i18n` option is now deprecated in favor of the new `i18n` module format section, which is only needed if `browser: true` must be specified for a namespace.
|
|
8
|
+
* Brought back the `nestedModuleSubdirs` feature from A2, which allows modules to be nested in subdirectories if `nestedModuleSubdirs: true` is set in `app.js`. As in A2, module configuration (including activation) can also be grouped in a `modules.js` file in such subdirectories.
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
|
|
12
|
+
* Fixes minor inline documentation comments.
|
|
13
|
+
* UI strings that are not registered localization keys will now display properly when they contain a colon (`:`). These were previously interpreted as i18next namespace/key pairs and the "namespace" portion was left out.
|
|
14
|
+
* Fixes a bug where changing the page type immediately after clicking "New Page" would produce a console error. In general, areas and checkboxes now correctly handle their value being changed to `null` by the parent schema after initial startup of the `AposInputArea` or `AposInputCheckboxes` component.
|
|
15
|
+
* It is now best practice to deliver namespaced i18n strings as JSON files in module-level subdirectories of `i18n/` named to match the namespace, e.g. `i18n/ourTeam` if the namespace is `ourTeam`. This allows base class modules to deliver phrases to any namespace without conflicting with those introduced at project level. The `i18n` option is now deprecated in favor of the new `i18n` module format section, which is only needed if `browser: true` must be specified for a namespace.
|
|
16
|
+
* Removes the `@apostrophecms/util` module template helper `indexBy`, which was using a lodash method not included in lodash v4.
|
|
17
|
+
* Removes an unimplemented `csrfExceptions` module section cascade. Use the `csrfExceptions` *option* of any module to set an array of URLs excluded from CSRF protection. More information is forthcoming in the documentation.
|
|
18
|
+
* Fix `[Object Object]` in the console when warning `A permission.can() call was made with a type that has no manager` is printed.
|
|
19
|
+
|
|
20
|
+
### Changes
|
|
21
|
+
|
|
22
|
+
* Temporarily removes `npm audit` from our automated tests because of a sub-dependency of vue-loader that doesn't actually cause a security vulnerability for apostrophe.
|
|
23
|
+
|
|
24
|
+
## 3.11.0 - 2022-01-06
|
|
25
|
+
|
|
26
|
+
### Adds
|
|
27
|
+
|
|
28
|
+
* Apostrophe now extends Passport's `req.login` to emit an `afterSessionLogin` event from the `@apostrophecms:login` module, with `req` as an argument. Note that this does not occur at all for login API calls that return a bearer token rather than establishing an Express session.
|
|
29
|
+
|
|
30
|
+
### Fixes
|
|
31
|
+
|
|
32
|
+
* Apostrophe's extension of `req.login` now accounts for the `req.logIn` alias and the skippable `options` parameter, which is relied upon in some `passport` strategies.
|
|
33
|
+
* Apostrophe now warns if a nonexistent widget type is configured for an area field, with special attention to when `-widget` has been erroneously included in the name. For backwards compatibility this is a startup warning rather than a fatal error, as sites generally did operate successfully otherwise with this type of bug present.
|
|
34
|
+
|
|
35
|
+
### Changes
|
|
36
|
+
|
|
37
|
+
* Unpins `vue-click-outside-element` the packaging of which has been fixed upstream.
|
|
38
|
+
* Adds deprecation note to `__testDefaults` option. It is not in use, but removing would be a minor BC break we don't need to make.
|
|
39
|
+
* Allows test modules to use a custom port as an option on the `@apostrophecms/express` module.
|
|
40
|
+
* Removes the code base pull request template to instead inherit the organization-level template.
|
|
41
|
+
* Adds `npm audit` back to the test scripts.
|
|
42
|
+
|
|
43
|
+
## 3.10.0 - 2021-12-22
|
|
44
|
+
|
|
45
|
+
### Fixes
|
|
46
|
+
|
|
47
|
+
* `slug` type fields can now have an empty string or `null` as their `def` value without the string `'none'` populating automatically.
|
|
48
|
+
* The `underline` feature works properly in tiptap toolbar configuration.
|
|
49
|
+
* Required checkbox fields now properly prevent editor submission when empty.
|
|
50
|
+
* Pins `vue-click-outside-element` to a version that does not attempt to use `eval` in its distribution build, which is incompatible with a strict Content Security Policy.
|
|
51
|
+
|
|
52
|
+
### Adds
|
|
53
|
+
|
|
54
|
+
* Adds a `last` option to fields. Setting `last: true` on a field puts that field at the end of the field's widget order. If more than one field has that option active the true last item will depend on general field registration order. If the field is ordered with the `fields.order` array or field group ordering, those specified orders will take precedence.
|
|
55
|
+
|
|
56
|
+
### Changes
|
|
57
|
+
|
|
58
|
+
* Adds deprecation notes to the widget class methods `getWidgetWrapperClasses` and `getWidgetClasses` from A2.
|
|
59
|
+
* Adds a deprecation note to the `reorganize` query builder for the next major version.
|
|
60
|
+
* Uses the runtime build of Vue. This has major performance and bundle size benefits, however it does require changes to Apostrophe admin UI apps that use a `template` property (components should require no changes, just apps require an update). These apps must now use a `render` function instead. Since custom admin UI apps are not yet a documented feature we do not regard this as a bc break.
|
|
61
|
+
* Compatible with the `@apostrophecms/security-headers` module, which supports a strict `Content-Security-Policy`.
|
|
62
|
+
* Adds a deprecation note to the `addLateCriteria` query builder.
|
|
63
|
+
* Updates the `toCount` doc type query method to use Math.ceil rather than Math.floor plus an additional step.
|
|
64
|
+
|
|
3
65
|
## 3.9.0 - 2021-12-08
|
|
4
66
|
|
|
5
67
|
### Adds
|
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const cluster = require('cluster');
|
|
|
7
7
|
const { cpus } = require('os');
|
|
8
8
|
const process = require('process');
|
|
9
9
|
const npmResolve = require('resolve');
|
|
10
|
+
const glob = require('glob');
|
|
10
11
|
|
|
11
12
|
let defaults = require('./defaults.js');
|
|
12
13
|
|
|
@@ -273,6 +274,33 @@ module.exports = async function(options) {
|
|
|
273
274
|
return _module;
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
function nestedModuleSubdirs() {
|
|
278
|
+
if (!options.nestedModuleSubdirs) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const configs = glob.sync(self.localModules + '/**/modules.js');
|
|
282
|
+
_.each(configs, function(config) {
|
|
283
|
+
try {
|
|
284
|
+
_.merge(self.options.modules, require(config));
|
|
285
|
+
} catch (e) {
|
|
286
|
+
console.error(stripIndent`
|
|
287
|
+
When nestedModuleSubdirs is active, any modules.js file beneath:
|
|
288
|
+
|
|
289
|
+
${self.localModules}
|
|
290
|
+
|
|
291
|
+
must export an object containing configuration for Apostrophe modules.
|
|
292
|
+
|
|
293
|
+
The file:
|
|
294
|
+
|
|
295
|
+
${config}
|
|
296
|
+
|
|
297
|
+
did not parse.
|
|
298
|
+
`);
|
|
299
|
+
throw e;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
276
304
|
function autodetectBundles() {
|
|
277
305
|
const modules = _.keys(self.options.modules);
|
|
278
306
|
_.each(modules, function(name) {
|
|
@@ -406,7 +434,13 @@ module.exports = async function(options) {
|
|
|
406
434
|
localModules: self.localModules,
|
|
407
435
|
defaultBaseClass: '@apostrophecms/module',
|
|
408
436
|
sections: [ 'helpers', 'handlers', 'routes', 'apiRoutes', 'restApiRoutes', 'renderRoutes', 'middleware', 'customTags', 'components', 'tasks' ],
|
|
409
|
-
|
|
437
|
+
nestedModuleSubdirs: self.options.nestedModuleSubdirs,
|
|
438
|
+
unparsedSections: [
|
|
439
|
+
'queries',
|
|
440
|
+
'extendQueries',
|
|
441
|
+
'icons',
|
|
442
|
+
'i18n'
|
|
443
|
+
]
|
|
410
444
|
});
|
|
411
445
|
|
|
412
446
|
self.synth = synth;
|
|
@@ -417,6 +451,8 @@ module.exports = async function(options) {
|
|
|
417
451
|
self.redefine = self.synth.redefine;
|
|
418
452
|
self.create = self.synth.create;
|
|
419
453
|
|
|
454
|
+
nestedModuleSubdirs();
|
|
455
|
+
|
|
420
456
|
_.each(self.options.modules, function(options, name) {
|
|
421
457
|
synth.define(name, options);
|
|
422
458
|
});
|
package/lib/moog.js
CHANGED
|
@@ -179,17 +179,42 @@ module.exports = function(options) {
|
|
|
179
179
|
...properties.add
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
const lastFields = Object.entries(that[cascade])
|
|
184
|
+
.filter(([ field, val ]) => val.last === true)
|
|
185
|
+
.map(([ field, val ]) => field);
|
|
186
|
+
|
|
182
187
|
if (properties.remove) {
|
|
183
188
|
for (const field of properties.remove) {
|
|
184
189
|
delete that[cascade][field];
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
if (properties.order) {
|
|
193
|
+
// 1. Ordered fields
|
|
194
|
+
// 2. Other fields not marked as last
|
|
195
|
+
// 3. Fields marked as last
|
|
188
196
|
that[cascade] = Object.fromEntries([
|
|
189
197
|
...properties.order.map(field => [ field, that[cascade][field] ]),
|
|
190
|
-
...Object.keys(that[cascade])
|
|
198
|
+
...Object.keys(that[cascade])
|
|
199
|
+
.filter(field => {
|
|
200
|
+
return !properties.order.includes(field) &&
|
|
201
|
+
!lastFields.includes(field);
|
|
202
|
+
})
|
|
203
|
+
.map(field => [ field, that[cascade][field] ]),
|
|
204
|
+
...lastFields.filter(field => !properties.order.includes(field))
|
|
205
|
+
.map(field => [ field, that[cascade][field] ])
|
|
206
|
+
]);
|
|
207
|
+
} else if (lastFields.length > 0) {
|
|
208
|
+
// 1. Fields not marked as last
|
|
209
|
+
// 2. Fields marked as last
|
|
210
|
+
that[cascade] = Object.fromEntries([
|
|
211
|
+
...Object.keys(that[cascade])
|
|
212
|
+
.filter(field => !lastFields.includes(field))
|
|
213
|
+
.map(field => [ field, that[cascade][field] ]),
|
|
214
|
+
...lastFields.map(field => [ field, that[cascade][field] ])
|
|
191
215
|
]);
|
|
192
216
|
}
|
|
217
|
+
|
|
193
218
|
if (properties.group) {
|
|
194
219
|
const groups = klona(that[`${cascade}Groups`]);
|
|
195
220
|
for (const value of Object.values(properties.group)) {
|
|
@@ -344,9 +369,9 @@ module.exports = function(options) {
|
|
|
344
369
|
}
|
|
345
370
|
|
|
346
371
|
function capture(section) {
|
|
347
|
-
that[section] = {};
|
|
372
|
+
that.__meta[section] = {};
|
|
348
373
|
for (const step of steps) {
|
|
349
|
-
that[section][step.__meta.name] = step[section];
|
|
374
|
+
that.__meta[section][step.__meta.name] = step[section];
|
|
350
375
|
}
|
|
351
376
|
}
|
|
352
377
|
|
|
@@ -10,7 +10,13 @@ export default function() {
|
|
|
10
10
|
return window.apos;
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
|
-
|
|
13
|
+
render: function (h) {
|
|
14
|
+
return h('TheAposAdminBar', {
|
|
15
|
+
props: {
|
|
16
|
+
items: apos.adminBar.items
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
14
20
|
});
|
|
15
21
|
}
|
|
16
22
|
};
|
|
@@ -287,6 +287,8 @@ module.exports = {
|
|
|
287
287
|
// are suitable for display in the reorganize view.
|
|
288
288
|
// The only pages excluded are those with a `reorganize`
|
|
289
289
|
// property explicitly set to `false`.
|
|
290
|
+
// NOTE: This query builder is deprecated and will be removed in the
|
|
291
|
+
// next major version.
|
|
290
292
|
reorganize: {
|
|
291
293
|
def: null,
|
|
292
294
|
finalize() {
|
|
@@ -111,7 +111,19 @@ export default function() {
|
|
|
111
111
|
renderings
|
|
112
112
|
};
|
|
113
113
|
},
|
|
114
|
-
|
|
114
|
+
render(h) {
|
|
115
|
+
return h(component, {
|
|
116
|
+
props: {
|
|
117
|
+
options: this.options,
|
|
118
|
+
items: this.items,
|
|
119
|
+
choices: this.choices,
|
|
120
|
+
id: this.id,
|
|
121
|
+
docId: this.docId,
|
|
122
|
+
fieldId: this.fieldId,
|
|
123
|
+
renderings: this.renderings
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
115
127
|
});
|
|
116
128
|
}
|
|
117
129
|
}
|
|
@@ -752,8 +752,7 @@ module.exports = {
|
|
|
752
752
|
if (!self.shouldRefreshOnRestart()) {
|
|
753
753
|
return '';
|
|
754
754
|
}
|
|
755
|
-
|
|
756
|
-
return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id">\n${script}</script>`);
|
|
755
|
+
return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id" src="${self.action}/refresh-on-restart"></script>`);
|
|
757
756
|
},
|
|
758
757
|
url(path) {
|
|
759
758
|
return `${self.getAssetBaseUrl()}${path}`;
|
|
@@ -765,6 +764,12 @@ module.exports = {
|
|
|
765
764
|
return;
|
|
766
765
|
}
|
|
767
766
|
return {
|
|
767
|
+
get: {
|
|
768
|
+
refreshOnRestart(req) {
|
|
769
|
+
req.res.setHeader('content-type', 'text/javascript');
|
|
770
|
+
return fs.readFileSync(path.join(__dirname, '/lib/refresh-on-restart.js'), 'utf8');
|
|
771
|
+
}
|
|
772
|
+
},
|
|
768
773
|
// Use a POST route so IE11 doesn't cache it
|
|
769
774
|
post: {
|
|
770
775
|
async restartId(req) {
|
|
@@ -28,7 +28,7 @@ module.exports = ({
|
|
|
28
28
|
optimization: {
|
|
29
29
|
minimize: process.env.NODE_ENV === 'production'
|
|
30
30
|
},
|
|
31
|
-
devtool: '
|
|
31
|
+
devtool: 'source-map',
|
|
32
32
|
output: {
|
|
33
33
|
path: outputPath,
|
|
34
34
|
filename: outputFilename
|
|
@@ -42,7 +42,7 @@ module.exports = ({
|
|
|
42
42
|
resolve: {
|
|
43
43
|
extensions: [ '*', '.js', '.vue', '.json' ],
|
|
44
44
|
alias: {
|
|
45
|
-
vue$: 'vue/dist/vue.esm.js',
|
|
45
|
+
vue$: 'vue/dist/vue.runtime.esm.js',
|
|
46
46
|
// resolve apostrophe modules
|
|
47
47
|
Modules: path.resolve(modulesDir)
|
|
48
48
|
},
|
|
@@ -675,10 +675,10 @@ module.exports = {
|
|
|
675
675
|
},
|
|
676
676
|
|
|
677
677
|
// Insert the given document. Called by `.insert()`. You will usually want to
|
|
678
|
-
// call the
|
|
678
|
+
// call the insert method of the appropriate doc type manager instead:
|
|
679
679
|
//
|
|
680
680
|
// ```javascript
|
|
681
|
-
// self.apos.doc.getManager(doc.type).
|
|
681
|
+
// self.apos.doc.getManager(doc.type).insert(...)
|
|
682
682
|
// ```
|
|
683
683
|
//
|
|
684
684
|
// However you can override this method to alter the
|
|
@@ -1152,8 +1152,10 @@ module.exports = {
|
|
|
1152
1152
|
|
|
1153
1153
|
// `.addLateCriteria({...})` provides an object to be merged directly into the final
|
|
1154
1154
|
// criteria object that will go to MongoDB. This is to be used only
|
|
1155
|
-
// in cases where MongoDB forbids the use of an operator inside
|
|
1156
|
-
//
|
|
1155
|
+
// in cases where MongoDB forbids the use of an operator inside `$and`.
|
|
1156
|
+
//
|
|
1157
|
+
// TODO: Since `$near` can now be used in `$and` operators, this query
|
|
1158
|
+
// builder is deprecated and should be removed in the 4.x major version.
|
|
1157
1159
|
addLateCriteria: {
|
|
1158
1160
|
set(c) {
|
|
1159
1161
|
let lateCriteria = query.get('lateCriteria');
|
|
@@ -1420,7 +1422,7 @@ module.exports = {
|
|
|
1420
1422
|
}
|
|
1421
1423
|
},
|
|
1422
1424
|
|
|
1423
|
-
// `.permission('
|
|
1425
|
+
// `.permission('edit')` would limit the returned docs to those for which the
|
|
1424
1426
|
// user associated with the query's `req` has the named permission.
|
|
1425
1427
|
// By default, `view` is checked for. You might want to specify
|
|
1426
1428
|
// `edit`.
|
|
@@ -1658,7 +1660,11 @@ module.exports = {
|
|
|
1658
1660
|
const _req = query.req.clone({
|
|
1659
1661
|
mode: 'published'
|
|
1660
1662
|
});
|
|
1661
|
-
const publishedDocs = await self.find(_req)
|
|
1663
|
+
const publishedDocs = await self.find(_req)
|
|
1664
|
+
._ids(results.map(result => {
|
|
1665
|
+
return result._id.replace(':draft', ':published');
|
|
1666
|
+
})).project(query.get('project')).toArray();
|
|
1667
|
+
|
|
1662
1668
|
for (const doc of results) {
|
|
1663
1669
|
const publishedDoc = publishedDocs.find(publishedDoc => doc.aposDocId === publishedDoc.aposDocId);
|
|
1664
1670
|
doc._publishedDoc = publishedDoc;
|
|
@@ -2148,10 +2154,8 @@ module.exports = {
|
|
|
2148
2154
|
const count = await mongo.count();
|
|
2149
2155
|
if (query.get('perPage')) {
|
|
2150
2156
|
const perPage = query.get('perPage');
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
totalPages++;
|
|
2154
|
-
}
|
|
2157
|
+
const totalPages = Math.ceil(count / perPage);
|
|
2158
|
+
|
|
2155
2159
|
query.set('totalPages', totalPages);
|
|
2156
2160
|
}
|
|
2157
2161
|
return count;
|
|
@@ -10,6 +10,7 @@ const fs = require('fs');
|
|
|
10
10
|
const _ = require('lodash');
|
|
11
11
|
const { stripIndent } = require('common-tags');
|
|
12
12
|
const ExpressSessionCookie = require('express-session/session/cookie');
|
|
13
|
+
const path = require('path');
|
|
13
14
|
|
|
14
15
|
const apostropheI18nDebugPlugin = {
|
|
15
16
|
type: 'postProcessor',
|
|
@@ -380,21 +381,65 @@ module.exports = {
|
|
|
380
381
|
// Add the i18next resources provided by the specified module,
|
|
381
382
|
// merging with any existing phrases for the same locales and namespaces
|
|
382
383
|
addResourcesForModule(module) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
384
|
+
self.addDefaultResourcesForModule(module);
|
|
385
|
+
self.addNamespacedResourcesForModule(module);
|
|
386
|
+
},
|
|
387
|
+
// Automatically adds any localizations found in .json files in the main `i18n` subdirectory
|
|
388
|
+
// of a module.
|
|
389
|
+
//
|
|
390
|
+
// These are added to the `default` namespace, unless the legacy `i18n.ns` option is set
|
|
391
|
+
// for the module (not the preferred way, use namespace subdirectories in new projects).
|
|
392
|
+
addDefaultResourcesForModule(module) {
|
|
393
|
+
const ns = (module.options.i18n && module.options.i18n.ns) || 'default';
|
|
387
394
|
self.namespaces[ns] = self.namespaces[ns] || {};
|
|
388
|
-
self.namespaces[ns].browser = self.namespaces[ns].browser ||
|
|
395
|
+
self.namespaces[ns].browser = self.namespaces[ns].browser || (module.options.i18n && module.options.i18n.browser);
|
|
389
396
|
for (const entry of module.__meta.chain) {
|
|
390
|
-
const localizationsDir =
|
|
391
|
-
if (!
|
|
392
|
-
|
|
397
|
+
const localizationsDir = path.join(entry.dirname, 'i18n');
|
|
398
|
+
if (!self.defaultLocalizationsDirsAdded.has(localizationsDir)) {
|
|
399
|
+
self.defaultLocalizationsDirsAdded.add(localizationsDir);
|
|
400
|
+
if (!fs.existsSync(localizationsDir)) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
for (const localizationFile of fs.readdirSync(localizationsDir)) {
|
|
404
|
+
if (!localizationFile.endsWith('.json')) {
|
|
405
|
+
// Likely a namespace subdirectory
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const data = JSON.parse(fs.readFileSync(path.join(localizationsDir, localizationFile)));
|
|
409
|
+
const locale = localizationFile.replace('.json', '');
|
|
410
|
+
self.i18next.addResourceBundle(locale, ns, data, true, true);
|
|
411
|
+
}
|
|
393
412
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
// Automatically adds any localizations found in subdirectories of the main `i18n`
|
|
416
|
+
// subdirectory of a module. The subdirectory's name is treated as an i18n namespace
|
|
417
|
+
// name.
|
|
418
|
+
addNamespacedResourcesForModule(module) {
|
|
419
|
+
for (const entry of module.__meta.chain) {
|
|
420
|
+
const metadata = module.__meta.i18n[entry.name] || {};
|
|
421
|
+
const localizationsDir = `${entry.dirname}/i18n`;
|
|
422
|
+
if (!self.namespacedLocalizationsDirsAdded.has(localizationsDir)) {
|
|
423
|
+
self.namespacedLocalizationsDirsAdded.add(localizationsDir);
|
|
424
|
+
if (!fs.existsSync(localizationsDir)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
for (const ns of fs.readdirSync(localizationsDir)) {
|
|
428
|
+
if (ns.endsWith('.json')) {
|
|
429
|
+
// A JSON file for the default namespace, already handled
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
self.namespaces[ns] = self.namespaces[ns] || {};
|
|
433
|
+
self.namespaces[ns].browser = self.namespaces[ns].browser ||
|
|
434
|
+
(metadata[ns] && metadata[ns].browser);
|
|
435
|
+
const namespaceDir = path.join(localizationsDir, ns);
|
|
436
|
+
for (const localizationFile of fs.readdirSync(namespaceDir)) {
|
|
437
|
+
const fullLocalizationFile = path.join(namespaceDir, localizationFile);
|
|
438
|
+
const data = JSON.parse(fs.readFileSync(fullLocalizationFile));
|
|
439
|
+
const locale = localizationFile.replace('.json', '');
|
|
440
|
+
self.i18next.addResourceBundle(locale, ns, data, true, true);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
398
443
|
}
|
|
399
444
|
}
|
|
400
445
|
},
|
|
@@ -402,6 +447,8 @@ module.exports = {
|
|
|
402
447
|
// itself, called by init. Later modules call addResourcesForModule(self),
|
|
403
448
|
// making phrases available gradually as Apostrophe starts up
|
|
404
449
|
addInitialResources() {
|
|
450
|
+
self.defaultLocalizationsDirsAdded = new Set();
|
|
451
|
+
self.namespacedLocalizationsDirsAdded = new Set();
|
|
405
452
|
for (const module of Object.values(self.apos.modules)) {
|
|
406
453
|
self.addResourcesForModule(module);
|
|
407
454
|
}
|
|
@@ -37,13 +37,6 @@
|
|
|
37
37
|
//
|
|
38
38
|
// Apostrophe's instance of the [passport](https://npmjs.org/package/passport) npm module.
|
|
39
39
|
// You may access this object if you need to implement additional passport "strategies."
|
|
40
|
-
//
|
|
41
|
-
// ## callAll method: loginAfterLogin
|
|
42
|
-
//
|
|
43
|
-
// The method `loginAfterLogin` is invoked on **all modules that have one**. This method
|
|
44
|
-
// is a good place to set `req.redirect` to the URL of your choice. If no module sets
|
|
45
|
-
// `req.redirect`, the newly logged-in user is redirected to the home page. `loginAfterLogin`
|
|
46
|
-
// is invoked with `req` and may be an async function.
|
|
47
40
|
|
|
48
41
|
const Passport = require('passport').Passport;
|
|
49
42
|
const LocalStrategy = require('passport-local');
|
|
@@ -405,15 +398,35 @@ module.exports = {
|
|
|
405
398
|
before: '@apostrophecms/i18n',
|
|
406
399
|
middleware(req, res, next) {
|
|
407
400
|
const superLogin = req.login.bind(req);
|
|
408
|
-
req.login = (user,
|
|
409
|
-
|
|
401
|
+
req.login = (user, ...args) => {
|
|
402
|
+
let options, callback;
|
|
403
|
+
// Support inconsistent calling conventions inside passport core
|
|
404
|
+
if (typeof args[0] === 'function') {
|
|
405
|
+
options = {};
|
|
406
|
+
callback = args[0];
|
|
407
|
+
} else {
|
|
408
|
+
options = args[0];
|
|
409
|
+
callback = args[1];
|
|
410
|
+
}
|
|
411
|
+
return superLogin(user, options, async (err) => {
|
|
410
412
|
if (err) {
|
|
411
413
|
return callback(err);
|
|
412
414
|
}
|
|
413
|
-
|
|
415
|
+
await self.emit('afterSessionLogin', req);
|
|
416
|
+
// Make sure no handler removed req.user
|
|
417
|
+
if (req.user) {
|
|
418
|
+
// Mark the login timestamp. Middleware takes care of ensuring
|
|
419
|
+
// that logins cannot be used to carry out actions prior
|
|
420
|
+
// to this property being added
|
|
421
|
+
req.session.loginAt = Date.now();
|
|
422
|
+
}
|
|
414
423
|
return callback(null);
|
|
415
424
|
});
|
|
416
425
|
};
|
|
426
|
+
// Passport itself maintains this bc alias, while refusing
|
|
427
|
+
// to actually decide which one is best in its own dev docs.
|
|
428
|
+
// Both have to exist to avoid bugs when passport calls itself
|
|
429
|
+
req.logIn = req.login;
|
|
417
430
|
return next();
|
|
418
431
|
}
|
|
419
432
|
},
|
|
@@ -5,8 +5,9 @@ export default function() {
|
|
|
5
5
|
if (el) {
|
|
6
6
|
return new Vue({
|
|
7
7
|
el,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
render: function (h) {
|
|
9
|
+
return h('TheAposLogin');
|
|
10
|
+
}
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
13
|
apos.bus.$on('admin-menu-click', async (item) => {
|
|
@@ -27,11 +27,14 @@ export default function() {
|
|
|
27
27
|
return this.$refs.modals.execute(componentName, props);
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
render(h) {
|
|
31
|
+
return h(apos.modal.components.the, {
|
|
32
|
+
ref: 'modals',
|
|
33
|
+
props: {
|
|
34
|
+
modals: apos.modal.modals
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
35
38
|
});
|
|
36
39
|
apos.modal.execute = theAposModals.execute;
|
|
37
40
|
apos.confirm = theAposModals.confirm;
|
|
@@ -29,10 +29,20 @@ const _ = require('lodash');
|
|
|
29
29
|
|
|
30
30
|
module.exports = {
|
|
31
31
|
|
|
32
|
-
cascades: [ 'csrfExceptions' ],
|
|
33
|
-
|
|
34
32
|
init(self) {
|
|
35
33
|
self.apos = self.options.apos;
|
|
34
|
+
const capturedSections = [
|
|
35
|
+
'queries',
|
|
36
|
+
'extendQueries',
|
|
37
|
+
'icons'
|
|
38
|
+
];
|
|
39
|
+
for (const section of capturedSections) {
|
|
40
|
+
// Unparsed sections are now captured in __meta, promote
|
|
41
|
+
// these to the top level to maintain bc. For new unparsed
|
|
42
|
+
// sections we'll leave them in `__meta` to avoid bc breaks
|
|
43
|
+
// with project-level properties of the module
|
|
44
|
+
self[section] = self.__meta[section];
|
|
45
|
+
}
|
|
36
46
|
// all apostrophe modules are properties of self.apos.modules.
|
|
37
47
|
// Those with an alias are also properties of self.apos
|
|
38
48
|
self.apos.modules[self.__meta.name] = self;
|
|
@@ -1977,14 +1977,13 @@ database.`);
|
|
|
1977
1977
|
);
|
|
1978
1978
|
},
|
|
1979
1979
|
// Returns the effective base URL for the given request.
|
|
1980
|
-
// If Apostrophe's top-level `baseUrl` option is set,
|
|
1981
|
-
//
|
|
1980
|
+
// If Apostrophe's top-level `baseUrl` option is set, or a hostname is
|
|
1981
|
+
// defined for the active locale, then that is consulted, otherwise the base URL
|
|
1982
|
+
// is the empty string. This makes it easier to build absolute
|
|
1982
1983
|
// URLs (when `baseUrl` is configured), or to harmlessly prepend
|
|
1983
1984
|
// the empty string (when it is not configured). The
|
|
1984
1985
|
// Apostrophe queries used to fetch Apostrophe pages
|
|
1985
|
-
// consult this method
|
|
1986
|
-
// `@apostrophecms/workflow` module to create correct absolute URLs
|
|
1987
|
-
// for specific locales.
|
|
1986
|
+
// consult this method.
|
|
1988
1987
|
getBaseUrl(req) {
|
|
1989
1988
|
const hostname = self.apos.i18n.locales[req.locale].hostname;
|
|
1990
1989
|
if (hostname) {
|
|
@@ -63,7 +63,7 @@ module.exports = {
|
|
|
63
63
|
const doc = (docOrType && docOrType._id) ? docOrType : null;
|
|
64
64
|
const manager = type && self.apos.doc.getManager(type);
|
|
65
65
|
if (type && !manager) {
|
|
66
|
-
self.apos.util.warn(
|
|
66
|
+
self.apos.util.warn('A permission.can() call was made with a type that has no manager:', type);
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
if (action === 'view') {
|
|
@@ -566,7 +566,7 @@ module.exports = {
|
|
|
566
566
|
},
|
|
567
567
|
//
|
|
568
568
|
// Update a piece. Convenience wrapper for `apos.doc.insert`.
|
|
569
|
-
// Returns the piece. `
|
|
569
|
+
// Returns the piece. `beforeUpdate`, `beforeSave`, `afterUpdate`
|
|
570
570
|
// and `afterSave` async events are emitted by this module.
|
|
571
571
|
async update(req, piece, options) {
|
|
572
572
|
return self.apos.doc.update(req, piece, options);
|
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue
CHANGED
|
@@ -44,6 +44,7 @@ import StarterKit from '@tiptap/starter-kit';
|
|
|
44
44
|
import TextAlign from '@tiptap/extension-text-align';
|
|
45
45
|
import Highlight from '@tiptap/extension-highlight';
|
|
46
46
|
import TextStyle from '@tiptap/extension-text-style';
|
|
47
|
+
import Underline from '@tiptap/extension-underline';
|
|
47
48
|
export default {
|
|
48
49
|
name: 'AposRichTextWidgetEditor',
|
|
49
50
|
components: {
|
|
@@ -181,7 +182,8 @@ export default {
|
|
|
181
182
|
types: [ 'heading', 'paragraph' ]
|
|
182
183
|
}),
|
|
183
184
|
Highlight,
|
|
184
|
-
TextStyle
|
|
185
|
+
TextStyle,
|
|
186
|
+
Underline
|
|
185
187
|
].concat(this.aposTiptapExtensions)
|
|
186
188
|
});
|
|
187
189
|
},
|
|
@@ -18,7 +18,7 @@ const _ = require('lodash');
|
|
|
18
18
|
const dayjs = require('dayjs');
|
|
19
19
|
const tinycolor = require('tinycolor2');
|
|
20
20
|
const { klona } = require('klona');
|
|
21
|
-
const {
|
|
21
|
+
const { stripIndents } = require('common-tags');
|
|
22
22
|
|
|
23
23
|
module.exports = {
|
|
24
24
|
options: {
|
|
@@ -74,6 +74,22 @@ module.exports = {
|
|
|
74
74
|
return true;
|
|
75
75
|
}
|
|
76
76
|
return _.isEqual(one[field.name], two[field.name]);
|
|
77
|
+
},
|
|
78
|
+
validate: function (field, options, warn, fail) {
|
|
79
|
+
if (field.options && field.options.widgets) {
|
|
80
|
+
for (const name of Object.keys(field.options.widgets)) {
|
|
81
|
+
if (!self.apos.modules[`${name}-widget`]) {
|
|
82
|
+
if (name.match(/-widget$/)) {
|
|
83
|
+
warn(stripIndents`
|
|
84
|
+
Do not include "-widget" in the name when configuring a widget in an area field.
|
|
85
|
+
Apostrophe will automatically add "-widget" when looking for the right module.
|
|
86
|
+
`);
|
|
87
|
+
} else {
|
|
88
|
+
warn(`Nonexistent widget type name ${name} in area field.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
77
93
|
}
|
|
78
94
|
});
|
|
79
95
|
|
|
@@ -142,11 +158,14 @@ module.exports = {
|
|
|
142
158
|
// leading slash required). Otherwise, expect a object-style slug
|
|
143
159
|
// (no slashes at all)
|
|
144
160
|
convert: function (req, field, data, destination) {
|
|
145
|
-
const options = {
|
|
161
|
+
const options = {
|
|
162
|
+
def: field.def
|
|
163
|
+
};
|
|
146
164
|
if (field.page) {
|
|
147
165
|
options.allow = '/';
|
|
148
166
|
}
|
|
149
167
|
destination[field.name] = self.apos.util.slugify(self.apos.launder.string(data[field.name], field.def), options);
|
|
168
|
+
|
|
150
169
|
if (field.page) {
|
|
151
170
|
if (!(destination[field.name].charAt(0) === '/')) {
|
|
152
171
|
destination[field.name] = '/' + destination[field.name];
|
|
@@ -1217,12 +1236,14 @@ module.exports = {
|
|
|
1217
1236
|
|
|
1218
1237
|
// all fields in the schema will end up in this variable
|
|
1219
1238
|
let newSchema = [];
|
|
1239
|
+
|
|
1220
1240
|
// loop over any groups and orders we want to respect
|
|
1221
1241
|
_.each(groups, function (group) {
|
|
1222
1242
|
|
|
1223
1243
|
_.each(group.fields, function (field) {
|
|
1224
1244
|
// find the field we are ordering
|
|
1225
1245
|
let f = _.find(schema, { name: field });
|
|
1246
|
+
|
|
1226
1247
|
if (!f) {
|
|
1227
1248
|
// May have already been migrated due to subclasses re-grouping fields
|
|
1228
1249
|
f = _.find(newSchema, { name: field });
|
|
@@ -1242,6 +1263,7 @@ module.exports = {
|
|
|
1242
1263
|
if (fIndex !== -1) {
|
|
1243
1264
|
newSchema.splice(fIndex, 1);
|
|
1244
1265
|
}
|
|
1266
|
+
|
|
1245
1267
|
newSchema.push(f);
|
|
1246
1268
|
|
|
1247
1269
|
// remove the field from the old schema, if that is where we got it from
|
|
@@ -2241,7 +2263,7 @@ module.exports = {
|
|
|
2241
2263
|
self.apos.util.error(format(s));
|
|
2242
2264
|
}
|
|
2243
2265
|
function format(s) {
|
|
2244
|
-
return
|
|
2266
|
+
return stripIndents`
|
|
2245
2267
|
${options.type} ${options.subtype}, ${field.type} field "${field.name}":
|
|
2246
2268
|
|
|
2247
2269
|
${s}
|
|
@@ -39,11 +39,7 @@ export default {
|
|
|
39
39
|
mixins: [ AposInputMixin ],
|
|
40
40
|
data () {
|
|
41
41
|
return {
|
|
42
|
-
next: this.value.data ||
|
|
43
|
-
metaType: 'area',
|
|
44
|
-
_id: cuid(),
|
|
45
|
-
items: []
|
|
46
|
-
},
|
|
42
|
+
next: this.value.data || this.getEmptyValue(),
|
|
47
43
|
error: false,
|
|
48
44
|
// This is just meant to be sufficient to prevent unintended collisions
|
|
49
45
|
// in the UI between id attributes
|
|
@@ -66,6 +62,17 @@ export default {
|
|
|
66
62
|
}
|
|
67
63
|
},
|
|
68
64
|
methods: {
|
|
65
|
+
getEmptyValue() {
|
|
66
|
+
return {
|
|
67
|
+
metaType: 'area',
|
|
68
|
+
_id: cuid(),
|
|
69
|
+
items: []
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
watchValue () {
|
|
73
|
+
this.error = this.value.error;
|
|
74
|
+
this.next = this.value.data || this.getEmptyValue();
|
|
75
|
+
},
|
|
69
76
|
validate(value) {
|
|
70
77
|
if (this.field.required) {
|
|
71
78
|
if (!value.items.length) {
|
|
@@ -32,13 +32,17 @@ export default {
|
|
|
32
32
|
getChoiceId(uid, value) {
|
|
33
33
|
return uid + value.replace(/\s/g, '');
|
|
34
34
|
},
|
|
35
|
+
watchValue () {
|
|
36
|
+
this.error = this.value.error;
|
|
37
|
+
this.next = this.value.data || [];
|
|
38
|
+
},
|
|
35
39
|
validate(values) {
|
|
36
|
-
|
|
40
|
+
// The choices and values should always be arrays.
|
|
41
|
+
if (!Array.isArray(this.field.choices) || !Array.isArray(values)) {
|
|
37
42
|
return 'malformed';
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
if (this.field.required &&
|
|
41
|
-
!Array.isArray(values) && (!values || !values.length)) {
|
|
45
|
+
if (this.field.required && !values.length) {
|
|
42
46
|
return 'required';
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -23,6 +23,23 @@ export default {
|
|
|
23
23
|
debug: i18n.debug,
|
|
24
24
|
interpolation: {
|
|
25
25
|
escapeValue: false
|
|
26
|
+
},
|
|
27
|
+
appendNamespaceToMissingKey: true,
|
|
28
|
+
parseMissingKeyHandler (key) {
|
|
29
|
+
// We include namespaces with unrecognized l10n keys using
|
|
30
|
+
// `appendNamespaceToMissingKey: true`. This passes strings containing
|
|
31
|
+
// colons that were never meant to be localized through to the UI.
|
|
32
|
+
//
|
|
33
|
+
// Strings that do not include colons ("Content area") are given the
|
|
34
|
+
// default namespace by i18next ("translation," by default). Here we
|
|
35
|
+
// check if the key starts with that default namespace, meaning it
|
|
36
|
+
// belongs to no other registered namespace, then remove that default
|
|
37
|
+
// namespace before passing this through to be processed and displayed.
|
|
38
|
+
if (key.startsWith(`${this.defaultNS[0]}:`)) {
|
|
39
|
+
return key.slice(this.defaultNS[0].length + 1);
|
|
40
|
+
} else {
|
|
41
|
+
return key;
|
|
42
|
+
}
|
|
26
43
|
}
|
|
27
44
|
});
|
|
28
45
|
|
|
@@ -936,15 +936,6 @@ module.exports = {
|
|
|
936
936
|
});
|
|
937
937
|
},
|
|
938
938
|
|
|
939
|
-
// If propertyName is _id, then the keys in the returned
|
|
940
|
-
// object will be the ids of each object in arr,
|
|
941
|
-
// and the values will be the corresponding objects.
|
|
942
|
-
// You may index by any property name.
|
|
943
|
-
|
|
944
|
-
indexBy: function(arr, propertyName) {
|
|
945
|
-
return _.indexBy(arr, propertyName);
|
|
946
|
-
},
|
|
947
|
-
|
|
948
939
|
// Find all the array elements, if any, that have the specified value for
|
|
949
940
|
// the specified property.
|
|
950
941
|
|
|
@@ -1061,11 +1052,14 @@ module.exports = {
|
|
|
1061
1052
|
|
|
1062
1053
|
function groupByArray(items, arrayName) {
|
|
1063
1054
|
const results = {};
|
|
1055
|
+
// looping over each item in the original array
|
|
1064
1056
|
_.each(items, function(item) {
|
|
1057
|
+
// looping over each item in the array within the top level item
|
|
1065
1058
|
_.each(item[arrayName] || [], function(inner) {
|
|
1066
1059
|
if (!results[inner]) {
|
|
1067
1060
|
results[inner] = [];
|
|
1068
1061
|
}
|
|
1062
|
+
// grouping top level items on the sub properties
|
|
1069
1063
|
results[inner].push(item);
|
|
1070
1064
|
});
|
|
1071
1065
|
});
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
// you are debugging a change and need to test all of the different ways a widget has
|
|
113
113
|
// been used, or are wondering if you can safely remove one.
|
|
114
114
|
|
|
115
|
+
const { stripIndent } = require('common-tags');
|
|
115
116
|
const _ = require('lodash');
|
|
116
117
|
|
|
117
118
|
module.exports = {
|
|
@@ -310,12 +311,20 @@ module.exports = {
|
|
|
310
311
|
},
|
|
311
312
|
|
|
312
313
|
// override to add CSS classes to the outer wrapper div of the widget.
|
|
314
|
+
// TODO: Remove in the 4.x major version.
|
|
313
315
|
getWidgetWrapperClasses(widget) {
|
|
316
|
+
self.apos.util.warnDev(stripIndent`
|
|
317
|
+
The getWidgetWrapperClasses method is deprecated and will be removed in the next
|
|
318
|
+
major version. The method in 3.x simply returns an empty array.`);
|
|
314
319
|
return [];
|
|
315
320
|
},
|
|
316
321
|
|
|
317
322
|
// Override to add CSS classes to the div of the widget itself.
|
|
323
|
+
// TODO: Remove in the 4.x major version.
|
|
318
324
|
getWidgetClasses(widget) {
|
|
325
|
+
self.apos.util.warnDev(stripIndent`
|
|
326
|
+
The getWidgetClasses method is deprecated and will be removed in the next major
|
|
327
|
+
version. The method in 3.x simply returns an empty array.`);
|
|
319
328
|
return [];
|
|
320
329
|
}
|
|
321
330
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@apostrophecms/vue-color": "^2.8.2",
|
|
31
|
-
"@babel/core": "^7.
|
|
32
|
-
"@babel/preset-env": "^7.
|
|
31
|
+
"@babel/core": "^7.16.7",
|
|
32
|
+
"@babel/preset-env": "^7.16.7",
|
|
33
33
|
"@tiptap/extension-highlight": "^2.0.0-beta.13",
|
|
34
34
|
"@tiptap/extension-link": "^2.0.0-beta.17",
|
|
35
35
|
"@tiptap/extension-text-align": "^2.0.0-beta.17",
|
|
36
36
|
"@tiptap/extension-text-style": "^2.0.0-beta.13",
|
|
37
|
-
"@tiptap/extension-underline": "^2.0.0-beta.
|
|
38
|
-
"@tiptap/starter-kit": "^2.0.0-beta.
|
|
39
|
-
"@tiptap/vue-2": "^2.0.0-beta.
|
|
40
|
-
"autoprefixer": "^10.
|
|
37
|
+
"@tiptap/extension-underline": "^2.0.0-beta.22",
|
|
38
|
+
"@tiptap/starter-kit": "^2.0.0-beta.164",
|
|
39
|
+
"@tiptap/vue-2": "^2.0.0-beta.73",
|
|
40
|
+
"autoprefixer": "^10.4.1",
|
|
41
41
|
"babel-loader": "^8.2.2",
|
|
42
42
|
"bluebird": "^3.7.2",
|
|
43
43
|
"body-parser": "^1.18.2",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"he": "^0.5.0",
|
|
69
69
|
"html-to-text": "^5.1.1",
|
|
70
70
|
"i18next": "^20.3.2",
|
|
71
|
-
"i18next-http-middleware": "^3.1.
|
|
71
|
+
"i18next-http-middleware": "^3.1.5",
|
|
72
72
|
"import-fresh": "^3.3.0",
|
|
73
73
|
"is-wsl": "^2.2.0",
|
|
74
74
|
"jsdom": "^17.0.0",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"resolve": "^1.19.0",
|
|
96
96
|
"resolve-from": "^5.0.0",
|
|
97
97
|
"sanitize-html": "^2.0.0",
|
|
98
|
-
"sass": "^1.
|
|
98
|
+
"sass": "^1.45.2",
|
|
99
99
|
"sass-loader": "^10.1.1",
|
|
100
100
|
"server-destroy": "^1.0.1",
|
|
101
101
|
"sluggo": "^0.3.0",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"uploadfs": "^1.17.1",
|
|
111
111
|
"v-tooltip": "^2.0.3",
|
|
112
112
|
"vue": "^2.6.14",
|
|
113
|
-
"vue-click-outside-element": "^1.0.
|
|
113
|
+
"vue-click-outside-element": "^1.0.15",
|
|
114
114
|
"vue-loader": "^15.9.6",
|
|
115
115
|
"vue-material-design-icons": "~4.12.1",
|
|
116
116
|
"vue-style-loader": "^4.1.2",
|
package/test/moog.js
CHANGED
|
@@ -284,6 +284,54 @@ describe('moog', function() {
|
|
|
284
284
|
assert(myObject.fieldsGroups.basics.fields.includes('five'));
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
+
it('should order fields with the last option unless the order array overrides', async function() {
|
|
288
|
+
const moog = require('../lib/moog.js')({});
|
|
289
|
+
|
|
290
|
+
moog.define('unorderedObject', {
|
|
291
|
+
cascades: [ 'fields' ],
|
|
292
|
+
fields: {
|
|
293
|
+
add: {
|
|
294
|
+
first: { type: 'string' },
|
|
295
|
+
last: {
|
|
296
|
+
type: 'string',
|
|
297
|
+
last: true
|
|
298
|
+
},
|
|
299
|
+
second: { type: 'string' },
|
|
300
|
+
third: { type: 'string' }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
moog.define('orderedObject', {
|
|
306
|
+
cascades: [ 'fields' ],
|
|
307
|
+
fields: {
|
|
308
|
+
add: {
|
|
309
|
+
first: { type: 'string' },
|
|
310
|
+
last: {
|
|
311
|
+
type: 'string',
|
|
312
|
+
last: true
|
|
313
|
+
},
|
|
314
|
+
second: { type: 'string' },
|
|
315
|
+
third: { type: 'string' }
|
|
316
|
+
},
|
|
317
|
+
order: [ 'last', 'third', 'second', 'first' ]
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
const unordered = await moog.create('unorderedObject', {});
|
|
321
|
+
assert(unordered);
|
|
322
|
+
assert(Object.keys(unordered.fields)[0] === 'first');
|
|
323
|
+
assert(Object.keys(unordered.fields)[1] === 'second');
|
|
324
|
+
assert(Object.keys(unordered.fields)[2] === 'third');
|
|
325
|
+
assert(Object.keys(unordered.fields)[3] === 'last');
|
|
326
|
+
|
|
327
|
+
const ordered = await moog.create('orderedObject', {});
|
|
328
|
+
assert(ordered);
|
|
329
|
+
assert(Object.keys(ordered.fields)[0] === 'last');
|
|
330
|
+
assert(Object.keys(ordered.fields)[1] === 'third');
|
|
331
|
+
assert(Object.keys(ordered.fields)[2] === 'second');
|
|
332
|
+
assert(Object.keys(ordered.fields)[3] === 'first');
|
|
333
|
+
});
|
|
334
|
+
|
|
287
335
|
// ==================================================
|
|
288
336
|
// `redefine` AND `isDefined`
|
|
289
337
|
// ==================================================
|
package/test/static-i18n.js
CHANGED
|
@@ -33,9 +33,19 @@ describe('static i18n', function() {
|
|
|
33
33
|
'apos-fr': {
|
|
34
34
|
options: {
|
|
35
35
|
i18n: {
|
|
36
|
+
// Legacy technique must work
|
|
36
37
|
ns: 'apostrophe'
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
},
|
|
41
|
+
// A base class that contributes some namespaced phrases in the new style way (subdirs)
|
|
42
|
+
'base-type': {
|
|
43
|
+
instantiate: false
|
|
44
|
+
},
|
|
45
|
+
// Also contributes namespaced phrases in the new style way (subdirs)
|
|
46
|
+
// plus default locale phrases in the root i18n folder
|
|
47
|
+
subtype: {
|
|
48
|
+
extend: 'base-type'
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
});
|
|
@@ -60,4 +70,22 @@ describe('static i18n', function() {
|
|
|
60
70
|
assert.strictEqual(apos.task.getReq({ locale: 'fr' }).t('apostrophe:richTextAlignCenter'), 'Aligner Le Centre');
|
|
61
71
|
});
|
|
62
72
|
|
|
73
|
+
it('should fetch default locale phrases from main i18n dir with no i18n option necessary', function() {
|
|
74
|
+
assert.strictEqual(apos.task.getReq().t('defaultTestOne'), 'Default Test One');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should fetch custom locale phrases from corresponding subdir', function() {
|
|
78
|
+
assert.strictEqual(apos.task.getReq().t('custom:customTestTwo'), 'Custom Test Two From Base Type');
|
|
79
|
+
assert.strictEqual(apos.task.getReq().t('custom:customTestThree'), 'Custom Test Three From Subtype');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('last appearance in inheritance + configuration order wins', function() {
|
|
83
|
+
assert.strictEqual(apos.task.getReq().t('custom:customTestOne'), 'Custom Test One From Subtype');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should honor the browser: true flag in the i18n section of an index.js file', function() {
|
|
87
|
+
const browserData = apos.i18n.getBrowserData(apos.task.getReq());
|
|
88
|
+
assert.strictEqual(browserData.i18n.en.custom.customTestOne, 'Custom Test One From Subtype');
|
|
89
|
+
});
|
|
90
|
+
|
|
63
91
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('With Nested Module Subdirs', function() {
|
|
5
|
+
this.timeout(t.timeout);
|
|
6
|
+
|
|
7
|
+
let apos;
|
|
8
|
+
|
|
9
|
+
after(function () {
|
|
10
|
+
return t.destroy(apos);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/// ///
|
|
14
|
+
// EXISTENCE
|
|
15
|
+
/// ///
|
|
16
|
+
|
|
17
|
+
it('should initialize', async function() {
|
|
18
|
+
apos = await t.create({
|
|
19
|
+
root: module,
|
|
20
|
+
nestedModuleSubdirs: true,
|
|
21
|
+
modules: {
|
|
22
|
+
example1: {}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
assert(apos.modules.example1);
|
|
26
|
+
// With nestedModuleSubdirs switched on, the index.js should be found,
|
|
27
|
+
// and modules.js should be loaded
|
|
28
|
+
assert(apos.modules.example1.options.folderLevelOption);
|
|
29
|
+
assert(apos.modules.example1.initialized);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('Without Nested Module Subdirs', function() {
|
|
5
|
+
this.timeout(t.timeout);
|
|
6
|
+
|
|
7
|
+
let apos;
|
|
8
|
+
|
|
9
|
+
after(function () {
|
|
10
|
+
return t.destroy(apos);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/// ///
|
|
14
|
+
// EXISTENCE
|
|
15
|
+
/// ///
|
|
16
|
+
|
|
17
|
+
it('should initialize', async function() {
|
|
18
|
+
apos = await t.create({
|
|
19
|
+
root: module,
|
|
20
|
+
modules: {
|
|
21
|
+
example1: {}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
assert(apos.modules.example1);
|
|
25
|
+
// Should fail because we didn't turn on nestedModuleSubdirs,
|
|
26
|
+
// so the index.js was not found and modules.js was not loaded
|
|
27
|
+
assert(!apos.modules.example1.options.folderLevelOption);
|
|
28
|
+
assert(!apos.modules.example1.initialized);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
});
|
package/test-lib/util.js
CHANGED
|
@@ -43,12 +43,14 @@ async function create(options) {
|
|
|
43
43
|
};
|
|
44
44
|
// Automatically configure Express, but not if we're in a special
|
|
45
45
|
// environment where the default apostrophe modules don't initialize
|
|
46
|
+
// TODO: Remove __testDefaults references in 4.x major version or formalize
|
|
47
|
+
// intended usage with documentation.
|
|
46
48
|
if (!config.__testDefaults) {
|
|
47
49
|
config.modules = config.modules || {};
|
|
48
50
|
const express = config.modules['@apostrophecms/express'] || {};
|
|
49
51
|
express.options = express.options || {};
|
|
50
|
-
// Allow OS to choose open port
|
|
51
|
-
express.options.port = null;
|
|
52
|
+
// Allow OS to choose open port if not explicitly set.
|
|
53
|
+
express.options.port = express.options.port || null;
|
|
52
54
|
express.options.address = express.options.address || 'localhost';
|
|
53
55
|
express.options.session = express.options.session || {};
|
|
54
56
|
express.options.session.secret = express.options.session.secret || 'test';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
Please ensure your pull request follows these guidelines:
|
|
2
|
-
|
|
3
|
-
- [ ] Link to the related issue with requirements and/or clearly describe 1) what problem this solves and 2) the requirements to review it against.
|
|
4
|
-
- [ ] Update the `CHANGELOG.md` file with the added feature or fix. If there is not yet a heading for an upcoming release, add one following the established pattern.
|
|
5
|
-
- [ ] Keep the pull request minimal! Break large updates into separate pull requests for individual features/fixes whenever possible. Small requests get reviewed more quickly.
|
|
6
|
-
- [ ] All tests must pass, including the linters. Run `npm run lint` to test code linting independently.
|
|
7
|
-
|
|
8
|
-
Thanks for contributing!
|