apostrophe 3.27.0 → 3.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/modules/@apostrophecms/attachment/index.js +9 -6
- package/modules/@apostrophecms/attachment/lib/tasks/rescale.js +1 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +26 -16
- package/modules/@apostrophecms/module/index.js +1 -1
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +18 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +11 -0
- package/package.json +1 -1
- package/test/areas.js +38 -2
- package/test/schemaBuilders.js +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.28.1 (2022-09-15)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* `AposInputBoolean` can now be `required` and have the value `false`.
|
|
8
|
+
* Schema fields containing boolean filters can now list both `yes` and `no` choices according to available values in the database.
|
|
9
|
+
* Fix attachment `getHeight()` and `getWidth()` template helpers by changing the assignment of the `attachment._crop` property.
|
|
10
|
+
* Change assignment of `attachment._focalPoint` for consistency.
|
|
11
|
+
|
|
12
|
+
## 3.28.0 (2022-08-31)
|
|
13
|
+
|
|
14
|
+
### Fixes
|
|
15
|
+
|
|
16
|
+
* Fix UI bug when creating a document via a relationship.
|
|
17
|
+
|
|
18
|
+
### Adds
|
|
19
|
+
|
|
20
|
+
* Support for uploading `webp` files for display as images. This is supported by all current browsers now that Microsoft has removed IE11. For best results, you should run `npm update` on your project to make sure you are receiving the latest release of `uploadfs` which uses `sharp` for image processing. Thanks to [Isaac Preston](https://github.com/ixc7) for this addition.
|
|
21
|
+
* Clicking outside a modal now closes it, the same way the `Escape` key does when pressed.
|
|
22
|
+
* `checkboxes` fields now support `min` and `max` properties. Thanks to [Gabe Flores](https://github.com/gabeflores-appstem).
|
|
23
|
+
|
|
3
24
|
## 3.27.0 (2022-08-18)
|
|
4
25
|
|
|
5
26
|
### Adds
|
|
@@ -54,7 +54,8 @@ module.exports = {
|
|
|
54
54
|
'gif',
|
|
55
55
|
'jpg',
|
|
56
56
|
'png',
|
|
57
|
-
'svg'
|
|
57
|
+
'svg',
|
|
58
|
+
'webp'
|
|
58
59
|
],
|
|
59
60
|
extensionMaps: { jpeg: 'jpg' },
|
|
60
61
|
// uploadfs should treat this as an image and create scaled versions
|
|
@@ -90,14 +91,16 @@ module.exports = {
|
|
|
90
91
|
self.croppable = {
|
|
91
92
|
gif: true,
|
|
92
93
|
jpg: true,
|
|
93
|
-
png: true
|
|
94
|
+
png: true,
|
|
95
|
+
webp: true
|
|
94
96
|
};
|
|
95
97
|
|
|
96
98
|
// Do NOT add keys here unless they have the value `true`
|
|
97
99
|
self.sized = {
|
|
98
100
|
gif: true,
|
|
99
101
|
jpg: true,
|
|
100
|
-
png: true
|
|
102
|
+
png: true,
|
|
103
|
+
webp: true
|
|
101
104
|
};
|
|
102
105
|
|
|
103
106
|
self.sizeAvailableInArchive = self.options.sizeAvailableInArchive || 'one-sixth';
|
|
@@ -705,8 +708,8 @@ module.exports = {
|
|
|
705
708
|
if (ancestorFields) {
|
|
706
709
|
value = _.clone(value);
|
|
707
710
|
o.attachment = value;
|
|
708
|
-
value._crop = _.pick(ancestorFields, 'width', 'height', 'top', 'left');
|
|
709
|
-
value._focalPoint = _.pick(ancestorFields, 'x', 'y');
|
|
711
|
+
value._crop = ancestorFields.width ? _.pick(ancestorFields, 'width', 'height', 'top', 'left') : undefined;
|
|
712
|
+
value._focalPoint = (typeof ancestorFields.x === 'number') ? _.pick(ancestorFields, 'x', 'y') : undefined;
|
|
710
713
|
break;
|
|
711
714
|
}
|
|
712
715
|
}
|
|
@@ -1018,7 +1021,7 @@ module.exports = {
|
|
|
1018
1021
|
return;
|
|
1019
1022
|
}
|
|
1020
1023
|
let sizes;
|
|
1021
|
-
if (![ 'gif', 'jpg', 'png' ].includes(self.resolveExtension(attachment.extension))) {
|
|
1024
|
+
if (![ 'gif', 'jpg', 'png', 'webp' ].includes(self.resolveExtension(attachment.extension))) {
|
|
1022
1025
|
sizes = [ { name: 'original' } ];
|
|
1023
1026
|
} else {
|
|
1024
1027
|
sizes = self.imageSizes.concat([ { name: 'original' } ]);
|
|
@@ -12,7 +12,7 @@ module.exports = function(self) {
|
|
|
12
12
|
const total = await self.db.count();
|
|
13
13
|
let n = 0;
|
|
14
14
|
await self.each({}, argv.parallel || 1, async function(file) {
|
|
15
|
-
if (!_.includes([ 'jpg', 'png', 'gif' ], file.extension)) {
|
|
15
|
+
if (!_.includes([ 'jpg', 'png', 'gif', 'webp' ], file.extension)) {
|
|
16
16
|
n++;
|
|
17
17
|
console.log('Skipping a non-image attachment: ' + file.name + '.' + file.extension);
|
|
18
18
|
return;
|
|
@@ -727,6 +727,9 @@ export default {
|
|
|
727
727
|
window.localStorage.setItem(this.savePreferenceName, pref);
|
|
728
728
|
},
|
|
729
729
|
onContentChanged(e) {
|
|
730
|
+
if (this.original?._id !== e.doc._id) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
730
733
|
if (e.doc.type !== this.docType) {
|
|
731
734
|
this.docType = e.doc.type;
|
|
732
735
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<transition
|
|
3
3
|
:name="transitionType"
|
|
4
|
-
@enter="
|
|
5
|
-
@leave="
|
|
4
|
+
@enter="onEnter"
|
|
5
|
+
@leave="onLeave"
|
|
6
6
|
:duration="250"
|
|
7
7
|
>
|
|
8
8
|
<section
|
|
@@ -14,12 +14,18 @@
|
|
|
14
14
|
ref="modalEl"
|
|
15
15
|
>
|
|
16
16
|
<transition :name="transitionType">
|
|
17
|
-
<div
|
|
17
|
+
<div
|
|
18
|
+
@click="close"
|
|
19
|
+
v-if="modal.showModal"
|
|
20
|
+
class="apos-modal__overlay"
|
|
21
|
+
/>
|
|
18
22
|
</transition>
|
|
19
23
|
<transition :name="transitionType" @after-leave="$emit('inactive')">
|
|
20
24
|
<div
|
|
21
|
-
v-if="modal.showModal"
|
|
22
|
-
class="
|
|
25
|
+
v-if="modal.showModal"
|
|
26
|
+
:class="innerClasses"
|
|
27
|
+
class="apos-modal__inner"
|
|
28
|
+
data-apos-modal-inner
|
|
23
29
|
>
|
|
24
30
|
<template v-if="modal.busy">
|
|
25
31
|
<div class="apos-modal__busy">
|
|
@@ -180,16 +186,13 @@ export default {
|
|
|
180
186
|
}
|
|
181
187
|
},
|
|
182
188
|
methods: {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (e.keyCode === 27) {
|
|
188
|
-
e.stopPropagation();
|
|
189
|
-
this.$emit('esc');
|
|
189
|
+
onKeydown (e) {
|
|
190
|
+
const hasPressedEsc = e.keyCode === 27;
|
|
191
|
+
if (hasPressedEsc) {
|
|
192
|
+
this.close(e);
|
|
190
193
|
}
|
|
191
194
|
},
|
|
192
|
-
|
|
195
|
+
onEnter () {
|
|
193
196
|
this.$emit('show-modal');
|
|
194
197
|
this.bindEventListeners();
|
|
195
198
|
apos.modal.stack = apos.modal.stack || [];
|
|
@@ -198,7 +201,7 @@ export default {
|
|
|
198
201
|
this.$emit('ready');
|
|
199
202
|
});
|
|
200
203
|
},
|
|
201
|
-
|
|
204
|
+
onLeave () {
|
|
202
205
|
this.removeEventListeners();
|
|
203
206
|
this.$emit('no-modal');
|
|
204
207
|
// pop doesn't quite suffice because of race conditions when
|
|
@@ -206,10 +209,17 @@ export default {
|
|
|
206
209
|
apos.modal.stack = apos.modal.stack.filter(modal => modal !== this);
|
|
207
210
|
},
|
|
208
211
|
bindEventListeners () {
|
|
209
|
-
window.addEventListener('keydown', this.
|
|
212
|
+
window.addEventListener('keydown', this.onKeydown);
|
|
210
213
|
},
|
|
211
214
|
removeEventListeners () {
|
|
212
|
-
window.removeEventListener('keydown', this.
|
|
215
|
+
window.removeEventListener('keydown', this.onKeydown);
|
|
216
|
+
},
|
|
217
|
+
close (e) {
|
|
218
|
+
if (apos.modal.stack[apos.modal.stack.length - 1] !== this) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
e.stopPropagation();
|
|
222
|
+
this.$emit('esc');
|
|
213
223
|
},
|
|
214
224
|
trapFocus () {
|
|
215
225
|
// Adapted from https://uxdesign.cc/how-to-trap-focus-inside-modal-to-make-it-ada-compliant-6a50f9a70700
|
|
@@ -165,7 +165,7 @@ module.exports = (self) => {
|
|
|
165
165
|
destination[field.name] = self.apos.launder.boolean(data[field.name], field.def);
|
|
166
166
|
},
|
|
167
167
|
isEmpty: function (field, value) {
|
|
168
|
-
return !value;
|
|
168
|
+
return !value && value !== false;
|
|
169
169
|
},
|
|
170
170
|
exporters: {
|
|
171
171
|
string: function (req, field, object, output) {
|
|
@@ -190,7 +190,7 @@ module.exports = (self) => {
|
|
|
190
190
|
return self.apos.launder.booleanOrNull(b);
|
|
191
191
|
},
|
|
192
192
|
choices: async function () {
|
|
193
|
-
const values = query.toDistinct(field.name);
|
|
193
|
+
const values = await query.toDistinct(field.name);
|
|
194
194
|
const choices = [];
|
|
195
195
|
if (_.includes(values, true)) {
|
|
196
196
|
choices.push({
|
|
@@ -198,7 +198,7 @@ module.exports = (self) => {
|
|
|
198
198
|
label: 'apostrophe:yes'
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
|
-
if (_.includes(values,
|
|
201
|
+
if (_.includes(values, false)) {
|
|
202
202
|
choices.push({
|
|
203
203
|
value: '0',
|
|
204
204
|
label: 'apostrophe:no'
|
|
@@ -254,6 +254,13 @@ module.exports = (self) => {
|
|
|
254
254
|
});
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
+
|
|
258
|
+
if ((field.min !== undefined) && (destination[field.name].length < field.min)) {
|
|
259
|
+
throw self.apos.error('min');
|
|
260
|
+
}
|
|
261
|
+
if ((field.max !== undefined) && (destination[field.name].length > field.max)) {
|
|
262
|
+
throw self.apos.error('max');
|
|
263
|
+
}
|
|
257
264
|
},
|
|
258
265
|
index: function (value, field, texts) {
|
|
259
266
|
const silent = field.silent === undefined ? true : field.silent;
|
|
@@ -300,6 +307,14 @@ module.exports = (self) => {
|
|
|
300
307
|
return choices;
|
|
301
308
|
}
|
|
302
309
|
});
|
|
310
|
+
},
|
|
311
|
+
validate: function (field, options, warn, fail) {
|
|
312
|
+
if (field.max && typeof field.max !== 'number') {
|
|
313
|
+
fail('Property "max" must be a number');
|
|
314
|
+
}
|
|
315
|
+
if (field.min && typeof field.min !== 'number') {
|
|
316
|
+
fail('Property "min" must be a number');
|
|
317
|
+
}
|
|
303
318
|
}
|
|
304
319
|
});
|
|
305
320
|
|
|
@@ -47,6 +47,17 @@ export default {
|
|
|
47
47
|
return 'required';
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
if (this.field.min) {
|
|
51
|
+
if ((values != null) && (values.length < this.field.min)) {
|
|
52
|
+
return 'min';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (this.field.max) {
|
|
56
|
+
if ((values != null) && (values.length > this.field.max)) {
|
|
57
|
+
return 'max';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
if (Array.isArray(values)) {
|
|
51
62
|
values.forEach(chosen => {
|
|
52
63
|
if (!this.choices.map(choice => {
|
package/package.json
CHANGED
package/test/areas.js
CHANGED
|
@@ -336,11 +336,29 @@ describe('Areas', function() {
|
|
|
336
336
|
}, true)
|
|
337
337
|
);
|
|
338
338
|
assert(
|
|
339
|
-
apos.schema.fieldTypes.boolean.isEmpty({
|
|
339
|
+
!apos.schema.fieldTypes.boolean.isEmpty({
|
|
340
340
|
type: 'boolean',
|
|
341
341
|
name: 'test'
|
|
342
342
|
}, false)
|
|
343
343
|
);
|
|
344
|
+
assert(
|
|
345
|
+
apos.schema.fieldTypes.boolean.isEmpty({
|
|
346
|
+
type: 'boolean',
|
|
347
|
+
name: 'test'
|
|
348
|
+
}, null)
|
|
349
|
+
);
|
|
350
|
+
assert(
|
|
351
|
+
apos.schema.fieldTypes.boolean.isEmpty({
|
|
352
|
+
type: 'boolean',
|
|
353
|
+
name: 'test'
|
|
354
|
+
}, undefined)
|
|
355
|
+
);
|
|
356
|
+
assert(
|
|
357
|
+
apos.schema.fieldTypes.boolean.isEmpty({
|
|
358
|
+
type: 'boolean',
|
|
359
|
+
name: 'test'
|
|
360
|
+
}, 0)
|
|
361
|
+
);
|
|
344
362
|
assert(
|
|
345
363
|
!apos.schema.fieldTypes.boolean.empty({
|
|
346
364
|
type: 'boolean',
|
|
@@ -348,11 +366,29 @@ describe('Areas', function() {
|
|
|
348
366
|
}, true)
|
|
349
367
|
);
|
|
350
368
|
assert(
|
|
351
|
-
apos.schema.fieldTypes.boolean.empty({
|
|
369
|
+
!apos.schema.fieldTypes.boolean.empty({
|
|
352
370
|
type: 'boolean',
|
|
353
371
|
name: 'test'
|
|
354
372
|
}, false)
|
|
355
373
|
);
|
|
374
|
+
assert(
|
|
375
|
+
apos.schema.fieldTypes.boolean.empty({
|
|
376
|
+
type: 'boolean',
|
|
377
|
+
name: 'test'
|
|
378
|
+
}, null)
|
|
379
|
+
);
|
|
380
|
+
assert(
|
|
381
|
+
apos.schema.fieldTypes.boolean.empty({
|
|
382
|
+
type: 'boolean',
|
|
383
|
+
name: 'test'
|
|
384
|
+
}, undefined)
|
|
385
|
+
);
|
|
386
|
+
assert(
|
|
387
|
+
apos.schema.fieldTypes.boolean.empty({
|
|
388
|
+
type: 'boolean',
|
|
389
|
+
name: 'test'
|
|
390
|
+
}, 0)
|
|
391
|
+
);
|
|
356
392
|
});
|
|
357
393
|
});
|
|
358
394
|
|
package/test/schemaBuilders.js
CHANGED
|
@@ -73,6 +73,11 @@ describe('Schema builders', function() {
|
|
|
73
73
|
idsStorage: 'favoriteIds',
|
|
74
74
|
label: 'Favorites',
|
|
75
75
|
withType: 'cat'
|
|
76
|
+
},
|
|
77
|
+
isACatPerson: {
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
label: 'Is a cat person?',
|
|
80
|
+
required: true
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
}
|
|
@@ -102,9 +107,11 @@ describe('Schema builders', function() {
|
|
|
102
107
|
}
|
|
103
108
|
for (i = 0; (i < people.length); i++) {
|
|
104
109
|
const person = people[i];
|
|
110
|
+
person.isACatPerson = true;
|
|
105
111
|
// person 10 has no favorite cat
|
|
106
112
|
if (i < 10) {
|
|
107
113
|
person.favoriteIds = [ cats[i].aposDocId ];
|
|
114
|
+
person.isACatPerson = false;
|
|
108
115
|
}
|
|
109
116
|
person.catsIds = [];
|
|
110
117
|
let j;
|
|
@@ -299,6 +306,23 @@ describe('Schema builders', function() {
|
|
|
299
306
|
assert(cats[0].label);
|
|
300
307
|
assert(cats[0].slug);
|
|
301
308
|
});
|
|
309
|
+
it('can obtain choices for isACatPerson', async function() {
|
|
310
|
+
const req = apos.task.getReq();
|
|
311
|
+
const query = apos.people.find(req);
|
|
312
|
+
const isACatPerson = await query.toChoices('isACatPerson');
|
|
313
|
+
const actual = isACatPerson;
|
|
314
|
+
const expected = [
|
|
315
|
+
{
|
|
316
|
+
value: '1',
|
|
317
|
+
label: 'apostrophe:yes'
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
value: '0',
|
|
321
|
+
label: 'apostrophe:no'
|
|
322
|
+
}
|
|
323
|
+
];
|
|
324
|
+
assert.deepEqual(actual, expected);
|
|
325
|
+
});
|
|
302
326
|
|
|
303
327
|
it('builder for favorite (by slug) exists', function() {
|
|
304
328
|
const req = apos.task.getReq();
|