payload-nested-docs-page-tree 1.2.3 → 1.4.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/LICENSE +21 -21
- package/README.md +136 -47
- package/dist/components/PageTreeContext.d.ts +13 -1
- package/dist/components/PageTreeContext.js +11 -1
- package/dist/components/PageTreeContext.js.map +1 -1
- package/dist/components/PageTreeListView.client.d.ts +3 -4
- package/dist/components/PageTreeListView.client.js +645 -381
- package/dist/components/PageTreeListView.client.js.map +1 -1
- package/dist/components/PageTreeListView.module.css +90 -67
- package/dist/components/PageTreeListView.server.js +18 -28
- package/dist/components/PageTreeListView.server.js.map +1 -1
- package/dist/components/PageTreeTitleCell.js +80 -18
- package/dist/components/PageTreeTitleCell.js.map +1 -1
- package/dist/endpoints/createMovePageEndpoint.d.ts +2 -0
- package/dist/endpoints/createMovePageEndpoint.js +166 -123
- package/dist/endpoints/createMovePageEndpoint.js.map +1 -1
- package/dist/endpoints/createReorderPageEndpoint.d.ts +8 -0
- package/dist/endpoints/createReorderPageEndpoint.js +703 -0
- package/dist/endpoints/createReorderPageEndpoint.js.map +1 -0
- package/dist/endpoints/shared.d.ts +40 -0
- package/dist/endpoints/shared.js +93 -0
- package/dist/endpoints/shared.js.map +1 -0
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/rsc.js.map +1 -1
- package/dist/index.js +105 -26
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +9 -2
- package/dist/types.js +0 -2
- package/dist/types.js.map +1 -1
- package/dist/utilities/badgeConfig.js.map +1 -1
- package/dist/utilities/diagnostics.d.ts +42 -0
- package/dist/utilities/diagnostics.js +83 -0
- package/dist/utilities/diagnostics.js.map +1 -0
- package/dist/utilities/dropTargets.d.ts +2 -2
- package/dist/utilities/dropTargets.js +9 -7
- package/dist/utilities/dropTargets.js.map +1 -1
- package/dist/utilities/pageTree.js +4 -6
- package/dist/utilities/pageTree.js.map +1 -1
- package/dist/utilities/pageTreeCollision.d.ts +6 -0
- package/dist/utilities/pageTreeCollision.js +39 -4
- package/dist/utilities/pageTreeCollision.js.map +1 -1
- package/dist/utilities/pageTreeConfig.js.map +1 -1
- package/dist/utilities/status.d.ts +1 -1
- package/dist/utilities/status.js +7 -3
- package/dist/utilities/status.js.map +1 -1
- package/package.json +13 -12
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { combineWhereConstraints, generateNKeysBetween } from 'payload/shared';
|
|
2
|
+
import { pageTreeMoveContextKey } from '../types.js';
|
|
3
|
+
import { createFlowID, DIAGNOSTICS_FLOW_CONTEXT_KEY, readMainRowSnapshot } from '../utilities/diagnostics.js';
|
|
4
|
+
import { getDocParentID, stringifyDocID } from '../utilities/pageTree.js';
|
|
5
|
+
import { assertUpdateAccess, collectionHasDrafts, getRequestedLocale, normalizeID, respond, toCollectionID } from './shared.js';
|
|
6
|
+
const SNAPSHOT_FIELDS = [
|
|
7
|
+
'_status'
|
|
8
|
+
];
|
|
9
|
+
function hasOwnProperty(value, key) {
|
|
10
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
11
|
+
}
|
|
12
|
+
function normalizeOptionalString(value) {
|
|
13
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
14
|
+
}
|
|
15
|
+
function normalizeFrontendOrderEntry(value, fallbackIndex) {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const data = value;
|
|
20
|
+
return {
|
|
21
|
+
slug: normalizeOptionalString(data.slug),
|
|
22
|
+
index: typeof data.index === 'number' && Number.isFinite(data.index) ? data.index : fallbackIndex,
|
|
23
|
+
orderKey: normalizeOptionalString(data.orderKey)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function normalizeFrontendOrder(value) {
|
|
27
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const data = value;
|
|
31
|
+
if (!Array.isArray(data.before) || !Array.isArray(data.after)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const before = data.before.slice(0, 250).map((entry, index)=>normalizeFrontendOrderEntry(entry, index)).filter((entry)=>Boolean(entry));
|
|
35
|
+
const after = data.after.slice(0, 250).map((entry, index)=>normalizeFrontendOrderEntry(entry, index)).filter((entry)=>Boolean(entry));
|
|
36
|
+
return {
|
|
37
|
+
activeSlug: normalizeOptionalString(data.activeSlug),
|
|
38
|
+
after,
|
|
39
|
+
before,
|
|
40
|
+
moveFromIndex: typeof data.moveFromIndex === 'number' && Number.isFinite(data.moveFromIndex) ? data.moveFromIndex : -1,
|
|
41
|
+
moveToIndex: typeof data.moveToIndex === 'number' && Number.isFinite(data.moveToIndex) ? data.moveToIndex : -1,
|
|
42
|
+
newAfterRowSlug: normalizeOptionalString(data.newAfterRowSlug),
|
|
43
|
+
newBeforeRowSlug: normalizeOptionalString(data.newBeforeRowSlug),
|
|
44
|
+
sort: normalizeOptionalString(data.sort),
|
|
45
|
+
targetSlug: normalizeOptionalString(data.targetSlug)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function normalizeReorderDocumentBody(value) {
|
|
49
|
+
const incomingData = typeof value === 'string' ? (()=>{
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(value);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
})() : value;
|
|
56
|
+
if (!incomingData || typeof incomingData !== 'object' || Array.isArray(incomingData)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const data = incomingData;
|
|
60
|
+
const target = data.target;
|
|
61
|
+
if (!Array.isArray(data.docsToMove) || data.docsToMove.length !== 1) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (data.newKeyWillBe !== 'greater' && data.newKeyWillBe !== 'less') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (typeof data.orderableFieldName !== 'string' || data.orderableFieldName.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const targetData = target;
|
|
74
|
+
const movedID = normalizeID(data.docsToMove[0]);
|
|
75
|
+
const targetID = normalizeID(targetData.id);
|
|
76
|
+
if (!movedID || !targetID) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const key = hasOwnProperty(targetData, 'key') ? targetData.key : null;
|
|
80
|
+
if (key !== null && key !== undefined && typeof key !== 'string') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
docsToMove: [
|
|
85
|
+
movedID
|
|
86
|
+
],
|
|
87
|
+
frontendOrder: normalizeFrontendOrder(data.frontendOrder),
|
|
88
|
+
newKeyWillBe: data.newKeyWillBe,
|
|
89
|
+
orderableFieldName: data.orderableFieldName,
|
|
90
|
+
target: {
|
|
91
|
+
id: targetID,
|
|
92
|
+
key: key || null
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function readBodyFromRequest(req) {
|
|
97
|
+
let lastAttempt = null;
|
|
98
|
+
if (req.data !== undefined && req.data !== null) {
|
|
99
|
+
lastAttempt = {
|
|
100
|
+
raw: req.data,
|
|
101
|
+
source: 'req.data'
|
|
102
|
+
};
|
|
103
|
+
const directBody = normalizeReorderDocumentBody(req.data);
|
|
104
|
+
if (directBody) {
|
|
105
|
+
return {
|
|
106
|
+
body: directBody,
|
|
107
|
+
lastAttempt
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (typeof req.json === 'function') {
|
|
112
|
+
try {
|
|
113
|
+
const raw = await req.json();
|
|
114
|
+
lastAttempt = {
|
|
115
|
+
raw,
|
|
116
|
+
source: 'req.json'
|
|
117
|
+
};
|
|
118
|
+
const jsonBody = normalizeReorderDocumentBody(raw);
|
|
119
|
+
if (jsonBody) {
|
|
120
|
+
return {
|
|
121
|
+
body: jsonBody,
|
|
122
|
+
lastAttempt
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Fall through to the text-based fallback when the runtime does not hydrate req.data.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (typeof req.text === 'function') {
|
|
130
|
+
try {
|
|
131
|
+
const raw = await req.text();
|
|
132
|
+
lastAttempt = {
|
|
133
|
+
raw,
|
|
134
|
+
source: 'req.text'
|
|
135
|
+
};
|
|
136
|
+
const textBody = normalizeReorderDocumentBody(raw);
|
|
137
|
+
if (textBody) {
|
|
138
|
+
return {
|
|
139
|
+
body: textBody,
|
|
140
|
+
lastAttempt
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
return {
|
|
145
|
+
body: null,
|
|
146
|
+
lastAttempt
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
body: null,
|
|
152
|
+
lastAttempt
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function getParentScopeWhere(args) {
|
|
156
|
+
const { parentFieldSlug, parentID } = args;
|
|
157
|
+
return {
|
|
158
|
+
[parentFieldSlug]: {
|
|
159
|
+
equals: parentID
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function getOrderableKey(doc, orderableFieldName) {
|
|
164
|
+
const value = doc?.[orderableFieldName];
|
|
165
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
166
|
+
}
|
|
167
|
+
function getDiagnosticDocString(value, locale) {
|
|
168
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const localizedValue = value;
|
|
175
|
+
const localeValue = locale ? localizedValue[locale] : undefined;
|
|
176
|
+
if (typeof localeValue === 'string' && localeValue.length > 0) {
|
|
177
|
+
return localeValue;
|
|
178
|
+
}
|
|
179
|
+
const firstString = Object.values(localizedValue).find((entry)=>typeof entry === 'string' && entry.length > 0);
|
|
180
|
+
return firstString ?? null;
|
|
181
|
+
}
|
|
182
|
+
function getBackendOrderEntry(args) {
|
|
183
|
+
const { doc, index, locale, orderableFieldName } = args;
|
|
184
|
+
return {
|
|
185
|
+
slug: getDiagnosticDocString(doc.slug, locale),
|
|
186
|
+
index,
|
|
187
|
+
orderKey: getOrderableKey(doc, orderableFieldName),
|
|
188
|
+
status: getDiagnosticDocString(doc._status, locale)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function getMainRowOrderSnapshot(snapshot, orderableFieldName) {
|
|
192
|
+
if (!snapshot) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
orderKey: normalizeOptionalString(snapshot[orderableFieldName]),
|
|
197
|
+
status: normalizeOptionalString(snapshot._status)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function readBackendSiblingOrder(args) {
|
|
201
|
+
const { collectionSlug, draft, orderableFieldName, parentScopeWhere, req } = args;
|
|
202
|
+
const locale = getRequestedLocale(req);
|
|
203
|
+
try {
|
|
204
|
+
const result = await req.payload.find({
|
|
205
|
+
collection: collectionSlug,
|
|
206
|
+
depth: 0,
|
|
207
|
+
draft: draft,
|
|
208
|
+
limit: 250,
|
|
209
|
+
locale: locale,
|
|
210
|
+
overrideAccess: true,
|
|
211
|
+
pagination: false,
|
|
212
|
+
req,
|
|
213
|
+
sort: orderableFieldName,
|
|
214
|
+
where: parentScopeWhere
|
|
215
|
+
});
|
|
216
|
+
return result.docs.map((doc, index)=>getBackendOrderEntry({
|
|
217
|
+
doc,
|
|
218
|
+
index,
|
|
219
|
+
locale,
|
|
220
|
+
orderableFieldName
|
|
221
|
+
}));
|
|
222
|
+
} catch {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function getOrderValuesBetween(args) {
|
|
227
|
+
const { adjacentDocKey, docsToMoveLength, movedBoundaryKey, newKeyWillBe, targetKey } = args;
|
|
228
|
+
const orderValues = newKeyWillBe === 'greater' ? generateNKeysBetween(targetKey, adjacentDocKey, docsToMoveLength) : generateNKeysBetween(adjacentDocKey, targetKey, docsToMoveLength);
|
|
229
|
+
if (orderValues[0] !== movedBoundaryKey) {
|
|
230
|
+
return {
|
|
231
|
+
orderValues,
|
|
232
|
+
usedMovedKeyBoundary: false
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (newKeyWillBe === 'greater' && movedBoundaryKey && movedBoundaryKey > targetKey) {
|
|
236
|
+
return {
|
|
237
|
+
orderValues: generateNKeysBetween(targetKey, movedBoundaryKey, docsToMoveLength),
|
|
238
|
+
usedMovedKeyBoundary: true
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (newKeyWillBe === 'less' && movedBoundaryKey && movedBoundaryKey < targetKey) {
|
|
242
|
+
return {
|
|
243
|
+
orderValues: generateNKeysBetween(movedBoundaryKey, targetKey, docsToMoveLength),
|
|
244
|
+
usedMovedKeyBoundary: true
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
orderValues,
|
|
249
|
+
usedMovedKeyBoundary: false
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async function readLatestVersion(args) {
|
|
253
|
+
const { id, collectionSlug, req } = args;
|
|
254
|
+
const versions = await req.payload.db.findVersions({
|
|
255
|
+
collection: collectionSlug,
|
|
256
|
+
limit: 1,
|
|
257
|
+
pagination: false,
|
|
258
|
+
req,
|
|
259
|
+
sort: '-updatedAt',
|
|
260
|
+
where: {
|
|
261
|
+
latest: {
|
|
262
|
+
equals: true
|
|
263
|
+
},
|
|
264
|
+
parent: {
|
|
265
|
+
equals: id
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const latestVersion = versions.docs[0];
|
|
270
|
+
if (!latestVersion?.version) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return latestVersion;
|
|
274
|
+
}
|
|
275
|
+
async function updateLatestVersionOrder(args) {
|
|
276
|
+
const { id, collectionSlug, orderableFieldName, orderValue, req } = args;
|
|
277
|
+
const latestVersion = await readLatestVersion({
|
|
278
|
+
id,
|
|
279
|
+
collectionSlug,
|
|
280
|
+
req
|
|
281
|
+
});
|
|
282
|
+
if (!latestVersion?.id || !latestVersion.version) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
await req.payload.db.updateVersion({
|
|
286
|
+
id: latestVersion.id,
|
|
287
|
+
collection: collectionSlug,
|
|
288
|
+
locale: getRequestedLocale(req),
|
|
289
|
+
req,
|
|
290
|
+
returning: false,
|
|
291
|
+
versionData: {
|
|
292
|
+
createdAt: latestVersion.createdAt,
|
|
293
|
+
latest: latestVersion.latest,
|
|
294
|
+
parent: id,
|
|
295
|
+
publishedLocale: latestVersion.publishedLocale,
|
|
296
|
+
updatedAt: latestVersion.updatedAt,
|
|
297
|
+
version: {
|
|
298
|
+
...latestVersion.version,
|
|
299
|
+
[orderableFieldName]: orderValue
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
async function updateOrderValueSilently(args) {
|
|
306
|
+
const { id, collectionSlug, hasDrafts, orderableFieldName, orderValue, req } = args;
|
|
307
|
+
await req.payload.db.updateOne({
|
|
308
|
+
id: id,
|
|
309
|
+
collection: collectionSlug,
|
|
310
|
+
data: {
|
|
311
|
+
[orderableFieldName]: orderValue,
|
|
312
|
+
updatedAt: null
|
|
313
|
+
},
|
|
314
|
+
locale: getRequestedLocale(req),
|
|
315
|
+
req,
|
|
316
|
+
returning: false
|
|
317
|
+
});
|
|
318
|
+
const updatedDraftVersion = hasDrafts ? await updateLatestVersionOrder({
|
|
319
|
+
id,
|
|
320
|
+
collectionSlug,
|
|
321
|
+
orderableFieldName,
|
|
322
|
+
orderValue,
|
|
323
|
+
req
|
|
324
|
+
}) : false;
|
|
325
|
+
return {
|
|
326
|
+
updatedDraftVersion
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function initializeMissingOrderKeys(args) {
|
|
330
|
+
const { collectionSlug, diagnostics, flow, hasDrafts, orderableFieldName, parentScopeWhere, req } = args;
|
|
331
|
+
const existingOrderedDocs = await req.payload.find({
|
|
332
|
+
collection: collectionSlug,
|
|
333
|
+
depth: 0,
|
|
334
|
+
draft: hasDrafts ? true : undefined,
|
|
335
|
+
limit: 1,
|
|
336
|
+
locale: getRequestedLocale(req),
|
|
337
|
+
overrideAccess: true,
|
|
338
|
+
pagination: false,
|
|
339
|
+
req,
|
|
340
|
+
select: {
|
|
341
|
+
[orderableFieldName]: true
|
|
342
|
+
},
|
|
343
|
+
sort: `-${orderableFieldName}`,
|
|
344
|
+
where: combineWhereConstraints([
|
|
345
|
+
{
|
|
346
|
+
[orderableFieldName]: {
|
|
347
|
+
exists: true
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
parentScopeWhere
|
|
351
|
+
])
|
|
352
|
+
});
|
|
353
|
+
const missingOrderDocs = await req.payload.find({
|
|
354
|
+
collection: collectionSlug,
|
|
355
|
+
depth: 0,
|
|
356
|
+
draft: hasDrafts ? true : undefined,
|
|
357
|
+
limit: 0,
|
|
358
|
+
locale: getRequestedLocale(req),
|
|
359
|
+
overrideAccess: true,
|
|
360
|
+
pagination: false,
|
|
361
|
+
req,
|
|
362
|
+
select: {
|
|
363
|
+
[orderableFieldName]: true
|
|
364
|
+
},
|
|
365
|
+
where: combineWhereConstraints([
|
|
366
|
+
{
|
|
367
|
+
[orderableFieldName]: {
|
|
368
|
+
exists: false
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
parentScopeWhere
|
|
372
|
+
])
|
|
373
|
+
});
|
|
374
|
+
const lastOrderValue = getOrderableKey(existingOrderedDocs.docs[0], orderableFieldName);
|
|
375
|
+
const orderValues = generateNKeysBetween(lastOrderValue, null, missingOrderDocs.docs.length);
|
|
376
|
+
for (const [index, doc] of missingOrderDocs.docs.entries()){
|
|
377
|
+
const docID = doc.id;
|
|
378
|
+
const orderValue = orderValues[index];
|
|
379
|
+
if (orderValue && (typeof docID === 'number' || typeof docID === 'string')) {
|
|
380
|
+
await updateOrderValueSilently({
|
|
381
|
+
id: docID,
|
|
382
|
+
collectionSlug,
|
|
383
|
+
hasDrafts,
|
|
384
|
+
orderableFieldName,
|
|
385
|
+
orderValue,
|
|
386
|
+
req
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
diagnostics.log({
|
|
391
|
+
collection: collectionSlug,
|
|
392
|
+
data: {
|
|
393
|
+
initializedCount: missingOrderDocs.docs.length,
|
|
394
|
+
orderableFieldName
|
|
395
|
+
},
|
|
396
|
+
flow,
|
|
397
|
+
level: 'info',
|
|
398
|
+
message: 'reorder endpoint initialized missing order keys',
|
|
399
|
+
source: 'reorder-endpoint:initial-migration'
|
|
400
|
+
});
|
|
401
|
+
return respond(200, {
|
|
402
|
+
message: 'initial migration',
|
|
403
|
+
success: true
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
export function createReorderPageEndpoint(args) {
|
|
407
|
+
const { collectionSlug, diagnostics, orderableFieldName, parentFieldSlug } = args;
|
|
408
|
+
const snapshotFields = [
|
|
409
|
+
...SNAPSHOT_FIELDS,
|
|
410
|
+
parentFieldSlug,
|
|
411
|
+
orderableFieldName
|
|
412
|
+
];
|
|
413
|
+
return {
|
|
414
|
+
handler: async (req)=>{
|
|
415
|
+
const movedIDFromRoute = normalizeID(req.routeParams?.id);
|
|
416
|
+
if (!movedIDFromRoute) {
|
|
417
|
+
return respond(400, {
|
|
418
|
+
message: 'Document ID was not specified.'
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
const { body, lastAttempt } = await readBodyFromRequest(req);
|
|
422
|
+
if (!body || body.orderableFieldName !== orderableFieldName || body.docsToMove[0] !== movedIDFromRoute) {
|
|
423
|
+
if (diagnostics.enabled) {
|
|
424
|
+
diagnostics.log({
|
|
425
|
+
collection: collectionSlug,
|
|
426
|
+
data: {
|
|
427
|
+
movedID: movedIDFromRoute,
|
|
428
|
+
rawBody: lastAttempt?.raw ?? null,
|
|
429
|
+
rawBodySource: lastAttempt?.source ?? null
|
|
430
|
+
},
|
|
431
|
+
flow: createFlowID('reorder-endpoint'),
|
|
432
|
+
level: 'warn',
|
|
433
|
+
message: 'reorder endpoint rejected request body',
|
|
434
|
+
source: 'reorder-endpoint:body-rejected'
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return respond(400, {
|
|
438
|
+
message: 'A valid reorder request is required.'
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
if (body.target.id === movedIDFromRoute) {
|
|
442
|
+
return respond(400, {
|
|
443
|
+
message: 'Document is already in that position.'
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
const movedID = toCollectionID({
|
|
447
|
+
id: movedIDFromRoute,
|
|
448
|
+
collectionSlug,
|
|
449
|
+
req
|
|
450
|
+
});
|
|
451
|
+
const targetID = toCollectionID({
|
|
452
|
+
id: body.target.id,
|
|
453
|
+
collectionSlug,
|
|
454
|
+
req
|
|
455
|
+
});
|
|
456
|
+
const accessError = await assertUpdateAccess({
|
|
457
|
+
id: movedID,
|
|
458
|
+
collectionSlug,
|
|
459
|
+
data: {},
|
|
460
|
+
req
|
|
461
|
+
});
|
|
462
|
+
if (accessError) {
|
|
463
|
+
return accessError;
|
|
464
|
+
}
|
|
465
|
+
const flow = createFlowID('reorder-endpoint');
|
|
466
|
+
const reorderStart = Date.now();
|
|
467
|
+
const hasDrafts = collectionHasDrafts({
|
|
468
|
+
collectionSlug,
|
|
469
|
+
req
|
|
470
|
+
});
|
|
471
|
+
if (!req.context) {
|
|
472
|
+
req.context = {};
|
|
473
|
+
}
|
|
474
|
+
;
|
|
475
|
+
req.context[pageTreeMoveContextKey] = true;
|
|
476
|
+
req.context[DIAGNOSTICS_FLOW_CONTEXT_KEY] = flow;
|
|
477
|
+
const relatedDocsResult = await req.payload.find({
|
|
478
|
+
collection: collectionSlug,
|
|
479
|
+
depth: 0,
|
|
480
|
+
draft: hasDrafts ? true : undefined,
|
|
481
|
+
limit: 2,
|
|
482
|
+
locale: getRequestedLocale(req),
|
|
483
|
+
overrideAccess: true,
|
|
484
|
+
req,
|
|
485
|
+
where: {
|
|
486
|
+
id: {
|
|
487
|
+
in: [
|
|
488
|
+
movedID,
|
|
489
|
+
targetID
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
const docsByID = new Map(relatedDocsResult.docs.map((doc)=>[
|
|
495
|
+
stringifyDocID(doc.id),
|
|
496
|
+
doc
|
|
497
|
+
]));
|
|
498
|
+
const movedDoc = docsByID.get(movedIDFromRoute);
|
|
499
|
+
const targetDoc = docsByID.get(body.target.id);
|
|
500
|
+
if (!movedDoc) {
|
|
501
|
+
return respond(404, {
|
|
502
|
+
message: 'Document not found.'
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
if (!targetDoc) {
|
|
506
|
+
return respond(400, {
|
|
507
|
+
message: 'Target document not found.'
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
const movedParentID = getDocParentID(movedDoc, parentFieldSlug);
|
|
511
|
+
const targetParentID = getDocParentID(targetDoc, parentFieldSlug);
|
|
512
|
+
if (movedParentID !== targetParentID) {
|
|
513
|
+
return respond(400, {
|
|
514
|
+
message: 'Documents can only be reordered within the same parent.'
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
const parentScopeWhere = getParentScopeWhere({
|
|
518
|
+
parentFieldSlug,
|
|
519
|
+
parentID: movedParentID === null ? null : toCollectionID({
|
|
520
|
+
id: movedParentID,
|
|
521
|
+
collectionSlug,
|
|
522
|
+
req
|
|
523
|
+
})
|
|
524
|
+
});
|
|
525
|
+
if (!body.target.key) {
|
|
526
|
+
return initializeMissingOrderKeys({
|
|
527
|
+
collectionSlug,
|
|
528
|
+
diagnostics,
|
|
529
|
+
flow,
|
|
530
|
+
hasDrafts,
|
|
531
|
+
orderableFieldName,
|
|
532
|
+
parentScopeWhere,
|
|
533
|
+
req
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
const beforeSnapshot = await readMainRowSnapshot({
|
|
537
|
+
id: movedID,
|
|
538
|
+
collectionSlug,
|
|
539
|
+
fields: snapshotFields,
|
|
540
|
+
req
|
|
541
|
+
});
|
|
542
|
+
const backendDraftOrderBefore = diagnostics.enabled ? await readBackendSiblingOrder({
|
|
543
|
+
collectionSlug,
|
|
544
|
+
draft: hasDrafts ? true : undefined,
|
|
545
|
+
orderableFieldName,
|
|
546
|
+
parentScopeWhere,
|
|
547
|
+
req
|
|
548
|
+
}) : null;
|
|
549
|
+
const backendMainOrderBefore = diagnostics.enabled ? await readBackendSiblingOrder({
|
|
550
|
+
collectionSlug,
|
|
551
|
+
draft: false,
|
|
552
|
+
orderableFieldName,
|
|
553
|
+
parentScopeWhere,
|
|
554
|
+
req
|
|
555
|
+
}) : null;
|
|
556
|
+
const adjacentDoc = await req.payload.find({
|
|
557
|
+
collection: collectionSlug,
|
|
558
|
+
depth: 0,
|
|
559
|
+
draft: hasDrafts ? true : undefined,
|
|
560
|
+
limit: 1,
|
|
561
|
+
locale: getRequestedLocale(req),
|
|
562
|
+
overrideAccess: true,
|
|
563
|
+
pagination: false,
|
|
564
|
+
req,
|
|
565
|
+
select: {
|
|
566
|
+
[orderableFieldName]: true
|
|
567
|
+
},
|
|
568
|
+
sort: body.newKeyWillBe === 'greater' ? orderableFieldName : `-${orderableFieldName}`,
|
|
569
|
+
where: combineWhereConstraints([
|
|
570
|
+
{
|
|
571
|
+
[orderableFieldName]: {
|
|
572
|
+
[body.newKeyWillBe === 'greater' ? 'greater_than' : 'less_than']: body.target.key
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
parentScopeWhere
|
|
576
|
+
])
|
|
577
|
+
});
|
|
578
|
+
const adjacentDocKey = getOrderableKey(adjacentDoc.docs[0], orderableFieldName);
|
|
579
|
+
const movedDocKey = getOrderableKey(movedDoc, orderableFieldName);
|
|
580
|
+
const movedSlug = getDiagnosticDocString(movedDoc.slug, getRequestedLocale(req));
|
|
581
|
+
const targetSlug = getDiagnosticDocString(targetDoc.slug, getRequestedLocale(req));
|
|
582
|
+
const movedMainRowKey = typeof beforeSnapshot?.[orderableFieldName] === 'string' && beforeSnapshot[orderableFieldName].length > 0 ? beforeSnapshot[orderableFieldName] : movedDocKey;
|
|
583
|
+
const { orderValues, usedMovedKeyBoundary } = getOrderValuesBetween({
|
|
584
|
+
adjacentDocKey,
|
|
585
|
+
docsToMoveLength: body.docsToMove.length,
|
|
586
|
+
movedBoundaryKey: movedMainRowKey,
|
|
587
|
+
newKeyWillBe: body.newKeyWillBe,
|
|
588
|
+
targetKey: body.target.key
|
|
589
|
+
});
|
|
590
|
+
const orderValue = orderValues[0];
|
|
591
|
+
if (!orderValue) {
|
|
592
|
+
return respond(500, {
|
|
593
|
+
message: 'Could not generate a new order key.'
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
if (diagnostics.enabled) {
|
|
597
|
+
diagnostics.log({
|
|
598
|
+
collection: collectionSlug,
|
|
599
|
+
data: {
|
|
600
|
+
adjacentDocKey,
|
|
601
|
+
backendOrderBefore: {
|
|
602
|
+
draft: backendDraftOrderBefore,
|
|
603
|
+
main: backendMainOrderBefore
|
|
604
|
+
},
|
|
605
|
+
frontendOrder: body.frontendOrder ?? null,
|
|
606
|
+
mainRowBefore: getMainRowOrderSnapshot(beforeSnapshot, orderableFieldName),
|
|
607
|
+
movedDocKey,
|
|
608
|
+
movedMainRowKey,
|
|
609
|
+
movedSlug,
|
|
610
|
+
newKeyWillBe: body.newKeyWillBe,
|
|
611
|
+
orderableFieldName,
|
|
612
|
+
orderValue,
|
|
613
|
+
target: {
|
|
614
|
+
slug: targetSlug,
|
|
615
|
+
key: body.target.key
|
|
616
|
+
},
|
|
617
|
+
usedMovedKeyBoundary
|
|
618
|
+
},
|
|
619
|
+
flow,
|
|
620
|
+
level: 'info',
|
|
621
|
+
message: 'reorder endpoint entering: order key update only',
|
|
622
|
+
source: 'reorder-endpoint:enter'
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
const updateResult = await updateOrderValueSilently({
|
|
627
|
+
id: movedID,
|
|
628
|
+
collectionSlug,
|
|
629
|
+
hasDrafts,
|
|
630
|
+
orderableFieldName,
|
|
631
|
+
orderValue,
|
|
632
|
+
req
|
|
633
|
+
});
|
|
634
|
+
if (diagnostics.enabled) {
|
|
635
|
+
const afterSnapshot = await readMainRowSnapshot({
|
|
636
|
+
id: movedID,
|
|
637
|
+
collectionSlug,
|
|
638
|
+
fields: snapshotFields,
|
|
639
|
+
req
|
|
640
|
+
});
|
|
641
|
+
const backendDraftOrderAfter = await readBackendSiblingOrder({
|
|
642
|
+
collectionSlug,
|
|
643
|
+
draft: hasDrafts ? true : undefined,
|
|
644
|
+
orderableFieldName,
|
|
645
|
+
parentScopeWhere,
|
|
646
|
+
req
|
|
647
|
+
});
|
|
648
|
+
const backendMainOrderAfter = await readBackendSiblingOrder({
|
|
649
|
+
collectionSlug,
|
|
650
|
+
draft: false,
|
|
651
|
+
orderableFieldName,
|
|
652
|
+
parentScopeWhere,
|
|
653
|
+
req
|
|
654
|
+
});
|
|
655
|
+
diagnostics.log({
|
|
656
|
+
collection: collectionSlug,
|
|
657
|
+
data: {
|
|
658
|
+
backendOrderAfter: {
|
|
659
|
+
draft: backendDraftOrderAfter,
|
|
660
|
+
main: backendMainOrderAfter
|
|
661
|
+
},
|
|
662
|
+
durMs: Date.now() - reorderStart,
|
|
663
|
+
mainRowAfter: getMainRowOrderSnapshot(afterSnapshot, orderableFieldName),
|
|
664
|
+
movedSlug,
|
|
665
|
+
orderValue,
|
|
666
|
+
updatedDraftVersion: updateResult.updatedDraftVersion
|
|
667
|
+
},
|
|
668
|
+
flow,
|
|
669
|
+
level: 'info',
|
|
670
|
+
message: 'reorder endpoint order key update succeeded',
|
|
671
|
+
source: 'reorder-endpoint:ok'
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
} catch (err) {
|
|
675
|
+
const error = err;
|
|
676
|
+
diagnostics.log({
|
|
677
|
+
collection: collectionSlug,
|
|
678
|
+
data: {
|
|
679
|
+
name: error?.name,
|
|
680
|
+
data: error?.data ?? null,
|
|
681
|
+
durMs: Date.now() - reorderStart,
|
|
682
|
+
message: error?.message,
|
|
683
|
+
movedSlug
|
|
684
|
+
},
|
|
685
|
+
flow,
|
|
686
|
+
level: 'error',
|
|
687
|
+
message: `reorder endpoint order key update failed: ${error?.message ?? 'unknown error'}`,
|
|
688
|
+
source: 'reorder-endpoint:error'
|
|
689
|
+
});
|
|
690
|
+
throw err;
|
|
691
|
+
}
|
|
692
|
+
return respond(200, {
|
|
693
|
+
movedID: movedIDFromRoute,
|
|
694
|
+
orderValues,
|
|
695
|
+
success: true
|
|
696
|
+
});
|
|
697
|
+
},
|
|
698
|
+
method: 'post',
|
|
699
|
+
path: '/:id/reorder'
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
//# sourceMappingURL=createReorderPageEndpoint.js.map
|