apostrophe 3.59.1 → 3.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/modules/@apostrophecms/area/index.js +1 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +1 -1
- package/modules/@apostrophecms/doc/index.js +11 -2
- package/modules/@apostrophecms/job/index.js +1 -0
- package/modules/@apostrophecms/module/index.js +10 -4
- package/modules/@apostrophecms/notification/index.js +4 -1
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +9 -0
- package/modules/@apostrophecms/page/index.js +83 -3
- package/modules/@apostrophecms/rich-text-widget/index.js +5 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +3 -3
- package/modules/@apostrophecms/template/index.js +35 -28
- package/package.json +1 -1
- package/test/external-front.js +43 -0
- package/test/pages-rest.js +15 -9
- package/test/published-pages.js +144 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.60.0 (2023-11-29)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Add the possibility to add custom classes to notifications.
|
|
8
|
+
Setting the `apos-notification--hidden` class will hide the notification, which can be useful when we only care about the event carried by it.
|
|
9
|
+
* Give the possibility to add horizontal rules from the insert menu of the rich text editor with the following widget option: `insert: [ 'horizontalRule' ]`.
|
|
10
|
+
Improve also the UX to focus back the editor after inserting a horizontal rule or a table.
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
* The `render-widget` route now provides an `options` property on the widget, so that
|
|
15
|
+
schema-level options of the widget are available to the external front end when
|
|
16
|
+
rendering a newly added or edited widget in the editor. Note that when rendering a full page,
|
|
17
|
+
this information is already available on the parent area: `area.options.widgets[widget.type]`
|
|
18
|
+
* Pages inserted directly in the published mode are now given a
|
|
19
|
+
correct `lastPublishedAt` property, correcting several bugs relating
|
|
20
|
+
to the page tree.
|
|
21
|
+
* A migration has been added to introduce `lastPublishedAt` wherever
|
|
22
|
+
it is missing for existing pages.
|
|
23
|
+
* Fixed a bug that prevented page ranks from renumbering properly during "insert after" operations.
|
|
24
|
+
* Added a one-time migration to make existing page ranks unique among peers.
|
|
25
|
+
* Fixes conditional fields not being properly updated when switching items in array editor.
|
|
26
|
+
* The `beforeSend` event for pages and the loading of deferred widgets are now
|
|
27
|
+
handled in `renderPage` with the proper timing so that areas can be annotated
|
|
28
|
+
successfully for "external front" use.
|
|
29
|
+
* The external front now receives 100% of the serialization-friendly data that Nunjucks receives,
|
|
30
|
+
including the `home` property etc. Note that the responsibility to avoid passing any nonserializable
|
|
31
|
+
or excessively large data in `req.data` falls on the developer when choosing to use the
|
|
32
|
+
`apos-external-front` feature.
|
|
33
|
+
* Wraps the group label in the expanded preview menu component in `$t()` to allow translation
|
|
34
|
+
|
|
3
35
|
## 3.59.1 (2023-11-14)
|
|
4
36
|
|
|
5
37
|
### Fixes
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
:key="groupIndex"
|
|
17
17
|
class="apos-widget-group"
|
|
18
18
|
>
|
|
19
|
-
<h2 class="apos-widget-group__label" v-if="group.label">{{ group.label }}</h2>
|
|
19
|
+
<h2 class="apos-widget-group__label" v-if="group.label">{{ $t(group.label) }}</h2>
|
|
20
20
|
<div
|
|
21
21
|
:class="[
|
|
22
22
|
`apos-widget-group--${group.columns}-column${
|
|
@@ -239,13 +239,22 @@ module.exports = {
|
|
|
239
239
|
})) {
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
|
+
const lastPublishedAt = doc.createdAt || new Date();
|
|
242
243
|
const draft = {
|
|
243
244
|
...doc,
|
|
244
245
|
_id: draftId,
|
|
245
246
|
aposLocale: draftLocale,
|
|
246
|
-
lastPublishedAt
|
|
247
|
+
lastPublishedAt
|
|
247
248
|
};
|
|
248
|
-
|
|
249
|
+
await manager.insertDraftOf(req, doc, draft, options);
|
|
250
|
+
// Published doc must know it is published, otherwise various bugs ensue
|
|
251
|
+
return self.apos.doc.db.updateOne({
|
|
252
|
+
_id: doc._id
|
|
253
|
+
}, {
|
|
254
|
+
$set: {
|
|
255
|
+
lastPublishedAt
|
|
256
|
+
}
|
|
257
|
+
});
|
|
249
258
|
}
|
|
250
259
|
},
|
|
251
260
|
fixUniqueError: {
|
|
@@ -379,8 +379,8 @@ module.exports = {
|
|
|
379
379
|
return self.apos.template.renderStringForModule(req, s, data, self);
|
|
380
380
|
},
|
|
381
381
|
|
|
382
|
-
// TIP: you
|
|
383
|
-
//
|
|
382
|
+
// TIP: more often you will want `self.sendPage`, which also sends the response
|
|
383
|
+
// to the browser.
|
|
384
384
|
//
|
|
385
385
|
// This method generates a complete HTML page for transmission to the
|
|
386
386
|
// browser. Returns HTML markup ready to send (but `self.sendPage` is
|
|
@@ -419,11 +419,19 @@ module.exports = {
|
|
|
419
419
|
//
|
|
420
420
|
// This method is async in 3.x and must be awaited.
|
|
421
421
|
//
|
|
422
|
+
// If the external front feature is in use for the request, then
|
|
423
|
+
// self.apos.template.annotateDataForExternalFront and
|
|
424
|
+
// self.apos.template.pruneDataForExternalFront are called
|
|
425
|
+
// and the data is returned, in place of normal Nunjucks rendering.
|
|
426
|
+
//
|
|
422
427
|
// No longer deprecated because it is a useful override point
|
|
423
428
|
// for this part of the behavior of sendPage.
|
|
424
429
|
|
|
425
430
|
async renderPage(req, template, data) {
|
|
431
|
+
await self.apos.page.emit('beforeSend', req);
|
|
432
|
+
await self.apos.area.loadDeferredWidgets(req);
|
|
426
433
|
if (req.aposExternalFront) {
|
|
434
|
+
data = self.apos.template.getRenderDataArgs(req, data, self);
|
|
427
435
|
await self.apos.template.annotateDataForExternalFront(req, template, data);
|
|
428
436
|
self.apos.template.pruneDataForExternalFront(req, template, data);
|
|
429
437
|
// Reply with JSON
|
|
@@ -484,8 +492,6 @@ module.exports = {
|
|
|
484
492
|
span.setAttribute(telemetry.Attributes.TEMPLATE, template);
|
|
485
493
|
|
|
486
494
|
try {
|
|
487
|
-
await self.apos.page.emit('beforeSend', req);
|
|
488
|
-
await self.apos.area.loadDeferredWidgets(req);
|
|
489
495
|
const result = await self.renderPage(req, template, data);
|
|
490
496
|
req.res.send(result);
|
|
491
497
|
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
@@ -99,6 +99,7 @@ module.exports = {
|
|
|
99
99
|
], 'info');
|
|
100
100
|
const icon = self.apos.launder.string(req.body.icon);
|
|
101
101
|
const message = self.apos.launder.string(req.body.message);
|
|
102
|
+
const classes = self.apos.launder.strings(req.body.classes);
|
|
102
103
|
const interpolate = launderInterpolate(req.body.interpolate);
|
|
103
104
|
const dismiss = self.apos.launder.integer(req.body.dismiss);
|
|
104
105
|
let buttons = req.body.buttons;
|
|
@@ -118,6 +119,7 @@ module.exports = {
|
|
|
118
119
|
}));
|
|
119
120
|
}
|
|
120
121
|
return self.trigger(req, message, {
|
|
122
|
+
classes,
|
|
121
123
|
interpolate,
|
|
122
124
|
dismiss,
|
|
123
125
|
icon,
|
|
@@ -280,7 +282,8 @@ module.exports = {
|
|
|
280
282
|
localize: has(req.body, 'localize')
|
|
281
283
|
? self.apos.launder.boolean(req.body.localize) : true,
|
|
282
284
|
job: options.job || null,
|
|
283
|
-
event: options.event
|
|
285
|
+
event: options.event,
|
|
286
|
+
classes: options.classes || null
|
|
284
287
|
};
|
|
285
288
|
|
|
286
289
|
if (copiedOptions.dismiss === true) {
|
|
@@ -79,6 +79,11 @@ export default {
|
|
|
79
79
|
computed: {
|
|
80
80
|
classList() {
|
|
81
81
|
const classes = [ 'apos-notification' ];
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(this.notification.classes) && this.notification.classes.length) {
|
|
84
|
+
classes.push(...this.notification.classes);
|
|
85
|
+
}
|
|
86
|
+
|
|
82
87
|
if (this.notification.type && this.notification.type !== 'none') {
|
|
83
88
|
classes.push(`apos-notification--${this.notification.type}`);
|
|
84
89
|
}
|
|
@@ -223,6 +228,10 @@ export default {
|
|
|
223
228
|
}
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
.apos-notification--hidden {
|
|
232
|
+
display: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
226
235
|
.apos-notification--long {
|
|
227
236
|
border-radius: 10px;
|
|
228
237
|
}
|
|
@@ -101,6 +101,8 @@ module.exports = {
|
|
|
101
101
|
self.addLegacyMigrations();
|
|
102
102
|
self.addMisreplicatedParkedPagesMigration();
|
|
103
103
|
self.addDuplicateParkedPagesMigration();
|
|
104
|
+
self.apos.migration.add('deduplicateRanks2', self.deduplicateRanks2Migration);
|
|
105
|
+
self.apos.migration.add('missingLastPublishedAt', self.missingLastPublishedAtMigration);
|
|
104
106
|
await self.createIndexes();
|
|
105
107
|
},
|
|
106
108
|
restApiRoutes(self) {
|
|
@@ -850,8 +852,8 @@ database.`);
|
|
|
850
852
|
const query = self.find(req, criteria, options).permission('edit').archived(null);
|
|
851
853
|
return query;
|
|
852
854
|
},
|
|
853
|
-
// Insert a page. `targetId` must be an existing page id,
|
|
854
|
-
// `position` may be `before`, `inside` or `after`. Alternatively
|
|
855
|
+
// Insert a page. `targetId` must be an existing page id, `_archive` or
|
|
856
|
+
// `_home`, and `position` may be `before`, `inside` or `after`. Alternatively
|
|
855
857
|
// `position` may be a zero-based offset for the new child
|
|
856
858
|
// of `targetId` (note that the `rank` property of sibling pages
|
|
857
859
|
// is not strictly ascending, so use an array index into `_children` to
|
|
@@ -930,7 +932,7 @@ database.`);
|
|
|
930
932
|
return self.insert(req, target._id, 'before', page, options);
|
|
931
933
|
}
|
|
932
934
|
page.rank = target.rank + 1;
|
|
933
|
-
const index = peers.findIndex(peer => peer.
|
|
935
|
+
const index = peers.findIndex(peer => peer._id === target._id);
|
|
934
936
|
if (index !== -1) {
|
|
935
937
|
pushed = peers.slice(index + 1).map(peer => peer._id);
|
|
936
938
|
}
|
|
@@ -2503,6 +2505,84 @@ database.`);
|
|
|
2503
2505
|
}
|
|
2504
2506
|
});
|
|
2505
2507
|
},
|
|
2508
|
+
async deduplicateRanks2Migration() {
|
|
2509
|
+
for (const locale of Object.keys(self.apos.i18n.locales)) {
|
|
2510
|
+
for (const mode of [ 'previous', 'draft', 'published' ]) {
|
|
2511
|
+
const pages = await self.apos.doc.db.find({
|
|
2512
|
+
slug: /^\//,
|
|
2513
|
+
aposLocale: `${locale}:${mode}`
|
|
2514
|
+
}, {
|
|
2515
|
+
path: 1,
|
|
2516
|
+
rank: 1,
|
|
2517
|
+
slug: 1
|
|
2518
|
+
}).toArray();
|
|
2519
|
+
const pagesByPath = new Map();
|
|
2520
|
+
for (const page of pages) {
|
|
2521
|
+
page._children = [];
|
|
2522
|
+
pagesByPath.set(page.path, page);
|
|
2523
|
+
}
|
|
2524
|
+
for (const page of pages) {
|
|
2525
|
+
if (page.level === 0) {
|
|
2526
|
+
// Home page has no parent
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
const parentPath = self.getParentPath(page);
|
|
2530
|
+
const parent = pagesByPath.get(parentPath);
|
|
2531
|
+
if (!parent) {
|
|
2532
|
+
self.apos.util.error(`Warning: page ${page._id} has no parent in the tree`);
|
|
2533
|
+
continue;
|
|
2534
|
+
}
|
|
2535
|
+
parent._children.push(page);
|
|
2536
|
+
}
|
|
2537
|
+
for (const page of pages) {
|
|
2538
|
+
const children = page._children;
|
|
2539
|
+
children.sort((a, b) => a.rank - b.rank);
|
|
2540
|
+
let lastRank = null;
|
|
2541
|
+
let bad = false;
|
|
2542
|
+
for (const child of children) {
|
|
2543
|
+
if (child.rank === lastRank) {
|
|
2544
|
+
bad = true;
|
|
2545
|
+
break;
|
|
2546
|
+
}
|
|
2547
|
+
lastRank = child.rank;
|
|
2548
|
+
}
|
|
2549
|
+
if (bad) {
|
|
2550
|
+
self.apos.util.warn(`Fixing ranks for children of ${page.slug} in ${page.aposLocale}`);
|
|
2551
|
+
for (let i = 0; (i < children.length); i++) {
|
|
2552
|
+
await self.apos.doc.db.updateOne({
|
|
2553
|
+
_id: children[i]._id
|
|
2554
|
+
}, {
|
|
2555
|
+
$set: {
|
|
2556
|
+
rank: i
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
},
|
|
2565
|
+
missingLastPublishedAtMigration() {
|
|
2566
|
+
return self.apos.migration.eachDoc({
|
|
2567
|
+
aposMode: 'published',
|
|
2568
|
+
lastPublishedAt: null
|
|
2569
|
+
}, async doc => {
|
|
2570
|
+
const draft = await self.apos.doc.db.findOne({
|
|
2571
|
+
_id: doc._id.replace(':published', ':draft')
|
|
2572
|
+
});
|
|
2573
|
+
if (!draft) {
|
|
2574
|
+
self.apos.util.error(`Warning: published document has no matching draft: ${doc._id}`);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
await self.apos.doc.db.updateOne({
|
|
2578
|
+
_id: doc._id
|
|
2579
|
+
}, {
|
|
2580
|
+
$set: {
|
|
2581
|
+
lastPublishedAt: draft.lastPublishedAt
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2584
|
+
});
|
|
2585
|
+
},
|
|
2506
2586
|
async inferLastTargetIdAndPosition(doc) {
|
|
2507
2587
|
const parentPath = self.getParentPath(doc);
|
|
2508
2588
|
const parentAposDocId = parentPath.split('/').pop();
|
|
@@ -220,6 +220,11 @@ module.exports = {
|
|
|
220
220
|
label: 'apostrophe:image',
|
|
221
221
|
description: 'apostrophe:imageDescription',
|
|
222
222
|
component: 'AposImageControlDialog'
|
|
223
|
+
},
|
|
224
|
+
horizontalRule: {
|
|
225
|
+
icon: 'minus-icon',
|
|
226
|
+
label: 'apostrophe:richTextHorizontalRule',
|
|
227
|
+
action: 'setHorizontalRule'
|
|
223
228
|
}
|
|
224
229
|
},
|
|
225
230
|
// Additional properties used in executing tiptap commands
|
|
@@ -138,9 +138,8 @@ export default {
|
|
|
138
138
|
async mounted() {
|
|
139
139
|
this.modal.active = true;
|
|
140
140
|
await this.evaluateExternalConditions();
|
|
141
|
-
this.evaluateConditions();
|
|
142
141
|
if (this.next.length) {
|
|
143
|
-
this.select(this.next[0]._id);
|
|
142
|
+
await this.select(this.next[0]._id);
|
|
144
143
|
}
|
|
145
144
|
if (this.serverError && this.serverError.data && this.serverError.data.errors) {
|
|
146
145
|
const first = this.serverError.data.errors[0];
|
|
@@ -168,6 +167,7 @@ export default {
|
|
|
168
167
|
hasErrors: false,
|
|
169
168
|
data: this.next.find(item => item._id === _id)
|
|
170
169
|
};
|
|
170
|
+
this.evaluateConditions();
|
|
171
171
|
this.triggerValidation = false;
|
|
172
172
|
}
|
|
173
173
|
},
|
|
@@ -193,7 +193,7 @@ export default {
|
|
|
193
193
|
const item = this.newInstance();
|
|
194
194
|
item._id = cuid();
|
|
195
195
|
this.next.push(item);
|
|
196
|
-
this.select(item._id);
|
|
196
|
+
await this.select(item._id);
|
|
197
197
|
this.updateMinMax();
|
|
198
198
|
}
|
|
199
199
|
},
|
|
@@ -276,35 +276,15 @@ module.exports = {
|
|
|
276
276
|
},
|
|
277
277
|
|
|
278
278
|
// Implementation detail of `renderBody` responsible for
|
|
279
|
-
// creating the input object passed to
|
|
280
|
-
//
|
|
281
|
-
// `apos`
|
|
279
|
+
// creating the input object passed to the template engine e.g. Nunjucks.
|
|
280
|
+
// Includes both serializable data like `user` and non-JSON-friendly
|
|
281
|
+
// properties like `apos`, `getOptions()` and `__req`. If you are only
|
|
282
|
+
// interested in serializable data use `getRenderDataArgs`
|
|
282
283
|
|
|
283
284
|
getRenderArgs(req, data, module) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
_.defaults(merged, data);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const args = {};
|
|
291
|
-
|
|
292
|
-
args.data = merged;
|
|
293
|
-
|
|
294
|
-
if (req.data) {
|
|
295
|
-
_.defaults(merged, req.data);
|
|
296
|
-
}
|
|
297
|
-
_.defaults(merged, {
|
|
298
|
-
user: req.user,
|
|
299
|
-
permissions: (req.user && req.user._permissions) || {}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
if (module.templateData) {
|
|
303
|
-
_.defaults(merged, module.templateData);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
args.data.locale = args.data.locale || req.locale;
|
|
307
|
-
|
|
285
|
+
const args = {
|
|
286
|
+
data: self.getRenderDataArgs(req, data, module)
|
|
287
|
+
};
|
|
308
288
|
args.apos = self.templateApos;
|
|
309
289
|
args.__t = req.t;
|
|
310
290
|
args.__ = key => {
|
|
@@ -328,6 +308,33 @@ module.exports = {
|
|
|
328
308
|
return args;
|
|
329
309
|
},
|
|
330
310
|
|
|
311
|
+
// Just the external front-compatible parts of `getRenderArgs` that
|
|
312
|
+
// go into `args.data` for Nunjucks, e.g. merging `req.data` and `data`, adding
|
|
313
|
+
// `req.user` as `user`, etc.
|
|
314
|
+
|
|
315
|
+
getRenderDataArgs(req, data, module) {
|
|
316
|
+
const merged = {};
|
|
317
|
+
|
|
318
|
+
if (data) {
|
|
319
|
+
_.defaults(merged, data);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (req.data) {
|
|
323
|
+
_.defaults(merged, req.data);
|
|
324
|
+
}
|
|
325
|
+
_.defaults(merged, {
|
|
326
|
+
user: req.user,
|
|
327
|
+
permissions: (req.user && req.user._permissions) || {}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
if (module.templateData) {
|
|
331
|
+
_.defaults(merged, module.templateData);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
merged.locale = merged.locale || req.locale;
|
|
335
|
+
return merged;
|
|
336
|
+
},
|
|
337
|
+
|
|
331
338
|
// Fetch a nunjucks environment in which `include`, `extends`, etc. search
|
|
332
339
|
// the views directories of the specified module and its ancestors.
|
|
333
340
|
// Typically you will call `self.render` on your module
|
|
@@ -897,7 +904,7 @@ module.exports = {
|
|
|
897
904
|
},
|
|
898
905
|
|
|
899
906
|
getDocsForExternalFront(req, template, data) {
|
|
900
|
-
return [ data.page, data.piece, ...(data.pieces || []) ].filter(doc => !!doc);
|
|
907
|
+
return [ data.home, ...(data.page?._ancestors || []), ...(data.page?._children || []), data.page, data.piece, ...(data.pieces || []) ].filter(doc => !!doc);
|
|
901
908
|
},
|
|
902
909
|
|
|
903
910
|
annotateDocForExternalFront(doc) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
let apos;
|
|
5
|
+
// Set env var so these tests work even if you have a dev key in your bashrc etc.
|
|
6
|
+
process.env.APOS_EXTERNAL_FRONT_KEY = 'this is a test external front key';
|
|
7
|
+
|
|
8
|
+
describe('External Front', function() {
|
|
9
|
+
|
|
10
|
+
this.timeout(t.timeout);
|
|
11
|
+
|
|
12
|
+
after(function() {
|
|
13
|
+
return t.destroy(apos);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('apostrophe should initialize normally', async function() {
|
|
17
|
+
apos = await t.create({
|
|
18
|
+
root: module
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
assert(apos.page.__meta.name === '@apostrophecms/page');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('fetch home with external front', async function() {
|
|
25
|
+
const data = await await apos.http.get('/', {
|
|
26
|
+
headers: {
|
|
27
|
+
'x-requested-with': 'AposExternalFront',
|
|
28
|
+
'apos-external-front-key': process.env.APOS_EXTERNAL_FRONT_KEY
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
assert.strictEqual(typeof data, 'object');
|
|
32
|
+
assert(data.page);
|
|
33
|
+
assert(data.home);
|
|
34
|
+
assert(data.page.slug === data.home.slug);
|
|
35
|
+
assert(data.page.slug === '/');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('fetch home normally', async function() {
|
|
39
|
+
const data = await await apos.http.get('/', {});
|
|
40
|
+
assert.strictEqual(typeof data, 'string');
|
|
41
|
+
assert(data.includes('Home Page Template'));
|
|
42
|
+
});
|
|
43
|
+
});
|
package/test/pages-rest.js
CHANGED
|
@@ -192,6 +192,7 @@ describe('Pages REST', function() {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
it('should be able to use db to insert documents', async function() {
|
|
195
|
+
const lastPublishedAt = new Date();
|
|
195
196
|
const testItems = [
|
|
196
197
|
{
|
|
197
198
|
_id: 'parent:en:published',
|
|
@@ -202,7 +203,8 @@ describe('Pages REST', function() {
|
|
|
202
203
|
visibility: 'public',
|
|
203
204
|
path: `${homeId.replace(':en:published', '')}/parent`,
|
|
204
205
|
level: 1,
|
|
205
|
-
rank: 0
|
|
206
|
+
rank: 0,
|
|
207
|
+
lastPublishedAt
|
|
206
208
|
},
|
|
207
209
|
{
|
|
208
210
|
_id: 'child:en:published',
|
|
@@ -213,7 +215,8 @@ describe('Pages REST', function() {
|
|
|
213
215
|
visibility: 'public',
|
|
214
216
|
path: `${homeId.replace(':en:published', '')}/parent/child`,
|
|
215
217
|
level: 2,
|
|
216
|
-
rank: 0
|
|
218
|
+
rank: 0,
|
|
219
|
+
lastPublishedAt
|
|
217
220
|
},
|
|
218
221
|
{
|
|
219
222
|
_id: 'grandchild:en:published',
|
|
@@ -224,7 +227,8 @@ describe('Pages REST', function() {
|
|
|
224
227
|
visibility: 'public',
|
|
225
228
|
path: `${homeId.replace(':en:published', '')}/parent/child/grandchild`,
|
|
226
229
|
level: 3,
|
|
227
|
-
rank: 0
|
|
230
|
+
rank: 0,
|
|
231
|
+
lastPublishedAt
|
|
228
232
|
},
|
|
229
233
|
{
|
|
230
234
|
_id: 'sibling:en:published',
|
|
@@ -235,8 +239,8 @@ describe('Pages REST', function() {
|
|
|
235
239
|
visibility: 'public',
|
|
236
240
|
path: `${homeId.replace(':en:published', '')}/parent/sibling`,
|
|
237
241
|
level: 2,
|
|
238
|
-
rank: 1
|
|
239
|
-
|
|
242
|
+
rank: 1,
|
|
243
|
+
lastPublishedAt
|
|
240
244
|
},
|
|
241
245
|
{
|
|
242
246
|
_id: 'cousin:en:published',
|
|
@@ -247,7 +251,8 @@ describe('Pages REST', function() {
|
|
|
247
251
|
visibility: 'public',
|
|
248
252
|
path: `${homeId.replace(':en:published', '')}/parent/sibling/cousin`,
|
|
249
253
|
level: 3,
|
|
250
|
-
rank: 0
|
|
254
|
+
rank: 0,
|
|
255
|
+
lastPublishedAt
|
|
251
256
|
},
|
|
252
257
|
{
|
|
253
258
|
_id: 'another-parent:en:published',
|
|
@@ -258,7 +263,8 @@ describe('Pages REST', function() {
|
|
|
258
263
|
visibility: 'public',
|
|
259
264
|
path: `${homeId.replace(':en:published', '')}/another-parent`,
|
|
260
265
|
level: 1,
|
|
261
|
-
rank: 1
|
|
266
|
+
rank: 1,
|
|
267
|
+
lastPublishedAt
|
|
262
268
|
},
|
|
263
269
|
{
|
|
264
270
|
_id: 'neighbor:en:published',
|
|
@@ -269,7 +275,8 @@ describe('Pages REST', function() {
|
|
|
269
275
|
visibility: 'public',
|
|
270
276
|
path: `${homeId.replace(':en:published', '')}/neighbor`,
|
|
271
277
|
level: 1,
|
|
272
|
-
rank: 2
|
|
278
|
+
rank: 2,
|
|
279
|
+
lastPublishedAt
|
|
273
280
|
}
|
|
274
281
|
];
|
|
275
282
|
|
|
@@ -482,7 +489,6 @@ describe('Pages REST', function() {
|
|
|
482
489
|
},
|
|
483
490
|
jar
|
|
484
491
|
});
|
|
485
|
-
|
|
486
492
|
const cousin = await apos.http.get('/api/v1/@apostrophecms/page/cousin:en:published', { jar });
|
|
487
493
|
const sibling = await apos.http.get('/api/v1/@apostrophecms/page/sibling:en:published', { jar });
|
|
488
494
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
let apos;
|
|
5
|
+
|
|
6
|
+
describe('Pages', function() {
|
|
7
|
+
|
|
8
|
+
this.timeout(t.timeout);
|
|
9
|
+
|
|
10
|
+
after(function() {
|
|
11
|
+
return t.destroy(apos);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// EXISTENCE
|
|
15
|
+
|
|
16
|
+
it('should be a property of the apos object', async function() {
|
|
17
|
+
apos = await t.create({
|
|
18
|
+
root: module,
|
|
19
|
+
modules: {
|
|
20
|
+
'@apostrophecms/page': {
|
|
21
|
+
options: {
|
|
22
|
+
park: [],
|
|
23
|
+
types: [
|
|
24
|
+
{
|
|
25
|
+
name: '@apostrophecms/home-page',
|
|
26
|
+
label: 'Home'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'test-page',
|
|
30
|
+
label: 'Test Page'
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
publicApiProjection: {
|
|
34
|
+
title: 1,
|
|
35
|
+
_url: 1
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
'test-page': {
|
|
40
|
+
extend: '@apostrophecms/page-type'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
assert(apos.page.__meta.name === '@apostrophecms/page');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('inserting child pages with a published req should produce the correct draft/published pairs', async function() {
|
|
49
|
+
const req = apos.task.getAdminReq();
|
|
50
|
+
const manager = apos.doc.getManager('test-page');
|
|
51
|
+
|
|
52
|
+
for (let i = 1; (i <= 10); i++) {
|
|
53
|
+
const page = manager.newInstance();
|
|
54
|
+
page.title = `test-child-${i}`;
|
|
55
|
+
page.type = 'test-page';
|
|
56
|
+
const { _id } = await apos.page.insert(req, '_home', 'lastChild', page, {});
|
|
57
|
+
const fetchedPage = await apos.page.find(req, { _id }).toObject();
|
|
58
|
+
assert.strictEqual(fetchedPage.aposMode, 'published');
|
|
59
|
+
assert(fetchedPage);
|
|
60
|
+
const draftReq = req.clone({
|
|
61
|
+
mode: 'draft'
|
|
62
|
+
});
|
|
63
|
+
const draft = await apos.page.find(draftReq, {
|
|
64
|
+
aposDocId: fetchedPage.aposDocId
|
|
65
|
+
}).toObject();
|
|
66
|
+
assert(draft);
|
|
67
|
+
assert.strictEqual(draft.aposMode, 'draft');
|
|
68
|
+
assert(draft.level === fetchedPage.level);
|
|
69
|
+
assert(draft.lastPublishedAt);
|
|
70
|
+
assert(fetchedPage.lastPublishedAt);
|
|
71
|
+
assert(draft.lastPublishedAt.getTime() === fetchedPage.lastPublishedAt.getTime());
|
|
72
|
+
}
|
|
73
|
+
assert(checkRanks('en:published'));
|
|
74
|
+
assert(checkRanks('en:draft'));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('Can fix the ranks after intentionally messing them up', async function() {
|
|
78
|
+
for (let i = 5; (i <= 10); i++) {
|
|
79
|
+
await apos.doc.db.updateMany({
|
|
80
|
+
title: `test-child-${i}`
|
|
81
|
+
}, {
|
|
82
|
+
$set: {
|
|
83
|
+
rank: i - 2
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await checkRanks('en:published');
|
|
89
|
+
assert(false);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// Good, supposed to fail
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await checkRanks('en:draft');
|
|
95
|
+
assert(false);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Good, supposed to fail
|
|
98
|
+
}
|
|
99
|
+
await apos.page.deduplicateRanks2Migration();
|
|
100
|
+
await checkRanks('en:published');
|
|
101
|
+
await checkRanks('en:draft');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('Can fix lastPublishedAt after intentionally messing it up', async function() {
|
|
105
|
+
let published = await apos.doc.db.findOne({
|
|
106
|
+
aposLocale: 'en:published',
|
|
107
|
+
slug: '/test-child-1'
|
|
108
|
+
});
|
|
109
|
+
assert(published.lastPublishedAt);
|
|
110
|
+
await apos.doc.db.updateOne({
|
|
111
|
+
_id: published._id
|
|
112
|
+
}, {
|
|
113
|
+
$unset: {
|
|
114
|
+
lastPublishedAt: 1
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
published = await apos.doc.db.findOne({
|
|
118
|
+
aposLocale: 'en:published',
|
|
119
|
+
slug: '/test-child-1'
|
|
120
|
+
});
|
|
121
|
+
assert(!published.lastPublishedAt);
|
|
122
|
+
await apos.page.missingLastPublishedAtMigration();
|
|
123
|
+
published = await apos.doc.db.findOne({
|
|
124
|
+
aposLocale: 'en:published',
|
|
125
|
+
slug: '/test-child-1'
|
|
126
|
+
});
|
|
127
|
+
assert(published.lastPublishedAt);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
async function checkRanks(aposLocale) {
|
|
132
|
+
const pages = await apos.doc.db.find({
|
|
133
|
+
level: 1,
|
|
134
|
+
aposLocale
|
|
135
|
+
}).project({
|
|
136
|
+
slug: 1,
|
|
137
|
+
rank: 1,
|
|
138
|
+
title: 1
|
|
139
|
+
}).toArray();
|
|
140
|
+
for (let i = 1; (i <= 10); i++) {
|
|
141
|
+
assert(pages.find(page => (page.rank === i - 1) && page.title === `test-child-${i}`));
|
|
142
|
+
}
|
|
143
|
+
assert(pages.find(page => (page.slug === '/archive') && (page.rank === 10)));
|
|
144
|
+
}
|