apostrophe 3.7.0 → 3.8.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 (39) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +34 -3
  4. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -1
  5. package/modules/@apostrophecms/asset/index.js +77 -13
  6. package/modules/@apostrophecms/attachment/index.js +1 -0
  7. package/modules/@apostrophecms/db/index.js +5 -6
  8. package/modules/@apostrophecms/doc-type/index.js +23 -3
  9. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  10. package/modules/@apostrophecms/i18n/i18n/en.json +15 -4
  11. package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
  12. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
  13. package/modules/@apostrophecms/i18n/i18n/sk.json +3 -4
  14. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  15. package/modules/@apostrophecms/image-widget/index.js +2 -1
  16. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  17. package/modules/@apostrophecms/job/index.js +164 -212
  18. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  19. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  20. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  21. package/modules/@apostrophecms/notification/index.js +116 -8
  22. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  23. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  24. package/modules/@apostrophecms/page/index.js +37 -30
  25. package/modules/@apostrophecms/piece-type/index.js +178 -61
  26. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -50
  27. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +0 -2
  28. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  29. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  30. package/modules/@apostrophecms/task/index.js +2 -2
  31. package/modules/@apostrophecms/template/index.js +2 -0
  32. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
  33. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  34. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  35. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  36. package/package.json +2 -2
  37. package/test/job.js +224 -0
  38. package/test/pieces.js +17 -0
  39. package/test-lib/util.js +32 -0
@@ -15,27 +15,34 @@
15
15
  <button
16
16
  v-for="(button, i) in notification.buttons"
17
17
  :key="i"
18
- :data-apos-bus-event="JSON.stringify({ name: button.name, data: button.data })"
18
+ :data-apos-bus-event="JSON.stringify({
19
+ name: button.name,
20
+ data: button.data
21
+ })"
19
22
  >
20
23
  {{ localize(button.label) }}
21
24
  </button>
22
25
  </span>
23
26
  <div
24
27
  class="apos-notification__progress"
25
- v-if="notification.progress && notification.progress.current"
28
+ v-if="job && job.total"
26
29
  >
27
30
  <div class="apos-notification__progress-bar">
28
31
  <div
29
32
  class="apos-notification__progress-now" role="progressbar"
30
- :aria-valuenow="notification.progress.current" :style="`width: ${progressPercent}`"
31
- aria-valuemin="0" :aria-valuemax="100"
33
+ :aria-valuenow="job.processed || 0"
34
+ :style="`width: ${job.percentage + '%'}`"
35
+ aria-valuemin="0" :aria-valuemax="job.total"
32
36
  />
33
37
  </div>
34
38
  <span class="apos-notification__progress-value">
35
- {{ progressPercent }}
39
+ {{ Math.floor(job.percentage) + '%' }}
36
40
  </span>
37
41
  </div>
38
- <button @click="close" class="apos-notification__button">
42
+ <button
43
+ v-if="!job"
44
+ @click="close" class="apos-notification__button"
45
+ >
39
46
  <Close
40
47
  class="apos-notification__close-icon"
41
48
  title="Close Notification"
@@ -58,6 +65,16 @@ export default {
58
65
  }
59
66
  },
60
67
  emits: [ 'close' ],
68
+ data () {
69
+ return {
70
+ job: this.notification.job && this.notification.job._id ? {
71
+ route: `${apos.modules['@apostrophecms/job'].action}/${this.notification.job._id}`,
72
+ processed: 0,
73
+ total: 1,
74
+ action: this.notification.job.action
75
+ } : null
76
+ };
77
+ },
61
78
  computed: {
62
79
  classList() {
63
80
  const classes = [ 'apos-notification' ];
@@ -65,7 +82,7 @@ export default {
65
82
  classes.push(`apos-notification--${this.notification.type}`);
66
83
  }
67
84
 
68
- if (this.notification.progress && this.notification.progress.notification.current) {
85
+ if (this.job) {
69
86
  classes.push('apos-notification--progress');
70
87
  }
71
88
 
@@ -86,9 +103,6 @@ export default {
86
103
  } else {
87
104
  return 'circle-icon';
88
105
  }
89
- },
90
- progressPercent () {
91
- return `${Math.floor((this.notification.progress.current / 100) * 100)}%`;
92
106
  }
93
107
  },
94
108
  async mounted() {
@@ -102,6 +116,40 @@ export default {
102
116
  this.close();
103
117
  }
104
118
  });
119
+
120
+ if (this.job) {
121
+ try {
122
+ const {
123
+ total,
124
+ processed,
125
+ percentage
126
+ } = await apos.http.get(this.job.route, {});
127
+
128
+ this.job.total = total;
129
+ this.job.processed = processed || 0;
130
+ this.job.percentage = percentage;
131
+ this.job.ids = this.notification.job.ids || [];
132
+
133
+ await this.pollJob();
134
+ } catch (error) {
135
+ console.error('Unable to find notification job:', this.notification.job._id);
136
+ this.job = null;
137
+ }
138
+ }
139
+ // Notifications may include events to emit.
140
+ if (this.notification.event?.name) {
141
+ try {
142
+ // Clear the event to make sure it's only emitted once across browsers.
143
+ const safe = await this.clearEvent(this.notification._id);
144
+
145
+ if (safe) {
146
+ // The notification doc will only still have the event in one instance.
147
+ apos.bus.$emit(this.notification.event.name, this.notification.event.data);
148
+ }
149
+ } catch (error) {
150
+ console.error(this.$t('apostrophe:notificationClearEventError'));
151
+ }
152
+ }
105
153
  },
106
154
  methods: {
107
155
  close() {
@@ -116,6 +164,36 @@ export default {
116
164
  result = s;
117
165
  }
118
166
  return result;
167
+ },
168
+ async pollJob () {
169
+ if (!this.job?.total) {
170
+ return;
171
+ }
172
+ const job = await apos.http.get(this.job.route, {});
173
+ this.job.processed = job.processed;
174
+ this.job.percentage = job.percentage;
175
+
176
+ if (this.job.processed < this.job.total) {
177
+ await new Promise(resolve => {
178
+ setTimeout(resolve, 500);
179
+ });
180
+
181
+ await this.pollJob();
182
+ } else {
183
+ if (this.job.ids) {
184
+ apos.bus.$emit('content-changed', {
185
+ docIds: this.job.ids,
186
+ action: this.job.action || 'batch-update'
187
+ });
188
+ }
189
+ }
190
+ },
191
+ // `clearEvent` returns true if the event was found and cleared. Otherwise
192
+ // returns `false`
193
+ async clearEvent(id) {
194
+ return await apos.http.post(`${apos.notification.action}/${id}/clear-event`, {
195
+ body: {}
196
+ });
119
197
  }
120
198
  }
121
199
  };
@@ -228,7 +306,7 @@ export default {
228
306
  height: 100%;
229
307
  background-color: var(--a-brand-green);
230
308
  background-image: linear-gradient(46deg, var(--a-brand-gold) 0%, var(--a-brand-red) 26%, var(--a-brand-magenta) 47%, var(--a-brand-blue) 76%, var(--a-brand-green) 100%);
231
- transition: width 0.2s ease-out;
309
+ transition: width 0.5s ease-out;
232
310
  }
233
311
 
234
312
  .apos-notification__progress-value {
@@ -25,7 +25,7 @@ export default {
25
25
  };
26
26
  },
27
27
  async mounted() {
28
- apos.notify = async function(message, options) {
28
+ apos.notify = async function(message, options = {}) {
29
29
 
30
30
  if (options.dismiss === true) {
31
31
  options.dismiss = 5;
@@ -1996,35 +1996,38 @@ database.`);
1996
1996
  //
1997
1997
  // To avoid RAM issues with very large selections, the current
1998
1998
  // implementation processes the pages in series.
1999
- async batchSimpleRoute(req, name, change) {
2000
- const batchOperation = _.find(self.options.batchOperations, { name: name });
2001
- const schema = batchOperation.schema || [];
2002
- const data = self.apos.schema.newInstance(schema);
2003
- await self.apos.schema.convert(req, schema, 'form', req.body, data);
2004
- let ids = self.apos.launder.ids(req.body.ids);
2005
- if (!ids) {
2006
- if (req.body._id) {
2007
- ids = self.apos.launder.id(req.body._id);
2008
- }
2009
- }
2010
- if (req.body.job) {
2011
- return runJob();
2012
- } else {
2013
- for (const id of ids) {
2014
- await one(req, id);
2015
- }
2016
- }
2017
- async function runJob() {
2018
- return self.apos.modules['@apostrophecms/job'].run(req, ids, one, { labels: { title: batchOperation.progressLabel || batchOperation.buttonLabel || batchOperation.label } });
2019
- }
2020
- async function one(req, id) {
2021
- const page = await self.findForBatch(req, { _id: id }).toObject();
2022
- if (!page) {
2023
- throw new Error('notfound');
2024
- }
2025
- await change(req, page, data);
2026
- }
2027
- },
1999
+ // TODO: restore this method when fully implemented.
2000
+ // async batchSimpleRoute(req, name, change) {
2001
+ // const batchOperation = _.find(self.options.batchOperations, { name: name });
2002
+ // const schema = batchOperation.schema || [];
2003
+ // const data = self.apos.schema.newInstance(schema);
2004
+ // await self.apos.schema.convert(req, schema, 'form', req.body, data);
2005
+ // let ids = self.apos.launder.ids(req.body.ids);
2006
+ // if (!ids) {
2007
+ // if (req.body._id) {
2008
+ // ids = self.apos.launder.id(req.body._id);
2009
+ // }
2010
+ // }
2011
+ // if (req.body.job) {
2012
+ // return runJob();
2013
+ // } else {
2014
+ // for (const id of ids) {
2015
+ // await one(req, id);
2016
+ // }
2017
+ // }
2018
+ // async function runJob() {
2019
+ // return self.apos.modules['@apostrophecms/job'].runBatch(req, ids, one, {
2020
+ // // TODO: Update with new progress notification config
2021
+ // });
2022
+ // }
2023
+ // async function one(req, id) {
2024
+ // const page = await self.findForBatch(req, { _id: id }).toObject();
2025
+ // if (!page) {
2026
+ // throw new Error('notfound');
2027
+ // }
2028
+ // await change(req, page, data);
2029
+ // }
2030
+ // },
2028
2031
  // Backward compatible method following moving this to page-type module.
2029
2032
  // This page module method may be deprecated in the next major version.
2030
2033
  allowedSchema(req, page, parentPage) {
@@ -2032,7 +2035,11 @@ database.`);
2032
2035
  .allowedSchema(req, page, parentPage);
2033
2036
  },
2034
2037
  getRestQuery(req) {
2035
- const query = self.find(req).ancestors(true).children(true).applyBuildersSafely(req.query);
2038
+ const query = self.find(req)
2039
+ .ancestors(true)
2040
+ .children(true)
2041
+ .attachments(true)
2042
+ .applyBuildersSafely(req.query);
2036
2043
  // Minimum standard for a REST query without a public projection
2037
2044
  // is being allowed to view drafts on the site
2038
2045
  if (!self.apos.permission.can(req, 'view-draft')) {
@@ -2,7 +2,12 @@ const _ = require('lodash');
2
2
 
3
3
  module.exports = {
4
4
  extend: '@apostrophecms/doc-type',
5
- cascades: [ 'filters', 'columns', 'batchOperations' ],
5
+ cascades: [
6
+ 'filters',
7
+ 'columns',
8
+ 'batchOperations',
9
+ 'utilityOperations'
10
+ ],
6
11
  options: {
7
12
  perPage: 10,
8
13
  quickCreate: true,
@@ -74,7 +79,7 @@ module.exports = {
74
79
  ],
75
80
  // TODO: Delete `allowedInChooser` if not used.
76
81
  allowedInChooser: false,
77
- def: true
82
+ def: null
78
83
  },
79
84
  archived: {
80
85
  label: 'apostrophe:archive',
@@ -96,44 +101,47 @@ module.exports = {
96
101
  }
97
102
  }
98
103
  },
104
+ utilityOperations: {},
99
105
  batchOperations: {
100
106
  add: {
101
107
  archive: {
102
108
  label: 'apostrophe:archive',
103
- inputType: 'radio',
104
- unlessFilter: {
105
- archived: true
109
+ messages: {
110
+ progress: 'Archiving {{ type }}...',
111
+ completed: 'Archived {{ count }} {{ type }}.'
112
+ },
113
+ icon: 'archive-arrow-down-icon',
114
+ if: {
115
+ archived: false
116
+ },
117
+ modalOptions: {
118
+ title: 'apostrophe:archiveType',
119
+ description: 'apostrophe:archivingBatchConfirmation',
120
+ confirmationButton: 'apostrophe:archivingBatchConfirmationButton'
106
121
  }
107
122
  },
108
123
  restore: {
109
124
  label: 'apostrophe:restore',
110
- unlessFilter: {
111
- archived: false
112
- }
113
- },
114
- visibility: {
115
- label: 'apostrophe:visibility',
116
- requiredField: 'visibility',
117
- fields: {
118
- add: {
119
- visibility: {
120
- type: 'select',
121
- label: 'apostrophe:visibilityLabel',
122
- def: 'public',
123
- choices: [
124
- {
125
- value: 'public',
126
- label: 'apostrophe:public'
127
- },
128
- {
129
- value: 'loginRequired',
130
- label: 'apostrophe:loginRequired'
131
- }
132
- ]
133
- }
134
- }
125
+ messages: {
126
+ progress: 'Restoring {{ type }}...',
127
+ completed: 'Restoring {{ count }} {{ type }}.'
128
+ },
129
+ icon: 'archive-arrow-up-icon',
130
+ if: {
131
+ archived: true
132
+ },
133
+ modalOptions: {
134
+ title: 'apostrophe:restoreType',
135
+ description: 'apostrophe:restoreBatchConfirmation',
136
+ confirmationButton: 'apostrophe:restoreBatchConfirmationButton'
135
137
  }
136
138
  }
139
+ },
140
+ group: {
141
+ more: {
142
+ icon: 'dots-vertical-icon',
143
+ operations: []
144
+ }
137
145
  }
138
146
  },
139
147
  init(self) {
@@ -179,7 +187,6 @@ module.exports = {
179
187
  if (self.apos.launder.boolean(req.query['render-areas']) === true) {
180
188
  await self.apos.area.renderDocsAreas(req, result.results);
181
189
  }
182
- self.apos.attachment.all(result.results, { annotate: true });
183
190
  if (query.get('choicesResults')) {
184
191
  result.choices = query.get('choicesResults');
185
192
  }
@@ -259,6 +266,58 @@ module.exports = {
259
266
  }
260
267
  return self.publish(req, draft);
261
268
  },
269
+ async archive (req) {
270
+ if (!Array.isArray(req.body._ids)) {
271
+ throw self.apos.error('invalid');
272
+ }
273
+
274
+ req.body._ids = req.body._ids.map(_id => {
275
+ return self.inferIdLocaleAndMode(req, _id);
276
+ });
277
+
278
+ return self.apos.modules['@apostrophecms/job'].runBatch(
279
+ req,
280
+ req.body._ids,
281
+ async function(req, id) {
282
+ const piece = await self.findOneForEditing(req, { _id: id });
283
+
284
+ if (!piece) {
285
+ throw self.apos.error('notfound');
286
+ }
287
+
288
+ piece.archived = true;
289
+ await self.update(req, piece);
290
+ }, {
291
+ action: 'archive'
292
+ }
293
+ );
294
+ },
295
+ async restore (req) {
296
+ if (!Array.isArray(req.body._ids)) {
297
+ throw self.apos.error('invalid');
298
+ }
299
+
300
+ req.body._ids = req.body._ids.map(_id => {
301
+ return self.inferIdLocaleAndMode(req, _id);
302
+ });
303
+
304
+ return self.apos.modules['@apostrophecms/job'].runBatch(
305
+ req,
306
+ req.body._ids,
307
+ async function(req, id) {
308
+ const piece = await self.findOneForEditing(req, { _id: id });
309
+
310
+ if (!piece) {
311
+ throw self.apos.error('notfound');
312
+ }
313
+
314
+ piece.archived = false;
315
+ await self.update(req, piece);
316
+ }, {
317
+ action: 'restore'
318
+ }
319
+ );
320
+ },
262
321
  ':_id/localize': async (req) => {
263
322
  const _id = self.inferIdLocaleAndMode(req, req.params._id);
264
323
  const draft = await self.findOneForEditing(req.clone({
@@ -388,20 +447,72 @@ module.exports = {
388
447
  },
389
448
  'apostrophe:modulesRegistered': {
390
449
  composeBatchOperations() {
391
- self.batchOperations = Object.keys(self.batchOperations).map(key => ({
392
- name: key,
393
- ...self.batchOperations[key]
394
- })).filter(batchOperation => {
395
- if (batchOperation.requiredField && !_.find(self.schema, { name: batchOperation.requiredField })) {
396
- return false;
397
- }
398
- if (batchOperation.onlyIf) {
399
- if (!batchOperation.onlyIf(self.name)) {
400
- return false;
450
+ const groupedOperations = Object.entries(self.batchOperations)
451
+ .reduce((acc, [ opName, properties ]) => {
452
+ // Check if there is a required schema field for this batch operation.
453
+ const requiredFieldNotFound = properties.requiredField && !self.schema
454
+ .some((field) => field.name === properties.requiredField);
455
+
456
+ if (requiredFieldNotFound) {
457
+ return acc;
401
458
  }
459
+ // Find a group for the operation, if there is one.
460
+ const associatedGroup = getAssociatedGroup(opName);
461
+ const currentOperation = {
462
+ action: opName,
463
+ ...properties
464
+ };
465
+ const { action, ...props } = getOperationOrGroup(
466
+ currentOperation,
467
+ associatedGroup,
468
+ acc
469
+ );
470
+
471
+ return {
472
+ ...acc,
473
+ [action]: {
474
+ ...props
475
+ }
476
+ };
477
+ }, {});
478
+
479
+ self.batchOperations = Object.entries(groupedOperations)
480
+ .map(([ action, properties ]) => ({
481
+ action,
482
+ ...properties
483
+ }));
484
+
485
+ function getOperationOrGroup (currentOp, [ groupName, groupProperties ], acc) {
486
+ if (!groupName) {
487
+ // Operation is not grouped. Return it as it is.
488
+ return currentOp;
402
489
  }
403
- return true;
404
- });
490
+
491
+ // Return the operation group with the new operation added.
492
+ return {
493
+ name: groupName,
494
+ ...groupProperties,
495
+ operations: [
496
+ ...(acc[groupName] && acc[groupName].operations) || [],
497
+ currentOp
498
+ ]
499
+ };
500
+ }
501
+
502
+ // Returns the object entry, e.g., `[groupName, { ...groupProperties }]`
503
+ function getAssociatedGroup (operation) {
504
+ return Object.entries(self.batchOperationsGroups)
505
+ .find(([ _key, { operations } ]) => {
506
+ return operations.includes(operation);
507
+ }) || [];
508
+ }
509
+ },
510
+ composeUtilityOperations() {
511
+ self.utilityOperations = Object.entries(self.utilityOperations || {})
512
+ .map(([ action, properties ]) => ({
513
+ action,
514
+ ...properties
515
+ }));
405
516
  }
406
517
  }
407
518
  };
@@ -538,33 +649,37 @@ module.exports = {
538
649
  // Pass `req`, the `name` of a configured batch operation, and
539
650
  // and a function that accepts (req, piece, data),
540
651
  // and returns a promise to perform the modification on that
541
- // one piece (including calling`update` if appropriate).
652
+ // one piece (including calling `update` if appropriate).
542
653
  //
543
654
  // `data` is an object containing any schema fields specified
544
655
  // for the batch operation. If there is no schema it will be
545
656
  // an empty object.
546
657
  //
547
- // Replies immediately to the request with `{ jobId: 'cxxxx' }`.
658
+ // Replies immediately to the request with `{ jobId: 'xxxxx' }`.
548
659
  // This can then be passed to appropriate browser-side APIs
549
660
  // to monitor progress.
550
661
  //
551
662
  // To avoid RAM issues with very large selections while ensuring
552
663
  // that all lifecycle events are fired correctly, the current
553
664
  // implementation processes the pieces in series.
554
- async batchSimpleRoute(req, name, change) {
555
- const batchOperation = _.find(self.batchOperations, { name: name });
556
- const schema = batchOperation.schema || [];
557
- const data = self.apos.schema.newInstance(schema);
558
- await self.apos.schema.convert(req, schema, req.body, data);
559
- await self.apos.modules['@apostrophecms/job'].run(req, one, { labels: { title: batchOperation.progressLabel || batchOperation.buttonLabel || batchOperation.label } });
560
- async function one(req, id) {
561
- const piece = self.findForEditing(req, { _id: id }).toObject();
562
- if (!piece) {
563
- throw self.apos.error('notfound');
564
- }
565
- await change(req, piece, data);
566
- }
567
- },
665
+ // TODO: restore this method when fully implemented.
666
+ // async batchSimpleRoute(req, name, change) {
667
+ // const batchOperation = _.find(self.batchOperations, { name: name });
668
+ // const schema = batchOperation.schema || [];
669
+ // const data = self.apos.schema.newInstance(schema);
670
+
671
+ // await self.apos.schema.convert(req, schema, req.body, data);
672
+ // await self.apos.modules['@apostrophecms/job'].runBatch(req, one, {
673
+ // // TODO: Update with new progress notification config
674
+ // });
675
+ // async function one(req, id) {
676
+ // const piece = self.findForEditing(req, { _id: id }).toObject();
677
+ // if (!piece) {
678
+ // throw self.apos.error('notfound');
679
+ // }
680
+ // await change(req, piece, data);
681
+ // }
682
+ // },
568
683
 
569
684
  // Accept a piece as untrusted input potentially
570
685
  // found in `input` (hint: you can pass `req.body`
@@ -765,7 +880,7 @@ module.exports = {
765
880
  return piece;
766
881
  },
767
882
  getRestQuery(req) {
768
- const query = self.find(req);
883
+ const query = self.find(req).attachments(true);
769
884
  query.applyBuildersSafely(req.query);
770
885
  if (!self.apos.permission.can(req, 'view-draft')) {
771
886
  if (!self.options.publicApiProjection) {
@@ -773,7 +888,7 @@ module.exports = {
773
888
  query.and({
774
889
  _id: null
775
890
  });
776
- } else {
891
+ } else if (!query.state.project) {
777
892
  query.project(self.options.publicApiProjection);
778
893
  }
779
894
  }
@@ -812,6 +927,7 @@ module.exports = {
812
927
  browserOptions.filters = self.filters;
813
928
  browserOptions.columns = self.columns;
814
929
  browserOptions.batchOperations = self.batchOperations;
930
+ browserOptions.utilityOperations = self.utilityOperations;
815
931
  browserOptions.insertViaUpload = self.options.insertViaUpload;
816
932
  browserOptions.quickCreate = !self.options.singleton && self.options.quickCreate && self.apos.permission.can(req, 'edit', self.name, 'draft');
817
933
  browserOptions.singleton = self.options.singleton;
@@ -828,6 +944,7 @@ module.exports = {
828
944
  editorModal: 'AposDocEditor',
829
945
  managerModal: 'AposDocsManager'
830
946
  });
947
+
831
948
  return browserOptions;
832
949
  },
833
950
  find(_super, req, criteria, projection) {