sanity-plugin-workflow 1.0.0-beta.1 → 1.0.0-beta.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.
Files changed (49) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +71 -12
  3. package/lib/{src/index.d.ts → index.d.ts} +3 -3
  4. package/lib/index.esm.js +1691 -1
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +1704 -1
  7. package/lib/index.js.map +1 -1
  8. package/package.json +48 -38
  9. package/src/actions/AssignWorkflow.tsx +48 -0
  10. package/src/actions/BeginWorkflow.tsx +68 -0
  11. package/src/actions/CompleteWorkflow.tsx +41 -0
  12. package/src/actions/RequestReviewAction.js +1 -7
  13. package/src/actions/UpdateWorkflow.tsx +142 -0
  14. package/src/badges/AssigneesBadge.tsx +52 -0
  15. package/src/badges/{index.tsx → StateBadge.tsx} +4 -8
  16. package/src/components/DocumentCard/AvatarGroup.tsx +12 -8
  17. package/src/components/DocumentCard/CompleteButton.tsx +53 -0
  18. package/src/components/DocumentCard/EditButton.tsx +3 -2
  19. package/src/components/DocumentCard/Field.tsx +38 -0
  20. package/src/components/DocumentCard/ValidationStatus.tsx +37 -0
  21. package/src/components/DocumentCard/core/DraftStatus.tsx +32 -0
  22. package/src/components/DocumentCard/core/PublishedStatus.tsx +32 -0
  23. package/src/components/DocumentCard/core/TimeAgo.tsx +11 -0
  24. package/src/components/DocumentCard/index.tsx +156 -50
  25. package/src/components/Filters.tsx +168 -0
  26. package/src/components/FloatingCard.tsx +29 -0
  27. package/src/components/StateTitle/Status.tsx +27 -0
  28. package/src/components/StateTitle/index.tsx +73 -0
  29. package/src/components/UserAssignment.tsx +57 -75
  30. package/src/components/UserAssignmentInput.tsx +27 -0
  31. package/src/components/UserDisplay.tsx +57 -0
  32. package/src/components/Validators.tsx +196 -0
  33. package/src/components/WorkflowTool.tsx +301 -160
  34. package/src/constants/index.ts +31 -0
  35. package/src/helpers/arraysContainMatchingString.ts +6 -0
  36. package/src/helpers/filterItemsAndSort.ts +39 -0
  37. package/src/helpers/initialRank.ts +13 -0
  38. package/src/hooks/useWorkflowDocuments.tsx +62 -70
  39. package/src/hooks/useWorkflowMetadata.tsx +0 -1
  40. package/src/index.ts +38 -58
  41. package/src/schema/workflow/workflow.metadata.ts +68 -0
  42. package/src/tools/index.ts +15 -0
  43. package/src/types/index.ts +27 -6
  44. package/src/actions/DemoteAction.tsx +0 -62
  45. package/src/actions/PromoteAction.tsx +0 -62
  46. package/src/components/Mutate.tsx +0 -54
  47. package/src/components/StateTimeline.tsx +0 -98
  48. package/src/components/UserSelectInput.tsx +0 -43
  49. package/src/schema/workflow/metadata.ts +0 -38
@@ -1,68 +1,44 @@
1
1
  import React from 'react'
2
- import {
3
- Flex,
4
- Card,
5
- Box,
6
- Grid,
7
- Spinner,
8
- Label,
9
- useToast,
10
- Container,
11
- useTheme,
12
- Button,
13
- } from '@sanity/ui'
2
+ import {Flex, Card, Grid, Spinner, Container, useTheme} from '@sanity/ui'
14
3
  import {Feedback, useProjectUsers} from 'sanity-plugin-utils'
15
- import {Tool, useClient} from 'sanity'
4
+ import {Tool, useCurrentUser} from 'sanity'
16
5
  import {
17
6
  DragDropContext,
18
7
  Droppable,
19
8
  Draggable,
20
9
  DropResult,
10
+ DragStart,
21
11
  } from 'react-beautiful-dnd'
22
12
 
23
- import {SanityDocumentWithMetadata, State} from '../types'
13
+ import {State, WorkflowConfig} from '../types'
24
14
  import {DocumentCard} from './DocumentCard'
25
- import Mutate from './Mutate'
26
15
  import {useWorkflowDocuments} from '../hooks/useWorkflowDocuments'
16
+ import {API_VERSION} from '../constants'
27
17
 
28
- function filterItemsByState(
29
- items: SanityDocumentWithMetadata[],
30
- stateId: string
31
- ) {
32
- return items.filter((item) => item?._metadata?.state === stateId)
33
- }
34
-
35
- type WorkflowToolOptions = {
36
- schemaTypes: string[]
37
- states: State[]
38
- }
18
+ import Validators from './Validators'
19
+ import Filters from './Filters'
20
+ import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
21
+ import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
22
+ import StateTitle from './StateTitle'
23
+ import {LexoRank} from 'lexorank'
39
24
 
40
25
  type WorkflowToolProps = {
41
- tool: Tool<WorkflowToolOptions>
42
- }
43
-
44
- type MutateProps = {
45
- _id: string
46
- _type: string
47
- state: State
48
- documentId: string
26
+ tool: Tool<WorkflowConfig>
49
27
  }
50
28
 
51
29
  export default function WorkflowTool(props: WorkflowToolProps) {
52
30
  const {schemaTypes = [], states = []} = props?.tool?.options ?? {}
53
31
 
54
- const [mutatingDocs, setMutatingDocs] = React.useState<MutateProps[]>([])
55
- const mutationFinished = React.useCallback((documentId: string) => {
56
- setMutatingDocs((docs) => docs.filter((doc) => doc._id !== documentId))
57
- }, [])
58
-
59
- const client = useClient()
60
- const toast = useToast()
61
-
62
32
  const isDarkMode = useTheme().sanity.color.dark
63
33
  const defaultCardTone = isDarkMode ? 'default' : 'transparent'
64
34
 
65
- const userList = useProjectUsers() || []
35
+ const userList = useProjectUsers({apiVersion: API_VERSION})
36
+
37
+ const user = useCurrentUser()
38
+ const userRoleNames = user?.roles?.length
39
+ ? user?.roles.map((r) => r.name)
40
+ : []
41
+
66
42
  const {workflowData, operations} = useWorkflowDocuments(schemaTypes)
67
43
 
68
44
  // Data to display in cards
@@ -71,54 +47,178 @@ export default function WorkflowTool(props: WorkflowToolProps) {
71
47
  // Operations to perform on cards
72
48
  const {move} = operations
73
49
 
74
- const documentsWithoutMetadataIds = data
75
- .filter((doc) => !doc._metadata)
76
- .map((d) => d._id.replace(`drafts.`, ``))
77
-
78
- const importDocuments = React.useCallback(async (ids: string[]) => {
79
- toast.push({
80
- title: 'Importing documents',
81
- status: 'info',
82
- })
83
-
84
- const tx = ids.reduce((item, documentId) => {
85
- return item.createOrReplace({
86
- _id: `workflow-metadata.${documentId}`,
87
- _type: 'workflow.metadata',
88
- state: states[0].id,
89
- documentId,
90
- })
91
- }, client.transaction())
92
-
93
- await tx.commit()
94
-
95
- toast.push({
96
- title: 'Imported documents',
97
- status: 'success',
98
- })
99
- }, [])
50
+ const [undroppableStates, setUndroppableStates] = React.useState<string[]>([])
51
+ const [draggingFrom, setDraggingFrom] = React.useState(``)
52
+
53
+ // When drag starts, check for any States we should not allow dropping on
54
+ // Because of either:
55
+ // 1. The "destination" State requires user assignment and the user is not assigned to the dragged document
56
+ // 2. The "source" State State has a list of transitions and the "destination" State is not in that list
57
+ const handleDragStart = React.useCallback(
58
+ (start: DragStart) => {
59
+ const {draggableId, source} = start
60
+ const {droppableId: currentStateId} = source
61
+ setDraggingFrom(currentStateId)
62
+
63
+ const document = data.find(
64
+ (item) => item._metadata?.documentId === draggableId
65
+ )
66
+ const state = states.find((s) => s.id === currentStateId)
67
+
68
+ // This shouldn't happen but TypeScript
69
+ if (!document || !state) return
70
+
71
+ const undroppableStateIds = []
72
+ const statesThatRequireAssignmentIds = states
73
+ .filter((s) => s.requireAssignment)
74
+ .map((s) => s.id)
75
+
76
+ if (statesThatRequireAssignmentIds.length) {
77
+ const documentAssignees = document._metadata?.assignees ?? []
78
+ const userIsAssignedToDocument = user?.id
79
+ ? documentAssignees.includes(user.id)
80
+ : false
81
+
82
+ if (!userIsAssignedToDocument) {
83
+ undroppableStateIds.push(...statesThatRequireAssignmentIds)
84
+ }
85
+ }
86
+
87
+ const statesThatCannotBeTransitionedToIds =
88
+ state.transitions && state.transitions.length
89
+ ? states
90
+ .filter((s) => !state.transitions?.includes(s.id))
91
+ .map((s) => s.id)
92
+ : []
93
+
94
+ if (statesThatCannotBeTransitionedToIds.length) {
95
+ undroppableStateIds.push(...statesThatCannotBeTransitionedToIds)
96
+ }
97
+
98
+ // Remove currentStateId from undroppableStates
99
+ const undroppableExceptSelf = undroppableStateIds.filter(
100
+ (id) => id !== currentStateId
101
+ )
102
+
103
+ if (undroppableExceptSelf.length) {
104
+ setUndroppableStates(undroppableExceptSelf)
105
+ }
106
+ },
107
+ [data, states, user]
108
+ )
100
109
 
101
110
  const handleDragEnd = React.useCallback(
102
111
  (result: DropResult) => {
112
+ // Reset undroppable states
113
+ setUndroppableStates([])
114
+ setDraggingFrom(``)
115
+
103
116
  const {draggableId, source, destination} = result
104
- console.log(
105
- `sending ${draggableId} from ${source.droppableId} to ${destination?.droppableId}`
106
- )
107
117
 
108
- if (!destination || destination.droppableId === source.droppableId) {
118
+ if (
119
+ // No destination?
120
+ !destination ||
121
+ // No change in position?
122
+ (destination.droppableId === source.droppableId &&
123
+ destination.index === source.index)
124
+ ) {
109
125
  return
110
126
  }
111
127
 
112
- // The list of mutating docs is how we un/publish documents
113
- const mutatingDoc = move(draggableId, destination, states)
128
+ // Find all items in current state
129
+ const destinationStateItems = [
130
+ ...filterItemsAndSort(data, destination.droppableId, [], null),
131
+ ]
132
+
133
+ let newOrder
134
+
135
+ if (!destinationStateItems.length) {
136
+ // Only item in state
137
+ // New minimum rank
138
+ newOrder = LexoRank.min().toString()
139
+ } else if (destination.index === 0) {
140
+ // Now first item in order
141
+ const firstItemOrderRank = [...destinationStateItems].shift()?._metadata
142
+ ?.orderRank
143
+ newOrder =
144
+ firstItemOrderRank && typeof firstItemOrderRank === 'string'
145
+ ? LexoRank.parse(firstItemOrderRank).genPrev().toString()
146
+ : LexoRank.min().toString()
147
+ } else if (destination.index + 1 === destinationStateItems.length) {
148
+ // Now last item in order
149
+ const lastItemOrderRank = [...destinationStateItems].pop()?._metadata
150
+ ?.orderRank
151
+ newOrder =
152
+ lastItemOrderRank && typeof lastItemOrderRank === 'string'
153
+ ? LexoRank.parse(lastItemOrderRank).genNext().toString()
154
+ : LexoRank.min().toString()
155
+ } else {
156
+ // Must be between two items
157
+ const itemBefore = destinationStateItems[destination.index]
158
+ const itemBeforeRank = itemBefore?._metadata?.orderRank
159
+ const itemBeforeRankParsed = itemBefore._metadata.orderRank
160
+ ? LexoRank.parse(itemBeforeRank)
161
+ : LexoRank.min()
162
+ const itemAfter = destinationStateItems[destination.index + 1]
163
+ const itemAfterRank = itemAfter?._metadata?.orderRank
164
+ const itemAfterRankParsed = itemAfter._metadata.orderRank
165
+ ? LexoRank.parse(itemAfterRank)
166
+ : LexoRank.max()
114
167
 
115
- if (mutatingDoc) {
116
- // @ts-ignore
117
- // @todo not sure if these types should be updated. will documentId every be undefined here?
118
- setMutatingDocs((current) => [...current, mutatingDoc])
168
+ newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString()
119
169
  }
170
+
171
+ move(draggableId, destination, states, newOrder)
172
+ },
173
+ [data, move, states]
174
+ )
175
+
176
+ const uniqueAssignedUsers = React.useMemo(() => {
177
+ const uniqueUserIds = data.reduce((acc, item) => {
178
+ const {assignees = []} = item._metadata ?? {}
179
+ const newAssignees = assignees?.length
180
+ ? assignees.filter((a) => !acc.includes(a))
181
+ : []
182
+ return newAssignees.length ? [...acc, ...newAssignees] : acc
183
+ }, [] as string[])
184
+
185
+ return userList.filter((u) => uniqueUserIds.includes(u.id))
186
+ }, [data, userList])
187
+
188
+ const [selectedUserIds, setSelectedUserIds] = React.useState<string[]>(
189
+ uniqueAssignedUsers.map((u) => u.id)
190
+ )
191
+ const toggleSelectedUser = React.useCallback((userId: string) => {
192
+ setSelectedUserIds((prev) =>
193
+ prev.includes(userId)
194
+ ? prev.filter((u) => u !== userId)
195
+ : [...prev, userId]
196
+ )
197
+ }, [])
198
+ const resetSelectedUsers = React.useCallback(() => {
199
+ setSelectedUserIds([])
200
+ }, [])
201
+
202
+ const [selectedSchemaTypes, setSelectedSchemaTypes] =
203
+ React.useState<string[]>(schemaTypes)
204
+ const toggleSelectedSchemaType = React.useCallback((schemaType: string) => {
205
+ setSelectedSchemaTypes((prev) =>
206
+ prev.includes(schemaType)
207
+ ? prev.filter((u) => u !== schemaType)
208
+ : [...prev, schemaType]
209
+ )
210
+ }, [])
211
+
212
+ const [invalidDocumentIds, setInvalidDocumentIds] = React.useState<string[]>(
213
+ []
214
+ )
215
+ const toggleInvalidDocumentId = React.useCallback(
216
+ (docId: string, action: 'ADD' | 'REMOVE') => {
217
+ setInvalidDocumentIds((prev) =>
218
+ action === 'ADD' ? [...prev, docId] : prev.filter((id) => id !== docId)
219
+ )
120
220
  },
121
- [move, states]
221
+ []
122
222
  )
123
223
 
124
224
  if (!states?.length) {
@@ -133,96 +233,137 @@ export default function WorkflowTool(props: WorkflowToolProps) {
133
233
  )
134
234
  }
135
235
 
136
- if (error) {
236
+ if (error && !data.length) {
137
237
  return (
138
238
  <Container width={1} padding={5}>
139
- <Feedback tone="critical" title="Error with query" />
239
+ <Feedback
240
+ tone="critical"
241
+ title="Error querying for Workflow documents"
242
+ />
140
243
  </Container>
141
244
  )
142
245
  }
143
246
 
144
247
  return (
145
- <>
146
- {mutatingDocs.length ? (
147
- <div style={{position: `absolute`, bottom: 0, background: 'red'}}>
148
- {mutatingDocs.map((mutate) => (
149
- <Mutate
150
- key={mutate._id}
151
- {...mutate}
152
- onComplete={mutationFinished}
153
- />
154
- ))}
155
- </div>
156
- ) : null}
157
- {documentsWithoutMetadataIds.length > 0 && (
158
- <Box padding={5}>
159
- <Card border padding={3} tone="caution">
160
- <Flex align="center" justify="center">
161
- <Button
162
- onClick={() => importDocuments(documentsWithoutMetadataIds)}
163
- >
164
- Import {documentsWithoutMetadataIds.length} Missing{' '}
165
- {documentsWithoutMetadataIds.length === 1
166
- ? `Document`
167
- : `Documents`}{' '}
168
- into Workflow
169
- </Button>
170
- </Flex>
171
- </Card>
172
- </Box>
173
- )}
174
- <DragDropContext onDragEnd={handleDragEnd}>
248
+ <Card height="fill" overflow="hidden">
249
+ <Validators data={data} userList={userList} states={states} />
250
+ <Filters
251
+ uniqueAssignedUsers={uniqueAssignedUsers}
252
+ selectedUserIds={selectedUserIds}
253
+ toggleSelectedUser={toggleSelectedUser}
254
+ resetSelectedUsers={resetSelectedUsers}
255
+ schemaTypes={schemaTypes}
256
+ selectedSchemaTypes={selectedSchemaTypes}
257
+ toggleSelectedSchemaType={toggleSelectedSchemaType}
258
+ />
259
+ <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
175
260
  <Grid columns={states.length} height="fill">
176
- {states.map((state: State, stateIndex: number) => (
177
- <Card key={state.id} borderLeft={stateIndex > 0}>
178
- <Card paddingY={4} padding={3} style={{pointerEvents: `none`}}>
179
- <Label>{state.title}</Label>
261
+ {states.map((state: State, stateIndex: number) => {
262
+ const userRoleCanDrop = state?.roles?.length
263
+ ? arraysContainMatchingString(state.roles, userRoleNames)
264
+ : true
265
+ const isDropDisabled =
266
+ !userRoleCanDrop || undroppableStates.includes(state.id)
267
+
268
+ return (
269
+ <Card
270
+ key={state.id}
271
+ borderLeft={stateIndex > 0}
272
+ tone={defaultCardTone}
273
+ height="fill"
274
+ overflow="auto"
275
+ >
276
+ <StateTitle
277
+ state={state}
278
+ requireAssignment={state.requireAssignment ?? false}
279
+ userRoleCanDrop={userRoleCanDrop}
280
+ // operation={state.operation}
281
+ isDropDisabled={isDropDisabled}
282
+ draggingFrom={draggingFrom}
283
+ />
284
+ <Droppable
285
+ droppableId={state.id}
286
+ isDropDisabled={isDropDisabled}
287
+ >
288
+ {(provided, snapshot) => (
289
+ <Card
290
+ ref={provided.innerRef}
291
+ tone={
292
+ snapshot.isDraggingOver ? `primary` : defaultCardTone
293
+ }
294
+ height="fill"
295
+ paddingTop={1}
296
+ >
297
+ {loading ? (
298
+ <Flex padding={5} align="center" justify="center">
299
+ <Spinner muted />
300
+ </Flex>
301
+ ) : null}
302
+
303
+ {data.length > 0 &&
304
+ filterItemsAndSort(
305
+ data,
306
+ state.id,
307
+ selectedUserIds,
308
+ selectedSchemaTypes
309
+ ).map((item, itemIndex) => {
310
+ const isInvalid = invalidDocumentIds.includes(
311
+ String(item?._metadata?.documentId)
312
+ )
313
+ const meInAssignees = user?.id
314
+ ? item?._metadata?.assignees?.includes(user.id)
315
+ : false
316
+ const isDragDisabled =
317
+ !userRoleCanDrop ||
318
+ isInvalid ||
319
+ !(state.requireAssignment
320
+ ? state.requireAssignment && meInAssignees
321
+ : true)
322
+ const {documentId} = item._metadata ?? {}
323
+
324
+ if (!documentId) {
325
+ return null
326
+ }
327
+
328
+ return (
329
+ <Draggable
330
+ // The metadata's documentId is always the published one to avoid rerendering
331
+ key={documentId}
332
+ draggableId={documentId}
333
+ index={itemIndex}
334
+ isDragDisabled={isDragDisabled}
335
+ >
336
+ {(draggableProvided, draggableSnapshot) => (
337
+ <div
338
+ ref={draggableProvided.innerRef}
339
+ {...draggableProvided.draggableProps}
340
+ {...draggableProvided.dragHandleProps}
341
+ >
342
+ <DocumentCard
343
+ userRoleCanDrop={userRoleCanDrop}
344
+ isDragDisabled={isDragDisabled}
345
+ isDragging={draggableSnapshot.isDragging}
346
+ item={item}
347
+ toggleInvalidDocumentId={
348
+ toggleInvalidDocumentId
349
+ }
350
+ userList={userList}
351
+ states={states}
352
+ />
353
+ </div>
354
+ )}
355
+ </Draggable>
356
+ )
357
+ })}
358
+ {provided.placeholder}
359
+ </Card>
360
+ )}
361
+ </Droppable>
180
362
  </Card>
181
- <Droppable droppableId={state.id}>
182
- {(provided, snapshot) => (
183
- <Card
184
- ref={provided.innerRef}
185
- tone={snapshot.isDraggingOver ? `primary` : defaultCardTone}
186
- height="fill"
187
- >
188
- {loading ? (
189
- <Flex padding={5} align="center" justify="center">
190
- <Spinner muted />
191
- </Flex>
192
- ) : null}
193
-
194
- {data.length > 0 &&
195
- filterItemsByState(data, state.id).map(
196
- (item, itemIndex) => (
197
- // The metadata's documentId is always the published one
198
- <Draggable
199
- key={item?._metadata?.documentId as string}
200
- draggableId={item?._metadata?.documentId as string}
201
- index={itemIndex}
202
- >
203
- {(draggableProvided, draggableSnapshot) => (
204
- <div
205
- ref={draggableProvided.innerRef}
206
- {...draggableProvided.draggableProps}
207
- {...draggableProvided.dragHandleProps}
208
- >
209
- <DocumentCard
210
- isDragging={draggableSnapshot.isDragging}
211
- item={item}
212
- userList={userList}
213
- />
214
- </div>
215
- )}
216
- </Draggable>
217
- )
218
- )}
219
- </Card>
220
- )}
221
- </Droppable>
222
- </Card>
223
- ))}
363
+ )
364
+ })}
224
365
  </Grid>
225
366
  </DragDropContext>
226
- </>
367
+ </Card>
227
368
  )
228
369
  }
@@ -0,0 +1,31 @@
1
+ import {WorkflowConfig, defineStates} from '../types'
2
+
3
+ export const API_VERSION = `2023-01-01`
4
+
5
+ export const DEFAULT_CONFIG: WorkflowConfig = {
6
+ schemaTypes: [],
7
+ states: defineStates([
8
+ {
9
+ id: 'inReview',
10
+ title: 'In review',
11
+ color: 'primary',
12
+ roles: ['editor', 'administrator'],
13
+ transitions: ['changesRequested', 'approved'],
14
+ },
15
+ {
16
+ id: 'changesRequested',
17
+ title: 'Changes requested',
18
+ color: 'warning',
19
+ roles: ['editor', 'administrator'],
20
+ transitions: ['approved'],
21
+ },
22
+ {
23
+ id: 'approved',
24
+ title: 'Approved',
25
+ color: 'success',
26
+ roles: ['administrator'],
27
+ requireAssignment: true,
28
+ transitions: ['changesRequested'],
29
+ },
30
+ ]),
31
+ }
@@ -0,0 +1,6 @@
1
+ export function arraysContainMatchingString(
2
+ one: string[],
3
+ two: string[]
4
+ ): boolean {
5
+ return one.some((item) => two.includes(item))
6
+ }
@@ -0,0 +1,39 @@
1
+ import {SanityDocumentWithMetadata} from '../types'
2
+
3
+ export function filterItemsAndSort(
4
+ items: SanityDocumentWithMetadata[],
5
+ stateId: string,
6
+ selectedUsers: string[] = [],
7
+ selectedSchemaTypes: null | string[] = []
8
+ ): SanityDocumentWithMetadata[] {
9
+ return (
10
+ items
11
+ // Only items of this state
12
+ .filter((item) => item?._metadata?.state === stateId)
13
+ // Only items with selected users, if the document has any assigned users
14
+ .filter((item) =>
15
+ selectedUsers.length && item._metadata?.assignees?.length
16
+ ? item._metadata?.assignees.some((assignee) =>
17
+ selectedUsers.includes(assignee)
18
+ )
19
+ : !selectedUsers.length
20
+ )
21
+ // Only items of selected schema types, if any are selected
22
+ .filter((item) => {
23
+ if (!selectedSchemaTypes) {
24
+ return true
25
+ }
26
+
27
+ return selectedSchemaTypes.length
28
+ ? selectedSchemaTypes.includes(item._type)
29
+ : false
30
+ })
31
+ // Sort by metadata orderRank, a string field
32
+ .sort((a, b) => {
33
+ const aOrderRank = a._metadata?.orderRank || '0'
34
+ const bOrderRank = b._metadata?.orderRank || '0'
35
+
36
+ return aOrderRank.localeCompare(bOrderRank)
37
+ })
38
+ )
39
+ }
@@ -0,0 +1,13 @@
1
+ import {LexoRank} from 'lexorank'
2
+
3
+ // Use in initial value field by passing in the rank value of the last document
4
+ // If not value passed, generate a sensibly low rank
5
+ export default function initialRank(lastRankValue = ``): string {
6
+ const lastRank =
7
+ lastRankValue && typeof lastRankValue === 'string'
8
+ ? LexoRank.parse(lastRankValue)
9
+ : LexoRank.min()
10
+ const nextRank = lastRank.genNext().genNext()
11
+
12
+ return (nextRank as any).value
13
+ }