apostrophe 3.42.0 → 3.43.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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.43.0 (2023-03-29)
4
+
5
+ ### Adds
6
+
7
+ * Add the possibility to override the default "Add Item" button label by setting the `itemLabel` option of an `array` field.
8
+ * Adds `touch` task for every piece type. This task invokes `update` on each piece, which will execute all of the same event handlers that normally execute when a piece of that type is updated. Example usage: `node app article:touch`.
9
+
10
+ ### Fixes
11
+
12
+ * Hide the suggestion help from the relationship input list when the user starts typing a search term.
13
+ * Hide the suggestion hint from the relationship input list when the user starts typing a search term except when there are no matches to display.
14
+ * Disable context menu for related items when their `relationship` field has no sub-[`fields`](https://v3.docs.apostrophecms.org/guide/relationships.html#providing-context-with-fields) configured.
15
+
3
16
  ## 3.42.0 (2023-03-16)
4
17
 
5
18
  ### Adds
@@ -31,17 +44,18 @@ current selection. Currently this works best with newly inserted documents.
31
44
  * Localized strings in the admin UI can now use `$t(key)` to localize a string inside
32
45
  an interpolated variable. This was accomplished by setting `skipOnVariables` to false
33
46
  for i18next, solely on the front end for admin UI purposes.
34
- * The syntax of the method defined for dynamic `choices` now accepts a module prefix to get the method from, and the `()` suffix.
47
+ * The syntax of the method defined for dynamic `choices` now accepts a module prefix to get the method from, and the `()` suffix.
35
48
  This has been done for consistency with the external conditions syntax shipped in the previous release. See the documentation for more information.
36
49
  * Added the `viewPermission` property of schema fields, and renamed `permission` to `editPermission` (with backwards
37
50
  compatibility) for clarity. You can now decide if a schema field requires permissions to be visible or editable.
38
51
  See the documentation for more information.
52
+ * Display the right environment label on login page. By default, based on `NODE_ENV`, overriden by `environmentLabel` option in `@apostrophecms/login` module. The environment variable `APOS_ENV_LABEL` will override this. Note that `NODE_ENV` should generally only be set to `development` (the default) or `production` as many Node.js modules opt into optimizations suitable for all deployed environments when it is set to `production`. This is why we offer the separate `APOS_ENV_LABEL` variable.
39
53
 
40
- ### Fixes
54
+ ### Fixes
41
55
 
42
56
  * Do not log unnecessary "required" errors for hidden fields.
43
57
  * Fixed a bug that prevented "Text Align" from working properly in the rich text editor in certain cases.
44
- * Fix typo in `@apostrophecms/doc-type` and `@apostrophecms/submitted-drafts` where we were using `canCreate` instead of `showCreate` to display the `Create New` button or showing the `Copy` button in `Manager` modals.
58
+ * Fix typo in `@apostrophecms/doc-type` and `@apostrophecms/submitted-drafts` where we were using `canCreate` instead of `showCreate` to display the `Create New` button or showing the `Copy` button in `Manager` modals.
45
59
  * Send external condition results in an object so that numbers are supported as returned values.
46
60
 
47
61
  ## 3.41.1 (2023-03-07)
@@ -52,7 +66,7 @@ No changes. Publishing to make sure 3.x is tagged `latest` in npm, rather than 2
52
66
 
53
67
  ### Adds
54
68
 
55
- * Handle external conditions to display fields according to the result of a module method, or multiple methods from different modules.
69
+ * Handle external conditions to display fields according to the result of a module method, or multiple methods from different modules.
56
70
  This can be useful for displaying fields according to the result of an external API or any business logic run on the server. See the documentation for more information.
57
71
 
58
72
  ### Fixes
@@ -5,6 +5,7 @@
5
5
  "addItem": "Add Item",
6
6
  "addRowAfter": "Add Row After",
7
7
  "addRowBefore": "Add Row Before",
8
+ "addType": "Add {{ type }}",
8
9
  "addWidgetType": "Add {{ label }}",
9
10
  "admin": "Admin",
10
11
  "affirmativeLabel": "Yes, continue.",
@@ -382,8 +382,9 @@ module.exports = {
382
382
  }
383
383
  }
384
384
  }
385
+
385
386
  return {
386
- env: process.env.NODE_ENV || 'development',
387
+ env: process.env.APOS_ENV_LABEL || self.options.environmentLabel || process.env.NODE_ENV || 'development',
387
388
  name: (process.env.npm_package_name && process.env.npm_package_name.replace(/-/g, ' ')) || 'Apostrophe',
388
389
  version: aposPackage.version || '3',
389
390
  requirementProps
@@ -110,7 +110,7 @@ export default {
110
110
  background: var(--a-danger);
111
111
  }
112
112
 
113
- &--success {
113
+ &--success, &--staging {
114
114
  background: var(--a-warning);
115
115
  }
116
116
  }
@@ -1206,6 +1206,53 @@ module.exports = {
1206
1206
 
1207
1207
  console.log(`Done unlocalizing module ${self.name}`);
1208
1208
  }
1209
+ },
1210
+
1211
+ touch: {
1212
+ usage: 'Invoke this task to touch (update without any change) all docs of this type.',
1213
+ async task(argv) {
1214
+ const req = self.apos.task.getAdminReq();
1215
+ let errCount = 0;
1216
+ let count = 0;
1217
+ let cursor;
1218
+ const criteria = self.options.autopublish
1219
+ ? { aposMode: 'draft' }
1220
+ : {};
1221
+
1222
+ try {
1223
+ // We have 30 minutes (by default) for each iteration.
1224
+ // https://www.mongodb.com/docs/manual/reference/method/cursor.noCursorTimeout/#session-idle-timeout-overrides-nocursortimeout
1225
+ cursor = (await self.find(req, criteria)
1226
+ .locale(null)
1227
+ .limit(0)
1228
+ .toMongo())
1229
+ .addCursorFlag('noCursorTimeout', true);
1230
+
1231
+ for await (const doc of cursor) {
1232
+ try {
1233
+ await self.update(req, doc);
1234
+ count++;
1235
+ } catch (e) {
1236
+ errCount++;
1237
+ self.apos.util.error(e);
1238
+ }
1239
+ }
1240
+ } catch (error) {
1241
+ self.apos.util.error(error);
1242
+ } finally {
1243
+ if (cursor) {
1244
+ await cursor.close();
1245
+ }
1246
+ }
1247
+ console.log(`Touched ${count} doc(s) with ${errCount} error(s)`);
1248
+
1249
+ // Return, useful for tests and internal API's
1250
+ // It's in effect only when invoked via apos.task.invoke().
1251
+ return {
1252
+ touched: count,
1253
+ errors: errCount
1254
+ };
1255
+ }
1209
1256
  }
1210
1257
  };
1211
1258
  }
@@ -52,7 +52,7 @@
52
52
  @input="setCheckedDocs"
53
53
  @item-clicked="editRelationship"
54
54
  :value="checkedDocs"
55
- :has-relationship-schema="!!(relationshipField && relationshipField.schema)"
55
+ :relationship-schema="relationshipField?.schema"
56
56
  />
57
57
  </div>
58
58
  </AposModalRail>
@@ -118,7 +118,7 @@
118
118
  </component>
119
119
  <AposButton
120
120
  type="primary"
121
- label="apostrophe:addItem"
121
+ :label="itemLabel"
122
122
  icon="plus-icon"
123
123
  :disabled="disableAdd()"
124
124
  :modifiers="['block']"
@@ -179,6 +179,14 @@ export default {
179
179
  handle: '.apos-drag-handle'
180
180
  };
181
181
  },
182
+ itemLabel() {
183
+ return this.field.itemLabel
184
+ ? {
185
+ key: 'apostrophe:addType',
186
+ type: this.$t(this.field.itemLabel)
187
+ }
188
+ : 'apostrophe:addItem';
189
+ },
182
190
  editLabel() {
183
191
  return {
184
192
  key: 'apostrophe:editType',
@@ -57,7 +57,7 @@
57
57
  :value="next"
58
58
  :duplicate="duplicate"
59
59
  :disabled="field.readOnly"
60
- :has-relationship-schema="!!field.schema"
60
+ :relationship-schema="field.schema"
61
61
  :editor-label="field.editorLabel"
62
62
  :editor-icon="field.editorIcon"
63
63
  />
@@ -241,19 +241,21 @@ export default {
241
241
  qs
242
242
  }
243
243
  );
244
- // filter items already selected
245
- const first = this.suggestion;
246
- const last = this.hint;
247
- this.searchList = [ first ]
248
- .concat((list.results || [])
249
- .filter(item => !this.next.map(i => i._id).includes(item._id))
250
- .map(item => ({
251
- ...item,
252
- disabled: this.disableUnpublished && !item.lastPublishedAt
253
- }))
254
- )
255
- .concat(last);
256
244
 
245
+ const removeSelectedItem = item => !this.next.map(i => i._id).includes(item._id);
246
+ const formatItems = item => ({
247
+ ...item,
248
+ disabled: this.disableUnpublished && !item.lastPublishedAt
249
+ });
250
+
251
+ const results = (list.results || [])
252
+ .filter(removeSelectedItem)
253
+ .map(formatItems);
254
+
255
+ const suggestion = !qs.autocomplete && this.suggestion;
256
+ const hint = (!qs.autocomplete || !results.length) && this.hint;
257
+
258
+ this.searchList = [ suggestion, ...results, hint ].filter(Boolean);
257
259
  this.searching = false;
258
260
  },
259
261
  async input () {
@@ -28,7 +28,7 @@
28
28
  :size="13"
29
29
  />
30
30
  <AposContextMenu
31
- v-if="hasRelationshipEditor && more.menu.length"
31
+ v-if="hasRelationshipFields && more.menu.length"
32
32
  :button="more.button"
33
33
  :menu="more.menu"
34
34
  @item-clicked="$emit('item-clicked', item)"
@@ -134,9 +134,9 @@ export default {
134
134
  type: Boolean,
135
135
  default: false
136
136
  },
137
- hasRelationshipSchema: {
138
- type: Boolean,
139
- default: false
137
+ relationshipSchema: {
138
+ type: Array,
139
+ default: () => null
140
140
  },
141
141
  editorLabel: {
142
142
  type: String,
@@ -178,9 +178,14 @@ export default {
178
178
  },
179
179
  hasRelationshipEditor() {
180
180
  if (this.item.attachment && this.item.attachment.group === 'images') {
181
- return this.hasRelationshipSchema && this.item.attachment._isCroppable;
181
+ return this.relationshipSchema && this.item.attachment._isCroppable;
182
182
  }
183
- return this.hasRelationshipSchema;
183
+ return this.relationshipSchema;
184
+ },
185
+ hasRelationshipFields() {
186
+ return this.hasRelationshipEditor &&
187
+ Array.isArray(this.relationshipSchema) &&
188
+ this.relationshipSchema.length;
184
189
  }
185
190
  },
186
191
  methods: {
@@ -31,7 +31,7 @@
31
31
  :parent="listId"
32
32
  :slat-count="next.length"
33
33
  :removable="removable"
34
- :has-relationship-schema="hasRelationshipSchema"
34
+ :relationship-schema="relationshipSchema"
35
35
  :editor-label="editorLabel"
36
36
  :editor-icon="editorIcon"
37
37
  />
@@ -66,9 +66,9 @@ export default {
66
66
  type: String,
67
67
  default: null
68
68
  },
69
- hasRelationshipSchema: {
70
- type: Boolean,
71
- default: false
69
+ relationshipSchema: {
70
+ type: Array,
71
+ default: () => null
72
72
  },
73
73
  editorLabel: {
74
74
  type: String,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.42.0",
3
+ "version": "3.43.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/login.js CHANGED
@@ -23,7 +23,8 @@ describe('Login', function() {
23
23
  },
24
24
  '@apostrophecms/login': {
25
25
  options: {
26
- passwordReset: true
26
+ passwordReset: true,
27
+ environmentLabel: 'test'
27
28
  }
28
29
  }
29
30
  }
@@ -43,6 +44,10 @@ describe('Login', function() {
43
44
  assert(apos.user.safe.remove);
44
45
  const response = await apos.user.safe.removeMany({});
45
46
  assert(response.result.ok === 1);
47
+
48
+ const loginModule = apos.modules['@apostrophecms/login'];
49
+ const context = await loginModule.getContext();
50
+ assert(context.env === 'test');
46
51
  });
47
52
 
48
53
  it('should be able to insert test user', async function() {
@@ -0,0 +1,94 @@
1
+ const assert = require('assert').strict;
2
+ const t = require('../test-lib/test.js');
3
+
4
+ describe('Pieces - tasks', function() {
5
+ this.timeout(t.timeout);
6
+ let apos;
7
+
8
+ before(async function () {
9
+ apos = await t.create({
10
+ root: module,
11
+ modules: {
12
+ article: {
13
+ extend: '@apostrophecms/piece-type',
14
+ options: {
15
+ alias: 'article',
16
+ name: 'article',
17
+ label: 'Article'
18
+ }
19
+ }
20
+ }
21
+ });
22
+ });
23
+
24
+ beforeEach(async function () {
25
+ await apos.doc.db.deleteMany({ type: 'article' });
26
+ });
27
+
28
+ after(function () {
29
+ return t.destroy(apos);
30
+ });
31
+
32
+ it('should generate pieces', async function () {
33
+ const before = await apos.doc.db.find({ type: 'article' }).count();
34
+ assert.equal(before, 0);
35
+ await apos.task.invoke('article:generate', {
36
+ total: 10
37
+ });
38
+ const after = await apos.doc.db.find({ type: 'article' }).count();
39
+ assert.equal(after, 20);
40
+ });
41
+
42
+ it('should touch pieces', async function () {
43
+ await apos.task.invoke('article:generate', {
44
+ total: 10
45
+ });
46
+ const docs = await apos.doc.db.find({ type: 'article' }).toArray();
47
+ assert.equal(docs.length, 20);
48
+
49
+ const result = await apos.task.invoke('article:touch');
50
+ assert.equal(result.touched, 20);
51
+ assert.equal(result.errors, 0);
52
+
53
+ const touched = await apos.doc.db.find({ type: 'article' }).toArray();
54
+ assert.equal(touched.length, 20);
55
+
56
+ for (const doc of touched) {
57
+ const old = docs.find(d => d._id === doc._id);
58
+ assert(old);
59
+ assert(old.updatedAt);
60
+ assert(doc.updatedAt);
61
+ assert.equal(
62
+ new Date(doc.updatedAt) > new Date(old.updatedAt),
63
+ true
64
+ );
65
+ }
66
+ });
67
+
68
+ it('should touch pieces with autopublish enabled', async function () {
69
+ apos.article.options.autopublish = true;
70
+ await apos.task.invoke('article:generate', {
71
+ total: 10
72
+ });
73
+ const docs = await apos.doc.db.find({ type: 'article' }).toArray();
74
+ assert.equal(docs.length, 30);
75
+
76
+ const result = await apos.task.invoke('article:touch');
77
+ assert.equal(result.touched, 10);
78
+ assert.equal(result.errors, 0);
79
+
80
+ const touched = await apos.doc.db.find({ type: 'article' }).toArray();
81
+ assert.equal(touched.length, 30);
82
+
83
+ for (const doc of touched) {
84
+ const old = docs.find(d => d._id === doc._id);
85
+ assert(old);
86
+ assert(old.updatedAt);
87
+ assert(doc.updatedAt);
88
+ assert.equal(
89
+ new Date(doc.updatedAt) > new Date(old.updatedAt),
90
+ true
91
+ );
92
+ }
93
+ });
94
+ });