@sanity/hierarchical-document-list 1.1.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 (120) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +95 -59
  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 +80 -51
  9. package/sanity.json +2 -6
  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/{lib/components/TreeEditorErrorBoundary.d.ts → src/components/TreeEditorErrorBoundary.tsx} +2 -4
  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/.husky/commit-msg +0 -4
  44. package/.husky/pre-commit +0 -4
  45. package/.idea/hierarchical-document-list.iml +0 -11
  46. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  47. package/.idea/misc.xml +0 -6
  48. package/.idea/modules.xml +0 -8
  49. package/.idea/prettier.xml +0 -7
  50. package/.idea/vcs.xml +0 -6
  51. package/CHANGELOG.md +0 -15
  52. package/commitlint.config.js +0 -3
  53. package/lib/TreeDeskStructure.d.ts +0 -8
  54. package/lib/TreeDeskStructure.js +0 -96
  55. package/lib/TreeInputComponent.d.ts +0 -19
  56. package/lib/TreeInputComponent.js +0 -50
  57. package/lib/components/DeskWarning.d.ts +0 -6
  58. package/lib/components/DeskWarning.js +0 -46
  59. package/lib/components/DocumentInNode.d.ts +0 -11
  60. package/lib/components/DocumentInNode.js +0 -81
  61. package/lib/components/DocumentPreviewStatus.d.ts +0 -7
  62. package/lib/components/DocumentPreviewStatus.js +0 -39
  63. package/lib/components/NodeActions.d.ts +0 -10
  64. package/lib/components/NodeActions.js +0 -61
  65. package/lib/components/NodeContentRenderer.d.ts +0 -8
  66. package/lib/components/NodeContentRenderer.js +0 -105
  67. package/lib/components/PlaceholderDropzone.d.ts +0 -9
  68. package/lib/components/PlaceholderDropzone.js +0 -30
  69. package/lib/components/SuppressedDnDManager.d.ts +0 -2
  70. package/lib/components/SuppressedDnDManager.js +0 -59
  71. package/lib/components/TreeEditor.d.ts +0 -12
  72. package/lib/components/TreeEditor.js +0 -74
  73. package/lib/components/TreeEditorErrorBoundary.js +0 -74
  74. package/lib/components/TreeNodeRenderer.d.ts +0 -3
  75. package/lib/components/TreeNodeRenderer.js +0 -59
  76. package/lib/components/TreeNodeRendererScaffold.d.ts +0 -4
  77. package/lib/components/TreeNodeRendererScaffold.js +0 -44
  78. package/lib/createDeskHierarchy.d.ts +0 -14
  79. package/lib/createDeskHierarchy.js +0 -84
  80. package/lib/createHierarchicalSchemas.d.ts +0 -98
  81. package/lib/createHierarchicalSchemas.js +0 -138
  82. package/lib/hooks/useAllItems.d.ts +0 -7
  83. package/lib/hooks/useAllItems.js +0 -119
  84. package/lib/hooks/useLocalTree.d.ts +0 -17
  85. package/lib/hooks/useLocalTree.js +0 -59
  86. package/lib/hooks/useTreeOperations.d.ts +0 -9
  87. package/lib/hooks/useTreeOperations.js +0 -39
  88. package/lib/hooks/useTreeOperationsProvider.d.ts +0 -14
  89. package/lib/hooks/useTreeOperationsProvider.js +0 -85
  90. package/lib/index.d.ts +0 -3
  91. package/lib/index.js +0 -12
  92. package/lib/schemas/hierarchy.tree.d.ts +0 -13
  93. package/lib/schemas/hierarchy.tree.js +0 -19
  94. package/lib/types.d.ts +0 -128
  95. package/lib/types.js +0 -2
  96. package/lib/utils/flatDataToTree.d.ts +0 -6
  97. package/lib/utils/flatDataToTree.js +0 -26
  98. package/lib/utils/getAdjescentNodes.d.ts +0 -12
  99. package/lib/utils/getAdjescentNodes.js +0 -19
  100. package/lib/utils/getCommonTreeProps.d.ts +0 -7
  101. package/lib/utils/getCommonTreeProps.js +0 -33
  102. package/lib/utils/getTreeHeight.d.ts +0 -3
  103. package/lib/utils/getTreeHeight.js +0 -11
  104. package/lib/utils/gradientPatchAdapter.d.ts +0 -4
  105. package/lib/utils/gradientPatchAdapter.js +0 -40
  106. package/lib/utils/idUtils.d.ts +0 -2
  107. package/lib/utils/idUtils.js +0 -13
  108. package/lib/utils/injectNodeTypeInPatches.d.ts +0 -12
  109. package/lib/utils/injectNodeTypeInPatches.js +0 -59
  110. package/lib/utils/moveItemInArray.d.ts +0 -5
  111. package/lib/utils/moveItemInArray.js +0 -26
  112. package/lib/utils/throwError.d.ts +0 -7
  113. package/lib/utils/throwError.js +0 -12
  114. package/lib/utils/treeData.d.ts +0 -18
  115. package/lib/utils/treeData.js +0 -118
  116. package/lib/utils/treePatches.d.ts +0 -15
  117. package/lib/utils/treePatches.js +0 -171
  118. package/lint-staged.config.js +0 -4
  119. package/screenshot-1.jpg +0 -0
  120. package/tsconfig.json +0 -20
package/src/types.ts ADDED
@@ -0,0 +1,148 @@
1
+ import {TreeItem} from '@nosferatu500/react-sortable-tree'
2
+ import {ArraySchemaType, ObjectSchemaType, SanityDocument} from 'sanity'
3
+ import {INTERNAL_NODE_TYPE, INTERNAL_NODE_VALUE_TYPE} from './utils/injectNodeTypeInPatches'
4
+
5
+ interface SanityReference {
6
+ _type: 'reference'
7
+ _ref: string
8
+ _weak?: boolean
9
+ }
10
+
11
+ /**
12
+ * Objects saved to tree documents in Sanity's Content Lake
13
+ */
14
+ export interface StoredTreeItem {
15
+ _key: string
16
+ _type: typeof INTERNAL_NODE_TYPE | string
17
+ value?: {
18
+ _type: typeof INTERNAL_NODE_VALUE_TYPE | string
19
+ reference?: SanityReference
20
+ docType?: string
21
+ }
22
+ /**
23
+ * _key of parent node
24
+ */
25
+ parent?: string | null
26
+ }
27
+
28
+ /**
29
+ * Tree items enhanced locally in the client with info from `allItems` and `visibilityMap`.
30
+ * `allItems` stop here and never become LocalTreeItems as they aren't added to react-sortable-tree.
31
+ *
32
+ * See `useLocalTree.ts` and `dataToEditorTree()`.
33
+ */
34
+ export interface EnhancedTreeItem extends StoredTreeItem {
35
+ expanded?: boolean | undefined
36
+ /**
37
+ * Used by DocumentInNode to render the preview for drafts if they exist.
38
+ * Also informs document status icons.
39
+ */
40
+ draftId?: string
41
+ draftUpdatedAt?: string
42
+ /**
43
+ * If not present, DocumentInNode will show up an error for invalid document.
44
+ * - undefined `publishedId` could mean the document is either deleted, or it doesn't match GROQ filters anymore
45
+ */
46
+ publishedId?: string
47
+ publishedUpdatedAt?: string
48
+ }
49
+
50
+ /**
51
+ * Tree items as found in the sortable tree itself.
52
+ */
53
+ export type LocalTreeItem = EnhancedTreeItem & Pick<TreeItem, 'title' | 'children'>
54
+
55
+ export interface TreeInputOptions {
56
+ /**
57
+ * What document types this hierarchy can refer to.
58
+ * Similar to the `to` property of the [reference field](https://www.sanity.io/docs/reference-type).
59
+ */
60
+ referenceTo: string[]
61
+
62
+ /**
63
+ * Used to provide fine-grained filtering for documents.
64
+ */
65
+ referenceOptions?: {
66
+ /**
67
+ * Static filter to apply to tree document queries.
68
+ */
69
+ filter?: string
70
+ /**
71
+ * Parameters / variables to pass to the GROQ query ran to fetch documents.
72
+ */
73
+ filterParams?: Record<string, unknown>
74
+ }
75
+
76
+ /**
77
+ * How deep should editors be allowed to nest items.
78
+ */
79
+ maxDepth?: number
80
+
81
+ /**
82
+ * Schema type for your hierarchical documents.
83
+ * Refer to documentation on how to provide these schemas in your studio.
84
+ *
85
+ * Defautlt: 'hierarchy.tree' - this schema is bundled with the plugin
86
+ */
87
+ documentType?: string
88
+ }
89
+
90
+ export interface TreeFieldSchema
91
+ extends Omit<ArraySchemaType, 'of' | 'type' | 'inputComponent' | 'jsonType'> {
92
+ options: ArraySchemaType['options'] & TreeInputOptions
93
+ }
94
+
95
+ export interface TreeNodeObjectSchema
96
+ extends Omit<ObjectSchemaType, 'name' | 'fields' | 'type' | 'inputComponent' | 'jsonType'> {
97
+ options: ObjectSchemaType['options'] & TreeInputOptions
98
+ }
99
+
100
+ export interface TreeDeskStructureProps extends TreeInputOptions {
101
+ /**
102
+ * _id of the document that will hold the tree data.
103
+ */
104
+ documentId: string
105
+
106
+ /**
107
+ * (Optional)
108
+ * Key for the field representing the hierarchical tree inside the document.
109
+ * `tree` by default.
110
+ */
111
+ fieldKeyInDocument?: string
112
+ }
113
+
114
+ export interface DocumentPair {
115
+ draft?: SanityDocument
116
+ published?: SanityDocument
117
+ }
118
+
119
+ export interface AllItems {
120
+ [publishedId: string]: DocumentPair | undefined
121
+ }
122
+
123
+ type DocumentOperation<Payload = unknown> = {
124
+ execute?: (payload?: Payload) => void
125
+ disabled?: boolean | string
126
+ }
127
+
128
+ export interface DocumentOperations {
129
+ patch?: DocumentOperation<unknown[]>
130
+ commit?: DocumentOperation
131
+ del?: DocumentOperation
132
+ delete?: DocumentOperation
133
+ discardChanges?: DocumentOperation
134
+ duplicate?: DocumentOperation
135
+ restore?: DocumentOperation
136
+ unpublish?: DocumentOperation
137
+ publish?: DocumentOperation
138
+ }
139
+
140
+ export interface VisibilityMap {
141
+ [_key: string]: boolean
142
+ }
143
+
144
+ export interface NodeProps {
145
+ node: LocalTreeItem
146
+ }
147
+
148
+ export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
@@ -0,0 +1,20 @@
1
+ import {getTreeFromFlatData} from '@nosferatu500/react-sortable-tree'
2
+ import {StoredTreeItem} from '../types'
3
+
4
+ interface TreeItemWithChildren extends StoredTreeItem {
5
+ children?: TreeItemWithChildren[]
6
+ }
7
+
8
+ export default function flatDataToTree(data: StoredTreeItem[]): TreeItemWithChildren[] {
9
+ return getTreeFromFlatData({
10
+ flatData: data.map((item) => ({
11
+ ...item,
12
+ // if parent: undefined, the tree won't be constructed
13
+ parent: item.parent || null,
14
+ })),
15
+ getKey: (item) => item._key,
16
+ getParentKey: (item) => item.parent,
17
+ // without rootKey, the tree won't be constructed
18
+ rootKey: null as any,
19
+ }) as TreeItemWithChildren[]
20
+ }
@@ -0,0 +1,30 @@
1
+ import {FlatDataItem, TreeItem} from '@nosferatu500/react-sortable-tree'
2
+
3
+ /**
4
+ * Gets adjescent non-children nodes of a given treeIndex.
5
+ */
6
+ export default function getAdjescentNodes({
7
+ flatTree,
8
+ node,
9
+ treeIndex,
10
+ }: {
11
+ flatTree: FlatDataItem[]
12
+ node: TreeItem
13
+ treeIndex: number
14
+ }): {
15
+ leadingNode?: FlatDataItem
16
+ followingNode?: FlatDataItem
17
+ } {
18
+ const leadingNode = flatTree
19
+ .slice(0, treeIndex)
20
+ .reverse()
21
+ // Disregard children nodes - these include the current node's key in their `path` array
22
+ .find((item) => !item.path.includes(node._key))
23
+
24
+ const followingNode = flatTree.slice(treeIndex + 1).find((item) => !item.path.includes(node._key))
25
+
26
+ return {
27
+ leadingNode,
28
+ followingNode,
29
+ }
30
+ }
@@ -0,0 +1,28 @@
1
+ import {ReactSortableTreeProps} from '@nosferatu500/react-sortable-tree/react-sortable-tree'
2
+ import React from 'react'
3
+ import NodeContentRenderer from '../components/NodeContentRenderer'
4
+ import PlaceholderDropzone from '../components/PlaceholderDropzone'
5
+ import TreeNodeRenderer from '../components/TreeNodeRenderer'
6
+ // import {ROW_HEIGHT} from './getTreeHeight'
7
+
8
+ export default function getCommonTreeProps({
9
+ placeholder
10
+ }: {
11
+ placeholder: {
12
+ title: string
13
+ subtitle?: string
14
+ }
15
+ }): Partial<ReactSortableTreeProps> {
16
+ return {
17
+ theme: {
18
+ nodeContentRenderer: NodeContentRenderer,
19
+ placeholderRenderer: (props: any) => <PlaceholderDropzone {...placeholder} {...props} />,
20
+ treeNodeRenderer: TreeNodeRenderer,
21
+ style: {height: '100%'},
22
+ innerStyle: undefined,
23
+ scaffoldBlockPxWidth: 44,
24
+ slideRegionSize: 100
25
+ // rowHeight: ROW_HEIGHT,
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,10 @@
1
+ import {getVisibleNodeCount, TreeItem} from '@nosferatu500/react-sortable-tree'
2
+
3
+ export const ROW_HEIGHT = 51
4
+
5
+ export default function getTreeHeight(treeData: TreeItem[]): string {
6
+ const visibleNodeCount = getVisibleNodeCount({treeData})
7
+
8
+ // prettier-ignore
9
+ return `${50 + (ROW_HEIGHT * visibleNodeCount)}px`
10
+ }
@@ -0,0 +1,43 @@
1
+ // Adapted from @sanity/form-builder/src/sanity/utils/gradientPatchAdapter.ts
2
+ import {arrayToJSONMatchPath} from '@sanity/mutator'
3
+
4
+ type Patch = Record<string, any>
5
+
6
+ type GradientPatch = Record<string, any>
7
+
8
+ export function toGradient(patches: Patch[]): GradientPatch[] {
9
+ return patches.map(toGradientPatch)
10
+ }
11
+
12
+ function toGradientPatch(patch: Patch): GradientPatch {
13
+ const matchPath = arrayToJSONMatchPath(patch.path || [])
14
+ if (patch.type === 'insert') {
15
+ const {position, items} = patch
16
+ return {
17
+ insert: {
18
+ [position]: matchPath,
19
+ items: items,
20
+ },
21
+ }
22
+ }
23
+
24
+ if (patch.type === 'unset') {
25
+ return {
26
+ unset: [matchPath],
27
+ }
28
+ }
29
+
30
+ if (!patch.type) {
31
+ throw new Error(`Missing patch type in patch ${JSON.stringify(patch)}`)
32
+ }
33
+ if (matchPath) {
34
+ return {
35
+ [patch.type]: {
36
+ [matchPath]: patch.value,
37
+ },
38
+ }
39
+ }
40
+ return {
41
+ [patch.type]: patch.value,
42
+ }
43
+ }
@@ -0,0 +1,7 @@
1
+ export function unprefixId(_id = ''): string {
2
+ return _id.replace('drafts.', '')
3
+ }
4
+
5
+ export function isDraft(_id = ''): boolean {
6
+ return _id.startsWith('drafts.')
7
+ }
@@ -0,0 +1,60 @@
1
+ export function getSchemaTypeName(
2
+ documentType: string,
3
+ type: 'document' | 'node' | 'nodeValue' | 'array'
4
+ ): string {
5
+ switch (type) {
6
+ case 'document':
7
+ return documentType
8
+ case 'array':
9
+ return `${documentType}.array`
10
+ case 'node':
11
+ return `${documentType}.node`
12
+ case 'nodeValue':
13
+ return `${documentType}.nodeValue`
14
+ default:
15
+ return documentType
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Temporary value of nodes' `_type` before they are normalized and persisted as the user-defined choice.
21
+ */
22
+ export const DEFAULT_DOC_TYPE = 'hierarchy.tree'
23
+ export const INTERNAL_NODE_TYPE = getSchemaTypeName(DEFAULT_DOC_TYPE, 'node')
24
+ export const INTERNAL_NODE_VALUE_TYPE = getSchemaTypeName(DEFAULT_DOC_TYPE, 'nodeValue')
25
+ export const INTERNAL_NODE_ARRAY_TYPE = getSchemaTypeName(DEFAULT_DOC_TYPE, 'array')
26
+
27
+ /**
28
+ * Barebones recursive utility to inject the desired nodeObjectType in patches generated in deeply nested components and utilities.
29
+ */
30
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
31
+ export default function injectNodeTypeInPatches(patchData: any, documentType: string): any {
32
+ if (Array.isArray(patchData)) {
33
+ return patchData.map((child) => injectNodeTypeInPatches(child, documentType))
34
+ }
35
+ if (typeof patchData === 'object' && patchData !== null) {
36
+ return Object.keys(patchData).reduce((newObject, key) => {
37
+ const value = patchData[key as keyof typeof patchData]
38
+
39
+ if (
40
+ key === '_type' &&
41
+ typeof value === 'string' &&
42
+ [INTERNAL_NODE_TYPE, INTERNAL_NODE_VALUE_TYPE].includes(value)
43
+ ) {
44
+ return {
45
+ ...newObject,
46
+ [key]: getSchemaTypeName(
47
+ documentType,
48
+ value === INTERNAL_NODE_TYPE ? 'node' : 'nodeValue'
49
+ ),
50
+ }
51
+ }
52
+
53
+ return {
54
+ ...newObject,
55
+ [key]: injectNodeTypeInPatches(value, documentType),
56
+ }
57
+ }, {})
58
+ }
59
+ return patchData
60
+ }
@@ -0,0 +1,26 @@
1
+ export default function moveItemInArray<ItemType = unknown>({
2
+ array,
3
+ fromIndex,
4
+ toIndex,
5
+ }: {
6
+ array: ItemType[]
7
+ fromIndex: number
8
+ toIndex: number
9
+ }): ItemType[] {
10
+ if (fromIndex === toIndex) {
11
+ return array
12
+ }
13
+
14
+ const newArray = [...array]
15
+
16
+ const target = newArray[fromIndex]
17
+ const inc = toIndex < fromIndex ? -1 : 1
18
+
19
+ for (let i = fromIndex; i !== toIndex; i += inc) {
20
+ newArray[i] = newArray[i + inc]
21
+ }
22
+
23
+ newArray[toIndex] = target
24
+
25
+ return newArray
26
+ }
@@ -0,0 +1,9 @@
1
+ const ERROR_MESSAGES = {
2
+ invalidDocumentId: 'Please add a documentId to your tree',
3
+ invalidDocumentType: 'Please add a valid documentType to createHierarchicalSchemas',
4
+ invalidReferenceTo: "Missing valid 'referenceTo' value",
5
+ }
6
+
7
+ export default function throwError(message: keyof typeof ERROR_MESSAGES, extraContext = ''): void {
8
+ throw new Error(`[hierarchical input] ${ERROR_MESSAGES[message]} ${extraContext}`)
9
+ }
@@ -0,0 +1,119 @@
1
+ import {TreeItem} from '@nosferatu500/react-sortable-tree'
2
+ import {randomKey} from '@sanity/util/content'
3
+ import {SanityDocument} from 'sanity'
4
+ import DocumentInNode from '../components/DocumentInNode'
5
+ import NodeActions from '../components/NodeActions'
6
+ import {
7
+ AllItems,
8
+ DocumentPair,
9
+ EnhancedTreeItem,
10
+ LocalTreeItem,
11
+ NodeProps,
12
+ StoredTreeItem,
13
+ VisibilityMap
14
+ } from '../types'
15
+ import flatDataToTree from './flatDataToTree'
16
+ import {INTERNAL_NODE_TYPE, INTERNAL_NODE_VALUE_TYPE} from './injectNodeTypeInPatches'
17
+
18
+ export const dataToEditorTree = ({
19
+ tree,
20
+ allItems,
21
+ visibilityMap
22
+ }: {
23
+ tree: StoredTreeItem[]
24
+ allItems: AllItems
25
+ visibilityMap: VisibilityMap
26
+ }): LocalTreeItem[] => {
27
+ const itemsWithTitle = tree
28
+ .filter((item) => item?.value?.reference?._ref)
29
+ .map((item) => {
30
+ const refId = item.value?.reference?._ref
31
+ const docPair = refId ? allItems[refId] : undefined
32
+ const draftDoc = docPair?.draft
33
+ const publishedDoc = docPair?.published
34
+
35
+ const enhancedItem: LocalTreeItem = {
36
+ ...item,
37
+ expanded: visibilityMap[item._key] !== false,
38
+ draftId: draftDoc?._id,
39
+ publishedId: publishedDoc?._id,
40
+ draftUpdatedAt: draftDoc?._updatedAt,
41
+ publishedUpdatedAt: publishedDoc?._updatedAt
42
+ }
43
+
44
+ return {
45
+ ...enhancedItem,
46
+ title: (nodeProps: NodeProps) => (
47
+ <DocumentInNode item={enhancedItem} action={<NodeActions nodeProps={nodeProps} />} />
48
+ ),
49
+ children: []
50
+ }
51
+ })
52
+ return flatDataToTree(itemsWithTitle)
53
+ }
54
+
55
+ const documentPairToNode = (doc?: DocumentPair): EnhancedTreeItem | undefined => {
56
+ if (!doc?.published?._id) {
57
+ return undefined
58
+ }
59
+
60
+ return {
61
+ _key: randomKey(12),
62
+ _type: INTERNAL_NODE_TYPE,
63
+ draftId: doc.draft?._id,
64
+ draftUpdatedAt: doc.draft?._updatedAt,
65
+ publishedId: doc.published._id,
66
+ publishedUpdatedAt: doc.published?._updatedAt,
67
+ value: {
68
+ _type: INTERNAL_NODE_VALUE_TYPE,
69
+ reference: {
70
+ _ref: doc.published._id,
71
+ _type: 'reference',
72
+ _weak: true
73
+ },
74
+ docType: doc.published._type
75
+ }
76
+ }
77
+ }
78
+
79
+ export const flatTree = (tree: TreeItem[]): TreeItem[] => {
80
+ return tree.reduce((flattened, item) => {
81
+ const {children, ...node} = item
82
+ return [...flattened, node, ...(Array.isArray(children) ? flatTree(children) : [])]
83
+ }, [] as TreeItem[])
84
+ }
85
+
86
+ export interface FetchData {
87
+ mainTree?: LocalTreeItem[]
88
+ allItems?: SanityDocument[]
89
+ }
90
+
91
+ export const getUnaddedItems = (data: {
92
+ allItems: AllItems
93
+ tree: StoredTreeItem[]
94
+ }): EnhancedTreeItem[] => {
95
+ if (!data.tree) {
96
+ return Object.entries(data.allItems)
97
+ .map((value) => documentPairToNode(value[1]))
98
+ .filter(Boolean) as EnhancedTreeItem[]
99
+ }
100
+
101
+ return Object.entries(data.allItems)
102
+ .filter(
103
+ ([publishedId]) =>
104
+ publishedId &&
105
+ // unadded items shouldn't be in the tree
106
+ !data.tree.some((treeItem) => treeItem?.value?.reference?._ref === publishedId)
107
+ )
108
+ .map(([, documentPair]) => documentPairToNode(documentPair))
109
+ .filter(Boolean) as EnhancedTreeItem[]
110
+ }
111
+
112
+ export function normalizeNodeForStorage(item: LocalTreeItem): StoredTreeItem {
113
+ return {
114
+ _key: item._key,
115
+ _type: item._type || INTERNAL_NODE_TYPE,
116
+ value: item.value,
117
+ parent: item.parent
118
+ }
119
+ }
@@ -0,0 +1,171 @@
1
+ import {
2
+ FlatDataItem,
3
+ FullTree,
4
+ getFlatDataFromTree,
5
+ NodeData,
6
+ TreeItem
7
+ } from '@nosferatu500/react-sortable-tree'
8
+ import {randomKey} from '@sanity/util/content'
9
+ import * as Patch from 'sanity'
10
+ import {LocalTreeItem, NodeProps} from '../types'
11
+ import getAdjescentNodes from './getAdjescentNodes'
12
+ import moveItemInArray from './moveItemInArray'
13
+ import {normalizeNodeForStorage} from './treeData'
14
+
15
+ export type HandleMovedNodeData = Omit<
16
+ NodeData & FullTree & any,
17
+ 'prevPath' | 'prevTreeIndex' | 'path' | 'treeIndex' | 'node'
18
+ > & {node: LocalTreeItem}
19
+
20
+ export type HandleMovedNode = (moveData: HandleMovedNodeData) => void
21
+
22
+ export function getAddItemPatch(item: LocalTreeItem): unknown[] {
23
+ const normalizedNode = normalizeNodeForStorage(item)
24
+
25
+ return [
26
+ // Add the node to the end of the tree
27
+ Patch.insert([normalizedNode], 'after', [-1])
28
+ ]
29
+ }
30
+
31
+ export function getDuplicateItemPatch(nodeProps: NodeProps): unknown[] {
32
+ const newItem = {
33
+ ...nodeProps.node,
34
+ _key: randomKey(12)
35
+ }
36
+ const normalizedNode = normalizeNodeForStorage(newItem)
37
+
38
+ return [
39
+ // Add duplicated node before the existing one
40
+ Patch.insert([normalizedNode], 'before', [{_key: nodeProps.node._key}])
41
+ ]
42
+ }
43
+
44
+ export function getRemoveItemPatch({node}: Pick<NodeProps, 'node'>): unknown[] {
45
+ const keyPath = {_key: node._key}
46
+ const children = getChildrenPaths(node)
47
+
48
+ return [
49
+ // 1. Unset the removed node
50
+ Patch.unset([keyPath]),
51
+
52
+ // 2. Unset its children
53
+ ...children.map((path) => Patch.unset([{_key: path}]))
54
+ ]
55
+ }
56
+
57
+ export function getMovedNodePatch(data: HandleMovedNodeData): unknown[] {
58
+ const {nextParentNode} = data
59
+ const keyPath = {_key: data.node._key}
60
+
61
+ // === REMOVING NODE FROM TREE ===
62
+ // `nextPath` will be null if the item is removed from tree
63
+ if (!Array.isArray(data.nextPath)) {
64
+ return getRemoveItemPatch({node: data.node})
65
+ }
66
+
67
+ const nextFlatTree = getFlatDataFromTree({
68
+ treeData: data.treeData,
69
+ getNodeKey: (t) => t.node._key
70
+ })
71
+ const normalizedNode = normalizeNodeForStorage(data.node)
72
+
73
+ const {leadingNode, followingNode} = getAdjescentNodes({
74
+ flatTree: nextFlatTree,
75
+ node: data.node,
76
+ treeIndex: data.nextTreeIndex
77
+ })
78
+
79
+ return [
80
+ // 1. Unset the moved node
81
+ // (will be ignored by Content Lake on new nodes with _key not yet in tree)
82
+ Patch.unset([keyPath]),
83
+
84
+ // 2. SIBLING-BASED PLACEMENT
85
+ // If we were to place solely based on nextTreeIndex, concurrent changes from other editors could put the new node in an unexpected position.
86
+ // Let's instead anchor it to the _key of the sibling coming before or after it.
87
+ leadingNode?.node?._key
88
+ ? // After the sibling before it
89
+ Patch.insert([normalizedNode], 'after', [{_key: leadingNode.node._key}])
90
+ : // Or before the sibling right after it, in case there's no leading sibling node
91
+ // prettier-ignore
92
+ Patch.insert([normalizedNode], 'before', [followingNode?.node?._key ? {_key: followingNode.node._key} : data.nextTreeIndex]),
93
+
94
+ // 3. Patch the new node with its new `parent`
95
+ nextParentNode
96
+ ? // If it has a parent node, set that parent's _key
97
+ Patch.set(nextParentNode._key, [keyPath, 'parent'])
98
+ : // Else remove the parent key entirely
99
+ Patch.unset([keyPath, 'parent'])
100
+ ]
101
+ }
102
+
103
+ function getChildrenPaths(node: TreeItem): string[] {
104
+ if (!Array.isArray(node.children)) {
105
+ return []
106
+ }
107
+
108
+ return node.children
109
+ .reduce(
110
+ (keyPaths, child) => [...keyPaths, child._key, ...getChildrenPaths(child)],
111
+ [] as string[]
112
+ )
113
+ .filter(Boolean)
114
+ }
115
+
116
+ export function getMoveItemPatch({
117
+ nodeProps: {node, treeIndex},
118
+ localTree,
119
+ direction = 'up'
120
+ }: {
121
+ nodeProps: any
122
+ localTree: TreeItem[]
123
+ direction: 'up' | 'down'
124
+ }): unknown[] {
125
+ const keyPath = {_key: node._key}
126
+
127
+ const nextTreeIndex = treeIndex + (direction === 'up' ? -1 : 1)
128
+
129
+ const flatTree = getFlatDataFromTree({
130
+ treeData: localTree,
131
+ getNodeKey: (t) => t.node._key
132
+ })
133
+ const nextFlatTree = moveItemInArray<FlatDataItem>({
134
+ array: flatTree,
135
+ fromIndex: treeIndex,
136
+ toIndex: nextTreeIndex
137
+ })
138
+ const {leadingNode, followingNode} = getAdjescentNodes({
139
+ flatTree: nextFlatTree,
140
+ node,
141
+ treeIndex: nextTreeIndex
142
+ })
143
+
144
+ const normalizedNode = normalizeNodeForStorage(node)
145
+
146
+ // When moving up, look at following node to figure out what is the next parent.
147
+ const nodeToInheritParent = direction === 'up' ? followingNode : leadingNode
148
+ const nextParentNode = nodeToInheritParent?.parentNode
149
+
150
+ return [
151
+ // 1. Unset the moved node
152
+ // (will be ignored by Content Lake on new nodes with _key not yet in tree)
153
+ Patch.unset([keyPath]),
154
+
155
+ // 2. SIBLING-BASED PLACEMENT
156
+ leadingNode?.node?._key
157
+ ? // After the sibling before it
158
+ Patch.insert([normalizedNode], 'after', [{_key: leadingNode.node._key}])
159
+ : // Or before the sibling right after it, in case there's no leading sibling node
160
+ Patch.insert([normalizedNode], 'before', [
161
+ followingNode?.node?._key ? {_key: followingNode.node._key} : nextTreeIndex
162
+ ]),
163
+
164
+ // 3. Patch the new node with its new `parent`
165
+ nextParentNode
166
+ ? // If it has a parent node, set that parent's _key
167
+ Patch.set(nextParentNode._key, [keyPath, 'parent'])
168
+ : // Else remove the parent key entirely
169
+ Patch.unset([keyPath, 'parent'])
170
+ ]
171
+ }
@@ -0,0 +1,11 @@
1
+ const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
2
+ const {name, version, sanityExchangeUrl} = require('./package.json')
3
+
4
+ export default showIncompatiblePluginDialog({
5
+ name: name,
6
+ versions: {
7
+ v3: version,
8
+ v2: undefined,
9
+ },
10
+ sanityExchangeUrl,
11
+ })
package/.husky/commit-msg DELETED
@@ -1,4 +0,0 @@
1
- #!/bin/sh
2
- . "$(dirname "$0")/_/husky.sh"
3
-
4
- npx --no -- commitlint --edit ""