apostrophe 3.53.0 → 3.55.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 (77) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/defaults.js +1 -0
  3. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +5 -2
  4. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +28 -19
  5. package/modules/@apostrophecms/any-doc-type/index.js +2 -2
  6. package/modules/@apostrophecms/any-page-type/index.js +2 -2
  7. package/modules/@apostrophecms/doc/index.js +55 -29
  8. package/modules/@apostrophecms/doc-type/index.js +11 -6
  9. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +4 -440
  10. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +445 -0
  11. package/modules/@apostrophecms/i18n/i18n/de.json +113 -105
  12. package/modules/@apostrophecms/i18n/i18n/es.json +10 -0
  13. package/modules/@apostrophecms/i18n/i18n/fr.json +8 -0
  14. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +10 -0
  15. package/modules/@apostrophecms/i18n/i18n/sk.json +8 -0
  16. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +1 -0
  17. package/modules/@apostrophecms/log/index.js +429 -0
  18. package/modules/@apostrophecms/login/index.js +47 -4
  19. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +14 -1
  20. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +1 -1
  21. package/modules/@apostrophecms/module/index.js +32 -6
  22. package/modules/@apostrophecms/module/lib/log.js +68 -0
  23. package/modules/@apostrophecms/page/index.js +71 -19
  24. package/modules/@apostrophecms/page/lib/legacy-migrations.js +0 -57
  25. package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +8 -285
  26. package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +291 -0
  27. package/modules/@apostrophecms/page-type/index.js +39 -26
  28. package/modules/@apostrophecms/piece-type/index.js +19 -11
  29. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
  30. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +2 -357
  31. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +2 -86
  32. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +2 -254
  33. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +2 -77
  34. package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +2 -44
  35. package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +2 -64
  36. package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +2 -94
  37. package/modules/@apostrophecms/schema/ui/apos/components/AposInputDateAndTime.vue +3 -47
  38. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +2 -82
  39. package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +2 -37
  40. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +2 -26
  41. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +2 -57
  42. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +2 -259
  43. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +2 -38
  44. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +2 -275
  45. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +2 -167
  46. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +2 -115
  47. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +3 -279
  48. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +2 -83
  49. package/modules/@apostrophecms/schema/ui/apos/lib/detectChange.js +10 -1
  50. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +361 -0
  51. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +89 -0
  52. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +257 -0
  53. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputAttachment.js +81 -0
  54. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputBoolean.js +48 -0
  55. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputCheckboxes.js +68 -0
  56. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +98 -0
  57. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +49 -0
  58. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +86 -0
  59. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputPassword.js +41 -0
  60. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +29 -0
  61. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRange.js +60 -0
  62. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +262 -0
  63. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSelect.js +41 -0
  64. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +278 -0
  65. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputString.js +170 -0
  66. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +118 -0
  67. package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +281 -0
  68. package/modules/@apostrophecms/schema/ui/apos/logic/AposSearchList.js +85 -0
  69. package/modules/@apostrophecms/template/index.js +1 -1
  70. package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +2 -2
  71. package/modules/@apostrophecms/util/index.js +83 -13
  72. package/modules/@apostrophecms/util/lib/logger.js +19 -17
  73. package/package.json +1 -1
  74. package/test/docs.js +35 -2
  75. package/test/log.js +1765 -0
  76. package/test/pages.js +57 -0
  77. package/test-lib/util.js +1 -1
@@ -0,0 +1,68 @@
1
+ // Per module log handlers.
2
+ // Usage (same arguments for all log handlers):
3
+ // ```js
4
+ // self.logError('event-type');
5
+ // self.logError('event-type', { key: 'value' });
6
+ // self.logError('event-type', 'some message');
7
+ // self.logError('event-type', 'some message', { key: 'value' });
8
+ // Prepend `req` followed by any of the above argument variations.
9
+ // self.logError(req, ...);
10
+ // ```
11
+ //
12
+ // Event type is required and can be any string.
13
+ // If `req` is provided, the `data` object argument will be enriched with
14
+ // additional information from the request.
15
+ // Example:
16
+ // self.logError('event-type', 'some message', { key: 'value' });
17
+ // will log:
18
+ // 'current-module-name: event-type: some message',
19
+ // {
20
+ // type: 'event-type',
21
+ // severity: 'error',
22
+ // module: 'current-module-name',
23
+ // key: 'value',
24
+ // }
25
+ // If the option `messageAs` of `@apostrophecms/log` is set to 'msg',
26
+ // the result of the above log entry will be:
27
+ // {
28
+ // type: 'event-type',
29
+ // severity: 'error',
30
+ // module: 'current-module-name',
31
+ // key: 'value',
32
+ // msg: 'current-module-name: event-type: some message',
33
+ // }
34
+ //
35
+ // If `filter` option is set, the log entry will be logged only if the
36
+ // `severity` or `eventType` match any filter. For more information about
37
+ // filters see `@apostrophecms/log` module.
38
+ module.exports = function (self) {
39
+ const exception = new Error(
40
+ `Structured logging is not available for module "${self.__meta.name}".`
41
+ );
42
+ return {
43
+ logDebug(...args) {
44
+ if (!self.__structuredLoggingEnabled) {
45
+ throw exception;
46
+ }
47
+ self.apos.structuredLog.logEntry(self, 'debug', ...args);
48
+ },
49
+ logInfo(...args) {
50
+ if (!self.__structuredLoggingEnabled) {
51
+ throw exception;
52
+ }
53
+ self.apos.structuredLog.logEntry(self, 'info', ...args);
54
+ },
55
+ logWarn(...args) {
56
+ if (!self.__structuredLoggingEnabled) {
57
+ throw exception;
58
+ }
59
+ self.apos.structuredLog.logEntry(self, 'warn', ...args);
60
+ },
61
+ logError(...args) {
62
+ if (!self.__structuredLoggingEnabled) {
63
+ throw exception;
64
+ }
65
+ self.apos.structuredLog.logEntry(self, 'error', ...args);
66
+ }
67
+ };
68
+ };
@@ -5,7 +5,7 @@ const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
5
5
  const expressCacheOnDemand = require('express-cache-on-demand')();
6
6
 
7
7
  module.exports = {
8
- cascades: [ 'batchOperations' ],
8
+ cascades: [ 'batchOperations', 'utilityOperations' ],
9
9
  options: {
10
10
  alias: 'page',
11
11
  types: [
@@ -127,7 +127,7 @@ module.exports = {
127
127
  getAll: [
128
128
  ...self.enableCacheOnDemand ? [ expressCacheOnDemand ] : [],
129
129
  async (req) => {
130
- self.publicApiCheck(req);
130
+ await self.publicApiCheckAsync(req);
131
131
  const all = self.apos.launder.boolean(req.query.all);
132
132
  const archived = self.apos.launder.booleanOrNull(req.query.archived);
133
133
  const flat = self.apos.launder.boolean(req.query.flat);
@@ -215,7 +215,7 @@ module.exports = {
215
215
  async (req, _id) => {
216
216
  _id = self.inferIdLocaleAndMode(req, _id);
217
217
  // Edit access to draft is sufficient to fetch either
218
- self.publicApiCheck(req);
218
+ await self.publicApiCheckAsync(req);
219
219
  const criteria = self.getIdCriteria(_id);
220
220
  const result = await self.getRestQuery(req).permission(false).and(criteria).toObject();
221
221
 
@@ -253,7 +253,7 @@ module.exports = {
253
253
  //
254
254
  // This call is atomic with respect to other REST write operations on pages.
255
255
  async post(req) {
256
- self.publicApiCheck(req);
256
+ await self.publicApiCheckAsync(req);
257
257
  let targetId = self.apos.launder.string(req.body._targetId);
258
258
  let position = self.apos.launder.string(req.body._position || 'lastChild');
259
259
  // Here we have to normalize before calling insert because we
@@ -338,7 +338,7 @@ module.exports = {
338
338
 
339
339
  async put(req, _id) {
340
340
  _id = self.inferIdLocaleAndMode(req, _id);
341
- self.publicApiCheck(req);
341
+ await self.publicApiCheckAsync(req);
342
342
 
343
343
  return self.withLock(req, async () => {
344
344
  const page = await self.findForEditing(req, { _id }).toObject();
@@ -385,7 +385,7 @@ module.exports = {
385
385
  },
386
386
  async delete(req, _id) {
387
387
  _id = self.inferIdLocaleAndMode(req, _id);
388
- self.publicApiCheck(req);
388
+ await self.publicApiCheckAsync(req);
389
389
  const page = await self.findOneForEditing(req, {
390
390
  _id
391
391
  });
@@ -396,9 +396,9 @@ module.exports = {
396
396
  // You may pass `_targetId` and `_position` to move the page within the tree. `_position`
397
397
  // may be `before`, `after` or `inside`. To move a page into or out of the archive, set
398
398
  // `archived` to `true` or `false`.
399
- patch(req, _id) {
399
+ async patch(req, _id) {
400
400
  _id = self.inferIdLocaleAndMode(req, _id);
401
- self.publicApiCheck(req);
401
+ await self.publicApiCheckAsync(req);
402
402
  return self.patch(req, _id);
403
403
  }
404
404
  };
@@ -659,6 +659,13 @@ database.`);
659
659
  };
660
660
  }
661
661
  }
662
+ },
663
+ composeUtilityOperations() {
664
+ self.utilityOperations = Object.entries(self.utilityOperations || {})
665
+ .map(([ action, properties ]) => ({
666
+ action,
667
+ ...properties
668
+ }));
662
669
  }
663
670
  },
664
671
  'apostrophe:ready': {
@@ -830,6 +837,7 @@ database.`);
830
837
  browserOptions.localized &&
831
838
  Object.keys(self.apos.i18n.locales).length > 1 &&
832
839
  Object.values(self.apos.i18n.locales).some(locale => locale._edit);
840
+ browserOptions.utilityOperations = self.utilityOperations;
833
841
  return browserOptions;
834
842
  },
835
843
  // Returns a query that finds pages the current user can edit
@@ -864,8 +872,6 @@ database.`);
864
872
  position = normalized.position;
865
873
  return self.withLock(req, async () => {
866
874
  let peers;
867
- page.aposLastTargetId = targetId;
868
- page.aposLastPosition = position;
869
875
  const target = await self.getTarget(req, targetId, position);
870
876
  if (!target) {
871
877
  throw self.apos.error('notfound');
@@ -1203,8 +1209,6 @@ database.`);
1203
1209
  }
1204
1210
  moved.level = level;
1205
1211
  moved.rank = rank;
1206
- moved.aposLastTargetId = targetId;
1207
- moved.aposLastPosition = position;
1208
1212
  // Are we in the archive? Our new parent reveals that
1209
1213
  if (parent.archived) {
1210
1214
  moved.archived = true;
@@ -1222,8 +1226,11 @@ database.`);
1222
1226
  // value. `position` is used to prevent attempts to move after the archive
1223
1227
  // "page."
1224
1228
  async getTarget(req, targetId, position) {
1225
- const criteria = self.getIdCriteria(targetId);
1226
- const target = await self.find(req, criteria).permission(false).archived(null).areas(false).ancestors({
1229
+ const criteria = self.getIdCriteria(self.inferIdLocaleAndMode(req, targetId));
1230
+ // Use findForEditing to ensure we get improvements to that method from
1231
+ // npm modules that make the query more inclusive. Then explicitly shut off
1232
+ // things we know we don't want to be blocked by
1233
+ const target = await self.findForEditing(req, criteria).permission(false).archived(null).areas(false).ancestors({
1227
1234
  depth: 1,
1228
1235
  archived: null,
1229
1236
  orphan: null,
@@ -2053,9 +2060,7 @@ database.`);
2053
2060
  page.rank = rank;
2054
2061
  const $set = {
2055
2062
  path: page.path,
2056
- rank: page.rank,
2057
- aposLastTargetId: home.aposDocId,
2058
- aposLastPosition: 'lastChild'
2063
+ rank: page.rank
2059
2064
  };
2060
2065
  if (argv['new-slug']) {
2061
2066
  $set.slug = argv['new-slug'];
@@ -2275,7 +2280,10 @@ database.`);
2275
2280
  return self.apos.doc.getManager(page.type)
2276
2281
  .allowedSchema(req, page, parentPage);
2277
2282
  },
2278
- getRestQuery(req) {
2283
+ // Can be extended on a project level with `_super(req, true)` to disable
2284
+ // permission check and public API projection. You shouldn't do this
2285
+ // if you're not sure what you're doing.
2286
+ getRestQuery(req, omitPermissionCheck = false) {
2279
2287
  const query = self.find(req)
2280
2288
  .ancestors(true)
2281
2289
  .children(true)
@@ -2283,7 +2291,7 @@ database.`);
2283
2291
  .applyBuildersSafely(req.query);
2284
2292
  // Minimum standard for a REST query without a public projection
2285
2293
  // is being allowed to view drafts on the site
2286
- if (!self.canAccessApi(req)) {
2294
+ if (!omitPermissionCheck && !self.canAccessApi(req)) {
2287
2295
  if (!self.options.publicApiProjection) {
2288
2296
  // Shouldn't be needed thanks to publicApiCheck, but be sure
2289
2297
  query.and({
@@ -2329,6 +2337,11 @@ database.`);
2329
2337
  }
2330
2338
  }
2331
2339
  },
2340
+ // An async version of the above. It can be overridden to implement
2341
+ // an asynchronous check of the public API permissions.
2342
+ async publicApiCheckAsync(req) {
2343
+ return self.publicApiCheck(req);
2344
+ },
2332
2345
  getAllProjection() {
2333
2346
  return {
2334
2347
  _url: 1,
@@ -2489,6 +2502,45 @@ database.`);
2489
2502
  await self.apos.attachment.recomputeAllDocReferences();
2490
2503
  }
2491
2504
  });
2505
+ },
2506
+ async inferLastTargetIdAndPosition(doc) {
2507
+ const parentPath = self.getParentPath(doc);
2508
+ const parentAposDocId = parentPath.split('/').pop();
2509
+ const parentId = doc.aposLocale
2510
+ ? `${parentAposDocId}:${doc.aposLocale}`
2511
+ : parentAposDocId;
2512
+ const peerCriteria = {
2513
+ path: self.matchDescendants(parentPath),
2514
+ level: doc.level
2515
+ };
2516
+ if (doc.aposLocale) {
2517
+ peerCriteria.aposLocale = doc.aposLocale;
2518
+ }
2519
+ const peers = await self.apos.doc.db.find(peerCriteria).sort({
2520
+ rank: 1
2521
+ }).project({
2522
+ _id: 1
2523
+ }).toArray();
2524
+ let targetId;
2525
+ let position;
2526
+ const index = peers.findIndex(peer => peer._id === doc._id);
2527
+ if (index === -1) {
2528
+ throw new Error('Cannot find page among its peers');
2529
+ }
2530
+ if (index === 0) {
2531
+ targetId = parentId;
2532
+ position = 'firstChild';
2533
+ } else if (index === (peers.length - 1)) {
2534
+ targetId = parentId;
2535
+ position = 'lastChild';
2536
+ } else {
2537
+ targetId = peers[index - 1]._id;
2538
+ position = 'after';
2539
+ }
2540
+ return {
2541
+ lastTargetId: targetId,
2542
+ lastPosition: position
2543
+ };
2492
2544
  }
2493
2545
  };
2494
2546
  },
@@ -6,7 +6,6 @@ module.exports = (self) => {
6
6
  addLegacyMigrations() {
7
7
  self.addDeduplicateRanksMigration();
8
8
  self.addFixHomePagePathMigration();
9
- self.addMissingLastTargetIdAndPositionMigration();
10
9
  self.addArchivedMigration();
11
10
  },
12
11
  addArchivedMigration() {
@@ -100,62 +99,6 @@ module.exports = (self) => {
100
99
  }
101
100
  });
102
101
  });
103
- },
104
- addMissingLastTargetIdAndPositionMigration() {
105
- self.apos.migration.add('missing-last-target-id-and-position', async () => {
106
- await self.apos.migration.eachDoc({
107
- slug: /^\//,
108
- // Home page should not have them, that's OK
109
- level: {
110
- $gte: 0
111
- },
112
- aposLastTargetId: {
113
- $exists: 0
114
- }
115
- }, 5, async (doc) => {
116
- const parentPath = self.getParentPath(doc);
117
- const parentAposDocId = parentPath.split('/').pop();
118
- let parentId;
119
- if (doc.aposLocale) {
120
- parentId = `${parentAposDocId}:${doc.aposLocale}`;
121
- } else {
122
- parentId = parentAposDocId;
123
- }
124
- const peerCriteria = {
125
- path: self.matchDescendants(parentPath),
126
- level: doc.level
127
- };
128
- if (doc.aposLocale) {
129
- peerCriteria.aposLocale = doc.aposLocale;
130
- }
131
- const peers = await self.apos.doc.db.find(peerCriteria).sort({
132
- rank: 1
133
- }).project({
134
- _id: 1
135
- }).toArray();
136
- let targetId;
137
- let position;
138
- const index = peers.findIndex(peer => peer._id === doc._id);
139
- if (index === -1) {
140
- return;
141
- }
142
- if (index === 0) {
143
- targetId = parentId;
144
- position = 'firstChild';
145
- } else {
146
- targetId = peers[index - 1]._id;
147
- position = 'after';
148
- }
149
- return self.apos.doc.db.updateOne({
150
- _id: doc._id
151
- }, {
152
- $set: {
153
- aposLastTargetId: targetId,
154
- aposLastPosition: position
155
- }
156
- });
157
- });
158
- });
159
102
  }
160
103
  };
161
104
  };
@@ -20,6 +20,10 @@
20
20
  />
21
21
  </template>
22
22
  <template #primaryControls>
23
+ <AposUtilityOperations
24
+ :module-options="moduleOptions"
25
+ :has-relationship-field="!!relationshipField"
26
+ />
23
27
  <AposContextMenu
24
28
  v-if="relationshipField"
25
29
  :menu="moreMenu"
@@ -86,294 +90,13 @@
86
90
  </template>
87
91
 
88
92
  <script>
89
- import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
90
- import AposArchiveMixin from 'Modules/@apostrophecms/ui/mixins/AposArchiveMixin';
91
- import AposPublishMixin from 'Modules/@apostrophecms/ui/mixins/AposPublishMixin';
92
- import AposDocsManagerMixin from 'Modules/@apostrophecms/modal/mixins/AposDocsManagerMixin';
93
- import { klona } from 'klona';
93
+ import AposPagesManagerLogic from 'Modules/@apostrophecms/page/logic/AposPagesManager';
94
94
 
95
95
  export default {
96
96
  name: 'AposPagesManager',
97
- mixins: [ AposModifiedMixin, AposDocsManagerMixin, AposArchiveMixin, AposPublishMixin ],
98
- emits: [ 'archive', 'search', 'safe-close', 'modal-result' ],
99
- data() {
100
-
101
- return {
102
- moduleName: '@apostrophecms/page',
103
- modal: {
104
- active: false,
105
- triggerFocusRefresh: 0,
106
- type: 'slide',
107
- showModal: false,
108
- width: 'two-thirds'
109
- },
110
- pages: [],
111
- pagesFlat: [],
112
- options: {
113
- columns: [
114
- {
115
- columnHeader: 'apostrophe:pageTitle',
116
- property: 'title',
117
- cellValue: 'title'
118
- },
119
- {
120
- name: 'labels',
121
- component: 'AposCellLabels'
122
- },
123
- {
124
- columnHeader: 'apostrophe:lastEdited',
125
- property: 'updatedAt',
126
- component: 'AposCellLastEdited',
127
- cellValue: 'updatedAt'
128
- },
129
- {
130
- property: 'contextMenu',
131
- component: 'AposCellContextMenu'
132
- }
133
- ]
134
- },
135
- treeOptions: {
136
- bulkSelect: !!this.relationshipField,
137
- draggable: true,
138
- ghostUnpublished: true
139
- },
140
- moreMenu: [
141
- {
142
- label: 'New Page',
143
- action: 'new'
144
- }
145
- ],
146
- moreMenuButton: {
147
- tooltip: {
148
- content: 'More Options',
149
- placement: 'bottom'
150
- },
151
- label: 'More Options',
152
- icon: 'dots-vertical-icon',
153
- iconOnly: true,
154
- type: 'subtle',
155
- modifiers: [ 'small', 'no-motion' ]
156
- },
157
- pageSetMenuSelection: 'live'
158
- };
159
- },
160
- computed: {
161
- moduleOptions() {
162
- return apos.page;
163
- },
164
- items() {
165
- if (!this.pages || !this.headers.length) {
166
- return [];
167
- }
168
- return klona(this.pages);
169
- },
170
- selectAllChoice() {
171
- const checkLen = this.checked.length;
172
- const rowLen = this.pagesFlat.length;
173
-
174
- return checkLen > 0 && checkLen !== rowLen ? {
175
- value: 'checked',
176
- indeterminate: true
177
- } : {
178
- value: 'checked'
179
- };
180
- },
181
- saveRelationshipLabel() {
182
- if (this.relationshipField && (this.relationshipField.max === 1)) {
183
- return 'Select Page';
184
- } else {
185
- return 'Select Pages';
186
- }
187
- },
188
- headers() {
189
- let headers = this.options.columns || [];
190
- if (!this.pageSetMenuSelectionIsLive) {
191
- headers = headers.filter(h => h.component !== 'AposCellLabels');
192
- }
193
- return headers;
194
- },
195
- pageSetMenu() {
196
- return [ {
197
- label: 'apostrophe:live',
198
- action: 'live',
199
- modifiers: this.pageSetMenuSelectionIsLive ? [ 'selected', 'disabled' ] : []
200
- }, {
201
- label: 'apostrophe:archived',
202
- action: 'archive',
203
- modifiers: !this.pageSetMenuSelectionIsLive ? [ 'selected', 'disabled' ] : []
204
- } ];
205
- },
206
- pageSetMenuButton() {
207
- const button = {
208
- label: this.pageSetMenuSelectionIsLive ? 'apostrophe:live' : 'apostrophe:archived',
209
- icon: 'chevron-down-icon',
210
- modifiers: [ 'no-motion', 'outline', 'icon-right' ],
211
- class: 'apos-pages-manager__page-set-menu-button'
212
- };
213
- return button;
214
- },
215
- pageSetMenuSelectionIsLive() {
216
- return this.pageSetMenuSelection === 'live';
217
- }
218
- },
219
- watch: {
220
- async pageSetMenuSelection() {
221
- await this.getPages();
222
- }
223
- },
224
- async mounted() {
225
- // Get the data. This will be more complex in actuality.
226
- this.modal.active = true;
227
- await this.getPages();
228
- this.modal.triggerFocusRefresh++;
229
-
230
- apos.bus.$on('content-changed', this.getPages);
231
- apos.bus.$on('command-menu-manager-create-new', this.create);
232
- apos.bus.$on('command-menu-manager-close', this.confirmAndCancel);
233
- },
234
- destroyed() {
235
- apos.bus.$off('content-changed', this.getPages);
236
- apos.bus.$off('command-menu-manager-create-new', this.create);
237
- apos.bus.$off('command-menu-manager-close', this.confirmAndCancel);
238
- },
239
- methods: {
240
- moreMenuHandler(action) {
241
- if (action === 'new') {
242
- this.create();
243
- }
244
- },
245
- async getPages () {
246
- const self = this;
247
- if (this.gettingPages) {
248
- // Avoid race conditions by trying again later if already in progress
249
- setTimeout(this.getPages, 100);
250
- return;
251
- }
252
- // Not reactive, so not in data()
253
- this.gettingPages = true;
254
- try {
255
- this.pages = [];
256
- this.pagesFlat = [];
257
-
258
- let pageTree = (await apos.http.get(
259
- '/api/v1/@apostrophecms/page', {
260
- busy: true,
261
- qs: {
262
- all: '1',
263
- archived: this.relationshipField || this.pageSetMenuSelectionIsLive ? '0' : 'any',
264
- // Also fetch published docs as _publishedDoc subproperties
265
- withPublished: 1
266
- },
267
- draft: true
268
- }
269
- ));
270
-
271
- // If editor is looking at the archive tree, trim the normal page tree response
272
- if (this.pageSetMenuSelection === 'archive') {
273
- pageTree = pageTree._children.find(page => page.slug === '/archive');
274
- pageTree = pageTree._children;
275
- }
276
-
277
- formatPage(pageTree);
278
-
279
- if (!pageTree.length && pageTree.length !== 0) {
280
- pageTree = [ pageTree ];
281
- }
282
-
283
- this.pages = [ ...pageTree ];
284
-
285
- } finally {
286
- this.gettingPages = false;
287
- }
288
-
289
- function formatPage(page) {
290
- if (page.length) {
291
- page.forEach(formatPage);
292
- return;
293
- }
294
-
295
- self.pagesFlat.push(klona(page));
296
-
297
- if (Array.isArray(page._children)) {
298
- page._children.forEach(formatPage);
299
- }
300
- }
301
- },
302
- async update(page) {
303
- const body = {
304
- _targetId: page.endContext,
305
- _position: page.endIndex
306
- };
307
-
308
- const route = `${this.moduleOptions.action}/${page.changedId}`;
309
- try {
310
- await apos.http.patch(route, {
311
- busy: true,
312
- body,
313
- draft: true
314
- });
315
- } catch (error) {
316
- await apos.notify(error.body.message || this.$t('apostrophe:treeError'), {
317
- type: 'danger',
318
- icon: 'alert-circle-icon',
319
- dismiss: true,
320
- localize: false
321
- });
322
- }
323
-
324
- await this.getPages();
325
- if (this.pagesFlat.find(page => {
326
- return (page.aposDocId === (window.apos.page.page && window.apos.page.page.aposDocId)) && page.archived;
327
- })) {
328
- // With the current page gone, we need to move to safe ground
329
- location.assign(`${window.apos.prefix}/`);
330
- }
331
- },
332
- toggleRowCheck(id) {
333
- if (this.checked.includes(id)) {
334
- this.checked = this.checked.filter(item => item !== id);
335
- } else {
336
- this.checked.push(id);
337
- }
338
- },
339
- selectAll(event) {
340
- if (!this.checked.length) {
341
- this.pagesFlat.forEach((row) => {
342
- this.toggleRowCheck(row._id);
343
- });
344
- return;
345
- }
346
-
347
- if (this.checked.length <= this.pagesFlat.length) {
348
- this.checked.forEach((id) => {
349
- this.toggleRowCheck(id);
350
- });
351
- }
352
- },
353
- async create() {
354
- const doc = await apos.modal.execute(this.moduleOptions.components.editorModal, {
355
- moduleName: this.moduleName
356
- });
357
- if (!doc) {
358
- // Cancel clicked
359
- return;
360
- }
361
- await this.getPages();
362
- if (this.relationshipField) {
363
- doc._fields = doc._fields || {};
364
- // Must push to checked docs or it will try to do it for us
365
- // and not include _fields
366
- this.checkedDocs.push(doc);
367
- this.checked.push(doc._id);
368
- }
369
- },
370
- setCheckedDocs(checkedDocs) {
371
- this.checked = checkedDocs.map(doc => doc._id);
372
- },
373
- updateCheckedDocs() {
374
- this.checkedDocs = this.checked.map(_id => this.pagesFlat.find(page => page._id === _id));
375
- }
376
- }
97
+ mixins: [ AposPagesManagerLogic ],
98
+ // Keep it for linting
99
+ emits: [ 'archive', 'search', 'safe-close', 'modal-result' ]
377
100
  };
378
101
  </script>
379
102