@sanity/orderable-document-list 0.0.10 → 1.0.0-v3-studio.2

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 (61) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +135 -55
  3. package/lib/index.d.ts +31 -0
  4. package/lib/index.d.ts.map +1 -0
  5. package/lib/index.js +827 -30
  6. package/lib/index.js.map +1 -1
  7. package/lib/index.modern.js +818 -0
  8. package/lib/index.modern.js.map +1 -0
  9. package/package.json +57 -23
  10. package/src/{Document.js → Document.tsx} +30 -27
  11. package/src/{DocumentListQuery.js → DocumentListQuery.tsx} +27 -23
  12. package/src/{DocumentListWrapper.js → DocumentListWrapper.tsx} +24 -29
  13. package/src/DraggableList.tsx +304 -0
  14. package/src/{Feedback.js → Feedback.tsx} +2 -7
  15. package/src/OrderableContext.ts +7 -0
  16. package/src/{OrderableDocumentList.js → OrderableDocumentList.tsx} +23 -12
  17. package/src/desk-structure/{orderableDocumentListDeskItem.js → orderableDocumentListDeskItem.ts} +24 -10
  18. package/src/fields/{orderRankField.js → orderRankField.ts} +12 -10
  19. package/src/fields/{orderRankOrdering.js → orderRankOrdering.ts} +0 -0
  20. package/src/helpers/client.ts +5 -0
  21. package/src/helpers/constants.ts +1 -0
  22. package/src/helpers/{initialRank.js → initialRank.ts} +2 -2
  23. package/src/helpers/{reorderDocuments.js → reorderDocuments.ts} +33 -44
  24. package/src/helpers/{resetOrder.js → resetOrder.ts} +3 -8
  25. package/src/index.ts +9 -0
  26. package/.babelrc +0 -3
  27. package/.eslintignore +0 -1
  28. package/.eslintrc.js +0 -50
  29. package/lib/Document.js +0 -101
  30. package/lib/Document.js.map +0 -1
  31. package/lib/DocumentListQuery.js +0 -163
  32. package/lib/DocumentListQuery.js.map +0 -1
  33. package/lib/DocumentListWrapper.js +0 -107
  34. package/lib/DocumentListWrapper.js.map +0 -1
  35. package/lib/DraggableList.js +0 -314
  36. package/lib/DraggableList.js.map +0 -1
  37. package/lib/Feedback.js +0 -31
  38. package/lib/Feedback.js.map +0 -1
  39. package/lib/OrderableContext.js +0 -15
  40. package/lib/OrderableContext.js.map +0 -1
  41. package/lib/OrderableDocumentList.js +0 -103
  42. package/lib/OrderableDocumentList.js.map +0 -1
  43. package/lib/desk-structure/orderableDocumentListDeskItem.js +0 -57
  44. package/lib/desk-structure/orderableDocumentListDeskItem.js.map +0 -1
  45. package/lib/fields/orderRankField.js +0 -64
  46. package/lib/fields/orderRankField.js.map +0 -1
  47. package/lib/fields/orderRankOrdering.js +0 -19
  48. package/lib/fields/orderRankOrdering.js.map +0 -1
  49. package/lib/helpers/constants.js +0 -9
  50. package/lib/helpers/constants.js.map +0 -1
  51. package/lib/helpers/initialRank.js +0 -18
  52. package/lib/helpers/initialRank.js.map +0 -1
  53. package/lib/helpers/reorderDocuments.js +0 -131
  54. package/lib/helpers/reorderDocuments.js.map +0 -1
  55. package/lib/helpers/resetOrder.js +0 -62
  56. package/lib/helpers/resetOrder.js.map +0 -1
  57. package/sanity.json +0 -7
  58. package/src/DraggableList.js +0 -276
  59. package/src/OrderableContext.js +0 -3
  60. package/src/helpers/constants.js +0 -1
  61. package/src/index.js +0 -5
@@ -0,0 +1,304 @@
1
+ import React, {useEffect, useState, useMemo, useCallback, CSSProperties} from 'react'
2
+ import {DragDropContext, Draggable, Droppable, type DropResult} from 'react-beautiful-dnd'
3
+ import {Box, Card, useToast} from '@sanity/ui'
4
+ import {usePaneRouter} from 'sanity/desk'
5
+ import type {SanityDocument, PatchOperations} from 'sanity'
6
+ import Document from './Document'
7
+ import {reorderDocuments} from './helpers/reorderDocuments'
8
+ import {ORDER_FIELD_NAME} from './helpers/constants'
9
+ import {useSanityClient} from './helpers/client'
10
+
11
+ const getItemStyle = (
12
+ draggableStyle: CSSProperties | undefined,
13
+ itemIsUpdating: boolean
14
+ ): CSSProperties => ({
15
+ userSelect: 'none',
16
+ transition: `opacity 500ms ease-in-out`,
17
+ opacity: itemIsUpdating ? 0.2 : 1,
18
+ pointerEvents: itemIsUpdating ? `none` : undefined,
19
+ ...draggableStyle,
20
+ })
21
+
22
+ const cardTone = (settings: ListSetting) => {
23
+ const {isDuplicate, isGhosting, isDragging, isSelected} = settings
24
+
25
+ if (isGhosting) return `transparent`
26
+ if (isDragging || isSelected) return `primary`
27
+ if (isDuplicate) return `caution`
28
+
29
+ return undefined
30
+ }
31
+
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
+ export default function DraggableList({
47
+ data,
48
+ type,
49
+ listIsUpdating,
50
+ setListIsUpdating,
51
+ }: DraggableListProps) {
52
+ const toast = useToast()
53
+ const router = usePaneRouter()
54
+ const {navigateIntent} = router
55
+
56
+ // Maintains local state order before transaction completes
57
+ const [orderedData, setOrderedData] = useState<SanityDocument[]>(data)
58
+
59
+ // Update local state when documents change from an outside source
60
+ useEffect(() => {
61
+ if (!listIsUpdating) setOrderedData(data)
62
+ /* eslint-disable-next-line react-hooks/exhaustive-deps */
63
+ }, [data])
64
+
65
+ const [draggingId, setDraggingId] = useState(``)
66
+ const [selectedIds, setSelectedIds] = useState<string[]>([])
67
+
68
+ const clearSelected = useCallback(() => setSelectedIds([]), [setSelectedIds])
69
+
70
+ const handleSelect = useCallback(
71
+ (clickedId: string, index: number, nativeEvent: MouseEvent) => {
72
+ const isSelected = selectedIds.includes(clickedId)
73
+ const selectMultiple = nativeEvent.shiftKey
74
+ const isUsingWindows = navigator.appVersion.indexOf('Win') !== -1
75
+ const selectAdditional = isUsingWindows ? nativeEvent.ctrlKey : nativeEvent.metaKey
76
+
77
+ let updatedIds = []
78
+
79
+ // No modifier keys pressed during click:
80
+ // - update selected to just this one
81
+ // - open document
82
+ if (!selectMultiple && !selectAdditional) {
83
+ navigateIntent('edit', {id: clickedId, type})
84
+ return setSelectedIds([clickedId])
85
+ }
86
+
87
+ // Shift key was held, add id's between last selected and this one
88
+ // ...before adding this one
89
+ if (selectMultiple && !isSelected) {
90
+ const lastSelectedId = selectedIds[selectedIds.length - 1]
91
+ const lastSelectedIndex = orderedData.findIndex((item) => item._id === lastSelectedId)
92
+
93
+ const firstSelected = index < lastSelectedIndex ? index : lastSelectedIndex
94
+ const lastSelected = index > lastSelectedIndex ? index : lastSelectedIndex
95
+
96
+ const betweenIds = orderedData
97
+ .filter((item, itemIndex) => itemIndex > firstSelected && itemIndex < lastSelected)
98
+ .map((item) => item._id)
99
+
100
+ updatedIds = [...selectedIds, ...betweenIds, clickedId]
101
+ } else if (isSelected) {
102
+ // Toggle off a single id
103
+ updatedIds = selectedIds.filter((id) => id !== clickedId)
104
+ } else {
105
+ // Toggle on a single id
106
+ updatedIds = [...selectedIds, clickedId]
107
+ }
108
+
109
+ return setSelectedIds(updatedIds)
110
+ },
111
+ [setSelectedIds, navigateIntent, orderedData, selectedIds, type]
112
+ )
113
+
114
+ const client = useSanityClient()
115
+
116
+ const transactPatches = useCallback(
117
+ async (patches: [string, PatchOperations][], message: string) => {
118
+ const transaction = client.transaction()
119
+
120
+ patches.forEach(([docId, ops]) => transaction.patch(docId, ops))
121
+
122
+ await transaction
123
+ .commit()
124
+ .then((updated) => {
125
+ clearSelected()
126
+ setDraggingId(``)
127
+ setListIsUpdating(false)
128
+ toast.push({
129
+ title: `${
130
+ updated.results.length === 1 ? `1 Document` : `${updated.results.length} Documents`
131
+ } Reordered`,
132
+ status: `success`,
133
+ description: message,
134
+ })
135
+ })
136
+ .catch(() => {
137
+ setDraggingId(``)
138
+ setListIsUpdating(false)
139
+ toast.push({
140
+ title: `Reordering failed`,
141
+ status: `error`,
142
+ })
143
+ })
144
+ },
145
+ [client, setDraggingId, clearSelected, setListIsUpdating, toast]
146
+ )
147
+
148
+ const handleDragEnd = useCallback(
149
+ (result: DropResult | undefined, entities: SanityDocument[]) => {
150
+ setDraggingId(``)
151
+
152
+ const {source, destination, draggableId} = result ?? {}
153
+
154
+ // Don't do anything if nothing changed
155
+ if (source?.index === destination?.index) return
156
+
157
+ // Don't do anything if we don't have the entitites
158
+ if (!entities?.length || !draggableId) return
159
+
160
+ // A document can be dragged without being one-of-many-selected
161
+ const effectedIds = selectedIds?.length ? selectedIds : [draggableId]
162
+
163
+ // Don't do anything if we don't have ids to effect
164
+ if (!effectedIds?.length) return
165
+
166
+ // Update state to update styles + prevent data refetching
167
+ setListIsUpdating(true)
168
+ setSelectedIds(effectedIds)
169
+
170
+ const {newOrder, patches, message} = reorderDocuments({
171
+ entities,
172
+ selectedIds: effectedIds,
173
+ source,
174
+ destination,
175
+ })
176
+
177
+ // Update local state
178
+ if (newOrder?.length) {
179
+ setOrderedData(newOrder as any)
180
+ }
181
+
182
+ // Transact new order patches
183
+ if (patches?.length) {
184
+ transactPatches(patches, message)
185
+ }
186
+ },
187
+ [selectedIds, setDraggingId, setSelectedIds, transactPatches, setListIsUpdating]
188
+ )
189
+
190
+ const handleDragStart = useCallback(
191
+ (start: {draggableId: string}) => {
192
+ const id = start.draggableId
193
+ const selected = selectedIds.includes(id)
194
+
195
+ // if dragging an item that is not selected - unselect all items
196
+ if (!selected) clearSelected()
197
+
198
+ setDraggingId(id)
199
+ },
200
+ [selectedIds, clearSelected, setDraggingId]
201
+ )
202
+
203
+ // Move one document up or down one place, by fake invoking the drag function
204
+ const incrementIndex = useCallback(
205
+ (shiftFrom: number, shiftTo: number, id: string, entities: SanityDocument[]) => {
206
+ const result = {
207
+ draggableId: id,
208
+ source: {index: shiftFrom},
209
+ destination: {index: shiftTo},
210
+ }
211
+
212
+ return handleDragEnd(result as DropResult, entities)
213
+ },
214
+ [handleDragEnd]
215
+ )
216
+
217
+ const onWindowKeyDown = useCallback(
218
+ (event: KeyboardEvent) => {
219
+ if (event.key === 'Escape') {
220
+ clearSelected()
221
+ }
222
+ },
223
+ [clearSelected]
224
+ )
225
+
226
+ useEffect(() => {
227
+ window.addEventListener('keydown', onWindowKeyDown)
228
+
229
+ return () => {
230
+ window.removeEventListener('keydown', onWindowKeyDown)
231
+ }
232
+ }, [onWindowKeyDown])
233
+
234
+ // Find all items with duplicate order field
235
+ const duplicateOrders = useMemo(() => {
236
+ if (!orderedData.length) return []
237
+
238
+ const orderField = orderedData.map((item) => item[ORDER_FIELD_NAME])
239
+
240
+ return orderField.filter((item, index) => orderField.indexOf(item) !== index)
241
+ }, [orderedData])
242
+
243
+ const onDragEnd = useCallback(
244
+ (result: DropResult) => handleDragEnd(result, orderedData),
245
+ [orderedData, handleDragEnd]
246
+ )
247
+
248
+ return (
249
+ <DragDropContext onDragStart={handleDragStart} onDragEnd={onDragEnd}>
250
+ <Droppable droppableId="documentSortZone">
251
+ {(provided) => (
252
+ <div {...provided.droppableProps} ref={provided.innerRef}>
253
+ {orderedData.map((item, index) => (
254
+ <Draggable
255
+ key={`${item._id}-${item[ORDER_FIELD_NAME]}`}
256
+ draggableId={item._id}
257
+ index={index}
258
+ // onClick={(event) => handleDraggableClick(event, provided, snapshot)}
259
+ >
260
+ {(innerProvided, innerSnapshot) => {
261
+ const isSelected = selectedIds.includes(item._id)
262
+ const isDragging = innerSnapshot.isDragging
263
+ const isGhosting = Boolean(!isDragging && draggingId && isSelected)
264
+ const isUpdating = listIsUpdating && isSelected
265
+ const isDisabled = Boolean(!item[ORDER_FIELD_NAME])
266
+ const isDuplicate = duplicateOrders.includes(item[ORDER_FIELD_NAME])
267
+ const tone = cardTone({isDuplicate, isGhosting, isDragging, isSelected})
268
+
269
+ return (
270
+ <div
271
+ ref={innerProvided.innerRef}
272
+ {...innerProvided.draggableProps}
273
+ {...innerProvided.dragHandleProps}
274
+ style={
275
+ isDisabled
276
+ ? {opacity: 0.2, pointerEvents: `none`}
277
+ : getItemStyle(innerProvided.draggableProps.style, isUpdating)
278
+ }
279
+ >
280
+ <Box paddingBottom={1}>
281
+ <Card tone={tone} shadow={isDragging ? 2 : undefined} radius={2}>
282
+ <Document
283
+ doc={item}
284
+ entities={orderedData}
285
+ handleSelect={handleSelect}
286
+ increment={incrementIndex}
287
+ index={index}
288
+ isFirst={index === 0}
289
+ isLast={index === orderedData.length - 1}
290
+ />
291
+ </Card>
292
+ </Box>
293
+ </div>
294
+ )
295
+ }}
296
+ </Draggable>
297
+ ))}
298
+ {provided.placeholder}
299
+ </div>
300
+ )}
301
+ </Droppable>
302
+ </DragDropContext>
303
+ )
304
+ }
@@ -1,8 +1,7 @@
1
- import PropTypes from 'prop-types'
2
- import React from 'react'
1
+ import React, {PropsWithChildren} from 'react'
3
2
  import {Box, Card, Text} from '@sanity/ui'
4
3
 
5
- export default function Feedback({children}) {
4
+ export default function Feedback({children}: PropsWithChildren<{}>) {
6
5
  return (
7
6
  <Box padding={3}>
8
7
  <Card padding={4} radius={2} shadow={1} tone="caution">
@@ -11,7 +10,3 @@ export default function Feedback({children}) {
11
10
  </Box>
12
11
  )
13
12
  }
14
-
15
- Feedback.propTypes = {
16
- children: PropTypes.node.isRequired,
17
- }
@@ -0,0 +1,7 @@
1
+ import React from 'react'
2
+
3
+ export interface OrderableContextValue {
4
+ showIncrements?: boolean
5
+ }
6
+
7
+ export const OrderableContext = React.createContext<OrderableContextValue>({})
@@ -1,20 +1,27 @@
1
- import PropTypes from 'prop-types'
2
1
  import React, {Component} from 'react'
3
2
 
3
+ import {SanityClient} from '@sanity/client'
4
+ import type {ToastParams} from '@sanity/ui'
4
5
  import DocumentListWrapper from './DocumentListWrapper'
5
6
  import {resetOrder} from './helpers/resetOrder'
6
7
 
7
- // Must use a Class Component here so the actionHandlers can be called
8
- export default class OrderableDocumentList extends Component {
9
- static propTypes = {
10
- options: PropTypes.shape({
11
- type: PropTypes.string,
12
- filter: PropTypes.string,
13
- params: PropTypes.object,
14
- }).isRequired,
8
+ export interface OrderableDocumentListProps {
9
+ options: {
10
+ type: string
11
+ client: SanityClient,
12
+ filter?: string,
13
+ params?: Record<string, unknown>
15
14
  }
15
+ }
16
+
17
+ interface State {
18
+ showIncrements: boolean
19
+ resetOrderTransaction: ToastParams
20
+ }
16
21
 
17
- constructor(props) {
22
+ // Must use a Class Component here so the actionHandlers can be called
23
+ export default class OrderableDocumentList extends Component<OrderableDocumentListProps, State> {
24
+ constructor(props: OrderableDocumentListProps) {
18
25
  super(props)
19
26
  this.state = {
20
27
  showIncrements: false,
@@ -38,7 +45,7 @@ export default class OrderableDocumentList extends Component {
38
45
  },
39
46
  }))
40
47
 
41
- const update = await resetOrder(this.props.options.type)
48
+ const update = await resetOrder(this.props.options.type, this.props.options.client)
42
49
 
43
50
  const reorderWasSuccessful = update?.results?.length
44
51
 
@@ -55,11 +62,15 @@ export default class OrderableDocumentList extends Component {
55
62
  }
56
63
 
57
64
  render() {
65
+ const type = this?.props?.options?.type
66
+ if (!type) {
67
+ return null
68
+ }
58
69
  return (
59
70
  <DocumentListWrapper
60
- type={this?.props?.options?.type}
61
71
  filter={this?.props?.options?.filter}
62
72
  params={this?.props?.options?.params}
73
+ type={type}
63
74
  showIncrements={this.state.showIncrements}
64
75
  resetOrderTransaction={this.state.resetOrderTransaction}
65
76
  />
@@ -1,26 +1,40 @@
1
- import S from '@sanity/desk-tool/structure-builder'
2
- import {SortIcon, GenerateIcon} from '@sanity/icons'
3
- import schema from 'part:@sanity/base/schema'
1
+ import {GenerateIcon, SortIcon} from '@sanity/icons'
2
+ import type {ConfigContext} from 'sanity'
4
3
 
4
+ import {ComponentType} from 'react'
5
+ import {StructureBuilder} from 'sanity/desk'
5
6
  import OrderableDocumentList from '../OrderableDocumentList'
6
7
 
7
- export function orderableDocumentListDeskItem(config = {}) {
8
- if (!config?.type) {
8
+ export interface OrderableListConfig {
9
+ type: string
10
+ id?: string
11
+ title?: string
12
+ icon?: ComponentType
13
+ params?: Record<string, unknown>
14
+ filter?: string
15
+ context: ConfigContext
16
+ S: StructureBuilder
17
+ }
18
+
19
+ export function orderableDocumentListDeskItem(config: OrderableListConfig) {
20
+ if (!config?.type || !config.context || !config.S) {
9
21
  throw new Error(`
10
- "type" not defined in orderableDocumentListDeskItem parameters.
11
- \n\n
22
+ type, context and S (StructureBuilder) must be provided.
23
+ context and S are available when configuring structure.
12
24
  Example: orderableDocumentListDeskItem({type: 'category'})
13
25
  `)
14
26
  }
15
27
 
16
- const {type, filter, params, title, icon, id} = config
28
+ const {type, filter, params, title, icon, id, context, S} = config
29
+ const {schema, getClient} = context
30
+ const client = getClient({apiVersion: '2021-09-01'})
17
31
 
18
32
  const listTitle = title ?? `Orderable ${type}`
19
33
  const listId = id ?? `orderable-${type}`
20
34
  const listIcon = icon ?? SortIcon
21
35
  const typeTitle = schema.get(type)?.title ?? type
22
36
 
23
- return S.listItem(type)
37
+ return S.listItem()
24
38
  .title(listTitle)
25
39
  .id(listId)
26
40
  .icon(listIcon)
@@ -33,7 +47,7 @@ export function orderableDocumentListDeskItem(config = {}) {
33
47
 
34
48
  type: 'component',
35
49
  component: OrderableDocumentList,
36
- options: {type, filter, params},
50
+ options: {type, filter, params, client},
37
51
  menuItems: [
38
52
  S.menuItem()
39
53
  .title(`Create new ${typeTitle}`)
@@ -1,35 +1,37 @@
1
- import sanityClient from 'part:@sanity/base/client'
1
+ import {type ConfigContext, defineField} from 'sanity'
2
2
  import {ORDER_FIELD_NAME} from '../helpers/constants'
3
3
  import initialRank from '../helpers/initialRank'
4
4
 
5
- const client = sanityClient.withConfig({apiVersion: `2021-05-19`})
5
+ export type SchemaContext = Omit<ConfigContext, 'schema' | 'currentUser' | 'client'>
6
6
 
7
- export const orderRankField = (config = {}) => {
7
+ export interface RankFieldConfig {
8
+ type: string
9
+ }
10
+
11
+ export const orderRankField = (config: RankFieldConfig) => {
8
12
  if (!config?.type) {
9
13
  throw new Error(
10
14
  `
11
- "type" not defined in orderRankField parameter object.
15
+ type must be provided.
12
16
  Example: orderRankField({type: 'category'})
13
17
  `
14
18
  )
15
19
  }
16
20
 
17
21
  const {type} = config
18
-
19
- return {
22
+ return defineField({
20
23
  title: 'Order Rank',
21
24
  readOnly: true,
22
25
  hidden: true,
23
26
  ...config,
24
27
  name: ORDER_FIELD_NAME,
25
28
  type: 'string',
26
- initialValue: async () => {
27
- const lastDocOrderRank = await client.fetch(
29
+ initialValue: async ({getClient}) => {
30
+ const lastDocOrderRank = await getClient({apiVersion: '2021-09-01'}).fetch(
28
31
  `*[_type == $type]|order(@[$order] desc)[0][$order]`,
29
32
  {type, order: ORDER_FIELD_NAME}
30
33
  )
31
-
32
34
  return initialRank(lastDocOrderRank)
33
35
  },
34
- }
36
+ })
35
37
  }
@@ -0,0 +1,5 @@
1
+ import {useClient} from 'sanity'
2
+
3
+ export function useSanityClient() {
4
+ return useClient({apiVersion: '2021-09-01'})
5
+ }
@@ -0,0 +1 @@
1
+ export const ORDER_FIELD_NAME = `orderRank` as const
@@ -2,9 +2,9 @@ import {LexoRank} from 'lexorank'
2
2
 
3
3
  // Use in initial value field by passing in the rank value of the last document
4
4
  // If not value passed, generate a sensibly low rank
5
- export default function initialRank(lastRankValue = ``) {
5
+ export default function initialRank(lastRankValue = ``): string {
6
6
  const lastRank = lastRankValue ? LexoRank.parse(lastRankValue) : LexoRank.min()
7
7
  const nextRank = lastRank.genNext().genNext()
8
8
 
9
- return nextRank.value
9
+ return (nextRank as any).value
10
10
  }
@@ -1,7 +1,25 @@
1
1
  import {LexoRank} from 'lexorank'
2
+ import type {PatchOperations, SanityDocument} from 'sanity'
2
3
  import {ORDER_FIELD_NAME} from './constants'
3
4
 
4
- function lexicographicalSort(a, b) {
5
+ export interface MaifestArgs {
6
+ entities: SanityDocument[]
7
+ selectedItems: SanityDocument[]
8
+ isMovingUp: boolean
9
+ curIndex: number
10
+ nextIndex: number
11
+ prevIndex: number
12
+ }
13
+
14
+ export interface ReorderArgs {
15
+ entities: SanityDocument[]
16
+ selectedIds: string[]
17
+ source: any
18
+ destination: any
19
+ debug?: boolean
20
+ }
21
+
22
+ function lexicographicalSort(a: {[ORDER_FIELD_NAME]: string}, b: {[ORDER_FIELD_NAME]: string}) {
5
23
  if (a[ORDER_FIELD_NAME] < b[ORDER_FIELD_NAME]) {
6
24
  return -1
7
25
  }
@@ -11,42 +29,13 @@ function lexicographicalSort(a, b) {
11
29
  return 0
12
30
  }
13
31
 
14
- // In lieu of actual *tests*, this is a table
15
- // to visualise the new order which if correct, shows:
16
- // 1. The `before` field (or start of the list)
17
- // 2. The inserted fields, in order
18
- // 3. The `after` document (or end of the list)
19
- // eslint-disable-next-line no-unused-vars
20
- function createManifest({entities, selectedItems, isMovingUp, curIndex, nextIndex, prevIndex}) {
21
- const table = [
22
- {
23
- name: `Before`,
24
- title:
25
- curIndex === 0 ? `<<Start of List>>` : entities[isMovingUp ? prevIndex : curIndex]?.title,
26
- order: curIndex === 0 ? `000` : entities[isMovingUp ? prevIndex : curIndex][ORDER_FIELD_NAME],
27
- },
28
- ...selectedItems.map((item, itemIndex) => ({
29
- name: itemIndex,
30
- title: item?.title,
31
- order: item[ORDER_FIELD_NAME],
32
- })),
33
- {
34
- name: `After`,
35
- title:
36
- curIndex === entities.length - 1
37
- ? `<<End of List>>`
38
- : entities[isMovingUp ? curIndex : nextIndex]?.title,
39
- order:
40
- curIndex === entities.length - 1
41
- ? `zzz`
42
- : entities[isMovingUp ? curIndex : nextIndex][ORDER_FIELD_NAME],
43
- },
44
- ]
45
-
46
- return table.sort(lexicographicalSort)
47
- }
48
-
49
- export const reorderDocuments = ({entities, selectedIds, source, destination, debug = false}) => {
32
+ export const reorderDocuments = ({
33
+ entities,
34
+ selectedIds,
35
+ source,
36
+ destination,
37
+ debug = false,
38
+ }: ReorderArgs) => {
50
39
  const startIndex = source.index
51
40
  const endIndex = destination.index
52
41
  const isMovingUp = startIndex > endIndex
@@ -70,21 +59,21 @@ export const reorderDocuments = ({entities, selectedIds, source, destination, de
70
59
  if (curIndex === endIndex) {
71
60
  const prevIndex = curIndex - 1
72
61
  const prevRank = entities[prevIndex]?.[ORDER_FIELD_NAME]
73
- ? LexoRank.parse(entities[prevIndex]?.[ORDER_FIELD_NAME])
62
+ ? LexoRank.parse(entities[prevIndex]?.[ORDER_FIELD_NAME] as string)
74
63
  : LexoRank.min()
75
64
 
76
- const curRank = LexoRank.parse(entities[curIndex][ORDER_FIELD_NAME])
65
+ const curRank = LexoRank.parse(entities[curIndex][ORDER_FIELD_NAME] as string)
77
66
 
78
67
  const nextIndex = curIndex + 1
79
68
  const nextRank = entities[nextIndex]?.[ORDER_FIELD_NAME]
80
- ? LexoRank.parse(entities[nextIndex]?.[ORDER_FIELD_NAME])
69
+ ? LexoRank.parse(entities[nextIndex]?.[ORDER_FIELD_NAME] as string)
81
70
  : LexoRank.max()
82
71
 
83
72
  let betweenRank = isMovingUp ? prevRank.between(curRank) : curRank.between(nextRank)
84
73
 
85
74
  // For each selected item, assign a new orderRank between now and next
86
75
  for (let selectedIndex = 0; selectedIndex < selectedItems.length; selectedIndex += 1) {
87
- selectedItems[selectedIndex][ORDER_FIELD_NAME] = betweenRank.value
76
+ selectedItems[selectedIndex][ORDER_FIELD_NAME] = (betweenRank as any).value as string
88
77
  betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank)
89
78
  }
90
79
 
@@ -100,10 +89,10 @@ export const reorderDocuments = ({entities, selectedIds, source, destination, de
100
89
 
101
90
  return {all: [...acc.all, cur], selected: acc.selected}
102
91
  },
103
- {all: [], selected: []}
92
+ {all: [] as SanityDocument[], selected: [] as SanityDocument[]}
104
93
  )
105
94
 
106
- const patches = selected.map((doc) => {
95
+ const patches: [string, PatchOperations][] = selected.map((doc) => {
107
96
  return [
108
97
  doc._id,
109
98
  {
@@ -115,7 +104,7 @@ export const reorderDocuments = ({entities, selectedIds, source, destination, de
115
104
  })
116
105
 
117
106
  // Safety-check to make sure everything is in order
118
- const allSorted = all.sort(lexicographicalSort)
107
+ const allSorted = (all as unknown as {[ORDER_FIELD_NAME]: string}[]).sort(lexicographicalSort)
119
108
 
120
109
  return {newOrder: allSorted, patches, message}
121
110
  }
@@ -1,14 +1,9 @@
1
1
  import {LexoRank} from 'lexorank'
2
- import sanityClient from 'part:@sanity/base/client'
3
2
  import {ORDER_FIELD_NAME} from './constants'
4
-
5
- const client = sanityClient.withConfig({
6
- apiVersion: '2021-09-01',
7
- })
8
-
3
+ import {SanityClient} from '@sanity/client'
9
4
  // Function to wipe and re-do ordering with LexoRank
10
5
  // Will at least attempt to start with the current order
11
- export async function resetOrder(type = ``) {
6
+ export async function resetOrder(type = ``, client: SanityClient) {
12
7
  const query = `*[_type == $type]|order(@[$order] asc)._id`
13
8
  const queryParams = {type, order: ORDER_FIELD_NAME}
14
9
  const documents = await client.fetch(query, queryParams)
@@ -25,7 +20,7 @@ export async function resetOrder(type = ``) {
25
20
  aLexoRank = aLexoRank.genNext().genNext()
26
21
 
27
22
  transaction.patch(documents[index], {
28
- set: {[ORDER_FIELD_NAME]: aLexoRank.value},
23
+ set: {[ORDER_FIELD_NAME]: (aLexoRank as any).value as string},
29
24
  })
30
25
  }
31
26