@sanity/orderable-document-list 1.0.3 → 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.
- package/README.md +54 -51
- package/lib/{src/index.d.ts → index.d.ts} +2 -8
- package/lib/index.esm.js +769 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +782 -1
- package/lib/index.js.map +1 -1
- package/package.json +40 -39
- package/src/Document.tsx +82 -44
- package/src/DocumentListQuery.tsx +75 -66
- package/src/DocumentListWrapper.tsx +10 -4
- package/src/DraggableList.tsx +42 -27
- package/src/OrderableDocumentList.tsx +3 -3
- package/src/desk-structure/orderableDocumentListDeskItem.ts +7 -3
- package/src/fields/orderRankOrdering.ts +2 -1
- package/src/helpers/initialRank.ts +1 -1
- package/src/helpers/reorderDocuments.ts +50 -24
- package/src/helpers/resetOrder.ts +1 -1
- package/src/types.ts +8 -0
- package/v2-incompatible.js +2 -2
- package/src/Feedback.tsx +0 -12
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {Box, Flex, Spinner, Stack} from '@sanity/ui'
|
|
1
|
+
import {useEffect, useMemo, useState} from 'react'
|
|
2
|
+
import {Box, Flex, Container, Spinner, Stack, Text} from '@sanity/ui'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import {useListeningQuery, Feedback} from 'sanity-plugin-utils'
|
|
5
5
|
import DraggableList from './DraggableList'
|
|
6
6
|
import {ORDER_FIELD_NAME} from './helpers/constants'
|
|
7
|
-
import
|
|
8
|
-
import {useSanityClient} from './helpers/client'
|
|
7
|
+
import {SanityDocumentWithOrder} from './types'
|
|
9
8
|
|
|
10
9
|
export interface DocumentListQueryProps {
|
|
11
10
|
type: string
|
|
@@ -13,11 +12,6 @@ export interface DocumentListQueryProps {
|
|
|
13
12
|
params?: Record<string, unknown>
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
//rxjs Subscription does not seem to comply with sanity client subscribe anymore
|
|
17
|
-
type ClientSubscription = ReturnType<
|
|
18
|
-
ReturnType<ReturnType<typeof useSanityClient>['listen']>['subscribe']
|
|
19
|
-
>
|
|
20
|
-
|
|
21
15
|
const DEFAULT_PARAMS = {}
|
|
22
16
|
|
|
23
17
|
export default function DocumentListQuery({
|
|
@@ -25,85 +19,100 @@ export default function DocumentListQuery({
|
|
|
25
19
|
filter,
|
|
26
20
|
params = DEFAULT_PARAMS,
|
|
27
21
|
}: DocumentListQueryProps) {
|
|
28
|
-
const [isLoading, setIsLoading] = useState(true)
|
|
29
22
|
const [listIsUpdating, setListIsUpdating] = useState(false)
|
|
30
|
-
const [data, setData] = useState<
|
|
31
|
-
|
|
32
|
-
const
|
|
23
|
+
const [data, setData] = useState<SanityDocumentWithOrder[] | null>([])
|
|
24
|
+
|
|
25
|
+
const query = `*[_type == $type ${filter ? `&& ${filter}` : ''}]|order(@[$order] asc){
|
|
26
|
+
_id, _type, ${ORDER_FIELD_NAME}
|
|
27
|
+
}`
|
|
28
|
+
const queryParams = {
|
|
29
|
+
...params,
|
|
30
|
+
type,
|
|
31
|
+
order: ORDER_FIELD_NAME,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
data: queryData,
|
|
36
|
+
loading,
|
|
37
|
+
error,
|
|
38
|
+
} = useListeningQuery<SanityDocumentWithOrder[]>(query, {
|
|
39
|
+
params: queryParams,
|
|
40
|
+
initialValue: [],
|
|
41
|
+
})
|
|
33
42
|
|
|
34
43
|
useEffect(() => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// eslint-disable-next-line require-await
|
|
42
|
-
const fetchData = async () => {
|
|
43
|
-
client.fetch<SanityDocument[]>(query, queryParams).then((documents) => {
|
|
44
|
-
// Remove published document from list if draft also exists
|
|
45
|
-
const filteredDocuments = documents.reduce((acc, cur) => {
|
|
46
|
-
if (!cur._id.startsWith(`drafts.`)) {
|
|
47
|
-
// eslint-disable-next-line max-nested-callbacks
|
|
48
|
-
const alsoHasDraft = documents.some((doc) => doc._id === `drafts.${cur._id}`)
|
|
49
|
-
|
|
50
|
-
return alsoHasDraft ? acc : [...acc, cur]
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return [...acc, cur]
|
|
54
|
-
}, [] as SanityDocument[])
|
|
55
|
-
|
|
56
|
-
setData(filteredDocuments)
|
|
57
|
-
|
|
58
|
-
if (isLoading) {
|
|
59
|
-
setIsLoading(false)
|
|
44
|
+
if (queryData) {
|
|
45
|
+
const filteredDocuments = queryData.reduce<SanityDocumentWithOrder[]>((acc, cur) => {
|
|
46
|
+
if (!cur._id.startsWith(`drafts.`)) {
|
|
47
|
+
// eslint-disable-next-line max-nested-callbacks
|
|
48
|
+
const alsoHasDraft = queryData.some((doc) => doc._id === `drafts.${cur._id}`)
|
|
49
|
+
return alsoHasDraft ? acc : [...acc, cur]
|
|
60
50
|
}
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
51
|
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
// Check if the draft has a published version
|
|
53
|
+
cur.hasPublished = queryData.some((doc) => doc._id === cur._id.replace(`drafts.`, ``))
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!subscription) {
|
|
70
|
-
subscription = client.listen(query, queryParams).subscribe(() => fetchData())
|
|
71
|
-
}
|
|
72
|
-
}
|
|
55
|
+
return [...acc, cur]
|
|
56
|
+
}, [])
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
58
|
+
setData(filteredDocuments)
|
|
59
|
+
} else {
|
|
60
|
+
setData([])
|
|
77
61
|
}
|
|
78
|
-
|
|
79
|
-
return () => subscription?.unsubscribe()
|
|
80
|
-
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
|
81
|
-
}, [type])
|
|
62
|
+
}, [queryData])
|
|
82
63
|
|
|
83
64
|
const unorderedDataCount = useMemo(
|
|
84
|
-
() => (data
|
|
65
|
+
() => (data?.length ? data.filter((doc) => !doc[ORDER_FIELD_NAME]).length : 0),
|
|
85
66
|
[data]
|
|
86
67
|
)
|
|
87
68
|
|
|
88
|
-
if (
|
|
69
|
+
if (loading) {
|
|
89
70
|
return (
|
|
90
71
|
<Flex style={{width: `100%`, height: `100%`}} align="center" justify="center">
|
|
91
72
|
<Spinner />
|
|
92
73
|
</Flex>
|
|
93
74
|
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (error) {
|
|
78
|
+
return (
|
|
79
|
+
<Box padding={2}>
|
|
80
|
+
<Feedback tone="critical" title="There was an error" description="Please try again later" />
|
|
81
|
+
</Box>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!data || data?.length == 0)
|
|
86
|
+
return (
|
|
87
|
+
<Flex align="center" direction="column" height="fill" justify="center">
|
|
88
|
+
<Container width={1}>
|
|
89
|
+
<Box paddingX={4} paddingY={5}>
|
|
90
|
+
<Text align="center" muted>
|
|
91
|
+
No documents of this type
|
|
92
|
+
</Text>
|
|
93
|
+
</Box>
|
|
94
|
+
</Container>
|
|
95
|
+
</Flex>
|
|
96
|
+
)
|
|
94
97
|
|
|
95
98
|
return (
|
|
96
99
|
<Stack space={1} style={{overflow: `auto`, height: `100%`}}>
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
<Box padding={2}>
|
|
101
|
+
{unorderedDataCount > 0 && (
|
|
102
|
+
<Box marginBottom={2}>
|
|
103
|
+
<Feedback
|
|
104
|
+
tone="caution"
|
|
105
|
+
description={
|
|
106
|
+
<>
|
|
107
|
+
{unorderedDataCount}/{data?.length} documents have no order. Select{' '}
|
|
108
|
+
<strong>Reset Order</strong> from the menu above to fix.
|
|
109
|
+
</>
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
</Box>
|
|
113
|
+
)}
|
|
104
114
|
<DraggableList
|
|
105
115
|
data={data}
|
|
106
|
-
type={type}
|
|
107
116
|
listIsUpdating={listIsUpdating}
|
|
108
117
|
setListIsUpdating={setListIsUpdating}
|
|
109
118
|
/>
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {useEffect, useMemo} from 'react'
|
|
2
2
|
import {useToast} from '@sanity/ui'
|
|
3
3
|
|
|
4
4
|
import {useSchema} from 'sanity'
|
|
5
|
-
import type
|
|
5
|
+
import {Box, type ToastParams} from '@sanity/ui'
|
|
6
|
+
import {Feedback} from 'sanity-plugin-utils'
|
|
6
7
|
import DocumentListQuery from './DocumentListQuery'
|
|
7
8
|
import {OrderableContext} from './OrderableContext'
|
|
8
9
|
|
|
9
10
|
import {ORDER_FIELD_NAME} from './helpers/constants'
|
|
10
|
-
import Feedback from './Feedback'
|
|
11
11
|
|
|
12
12
|
export interface DocumentListWrapperProps {
|
|
13
13
|
showIncrements: boolean
|
|
14
14
|
type: string
|
|
15
15
|
resetOrderTransaction: ToastParams
|
|
16
|
+
// eslint-disable-next-line react/require-default-props
|
|
16
17
|
filter?: string
|
|
18
|
+
// eslint-disable-next-line react/require-default-props
|
|
17
19
|
params?: Record<string, unknown>
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -88,7 +90,11 @@ export default function DocumentListWrapper({
|
|
|
88
90
|
}, [type, schema])
|
|
89
91
|
|
|
90
92
|
if (schemaIsInvalid) {
|
|
91
|
-
return
|
|
93
|
+
return (
|
|
94
|
+
<Box padding={2}>
|
|
95
|
+
<Feedback description={schemaIsInvalid} tone="caution" />
|
|
96
|
+
</Box>
|
|
97
|
+
)
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
return (
|
package/src/DraggableList.tsx
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {useEffect, useState, useMemo, useCallback, 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
|
+
import type {PatchOperations} from 'sanity'
|
|
4
5
|
import {usePaneRouter} from 'sanity/desk'
|
|
5
|
-
|
|
6
|
+
|
|
6
7
|
import Document from './Document'
|
|
7
8
|
import {reorderDocuments} from './helpers/reorderDocuments'
|
|
8
9
|
import {ORDER_FIELD_NAME} from './helpers/constants'
|
|
9
10
|
import {useSanityClient} from './helpers/client'
|
|
11
|
+
import {SanityDocumentWithOrder} from './types'
|
|
12
|
+
|
|
13
|
+
interface ListSetting {
|
|
14
|
+
isDuplicate: boolean
|
|
15
|
+
isGhosting: boolean
|
|
16
|
+
isDragging: boolean
|
|
17
|
+
isSelected: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DraggableListProps {
|
|
21
|
+
data: SanityDocumentWithOrder[]
|
|
22
|
+
listIsUpdating: boolean
|
|
23
|
+
setListIsUpdating: (val: boolean) => void
|
|
24
|
+
}
|
|
10
25
|
|
|
11
26
|
const getItemStyle = (
|
|
12
27
|
draggableStyle: CSSProperties | undefined,
|
|
@@ -29,32 +44,19 @@ const cardTone = (settings: ListSetting) => {
|
|
|
29
44
|
return undefined
|
|
30
45
|
}
|
|
31
46
|
|
|
32
|
-
interface ListSetting {
|
|
33
|
-
isDuplicate: boolean
|
|
34
|
-
isGhosting: boolean
|
|
35
|
-
isDragging: boolean
|
|
36
|
-
isSelected: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface DraggableListProps {
|
|
40
|
-
data: SanityDocument[]
|
|
41
|
-
type: string
|
|
42
|
-
listIsUpdating: boolean
|
|
43
|
-
setListIsUpdating: (val: boolean) => void
|
|
44
|
-
}
|
|
45
|
-
|
|
46
47
|
export default function DraggableList({
|
|
47
48
|
data,
|
|
48
|
-
type,
|
|
49
49
|
listIsUpdating,
|
|
50
50
|
setListIsUpdating,
|
|
51
51
|
}: DraggableListProps) {
|
|
52
52
|
const toast = useToast()
|
|
53
53
|
const router = usePaneRouter()
|
|
54
|
-
const {
|
|
54
|
+
const {groupIndex, routerPanesState} = router
|
|
55
|
+
|
|
56
|
+
const currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || false
|
|
55
57
|
|
|
56
58
|
// Maintains local state order before transaction completes
|
|
57
|
-
const [orderedData, setOrderedData] = useState<
|
|
59
|
+
const [orderedData, setOrderedData] = useState<SanityDocumentWithOrder[]>(data)
|
|
58
60
|
|
|
59
61
|
// Update local state when documents change from an outside source
|
|
60
62
|
useEffect(() => {
|
|
@@ -63,7 +65,7 @@ export default function DraggableList({
|
|
|
63
65
|
}, [data])
|
|
64
66
|
|
|
65
67
|
const [draggingId, setDraggingId] = useState(``)
|
|
66
|
-
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
|
68
|
+
const [selectedIds, setSelectedIds] = useState<string[]>(currentDoc ? [currentDoc] : [])
|
|
67
69
|
|
|
68
70
|
const clearSelected = useCallback(() => setSelectedIds([]), [setSelectedIds])
|
|
69
71
|
|
|
@@ -80,10 +82,14 @@ export default function DraggableList({
|
|
|
80
82
|
// - update selected to just this one
|
|
81
83
|
// - open document
|
|
82
84
|
if (!selectMultiple && !selectAdditional) {
|
|
83
|
-
navigateIntent('edit', {id: clickedId, type})
|
|
84
85
|
return setSelectedIds([clickedId])
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
// If shift key was held, prevent default to avoid new window opening
|
|
89
|
+
if (selectMultiple) {
|
|
90
|
+
nativeEvent.preventDefault()
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
// Shift key was held, add id's between last selected and this one
|
|
88
94
|
// ...before adding this one
|
|
89
95
|
if (selectMultiple && !isSelected) {
|
|
@@ -108,7 +114,7 @@ export default function DraggableList({
|
|
|
108
114
|
|
|
109
115
|
return setSelectedIds(updatedIds)
|
|
110
116
|
},
|
|
111
|
-
[setSelectedIds,
|
|
117
|
+
[setSelectedIds, orderedData, selectedIds]
|
|
112
118
|
)
|
|
113
119
|
|
|
114
120
|
const client = useSanityClient()
|
|
@@ -146,7 +152,7 @@ export default function DraggableList({
|
|
|
146
152
|
)
|
|
147
153
|
|
|
148
154
|
const handleDragEnd = useCallback(
|
|
149
|
-
(result: DropResult | undefined, entities:
|
|
155
|
+
(result: DropResult | undefined, entities: SanityDocumentWithOrder[]) => {
|
|
150
156
|
setDraggingId(``)
|
|
151
157
|
|
|
152
158
|
const {source, destination, draggableId} = result ?? {}
|
|
@@ -176,7 +182,7 @@ export default function DraggableList({
|
|
|
176
182
|
|
|
177
183
|
// Update local state
|
|
178
184
|
if (newOrder?.length) {
|
|
179
|
-
setOrderedData(newOrder
|
|
185
|
+
setOrderedData(newOrder)
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
// Transact new order patches
|
|
@@ -202,7 +208,7 @@ export default function DraggableList({
|
|
|
202
208
|
|
|
203
209
|
// Move one document up or down one place, by fake invoking the drag function
|
|
204
210
|
const incrementIndex = useCallback(
|
|
205
|
-
(shiftFrom: number, shiftTo: number, id: string, entities:
|
|
211
|
+
(shiftFrom: number, shiftTo: number, id: string, entities: SanityDocumentWithOrder[]) => {
|
|
206
212
|
const result = {
|
|
207
213
|
draggableId: id,
|
|
208
214
|
source: {index: shiftFrom},
|
|
@@ -265,6 +271,9 @@ export default function DraggableList({
|
|
|
265
271
|
const isDisabled = Boolean(!item[ORDER_FIELD_NAME])
|
|
266
272
|
const isDuplicate = duplicateOrders.includes(item[ORDER_FIELD_NAME])
|
|
267
273
|
const tone = cardTone({isDuplicate, isGhosting, isDragging, isSelected})
|
|
274
|
+
const selectedCount = selectedIds.length
|
|
275
|
+
|
|
276
|
+
const dragBadge = isDragging && selectedCount > 1 ? selectedCount : false
|
|
268
277
|
|
|
269
278
|
return (
|
|
270
279
|
<div
|
|
@@ -278,15 +287,21 @@ export default function DraggableList({
|
|
|
278
287
|
}
|
|
279
288
|
>
|
|
280
289
|
<Box paddingBottom={1}>
|
|
281
|
-
<Card
|
|
290
|
+
<Card
|
|
291
|
+
tone={tone}
|
|
292
|
+
shadow={isDragging ? 2 : undefined}
|
|
293
|
+
radius={2}
|
|
294
|
+
// eslint-disable-next-line react/jsx-no-bind
|
|
295
|
+
onClick={(e) => handleSelect(item._id, index, e.nativeEvent)}
|
|
296
|
+
>
|
|
282
297
|
<Document
|
|
283
298
|
doc={item}
|
|
284
299
|
entities={orderedData}
|
|
285
|
-
handleSelect={handleSelect}
|
|
286
300
|
increment={incrementIndex}
|
|
287
301
|
index={index}
|
|
288
302
|
isFirst={index === 0}
|
|
289
303
|
isLast={index === orderedData.length - 1}
|
|
304
|
+
dragBadge={dragBadge}
|
|
290
305
|
/>
|
|
291
306
|
</Card>
|
|
292
307
|
</Box>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {Component} from 'react'
|
|
2
2
|
|
|
3
3
|
import {SanityClient} from '@sanity/client'
|
|
4
4
|
import type {ToastParams} from '@sanity/ui'
|
|
@@ -8,8 +8,8 @@ import {resetOrder} from './helpers/resetOrder'
|
|
|
8
8
|
export interface OrderableDocumentListProps {
|
|
9
9
|
options: {
|
|
10
10
|
type: string
|
|
11
|
-
client: SanityClient
|
|
12
|
-
filter?: string
|
|
11
|
+
client: SanityClient
|
|
12
|
+
filter?: string
|
|
13
13
|
params?: Record<string, unknown>
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -2,7 +2,7 @@ import {GenerateIcon, SortIcon} from '@sanity/icons'
|
|
|
2
2
|
import type {ConfigContext} from 'sanity'
|
|
3
3
|
|
|
4
4
|
import {ComponentType} from 'react'
|
|
5
|
-
import {StructureBuilder} from 'sanity/desk'
|
|
5
|
+
import {StructureBuilder, type ListItem} from 'sanity/desk'
|
|
6
6
|
import OrderableDocumentList from '../OrderableDocumentList'
|
|
7
7
|
|
|
8
8
|
export interface OrderableListConfig {
|
|
@@ -16,7 +16,7 @@ export interface OrderableListConfig {
|
|
|
16
16
|
S: StructureBuilder
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function orderableDocumentListDeskItem(config: OrderableListConfig) {
|
|
19
|
+
export function orderableDocumentListDeskItem(config: OrderableListConfig): ListItem {
|
|
20
20
|
if (!config?.type || !config.context || !config.S) {
|
|
21
21
|
throw new Error(`
|
|
22
22
|
type, context and S (StructureBuilder) must be provided.
|
|
@@ -54,7 +54,11 @@ export function orderableDocumentListDeskItem(config: OrderableListConfig) {
|
|
|
54
54
|
.intent({type: 'create', params: {type}})
|
|
55
55
|
.serialize(),
|
|
56
56
|
S.menuItem().title(`Reset Order`).icon(GenerateIcon).action(`resetOrder`).serialize(),
|
|
57
|
-
S.menuItem()
|
|
57
|
+
S.menuItem()
|
|
58
|
+
.title(`Toggle Increments`)
|
|
59
|
+
.icon(SortIcon)
|
|
60
|
+
.action(`showIncrements`)
|
|
61
|
+
.serialize(),
|
|
58
62
|
],
|
|
59
63
|
})
|
|
60
64
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import {SortOrdering} from 'sanity'
|
|
1
2
|
import {ORDER_FIELD_NAME} from '../helpers/constants'
|
|
2
3
|
|
|
3
|
-
export const orderRankOrdering = {
|
|
4
|
+
export const orderRankOrdering: SortOrdering = {
|
|
4
5
|
title: 'Ordered',
|
|
5
6
|
name: 'ordered',
|
|
6
7
|
by: [{field: ORDER_FIELD_NAME, direction: 'asc'}],
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {LexoRank} from 'lexorank'
|
|
2
|
-
import type {PatchOperations
|
|
2
|
+
import type {PatchOperations} from 'sanity'
|
|
3
|
+
|
|
4
|
+
import {SanityDocumentWithOrder} from '../types'
|
|
3
5
|
import {ORDER_FIELD_NAME} from './constants'
|
|
4
6
|
|
|
5
7
|
export interface MaifestArgs {
|
|
6
|
-
entities:
|
|
7
|
-
selectedItems:
|
|
8
|
+
entities: SanityDocumentWithOrder[]
|
|
9
|
+
selectedItems: SanityDocumentWithOrder[]
|
|
8
10
|
isMovingUp: boolean
|
|
9
11
|
curIndex: number
|
|
10
12
|
nextIndex: number
|
|
@@ -12,18 +14,24 @@ export interface MaifestArgs {
|
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export interface ReorderArgs {
|
|
15
|
-
entities:
|
|
17
|
+
entities: SanityDocumentWithOrder[]
|
|
16
18
|
selectedIds: string[]
|
|
17
19
|
source: any
|
|
18
20
|
destination: any
|
|
19
|
-
debug?: boolean
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
export interface ReorderReturn {
|
|
24
|
+
newOrder: SanityDocumentWithOrder[]
|
|
25
|
+
patches: [string, PatchOperations][]
|
|
26
|
+
message: any
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function lexicographicalSort(a: SanityDocumentWithOrder, b: SanityDocumentWithOrder) {
|
|
30
|
+
if (!a[ORDER_FIELD_NAME] || !b[ORDER_FIELD_NAME]) {
|
|
31
|
+
return 0
|
|
32
|
+
} else if (a[ORDER_FIELD_NAME] < b[ORDER_FIELD_NAME]) {
|
|
24
33
|
return -1
|
|
25
|
-
}
|
|
26
|
-
if (a[ORDER_FIELD_NAME] > b[ORDER_FIELD_NAME]) {
|
|
34
|
+
} else if (a[ORDER_FIELD_NAME] > b[ORDER_FIELD_NAME]) {
|
|
27
35
|
return 1
|
|
28
36
|
}
|
|
29
37
|
return 0
|
|
@@ -34,28 +42,30 @@ export const reorderDocuments = ({
|
|
|
34
42
|
selectedIds,
|
|
35
43
|
source,
|
|
36
44
|
destination,
|
|
37
|
-
|
|
38
|
-
}: ReorderArgs) => {
|
|
45
|
+
}: ReorderArgs): ReorderReturn => {
|
|
39
46
|
const startIndex = source.index
|
|
40
47
|
const endIndex = destination.index
|
|
41
48
|
const isMovingUp = startIndex > endIndex
|
|
42
49
|
const selectedItems = entities.filter((item) => selectedIds.includes(item._id))
|
|
43
50
|
const message = [
|
|
44
51
|
`Moved`,
|
|
45
|
-
selectedItems.length === 1 ? `1
|
|
52
|
+
selectedItems.length === 1 ? `1 document` : `${selectedItems.length} documents`,
|
|
46
53
|
isMovingUp ? `up` : `down`,
|
|
47
54
|
`from position`,
|
|
48
55
|
`${startIndex + 1} to ${endIndex + 1}`,
|
|
49
56
|
].join(' ')
|
|
50
57
|
|
|
51
|
-
const {all, selected} = entities.reduce
|
|
58
|
+
const {all, selected} = entities.reduce<{
|
|
59
|
+
all: SanityDocumentWithOrder[]
|
|
60
|
+
selected: SanityDocumentWithOrder[]
|
|
61
|
+
}>(
|
|
52
62
|
(acc, cur, curIndex) => {
|
|
53
63
|
// Selected items get spread in below, so skip them here
|
|
54
64
|
if (selectedIds.includes(cur._id)) {
|
|
55
65
|
return {all: acc.all, selected: acc.selected}
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
// Drop
|
|
68
|
+
// Drop selected items in
|
|
59
69
|
if (curIndex === endIndex) {
|
|
60
70
|
const prevIndex = curIndex - 1
|
|
61
71
|
const prevRank = entities[prevIndex]?.[ORDER_FIELD_NAME]
|
|
@@ -73,7 +83,7 @@ export const reorderDocuments = ({
|
|
|
73
83
|
|
|
74
84
|
// For each selected item, assign a new orderRank between now and next
|
|
75
85
|
for (let selectedIndex = 0; selectedIndex < selectedItems.length; selectedIndex += 1) {
|
|
76
|
-
selectedItems[selectedIndex][ORDER_FIELD_NAME] = (
|
|
86
|
+
selectedItems[selectedIndex][ORDER_FIELD_NAME] = betweenRank.toString()
|
|
77
87
|
betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank)
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -89,22 +99,38 @@ export const reorderDocuments = ({
|
|
|
89
99
|
|
|
90
100
|
return {all: [...acc.all, cur], selected: acc.selected}
|
|
91
101
|
},
|
|
92
|
-
{all: []
|
|
102
|
+
{all: [], selected: []}
|
|
93
103
|
)
|
|
94
104
|
|
|
95
|
-
const patches
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const patches = selected.flatMap((doc) => {
|
|
106
|
+
const docPatches: [string, PatchOperations][] = [
|
|
107
|
+
[
|
|
108
|
+
doc._id,
|
|
109
|
+
{
|
|
110
|
+
set: {
|
|
111
|
+
[ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],
|
|
112
|
+
},
|
|
101
113
|
},
|
|
102
|
-
|
|
114
|
+
],
|
|
103
115
|
]
|
|
116
|
+
|
|
117
|
+
// If it's a draft, we need to patch the published document as well
|
|
118
|
+
if (doc._id.startsWith(`drafts.`) && doc.hasPublished) {
|
|
119
|
+
docPatches.push([
|
|
120
|
+
doc._id.replace(`drafts.`, ``),
|
|
121
|
+
{
|
|
122
|
+
set: {
|
|
123
|
+
[ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return docPatches
|
|
104
130
|
})
|
|
105
131
|
|
|
106
132
|
// Safety-check to make sure everything is in order
|
|
107
|
-
const allSorted =
|
|
133
|
+
const allSorted = all.sort(lexicographicalSort)
|
|
108
134
|
|
|
109
135
|
return {newOrder: allSorted, patches, message}
|
|
110
136
|
}
|
|
@@ -20,7 +20,7 @@ export async function resetOrder(type = ``, client: SanityClient) {
|
|
|
20
20
|
aLexoRank = aLexoRank.genNext().genNext()
|
|
21
21
|
|
|
22
22
|
transaction.patch(documents[index], {
|
|
23
|
-
set: {[ORDER_FIELD_NAME]: (
|
|
23
|
+
set: {[ORDER_FIELD_NAME]: aLexoRank.toString()},
|
|
24
24
|
})
|
|
25
25
|
}
|
|
26
26
|
|
package/src/types.ts
ADDED
package/v2-incompatible.js
CHANGED
package/src/Feedback.tsx
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React, {PropsWithChildren} from 'react'
|
|
2
|
-
import {Box, Card, Text} from '@sanity/ui'
|
|
3
|
-
|
|
4
|
-
export default function Feedback({children}: PropsWithChildren<{}>) {
|
|
5
|
-
return (
|
|
6
|
-
<Box padding={3}>
|
|
7
|
-
<Card padding={4} radius={2} shadow={1} tone="caution">
|
|
8
|
-
<Text>{children}</Text>
|
|
9
|
-
</Card>
|
|
10
|
-
</Box>
|
|
11
|
-
)
|
|
12
|
-
}
|