apostrophe 3.6.0 → 3.9.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/.eslintrc +4 -0
- package/.github/workflows/main.yml +45 -0
- package/CHANGELOG.md +92 -3
- package/README.md +2 -3
- package/index.js +104 -3
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +15 -10
- package/modules/@apostrophecms/asset/index.js +105 -15
- package/modules/@apostrophecms/attachment/index.js +1 -4
- package/modules/@apostrophecms/db/index.js +5 -6
- package/modules/@apostrophecms/doc/index.js +2 -0
- package/modules/@apostrophecms/doc-type/index.js +39 -16
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +0 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +23 -4
- package/modules/@apostrophecms/i18n/i18n/es.json +1 -2
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +3 -4
- package/modules/@apostrophecms/i18n/index.js +36 -6
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
- package/modules/@apostrophecms/image-widget/index.js +2 -1
- package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
- package/modules/@apostrophecms/job/index.js +165 -220
- package/modules/@apostrophecms/login/index.js +0 -15
- package/modules/@apostrophecms/migration/index.js +1 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
- package/modules/@apostrophecms/module/index.js +1 -4
- package/modules/@apostrophecms/notification/index.js +116 -8
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
- package/modules/@apostrophecms/page/index.js +84 -52
- package/modules/@apostrophecms/page-type/index.js +5 -1
- package/modules/@apostrophecms/piece-type/index.js +183 -61
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +180 -50
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +141 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
- package/modules/@apostrophecms/schema/index.js +81 -25
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +9 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +11 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +2 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +0 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogo.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogoIcon.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogoPadless.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +0 -1
- package/modules/@apostrophecms/search/index.js +53 -33
- package/modules/@apostrophecms/task/index.js +7 -3
- package/modules/@apostrophecms/template/index.js +7 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +9 -3
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +3 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- package/modules/@apostrophecms/util/ui/src/util.js +15 -0
- package/modules/@apostrophecms/widget-type/index.js +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +5 -19
- package/package.json +2 -2
- package/test/job.js +224 -0
- package/test/pieces.js +34 -0
- package/test-lib/util.js +32 -0
- package/.circleci/config.yml +0 -94
- package/.scratch.md +0 -2
|
@@ -2,7 +2,12 @@ const _ = require('lodash');
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
extend: '@apostrophecms/doc-type',
|
|
5
|
-
cascades: [
|
|
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:
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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,77 @@ module.exports = {
|
|
|
388
447
|
},
|
|
389
448
|
'apostrophe:modulesRegistered': {
|
|
390
449
|
composeBatchOperations() {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
+
}));
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
'@apostrophecms/search:determineTypes': {
|
|
519
|
+
checkSearchable(types) {
|
|
520
|
+
self.searchDetermineTypes(types);
|
|
405
521
|
}
|
|
406
522
|
}
|
|
407
523
|
};
|
|
@@ -538,33 +654,37 @@ module.exports = {
|
|
|
538
654
|
// Pass `req`, the `name` of a configured batch operation, and
|
|
539
655
|
// and a function that accepts (req, piece, data),
|
|
540
656
|
// and returns a promise to perform the modification on that
|
|
541
|
-
// one piece (including calling`update` if appropriate).
|
|
657
|
+
// one piece (including calling `update` if appropriate).
|
|
542
658
|
//
|
|
543
659
|
// `data` is an object containing any schema fields specified
|
|
544
660
|
// for the batch operation. If there is no schema it will be
|
|
545
661
|
// an empty object.
|
|
546
662
|
//
|
|
547
|
-
// Replies immediately to the request with `{ jobId: '
|
|
663
|
+
// Replies immediately to the request with `{ jobId: 'xxxxx' }`.
|
|
548
664
|
// This can then be passed to appropriate browser-side APIs
|
|
549
665
|
// to monitor progress.
|
|
550
666
|
//
|
|
551
667
|
// To avoid RAM issues with very large selections while ensuring
|
|
552
668
|
// that all lifecycle events are fired correctly, the current
|
|
553
669
|
// implementation processes the pieces in series.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
670
|
+
// TODO: restore this method when fully implemented.
|
|
671
|
+
// async batchSimpleRoute(req, name, change) {
|
|
672
|
+
// const batchOperation = _.find(self.batchOperations, { name: name });
|
|
673
|
+
// const schema = batchOperation.schema || [];
|
|
674
|
+
// const data = self.apos.schema.newInstance(schema);
|
|
675
|
+
|
|
676
|
+
// await self.apos.schema.convert(req, schema, req.body, data);
|
|
677
|
+
// await self.apos.modules['@apostrophecms/job'].runBatch(req, one, {
|
|
678
|
+
// // TODO: Update with new progress notification config
|
|
679
|
+
// });
|
|
680
|
+
// async function one(req, id) {
|
|
681
|
+
// const piece = self.findForEditing(req, { _id: id }).toObject();
|
|
682
|
+
// if (!piece) {
|
|
683
|
+
// throw self.apos.error('notfound');
|
|
684
|
+
// }
|
|
685
|
+
// await change(req, piece, data);
|
|
686
|
+
// }
|
|
687
|
+
// },
|
|
568
688
|
|
|
569
689
|
// Accept a piece as untrusted input potentially
|
|
570
690
|
// found in `input` (hint: you can pass `req.body`
|
|
@@ -765,7 +885,7 @@ module.exports = {
|
|
|
765
885
|
return piece;
|
|
766
886
|
},
|
|
767
887
|
getRestQuery(req) {
|
|
768
|
-
const query = self.find(req);
|
|
888
|
+
const query = self.find(req).attachments(true);
|
|
769
889
|
query.applyBuildersSafely(req.query);
|
|
770
890
|
if (!self.apos.permission.can(req, 'view-draft')) {
|
|
771
891
|
if (!self.options.publicApiProjection) {
|
|
@@ -773,7 +893,7 @@ module.exports = {
|
|
|
773
893
|
query.and({
|
|
774
894
|
_id: null
|
|
775
895
|
});
|
|
776
|
-
} else {
|
|
896
|
+
} else if (!query.state.project) {
|
|
777
897
|
query.project(self.options.publicApiProjection);
|
|
778
898
|
}
|
|
779
899
|
}
|
|
@@ -812,6 +932,7 @@ module.exports = {
|
|
|
812
932
|
browserOptions.filters = self.filters;
|
|
813
933
|
browserOptions.columns = self.columns;
|
|
814
934
|
browserOptions.batchOperations = self.batchOperations;
|
|
935
|
+
browserOptions.utilityOperations = self.utilityOperations;
|
|
815
936
|
browserOptions.insertViaUpload = self.options.insertViaUpload;
|
|
816
937
|
browserOptions.quickCreate = !self.options.singleton && self.options.quickCreate && self.apos.permission.can(req, 'edit', self.name, 'draft');
|
|
817
938
|
browserOptions.singleton = self.options.singleton;
|
|
@@ -828,6 +949,7 @@ module.exports = {
|
|
|
828
949
|
editorModal: 'AposDocEditor',
|
|
829
950
|
managerModal: 'AposDocsManager'
|
|
830
951
|
});
|
|
952
|
+
|
|
831
953
|
return browserOptions;
|
|
832
954
|
},
|
|
833
955
|
find(_super, req, criteria, projection) {
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
</template>
|
|
20
20
|
<template #primaryControls>
|
|
21
21
|
<AposContextMenu
|
|
22
|
-
v-if="
|
|
23
|
-
:button="
|
|
24
|
-
:menu="
|
|
25
|
-
@item-clicked="
|
|
22
|
+
v-if="utilityOperations.menu.length"
|
|
23
|
+
:button="utilityOperations.button"
|
|
24
|
+
:menu="utilityOperations.menu"
|
|
25
|
+
@item-clicked="utilityOperationsHandler"
|
|
26
26
|
/>
|
|
27
27
|
<AposButton
|
|
28
28
|
v-if="relationshipField"
|
|
@@ -66,19 +66,33 @@
|
|
|
66
66
|
:selected-state="selectAllState"
|
|
67
67
|
:total-pages="totalPages"
|
|
68
68
|
:current-page="currentPage"
|
|
69
|
-
:filters="moduleOptions.filters"
|
|
70
69
|
:filter-choices="filterChoices"
|
|
71
70
|
:filter-values="filterValues"
|
|
71
|
+
:filters="moduleOptions.filters"
|
|
72
72
|
:labels="moduleLabels"
|
|
73
|
+
:displayed-items="items.length"
|
|
74
|
+
:is-relationship="!!relationshipField"
|
|
75
|
+
:checked-count="checked.length"
|
|
76
|
+
:batch-operations="moduleOptions.batchOperations"
|
|
73
77
|
@select-click="selectAll"
|
|
74
78
|
@search="search"
|
|
75
79
|
@page-change="updatePage"
|
|
76
80
|
@filter="filter"
|
|
81
|
+
@batch="handleBatchAction"
|
|
77
82
|
:options="{
|
|
78
|
-
disableUnchecked: maxReached()
|
|
79
|
-
hideSelectAll: !relationshipField
|
|
83
|
+
disableUnchecked: maxReached()
|
|
80
84
|
}"
|
|
81
85
|
/>
|
|
86
|
+
<AposDocsManagerSelectBox
|
|
87
|
+
:selected-state="selectAllState"
|
|
88
|
+
:module-labels="moduleLabels"
|
|
89
|
+
:filter-values="filterValues"
|
|
90
|
+
:checked-ids="checked"
|
|
91
|
+
:all-pieces-selection="allPiecesSelection"
|
|
92
|
+
:displayed-items="items.length"
|
|
93
|
+
@select-all="selectAllPieces"
|
|
94
|
+
@set-all-pieces-selection="setAllPiecesSelection"
|
|
95
|
+
/>
|
|
82
96
|
</template>
|
|
83
97
|
<template #bodyMain>
|
|
84
98
|
<AposDocsManagerDisplay
|
|
@@ -90,7 +104,6 @@
|
|
|
90
104
|
:options="{
|
|
91
105
|
...moduleOptions,
|
|
92
106
|
disableUnchecked: maxReached(),
|
|
93
|
-
hideCheckboxes: !relationshipField,
|
|
94
107
|
disableUnpublished: disableUnpublished,
|
|
95
108
|
manuallyPublished: manuallyPublished
|
|
96
109
|
}"
|
|
@@ -136,7 +149,7 @@ export default {
|
|
|
136
149
|
filterValues: {},
|
|
137
150
|
queryExtras: {},
|
|
138
151
|
holdQueries: false,
|
|
139
|
-
|
|
152
|
+
utilityOperations: {
|
|
140
153
|
button: {
|
|
141
154
|
label: 'apostrophe:moreOperations',
|
|
142
155
|
iconOnly: true,
|
|
@@ -145,7 +158,11 @@ export default {
|
|
|
145
158
|
},
|
|
146
159
|
menu: []
|
|
147
160
|
},
|
|
148
|
-
filterChoices: {}
|
|
161
|
+
filterChoices: {},
|
|
162
|
+
allPiecesSelection: {
|
|
163
|
+
isSelected: false,
|
|
164
|
+
total: 0
|
|
165
|
+
}
|
|
149
166
|
};
|
|
150
167
|
},
|
|
151
168
|
computed: {
|
|
@@ -190,6 +207,16 @@ export default {
|
|
|
190
207
|
},
|
|
191
208
|
disableUnpublished() {
|
|
192
209
|
return this.relationshipField && apos.modules[this.relationshipField.withType].localized;
|
|
210
|
+
},
|
|
211
|
+
selectAllChoice() {
|
|
212
|
+
const checkCount = this.checked.length;
|
|
213
|
+
const pageNotFullyChecked = this.items
|
|
214
|
+
.some((item) => !this.checked.includes(item._id));
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
value: 'checked',
|
|
218
|
+
indeterminate: checkCount && pageNotFullyChecked
|
|
219
|
+
};
|
|
193
220
|
}
|
|
194
221
|
},
|
|
195
222
|
created() {
|
|
@@ -206,17 +233,10 @@ export default {
|
|
|
206
233
|
this.headers = this.computeHeaders();
|
|
207
234
|
// Get the data. This will be more complex in actuality.
|
|
208
235
|
this.modal.active = true;
|
|
209
|
-
this.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
action: 'new',
|
|
214
|
-
label: {
|
|
215
|
-
key: 'apostrophe:newDocType',
|
|
216
|
-
type: this.$t(this.moduleLabels.singular)
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
}
|
|
236
|
+
this.setUtilityOperations();
|
|
237
|
+
await this.getPieces();
|
|
238
|
+
await this.getAllPiecesTotal();
|
|
239
|
+
|
|
220
240
|
apos.bus.$on('content-changed', this.getPieces);
|
|
221
241
|
},
|
|
222
242
|
destroyed() {
|
|
@@ -224,10 +244,13 @@ export default {
|
|
|
224
244
|
apos.bus.$off('content-changed', this.getPieces);
|
|
225
245
|
},
|
|
226
246
|
methods: {
|
|
227
|
-
|
|
247
|
+
utilityOperationsHandler(action) {
|
|
228
248
|
if (action === 'new') {
|
|
229
249
|
this.create();
|
|
250
|
+
return;
|
|
230
251
|
}
|
|
252
|
+
|
|
253
|
+
this.handleUtilityOperation(action);
|
|
231
254
|
},
|
|
232
255
|
setCheckedDocs(checked) {
|
|
233
256
|
this.checkedDocs = checked;
|
|
@@ -238,6 +261,29 @@ export default {
|
|
|
238
261
|
async create() {
|
|
239
262
|
await this.edit(null);
|
|
240
263
|
},
|
|
264
|
+
async handleUtilityOperation(action) {
|
|
265
|
+
const operation = this.utilityOperations.menu
|
|
266
|
+
.find((op) => op.action === action);
|
|
267
|
+
|
|
268
|
+
if (!operation) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const {
|
|
273
|
+
modal, ...modalOptions
|
|
274
|
+
} = operation.modalOptions || {};
|
|
275
|
+
|
|
276
|
+
if (modal) {
|
|
277
|
+
await apos.modal.execute(modal, {
|
|
278
|
+
moduleAction: this.moduleOptions.action,
|
|
279
|
+
action,
|
|
280
|
+
labels: this.moduleLabels,
|
|
281
|
+
messages: operation.messages,
|
|
282
|
+
...modalOptions
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
|
|
241
287
|
// If pieceOrId is null, a new piece is created
|
|
242
288
|
async edit(pieceOrId) {
|
|
243
289
|
let piece;
|
|
@@ -271,42 +317,68 @@ export default {
|
|
|
271
317
|
async finishSaved() {
|
|
272
318
|
await this.getPieces();
|
|
273
319
|
},
|
|
274
|
-
async
|
|
275
|
-
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
this.holdQueries = true;
|
|
280
|
-
|
|
281
|
-
const qs = {
|
|
320
|
+
async request (mergeOptions) {
|
|
321
|
+
const options = {
|
|
282
322
|
...this.filterValues,
|
|
283
|
-
page: this.currentPage,
|
|
284
323
|
...this.queryExtras,
|
|
285
|
-
|
|
324
|
+
...mergeOptions,
|
|
286
325
|
withPublished: 1
|
|
287
326
|
};
|
|
288
327
|
|
|
289
328
|
// Avoid undefined properties.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
329
|
+
const qs = Object.entries(options)
|
|
330
|
+
.reduce((acc, [ key, val ]) => ({
|
|
331
|
+
...acc,
|
|
332
|
+
...val !== undefined && { [key]: val }
|
|
333
|
+
}), {});
|
|
334
|
+
|
|
335
|
+
return apos.http.get(this.moduleOptions.action, {
|
|
336
|
+
qs,
|
|
337
|
+
busy: true,
|
|
338
|
+
draft: true
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
async getPieces () {
|
|
342
|
+
if (this.holdQueries) {
|
|
343
|
+
return;
|
|
294
344
|
}
|
|
295
345
|
|
|
296
|
-
|
|
297
|
-
this.moduleOptions.action, {
|
|
298
|
-
busy: true,
|
|
299
|
-
qs,
|
|
300
|
-
draft: true
|
|
301
|
-
}
|
|
302
|
-
);
|
|
346
|
+
this.holdQueries = true;
|
|
303
347
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
348
|
+
const {
|
|
349
|
+
currentPage, pages, results, choices
|
|
350
|
+
} = await this.request({
|
|
351
|
+
page: this.currentPage
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
this.currentPage = currentPage;
|
|
355
|
+
this.totalPages = pages;
|
|
356
|
+
this.items = results;
|
|
357
|
+
this.filterChoices = choices;
|
|
308
358
|
this.holdQueries = false;
|
|
309
359
|
},
|
|
360
|
+
async getAllPiecesTotal () {
|
|
361
|
+
const { count: total } = await this.request({ count: 1 });
|
|
362
|
+
|
|
363
|
+
this.setAllPiecesSelection({
|
|
364
|
+
isSelected: false,
|
|
365
|
+
total
|
|
366
|
+
});
|
|
367
|
+
},
|
|
368
|
+
async selectAllPieces () {
|
|
369
|
+
const { results: docs } = await this.request({
|
|
370
|
+
project: {
|
|
371
|
+
_id: 1
|
|
372
|
+
},
|
|
373
|
+
attachments: false,
|
|
374
|
+
perPage: this.allPiecesSelection.total
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
this.setAllPiecesSelection({
|
|
378
|
+
isSelected: true,
|
|
379
|
+
docs
|
|
380
|
+
});
|
|
381
|
+
},
|
|
310
382
|
updatePage(num) {
|
|
311
383
|
if (num) {
|
|
312
384
|
this.currentPage = num;
|
|
@@ -325,6 +397,7 @@ export default {
|
|
|
325
397
|
this.currentPage = 1;
|
|
326
398
|
|
|
327
399
|
await this.getPieces();
|
|
400
|
+
await this.getAllPiecesTotal();
|
|
328
401
|
},
|
|
329
402
|
async filter(filter, value) {
|
|
330
403
|
if (this.filterValues[filter] === value) {
|
|
@@ -334,10 +407,12 @@ export default {
|
|
|
334
407
|
this.filterValues[filter] = value;
|
|
335
408
|
this.currentPage = 1;
|
|
336
409
|
|
|
337
|
-
this.getPieces();
|
|
410
|
+
await this.getPieces();
|
|
411
|
+
await this.getAllPiecesTotal();
|
|
338
412
|
this.headers = this.computeHeaders();
|
|
339
|
-
},
|
|
340
413
|
|
|
414
|
+
this.setCheckedDocs([]);
|
|
415
|
+
},
|
|
341
416
|
shortcutNew(event) {
|
|
342
417
|
const interesting = (event.keyCode === 78 || event.keyCode === 67); // C(reate) or N(ew)
|
|
343
418
|
const topModal = apos.modal.stack[apos.modal.stack.length - 1] ? apos.modal.stack[apos.modal.stack.length - 1].id : null;
|
|
@@ -349,7 +424,6 @@ export default {
|
|
|
349
424
|
this.create();
|
|
350
425
|
}
|
|
351
426
|
},
|
|
352
|
-
|
|
353
427
|
bindShortcuts() {
|
|
354
428
|
window.addEventListener('keydown', this.shortcutNew);
|
|
355
429
|
},
|
|
@@ -376,6 +450,62 @@ export default {
|
|
|
376
450
|
_fields: result
|
|
377
451
|
});
|
|
378
452
|
}
|
|
453
|
+
},
|
|
454
|
+
setAllPiecesSelection ({
|
|
455
|
+
isSelected, total, docs
|
|
456
|
+
}) {
|
|
457
|
+
if (typeof isSelected === 'boolean') {
|
|
458
|
+
this.allPiecesSelection.isSelected = isSelected;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (typeof total === 'number') {
|
|
462
|
+
this.allPiecesSelection.total = total;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (docs) {
|
|
466
|
+
this.setCheckedDocs(docs);
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
async handleBatchAction({
|
|
470
|
+
label, action, requestOptions = {}, messages
|
|
471
|
+
}) {
|
|
472
|
+
if (action) {
|
|
473
|
+
try {
|
|
474
|
+
await apos.http.post(`${this.moduleOptions.action}/${action}`, {
|
|
475
|
+
body: {
|
|
476
|
+
...requestOptions,
|
|
477
|
+
_ids: this.checked,
|
|
478
|
+
messages: messages,
|
|
479
|
+
type: this.checked.length === 1 ? this.moduleLabels.singular
|
|
480
|
+
: this.moduleLabels.plural
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
} catch (error) {
|
|
484
|
+
apos.notify('apostrophe:errorBatchOperationNoti', {
|
|
485
|
+
interpolate: { operation: label },
|
|
486
|
+
type: 'danger'
|
|
487
|
+
});
|
|
488
|
+
console.error(error);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
setUtilityOperations () {
|
|
493
|
+
const { utilityOperations } = this.moduleOptions;
|
|
494
|
+
|
|
495
|
+
const newPiece = {
|
|
496
|
+
action: 'new',
|
|
497
|
+
label: {
|
|
498
|
+
key: 'apostrophe:newDocType',
|
|
499
|
+
type: this.$t(this.moduleLabels.singular)
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
this.utilityOperations.menu = [
|
|
504
|
+
...this.relationshipField && this.moduleOptions.canEdit
|
|
505
|
+
? [ newPiece ] : [],
|
|
506
|
+
...this.utilityOperations.menu,
|
|
507
|
+
...(!this.relationshipField && Array.isArray(utilityOperations) && utilityOperations) || []
|
|
508
|
+
];
|
|
379
509
|
}
|
|
380
510
|
}
|
|
381
511
|
};
|