payload-nested-docs-page-tree 1.1.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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/components/PageTreeContext.d.ts +15 -0
  4. package/dist/components/PageTreeContext.js +26 -0
  5. package/dist/components/PageTreeContext.js.map +1 -0
  6. package/dist/components/PageTreeListView.client.d.ts +17 -0
  7. package/dist/components/PageTreeListView.client.js +960 -0
  8. package/dist/components/PageTreeListView.client.js.map +1 -0
  9. package/dist/components/PageTreeListView.module.css +358 -0
  10. package/dist/components/PageTreeListView.server.d.ts +3 -0
  11. package/dist/components/PageTreeListView.server.js +390 -0
  12. package/dist/components/PageTreeListView.server.js.map +1 -0
  13. package/dist/components/PageTreeTitleCell.d.ts +6 -0
  14. package/dist/components/PageTreeTitleCell.js +80 -0
  15. package/dist/components/PageTreeTitleCell.js.map +1 -0
  16. package/dist/endpoints/createMovePageEndpoint.d.ts +5 -0
  17. package/dist/endpoints/createMovePageEndpoint.js +265 -0
  18. package/dist/endpoints/createMovePageEndpoint.js.map +1 -0
  19. package/dist/exports/client.d.ts +1 -0
  20. package/dist/exports/client.js +3 -0
  21. package/dist/exports/client.js.map +1 -0
  22. package/dist/exports/rsc.d.ts +1 -0
  23. package/dist/exports/rsc.js +3 -0
  24. package/dist/exports/rsc.js.map +1 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +142 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/types.d.ts +35 -0
  29. package/dist/types.js +8 -0
  30. package/dist/types.js.map +1 -0
  31. package/dist/utilities/badgeConfig.d.ts +2 -0
  32. package/dist/utilities/badgeConfig.js +30 -0
  33. package/dist/utilities/badgeConfig.js.map +1 -0
  34. package/dist/utilities/dropTargets.d.ts +31 -0
  35. package/dist/utilities/dropTargets.js +73 -0
  36. package/dist/utilities/dropTargets.js.map +1 -0
  37. package/dist/utilities/moveValidation.d.ts +11 -0
  38. package/dist/utilities/moveValidation.js +44 -0
  39. package/dist/utilities/moveValidation.js.map +1 -0
  40. package/dist/utilities/pageTree.d.ts +37 -0
  41. package/dist/utilities/pageTree.js +300 -0
  42. package/dist/utilities/pageTree.js.map +1 -0
  43. package/dist/utilities/pageTreeCollision.d.ts +16 -0
  44. package/dist/utilities/pageTreeCollision.js +70 -0
  45. package/dist/utilities/pageTreeCollision.js.map +1 -0
  46. package/dist/utilities/pageTreeConfig.d.ts +3 -0
  47. package/dist/utilities/pageTreeConfig.js +21 -0
  48. package/dist/utilities/pageTreeConfig.js.map +1 -0
  49. package/dist/utilities/status.d.ts +17 -0
  50. package/dist/utilities/status.js +72 -0
  51. package/dist/utilities/status.js.map +1 -0
  52. package/package.json +115 -0
@@ -0,0 +1,960 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { DndContext, DragOverlay, PointerSensor, useDroppable, useSensor, useSensors } from '@dnd-kit/core';
4
+ import { DefaultListView, ListQueryProvider, SelectAll, SelectRow, SortHeader, toast, useConfig, useLocale, useTranslation } from '@payloadcms/ui';
5
+ import { useRouter, useSearchParams } from 'next/navigation';
6
+ import React from 'react';
7
+ import { buildInsertDropTargets, getDropTargetParentDoc, getOrderPlacementFromDropTarget } from '../utilities/dropTargets.js';
8
+ import { CANCEL_DRAG_MESSAGE, getDropValidation } from '../utilities/moveValidation.js';
9
+ import { buildDocSlugPath, buildPageTreeDocs, buildProspectiveDocSlugPath, getDocDisplayLabel, getVisibleTreeDocs } from '../utilities/pageTree.js';
10
+ import { pageTreeCollisionDetectionStrategy } from '../utilities/pageTreeCollision.js';
11
+ import { getPageTreeBadgeColor, getPageTreeBadgeLabel, getPageTreeDisplayStatus } from '../utilities/status.js';
12
+ import { PageTreeProvider } from './PageTreeContext.js';
13
+ import styles from './PageTreeListView.module.css';
14
+ import { PageTreeTitleCell } from './PageTreeTitleCell.js';
15
+ const MANUAL_ORDER_COLUMN_ACCESSOR = '_dragHandle';
16
+ const SILENT_MOVE_MESSAGES = new Set([
17
+ CANCEL_DRAG_MESSAGE
18
+ ]);
19
+ function getRowDropID(rowID) {
20
+ return `page-drop:${rowID}`;
21
+ }
22
+ function getPageTreeDragData(value) {
23
+ return value && typeof value === 'object' ? value : {};
24
+ }
25
+ function getPayloadDocID(doc) {
26
+ return typeof doc.id === 'number' || typeof doc.id === 'string' ? doc.id : undefined;
27
+ }
28
+ function getManualOrderDropValidation(args) {
29
+ const { activeDoc, targetDoc } = args;
30
+ if (!targetDoc) {
31
+ return {
32
+ isValid: true,
33
+ parentID: null
34
+ };
35
+ }
36
+ if (targetDoc.__pageTreeID === activeDoc.__pageTreeID) {
37
+ return {
38
+ isValid: false,
39
+ message: CANCEL_DRAG_MESSAGE,
40
+ parentID: targetDoc.__pageTreeID
41
+ };
42
+ }
43
+ if (targetDoc.__pageTreeAncestorIDs.includes(activeDoc.__pageTreeID)) {
44
+ return {
45
+ isValid: false,
46
+ message: 'A document cannot be moved under one of its descendants.',
47
+ parentID: targetDoc.__pageTreeID
48
+ };
49
+ }
50
+ return {
51
+ isValid: true,
52
+ parentID: targetDoc.__pageTreeID
53
+ };
54
+ }
55
+ function getDropValidationForMode(args) {
56
+ const { activeDoc, allowSameParent, targetDoc } = args;
57
+ if (allowSameParent) {
58
+ return getManualOrderDropValidation({
59
+ activeDoc,
60
+ targetDoc
61
+ });
62
+ }
63
+ return getDropValidation({
64
+ activeDoc,
65
+ targetDoc
66
+ });
67
+ }
68
+ function getCurrentOrderPlacement(args) {
69
+ const { activeDoc, docs } = args;
70
+ const activeIndex = docs.findIndex((doc)=>doc.__pageTreeID === activeDoc.__pageTreeID);
71
+ const movingSubtreeIDs = new Set(docs.filter((doc)=>doc.__pageTreeID === activeDoc.__pageTreeID || doc.__pageTreeAncestorIDs.includes(activeDoc.__pageTreeID)).map((doc)=>doc.__pageTreeID));
72
+ const siblingIsNotMoving = (doc)=>doc.__pageTreeParentID === activeDoc.__pageTreeParentID && !movingSubtreeIDs.has(doc.__pageTreeID);
73
+ if (activeIndex < 0) {
74
+ return {
75
+ nextSiblingID: null,
76
+ parentID: activeDoc.__pageTreeParentID,
77
+ previousSiblingID: null
78
+ };
79
+ }
80
+ return {
81
+ nextSiblingID: docs.slice(activeIndex + 1).find(siblingIsNotMoving)?.__pageTreeID ?? null,
82
+ parentID: activeDoc.__pageTreeParentID,
83
+ previousSiblingID: docs.slice(0, activeIndex).filter(siblingIsNotMoving).at(-1)?.__pageTreeID ?? null
84
+ };
85
+ }
86
+ function orderPlacementHasChanged(currentPlacement, nextPlacement) {
87
+ return currentPlacement.parentID !== nextPlacement.parentID || currentPlacement.previousSiblingID !== nextPlacement.previousSiblingID || currentPlacement.nextSiblingID !== nextPlacement.nextSiblingID;
88
+ }
89
+ function getReorderTargetFromPlacement(args) {
90
+ const { currentSort, docsByID, orderableFieldName, placement } = args;
91
+ const targetDocID = placement.previousSiblingID ?? placement.nextSiblingID;
92
+ if (!targetDocID) {
93
+ return null;
94
+ }
95
+ const targetDoc = docsByID.get(targetDocID);
96
+ if (!targetDoc) {
97
+ return null;
98
+ }
99
+ return {
100
+ newKeyWillBe: placement.previousSiblingID !== null && currentSort === orderableFieldName || placement.previousSiblingID === null && currentSort === `-${orderableFieldName}` ? 'greater' : 'less',
101
+ targetDoc
102
+ };
103
+ }
104
+ function buildPaginatedData(docs, limit, requestedPage) {
105
+ const totalDocs = docs.length;
106
+ const totalPages = totalDocs > 0 ? Math.max(1, Math.ceil(totalDocs / limit)) : 1;
107
+ const page = Math.min(Math.max(requestedPage, 1), totalPages);
108
+ const startIndex = (page - 1) * limit;
109
+ const pageDocs = docs.slice(startIndex, startIndex + limit);
110
+ return {
111
+ docs: pageDocs,
112
+ hasNextPage: page < totalPages,
113
+ hasPrevPage: page > 1,
114
+ limit,
115
+ nextPage: page < totalPages ? page + 1 : null,
116
+ page,
117
+ pagingCounter: totalDocs === 0 ? 0 : startIndex + 1,
118
+ prevPage: page > 1 ? page - 1 : null,
119
+ totalDocs,
120
+ totalPages
121
+ };
122
+ }
123
+ function normalizeSort(value) {
124
+ if (Array.isArray(value)) {
125
+ const sortValues = value.filter((entry)=>typeof entry === 'string' && entry.length > 0);
126
+ return sortValues.length > 0 ? sortValues.join(',') : undefined;
127
+ }
128
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
129
+ }
130
+ function normalizePositiveInt(value, fallback) {
131
+ if (typeof value !== 'string') {
132
+ return fallback;
133
+ }
134
+ const parsedValue = Number.parseInt(value, 10);
135
+ return Number.isNaN(parsedValue) || parsedValue <= 0 ? fallback : parsedValue;
136
+ }
137
+ function normalizePageTreeColumnState(columnState, useAsTitle) {
138
+ return columnState.filter((column)=>column.accessor !== MANUAL_ORDER_COLUMN_ACCESSOR).map((column)=>column.accessor === useAsTitle ? {
139
+ ...column,
140
+ active: true
141
+ } : column);
142
+ }
143
+ function sliceColumnState(columnState, docs, renderedCellIndexByDocID) {
144
+ return columnState.map((column)=>({
145
+ ...column,
146
+ renderedCells: docs.map((doc)=>{
147
+ const renderedCellIndex = renderedCellIndexByDocID.get(doc.__pageTreeID) ?? doc.__pageTreeOrderIndex;
148
+ return column.renderedCells?.[renderedCellIndex] ?? null;
149
+ })
150
+ }));
151
+ }
152
+ function getSelectableRowData(doc) {
153
+ const record = doc;
154
+ return {
155
+ id: String(doc.id ?? doc.__pageTreeID),
156
+ _isLocked: Boolean(record._isLocked),
157
+ _userEditing: record._userEditing
158
+ };
159
+ }
160
+ function shouldSilenceMoveMessage(message) {
161
+ return typeof message === 'string' && SILENT_MOVE_MESSAGES.has(message);
162
+ }
163
+ function getStatusClassName(status) {
164
+ if (status === 'changed' || status === 'draft' || status === 'published') {
165
+ return status;
166
+ }
167
+ return 'unknown';
168
+ }
169
+ function renderStatusBadge(args) {
170
+ const { badgeConfig, doc, index, t } = args;
171
+ const status = getPageTreeDisplayStatus(doc);
172
+ const customColor = getPageTreeBadgeColor({
173
+ badgeColors: badgeConfig.colors,
174
+ status
175
+ });
176
+ const statusClass = getStatusClassName(status);
177
+ const style = customColor ? {
178
+ '--page-tree-badge-base': customColor
179
+ } : undefined;
180
+ return /*#__PURE__*/ _jsx("span", {
181
+ className: [
182
+ 'pages-hierarchy-status-badge',
183
+ `pages-hierarchy-status-badge--${statusClass}`
184
+ ].join(' '),
185
+ "data-custom-color": customColor ? 'true' : undefined,
186
+ style: style,
187
+ children: getPageTreeBadgeLabel({
188
+ badgeLabels: badgeConfig.labels,
189
+ status,
190
+ t
191
+ })
192
+ }, doc.__pageTreeID ?? index);
193
+ }
194
+ function createManualOrderColumn(docs) {
195
+ return {
196
+ accessor: MANUAL_ORDER_COLUMN_ACCESSOR,
197
+ active: true,
198
+ field: {
199
+ hidden: true
200
+ },
201
+ Heading: /*#__PURE__*/ _jsx(SortHeader, {}),
202
+ renderedCells: docs.map(()=>null)
203
+ };
204
+ }
205
+ function insertManualOrderColumn(columns, orderColumn) {
206
+ const selectColumnIndex = columns.findIndex((column)=>column.accessor === '_select');
207
+ const insertIndex = selectColumnIndex >= 0 ? selectColumnIndex + 1 : 0;
208
+ return [
209
+ ...columns.slice(0, insertIndex),
210
+ orderColumn,
211
+ ...columns.slice(insertIndex)
212
+ ];
213
+ }
214
+ function buildTableColumns(args) {
215
+ const { badgeConfig, columnState, docs, enableRowSelections, orderableFieldName, parentFieldSlug, t, useAsTitle } = args;
216
+ const columnsToUse = columnState.map((column)=>{
217
+ if (column.accessor === useAsTitle) {
218
+ return {
219
+ ...column,
220
+ active: true,
221
+ renderedCells: docs.map((doc, index)=>/*#__PURE__*/ _jsx(PageTreeTitleCell, {
222
+ doc: doc,
223
+ children: column.renderedCells?.[index] ?? getDocDisplayLabel(doc)
224
+ }, doc.__pageTreeID ?? index))
225
+ };
226
+ }
227
+ if (column.accessor === '_status') {
228
+ return {
229
+ ...column,
230
+ renderedCells: docs.map((doc, index)=>renderStatusBadge({
231
+ badgeConfig,
232
+ doc,
233
+ index,
234
+ t
235
+ }))
236
+ };
237
+ }
238
+ if (column.accessor === parentFieldSlug) {
239
+ return {
240
+ ...column,
241
+ renderedCells: docs.map((doc, index)=>doc.__pageTreeParentID !== null ? column.renderedCells?.[index] ?? null : /*#__PURE__*/ _jsx("span", {
242
+ className: "pages-hierarchy-empty-cell",
243
+ children: "-"
244
+ }, doc.__pageTreeID ?? index))
245
+ };
246
+ }
247
+ return column;
248
+ });
249
+ if (enableRowSelections) {
250
+ columnsToUse.unshift({
251
+ accessor: '_select',
252
+ active: true,
253
+ field: {
254
+ hidden: true
255
+ },
256
+ Heading: /*#__PURE__*/ _jsx(SelectAll, {}),
257
+ renderedCells: docs.map((doc, index)=>/*#__PURE__*/ _jsx(SelectRow, {
258
+ rowData: getSelectableRowData(doc)
259
+ }, doc.__pageTreeID ?? index))
260
+ });
261
+ }
262
+ if (orderableFieldName && !columnsToUse.some((column)=>column.accessor === MANUAL_ORDER_COLUMN_ACCESSOR)) {
263
+ return insertManualOrderColumn(columnsToUse, createManualOrderColumn(docs));
264
+ }
265
+ return columnsToUse;
266
+ }
267
+ function HierarchyInsertRow({ activeColumnsCount, activeDragRowID, dropTarget, dropValidation, isMovePending }) {
268
+ const { isOver, setNodeRef } = useDroppable({
269
+ id: dropTarget.dropID,
270
+ data: dropTarget,
271
+ disabled: isMovePending
272
+ });
273
+ const hasActiveDrag = Boolean(activeDragRowID);
274
+ return /*#__PURE__*/ _jsx("tr", {
275
+ className: "pages-hierarchy-insert-row",
276
+ "data-drag-over": isOver ? 'true' : 'false',
277
+ "data-drop-valid": hasActiveDrag ? dropValidation?.isValid ? 'true' : 'false' : undefined,
278
+ "data-page-tree-insert": "true",
279
+ children: /*#__PURE__*/ _jsx("td", {
280
+ colSpan: activeColumnsCount,
281
+ children: /*#__PURE__*/ _jsx("div", {
282
+ className: "pages-hierarchy-insert-row__target",
283
+ "data-insert-depth": dropTarget.depth,
284
+ ref: setNodeRef
285
+ })
286
+ })
287
+ });
288
+ }
289
+ function HierarchyTableRow({ activeColumns, activeDragRowID, doc, dropValidation, insertAfterDropID, insertBeforeDropID, isMovePending, rowIndex, titleCellAccessor }) {
290
+ const { isOver, setNodeRef } = useDroppable({
291
+ id: getRowDropID(doc.__pageTreeID),
292
+ data: {
293
+ dropType: 'row',
294
+ insertAfterDropID,
295
+ insertBeforeDropID,
296
+ rowID: doc.__pageTreeID
297
+ },
298
+ disabled: isMovePending
299
+ });
300
+ const hasActiveDrag = Boolean(activeDragRowID);
301
+ const isActiveDragRow = activeDragRowID === doc.__pageTreeID;
302
+ return /*#__PURE__*/ _jsx("tr", {
303
+ className: `row-${rowIndex + 1}`,
304
+ "data-drag-over": isOver ? 'true' : 'false',
305
+ "data-drop-valid": hasActiveDrag ? dropValidation?.isValid ? 'true' : 'false' : undefined,
306
+ "data-id": doc.id,
307
+ "data-is-drag-source": isActiveDragRow ? 'true' : 'false',
308
+ "data-page-tree-row": "true",
309
+ ref: setNodeRef,
310
+ children: activeColumns.map((column, columnIndex)=>{
311
+ const { accessor } = column;
312
+ return /*#__PURE__*/ _jsx("td", {
313
+ className: `cell-${accessor.replace(/\./g, '__')}`,
314
+ "data-page-tree-manual-order-cell": accessor === MANUAL_ORDER_COLUMN_ACCESSOR ? 'true' : undefined,
315
+ "data-page-tree-title-cell": accessor === titleCellAccessor ? 'true' : undefined,
316
+ children: column.renderedCells?.[rowIndex] ?? null
317
+ }, columnIndex);
318
+ })
319
+ });
320
+ }
321
+ function HierarchyTable({ activeDragRowID, allDocsByID, allowSameParentDrops, columns, data, isMovePending, titleCellAccessor }) {
322
+ const activeColumns = React.useMemo(()=>columns.filter((column)=>column?.active), [
323
+ columns
324
+ ]);
325
+ const hasManualOrderColumn = React.useMemo(()=>activeColumns.some((column)=>column.accessor === MANUAL_ORDER_COLUMN_ACCESSOR), [
326
+ activeColumns
327
+ ]);
328
+ const insertDropTargets = React.useMemo(()=>buildInsertDropTargets(data), [
329
+ data
330
+ ]);
331
+ const activeDoc = activeDragRowID ? allDocsByID.get(activeDragRowID) ?? null : null;
332
+ const rowDropValidationByID = React.useMemo(()=>{
333
+ if (!activeDoc) {
334
+ return new Map();
335
+ }
336
+ return new Map(data.map((doc)=>[
337
+ doc.__pageTreeID,
338
+ getDropValidationForMode({
339
+ activeDoc,
340
+ allowSameParent: allowSameParentDrops,
341
+ targetDoc: doc
342
+ })
343
+ ]));
344
+ }, [
345
+ activeDoc,
346
+ allowSameParentDrops,
347
+ data
348
+ ]);
349
+ const insertDropValidationByID = React.useMemo(()=>{
350
+ if (!activeDoc) {
351
+ return new Map();
352
+ }
353
+ return new Map(insertDropTargets.map((dropTarget)=>[
354
+ dropTarget.dropID,
355
+ getDropValidationForMode({
356
+ activeDoc,
357
+ allowSameParent: allowSameParentDrops,
358
+ targetDoc: dropTarget.parentID === null ? undefined : allDocsByID.get(dropTarget.parentID) ?? undefined
359
+ })
360
+ ]));
361
+ }, [
362
+ activeDoc,
363
+ allowSameParentDrops,
364
+ allDocsByID,
365
+ insertDropTargets
366
+ ]);
367
+ if (activeColumns.length === 0) {
368
+ return /*#__PURE__*/ _jsx("div", {
369
+ children: "No columns selected"
370
+ });
371
+ }
372
+ return /*#__PURE__*/ _jsx("div", {
373
+ className: [
374
+ 'table-wrap pages-hierarchy-table',
375
+ activeDoc ? 'pages-hierarchy-table--dragging' : ''
376
+ ].filter(Boolean).join(' '),
377
+ "data-page-tree-orderable": hasManualOrderColumn ? 'true' : undefined,
378
+ children: /*#__PURE__*/ _jsx("div", {
379
+ className: "table table--appearance-default",
380
+ children: /*#__PURE__*/ _jsxs("table", {
381
+ cellPadding: "0",
382
+ cellSpacing: "0",
383
+ children: [
384
+ /*#__PURE__*/ _jsx("thead", {
385
+ children: /*#__PURE__*/ _jsx("tr", {
386
+ children: activeColumns.map((column, index)=>/*#__PURE__*/ _jsx("th", {
387
+ "data-page-tree-manual-order-cell": column.accessor === MANUAL_ORDER_COLUMN_ACCESSOR ? 'true' : undefined,
388
+ "data-page-tree-title-cell": column.accessor === titleCellAccessor ? 'true' : undefined,
389
+ id: `heading-${column.accessor.replace(/\./g, '__')}`,
390
+ children: column.Heading
391
+ }, index))
392
+ })
393
+ }),
394
+ /*#__PURE__*/ _jsxs("tbody", {
395
+ children: [
396
+ insertDropTargets[0] ? /*#__PURE__*/ _jsx(HierarchyInsertRow, {
397
+ activeColumnsCount: activeColumns.length,
398
+ activeDragRowID: activeDragRowID,
399
+ dropTarget: insertDropTargets[0],
400
+ dropValidation: insertDropValidationByID.get(insertDropTargets[0].dropID),
401
+ isMovePending: isMovePending
402
+ }, insertDropTargets[0].dropID) : null,
403
+ data.map((doc, rowIndex)=>/*#__PURE__*/ _jsxs(React.Fragment, {
404
+ children: [
405
+ /*#__PURE__*/ _jsx(HierarchyTableRow, {
406
+ activeColumns: activeColumns,
407
+ activeDragRowID: activeDragRowID,
408
+ doc: doc,
409
+ dropValidation: rowDropValidationByID.get(doc.__pageTreeID),
410
+ insertAfterDropID: insertDropTargets[rowIndex + 1].dropID,
411
+ insertBeforeDropID: insertDropTargets[rowIndex].dropID,
412
+ isMovePending: isMovePending,
413
+ rowIndex: rowIndex,
414
+ titleCellAccessor: titleCellAccessor
415
+ }),
416
+ insertDropTargets[rowIndex + 1] ? /*#__PURE__*/ _jsx(HierarchyInsertRow, {
417
+ activeColumnsCount: activeColumns.length,
418
+ activeDragRowID: activeDragRowID,
419
+ dropTarget: insertDropTargets[rowIndex + 1],
420
+ dropValidation: insertDropValidationByID.get(insertDropTargets[rowIndex + 1].dropID),
421
+ isMovePending: isMovePending
422
+ }, insertDropTargets[rowIndex + 1].dropID) : null
423
+ ]
424
+ }, doc.__pageTreeID))
425
+ ]
426
+ })
427
+ ]
428
+ })
429
+ })
430
+ });
431
+ }
432
+ export default function PageTreeListViewClient({ allDocs, badgeConfig, canMoveDocs, columnState, orderableFieldName, parentFieldSlug, query, sourceDocs, useAsTitle, ...props }) {
433
+ const router = useRouter();
434
+ const searchParams = useSearchParams();
435
+ const { config } = useConfig();
436
+ const locale = useLocale();
437
+ const { i18n } = useTranslation();
438
+ const [activeDragRowID, setActiveDragRowID] = React.useState(null);
439
+ const [activeDropTarget, setActiveDropTarget] = React.useState(null);
440
+ const [collapsedIDs, setCollapsedIDs] = React.useState(()=>new Set());
441
+ const [localSourceDocs, setLocalSourceDocs] = React.useState(sourceDocs);
442
+ const [pendingMoveRowID, setPendingMoveRowID] = React.useState(null);
443
+ React.useEffect(()=>{
444
+ setLocalSourceDocs(sourceDocs);
445
+ }, [
446
+ sourceDocs
447
+ ]);
448
+ const toggleRow = React.useCallback((rowID)=>{
449
+ setCollapsedIDs((currentState)=>{
450
+ const nextState = new Set(currentState);
451
+ if (nextState.has(rowID)) {
452
+ nextState.delete(rowID);
453
+ } else {
454
+ nextState.add(rowID);
455
+ }
456
+ return nextState;
457
+ });
458
+ }, []);
459
+ const currentSort = React.useMemo(()=>{
460
+ const searchParamSort = searchParams.getAll('sort');
461
+ if (searchParamSort.length > 0) {
462
+ return normalizeSort(searchParamSort);
463
+ }
464
+ return normalizeSort(query.sort);
465
+ }, [
466
+ query.sort,
467
+ searchParams
468
+ ]);
469
+ const manualOrderIsActive = Boolean(orderableFieldName && (currentSort === orderableFieldName || currentSort === `-${orderableFieldName}`));
470
+ const currentLimit = React.useMemo(()=>normalizePositiveInt(searchParams.get('limit'), typeof query.limit === 'number' && query.limit > 0 ? query.limit : 10), [
471
+ query.limit,
472
+ searchParams
473
+ ]);
474
+ const currentRequestedPage = React.useMemo(()=>normalizePositiveInt(searchParams.get('page'), typeof query.page === 'number' && query.page > 0 ? query.page : 1), [
475
+ query.page,
476
+ searchParams
477
+ ]);
478
+ const liveAllDocs = React.useMemo(()=>buildPageTreeDocs(localSourceDocs, {
479
+ parentFieldSlug,
480
+ sort: currentSort
481
+ }), [
482
+ currentSort,
483
+ localSourceDocs,
484
+ parentFieldSlug
485
+ ]);
486
+ const collapseResetKey = React.useMemo(()=>JSON.stringify([
487
+ props.collectionSlug,
488
+ props.viewType,
489
+ searchParams.toString()
490
+ ]), [
491
+ props.collectionSlug,
492
+ props.viewType,
493
+ searchParams
494
+ ]);
495
+ const hierarchyValue = React.useMemo(()=>({
496
+ activeDragRowID,
497
+ canMoveDocs,
498
+ collapsedIDs,
499
+ pendingMoveRowID,
500
+ toggleRow
501
+ }), [
502
+ activeDragRowID,
503
+ canMoveDocs,
504
+ collapsedIDs,
505
+ pendingMoveRowID,
506
+ toggleRow
507
+ ]);
508
+ React.useEffect(()=>{
509
+ setActiveDragRowID(null);
510
+ setActiveDropTarget(null);
511
+ setCollapsedIDs(new Set());
512
+ }, [
513
+ collapseResetKey
514
+ ]);
515
+ const visibleDocs = React.useMemo(()=>getVisibleTreeDocs(liveAllDocs, collapsedIDs), [
516
+ liveAllDocs,
517
+ collapsedIDs
518
+ ]);
519
+ const paginatedData = React.useMemo(()=>buildPaginatedData(visibleDocs, currentLimit, currentRequestedPage), [
520
+ currentLimit,
521
+ currentRequestedPage,
522
+ visibleDocs
523
+ ]);
524
+ const paginatedDocs = paginatedData.docs;
525
+ const allDocsByID = React.useMemo(()=>new Map(liveAllDocs.map((doc)=>[
526
+ doc.__pageTreeID,
527
+ doc
528
+ ])), [
529
+ liveAllDocs
530
+ ]);
531
+ const paginatedDocsByID = React.useMemo(()=>new Map(paginatedDocs.map((doc)=>[
532
+ doc.__pageTreeID,
533
+ doc
534
+ ])), [
535
+ paginatedDocs
536
+ ]);
537
+ const renderedCellIndexByDocID = React.useMemo(()=>new Map(allDocs.map((doc, index)=>[
538
+ doc.__pageTreeID,
539
+ index
540
+ ])), [
541
+ allDocs
542
+ ]);
543
+ const normalizedColumnState = React.useMemo(()=>normalizePageTreeColumnState(columnState, useAsTitle), [
544
+ columnState,
545
+ useAsTitle
546
+ ]);
547
+ const paginatedColumnState = React.useMemo(()=>sliceColumnState(normalizedColumnState, paginatedDocs, renderedCellIndexByDocID), [
548
+ normalizedColumnState,
549
+ paginatedDocs,
550
+ renderedCellIndexByDocID
551
+ ]);
552
+ const tableColumns = React.useMemo(()=>buildTableColumns({
553
+ badgeConfig,
554
+ columnState: paginatedColumnState,
555
+ docs: paginatedDocs,
556
+ enableRowSelections: props.enableRowSelections,
557
+ orderableFieldName,
558
+ parentFieldSlug,
559
+ t: i18n.t,
560
+ useAsTitle
561
+ }), [
562
+ paginatedColumnState,
563
+ paginatedDocs,
564
+ badgeConfig,
565
+ orderableFieldName,
566
+ parentFieldSlug,
567
+ props.enableRowSelections,
568
+ i18n.t,
569
+ useAsTitle
570
+ ]);
571
+ const activeDragDoc = activeDragRowID ? paginatedDocsByID.get(activeDragRowID) ?? null : null;
572
+ const activeDragPreviewPath = React.useMemo(()=>{
573
+ if (!activeDragDoc) {
574
+ return null;
575
+ }
576
+ if (!activeDropTarget) {
577
+ return buildDocSlugPath({
578
+ doc: activeDragDoc,
579
+ docsByID: allDocsByID
580
+ });
581
+ }
582
+ const targetDoc = getDropTargetParentDoc({
583
+ docsByID: allDocsByID,
584
+ dropTarget: activeDropTarget
585
+ });
586
+ const dropValidation = getDropValidationForMode({
587
+ activeDoc: activeDragDoc,
588
+ allowSameParent: manualOrderIsActive,
589
+ targetDoc: targetDoc ?? undefined
590
+ });
591
+ if (!dropValidation.isValid) {
592
+ return buildDocSlugPath({
593
+ doc: activeDragDoc,
594
+ docsByID: allDocsByID
595
+ });
596
+ }
597
+ return buildProspectiveDocSlugPath({
598
+ activeDoc: activeDragDoc,
599
+ docsByID: allDocsByID,
600
+ targetDoc: targetDoc ?? undefined
601
+ });
602
+ }, [
603
+ activeDragDoc,
604
+ activeDropTarget,
605
+ allDocsByID,
606
+ manualOrderIsActive
607
+ ]);
608
+ const isMovePending = pendingMoveRowID !== null;
609
+ const sensors = useSensors(useSensor(PointerSensor, {
610
+ activationConstraint: {
611
+ distance: 4
612
+ }
613
+ }));
614
+ const moveDocument = React.useCallback(async (args)=>{
615
+ const { parentID, rowID } = args;
616
+ const params = new URLSearchParams();
617
+ if (locale?.code) {
618
+ params.set('locale', locale.code);
619
+ }
620
+ const response = await fetch(`${config.routes.api}/${props.collectionSlug}/${encodeURIComponent(rowID)}/move${params.size > 0 ? `?${params.toString()}` : ''}`, {
621
+ body: JSON.stringify({
622
+ parentID
623
+ }),
624
+ credentials: 'include',
625
+ headers: {
626
+ 'Accept-Language': i18n.language,
627
+ 'Content-Type': 'application/json'
628
+ },
629
+ method: 'POST'
630
+ });
631
+ const result = await response.json().catch(()=>null);
632
+ if (!response.ok) {
633
+ throw new Error(result?.message ?? 'Could not move document.');
634
+ }
635
+ return result;
636
+ }, [
637
+ config.routes.api,
638
+ i18n.language,
639
+ locale?.code,
640
+ props.collectionSlug
641
+ ]);
642
+ const reorderDocument = React.useCallback(async (args)=>{
643
+ const { movedDocID, newKeyWillBe, targetDoc } = args;
644
+ const targetID = getPayloadDocID(targetDoc);
645
+ if (!orderableFieldName || targetID === undefined) {
646
+ return undefined;
647
+ }
648
+ const params = new URLSearchParams();
649
+ if (locale?.code) {
650
+ params.set('locale', locale.code);
651
+ }
652
+ const response = await fetch(`${config.routes.api}/reorder${params.size > 0 ? `?${params.toString()}` : ''}`, {
653
+ body: JSON.stringify({
654
+ collectionSlug: props.collectionSlug,
655
+ docsToMove: [
656
+ movedDocID
657
+ ],
658
+ newKeyWillBe,
659
+ orderableFieldName,
660
+ target: {
661
+ id: targetID,
662
+ key: targetDoc[orderableFieldName]
663
+ }
664
+ }),
665
+ credentials: 'include',
666
+ headers: {
667
+ 'Accept-Language': i18n.language,
668
+ 'Content-Type': 'application/json'
669
+ },
670
+ method: 'POST'
671
+ });
672
+ const result = await response.json().catch(()=>null);
673
+ if (response.status === 403) {
674
+ throw new Error('You do not have permission to reorder these rows');
675
+ }
676
+ if (!response.ok) {
677
+ throw new Error('Failed to reorder. This can happen if you reorder several rows too quickly. Please try again.');
678
+ }
679
+ if (result?.message === 'initial migration') {
680
+ throw new Error('You have enabled "orderable" on a collection with existing documents and this is the first time you have sorted documents. We have run an automatic migration to add an initial order to the documents. Please refresh the page and try again.');
681
+ }
682
+ return result?.orderValues?.[0];
683
+ }, [
684
+ config.routes.api,
685
+ i18n.language,
686
+ locale?.code,
687
+ orderableFieldName,
688
+ props.collectionSlug
689
+ ]);
690
+ const handleManualOrderMoveEnd = React.useCallback(async (args)=>{
691
+ const { activeDoc, dropTarget, rowID } = args;
692
+ if (!orderableFieldName) {
693
+ return;
694
+ }
695
+ const targetDoc = getDropTargetParentDoc({
696
+ docsByID: allDocsByID,
697
+ dropTarget
698
+ });
699
+ const dropValidation = getManualOrderDropValidation({
700
+ activeDoc,
701
+ targetDoc: targetDoc ?? undefined
702
+ });
703
+ if (!dropValidation.isValid) {
704
+ if (!shouldSilenceMoveMessage(dropValidation.message)) {
705
+ toast.error(dropValidation.message ?? 'Could not move document.');
706
+ }
707
+ return;
708
+ }
709
+ const placement = getOrderPlacementFromDropTarget({
710
+ activeDoc,
711
+ docs: paginatedDocs,
712
+ docsByID: allDocsByID,
713
+ dropTarget
714
+ });
715
+ if (!placement) {
716
+ return;
717
+ }
718
+ const currentPlacement = getCurrentOrderPlacement({
719
+ activeDoc,
720
+ docs: paginatedDocs
721
+ });
722
+ const parentChanged = activeDoc.__pageTreeParentID !== placement.parentID;
723
+ const orderChanged = orderPlacementHasChanged(currentPlacement, placement);
724
+ const reorderTarget = orderChanged ? getReorderTargetFromPlacement({
725
+ currentSort,
726
+ docsByID: allDocsByID,
727
+ orderableFieldName,
728
+ placement
729
+ }) : null;
730
+ if (!parentChanged && !reorderTarget) {
731
+ return;
732
+ }
733
+ const movedDocID = getPayloadDocID(activeDoc);
734
+ if (movedDocID === undefined) {
735
+ return;
736
+ }
737
+ const parentDoc = placement.parentID ? allDocsByID.get(placement.parentID) : undefined;
738
+ const nextParentValue = placement.parentID === null ? null : parentDoc ? getPayloadDocID(parentDoc) ?? placement.parentID : placement.parentID;
739
+ let parentMoveSucceeded = false;
740
+ setPendingMoveRowID(rowID);
741
+ try {
742
+ if (parentChanged) {
743
+ await moveDocument({
744
+ parentID: placement.parentID,
745
+ rowID
746
+ });
747
+ parentMoveSucceeded = true;
748
+ }
749
+ const nextOrderValue = reorderTarget ? await reorderDocument({
750
+ movedDocID,
751
+ newKeyWillBe: reorderTarget.newKeyWillBe,
752
+ targetDoc: reorderTarget.targetDoc
753
+ }) : undefined;
754
+ if (parentChanged || typeof nextOrderValue === 'string') {
755
+ setLocalSourceDocs((currentDocs)=>currentDocs.map((doc)=>String(doc.id ?? '') === String(movedDocID) ? {
756
+ ...doc,
757
+ ...parentChanged ? {
758
+ [parentFieldSlug]: nextParentValue
759
+ } : {},
760
+ ...typeof nextOrderValue === 'string' ? {
761
+ [orderableFieldName]: nextOrderValue
762
+ } : {}
763
+ } : doc));
764
+ }
765
+ if (parentChanged) {
766
+ toast.success(`Moved "${getDocDisplayLabel(activeDoc)}".`);
767
+ }
768
+ React.startTransition(()=>{
769
+ router.refresh();
770
+ });
771
+ } catch (error) {
772
+ const message = error instanceof Error && error.message ? error.message : 'Could not move document.';
773
+ toast.error(parentMoveSucceeded ? `Moved "${getDocDisplayLabel(activeDoc)}", but could not update its manual order. ${message}` : message);
774
+ if (parentMoveSucceeded) {
775
+ React.startTransition(()=>{
776
+ router.refresh();
777
+ });
778
+ }
779
+ } finally{
780
+ setPendingMoveRowID(null);
781
+ }
782
+ }, [
783
+ allDocsByID,
784
+ currentSort,
785
+ moveDocument,
786
+ orderableFieldName,
787
+ paginatedDocs,
788
+ parentFieldSlug,
789
+ reorderDocument,
790
+ router
791
+ ]);
792
+ const handleDragCancel = React.useCallback(()=>{
793
+ setActiveDragRowID(null);
794
+ setActiveDropTarget(null);
795
+ }, []);
796
+ const handleDragStart = React.useCallback((event)=>{
797
+ const dragData = getPageTreeDragData(event.active.data.current);
798
+ if (!canMoveDocs || isMovePending) {
799
+ return;
800
+ }
801
+ const rowID = dragData.rowID;
802
+ if (typeof rowID === 'string' && paginatedDocsByID.has(rowID)) {
803
+ setActiveDragRowID(rowID);
804
+ setActiveDropTarget(null);
805
+ }
806
+ }, [
807
+ canMoveDocs,
808
+ isMovePending,
809
+ paginatedDocsByID
810
+ ]);
811
+ const handleDragOver = React.useCallback((event)=>{
812
+ const overData = event.over?.data.current;
813
+ if (!overData) {
814
+ setActiveDropTarget(null);
815
+ return;
816
+ }
817
+ if (overData.dropType === 'insert') {
818
+ setActiveDropTarget(overData);
819
+ return;
820
+ }
821
+ if (overData.dropType === 'row') {
822
+ setActiveDropTarget(overData);
823
+ return;
824
+ }
825
+ setActiveDropTarget(null);
826
+ }, []);
827
+ const handleDragEnd = React.useCallback(async (event)=>{
828
+ const dragData = getPageTreeDragData(event.active.data.current);
829
+ const rowID = dragData.rowID;
830
+ const activeDoc = typeof rowID === 'string' ? paginatedDocsByID.get(rowID) ?? null : null;
831
+ const overData = event.over?.data.current;
832
+ setActiveDragRowID(null);
833
+ setActiveDropTarget(null);
834
+ if (!rowID || !activeDoc || !overData) {
835
+ return;
836
+ }
837
+ if (manualOrderIsActive && orderableFieldName) {
838
+ await handleManualOrderMoveEnd({
839
+ activeDoc,
840
+ dropTarget: overData,
841
+ rowID
842
+ });
843
+ return;
844
+ }
845
+ const targetDoc = getDropTargetParentDoc({
846
+ docsByID: allDocsByID,
847
+ dropTarget: overData
848
+ });
849
+ const dropValidation = getDropValidation({
850
+ activeDoc,
851
+ targetDoc: targetDoc ?? undefined
852
+ });
853
+ if (!dropValidation.isValid) {
854
+ if (!shouldSilenceMoveMessage(dropValidation.message)) {
855
+ toast.error(dropValidation.message ?? 'Could not move document.');
856
+ }
857
+ return;
858
+ }
859
+ setPendingMoveRowID(rowID);
860
+ try {
861
+ await moveDocument({
862
+ parentID: dropValidation.parentID,
863
+ rowID
864
+ });
865
+ toast.success(`Moved "${getDocDisplayLabel(activeDoc)}".`);
866
+ React.startTransition(()=>{
867
+ router.refresh();
868
+ });
869
+ } catch (error) {
870
+ const message = error instanceof Error && error.message ? error.message : 'Could not move document.';
871
+ if (shouldSilenceMoveMessage(message)) {
872
+ return;
873
+ }
874
+ toast.error(message);
875
+ } finally{
876
+ setPendingMoveRowID(null);
877
+ }
878
+ }, [
879
+ allDocsByID,
880
+ handleManualOrderMoveEnd,
881
+ manualOrderIsActive,
882
+ moveDocument,
883
+ orderableFieldName,
884
+ paginatedDocsByID,
885
+ router
886
+ ]);
887
+ const handleDragEndSync = React.useCallback((event)=>{
888
+ void handleDragEnd(event);
889
+ }, [
890
+ handleDragEnd
891
+ ]);
892
+ const tableNode = React.useMemo(()=>/*#__PURE__*/ _jsxs(DndContext, {
893
+ collisionDetection: pageTreeCollisionDetectionStrategy,
894
+ onDragCancel: handleDragCancel,
895
+ onDragEnd: handleDragEndSync,
896
+ onDragOver: handleDragOver,
897
+ onDragStart: handleDragStart,
898
+ sensors: sensors,
899
+ children: [
900
+ /*#__PURE__*/ _jsx(HierarchyTable, {
901
+ activeDragRowID: activeDragRowID,
902
+ allDocsByID: allDocsByID,
903
+ allowSameParentDrops: manualOrderIsActive,
904
+ columns: tableColumns,
905
+ data: paginatedDocs,
906
+ isMovePending: isMovePending,
907
+ titleCellAccessor: useAsTitle
908
+ }),
909
+ /*#__PURE__*/ _jsx(DragOverlay, {
910
+ dropAnimation: null,
911
+ style: {
912
+ cursor: 'grabbing'
913
+ },
914
+ children: activeDragPreviewPath ? /*#__PURE__*/ _jsx("div", {
915
+ className: "pages-hierarchy-drag-overlay",
916
+ children: activeDragPreviewPath
917
+ }) : null
918
+ })
919
+ ]
920
+ }), [
921
+ activeDragPreviewPath,
922
+ activeDragRowID,
923
+ handleDragCancel,
924
+ handleDragEndSync,
925
+ handleDragOver,
926
+ handleDragStart,
927
+ isMovePending,
928
+ allDocsByID,
929
+ manualOrderIsActive,
930
+ paginatedDocs,
931
+ sensors,
932
+ tableColumns,
933
+ useAsTitle
934
+ ]);
935
+ return /*#__PURE__*/ _jsx("div", {
936
+ className: styles.root,
937
+ children: /*#__PURE__*/ _jsx(PageTreeProvider, {
938
+ value: hierarchyValue,
939
+ children: /*#__PURE__*/ _jsx(ListQueryProvider, {
940
+ collectionSlug: props.collectionSlug,
941
+ data: paginatedData,
942
+ modifySearchParams: true,
943
+ orderableFieldName: orderableFieldName,
944
+ query: {
945
+ ...query,
946
+ limit: currentLimit,
947
+ page: paginatedData.page,
948
+ sort: currentSort
949
+ },
950
+ children: /*#__PURE__*/ _jsx(DefaultListView, {
951
+ ...props,
952
+ columnState: paginatedColumnState,
953
+ Table: tableNode
954
+ })
955
+ })
956
+ })
957
+ });
958
+ }
959
+
960
+ //# sourceMappingURL=PageTreeListView.client.js.map