apostrophe 3.41.1 → 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.
Files changed (48) hide show
  1. package/CHANGELOG.md +60 -2
  2. package/modules/@apostrophecms/admin-bar/index.js +1 -0
  3. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -1
  4. package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
  5. package/modules/@apostrophecms/doc/ui/apos/apps/AposDoc.js +42 -0
  6. package/modules/@apostrophecms/doc-type/index.js +82 -51
  7. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +10 -3
  8. package/modules/@apostrophecms/file/index.js +2 -1
  9. package/modules/@apostrophecms/file-tag/index.js +2 -1
  10. package/modules/@apostrophecms/i18n/i18n/en.json +116 -109
  11. package/modules/@apostrophecms/i18n/i18n/es.json +83 -78
  12. package/modules/@apostrophecms/i18n/i18n/fr.json +89 -84
  13. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +81 -76
  14. package/modules/@apostrophecms/i18n/i18n/sk.json +91 -86
  15. package/modules/@apostrophecms/image/index.js +5 -1
  16. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -1
  17. package/modules/@apostrophecms/image-tag/index.js +2 -1
  18. package/modules/@apostrophecms/login/index.js +2 -1
  19. package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +1 -1
  20. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +35 -2
  21. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +2 -2
  22. package/modules/@apostrophecms/page/index.js +8 -4
  23. package/modules/@apostrophecms/piece-type/index.js +71 -3
  24. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +4 -64
  25. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +9 -2
  26. package/modules/@apostrophecms/piece-type/ui/apos/components/AposUtilityOperations.vue +126 -0
  27. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +10 -10
  28. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +44 -48
  29. package/modules/@apostrophecms/schema/index.js +105 -35
  30. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +21 -2
  31. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +348 -113
  32. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +76 -22
  33. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +6 -0
  34. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +1 -1
  35. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +7 -1
  36. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +18 -4
  37. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +111 -30
  38. package/modules/@apostrophecms/submitted-draft/index.js +1 -1
  39. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +11 -6
  40. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +9 -5
  41. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +1 -0
  42. package/modules/@apostrophecms/user/index.js +2 -1
  43. package/modules/@apostrophecms/widget-type/index.js +6 -3
  44. package/package.json +15 -15
  45. package/test/login.js +6 -1
  46. package/test/pieces-tasks.js +94 -0
  47. package/test/pieces.js +726 -13
  48. package/test/schemas.js +392 -22
package/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
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
+
16
+ ## 3.42.0 (2023-03-16)
17
+
18
+ ### Adds
19
+
20
+ * You can now set `style: table` on inline arrays. It will display the array as a regular HTML table instead of an accordion.
21
+ See the [array field documentation](https://v3.docs.apostrophecms.org/reference/field-types/array.html#settings) for more information.
22
+ * You can now set `draggable: false` on inline arrays. It will disable the drag and drop feature. Useful when the order is not significant.
23
+ See the [array field documentation](https://v3.docs.apostrophecms.org/reference/field-types/array.html#settings) for more information.
24
+ * You can now set the label and icon to display on inline arrays when they are empty.
25
+ See the [array field documentation](https://v3.docs.apostrophecms.org/reference/field-types/array.html#whenEmpty) for more information.
26
+ * We have added a new and improved suggestion UI to relationship fields.
27
+ * The `utilityOperations` feature of piece types now supports additional properties:
28
+ `relationship: true` (show the operation only when editing a relationship), `relationship: false` (never show
29
+ the operation when editing a relationship), `button: true`, `icon` and `iconOnly: true`.
30
+ When `button: true` is specified, the operation appears as a standalone button rather than
31
+ being tucked away in the "more" menu.
32
+ * In addition, `utilityOperations` can now specify `eventOptions` with an `event` subproperty
33
+ instead of `modalOptions`. This is useful with the new `edit` event (see below).
34
+ * Those extending our admin UI on the front end can now open a modal to create or edit a page or piece by calling
35
+ `await apos.doc.edit({ type: 'article' })` (the type here is an example). To edit an existing document add an
36
+ `_id` property. To copy an existing document (like our "duplicate" feature) add a `copyOf`
37
+ property. When creating new pages, `type` can be sent to `@apostrophecms/page` for convenience
38
+ (note that the `type` property does not override the default or current page type in the editor).
39
+ * The `edit` Apostrophe event is now available and takes an object with the same properties
40
+ as above. This is useful when configuring `utilityOperations`.
41
+ * The `content-changed` Apostrophe event can now be emitted with a `select: true` property. If a
42
+ document manager for the relevant content type is open, it will attempt to add the document to the
43
+ current selection. Currently this works best with newly inserted documents.
44
+ * Localized strings in the admin UI can now use `$t(key)` to localize a string inside
45
+ an interpolated variable. This was accomplished by setting `skipOnVariables` to false
46
+ for i18next, solely on the front end for admin UI purposes.
47
+ * The syntax of the method defined for dynamic `choices` now accepts a module prefix to get the method from, and the `()` suffix.
48
+ This has been done for consistency with the external conditions syntax shipped in the previous release. See the documentation for more information.
49
+ * Added the `viewPermission` property of schema fields, and renamed `permission` to `editPermission` (with backwards
50
+ compatibility) for clarity. You can now decide if a schema field requires permissions to be visible or editable.
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.
53
+
54
+ ### Fixes
55
+
56
+ * Do not log unnecessary "required" errors for hidden fields.
57
+ * Fixed a bug that prevented "Text Align" from working properly in the rich text editor in certain cases.
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.
59
+ * Send external condition results in an object so that numbers are supported as returned values.
60
+
3
61
  ## 3.41.1 (2023-03-07)
4
62
 
5
63
  No changes. Publishing to make sure 3.x is tagged `latest` in npm, rather than 2.x.
@@ -8,8 +66,8 @@ No changes. Publishing to make sure 3.x is tagged `latest` in npm, rather than 2
8
66
 
9
67
  ### Adds
10
68
 
11
- * Handle external conditions to display fields according to the result of a module method, or multiple methods from different modules.
12
- This can be useful for displaying fields according to the result of an external API or any business logic run on the server.
69
+ * Handle external conditions to display fields according to the result of a module method, or multiple methods from different modules.
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.
13
71
 
14
72
  ### Fixes
15
73
 
@@ -338,6 +338,7 @@ module.exports = {
338
338
  submitted: context.submitted,
339
339
  lastPublishedAt: context.lastPublishedAt,
340
340
  _edit: context._edit,
341
+ _publish: context._publish,
341
342
  aposMode: context.aposMode,
342
343
  aposLocale: context.aposLocale,
343
344
  aposDocId: context.aposDocId
@@ -98,7 +98,7 @@ export default {
98
98
  return !!this.patchesSinceSave.length;
99
99
  },
100
100
  canPublish() {
101
- return apos.modules[this.context.type].canPublish;
101
+ return this.context._publish || apos.modules[this.context.type].canPublish;
102
102
  },
103
103
  readyToPublish() {
104
104
  if (this.canPublish) {
@@ -15,6 +15,7 @@ module.exports = {
15
15
  'arrow-left-icon': 'ArrowLeft',
16
16
  'arrow-right-icon': 'ArrowRight',
17
17
  'arrow-up-icon': 'ArrowUp',
18
+ 'binoculars-icon': 'Binoculars',
18
19
  'calendar-icon': 'Calendar',
19
20
  'check-all-icon': 'CheckAll',
20
21
  'check-bold-icon': 'CheckBold',
@@ -101,6 +102,8 @@ module.exports = {
101
102
  'redo-icon': 'RedoVariant',
102
103
  'refresh-icon': 'Refresh',
103
104
  'sign-text-icon': 'SignText',
105
+ 'tag-icon': 'Tag',
106
+ 'text-box-icon': 'TextBox',
104
107
  'text-box-remove-icon': 'TextBoxRemove',
105
108
  'trash-can-icon': 'TrashCan',
106
109
  'trash-can-outline-icon': 'TrashCanOutline',
@@ -0,0 +1,42 @@
1
+ // High-level conveniences for working with documents in Apostrophe
2
+ // on the front end, so that developers need only know the document
3
+ // type they want and do not have to look up modal names in module
4
+ // configuration etc.
5
+
6
+ export default () => {
7
+ // Create or edit a document. `type` must be the document type, or
8
+ // `@apostrophecms/page`.
9
+ //
10
+ // `_id` should be the `_id` of the existing document to edit; leave
11
+ // blank to create a new document.
12
+ //
13
+ // `copyOf` is an optional, existing document from which properties should be copied.
14
+ //
15
+ // On success, returns the new or updated document. If the modal is cancelled,
16
+ // `undefined` is returned. Be sure to `await` the result.
17
+ apos.doc.edit = async ({
18
+ type,
19
+ _id,
20
+ copyOf
21
+ }) => {
22
+ if (!type) {
23
+ throw new Error('You must specify the type of document to edit.');
24
+ }
25
+ if (apos.page.validPageTypes.includes(type)) {
26
+ type = '@apostrophecms/page';
27
+ }
28
+ const modal = apos.modules[type]?.components?.editorModal;
29
+ if (!modal) {
30
+ throw new Error(`${type} is not a valid piece or page type, or cannot be edited`);
31
+ }
32
+ return apos.modal.execute(modal, {
33
+ moduleName: type,
34
+ docId: _id,
35
+ copyOf
36
+ });
37
+ };
38
+ // If you don't care about the returned value, you can emit an
39
+ // 'edit' apostrophe event with the same object you would pass to
40
+ // apos.doc.edit().
41
+ apos.bus.$on('edit', apos.doc.edit);
42
+ };
@@ -10,7 +10,13 @@ module.exports = {
10
10
  publishRole: 'editor',
11
11
  viewRole: false,
12
12
  previewDraft: true,
13
- relatedDocType: null
13
+ relatedDocType: null,
14
+ relationshipSuggestionLabel: 'apostrophe:relationshipSuggestionLabel',
15
+ relationshipSuggestionHelp: 'apostrophe:relationshipSuggestionHelp',
16
+ relationshipSuggestionLimit: 25,
17
+ relationshipSuggestionSort: { updatedAt: -1 },
18
+ relationshipSuggestionIcon: 'text-box-icon',
19
+ relationshipSuggestionFields: [ 'slug' ]
14
20
  },
15
21
  cascades: [ 'fields' ],
16
22
  fields(self) {
@@ -77,7 +83,7 @@ module.exports = {
77
83
  self.__meta.name === '@apostrophecms/global' ||
78
84
  self.apos.instanceOf(self, '@apostrophecms/any-page-type') ||
79
85
  self.apos.instanceOf(self, '@apostrophecms/page-type') ||
80
- self.options.canCreate === false ||
86
+ self.options.showCreate === false ||
81
87
  self.options.showPermissions === false
82
88
  ) {
83
89
  return null;
@@ -212,54 +218,12 @@ module.exports = {
212
218
  handlers(self) {
213
219
  return {
214
220
  beforeSave: {
215
- async updateCacheField(req, doc) {
216
- const relatedDocsIds = self.getRelatedDocsIds(req, doc);
217
-
218
- // - Remove current doc reference from docs that include it
219
- // - Update these docs' cache field
220
- await self.apos.doc.db.updateMany({
221
- relatedReverseIds: { $in: [ doc.aposDocId ] },
222
- aposLocale: { $in: [ doc.aposLocale, null ] }
223
- }, {
224
- $pull: { relatedReverseIds: doc.aposDocId },
225
- $set: { cacheInvalidatedAt: doc.updatedAt }
226
- });
227
-
228
- if (relatedDocsIds.length) {
229
- // - Add current doc reference to related docs
230
- // - Update related docs' cache field
231
- await self.apos.doc.db.updateMany({
232
- aposDocId: { $in: relatedDocsIds },
233
- aposLocale: { $in: [ doc.aposLocale, null ] }
234
- }, {
235
- $push: { relatedReverseIds: doc.aposDocId },
236
- $set: { cacheInvalidatedAt: doc.updatedAt }
237
- });
238
- }
239
-
240
- if (doc.relatedReverseIds && doc.relatedReverseIds.length) {
241
- // Update related reverse docs' cache field
242
- await self.apos.doc.db.updateMany({
243
- aposDocId: { $in: doc.relatedReverseIds },
244
- aposLocale: { $in: [ doc.aposLocale, null ] }
245
- }, {
246
- $set: { cacheInvalidatedAt: doc.updatedAt }
247
- });
248
- }
249
-
250
- if (doc._parentSlug) {
251
- // Update piece index page's cache field
252
- await self.apos.doc.db.updateOne({
253
- slug: doc._parentSlug,
254
- aposLocale: { $in: [ doc.aposLocale, null ] }
255
- }, {
256
- $set: { cacheInvalidatedAt: doc.updatedAt }
257
- });
258
- }
259
- },
260
221
  prepareForStorage(req, doc) {
261
222
  self.apos.schema.prepareForStorage(req, doc);
262
223
  },
224
+ async updateCacheField(req, doc) {
225
+ await self.updateCacheField(req, doc);
226
+ },
263
227
  slugPrefix(req, doc) {
264
228
  const prefix = self.options.slugPrefix;
265
229
  if (prefix) {
@@ -398,6 +362,51 @@ module.exports = {
398
362
 
399
363
  methods(self) {
400
364
  return {
365
+ async updateCacheField(req, doc) {
366
+ const relatedDocsIds = self.getRelatedDocsIds(req, doc);
367
+
368
+ // - Remove current doc reference from docs that include it
369
+ // - Update these docs' cache field
370
+ await self.apos.doc.db.updateMany({
371
+ relatedReverseIds: { $in: [ doc.aposDocId ] },
372
+ aposLocale: { $in: [ doc.aposLocale, null ] }
373
+ }, {
374
+ $pull: { relatedReverseIds: doc.aposDocId },
375
+ $set: { cacheInvalidatedAt: doc.updatedAt }
376
+ });
377
+
378
+ if (relatedDocsIds.length) {
379
+ // - Add current doc reference to related docs
380
+ // - Update related docs' cache field
381
+ await self.apos.doc.db.updateMany({
382
+ aposDocId: { $in: relatedDocsIds },
383
+ aposLocale: { $in: [ doc.aposLocale, null ] }
384
+ }, {
385
+ $push: { relatedReverseIds: doc.aposDocId },
386
+ $set: { cacheInvalidatedAt: doc.updatedAt }
387
+ });
388
+ }
389
+
390
+ if (doc.relatedReverseIds && doc.relatedReverseIds.length) {
391
+ // Update related reverse docs' cache field
392
+ await self.apos.doc.db.updateMany({
393
+ aposDocId: { $in: doc.relatedReverseIds },
394
+ aposLocale: { $in: [ doc.aposLocale, null ] }
395
+ }, {
396
+ $set: { cacheInvalidatedAt: doc.updatedAt }
397
+ });
398
+ }
399
+
400
+ if (doc._parentSlug) {
401
+ // Update piece index page's cache field
402
+ await self.apos.doc.db.updateOne({
403
+ slug: doc._parentSlug,
404
+ aposLocale: { $in: [ doc.aposLocale, null ] }
405
+ }, {
406
+ $set: { cacheInvalidatedAt: doc.updatedAt }
407
+ });
408
+ }
409
+ },
401
410
  addContextMenu() {
402
411
  self.apos.doc.addContextOperation(self.__meta.name, {
403
412
  action: 'shareDraft',
@@ -412,7 +421,7 @@ module.exports = {
412
421
  const relatedDocsIds = [];
413
422
  const handlers = {
414
423
  relationship: (field, doc) => {
415
- relatedDocsIds.push(...doc[field.name].map(relatedDoc => self.apos.doc.toAposDocId(relatedDoc)));
424
+ relatedDocsIds.push(...(doc[field.idsStorage] || []));
416
425
  }
417
426
  };
418
427
 
@@ -522,13 +531,16 @@ module.exports = {
522
531
  change.text = doc.title;
523
532
  },
524
533
  // Return a new schema containing only fields for which the
525
- // current user has the permission specified by the `permission`
526
- // property of the schema field, or there is no `permission` property for the field.
534
+ // current user has the permission specified by the `editPermission`
535
+ // property of the schema field, or there is no `editPermission`|`viewPermission` property for the field.
527
536
  allowedSchema(req) {
528
537
  let disabled;
529
538
  let type;
530
539
  const schema = _.filter(self.schema, function (field) {
531
- return !field.permission || self.apos.permission.can(req, field.permission && field.permission.action, field.permission && field.permission.type);
540
+ return (!field.editPermission && !field.viewPermission) ||
541
+ (field.editPermission && self.apos.permission.can(req, field.editPermission.action, field.editPermission.type)) ||
542
+ (field.viewPermission && self.apos.permission.can(req, field.viewPermission.action, field.viewPermission.type)) ||
543
+ false;
532
544
  });
533
545
  const typeIndex = _.findIndex(schema, { name: 'type' });
534
546
  if (typeIndex !== -1) {
@@ -1375,6 +1387,25 @@ module.exports = {
1375
1387
  });
1376
1388
 
1377
1389
  return draft;
1390
+ },
1391
+
1392
+ // Remove forbidden fields from document
1393
+ // A forbidden field is a field for which the current user does not have the appropriate viewPermission to see it
1394
+ removeForbiddenFields(req, doc) {
1395
+ if (!doc) {
1396
+ return doc;
1397
+ }
1398
+
1399
+ const forbiddenSchemaFields = Object.values(self.schema)
1400
+ .filter(field => {
1401
+ return field.viewPermission && !self.apos.permission.can(req, field.viewPermission.action, field.viewPermission.type);
1402
+ });
1403
+
1404
+ forbiddenSchemaFields.forEach(field => {
1405
+ delete doc[field.name];
1406
+ });
1407
+
1408
+ return doc;
1378
1409
  }
1379
1410
  };
1380
1411
  },
@@ -174,6 +174,13 @@ export default {
174
174
  followingUtils() {
175
175
  return this.followingValues('utility');
176
176
  },
177
+ canPublish() {
178
+ if (this.original && this.original._id) {
179
+ return this.original._publish;
180
+ }
181
+
182
+ return this.moduleOptions.canPublish;
183
+ },
177
184
  saveDisabled() {
178
185
  if (this.restoreOnly) {
179
186
  // Can always restore if it's a read-only view of the archive
@@ -197,7 +204,7 @@ export default {
197
204
  if (!this.manuallyPublished) {
198
205
  return true;
199
206
  }
200
- if (this.moduleOptions.canPublish) {
207
+ if (this.canPublish) {
201
208
  // Primary button is "publish". If it is previously published and the
202
209
  // draft is not modified since then, don't allow it
203
210
  return this.published && !this.isModifiedFromPublished;
@@ -261,7 +268,7 @@ export default {
261
268
  if (this.restoreOnly) {
262
269
  return 'apostrophe:restore';
263
270
  } else if (this.manuallyPublished) {
264
- if (this.moduleOptions.canPublish) {
271
+ if (this.canPublish) {
265
272
  if (this.copyOf) {
266
273
  return 'apostrophe:publish';
267
274
  } else if (this.original && this.original.lastPublishedAt) {
@@ -468,7 +475,7 @@ export default {
468
475
  await this.loadDoc();
469
476
  },
470
477
  async onSave(navigate = false) {
471
- if (this.moduleOptions.canPublish || !this.manuallyPublished) {
478
+ if (this.canPublish || !this.manuallyPublished) {
472
479
  await this.save({
473
480
  andPublish: this.manuallyPublished,
474
481
  navigate
@@ -23,7 +23,8 @@ module.exports = {
23
23
  showPermissions: true,
24
24
  // Files should by default be considered "related documents" when localizing
25
25
  // another document that references them
26
- relatedDocument: true
26
+ relatedDocument: true,
27
+ relationshipSuggestionIcon: 'file-document-icon'
27
28
  },
28
29
  fields: {
29
30
  remove: [ 'visibility' ],
@@ -7,7 +7,8 @@ module.exports = {
7
7
  autopublish: true,
8
8
  editRole: 'editor',
9
9
  publishRole: 'editor',
10
- shortcut: 'G,g'
10
+ shortcut: 'G,g',
11
+ relationshipSuggestionIcon: 'tag-icon'
11
12
  },
12
13
  fields: {
13
14
  remove: [ 'visibility' ]