@sanity/hierarchical-document-list 1.0.0 → 2.0.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.
Files changed (106) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +365 -297
  3. package/dist/index.d.ts +240 -0
  4. package/dist/index.esm.js +18 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +18 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +94 -55
  9. package/sanity.json +8 -12
  10. package/src/TreeDeskStructure.tsx +80 -0
  11. package/src/TreeInputComponent.tsx +41 -0
  12. package/src/components/DeskWarning.tsx +40 -0
  13. package/src/components/DocumentInNode.tsx +133 -0
  14. package/src/components/DocumentPreviewStatus.tsx +70 -0
  15. package/src/components/NodeActions.tsx +85 -0
  16. package/src/components/NodeContentRenderer.tsx +141 -0
  17. package/src/components/PlaceholderDropzone.tsx +45 -0
  18. package/src/components/TreeEditor.tsx +167 -0
  19. package/src/components/TreeEditorErrorBoundary.tsx +14 -0
  20. package/src/components/TreeNodeRenderer.tsx +37 -0
  21. package/src/components/TreeNodeRendererScaffold.tsx +193 -0
  22. package/src/createDeskHierarchy.tsx +110 -0
  23. package/src/createHierarchicalSchemas.tsx +151 -0
  24. package/src/hooks/useAllItems.ts +119 -0
  25. package/src/hooks/useLocalTree.ts +40 -0
  26. package/src/hooks/useTreeOperations.ts +25 -0
  27. package/src/hooks/useTreeOperationsProvider.ts +86 -0
  28. package/src/index.ts +25 -0
  29. package/src/schemas/hierarchy.tree.ts +19 -0
  30. package/src/types.ts +148 -0
  31. package/src/utils/flatDataToTree.ts +20 -0
  32. package/src/utils/getAdjescentNodes.ts +30 -0
  33. package/src/utils/getCommonTreeProps.tsx +28 -0
  34. package/src/utils/getTreeHeight.ts +10 -0
  35. package/src/utils/gradientPatchAdapter.ts +43 -0
  36. package/src/utils/idUtils.ts +7 -0
  37. package/src/utils/injectNodeTypeInPatches.ts +60 -0
  38. package/src/utils/moveItemInArray.ts +26 -0
  39. package/src/utils/throwError.ts +9 -0
  40. package/src/utils/treeData.tsx +119 -0
  41. package/src/utils/treePatches.ts +171 -0
  42. package/v2-incompatible.js +11 -0
  43. package/lib/TreeDeskStructure.d.ts +0 -8
  44. package/lib/TreeDeskStructure.js +0 -96
  45. package/lib/TreeInputComponent.d.ts +0 -19
  46. package/lib/TreeInputComponent.js +0 -52
  47. package/lib/components/DeskWarning.d.ts +0 -6
  48. package/lib/components/DeskWarning.js +0 -46
  49. package/lib/components/DocumentInNode.d.ts +0 -11
  50. package/lib/components/DocumentInNode.js +0 -82
  51. package/lib/components/DocumentPreviewStatus.d.ts +0 -7
  52. package/lib/components/DocumentPreviewStatus.js +0 -39
  53. package/lib/components/NodeActions.d.ts +0 -10
  54. package/lib/components/NodeActions.js +0 -61
  55. package/lib/components/NodeContentRenderer.d.ts +0 -8
  56. package/lib/components/NodeContentRenderer.js +0 -105
  57. package/lib/components/PlaceholderDropzone.d.ts +0 -9
  58. package/lib/components/PlaceholderDropzone.js +0 -30
  59. package/lib/components/TreeEditor.d.ts +0 -12
  60. package/lib/components/TreeEditor.js +0 -59
  61. package/lib/components/TreeEditorErrorBoundary.d.ts +0 -3
  62. package/lib/components/TreeEditorErrorBoundary.js +0 -59
  63. package/lib/components/TreeNodeRenderer.d.ts +0 -3
  64. package/lib/components/TreeNodeRenderer.js +0 -59
  65. package/lib/components/TreeNodeRendererScaffold.d.ts +0 -4
  66. package/lib/components/TreeNodeRendererScaffold.js +0 -44
  67. package/lib/createDeskHierarchy.d.ts +0 -14
  68. package/lib/createDeskHierarchy.js +0 -85
  69. package/lib/createHierarchicalSchemas.d.ts +0 -78
  70. package/lib/createHierarchicalSchemas.js +0 -138
  71. package/lib/hooks/useAllItems.d.ts +0 -7
  72. package/lib/hooks/useAllItems.js +0 -119
  73. package/lib/hooks/useLocalTree.d.ts +0 -17
  74. package/lib/hooks/useLocalTree.js +0 -59
  75. package/lib/hooks/useTreeOperations.d.ts +0 -9
  76. package/lib/hooks/useTreeOperations.js +0 -39
  77. package/lib/hooks/useTreeOperationsProvider.d.ts +0 -14
  78. package/lib/hooks/useTreeOperationsProvider.js +0 -85
  79. package/lib/index.d.ts +0 -3
  80. package/lib/index.js +0 -12
  81. package/lib/schemas/hierarchy.tree.d.ts +0 -13
  82. package/lib/schemas/hierarchy.tree.js +0 -19
  83. package/lib/utils/flatDataToTree.d.ts +0 -6
  84. package/lib/utils/flatDataToTree.js +0 -26
  85. package/lib/utils/getAdjescentNodes.d.ts +0 -12
  86. package/lib/utils/getAdjescentNodes.js +0 -19
  87. package/lib/utils/getCommonTreeProps.d.ts +0 -7
  88. package/lib/utils/getCommonTreeProps.js +0 -33
  89. package/lib/utils/getTreeHeight.d.ts +0 -3
  90. package/lib/utils/getTreeHeight.js +0 -11
  91. package/lib/utils/gradientPatchAdapter.d.ts +0 -4
  92. package/lib/utils/gradientPatchAdapter.js +0 -42
  93. package/lib/utils/idUtils.d.ts +0 -2
  94. package/lib/utils/idUtils.js +0 -13
  95. package/lib/utils/injectNodeTypeInPatches.d.ts +0 -12
  96. package/lib/utils/injectNodeTypeInPatches.js +0 -58
  97. package/lib/utils/moveItemInArray.d.ts +0 -5
  98. package/lib/utils/moveItemInArray.js +0 -26
  99. package/lib/utils/throwError.d.ts +0 -7
  100. package/lib/utils/throwError.js +0 -12
  101. package/lib/utils/treeData.d.ts +0 -18
  102. package/lib/utils/treeData.js +0 -118
  103. package/lib/utils/treePatches.d.ts +0 -15
  104. package/lib/utils/treePatches.js +0 -171
  105. package/screenshot-1.jpg +0 -0
  106. package/tsconfig.json +0 -20
@@ -0,0 +1,110 @@
1
+ import {AddIcon} from '@sanity/icons'
2
+ import * as React from 'react'
3
+ import type {ConfigContext} from 'sanity'
4
+ import {StructureBuilder} from 'sanity/desk'
5
+
6
+ import TreeDeskStructure from './TreeDeskStructure'
7
+ import {TreeDeskStructureProps} from './types'
8
+ import throwError from './utils/throwError'
9
+
10
+ export interface TreeProps extends TreeDeskStructureProps {
11
+ /**
12
+ * Visible title above the tree.
13
+ * Also used as the label in the desk list item.
14
+ */
15
+ title: string
16
+
17
+ /**
18
+ * Optional icon for rendering the item in the desk structure.
19
+ */
20
+ icon?: any
21
+
22
+ context?: ConfigContext | any
23
+ S?: StructureBuilder | any
24
+ /**
25
+ * Restrict document types that can be created.
26
+ */
27
+ creatableTypes?: string[]
28
+ }
29
+
30
+ const deskTreeValidator = (props: TreeProps): React.FC => {
31
+ const {documentId, referenceTo} = props
32
+ if (typeof documentId !== 'string' && !documentId) {
33
+ throwError('invalidDocumentId')
34
+ }
35
+ if (!Array.isArray(referenceTo)) {
36
+ throwError('invalidReferenceTo', `(documentId "${documentId}")`)
37
+ }
38
+
39
+ return (deskProps) => <TreeDeskStructure {...deskProps} options={props} />
40
+ }
41
+
42
+ export default function createDeskHierarchy(props: TreeProps) {
43
+ const {documentId, referenceTo, referenceOptions, context, S, creatableTypes} = props
44
+ if (!S || !context) {
45
+ throw new Error('Invalid configuration. S or context props are undefined. ' +
46
+ 'These props are available as function parameters when configuring structure, and must be passed along to createDeskHierarchy. ' +
47
+ 'Confer the plugin README for example usage.')
48
+ }
49
+
50
+ const {schema} = context
51
+
52
+ const safelyCreatableTypes =
53
+ creatableTypes && !creatableTypes.some((type) => referenceTo.indexOf(type))
54
+ ? creatableTypes
55
+ : referenceTo
56
+
57
+ let mainList = (
58
+ referenceTo?.length === 1
59
+ ? S.documentTypeList(referenceTo[0]).schemaType(referenceTo[0])
60
+ : S.documentList().filter('_type in $types').params({types: referenceTo})
61
+ )
62
+ .id(documentId)
63
+ .menuItems(
64
+ (safelyCreatableTypes || []).map((schemaType) =>
65
+ S.menuItem()
66
+ .intent({
67
+ type: 'create',
68
+ params: {type: schemaType}
69
+ })
70
+ .title(`Create ${schema.get(schemaType)?.title}`)
71
+ .icon(schema.get(schemaType)?.icon || AddIcon)
72
+ )
73
+ )
74
+ .canHandleIntent((intent: string, c: Record<string, unknown>) => {
75
+ // Can edit itself
76
+ if (intent === 'edit' && c.id === props.documentId) {
77
+ return true
78
+ }
79
+ // Can create & edit referenced document types
80
+ if (safelyCreatableTypes.includes(c.type as string)) {
81
+ return true
82
+ }
83
+ return false
84
+ })
85
+
86
+ if (referenceOptions?.filter) {
87
+ mainList = mainList.filter(referenceOptions.filter)
88
+ }
89
+
90
+ if (referenceOptions?.filterParams) {
91
+ mainList = mainList.params(referenceOptions.filterParams)
92
+ }
93
+
94
+ return S.listItem()
95
+ .id(documentId)
96
+ .title(props.title || documentId)
97
+ .icon(props.icon)
98
+ .child(
99
+ Object.assign(
100
+ mainList.serialize(),
101
+ {
102
+ type: 'component',
103
+ component: deskTreeValidator(props),
104
+ options: props,
105
+ __preserveInstance: true
106
+ },
107
+ props.title ? {title: props.title} : {}
108
+ )
109
+ )
110
+ }
@@ -0,0 +1,151 @@
1
+ import * as React from 'react'
2
+ import {ArraySchemaType} from 'sanity'
3
+ import {DEFAULT_FIELD_KEY} from './TreeDeskStructure'
4
+ import TreeInputComponent from './TreeInputComponent'
5
+ import {TreeDeskStructureProps, TreeFieldSchema} from './types'
6
+ import {
7
+ INTERNAL_NODE_ARRAY_TYPE,
8
+ INTERNAL_NODE_TYPE,
9
+ INTERNAL_NODE_VALUE_TYPE,
10
+ getSchemaTypeName
11
+ } from './utils/injectNodeTypeInPatches'
12
+ import throwError from './utils/throwError'
13
+
14
+ type SchemaOptions = Omit<TreeDeskStructureProps, 'documentId' | 'maxDepth'>
15
+
16
+ function createHierarchicalNodeValueType({
17
+ referenceTo,
18
+ referenceOptions,
19
+ documentType
20
+ }: SchemaOptions) {
21
+ return {
22
+ // when used inside the field, name & type are overwritten by createHierarchicalNodeType
23
+ name: documentType ? getSchemaTypeName(documentType, 'nodeValue') : INTERNAL_NODE_VALUE_TYPE,
24
+ type: 'object',
25
+ title: `Hierarchical node value (${documentType})`,
26
+
27
+ fields: [
28
+ {name: 'docType', type: 'string'},
29
+ {
30
+ name: 'reference',
31
+ type: 'reference',
32
+ weak: true,
33
+ to: referenceTo.map((type) => ({type})),
34
+ options: referenceOptions
35
+ }
36
+ ]
37
+ }
38
+ }
39
+
40
+ function createHierarchicalNodeType(options: SchemaOptions) {
41
+ return {
42
+ // name & type are overwritten by createHierarchicalField
43
+ name: options.documentType
44
+ ? getSchemaTypeName(options.documentType, 'node')
45
+ : INTERNAL_NODE_TYPE,
46
+ title: `Hierarchical node (${options.documentType})`,
47
+ type: 'object',
48
+ fields: [
49
+ {name: 'parent', type: 'string'},
50
+
51
+ options.documentType
52
+ ? {name: 'value', type: getSchemaTypeName(options.documentType, 'nodeValue')}
53
+ : // If no documentType is defined, use an anonymized inline object to avoid
54
+ // having to define another custom schema type through the plugin
55
+ {
56
+ ...createHierarchicalNodeValueType(options),
57
+ name: 'value',
58
+ type: 'object'
59
+ }
60
+ ]
61
+ }
62
+ }
63
+
64
+ function createHierarchicalArrayType(options: SchemaOptions) {
65
+ return {
66
+ // name & type are overwritten by createHierarchicalField
67
+ name: options.documentType
68
+ ? getSchemaTypeName(options.documentType, 'array')
69
+ : INTERNAL_NODE_ARRAY_TYPE,
70
+ title: `Hierarchical array of nodes (${options.documentType})`,
71
+ type: 'array',
72
+ of: [
73
+ options.documentType
74
+ ? {type: getSchemaTypeName(options.documentType, 'node')}
75
+ : createHierarchicalNodeType(options)
76
+ ]
77
+ }
78
+ }
79
+
80
+ export function createHierarchicalField({name, title, options, ...rest}: TreeFieldSchema): Omit<
81
+ ArraySchemaType,
82
+ 'type' | 'jsonType' | 'of'
83
+ > & {
84
+ type: string
85
+ inputComponent: React.FC<any>
86
+ of?: any[]
87
+ } {
88
+ if (!Array.isArray(options?.referenceTo)) {
89
+ throwError('invalidReferenceTo', `(field of name "${name}")`)
90
+ }
91
+
92
+ return {
93
+ ...rest,
94
+ name,
95
+ title,
96
+ inputComponent: TreeInputComponent,
97
+ options,
98
+ ...(options.documentType
99
+ ? {type: getSchemaTypeName(options.documentType, 'array')}
100
+ : {
101
+ ...createHierarchicalArrayType(options),
102
+ name
103
+ })
104
+ }
105
+ }
106
+
107
+ function createHierarchicalDocType(options: SchemaOptions) {
108
+ return {
109
+ name: options.documentType,
110
+ title: 'Hierarchical tree',
111
+ type: 'document',
112
+ // The plugin needs to define a `schemaType` with liveEdit enabled so that
113
+ // `useDocumentOperation` in TreeDeskStructure.tsx doesn't create drafts at every patch.
114
+ liveEdit: true,
115
+ fields: [
116
+ createHierarchicalField({
117
+ name: options.fieldKeyInDocument || DEFAULT_FIELD_KEY,
118
+ title: 'Hierarchical Tree',
119
+ options
120
+ })
121
+ ],
122
+ preview: {
123
+ select: {
124
+ id: '_id',
125
+ tree: 'tree'
126
+ },
127
+ prepare({id, tree}: {id: string; tree: unknown[]}): Record<string, string> {
128
+ return {
129
+ title: `Hierarchical documents (ID: ${id})`,
130
+ subtitle: `${tree?.length || 0} document(s) in its list.`
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ export default function createHierarchicalSchemas(options: SchemaOptions) {
138
+ if (!Array.isArray(options.referenceTo) || options.referenceTo.length <= 0) {
139
+ throwError('invalidReferenceTo')
140
+ }
141
+ if (!options.documentType) {
142
+ throwError('invalidDocumentType')
143
+ }
144
+
145
+ return [
146
+ createHierarchicalDocType(options),
147
+ createHierarchicalArrayType(options),
148
+ createHierarchicalNodeType(options),
149
+ createHierarchicalNodeValueType(options)
150
+ ]
151
+ }
@@ -0,0 +1,119 @@
1
+ import * as React from 'react'
2
+ import {SanityDocument, useClient} from 'sanity'
3
+ import {AllItems, TreeInputOptions} from '../types'
4
+ import {isDraft, unprefixId} from '../utils/idUtils'
5
+
6
+ function getDeskFilter({referenceTo, referenceOptions}: TreeInputOptions): {
7
+ filter: string
8
+ params: Record<string, unknown>
9
+ } {
10
+ const filterParts: string[] = ['_type in $docTypes']
11
+
12
+ if (referenceOptions?.filter) {
13
+ filterParts.push(referenceOptions.filter)
14
+ }
15
+
16
+ return {
17
+ filter: filterParts.join(' && '),
18
+ params: {
19
+ ...(referenceOptions?.filterParams || {}),
20
+ docTypes: referenceTo.map((schemaType) => schemaType)
21
+ }
22
+ }
23
+ }
24
+
25
+ type Status = 'loading' | 'success' | 'error'
26
+
27
+ type ACTIONTYPE =
28
+ | {type: 'addOrEditItem'; item: SanityDocument}
29
+ | {type: 'removeItem'; itemId: string}
30
+ | {type: 'setInitialData'; items: SanityDocument[]}
31
+
32
+ function updateItemInState(state: AllItems, item: SanityDocument): AllItems {
33
+ const newState = {...state}
34
+ const publishedId = unprefixId(item._id)
35
+ newState[publishedId] = {
36
+ ...(newState[publishedId] || {}),
37
+ [isDraft(item._id) ? 'draft' : 'published']: item
38
+ }
39
+ return newState
40
+ }
41
+
42
+ function allItemsReducer(state: AllItems, action: ACTIONTYPE): AllItems {
43
+ if (action.type === 'addOrEditItem' && action.item?._id) {
44
+ return updateItemInState(state, action.item)
45
+ }
46
+
47
+ if (action.type === 'removeItem') {
48
+ const publishedId = unprefixId(action.itemId)
49
+ return {
50
+ ...state,
51
+ [publishedId]: isDraft(action.itemId)
52
+ ? // If a draft, keep only published
53
+ {
54
+ published: state[publishedId]?.published
55
+ }
56
+ : {
57
+ draft: state[publishedId]?.draft
58
+ }
59
+ }
60
+ }
61
+
62
+ if (action.type === 'setInitialData') {
63
+ return action.items.reduce(updateItemInState, {} as AllItems)
64
+ }
65
+ return state
66
+ }
67
+
68
+ export default function useAllItems(options: TreeInputOptions): {
69
+ status: Status
70
+ allItems: AllItems
71
+ } {
72
+ const client = useClient({
73
+ apiVersion: '2021-09-01'
74
+ })
75
+ const [status, setStatus] = React.useState<Status>('loading')
76
+ const [allItems, dispatch] = React.useReducer(allItemsReducer, {})
77
+
78
+ function handleListener(event: any) {
79
+ if (event.type !== 'mutation') {
80
+ return
81
+ }
82
+
83
+ if (event.result) {
84
+ dispatch({type: 'addOrEditItem', item: event.result})
85
+ } else {
86
+ dispatch({type: 'removeItem', itemId: event.documentId})
87
+ }
88
+ }
89
+
90
+ function handleFirstLoad(items: SanityDocument[]) {
91
+ dispatch({type: 'setInitialData', items})
92
+ setStatus('success')
93
+ }
94
+
95
+ React.useEffect(() => {
96
+ const {filter, params} = getDeskFilter(options)
97
+ const query = `*[${filter}] {
98
+ _id,
99
+ _type,
100
+ _updatedAt,
101
+ }`
102
+ client
103
+ .fetch<SanityDocument[]>(query, params)
104
+ .then(handleFirstLoad)
105
+ .catch(() => {
106
+ setStatus('error')
107
+ })
108
+
109
+ const listener = client.listen(query, params).subscribe(handleListener)
110
+ return () => {
111
+ listener.unsubscribe()
112
+ }
113
+ }, [])
114
+
115
+ return {
116
+ status,
117
+ allItems
118
+ }
119
+ }
@@ -0,0 +1,40 @@
1
+ import * as React from 'react'
2
+ import {AllItems, LocalTreeItem, StoredTreeItem, VisibilityMap} from '../types'
3
+ import {dataToEditorTree} from '../utils/treeData'
4
+
5
+ /**
6
+ * Enhances tree data with information on:
7
+ * - `expanded` - native property of react-sortable-tree to determine collapsing & expanding of a node's children
8
+ * - `draftId` & `publishedId` - refer to LocalTreeItem's type annotations
9
+ *
10
+ * Doesn't modify the main tree or has side-effects on data.
11
+ * Has the added benefit of being local to the user, so external changes won't affect local visibility.
12
+ */
13
+ export default function useLocalTree({
14
+ tree,
15
+ allItems,
16
+ }: {
17
+ tree: StoredTreeItem[]
18
+ allItems: AllItems
19
+ }): {
20
+ handleVisibilityToggle: (data: any) => void
21
+ localTree: LocalTreeItem[]
22
+ } {
23
+ const [visibilityMap, setVisibilityMap] = React.useState<VisibilityMap>({})
24
+
25
+ function handleVisibilityToggle(data: any) {
26
+ setVisibilityMap({
27
+ ...visibilityMap,
28
+ [data.node._key]: data.expanded,
29
+ })
30
+ }
31
+
32
+ return {
33
+ localTree: dataToEditorTree({
34
+ tree,
35
+ allItems,
36
+ visibilityMap,
37
+ }),
38
+ handleVisibilityToggle,
39
+ }
40
+ }
@@ -0,0 +1,25 @@
1
+ import * as React from 'react'
2
+ import useAllItems from './useAllItems'
3
+ import useTreeOperationsProvider from './useTreeOperationsProvider'
4
+
5
+ type ContextValue = ReturnType<typeof useTreeOperationsProvider> & {
6
+ allItemsStatus: ReturnType<typeof useAllItems>['status']
7
+ }
8
+
9
+ function placeholder() {
10
+ // no-op
11
+ }
12
+
13
+ export const TreeOperationsContext = React.createContext<ContextValue>({
14
+ addItem: placeholder,
15
+ duplicateItem: placeholder,
16
+ removeItem: placeholder,
17
+ handleMovedNode: placeholder,
18
+ moveItemDown: placeholder,
19
+ moveItemUp: placeholder,
20
+ allItemsStatus: 'loading',
21
+ })
22
+
23
+ export default function useTreeOperations(): ContextValue {
24
+ return React.useContext(TreeOperationsContext)
25
+ }
@@ -0,0 +1,86 @@
1
+ import {PatchEvent, PathSegment, prefixPath, setIfMissing} from 'sanity'
2
+ import {LocalTreeItem, NodeProps} from '../types'
3
+ import {
4
+ HandleMovedNode,
5
+ HandleMovedNodeData,
6
+ getAddItemPatch,
7
+ getDuplicateItemPatch,
8
+ getMoveItemPatch,
9
+ getMovedNodePatch,
10
+ getRemoveItemPatch
11
+ } from '../utils/treePatches'
12
+
13
+ export default function useTreeOperationsProvider(props: {
14
+ patchPrefix?: PathSegment
15
+ onChange: (patch: PatchEvent) => void
16
+ localTree: LocalTreeItem[]
17
+ }): {
18
+ handleMovedNode: HandleMovedNode
19
+ addItem: (item: LocalTreeItem) => void
20
+ duplicateItem: (nodeProps: NodeProps) => void
21
+ removeItem: (nodeProps: NodeProps) => void
22
+ moveItemUp: (nodeProps: NodeProps) => void
23
+ moveItemDown: (nodeProps: NodeProps) => void
24
+ } {
25
+ const {localTree} = props
26
+
27
+ function runPatches(patches: any) {
28
+ const finalPatches = [
29
+ // Ensure tree array exists before any operation
30
+ setIfMissing([]),
31
+ ...(patches || [])
32
+ ]
33
+ let patchEvent = PatchEvent.from(finalPatches)
34
+ if (props.patchPrefix) {
35
+ patchEvent = PatchEvent.from(
36
+ finalPatches.map((patch) => prefixPath(patch, props.patchPrefix as PathSegment))
37
+ )
38
+ }
39
+ props.onChange(patchEvent)
40
+ }
41
+
42
+ function handleMovedNode(data: HandleMovedNodeData & {node: LocalTreeItem}) {
43
+ runPatches(getMovedNodePatch(data))
44
+ }
45
+
46
+ function addItem(item: LocalTreeItem) {
47
+ runPatches(getAddItemPatch(item))
48
+ }
49
+
50
+ function duplicateItem(nodeProps: NodeProps & {node: LocalTreeItem}) {
51
+ runPatches(getDuplicateItemPatch(nodeProps))
52
+ }
53
+
54
+ function removeItem(nodeProps: NodeProps) {
55
+ runPatches(getRemoveItemPatch(nodeProps))
56
+ }
57
+
58
+ function moveItemUp(nodeProps: NodeProps) {
59
+ runPatches(
60
+ getMoveItemPatch({
61
+ nodeProps,
62
+ localTree,
63
+ direction: 'up'
64
+ })
65
+ )
66
+ }
67
+
68
+ function moveItemDown(nodeProps: NodeProps) {
69
+ runPatches(
70
+ getMoveItemPatch({
71
+ nodeProps,
72
+ localTree,
73
+ direction: 'down'
74
+ })
75
+ )
76
+ }
77
+
78
+ return {
79
+ handleMovedNode,
80
+ addItem,
81
+ removeItem,
82
+ moveItemUp,
83
+ moveItemDown,
84
+ duplicateItem
85
+ }
86
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ import {definePlugin} from 'sanity'
2
+ import {default as createDeskHierarchy, type TreeProps} from './createDeskHierarchy'
3
+ import {default as createHierarchicalSchemas} from './createHierarchicalSchemas'
4
+ import {default as hierarchyTree} from './schemas/hierarchy.tree'
5
+ import {default as flatDataToTree} from './utils/flatDataToTree'
6
+
7
+ export {createDeskHierarchy, createHierarchicalSchemas, flatDataToTree, hierarchyTree, TreeProps}
8
+
9
+ /**
10
+ * Usage in `sanity.config.ts` (or .js)
11
+ *
12
+ * ```ts
13
+ * import {defineConfig} from 'sanity'
14
+ * import {myPlugin} from 'sanity-plugin-hdl-sanity'
15
+ *
16
+ * export default defineConfig({
17
+ * // ...
18
+ * plugins: [myPlugin()],
19
+ * })
20
+ * ```
21
+ */
22
+
23
+ export const hierarchicalDocumentList = definePlugin({
24
+ name: 'sanity-plugin-hierarchical-document-list'
25
+ })
@@ -0,0 +1,19 @@
1
+ import {defineType} from 'sanity'
2
+
3
+ export default defineType({
4
+ name: 'hierarchy.tree',
5
+ title: 'Hierarchical tree',
6
+ type: 'document',
7
+ // The plugin needs to define a `schemaType` with liveEdit enabled so that
8
+ // `useDocumentOperation` in TreeDeskStructure.tsx doesn't create drafts at every patch.
9
+ liveEdit: true,
10
+ // Let's avoid defining the actual hierarchical field, else GraphQL users won't be able to deploy schemas
11
+ fields: [
12
+ {
13
+ name: 'unusedField',
14
+ title: 'Unused field',
15
+ type: 'string',
16
+ hidden: true,
17
+ },
18
+ ],
19
+ })