apostrophe 4.4.2 → 4.4.3

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.4.3 (2024-06-17)
4
+
5
+ ### Fixes
6
+
7
+ * Do not use schema `field.def` when calling `convert`. Applying defaults to new documents is the job of `newInstance()` and similar code.
8
+ If you wish a field to be mandatory use `required: true`.
9
+ * As a convenience, using `POST` for pieces and pages with `_newInstance: true` keeps any additional `req.body` properties in the API response.
10
+ This feature unofficially existed before, it is now supported.
11
+
3
12
  ## 4.4.2 (2024-06-14)
4
13
 
5
14
  ### Fixes
@@ -58,13 +67,19 @@ We will now end up with page B slug as `/peer/page` and not `/peer/peer/page` as
58
67
  * Detect shortcut conflicts when using multiple shortcuts.
59
68
  * Updating schema fields as read-only no longer reset the value when updating the document.
60
69
  * Fixes stylelint config file, uses config from our shared configuration, fixes all lint errors.
61
- * Removes `$nextTick` use to re render schema in `AposArrayEditor` because it was triggering weird vue error in production.
62
- Instead, makes the AposSchema for loop keys more unique using `modelValue.data._id`,
63
- if document changes it re-renders schema fields.
64
70
  * Fixes `TheAposCommandMenu` modals not computing shortcuts from the current opened modal.
65
71
  * Fixes select boxes of relationships, we can now check manually published relationships, and `AposSlatList` renders properly checked relationships.
66
72
  * Fixes issues in `AposInputArray` on production build to be able to add, remove and edit array items after `required` error.
67
73
  * Relationships browse button isn't disabled when max is reached.
74
+ * In media manager images checkboxes are disabled when max is reached.
75
+
76
+ ## 4.3.3 (2024-06-04)
77
+
78
+ ### Fixes
79
+
80
+ * Removes `$nextTick` use to re render schema in `AposArrayEditor` because it was triggering weird vue error in production.
81
+ Instead, makes the AposSchema for loop keys more unique using `modelValue.data._id`,
82
+ if document changes it re-renders schema fields.
68
83
  * In media manager image checkboxes are disabled when max is reached.
69
84
  * Fixes tiptap bubble menu jumping on Firefox when clicking on buttons. Also fixes the fact that
70
85
  double clicking on bubble menu out of buttons would prevent it from closing when unfocusing the rich text area.
@@ -319,7 +319,11 @@ module.exports = {
319
319
  // simply get a new page doc and return it
320
320
  const parentPage = await self.findForEditing(req, self.getIdCriteria(targetId))
321
321
  .permission('create', '@apostrophecms/any-page-type').toObject();
322
- const newChild = self.newChild(parentPage);
322
+ const { _newInstance, ...body } = req.body;
323
+ const newChild = {
324
+ ...self.newChild(parentPage),
325
+ ...body
326
+ };
323
327
  newChild._previewable = true;
324
328
  return newChild;
325
329
  }
@@ -350,7 +354,6 @@ module.exports = {
350
354
  page = self.newChild(parentPage);
351
355
  }
352
356
  await manager.convert(req, input, page, {
353
- onlyPresentFields: true,
354
357
  copyingId
355
358
  });
356
359
  await self.insert(req, targetPage._id, position, page, { lock: false });
@@ -301,7 +301,11 @@ module.exports = {
301
301
  async post(req) {
302
302
  await self.publicApiCheckAsync(req);
303
303
  if (req.body._newInstance) {
304
- const newInstance = self.newInstance();
304
+ const { _newInstance, ...body } = req.body;
305
+ const newInstance = {
306
+ ...self.newInstance(),
307
+ ...body
308
+ };
305
309
  newInstance._previewable = self.addUrlsViaModule && (await self.addUrlsViaModule.readyToAddUrlsToPieces(req, self.name));
306
310
  delete newInstance._url;
307
311
  return newInstance;
@@ -817,7 +821,6 @@ module.exports = {
817
821
  const piece = self.newInstance();
818
822
  const copyingId = self.apos.launder.id(input._copyingId);
819
823
  await self.convert(req, input, piece, {
820
- onlyPresentFields: true,
821
824
  copyingId
822
825
  });
823
826
  await self.emit('afterConvert', req, input, piece);
@@ -96,7 +96,7 @@ module.exports = (self) => {
96
96
  self.addFieldType({
97
97
  name: 'string',
98
98
  convert(req, field, data, destination) {
99
- destination[field.name] = self.apos.launder.string(data[field.name], field.def);
99
+ destination[field.name] = self.apos.launder.string(data[field.name]);
100
100
  destination[field.name] = checkStringLength(destination[field.name], field.min, field.max);
101
101
  // If field is required but empty (and client side didn't catch that)
102
102
  // This is new and until now if JS client side failed, then it would
@@ -165,7 +165,7 @@ module.exports = (self) => {
165
165
  convert (req, field, data, destination) {
166
166
  const options = self.getSlugFieldOptions(field, data);
167
167
 
168
- destination[field.name] = self.apos.util.slugify(self.apos.launder.string(data[field.name], field.def), options);
168
+ destination[field.name] = self.apos.util.slugify(self.apos.launder.string(data[field.name]), options);
169
169
 
170
170
  if (field.page) {
171
171
  if (!(destination[field.name].charAt(0) === '/')) {
@@ -205,7 +205,7 @@ module.exports = (self) => {
205
205
  self.addFieldType({
206
206
  name: 'boolean',
207
207
  convert: function (req, field, data, destination) {
208
- destination[field.name] = self.apos.launder.boolean(data[field.name], field.def);
208
+ destination[field.name] = self.apos.launder.boolean(data[field.name]);
209
209
  },
210
210
  isEmpty: function (field, value) {
211
211
  return !value && value !== false;
@@ -256,7 +256,7 @@ module.exports = (self) => {
256
256
  self.addFieldType({
257
257
  name: 'color',
258
258
  async convert(req, field, data, destination) {
259
- destination[field.name] = self.apos.launder.string(data[field.name], field.def);
259
+ destination[field.name] = self.apos.launder.string(data[field.name]);
260
260
 
261
261
  if (field.required && (_.isUndefined(destination[field.name]) || !destination[field.name].toString().length)) {
262
262
  throw self.apos.error('required');
@@ -366,7 +366,7 @@ module.exports = (self) => {
366
366
  dynamicChoices: true,
367
367
  async convert(req, field, data, destination) {
368
368
  const choices = await self.getChoices(req, field);
369
- destination[field.name] = self.apos.launder.select(data[field.name], choices, field.def);
369
+ destination[field.name] = self.apos.launder.select(data[field.name], choices);
370
370
  },
371
371
  index: function (value, field, texts) {
372
372
  const silent = field.silent === undefined ? true : field.silent;
@@ -438,7 +438,7 @@ module.exports = (self) => {
438
438
  name: 'integer',
439
439
  vueComponent: 'AposInputString',
440
440
  async convert(req, field, data, destination) {
441
- destination[field.name] = self.apos.launder.integer(data[field.name], field.def, field.min, field.max);
441
+ destination[field.name] = self.apos.launder.integer(data[field.name], undefined, field.min, field.max);
442
442
  if (field.required && ((data[field.name] == null) || !data[field.name].toString().length)) {
443
443
  throw self.apos.error('required');
444
444
  }
@@ -492,7 +492,7 @@ module.exports = (self) => {
492
492
  name: 'float',
493
493
  vueComponent: 'AposInputString',
494
494
  async convert(req, field, data, destination) {
495
- destination[field.name] = self.apos.launder.float(data[field.name], field.def, field.min, field.max);
495
+ destination[field.name] = self.apos.launder.float(data[field.name], undefined, field.min, field.max);
496
496
  if (field.required && (_.isUndefined(data[field.name]) || !data[field.name].toString().length)) {
497
497
  throw self.apos.error('required');
498
498
  }
@@ -563,7 +563,7 @@ module.exports = (self) => {
563
563
  name: 'url',
564
564
  vueComponent: 'AposInputString',
565
565
  async convert(req, field, data, destination) {
566
- destination[field.name] = self.apos.launder.url(data[field.name], field.def, true);
566
+ destination[field.name] = self.apos.launder.url(data[field.name], undefined, true);
567
567
 
568
568
  if (field.required && (data[field.name] == null || !data[field.name].toString().length)) {
569
569
  throw self.apos.error('required');
@@ -642,7 +642,7 @@ module.exports = (self) => {
642
642
  return;
643
643
  }
644
644
 
645
- destination[field.name] = self.apos.launder.date(newDateVal, field.def);
645
+ destination[field.name] = self.apos.launder.date(newDateVal);
646
646
  },
647
647
  validate: function (field, options, warn, fail) {
648
648
  if (field.max && !field.max.match(dateRegex)) {
@@ -698,7 +698,7 @@ module.exports = (self) => {
698
698
  name: 'time',
699
699
  vueComponent: 'AposInputString',
700
700
  async convert(req, field, data, destination) {
701
- destination[field.name] = self.apos.launder.time(data[field.name], field.def);
701
+ destination[field.name] = self.apos.launder.time(data[field.name]);
702
702
  }
703
703
  });
704
704
 
@@ -718,7 +718,7 @@ module.exports = (self) => {
718
718
  // This is the only field type that we never update unless
719
719
  // there is actually a new value — a blank password is not cool. -Tom
720
720
  if (data[field.name]) {
721
- destination[field.name] = self.apos.launder.string(data[field.name], field.def);
721
+ destination[field.name] = self.apos.launder.string(data[field.name]);
722
722
 
723
723
  destination[field.name] = checkStringLength(destination[field.name], field.min, field.max);
724
724
  }
@@ -733,7 +733,7 @@ module.exports = (self) => {
733
733
  name: 'range',
734
734
  vueComponent: 'AposInputRange',
735
735
  async convert(req, field, data, destination) {
736
- destination[field.name] = self.apos.launder.float(data[field.name], field.def, field.min, field.max);
736
+ destination[field.name] = self.apos.launder.float(data[field.name], undefined, field.min, field.max);
737
737
  if (field.required && (_.isUndefined(data[field.name]) || !data[field.name].toString().length)) {
738
738
  throw self.apos.error('required');
739
739
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "4.4.2",
3
+ "version": "4.4.3",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,5 +1,6 @@
1
1
  const t = require('../test-lib/test.js');
2
2
  const assert = require('assert');
3
+ const cuid = require('cuid');
3
4
 
4
5
  const areaConfig = {
5
6
  '@apostrophecms/image': {},
@@ -1618,4 +1619,105 @@ describe('Pages REST', function() {
1618
1619
  }
1619
1620
  });
1620
1621
 
1622
+ it('can insert a test-page with _newInstance and additional properties', async function() {
1623
+ const newInstance = await apos.http.post('/api/v1/@apostrophecms/page', {
1624
+ body: {
1625
+ _newInstance: true,
1626
+ slug: '/page-01',
1627
+ type: 'test-page',
1628
+ title: 'Page 01'
1629
+ },
1630
+ jar
1631
+ });
1632
+ const inserted = await apos.http.post('/api/v1/@apostrophecms/page', {
1633
+ body: {
1634
+ ...newInstance,
1635
+ body: {
1636
+ metaType: 'area',
1637
+ items: [
1638
+ {
1639
+ metaType: 'widget',
1640
+ type: '@apostrophecms/rich-text',
1641
+ id: cuid(),
1642
+ content: '<p>This is the product key product with relationship</p>'
1643
+ }
1644
+ ]
1645
+ }
1646
+ },
1647
+ jar
1648
+ });
1649
+
1650
+ const actual = {
1651
+ newInstance,
1652
+ inserted
1653
+ };
1654
+ const expected = {
1655
+ newInstance: {
1656
+ _previewable: true,
1657
+ orphan: false,
1658
+ slug: '/page-01',
1659
+ title: 'Page 01',
1660
+ type: 'test-page',
1661
+ visibility: 'public'
1662
+ },
1663
+ inserted: {
1664
+ _ancestors: inserted._ancestors,
1665
+ _create: true,
1666
+ _delete: true,
1667
+ _edit: true,
1668
+ _id: inserted._id,
1669
+ _publish: true,
1670
+ _url: '/page-01',
1671
+ aposDocId: inserted.aposDocId,
1672
+ aposLocale: 'en:published',
1673
+ aposMode: 'published',
1674
+ archived: false,
1675
+ body: {
1676
+ _docId: inserted.body._docId,
1677
+ _edit: true,
1678
+ _id: inserted.body._id,
1679
+ items: [
1680
+ {
1681
+ _docId: inserted.body.items.at(0)._docId,
1682
+ _edit: true,
1683
+ _id: inserted.body.items.at(0)._id,
1684
+ aposPlaceholder: false,
1685
+ content: '<p>This is the product key product with relationship</p>',
1686
+ imageIds: [],
1687
+ metaType: 'widget',
1688
+ permalinkIds: [],
1689
+ type: '@apostrophecms/rich-text'
1690
+ }
1691
+ ],
1692
+ metaType: 'area'
1693
+ },
1694
+ cacheInvalidatedAt: inserted.cacheInvalidatedAt,
1695
+ color: '',
1696
+ createdAt: inserted.createdAt,
1697
+ highSearchText: inserted.highSearchText,
1698
+ highSearchWords: inserted.highSearchWords,
1699
+ lastPublishedAt: inserted.lastPublishedAt,
1700
+ level: 1,
1701
+ lowSearchText: inserted.lowSearchText,
1702
+ metaType: 'doc',
1703
+ orphan: false,
1704
+ path: inserted.path,
1705
+ rank: inserted.rank,
1706
+ searchSummary: inserted.searchSummary,
1707
+ slug: '/page-01',
1708
+ title: 'Page 01',
1709
+ titleSortified: inserted.titleSortified,
1710
+ type: 'test-page',
1711
+ updatedAt: inserted.updatedAt,
1712
+ updatedBy: {
1713
+ _id: inserted.updatedBy._id,
1714
+ title: 'admin',
1715
+ username: 'admin'
1716
+ },
1717
+ visibility: 'public'
1718
+ }
1719
+ };
1720
+
1721
+ assert.deepEqual(actual, expected);
1722
+ });
1621
1723
  });
package/test/pieces.js CHANGED
@@ -964,6 +964,129 @@ describe('Pieces', function() {
964
964
  relatedProductId = response._id;
965
965
  });
966
966
 
967
+ it('can insert a product with _newInstance and additional properties', async function() {
968
+ const newInstance = await apos.http.post('/api/v1/product', {
969
+ body: {
970
+ _newInstance: true,
971
+ title: 'Product 01'
972
+ },
973
+ jar
974
+ });
975
+ const inserted = await apos.http.post('/api/v1/product', {
976
+ body: {
977
+ ...newInstance,
978
+ body: {
979
+ metaType: 'area',
980
+ items: [
981
+ {
982
+ metaType: 'widget',
983
+ type: '@apostrophecms/rich-text',
984
+ id: cuid(),
985
+ content: '<p>This is the product key product with relationship</p>'
986
+ }
987
+ ]
988
+ }
989
+ },
990
+ jar
991
+ });
992
+
993
+ const actual = {
994
+ newInstance,
995
+ inserted
996
+ };
997
+ const expected = {
998
+ newInstance: {
999
+ _articles: null,
1000
+ _previewable: true,
1001
+ archived: false,
1002
+ body: {
1003
+ _id: newInstance.body._id,
1004
+ items: [],
1005
+ metaType: 'area'
1006
+ },
1007
+ color: null,
1008
+ photo: null,
1009
+ relationshipsInArray: [],
1010
+ relationshipsInObject: {
1011
+ _articles: null
1012
+ },
1013
+ slug: '',
1014
+ title: 'Product 01',
1015
+ type: 'product',
1016
+ visibility: 'public'
1017
+ },
1018
+ inserted: {
1019
+ _articles: [],
1020
+ _create: true,
1021
+ _delete: true,
1022
+ _edit: true,
1023
+ _id: inserted._id,
1024
+ _parent: inserted._parent,
1025
+ _parentSlug: '/products',
1026
+ _parentUrl: '/products',
1027
+ _publish: true,
1028
+ _url: '/products/product-01',
1029
+ aposDocId: inserted.aposDocId,
1030
+ aposLocale: 'en:published',
1031
+ aposMode: 'published',
1032
+ archived: false,
1033
+ articlesFields: {},
1034
+ articlesIds: [],
1035
+ body: {
1036
+ _docId: inserted.body._docId,
1037
+ _edit: true,
1038
+ _id: inserted.body._id,
1039
+ items: [
1040
+ {
1041
+ _docId: inserted.body.items.at(0)._docId,
1042
+ _edit: true,
1043
+ _id: inserted.body.items.at(0)._id,
1044
+ aposPlaceholder: false,
1045
+ content: '<p>This is the product key product with relationship</p>',
1046
+ imageIds: [],
1047
+ metaType: 'widget',
1048
+ permalinkIds: [],
1049
+ type: '@apostrophecms/rich-text'
1050
+ }
1051
+ ],
1052
+ metaType: 'area'
1053
+ },
1054
+ cacheInvalidatedAt: inserted.cacheInvalidatedAt,
1055
+ color: null,
1056
+ createdAt: inserted.createdAt,
1057
+ highSearchText: inserted.highSearchText,
1058
+ highSearchWords: inserted.highSearchWords,
1059
+ lastPublishedAt: inserted.lastPublishedAt,
1060
+ lowSearchText: inserted.lowSearchText,
1061
+ metaType: 'doc',
1062
+ photo: null,
1063
+ relationshipsInArray: [],
1064
+ relationshipsInObject: {
1065
+ _articles: [],
1066
+ _id: inserted.relationshipsInObject._id,
1067
+ articlesFields: {},
1068
+ articlesIds: [],
1069
+ metaType: 'object',
1070
+ scopedObjectName: 'doc.product.relationshipsInObject'
1071
+ },
1072
+ searchSummary: inserted.searchSummary,
1073
+ slug: 'product-01',
1074
+ title: 'Product 01',
1075
+ titleSortified: inserted.titleSortified,
1076
+ type: 'product',
1077
+ updatedAt: inserted.updatedAt,
1078
+ updatedBy: {
1079
+ _id: inserted.updatedBy._id,
1080
+ title: 'admin',
1081
+ username: 'admin'
1082
+ },
1083
+ visibility: 'public'
1084
+ }
1085
+ };
1086
+
1087
+ assert.deepEqual(actual, expected);
1088
+ });
1089
+
967
1090
  it('can GET a product with relationships', async function() {
968
1091
  const response = await apos.http.get('/api/v1/product');
969
1092
  assert(response);
package/test/schemas.js CHANGED
@@ -1326,7 +1326,7 @@ describe('Schemas', function() {
1326
1326
  assert(result.name === input.name);
1327
1327
  assert(result.address === input.address);
1328
1328
  // default
1329
- assert(result.variety === simpleFields[2].choices[0].value);
1329
+ assert(result.variety === undefined);
1330
1330
  assert(result.slug === 'this-is-cool');
1331
1331
  });
1332
1332