@sanity/orderable-document-list 1.2.1 → 1.2.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Sanity.io
3
+ Copyright (c) 2025 Sanity.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -17,12 +17,12 @@ A Sanity Studio with [Desk Structure](https://www.sanity.io/docs/structure-build
17
17
 
18
18
  ```ts
19
19
  import {defineConfig} from 'sanity'
20
- import {deskTool, StructureBuilder} from 'sanity/structure'
20
+ import {structureTool, StructureBuilder} from 'sanity/structure'
21
21
 
22
22
  export default defineConfig({
23
23
  //...
24
24
  plugins: [
25
- deskTool({
25
+ structureTool({
26
26
  structure: (S, context) => {
27
27
  /* Structure code */
28
28
  },
@@ -54,13 +54,13 @@ The config parameter requires `type`, `S` and `context`. It also accepts `title`
54
54
 
55
55
  ```ts
56
56
  import {defineConfig} from 'sanity'
57
- import {deskTool, StructureBuilder} from 'sanity/structure'
57
+ import {structureTool, StructureBuilder} from 'sanity/structure'
58
58
  import {orderableDocumentListDeskItem} from '@sanity/orderable-document-list'
59
59
 
60
60
  export default defineConfig({
61
61
  //...
62
62
  plugins: [
63
- deskTool({
63
+ structureTool({
64
64
  structure: (S, context) => {
65
65
  return S.list()
66
66
  .title('Content')
@@ -115,13 +115,13 @@ You can configure the placement of new documents by setting `newItemPosition` to
115
115
  ```js
116
116
  // sanity.config.js
117
117
  import {defineConfig} from "sanity";
118
- import {deskTool, StructureBuilder} from "sanity/structure";
118
+ import {structureTool, StructureBuilder} from "sanity/structure";
119
119
  import {orderRankField, orderRankOrdering} from '@sanity/orderable-document-list'
120
120
 
121
121
  export default defineConfig({
122
122
  //...
123
123
  plugins: [
124
- deskTool({structure: (S, context) => {/* snip */}})
124
+ structureTool({structure: (S, context) => {/* snip */}})
125
125
  ],
126
126
  schema: {
127
127
  types: (previousTypes) => {
package/lib/index.cjs ADDED
@@ -0,0 +1,499 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: !0 });
3
+ var sanity = require("sanity"), lexorank = require("lexorank"), icons = require("@sanity/icons"), jsxRuntime = require("react/jsx-runtime"), react = require("react"), ui = require("@sanity/ui"), sanityPluginUtils = require("sanity-plugin-utils"), dnd = require("@hello-pangea/dnd"), structure = require("sanity/structure");
4
+ const ORDER_FIELD_NAME = "orderRank", API_VERSION = "2021-09-01";
5
+ function initialRank(compareRankValue = "", newItemPosition = "after") {
6
+ const compareRank = compareRankValue ? lexorank.LexoRank.parse(compareRankValue) : lexorank.LexoRank.min();
7
+ return (newItemPosition === "before" ? compareRank.genPrev().genPrev() : compareRank.genNext().genNext()).toString();
8
+ }
9
+ const orderRankField = (config) => {
10
+ if (!config?.type)
11
+ throw new Error(
12
+ `
13
+ type must be provided.
14
+ Example: orderRankField({type: 'category'})
15
+ `
16
+ );
17
+ const { type, newItemPosition = "after" } = config;
18
+ return sanity.defineField({
19
+ title: "Order Rank",
20
+ readOnly: !0,
21
+ hidden: !0,
22
+ ...config,
23
+ name: ORDER_FIELD_NAME,
24
+ type: "string",
25
+ initialValue: async (p, { getClient }) => {
26
+ const direction = newItemPosition === "before" ? "asc" : "desc", lastDocOrderRank = await getClient({ apiVersion: API_VERSION }).fetch(
27
+ `*[_type == $type]|order(@[$order] ${direction})[0][$order]`,
28
+ { type, order: ORDER_FIELD_NAME },
29
+ { tag: "orderable-document-list.last-doc-order-rank" }
30
+ );
31
+ return initialRank(lastDocOrderRank, newItemPosition);
32
+ }
33
+ });
34
+ }, orderRankOrdering = {
35
+ title: "Ordered",
36
+ name: "ordered",
37
+ by: [{ field: ORDER_FIELD_NAME, direction: "asc" }]
38
+ }, OrderableContext = react.createContext({});
39
+ function Document({
40
+ doc,
41
+ increment,
42
+ entities,
43
+ index,
44
+ isFirst,
45
+ isLast,
46
+ dragBadge
47
+ }) {
48
+ const { showIncrements } = react.useContext(OrderableContext), schema = sanity.useSchema(), router = structure.usePaneRouter(), { ChildLink, groupIndex, routerPanesState } = router, currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || !1, pressed = currentDoc === doc._id || currentDoc === doc._id.replace("drafts.", ""), selected = pressed && routerPanesState.length === groupIndex + 2, Link = react.useMemo(
49
+ () => function(linkProps) {
50
+ return /* @__PURE__ */ jsxRuntime.jsx(ChildLink, { ...linkProps, childId: doc._id });
51
+ },
52
+ [ChildLink, doc._id]
53
+ );
54
+ return /* @__PURE__ */ jsxRuntime.jsx(
55
+ sanity.PreviewCard,
56
+ {
57
+ __unstable_focusRing: !0,
58
+ as: Link,
59
+ "data-as": "a",
60
+ "data-ui": "PaneItem",
61
+ radius: 2,
62
+ pressed,
63
+ selected,
64
+ sizing: "border",
65
+ tabIndex: -1,
66
+ tone: "inherit",
67
+ width: "100%",
68
+ flex: 1,
69
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", children: [
70
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { paddingX: 2, style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, children: /* @__PURE__ */ jsxRuntime.jsx(icons.DragHandleIcon, { cursor: "grab" }) }) }),
71
+ showIncrements && /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { style: { flexShrink: 0 }, align: "center", gap: 1, paddingRight: 1, children: [
72
+ /* @__PURE__ */ jsxRuntime.jsx(
73
+ ui.Button,
74
+ {
75
+ padding: 2,
76
+ mode: "ghost",
77
+ onClick: () => increment(index, index + -1, doc._id, entities),
78
+ disabled: isFirst,
79
+ icon: icons.ChevronUpIcon
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsxRuntime.jsx(
83
+ ui.Button,
84
+ {
85
+ padding: 2,
86
+ mode: "ghost",
87
+ disabled: isLast,
88
+ onClick: () => increment(index, index + 1, doc._id, entities),
89
+ icon: icons.ChevronDownIcon
90
+ }
91
+ )
92
+ ] }),
93
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { style: { width: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { flex: 1, align: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
94
+ sanity.Preview,
95
+ {
96
+ layout: "default",
97
+ value: doc,
98
+ schemaType: schema.get(doc._type)
99
+ }
100
+ ) }) }),
101
+ dragBadge && /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { tone: "default", marginRight: 4, radius: 5, children: /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarCounter, { count: dragBadge }) })
102
+ ] })
103
+ }
104
+ );
105
+ }
106
+ function lexicographicalSort(a, b) {
107
+ return !a[ORDER_FIELD_NAME] || !b[ORDER_FIELD_NAME] ? 0 : a[ORDER_FIELD_NAME] < b[ORDER_FIELD_NAME] ? -1 : a[ORDER_FIELD_NAME] > b[ORDER_FIELD_NAME] ? 1 : 0;
108
+ }
109
+ const reorderDocuments = ({
110
+ entities,
111
+ selectedIds,
112
+ source,
113
+ destination
114
+ }) => {
115
+ const startIndex = source.index, endIndex = destination.index, isMovingUp = startIndex > endIndex, selectedItems = entities.filter((item) => selectedIds.includes(item._id)), message = [
116
+ "Moved",
117
+ selectedItems.length === 1 ? "1 document" : `${selectedItems.length} documents`,
118
+ isMovingUp ? "up" : "down",
119
+ "from position",
120
+ `${startIndex + 1} to ${endIndex + 1}`
121
+ ].join(" "), { all, selected } = entities.reduce(
122
+ (acc, cur, curIndex) => {
123
+ if (selectedIds.includes(cur._id))
124
+ return { all: acc.all, selected: acc.selected };
125
+ if (curIndex === endIndex) {
126
+ const prevIndex = curIndex - 1, prevRank = entities[prevIndex]?.[ORDER_FIELD_NAME] ? lexorank.LexoRank.parse(entities[prevIndex]?.[ORDER_FIELD_NAME]) : lexorank.LexoRank.min(), curRank = lexorank.LexoRank.parse(entities[curIndex][ORDER_FIELD_NAME]), nextIndex = curIndex + 1, nextRank = entities[nextIndex]?.[ORDER_FIELD_NAME] ? lexorank.LexoRank.parse(entities[nextIndex]?.[ORDER_FIELD_NAME]) : lexorank.LexoRank.max();
127
+ let betweenRank = isMovingUp ? prevRank.between(curRank) : curRank.between(nextRank);
128
+ for (let selectedIndex = 0; selectedIndex < selectedItems.length; selectedIndex += 1)
129
+ selectedItems[selectedIndex][ORDER_FIELD_NAME] = betweenRank.toString(), betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank);
130
+ return {
131
+ // The `all` array gets sorted by order field later anyway
132
+ // so that this probably isn't necessary ¯\_(ツ)_/¯
133
+ all: isMovingUp ? [...acc.all, ...selectedItems, cur] : [...acc.all, cur, ...selectedItems],
134
+ selected: selectedItems
135
+ };
136
+ }
137
+ return { all: [...acc.all, cur], selected: acc.selected };
138
+ },
139
+ { all: [], selected: [] }
140
+ ), patches = selected.flatMap((doc) => {
141
+ const docPatches = [
142
+ [
143
+ doc._id,
144
+ {
145
+ set: {
146
+ [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME]
147
+ }
148
+ }
149
+ ]
150
+ ];
151
+ return doc._id.startsWith("drafts.") && doc.hasPublished && docPatches.push([
152
+ doc._id.replace("drafts.", ""),
153
+ {
154
+ set: {
155
+ [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME]
156
+ }
157
+ }
158
+ ]), docPatches;
159
+ });
160
+ return { newOrder: all.sort(lexicographicalSort), patches, message };
161
+ };
162
+ function useSanityClient() {
163
+ return sanity.useClient({ apiVersion: API_VERSION });
164
+ }
165
+ const getItemStyle = (draggableStyle, itemIsUpdating) => ({
166
+ userSelect: "none",
167
+ transition: "opacity 500ms ease-in-out",
168
+ opacity: itemIsUpdating ? 0.2 : 1,
169
+ pointerEvents: itemIsUpdating ? "none" : void 0,
170
+ ...draggableStyle
171
+ }), cardTone = (settings) => {
172
+ const { isDuplicate, isGhosting, isDragging, isSelected } = settings;
173
+ if (isGhosting) return "transparent";
174
+ if (isDragging || isSelected) return "primary";
175
+ if (isDuplicate) return "caution";
176
+ };
177
+ function DraggableList({ data, listIsUpdating, setListIsUpdating }) {
178
+ const toast = ui.useToast(), router = structure.usePaneRouter(), { groupIndex, routerPanesState } = router, currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || !1, [orderedData, setOrderedData] = react.useState(data);
179
+ react.useEffect(() => {
180
+ listIsUpdating || setOrderedData(data);
181
+ }, [data]);
182
+ const [draggingId, setDraggingId] = react.useState(""), [selectedIds, setSelectedIds] = react.useState(currentDoc ? [currentDoc] : []), clearSelected = react.useCallback(() => setSelectedIds([]), [setSelectedIds]), handleSelect = react.useCallback(
183
+ (clickedId, index, nativeEvent) => {
184
+ const isSelected = selectedIds.includes(clickedId), selectMultiple = nativeEvent.shiftKey, selectAdditional = navigator.appVersion.indexOf("Win") !== -1 ? nativeEvent.ctrlKey : nativeEvent.metaKey;
185
+ let updatedIds = [];
186
+ if (!selectMultiple && !selectAdditional)
187
+ return setSelectedIds([clickedId]);
188
+ if (selectMultiple && nativeEvent.preventDefault(), selectMultiple && !isSelected) {
189
+ const lastSelectedId = selectedIds[selectedIds.length - 1], lastSelectedIndex = orderedData.findIndex((item) => item._id === lastSelectedId), firstSelected = index < lastSelectedIndex ? index : lastSelectedIndex, lastSelected = index > lastSelectedIndex ? index : lastSelectedIndex, betweenIds = orderedData.filter((item, itemIndex) => itemIndex > firstSelected && itemIndex < lastSelected).map((item) => item._id);
190
+ updatedIds = [...selectedIds, ...betweenIds, clickedId];
191
+ } else isSelected ? updatedIds = selectedIds.filter((id) => id !== clickedId) : updatedIds = [...selectedIds, clickedId];
192
+ return setSelectedIds(updatedIds);
193
+ },
194
+ [setSelectedIds, orderedData, selectedIds]
195
+ ), client = useSanityClient(), transactPatches = react.useCallback(
196
+ async (patches, message) => {
197
+ const transaction = client.transaction();
198
+ patches.forEach(([docId, ops]) => transaction.patch(docId, ops));
199
+ try {
200
+ const updated = await transaction.commit({
201
+ visibility: "async",
202
+ tag: "orderable-document-list.reorder"
203
+ });
204
+ clearSelected(), setDraggingId(""), setListIsUpdating(!1), toast.push({
205
+ title: `${updated.results.length === 1 ? "1 document" : `${updated.results.length} documents`} reordered`,
206
+ status: "success",
207
+ description: message
208
+ });
209
+ } catch {
210
+ setDraggingId(""), setListIsUpdating(!1), toast.push({
211
+ title: "Reordering failed",
212
+ status: "error"
213
+ });
214
+ }
215
+ },
216
+ [client, setDraggingId, clearSelected, setListIsUpdating, toast]
217
+ ), handleDragEnd = react.useCallback(
218
+ (result, entities) => {
219
+ setDraggingId("");
220
+ const { source, destination, draggableId } = result ?? {};
221
+ if (source?.index === destination?.index || !entities?.length || !draggableId) return;
222
+ const effectedIds = selectedIds?.length ? selectedIds : [draggableId];
223
+ if (!effectedIds?.length) return;
224
+ setListIsUpdating(!0), setSelectedIds(effectedIds);
225
+ const { newOrder, patches, message } = reorderDocuments({
226
+ entities,
227
+ selectedIds: effectedIds,
228
+ source,
229
+ destination
230
+ });
231
+ newOrder?.length && setOrderedData(newOrder), patches?.length && transactPatches(patches, message);
232
+ },
233
+ [selectedIds, setDraggingId, setSelectedIds, transactPatches, setListIsUpdating]
234
+ ), handleDragStart = react.useCallback(
235
+ (start) => {
236
+ const id = start.draggableId;
237
+ selectedIds.includes(id) || clearSelected(), setDraggingId(id);
238
+ },
239
+ [selectedIds, clearSelected, setDraggingId]
240
+ ), incrementIndex = react.useCallback(
241
+ (shiftFrom, shiftTo, id, entities) => handleDragEnd({
242
+ draggableId: id,
243
+ source: { index: shiftFrom },
244
+ destination: { index: shiftTo }
245
+ }, entities),
246
+ [handleDragEnd]
247
+ ), onWindowKeyDown = react.useCallback(
248
+ (event) => {
249
+ event.key === "Escape" && clearSelected();
250
+ },
251
+ [clearSelected]
252
+ );
253
+ react.useEffect(() => (window.addEventListener("keydown", onWindowKeyDown), () => {
254
+ window.removeEventListener("keydown", onWindowKeyDown);
255
+ }), [onWindowKeyDown]);
256
+ const duplicateOrders = react.useMemo(() => {
257
+ if (!orderedData.length) return [];
258
+ const orderField = orderedData.map((item) => item[ORDER_FIELD_NAME]);
259
+ return orderField.filter((item, index) => orderField.indexOf(item) !== index);
260
+ }, [orderedData]), onDragEnd = react.useCallback(
261
+ (result) => handleDragEnd(result, orderedData),
262
+ [orderedData, handleDragEnd]
263
+ );
264
+ return /* @__PURE__ */ jsxRuntime.jsx(dnd.DragDropContext, { onDragStart: handleDragStart, onDragEnd, children: /* @__PURE__ */ jsxRuntime.jsx(dnd.Droppable, { droppableId: "documentSortZone", children: (provided) => /* @__PURE__ */ jsxRuntime.jsxs("div", { ...provided.droppableProps, ref: provided.innerRef, children: [
265
+ orderedData.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
266
+ dnd.Draggable,
267
+ {
268
+ draggableId: item._id,
269
+ index,
270
+ children: (innerProvided, innerSnapshot) => {
271
+ const isSelected = selectedIds.includes(item._id), isDragging = innerSnapshot.isDragging, isGhosting = !!(!isDragging && draggingId && isSelected), isUpdating = listIsUpdating && isSelected, isDisabled = !item[ORDER_FIELD_NAME], isDuplicate = duplicateOrders.includes(item[ORDER_FIELD_NAME]), tone = cardTone({ isDuplicate, isGhosting, isDragging, isSelected }), selectedCount = selectedIds.length, dragBadge = isDragging && selectedCount > 1 ? selectedCount : !1;
272
+ return /* @__PURE__ */ jsxRuntime.jsx(
273
+ "div",
274
+ {
275
+ ref: innerProvided.innerRef,
276
+ ...innerProvided.draggableProps,
277
+ ...innerProvided.dragHandleProps,
278
+ style: isDisabled ? { opacity: 0.2, pointerEvents: "none" } : getItemStyle(innerProvided.draggableProps.style, isUpdating),
279
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { paddingBottom: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
280
+ ui.Card,
281
+ {
282
+ tone,
283
+ shadow: isDragging ? 2 : void 0,
284
+ radius: 2,
285
+ onClick: (e) => handleSelect(item._id, index, e.nativeEvent),
286
+ children: /* @__PURE__ */ jsxRuntime.jsx(
287
+ Document,
288
+ {
289
+ doc: item,
290
+ entities: orderedData,
291
+ increment: incrementIndex,
292
+ index,
293
+ isFirst: index === 0,
294
+ isLast: index === orderedData.length - 1,
295
+ dragBadge
296
+ }
297
+ )
298
+ }
299
+ ) })
300
+ }
301
+ );
302
+ }
303
+ },
304
+ `${item._id}-${item[ORDER_FIELD_NAME]}`
305
+ )),
306
+ provided.placeholder
307
+ ] }) }) });
308
+ }
309
+ const DEFAULT_PARAMS = {};
310
+ function DocumentListQuery({ type, filter, params = DEFAULT_PARAMS }) {
311
+ const [listIsUpdating, setListIsUpdating] = react.useState(!1), [data, setData] = react.useState([]), query = `*[_type == $type ${filter ? `&& ${filter}` : ""}]|order(@[$order] asc){
312
+ _id, _type, ${ORDER_FIELD_NAME}
313
+ }`, queryParams = {
314
+ ...params,
315
+ type,
316
+ order: ORDER_FIELD_NAME
317
+ }, {
318
+ data: _queryData,
319
+ loading,
320
+ error
321
+ } = sanityPluginUtils.useListeningQuery(query, {
322
+ params: queryParams,
323
+ initialValue: []
324
+ }), queryData = _queryData;
325
+ react.useEffect(() => {
326
+ if (queryData) {
327
+ const filteredDocuments = queryData.reduce((acc, cur) => cur._id.startsWith("drafts.") ? (cur.hasPublished = queryData.some((doc) => doc._id === cur._id.replace("drafts.", "")), [...acc, cur]) : queryData.some((doc) => doc._id === `drafts.${cur._id}`) ? acc : [...acc, cur], []);
328
+ setData(filteredDocuments);
329
+ } else
330
+ setData([]);
331
+ }, [queryData]);
332
+ const unorderedDataCount = react.useMemo(
333
+ () => data?.length ? data.filter((doc) => !doc[ORDER_FIELD_NAME]).length : 0,
334
+ [data]
335
+ );
336
+ return loading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { style: { width: "100%", height: "100%" }, align: "center", justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 2, children: /* @__PURE__ */ jsxRuntime.jsx(sanityPluginUtils.Feedback, { tone: "critical", title: "There was an error", description: "Please try again later" }) }) : !data || data?.length == 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", direction: "column", height: "fill", justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { width: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { paddingX: 4, paddingY: 5, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { align: "center", muted: !0, children: "No documents of this type" }) }) }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 1, style: { overflow: "auto", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { padding: 2, children: [
337
+ unorderedDataCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
338
+ sanityPluginUtils.Feedback,
339
+ {
340
+ tone: "caution",
341
+ description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
342
+ unorderedDataCount,
343
+ "/",
344
+ data?.length,
345
+ " documents have no order. Select",
346
+ " ",
347
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Reset Order" }),
348
+ " from the menu above to fix."
349
+ ] })
350
+ }
351
+ ) }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(
353
+ DraggableList,
354
+ {
355
+ data,
356
+ listIsUpdating,
357
+ setListIsUpdating
358
+ }
359
+ )
360
+ ] }) });
361
+ }
362
+ function DocumentListWrapper({
363
+ type,
364
+ showIncrements,
365
+ resetOrderTransaction,
366
+ filter,
367
+ params
368
+ }) {
369
+ const toast = ui.useToast(), schema = sanity.useSchema();
370
+ react.useEffect(() => {
371
+ resetOrderTransaction?.title && resetOrderTransaction?.status && toast.push(resetOrderTransaction);
372
+ }, [resetOrderTransaction, toast]);
373
+ const schemaIsInvalid = react.useMemo(() => {
374
+ if (!type)
375
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
376
+ "No ",
377
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "type" }),
378
+ " was configured"
379
+ ] });
380
+ const typeSchema = schema.get(type);
381
+ return typeSchema ? !("fields" in typeSchema) || !typeSchema.fields.some((field) => field?.name === ORDER_FIELD_NAME) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
382
+ "Schema ",
383
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: type }),
384
+ " must have an ",
385
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: ORDER_FIELD_NAME }),
386
+ " field of type",
387
+ " ",
388
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "string" })
389
+ ] }) : "fields" in typeSchema && typeSchema.fields.some(
390
+ (field) => field?.name === ORDER_FIELD_NAME && field?.type?.name !== "string"
391
+ ) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
392
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: ORDER_FIELD_NAME }),
393
+ " field on Schema ",
394
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: type }),
395
+ " must be",
396
+ " ",
397
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "string" }),
398
+ " type"
399
+ ] }) : "" : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
400
+ "Schema ",
401
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: type }),
402
+ " not found"
403
+ ] });
404
+ }, [type, schema]);
405
+ return schemaIsInvalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 2, children: /* @__PURE__ */ jsxRuntime.jsx(sanityPluginUtils.Feedback, { description: schemaIsInvalid, tone: "caution" }) }) : /* @__PURE__ */ jsxRuntime.jsx(OrderableContext.Provider, { value: { showIncrements }, children: /* @__PURE__ */ jsxRuntime.jsx(DocumentListQuery, { type, filter, params }) });
406
+ }
407
+ async function resetOrder(type, client) {
408
+ const query = "*[_type == $type]|order(@[$order] asc)._id", queryParams = { type, order: ORDER_FIELD_NAME }, documentIds = await client.fetch(query, queryParams, {
409
+ tag: "orderable-document-list.reset-order"
410
+ });
411
+ if (documentIds.length === 0)
412
+ return null;
413
+ let aLexoRank = lexorank.LexoRank.min();
414
+ return documentIds.reduce((trx, documentId) => (aLexoRank = aLexoRank.genNext().genNext(), trx.patch(documentId, {
415
+ set: { [ORDER_FIELD_NAME]: aLexoRank.toString() }
416
+ })), client.transaction()).commit({
417
+ visibility: "async",
418
+ tag: "orderable-document-list.reset-order"
419
+ });
420
+ }
421
+ class OrderableDocumentList extends react.Component {
422
+ constructor(props) {
423
+ super(props), this.state = {
424
+ showIncrements: !1,
425
+ resetOrderTransaction: {}
426
+ };
427
+ }
428
+ actionHandlers = {
429
+ showIncrements: () => {
430
+ this.setState((state) => ({
431
+ showIncrements: !state.showIncrements
432
+ }));
433
+ },
434
+ resetOrder: async () => {
435
+ this.setState(() => ({
436
+ resetOrderTransaction: {
437
+ status: "info",
438
+ title: "Reordering started...",
439
+ closable: !0
440
+ }
441
+ }));
442
+ const update = await resetOrder(this.props.options.type, this.props.options.client), reorderWasSuccessful = update?.results?.length;
443
+ this.setState(() => ({
444
+ resetOrderTransaction: {
445
+ status: reorderWasSuccessful ? "success" : "info",
446
+ title: reorderWasSuccessful ? `Reordered ${update.results.length === 1 ? "Document" : "Documents"}` : "Reordering failed",
447
+ closable: !0
448
+ }
449
+ }));
450
+ }
451
+ };
452
+ render() {
453
+ const type = this?.props?.options?.type;
454
+ return type ? /* @__PURE__ */ jsxRuntime.jsx(
455
+ DocumentListWrapper,
456
+ {
457
+ filter: this?.props?.options?.filter,
458
+ params: this?.props?.options?.params,
459
+ type,
460
+ showIncrements: this.state.showIncrements,
461
+ resetOrderTransaction: this.state.resetOrderTransaction
462
+ }
463
+ ) : null;
464
+ }
465
+ }
466
+ function orderableDocumentListDeskItem(config) {
467
+ if (!config?.type || !config.context || !config.S)
468
+ throw new Error(`
469
+ type, context and S (StructureBuilder) must be provided.
470
+ context and S are available when configuring structure.
471
+ Example: orderableDocumentListDeskItem({type: 'category'})
472
+ `);
473
+ const { type, filter, menuItems = [], createIntent, params, title, icon, id, context, S } = config, { schema, getClient } = context, client = getClient({ apiVersion: API_VERSION }), listTitle = title ?? `Orderable ${type}`, listId = id ?? `orderable-${type}`, listIcon = icon ?? icons.SortIcon, typeTitle = schema.get(type)?.title ?? type;
474
+ return createIntent !== !1 && menuItems.push(
475
+ S.menuItem().title(`Create new ${typeTitle}`).intent({ type: "create", params: { type } }).serialize()
476
+ ), S.listItem().title(listTitle).id(listId).icon(listIcon).schemaType(type).child(
477
+ Object.assign(
478
+ S.documentTypeList(type).canHandleIntent(() => !!createIntent).serialize(),
479
+ {
480
+ // Prevents the component from re-rendering when switching documents
481
+ __preserveInstance: !0,
482
+ // Prevents the component from NOT re-rendering when switching listItems
483
+ key: listId,
484
+ type: "component",
485
+ component: OrderableDocumentList,
486
+ options: { type, filter, params, client },
487
+ menuItems: [
488
+ ...menuItems,
489
+ S.menuItem().title("Reset Order").icon(icons.GenerateIcon).action("resetOrder").serialize(),
490
+ S.menuItem().title("Toggle Increments").icon(icons.SortIcon).action("showIncrements").serialize()
491
+ ]
492
+ }
493
+ )
494
+ ).serialize();
495
+ }
496
+ exports.orderRankField = orderRankField;
497
+ exports.orderRankOrdering = orderRankOrdering;
498
+ exports.orderableDocumentListDeskItem = orderableDocumentListDeskItem;
499
+ //# sourceMappingURL=index.cjs.map