@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 +1 -1
- package/README.md +6 -6
- package/lib/index.cjs +499 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +44 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +427 -711
- package/lib/index.js.map +1 -1
- package/package.json +39 -40
- package/src/Document.tsx +4 -4
- package/src/DocumentListQuery.tsx +7 -9
- package/src/DocumentListWrapper.tsx +4 -5
- package/src/DraggableList.tsx +41 -44
- package/src/OrderableContext.ts +2 -2
- package/src/OrderableDocumentList.tsx +3 -3
- package/src/desk-structure/orderableDocumentListDeskItem.ts +27 -21
- package/src/fields/orderRankField.ts +5 -4
- package/src/fields/orderRankOrdering.ts +1 -1
- package/src/helpers/client.ts +2 -2
- package/src/helpers/initialRank.ts +2 -2
- package/src/helpers/reorderDocuments.ts +8 -8
- package/src/helpers/resetOrder.ts +17 -12
- package/lib/index.esm.js +0 -778
- package/lib/index.esm.js.map +0 -1
package/src/DraggableList.tsx
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {useEffect, useState, useMemo, useCallback, CSSProperties} from 'react'
|
|
1
|
+
import {useEffect, useState, useMemo, useCallback, type CSSProperties} from 'react'
|
|
2
2
|
import {DragDropContext, Draggable, Droppable, type DropResult} from '@hello-pangea/dnd'
|
|
3
3
|
import {Box, Card, useToast} from '@sanity/ui'
|
|
4
4
|
import type {PatchOperations} from 'sanity'
|
|
5
5
|
import {usePaneRouter} from 'sanity/structure'
|
|
6
6
|
|
|
7
|
-
import Document from './Document'
|
|
7
|
+
import {Document} from './Document'
|
|
8
8
|
import {reorderDocuments} from './helpers/reorderDocuments'
|
|
9
9
|
import {ORDER_FIELD_NAME} from './helpers/constants'
|
|
10
10
|
import {useSanityClient} from './helpers/client'
|
|
11
|
-
import {SanityDocumentWithOrder} from './types'
|
|
11
|
+
import type {SanityDocumentWithOrder} from './types'
|
|
12
12
|
|
|
13
13
|
interface ListSetting {
|
|
14
14
|
isDuplicate: boolean
|
|
@@ -25,30 +25,26 @@ export interface DraggableListProps {
|
|
|
25
25
|
|
|
26
26
|
const getItemStyle = (
|
|
27
27
|
draggableStyle: CSSProperties | undefined,
|
|
28
|
-
itemIsUpdating: boolean
|
|
28
|
+
itemIsUpdating: boolean,
|
|
29
29
|
): CSSProperties => ({
|
|
30
30
|
userSelect: 'none',
|
|
31
|
-
transition:
|
|
31
|
+
transition: 'opacity 500ms ease-in-out',
|
|
32
32
|
opacity: itemIsUpdating ? 0.2 : 1,
|
|
33
|
-
pointerEvents: itemIsUpdating ?
|
|
33
|
+
pointerEvents: itemIsUpdating ? 'none' : undefined,
|
|
34
34
|
...draggableStyle,
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
const cardTone = (settings: ListSetting) => {
|
|
38
38
|
const {isDuplicate, isGhosting, isDragging, isSelected} = settings
|
|
39
39
|
|
|
40
|
-
if (isGhosting) return
|
|
41
|
-
if (isDragging || isSelected) return
|
|
42
|
-
if (isDuplicate) return
|
|
40
|
+
if (isGhosting) return 'transparent'
|
|
41
|
+
if (isDragging || isSelected) return 'primary'
|
|
42
|
+
if (isDuplicate) return 'caution'
|
|
43
43
|
|
|
44
44
|
return undefined
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export
|
|
48
|
-
data,
|
|
49
|
-
listIsUpdating,
|
|
50
|
-
setListIsUpdating,
|
|
51
|
-
}: DraggableListProps) {
|
|
47
|
+
export function DraggableList({data, listIsUpdating, setListIsUpdating}: DraggableListProps) {
|
|
52
48
|
const toast = useToast()
|
|
53
49
|
const router = usePaneRouter()
|
|
54
50
|
const {groupIndex, routerPanesState} = router
|
|
@@ -64,7 +60,7 @@ export default function DraggableList({
|
|
|
64
60
|
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
|
65
61
|
}, [data])
|
|
66
62
|
|
|
67
|
-
const [draggingId, setDraggingId] = useState(
|
|
63
|
+
const [draggingId, setDraggingId] = useState('')
|
|
68
64
|
const [selectedIds, setSelectedIds] = useState<string[]>(currentDoc ? [currentDoc] : [])
|
|
69
65
|
|
|
70
66
|
const clearSelected = useCallback(() => setSelectedIds([]), [setSelectedIds])
|
|
@@ -114,7 +110,7 @@ export default function DraggableList({
|
|
|
114
110
|
|
|
115
111
|
return setSelectedIds(updatedIds)
|
|
116
112
|
},
|
|
117
|
-
[setSelectedIds, orderedData, selectedIds]
|
|
113
|
+
[setSelectedIds, orderedData, selectedIds],
|
|
118
114
|
)
|
|
119
115
|
|
|
120
116
|
const client = useSanityClient()
|
|
@@ -125,35 +121,36 @@ export default function DraggableList({
|
|
|
125
121
|
|
|
126
122
|
patches.forEach(([docId, ops]) => transaction.patch(docId, ops))
|
|
127
123
|
|
|
128
|
-
|
|
129
|
-
.commit(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
setDraggingId(``)
|
|
133
|
-
setListIsUpdating(false)
|
|
134
|
-
toast.push({
|
|
135
|
-
title: `${
|
|
136
|
-
updated.results.length === 1 ? `1 Document` : `${updated.results.length} Documents`
|
|
137
|
-
} Reordered`,
|
|
138
|
-
status: `success`,
|
|
139
|
-
description: message,
|
|
140
|
-
})
|
|
124
|
+
try {
|
|
125
|
+
const updated = await transaction.commit({
|
|
126
|
+
visibility: 'async',
|
|
127
|
+
tag: 'orderable-document-list.reorder',
|
|
141
128
|
})
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
129
|
+
clearSelected()
|
|
130
|
+
setDraggingId('')
|
|
131
|
+
setListIsUpdating(false)
|
|
132
|
+
toast.push({
|
|
133
|
+
title: `${
|
|
134
|
+
updated.results.length === 1 ? '1 document' : `${updated.results.length} documents`
|
|
135
|
+
} reordered`,
|
|
136
|
+
status: 'success',
|
|
137
|
+
description: message,
|
|
138
|
+
})
|
|
139
|
+
} catch (err) {
|
|
140
|
+
setDraggingId('')
|
|
141
|
+
setListIsUpdating(false)
|
|
142
|
+
toast.push({
|
|
143
|
+
title: 'Reordering failed',
|
|
144
|
+
status: 'error',
|
|
149
145
|
})
|
|
146
|
+
}
|
|
150
147
|
},
|
|
151
|
-
[client, setDraggingId, clearSelected, setListIsUpdating, toast]
|
|
148
|
+
[client, setDraggingId, clearSelected, setListIsUpdating, toast],
|
|
152
149
|
)
|
|
153
150
|
|
|
154
151
|
const handleDragEnd = useCallback(
|
|
155
152
|
(result: DropResult | undefined, entities: SanityDocumentWithOrder[]) => {
|
|
156
|
-
setDraggingId(
|
|
153
|
+
setDraggingId('')
|
|
157
154
|
|
|
158
155
|
const {source, destination, draggableId} = result ?? {}
|
|
159
156
|
|
|
@@ -190,7 +187,7 @@ export default function DraggableList({
|
|
|
190
187
|
transactPatches(patches, message)
|
|
191
188
|
}
|
|
192
189
|
},
|
|
193
|
-
[selectedIds, setDraggingId, setSelectedIds, transactPatches, setListIsUpdating]
|
|
190
|
+
[selectedIds, setDraggingId, setSelectedIds, transactPatches, setListIsUpdating],
|
|
194
191
|
)
|
|
195
192
|
|
|
196
193
|
const handleDragStart = useCallback(
|
|
@@ -203,7 +200,7 @@ export default function DraggableList({
|
|
|
203
200
|
|
|
204
201
|
setDraggingId(id)
|
|
205
202
|
},
|
|
206
|
-
[selectedIds, clearSelected, setDraggingId]
|
|
203
|
+
[selectedIds, clearSelected, setDraggingId],
|
|
207
204
|
)
|
|
208
205
|
|
|
209
206
|
// Move one document up or down one place, by fake invoking the drag function
|
|
@@ -217,7 +214,7 @@ export default function DraggableList({
|
|
|
217
214
|
|
|
218
215
|
return handleDragEnd(result as DropResult, entities)
|
|
219
216
|
},
|
|
220
|
-
[handleDragEnd]
|
|
217
|
+
[handleDragEnd],
|
|
221
218
|
)
|
|
222
219
|
|
|
223
220
|
const onWindowKeyDown = useCallback(
|
|
@@ -226,7 +223,7 @@ export default function DraggableList({
|
|
|
226
223
|
clearSelected()
|
|
227
224
|
}
|
|
228
225
|
},
|
|
229
|
-
[clearSelected]
|
|
226
|
+
[clearSelected],
|
|
230
227
|
)
|
|
231
228
|
|
|
232
229
|
useEffect(() => {
|
|
@@ -248,7 +245,7 @@ export default function DraggableList({
|
|
|
248
245
|
|
|
249
246
|
const onDragEnd = useCallback(
|
|
250
247
|
(result: DropResult) => handleDragEnd(result, orderedData),
|
|
251
|
-
[orderedData, handleDragEnd]
|
|
248
|
+
[orderedData, handleDragEnd],
|
|
252
249
|
)
|
|
253
250
|
|
|
254
251
|
return (
|
|
@@ -282,7 +279,7 @@ export default function DraggableList({
|
|
|
282
279
|
{...innerProvided.dragHandleProps}
|
|
283
280
|
style={
|
|
284
281
|
isDisabled
|
|
285
|
-
? {opacity: 0.2, pointerEvents:
|
|
282
|
+
? {opacity: 0.2, pointerEvents: 'none'}
|
|
286
283
|
: getItemStyle(innerProvided.draggableProps.style, isUpdating)
|
|
287
284
|
}
|
|
288
285
|
>
|
package/src/OrderableContext.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {createContext} from 'react'
|
|
2
2
|
|
|
3
3
|
export interface OrderableContextValue {
|
|
4
4
|
showIncrements?: boolean
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export const OrderableContext =
|
|
7
|
+
export const OrderableContext = createContext<OrderableContextValue>({})
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {Component} from 'react'
|
|
2
2
|
|
|
3
|
-
import {SanityClient} from '@sanity/client'
|
|
3
|
+
import type {SanityClient} from '@sanity/client'
|
|
4
4
|
import type {ToastParams} from '@sanity/ui'
|
|
5
|
-
import DocumentListWrapper from './DocumentListWrapper'
|
|
5
|
+
import {DocumentListWrapper} from './DocumentListWrapper'
|
|
6
6
|
import {resetOrder} from './helpers/resetOrder'
|
|
7
7
|
|
|
8
8
|
export interface OrderableDocumentListProps {
|
|
@@ -20,7 +20,7 @@ interface State {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Must use a Class Component here so the actionHandlers can be called
|
|
23
|
-
export
|
|
23
|
+
export class OrderableDocumentList extends Component<OrderableDocumentListProps, State> {
|
|
24
24
|
constructor(props: OrderableDocumentListProps) {
|
|
25
25
|
super(props)
|
|
26
26
|
this.state = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {GenerateIcon, SortIcon} from '@sanity/icons'
|
|
2
2
|
import type {ConfigContext} from 'sanity'
|
|
3
3
|
|
|
4
|
-
import {ComponentType} from 'react'
|
|
4
|
+
import type {ComponentType} from 'react'
|
|
5
5
|
import {StructureBuilder, type ListItem, type MenuItem} from 'sanity/structure'
|
|
6
|
-
import OrderableDocumentList from '../OrderableDocumentList'
|
|
6
|
+
import {OrderableDocumentList} from '../OrderableDocumentList'
|
|
7
7
|
import {API_VERSION} from '../helpers/constants'
|
|
8
8
|
|
|
9
9
|
export interface OrderableListConfig {
|
|
@@ -42,33 +42,39 @@ export function orderableDocumentListDeskItem(config: OrderableListConfig): List
|
|
|
42
42
|
S.menuItem()
|
|
43
43
|
.title(`Create new ${typeTitle}`)
|
|
44
44
|
.intent({type: 'create', params: {type}})
|
|
45
|
-
.serialize()
|
|
45
|
+
.serialize(),
|
|
46
46
|
)
|
|
47
47
|
}
|
|
48
48
|
return S.listItem()
|
|
49
49
|
.title(listTitle)
|
|
50
50
|
.id(listId)
|
|
51
51
|
.icon(listIcon)
|
|
52
|
+
.schemaType(type)
|
|
52
53
|
.child(
|
|
53
|
-
Object.assign(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
Object.assign(
|
|
55
|
+
S.documentTypeList(type)
|
|
56
|
+
.canHandleIntent(() => !!createIntent)
|
|
57
|
+
.serialize(),
|
|
58
|
+
{
|
|
59
|
+
// Prevents the component from re-rendering when switching documents
|
|
60
|
+
__preserveInstance: true,
|
|
61
|
+
// Prevents the component from NOT re-rendering when switching listItems
|
|
62
|
+
key: listId,
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
type: 'component',
|
|
65
|
+
component: OrderableDocumentList,
|
|
66
|
+
options: {type, filter, params, client},
|
|
67
|
+
menuItems: [
|
|
68
|
+
...menuItems,
|
|
69
|
+
S.menuItem().title(`Reset Order`).icon(GenerateIcon).action(`resetOrder`).serialize(),
|
|
70
|
+
S.menuItem()
|
|
71
|
+
.title(`Toggle Increments`)
|
|
72
|
+
.icon(SortIcon)
|
|
73
|
+
.action(`showIncrements`)
|
|
74
|
+
.serialize(),
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
),
|
|
72
78
|
)
|
|
73
79
|
.serialize()
|
|
74
80
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {type ConfigContext, defineField} from 'sanity'
|
|
2
2
|
import {API_VERSION, ORDER_FIELD_NAME} from '../helpers/constants'
|
|
3
|
-
import initialRank from '../helpers/initialRank'
|
|
4
|
-
import {NewItemPosition} from '../types'
|
|
3
|
+
import {initialRank} from '../helpers/initialRank'
|
|
4
|
+
import type {NewItemPosition} from '../types'
|
|
5
5
|
|
|
6
6
|
export type SchemaContext = Omit<ConfigContext, 'schema' | 'currentUser' | 'client'>
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@ export const orderRankField = (config: RankFieldConfig) => {
|
|
|
16
16
|
`
|
|
17
17
|
type must be provided.
|
|
18
18
|
Example: orderRankField({type: 'category'})
|
|
19
|
-
|
|
19
|
+
`,
|
|
20
20
|
)
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -33,7 +33,8 @@ export const orderRankField = (config: RankFieldConfig) => {
|
|
|
33
33
|
|
|
34
34
|
const lastDocOrderRank = await getClient({apiVersion: API_VERSION}).fetch(
|
|
35
35
|
`*[_type == $type]|order(@[$order] ${direction})[0][$order]`,
|
|
36
|
-
{type, order: ORDER_FIELD_NAME}
|
|
36
|
+
{type, order: ORDER_FIELD_NAME},
|
|
37
|
+
{tag: 'orderable-document-list.last-doc-order-rank'},
|
|
37
38
|
)
|
|
38
39
|
return initialRank(lastDocOrderRank, newItemPosition)
|
|
39
40
|
},
|
package/src/helpers/client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {useClient} from 'sanity'
|
|
1
|
+
import {type SanityClient, useClient} from 'sanity'
|
|
2
2
|
import {API_VERSION} from './constants'
|
|
3
3
|
|
|
4
|
-
export function useSanityClient() {
|
|
4
|
+
export function useSanityClient(): SanityClient {
|
|
5
5
|
return useClient({apiVersion: API_VERSION})
|
|
6
6
|
}
|
|
@@ -3,9 +3,9 @@ import {NewItemPosition} from '../types'
|
|
|
3
3
|
|
|
4
4
|
// Use in initial value field by passing in the rank value of the last document
|
|
5
5
|
// If not value passed, generate a sensibly low rank
|
|
6
|
-
export
|
|
6
|
+
export function initialRank(
|
|
7
7
|
compareRankValue = ``,
|
|
8
|
-
newItemPosition: NewItemPosition = 'after'
|
|
8
|
+
newItemPosition: NewItemPosition = 'after',
|
|
9
9
|
): string {
|
|
10
10
|
const compareRank = compareRankValue ? LexoRank.parse(compareRankValue) : LexoRank.min()
|
|
11
11
|
const rank =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {LexoRank} from 'lexorank'
|
|
2
2
|
import type {PatchOperations} from 'sanity'
|
|
3
3
|
|
|
4
|
-
import {SanityDocumentWithOrder} from '../types'
|
|
4
|
+
import type {SanityDocumentWithOrder} from '../types'
|
|
5
5
|
import {ORDER_FIELD_NAME} from './constants'
|
|
6
6
|
|
|
7
7
|
export interface MaifestArgs {
|
|
@@ -48,10 +48,10 @@ export const reorderDocuments = ({
|
|
|
48
48
|
const isMovingUp = startIndex > endIndex
|
|
49
49
|
const selectedItems = entities.filter((item) => selectedIds.includes(item._id))
|
|
50
50
|
const message = [
|
|
51
|
-
|
|
52
|
-
selectedItems.length === 1 ?
|
|
53
|
-
isMovingUp ?
|
|
54
|
-
|
|
51
|
+
'Moved',
|
|
52
|
+
selectedItems.length === 1 ? '1 document' : `${selectedItems.length} documents`,
|
|
53
|
+
isMovingUp ? 'up' : 'down',
|
|
54
|
+
'from position',
|
|
55
55
|
`${startIndex + 1} to ${endIndex + 1}`,
|
|
56
56
|
].join(' ')
|
|
57
57
|
|
|
@@ -99,7 +99,7 @@ export const reorderDocuments = ({
|
|
|
99
99
|
|
|
100
100
|
return {all: [...acc.all, cur], selected: acc.selected}
|
|
101
101
|
},
|
|
102
|
-
{all: [], selected: []}
|
|
102
|
+
{all: [], selected: []},
|
|
103
103
|
)
|
|
104
104
|
|
|
105
105
|
const patches = selected.flatMap((doc) => {
|
|
@@ -115,9 +115,9 @@ export const reorderDocuments = ({
|
|
|
115
115
|
]
|
|
116
116
|
|
|
117
117
|
// If it's a draft, we need to patch the published document as well
|
|
118
|
-
if (doc._id.startsWith(
|
|
118
|
+
if (doc._id.startsWith('drafts.') && doc.hasPublished) {
|
|
119
119
|
docPatches.push([
|
|
120
|
-
doc._id.replace(
|
|
120
|
+
doc._id.replace('drafts.', ''),
|
|
121
121
|
{
|
|
122
122
|
set: {
|
|
123
123
|
[ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import {LexoRank} from 'lexorank'
|
|
2
|
-
import {SanityClient} from '@sanity/client'
|
|
2
|
+
import type {MultipleMutationResult, SanityClient} from '@sanity/client'
|
|
3
3
|
import {ORDER_FIELD_NAME} from './constants'
|
|
4
|
+
|
|
4
5
|
// Function to wipe and re-do ordering with LexoRank
|
|
5
6
|
// Will at least attempt to start with the current order
|
|
6
|
-
export async function resetOrder(
|
|
7
|
+
export async function resetOrder(
|
|
8
|
+
type: string,
|
|
9
|
+
client: SanityClient,
|
|
10
|
+
): Promise<MultipleMutationResult | null> {
|
|
7
11
|
const query = `*[_type == $type]|order(@[$order] asc)._id`
|
|
8
12
|
const queryParams = {type, order: ORDER_FIELD_NAME}
|
|
9
|
-
const
|
|
13
|
+
const documentIds = await client.fetch<Array<string>>(query, queryParams, {
|
|
14
|
+
tag: 'orderable-document-list.reset-order',
|
|
15
|
+
})
|
|
10
16
|
|
|
11
|
-
if (
|
|
17
|
+
if (documentIds.length === 0) {
|
|
12
18
|
return null
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
const transaction = client.transaction()
|
|
16
21
|
let aLexoRank = LexoRank.min()
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
const transaction = documentIds.reduce((trx, documentId) => {
|
|
19
24
|
// Generate next rank before even the first document so there's room to move!
|
|
20
25
|
aLexoRank = aLexoRank.genNext().genNext()
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
return trx.patch(documentId, {
|
|
23
28
|
set: {[ORDER_FIELD_NAME]: aLexoRank.toString()},
|
|
24
29
|
})
|
|
25
|
-
}
|
|
30
|
+
}, client.transaction())
|
|
26
31
|
|
|
27
|
-
return transaction
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
|
|
32
|
+
return transaction.commit({
|
|
33
|
+
visibility: 'async',
|
|
34
|
+
tag: 'orderable-document-list.reset-order',
|
|
35
|
+
})
|
|
31
36
|
}
|