sanity-plugin-workflow 1.0.6 → 2.0.1

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 (52) hide show
  1. package/README.md +2 -17
  2. package/dist/index.d.ts +17 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1647 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +32 -71
  7. package/lib/index.d.ts +0 -20
  8. package/lib/index.esm.js +0 -2135
  9. package/lib/index.esm.js.map +0 -1
  10. package/lib/index.js +0 -2147
  11. package/lib/index.js.map +0 -1
  12. package/sanity.json +0 -8
  13. package/src/actions/AssignWorkflow.tsx +0 -47
  14. package/src/actions/BeginWorkflow.tsx +0 -63
  15. package/src/actions/CompleteWorkflow.tsx +0 -64
  16. package/src/actions/UpdateWorkflow.tsx +0 -126
  17. package/src/badges/AssigneesBadge.tsx +0 -53
  18. package/src/badges/StateBadge.tsx +0 -28
  19. package/src/components/DocumentCard/AvatarGroup.tsx +0 -43
  20. package/src/components/DocumentCard/CompleteButton.tsx +0 -56
  21. package/src/components/DocumentCard/EditButton.tsx +0 -28
  22. package/src/components/DocumentCard/Field.tsx +0 -38
  23. package/src/components/DocumentCard/Validate.tsx +0 -21
  24. package/src/components/DocumentCard/ValidationStatus.tsx +0 -37
  25. package/src/components/DocumentCard/core/DraftStatus.tsx +0 -32
  26. package/src/components/DocumentCard/core/PublishedStatus.tsx +0 -39
  27. package/src/components/DocumentCard/core/TimeAgo.tsx +0 -11
  28. package/src/components/DocumentCard/index.tsx +0 -200
  29. package/src/components/DocumentList.tsx +0 -169
  30. package/src/components/Filters.tsx +0 -174
  31. package/src/components/FloatingCard.tsx +0 -36
  32. package/src/components/StateTitle/Status.tsx +0 -27
  33. package/src/components/StateTitle/index.tsx +0 -78
  34. package/src/components/UserAssignment.tsx +0 -121
  35. package/src/components/UserAssignmentInput.tsx +0 -27
  36. package/src/components/UserDisplay.tsx +0 -57
  37. package/src/components/Verify.tsx +0 -297
  38. package/src/components/WorkflowContext.tsx +0 -71
  39. package/src/components/WorkflowSignal.tsx +0 -30
  40. package/src/components/WorkflowTool.tsx +0 -437
  41. package/src/constants/index.ts +0 -31
  42. package/src/helpers/arraysContainMatchingString.ts +0 -6
  43. package/src/helpers/filterItemsAndSort.ts +0 -41
  44. package/src/helpers/generateMultipleOrderRanks.ts +0 -80
  45. package/src/helpers/initialRank.ts +0 -13
  46. package/src/hooks/useWorkflowDocuments.tsx +0 -167
  47. package/src/hooks/useWorkflowMetadata.tsx +0 -49
  48. package/src/index.ts +0 -97
  49. package/src/schema/workflow/workflow.metadata.ts +0 -68
  50. package/src/tools/index.ts +0 -15
  51. package/src/types/index.ts +0 -71
  52. package/v2-incompatible.js +0 -11
@@ -1,437 +0,0 @@
1
- import {
2
- DragDropContext,
3
- DraggableChildrenFn,
4
- DragStart,
5
- Droppable,
6
- DropResult,
7
- } from '@hello-pangea/dnd'
8
- import {
9
- Box,
10
- Card,
11
- Container,
12
- Flex,
13
- Grid,
14
- Spinner,
15
- useTheme,
16
- useToast,
17
- } from '@sanity/ui'
18
- import {LexoRank} from 'lexorank'
19
- import React from 'react'
20
- import {Tool, useCurrentUser} from 'sanity'
21
- import {Feedback, useProjectUsers} from 'sanity-plugin-utils'
22
-
23
- import {API_VERSION} from '../constants'
24
- import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
25
- import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
26
- import {useWorkflowDocuments} from '../hooks/useWorkflowDocuments'
27
- import {State, WorkflowConfig} from '../types'
28
- import {DocumentCard} from './DocumentCard'
29
- import DocumentList from './DocumentList'
30
- import Filters from './Filters'
31
- import StateTitle from './StateTitle'
32
- import Verify from './Verify'
33
-
34
- type WorkflowToolProps = {
35
- tool: Tool<WorkflowConfig>
36
- }
37
-
38
- export default function WorkflowTool(props: WorkflowToolProps) {
39
- const {schemaTypes = [], states = []} = props?.tool?.options ?? {}
40
-
41
- const isDarkMode = useTheme().sanity.color.dark
42
- const defaultCardTone = isDarkMode ? 'default' : 'transparent'
43
- const toast = useToast()
44
-
45
- const userList = useProjectUsers({apiVersion: API_VERSION})
46
-
47
- const user = useCurrentUser()
48
- const userRoleNames = user?.roles?.length
49
- ? user?.roles.map((r) => r.name)
50
- : []
51
-
52
- const {workflowData, operations} = useWorkflowDocuments(schemaTypes)
53
- const [patchingIds, setPatchingIds] = React.useState<string[]>([])
54
-
55
- // Data to display in cards
56
- const {data, loading, error} = workflowData
57
-
58
- // Operations to perform on cards
59
- const {move} = operations
60
-
61
- const [undroppableStates, setUndroppableStates] = React.useState<string[]>([])
62
- const [draggingFrom, setDraggingFrom] = React.useState(``)
63
-
64
- // When drag starts, check for any States we should not allow dropping on
65
- // Because of either:
66
- // 1. The "destination" State requires user assignment and the user is not assigned to the dragged document
67
- // 2. The "source" State State has a list of transitions and the "destination" State is not in that list
68
- const handleDragStart = React.useCallback(
69
- (start: DragStart) => {
70
- const {draggableId, source} = start
71
- const {droppableId: currentStateId} = source
72
- setDraggingFrom(currentStateId)
73
-
74
- const document = data.find(
75
- (item) => item._metadata?.documentId === draggableId
76
- )
77
- const state = states.find((s) => s.id === currentStateId)
78
-
79
- // This shouldn't happen but TypeScript
80
- if (!document || !state) return
81
-
82
- const undroppableStateIds = []
83
- const statesThatRequireAssignmentIds = states
84
- .filter((s) => s.requireAssignment)
85
- .map((s) => s.id)
86
-
87
- if (statesThatRequireAssignmentIds.length) {
88
- const documentAssignees = document._metadata?.assignees ?? []
89
- const userIsAssignedToDocument = user?.id
90
- ? documentAssignees.includes(user.id)
91
- : false
92
-
93
- if (!userIsAssignedToDocument) {
94
- undroppableStateIds.push(...statesThatRequireAssignmentIds)
95
- }
96
- }
97
-
98
- const statesThatCannotBeTransitionedToIds =
99
- state.transitions && state.transitions.length
100
- ? states
101
- .filter((s) => !state.transitions?.includes(s.id))
102
- .map((s) => s.id)
103
- : []
104
-
105
- if (statesThatCannotBeTransitionedToIds.length) {
106
- undroppableStateIds.push(...statesThatCannotBeTransitionedToIds)
107
- }
108
-
109
- // Remove currentStateId from undroppableStates
110
- const undroppableExceptSelf = undroppableStateIds.filter(
111
- (id) => id !== currentStateId
112
- )
113
-
114
- if (undroppableExceptSelf.length) {
115
- setUndroppableStates(undroppableExceptSelf)
116
- }
117
- },
118
- [data, states, user]
119
- )
120
-
121
- const handleDragEnd = React.useCallback(
122
- async (result: DropResult) => {
123
- // Reset undroppable states
124
- setUndroppableStates([])
125
- setDraggingFrom(``)
126
-
127
- const {draggableId, source, destination} = result
128
-
129
- if (
130
- // No destination?
131
- !destination ||
132
- // No change in position?
133
- (destination.droppableId === source.droppableId &&
134
- destination.index === source.index)
135
- ) {
136
- return
137
- }
138
-
139
- // Find all items in current state
140
- const destinationStateItems = [
141
- ...filterItemsAndSort(data, destination.droppableId, [], null),
142
- ]
143
- const destinationStateIndex = states.findIndex(
144
- (s) => s.id === destination.droppableId
145
- )
146
- const globalStateMinimumRank = data[0]._metadata.orderRank
147
- const globalStateMaximumRank = data[data.length - 1]._metadata.orderRank
148
-
149
- let newOrder
150
-
151
- if (!destinationStateItems.length) {
152
- // Only item in state
153
- // New minimum rank
154
- if (destinationStateIndex === 0) {
155
- // Only the first state should generate an absolute minimum rank
156
- newOrder = LexoRank.min().toString()
157
- } else {
158
- // Otherwise create one rank above the minimum
159
- newOrder = LexoRank.min().genNext().toString()
160
- }
161
- } else if (destination.index === 0) {
162
- // Now first item in order
163
- const firstItemOrderRank = [...destinationStateItems].shift()?._metadata
164
- ?.orderRank
165
-
166
- if (firstItemOrderRank && typeof firstItemOrderRank === 'string') {
167
- newOrder = LexoRank.parse(firstItemOrderRank).genPrev().toString()
168
- } else if (destinationStateIndex === 0) {
169
- // Only the first state should generate an absolute minimum rank
170
- newOrder = LexoRank.min().toString()
171
- } else {
172
- // Otherwise create the next rank between min and the globally minimum rank
173
- newOrder = LexoRank.parse(globalStateMinimumRank)
174
- .between(LexoRank.min())
175
- .toString()
176
- }
177
- } else if (destination.index + 1 === destinationStateItems.length) {
178
- // Now last item in order
179
- const lastItemOrderRank = [...destinationStateItems].pop()?._metadata
180
- ?.orderRank
181
-
182
- if (lastItemOrderRank && typeof lastItemOrderRank === 'string') {
183
- newOrder = LexoRank.parse(lastItemOrderRank).genNext().toString()
184
- } else if (destinationStateIndex === states.length - 1) {
185
- // Only the last state should generate an absolute maximum rank
186
- newOrder = LexoRank.max().toString()
187
- } else {
188
- // Otherwise create the next rank between max and the globally maximum rank
189
- newOrder = LexoRank.parse(globalStateMaximumRank)
190
- .between(LexoRank.min())
191
- .toString()
192
- }
193
- } else {
194
- // Must be between two items
195
- const itemBefore = destinationStateItems[destination.index - 1]
196
- const itemBeforeRank = itemBefore?._metadata?.orderRank
197
- let itemBeforeRankParsed
198
- if (itemBeforeRank) {
199
- itemBeforeRankParsed = LexoRank.parse(itemBeforeRank)
200
- } else if (destinationStateIndex === 0) {
201
- itemBeforeRankParsed = LexoRank.min()
202
- } else {
203
- itemBeforeRankParsed = LexoRank.parse(globalStateMinimumRank)
204
- }
205
-
206
- const itemAfter = destinationStateItems[destination.index]
207
- const itemAfterRank = itemAfter?._metadata?.orderRank
208
- let itemAfterRankParsed
209
- if (itemAfterRank) {
210
- itemAfterRankParsed = LexoRank.parse(itemAfterRank)
211
- } else if (destinationStateIndex === states.length - 1) {
212
- itemAfterRankParsed = LexoRank.max()
213
- } else {
214
- itemAfterRankParsed = LexoRank.parse(globalStateMaximumRank)
215
- }
216
-
217
- newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString()
218
- }
219
-
220
- setPatchingIds([...patchingIds, draggableId])
221
- toast.push({
222
- status: 'info',
223
- title: 'Updating document state...',
224
- })
225
- await move(draggableId, destination, states, newOrder)
226
- setPatchingIds((ids: string[]) => ids.filter((id) => id !== draggableId))
227
- },
228
- [data, patchingIds, toast, move, states]
229
- )
230
-
231
- // Used for the user filter UI
232
- const uniqueAssignedUsers = React.useMemo(() => {
233
- const uniqueUserIds = data.reduce((acc, item) => {
234
- const {assignees = []} = item._metadata ?? {}
235
- const newAssignees = assignees?.length
236
- ? assignees.filter((a) => !acc.includes(a))
237
- : []
238
- return newAssignees.length ? [...acc, ...newAssignees] : acc
239
- }, [] as string[])
240
-
241
- return userList.filter((u) => uniqueUserIds.includes(u.id))
242
- }, [data, userList])
243
-
244
- // Selected user IDs filter the visible workflow documents
245
- const [selectedUserIds, setSelectedUserIds] = React.useState<string[]>(
246
- uniqueAssignedUsers.map((u) => u.id)
247
- )
248
- const toggleSelectedUser = React.useCallback((userId: string) => {
249
- setSelectedUserIds((prev) =>
250
- prev.includes(userId)
251
- ? prev.filter((u) => u !== userId)
252
- : [...prev, userId]
253
- )
254
- }, [])
255
- const resetSelectedUsers = React.useCallback(() => {
256
- setSelectedUserIds([])
257
- }, [])
258
-
259
- // Selected schema types filter the visible workflow documents
260
- const [selectedSchemaTypes, setSelectedSchemaTypes] =
261
- React.useState<string[]>(schemaTypes)
262
- const toggleSelectedSchemaType = React.useCallback((schemaType: string) => {
263
- setSelectedSchemaTypes((prev) =>
264
- prev.includes(schemaType)
265
- ? prev.filter((u) => u !== schemaType)
266
- : [...prev, schemaType]
267
- )
268
- }, [])
269
-
270
- // Document IDs that have validation errors
271
- const [invalidDocumentIds, setInvalidDocumentIds] = React.useState<string[]>(
272
- []
273
- )
274
- const toggleInvalidDocumentId = React.useCallback(
275
- (docId: string, action: 'ADD' | 'REMOVE') => {
276
- setInvalidDocumentIds((prev) =>
277
- action === 'ADD' ? [...prev, docId] : prev.filter((id) => id !== docId)
278
- )
279
- },
280
- []
281
- )
282
-
283
- const Clone: DraggableChildrenFn = React.useCallback(
284
- (provided, snapshot, rubric) => {
285
- const item = data.find(
286
- (doc) => doc?._metadata?.documentId === rubric.draggableId
287
- )
288
-
289
- return (
290
- <div
291
- {...provided.draggableProps}
292
- {...provided.dragHandleProps}
293
- ref={provided.innerRef}
294
- >
295
- {item ? (
296
- <DocumentCard
297
- // Assumed false, if it's dragging it's not disabled
298
- isDragDisabled={false}
299
- // Assumed false, if it's dragging it's not patching
300
- isPatching={false}
301
- // Assumed true, if you can drag it you can drop it
302
- userRoleCanDrop
303
- isDragging={snapshot.isDragging}
304
- item={item}
305
- states={states}
306
- toggleInvalidDocumentId={toggleInvalidDocumentId}
307
- userList={userList}
308
- />
309
- ) : (
310
- <Feedback title="Item not found" tone="caution" />
311
- )}
312
- </div>
313
- )
314
- },
315
- [data, states, toggleInvalidDocumentId, userList]
316
- )
317
-
318
- if (!states?.length) {
319
- return (
320
- <Container width={1} padding={5}>
321
- <Feedback
322
- tone="caution"
323
- title="Plugin options error"
324
- description="No States defined in plugin config"
325
- />
326
- </Container>
327
- )
328
- }
329
-
330
- if (error && !data.length) {
331
- return (
332
- <Container width={1} padding={5}>
333
- <Feedback
334
- tone="critical"
335
- title="Error querying for Workflow documents"
336
- />
337
- </Container>
338
- )
339
- }
340
-
341
- return (
342
- <Flex direction="column" height="fill" overflow="hidden">
343
- <Verify data={data} userList={userList} states={states} />
344
-
345
- <Filters
346
- uniqueAssignedUsers={uniqueAssignedUsers}
347
- selectedUserIds={selectedUserIds}
348
- toggleSelectedUser={toggleSelectedUser}
349
- resetSelectedUsers={resetSelectedUsers}
350
- schemaTypes={schemaTypes}
351
- selectedSchemaTypes={selectedSchemaTypes}
352
- toggleSelectedSchemaType={toggleSelectedSchemaType}
353
- />
354
- <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
355
- <Grid columns={states.length} height="fill">
356
- {states.map((state: State, stateIndex: number) => {
357
- const userRoleCanDrop = state?.roles?.length
358
- ? arraysContainMatchingString(state.roles, userRoleNames)
359
- : true
360
- const isDropDisabled =
361
- !userRoleCanDrop || undroppableStates.includes(state.id)
362
-
363
- return (
364
- <Card
365
- key={state.id}
366
- borderLeft={stateIndex > 0}
367
- tone={defaultCardTone}
368
- >
369
- <Flex direction="column" height="fill">
370
- <StateTitle
371
- state={state}
372
- requireAssignment={state.requireAssignment ?? false}
373
- userRoleCanDrop={userRoleCanDrop}
374
- isDropDisabled={isDropDisabled}
375
- draggingFrom={draggingFrom}
376
- documentCount={
377
- filterItemsAndSort(
378
- data,
379
- state.id,
380
- selectedUserIds,
381
- selectedSchemaTypes
382
- ).length
383
- }
384
- />
385
- <Box flex={1}>
386
- <Droppable
387
- droppableId={state.id}
388
- isDropDisabled={isDropDisabled}
389
- // props required for virtualization
390
- mode="virtual"
391
- renderClone={Clone}
392
- >
393
- {(provided, snapshot) => (
394
- <Card
395
- ref={provided.innerRef}
396
- tone={
397
- snapshot.isDraggingOver
398
- ? `primary`
399
- : defaultCardTone
400
- }
401
- height="fill"
402
- >
403
- {loading ? (
404
- <Flex padding={5} align="center" justify="center">
405
- <Spinner muted />
406
- </Flex>
407
- ) : null}
408
-
409
- <DocumentList
410
- data={data}
411
- invalidDocumentIds={invalidDocumentIds}
412
- patchingIds={patchingIds}
413
- selectedSchemaTypes={selectedSchemaTypes}
414
- selectedUserIds={selectedUserIds}
415
- state={state}
416
- states={states}
417
- toggleInvalidDocumentId={toggleInvalidDocumentId}
418
- user={user}
419
- userList={userList}
420
- userRoleCanDrop={userRoleCanDrop}
421
- />
422
-
423
- {/* Not required for virtualized lists */}
424
- {/* {provided.placeholder} */}
425
- </Card>
426
- )}
427
- </Droppable>
428
- </Box>
429
- </Flex>
430
- </Card>
431
- )
432
- })}
433
- </Grid>
434
- </DragDropContext>
435
- </Flex>
436
- )
437
- }
@@ -1,31 +0,0 @@
1
- import {defineStates, WorkflowConfig} from '../types'
2
-
3
- export const API_VERSION = `2023-01-01`
4
-
5
- export const DEFAULT_CONFIG: Required<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
- transitions: ['changesRequested'],
28
- requireAssignment: true,
29
- },
30
- ]),
31
- }
@@ -1,6 +0,0 @@
1
- export function arraysContainMatchingString(
2
- one: string[],
3
- two: string[]
4
- ): boolean {
5
- return one.some((item) => two.includes(item))
6
- }
@@ -1,41 +0,0 @@
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 that have existing documents
12
- .filter((item) => item?._id)
13
- // Only items of this state
14
- .filter((item) => item?._metadata?.state === stateId)
15
- // Only items with selected users, if the document has any assigned users
16
- .filter((item) =>
17
- selectedUsers.length && item._metadata?.assignees?.length
18
- ? item._metadata?.assignees.some((assignee) =>
19
- selectedUsers.includes(assignee)
20
- )
21
- : !selectedUsers.length
22
- )
23
- // Only items of selected schema types, if any are selected
24
- .filter((item) => {
25
- if (!selectedSchemaTypes) {
26
- return true
27
- }
28
-
29
- return selectedSchemaTypes.length
30
- ? selectedSchemaTypes.includes(item._type)
31
- : false
32
- })
33
- // Sort by metadata orderRank, a string field
34
- .sort((a, b) => {
35
- const aOrderRank = a._metadata?.orderRank || '0'
36
- const bOrderRank = b._metadata?.orderRank || '0'
37
-
38
- return aOrderRank.localeCompare(bOrderRank)
39
- })
40
- )
41
- }
@@ -1,80 +0,0 @@
1
- import {LexoRank} from 'lexorank'
2
-
3
- function generateMiddleValue(ranks: (LexoRank | undefined)[]) {
4
- // Has no undefined values
5
- if (!ranks.some((rank) => !rank)) {
6
- return ranks
7
- }
8
-
9
- // Find the first undefined value
10
- const firstUndefined = ranks.findIndex((rank) => !rank)
11
-
12
- // Find the first defined value after the undefined value
13
- const firstDefinedAfter = ranks.findIndex(
14
- (rank, index) => rank && index > firstUndefined
15
- )
16
- // Find the first defined value before the undefined value
17
- const firstDefinedBefore = ranks.findLastIndex(
18
- (rank, index) => rank && index < firstUndefined
19
- )
20
-
21
- if (firstDefinedAfter === -1 || firstDefinedBefore === -1) {
22
- throw new Error(
23
- `Unable to generate middle value between indexes ${firstDefinedBefore} and ${firstDefinedAfter}`
24
- )
25
- }
26
-
27
- const beforeRank = ranks[firstDefinedBefore]
28
- const afterRank = ranks[firstDefinedAfter]
29
-
30
- if (
31
- !beforeRank ||
32
- typeof beforeRank === 'undefined' ||
33
- !afterRank ||
34
- typeof afterRank === 'undefined'
35
- ) {
36
- throw new Error(
37
- `Unable to generate middle value between indexes ${firstDefinedBefore} and ${firstDefinedAfter}`
38
- )
39
- }
40
-
41
- // Generate a new value between the two
42
- const between = beforeRank.between(afterRank)
43
-
44
- // Calculate the middle index between the defined values
45
- const middle = Math.floor((firstDefinedAfter + firstDefinedBefore) / 2)
46
-
47
- if (ranks[middle]) {
48
- throw new Error(`Should not have overwritten value at index ${middle}`)
49
- }
50
-
51
- // Insert the new value into the array
52
- ranks[middle] = between
53
-
54
- // Return as a new array
55
- return ranks
56
- }
57
-
58
- // Generates an array of LexoRanks between two values
59
- export function generateMultipleOrderRanks(
60
- count: number,
61
- start?: LexoRank,
62
- end?: LexoRank
63
- ): LexoRank[] {
64
- // Begin array with correct size
65
- let ranks = [...Array(count)]
66
-
67
- // Use or create default values
68
- const rankStart = start ?? LexoRank.min().genNext().genNext()
69
- const rankEnd = end ?? LexoRank.max().genPrev().genPrev()
70
-
71
- ranks[0] = rankStart
72
- ranks[count - 1] = rankEnd
73
-
74
- // Keep processing the array until every value between undefined values is defined
75
- for (let i = 0; i < count; i++) {
76
- ranks = generateMiddleValue(ranks)
77
- }
78
-
79
- return ranks.sort((a, b) => a.toString().localeCompare(b.toString()))
80
- }
@@ -1,13 +0,0 @@
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
- }