apostrophe 3.9.0 → 3.10.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 +22 -0
- package/lib/moog.js +26 -1
- 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-type/index.js +12 -8
- 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/notification/ui/apos/apps/AposNotification.js +3 -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 +7 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +3 -3
- package/modules/@apostrophecms/widget-type/index.js +9 -0
- package/package.json +3 -3
- package/test/moog.js +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.10.0 - 2021-12-22
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* `slug` type fields can now have an empty string or `null` as their `def` value without the string `'none'` populating automatically.
|
|
8
|
+
* The `underline` feature works properly in tiptap toolbar configuration.
|
|
9
|
+
* Required checkbox fields now properly prevent editor submission when empty.
|
|
10
|
+
* 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.
|
|
11
|
+
|
|
12
|
+
### Adds
|
|
13
|
+
|
|
14
|
+
* 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.
|
|
15
|
+
|
|
16
|
+
### Changes
|
|
17
|
+
|
|
18
|
+
* Adds deprecation notes to the widget class methods `getWidgetWrapperClasses` and `getWidgetClasses` from A2.
|
|
19
|
+
* Adds a deprecation note to the `reorganize` query builder for the next major version.
|
|
20
|
+
* 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.
|
|
21
|
+
* Compatible with the `@apostrophecms/security-headers` module, which supports a strict `Content-Security-Policy`.
|
|
22
|
+
* Adds a deprecation note to the `addLateCriteria` query builder.
|
|
23
|
+
* Updates the `toCount` doc type query method to use Math.ceil rather than Math.floor plus an additional step.
|
|
24
|
+
|
|
3
25
|
## 3.9.0 - 2021-12-08
|
|
4
26
|
|
|
5
27
|
### Adds
|
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)) {
|
|
@@ -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
|
},
|
|
@@ -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;
|
|
@@ -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;
|
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
|
},
|
|
@@ -142,11 +142,14 @@ module.exports = {
|
|
|
142
142
|
// leading slash required). Otherwise, expect a object-style slug
|
|
143
143
|
// (no slashes at all)
|
|
144
144
|
convert: function (req, field, data, destination) {
|
|
145
|
-
const options = {
|
|
145
|
+
const options = {
|
|
146
|
+
def: field.def
|
|
147
|
+
};
|
|
146
148
|
if (field.page) {
|
|
147
149
|
options.allow = '/';
|
|
148
150
|
}
|
|
149
151
|
destination[field.name] = self.apos.util.slugify(self.apos.launder.string(data[field.name], field.def), options);
|
|
152
|
+
|
|
150
153
|
if (field.page) {
|
|
151
154
|
if (!(destination[field.name].charAt(0) === '/')) {
|
|
152
155
|
destination[field.name] = '/' + destination[field.name];
|
|
@@ -1217,12 +1220,14 @@ module.exports = {
|
|
|
1217
1220
|
|
|
1218
1221
|
// all fields in the schema will end up in this variable
|
|
1219
1222
|
let newSchema = [];
|
|
1223
|
+
|
|
1220
1224
|
// loop over any groups and orders we want to respect
|
|
1221
1225
|
_.each(groups, function (group) {
|
|
1222
1226
|
|
|
1223
1227
|
_.each(group.fields, function (field) {
|
|
1224
1228
|
// find the field we are ordering
|
|
1225
1229
|
let f = _.find(schema, { name: field });
|
|
1230
|
+
|
|
1226
1231
|
if (!f) {
|
|
1227
1232
|
// May have already been migrated due to subclasses re-grouping fields
|
|
1228
1233
|
f = _.find(newSchema, { name: field });
|
|
@@ -1242,6 +1247,7 @@ module.exports = {
|
|
|
1242
1247
|
if (fIndex !== -1) {
|
|
1243
1248
|
newSchema.splice(fIndex, 1);
|
|
1244
1249
|
}
|
|
1250
|
+
|
|
1245
1251
|
newSchema.push(f);
|
|
1246
1252
|
|
|
1247
1253
|
// remove the field from the old schema, if that is where we got it from
|
|
@@ -33,12 +33,12 @@ export default {
|
|
|
33
33
|
return uid + value.replace(/\s/g, '');
|
|
34
34
|
},
|
|
35
35
|
validate(values) {
|
|
36
|
-
|
|
36
|
+
// The choices and values should always be arrays.
|
|
37
|
+
if (!Array.isArray(this.field.choices) || !Array.isArray(values)) {
|
|
37
38
|
return 'malformed';
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
if (this.field.required &&
|
|
41
|
-
!Array.isArray(values) && (!values || !values.length)) {
|
|
41
|
+
if (this.field.required && !values.length) {
|
|
42
42
|
return 'required';
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -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.10.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
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.
|
|
37
|
+
"@tiptap/extension-underline": "^2.0.0-beta.22",
|
|
38
38
|
"@tiptap/starter-kit": "^2.0.0-beta.75",
|
|
39
39
|
"@tiptap/vue-2": "^2.0.0-beta.34",
|
|
40
40
|
"autoprefixer": "^10.2.4",
|
|
@@ -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": "
|
|
113
|
+
"vue-click-outside-element": "1.0.13",
|
|
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
|
// ==================================================
|