@sanity/hierarchical-document-list 0.1.0-next.1 → 0.1.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,8 @@ Plugin for editing hierarchical references in the [Sanity studio](https://www.sa
4
4
 
5
5
  ![Screenshot of the plugin](/screenshot-1.jpg)
6
6
 
7
+ ⚠️ **Compatibility:** This plugin requires Sanity Studio [version 2.25.0](https://github.com/sanity-io/sanity/releases/tag/v2.25.0) or higher.
8
+
7
9
  ## Getting started
8
10
 
9
11
  ```bash
@@ -28,7 +30,7 @@ export default () => {
28
30
  createDeskHierarchy({
29
31
  title: 'Main table of contents',
30
32
 
31
- // The hiearchy will be stored in this document ID 👇
33
+ // The hierarchy will be stored in this document ID 👇
32
34
  documentId: 'main-table-of-contents',
33
35
 
34
36
  // Document types editors should be able to include in the hierarchy
@@ -65,20 +67,26 @@ The plugin stores flat arrays which represent your hierarchical data through `pa
65
67
  {
66
68
  "_key": "741b9edde2ba",
67
69
  "_type": "hierarchy.node",
68
- "node": {
69
- "_ref": "75c47994-e6bb-487a-b8c9-b283f2436031",
70
- "_type": "reference",
71
- "_weak": true // This plugin includes weak references by default
70
+ "value": {
71
+ "reference": {
72
+ "_ref": "75c47994-e6bb-487a-b8c9-b283f2436031",
73
+ "_type": "reference",
74
+ "_weak": true // This plugin includes weak references by default
75
+ },
76
+ "docType": "docs.article"
72
77
  }
73
78
  // no `parent`, this item is top-level
74
79
  },
75
80
  {
76
81
  "_key": "f92eaeec96f7",
77
82
  "_type": "hierarchy.node",
78
- "node": {
79
- "_ref": "7ad60a02-5d6e-47d8-92e2-6724cc130058",
80
- "_type": "reference",
81
- "_weak": true
83
+ "value": {
84
+ "reference": {
85
+ "_ref": "7ad60a02-5d6e-47d8-92e2-6724cc130058",
86
+ "_type": "reference",
87
+ "_weak": true
88
+ },
89
+ "docType": "site.post"
82
90
  },
83
91
  // The `parent` property points to the _key of the parent node where this one is nested
84
92
  "parent": "741b9edde2ba"
@@ -98,10 +106,12 @@ From the the above, we know how to expand referenced documents in GROQ:
98
106
  parent,
99
107
 
100
108
  // "Expand" the reference to the node
101
- node->{
102
- // Get whatever property you need from your documents
103
- title,
104
- slug,
109
+ value {
110
+ reference->{
111
+ // Get whatever property you need from your documents
112
+ title,
113
+ slug,
114
+ }
105
115
  }
106
116
  }
107
117
  }
@@ -119,13 +129,13 @@ Find a given document in a hierarchy and get its parent - useful for rendering b
119
129
  // From the tree, get the 1st node that references a given document _id
120
130
  tree[node._ref == "my-book-section"][0] {
121
131
  _key,
122
- "section": node->{
132
+ "section": node.reference->{
123
133
  title,
124
134
  },
125
135
  // Then, from the tree get the element matching the `parent` _key of the found node
126
136
  "parentChapter": ^.tree[_key == ^.parent][0]{
127
137
  _key,
128
- "chapter": node->{
138
+ "chapter": node.reference->{
129
139
  title,
130
140
  contributors,
131
141
  }
@@ -148,10 +158,12 @@ const hierarchyDocument = await client.fetch(`*[_id == "book-v3-review-a"][0]{
148
158
  // Make sure you include each item's _key and parent
149
159
  _key,
150
160
  parent,
151
- node->{
152
- title,
153
- slug,
154
- content,
161
+ value {
162
+ reference->{
163
+ title,
164
+ slug,
165
+ content,
166
+ }
155
167
  }
156
168
  }
157
169
  }`)
@@ -173,9 +185,10 @@ export default {
173
185
  name: 'myCustomHierarchicalType',
174
186
  title: 'Custom document type for holding hierarchical data',
175
187
  type: 'document',
188
+ liveEdit: true, // 👉 Important: set liveEdit to `true` to ensure the UI works properly
176
189
  fields: [
177
190
  createHierarchicalField({
178
- name: 'treeData', // custom key for
191
+ name: 'customTreeDataKey', // key for the tree field in the document
179
192
  title: 'Custom tree',
180
193
  options: {
181
194
  referenceTo: ['category']
@@ -185,26 +198,25 @@ export default {
185
198
  }
186
199
  ```
187
200
 
188
- ---
189
-
190
- 📌 **Note:** you can also use the method above to add hierarchies inside the schema of documents and objects. We're considering adapting this input to support any type of nest-able data, not only references. Until then, avoid `createHierarchicalField` for fields in nested schemas as, in these contexts, it lacks the necessary affordances for a good editing experience.
191
-
192
- ---
193
-
194
201
  Then, in your desk structure where you added the hierarchical document(s), include the right `documentType` and `fieldKeyInDocument` properties:
195
202
 
196
203
  ```js
197
204
  createDeskHierarchy({
198
- title: 'Hierarchies',
199
- referenceTo: ['product', 'collection'],
200
- documentId: 'hierarchies',
201
-
202
- // Include whatever values you defined in your schema
203
- documentType: 'myCustomHierarchicalType',
204
- fieldKeyInDocument: 'treeData'
205
+ // Include whatever values you defined in your schema in the step above
206
+ documentType: 'myCustomHierarchicalType', // the name of your document type
207
+ fieldKeyInDocument: 'customTreeDataKey' // the name of the hierarchical field
208
+ // ...
205
209
  })
206
210
  ```
207
211
 
212
+ ---
213
+
214
+ 📌 **Note:** you can also use the method above to add hierarchies inside the schema of documents and objects, which would be editable outside the desk structure.
215
+
216
+ We're considering adapting this input to support any type of nest-able data, not only references. Until then, avoid `createHierarchicalField` for fields in nested schemas as, in these contexts, it lacks the necessary affordances for a good editing experience.
217
+
218
+ ---
219
+
208
220
  ## License
209
221
 
210
222
  MIT-licensed. See LICENSE.
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { PublishIcon } from '@sanity/icons';
3
3
  import { useDocumentOperation, useEditState } from '@sanity/react-hooks';
4
- import { Box, Button, Card, Container, Flex, Heading, Spinner, Stack, Text, useToast } from '@sanity/ui';
4
+ import { Box, Button, Flex, Spinner, useToast } from '@sanity/ui';
5
5
  import React from 'react';
6
+ import DeskWarning from './components/DeskWarning';
6
7
  import TreeEditor from './components/TreeEditor';
7
8
  import { toGradient } from './utils/gradientPatchAdapter';
8
9
  const DEFAULT_TREE_FIELD_KEY = 'tree';
@@ -10,7 +11,7 @@ const DEFAULT_TREE_DOC_TYPE = 'hierarchy.tree';
10
11
  const TreeDeskStructure = (props) => {
11
12
  const treeDocType = props.options.documentType || DEFAULT_TREE_DOC_TYPE;
12
13
  const treeFieldKey = props.options.fieldKeyInDocument || DEFAULT_TREE_FIELD_KEY;
13
- const { published, draft } = useEditState(props.options.documentId, treeDocType);
14
+ const { published, draft, liveEdit } = useEditState(props.options.documentId, treeDocType);
14
15
  const { patch, ...ops } = useDocumentOperation(props.options.documentId, treeDocType);
15
16
  const { push } = useToast();
16
17
  const treeValue = (published?.[treeFieldKey] || []);
@@ -26,14 +27,17 @@ const TreeDeskStructure = (props) => {
26
27
  patch.execute([{ setIfMissing: { [treeFieldKey]: [] } }]);
27
28
  }
28
29
  }, [published?._id, patch]);
30
+ if (!liveEdit) {
31
+ return (_jsx(DeskWarning, { title: "Invalid configuration", subtitle: "The `documentType` passed to `createDeskHiearchy` isn't live editable. \\nTo continue using this plugin, add `liveEdit: true` to your custom schema type or unset `documentType` in your hierarchy configuration." }, void 0));
32
+ }
29
33
  if (draft?._id) {
30
- return (_jsx(Container, { padding: 5, style: { maxWidth: '25rem' }, sizing: 'content', children: _jsx(Card, { padding: 4, border: true, radius: 2, width: 0, tone: "caution", children: _jsxs(Stack, { space: 3, children: [_jsx(Heading, { size: 1, children: "This hierarchy tree contains a draft" }, void 0), _jsx(Text, { size: 1, children: "Click on the button below to publish your draft in order to continue editing the live published document." }, void 0), _jsx(Box, { marginTop: 2, children: _jsx(Button, { fontSize: 1, tone: "positive", text: "Publish draft", icon: PublishIcon, onClick: () => {
31
- ops.publish?.execute?.();
32
- push({
33
- status: 'info',
34
- title: 'Publishing draft...'
35
- });
36
- } }, void 0) }, void 0)] }, void 0) }, void 0) }, void 0));
34
+ return (_jsx(DeskWarning, { title: "This hierarchy tree contains a draft", subtitle: "Click on the button below to publish your draft in order to continue editing the live\n published document.", children: _jsx(Button, { fontSize: 1, tone: "positive", text: "Publish draft", icon: PublishIcon, onClick: () => {
35
+ ops.publish?.execute?.();
36
+ push({
37
+ status: 'info',
38
+ title: 'Publishing draft...'
39
+ });
40
+ } }, void 0) }, void 0));
37
41
  }
38
42
  if (!published?._id) {
39
43
  return (_jsx(Flex, { padding: 5, align: 'center', justify: 'center', height: 'fill', children: _jsx(Spinner, { width: 4, muted: true }, void 0) }, void 0));
@@ -1,11 +1,11 @@
1
1
  import { FormFieldPresence } from '@sanity/base/presence';
2
2
  import { Marker, Path } from '@sanity/types';
3
3
  import React from 'react';
4
- import { SanityTreeItem, TreeFieldSchema } from './types';
4
+ import { StoredTreeItem, TreeFieldSchema } from './types';
5
5
  export interface TreeInputComponentProps {
6
6
  type: TreeFieldSchema;
7
- value: SanityTreeItem[];
8
- compareValue: SanityTreeItem[];
7
+ value: StoredTreeItem[];
8
+ compareValue: StoredTreeItem[];
9
9
  markers: Marker[];
10
10
  level: number;
11
11
  onChange: (event: unknown) => void;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ declare const DeskWarning: React.FC<{
3
+ title: string;
4
+ subtitle?: string;
5
+ }>;
6
+ export default DeskWarning;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Card, Container, Heading, Stack, Text } from '@sanity/ui';
3
+ import React from 'react';
4
+ // React component that wraps text between two delimiters in a <pre> tag
5
+ const WrapCodeBlocks = ({ text }) => {
6
+ return (_jsx(_Fragment, { children: text.split('`').map((part, i) => (_jsx(React.Fragment, { children: i % 2 === 0 ? part : _jsx("code", { children: part }, void 0) }, i))) }, void 0));
7
+ };
8
+ const DeskWarning = ({ subtitle, title, children }) => {
9
+ return (_jsx(Container, { padding: 5, style: { maxWidth: '25rem' }, sizing: 'content', children: _jsx(Card, { padding: 4, border: true, radius: 2, width: 0, tone: "caution", children: _jsxs(Stack, { space: 3, children: [_jsx(Heading, { size: 1, children: title }, void 0), subtitle &&
10
+ subtitle.split('\\n').map((line) => (_jsx(Text, { size: 1, children: _jsx(WrapCodeBlocks, { text: line }, void 0) }, void 0))), children && _jsx(Box, { marginTop: 2, children: children }, void 0)] }, void 0) }, void 0) }, void 0));
11
+ };
12
+ export default DeskWarning;
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { SanityTreeItem } from '../types';
2
+ import { LocalTreeItem } from '../types';
3
3
  /**
4
4
  * Renders a preview for each referenced document.
5
5
  * Nested inside TreeNode.tsx
6
6
  */
7
7
  declare const DocumentInNode: React.FC<{
8
- item: SanityTreeItem;
8
+ item: LocalTreeItem;
9
9
  action?: React.ReactNode;
10
10
  }>;
11
11
  export default DocumentInNode;
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { NodeRendererProps } from 'react-sortable-tree';
2
+ import { NodeProps } from '../types';
3
3
  /**
4
4
  * Applicable only to nodes inside the main tree.
5
5
  * Unadded items have their actions defined in TreeEditor.
6
6
  */
7
7
  declare const NodeActions: React.FC<{
8
- nodeProps: NodeRendererProps;
8
+ nodeProps: NodeProps;
9
9
  }>;
10
10
  export default NodeActions;
@@ -11,12 +11,13 @@ import useTreeOperations from '../utils/useTreeOperations';
11
11
  const NodeActions = ({ nodeProps }) => {
12
12
  const operations = useTreeOperations();
13
13
  const { node } = nodeProps;
14
+ const { reference, docType } = node?.value || {};
14
15
  // Adapted from @sanity\form-builder\src\inputs\ReferenceInput\ArrayItemReferenceInput.tsx
15
16
  const OpenLink = React.useMemo(() =>
16
17
  // eslint-disable-next-line @typescript-eslint/no-shadow
17
18
  React.forwardRef(function OpenLinkInner(restProps, _ref) {
18
- return (_jsx(IntentLink, { ...restProps, intent: "edit", params: { id: node.node?._ref, type: node.nodeDocType }, target: "_blank", rel: "noopener noreferrer", ref: _ref }, void 0));
19
- }), [node.node?._ref, node?.nodeDocType]);
19
+ return (_jsx(IntentLink, { ...restProps, intent: "edit", params: { id: reference?._ref, type: docType }, target: "_blank", rel: "noopener noreferrer", ref: _ref }, void 0));
20
+ }), [reference?._ref, docType]);
20
21
  const isValid = !!node.publishedId;
21
22
  return (_jsx(MenuButton, { button: _jsx(Button, { padding: 2, mode: "bleed", icon: EllipsisVerticalIcon }, void 0), id: `hiearchical-doc-list--${node._key}-menuButton`, menu: _jsxs(Menu, { children: [_jsx(MenuItem, { text: "Remove from list", tone: "critical", icon: RemoveCircleIcon, onClick: () => operations.removeItem(nodeProps) }, void 0), _jsx(MenuItem, { text: "Duplicate", icon: CopyIcon, disabled: !isValid, onClick: () => operations.duplicateItem(nodeProps) }, void 0), _jsx(MenuDivider, {}, void 0), _jsx(MenuItem, { text: "Open in new tab", icon: LaunchIcon, disabled: !isValid, as: OpenLink, "data-as": "a" }, void 0)] }, void 0), placement: "right", popover: { portal: true, tone: 'default' } }, void 0));
22
23
  };
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { SanityTreeItem, TreeInputOptions } from '../types';
2
+ import { StoredTreeItem, TreeInputOptions } from '../types';
3
3
  /**
4
4
  * The loaded tree users interact with
5
5
  */
6
6
  declare const TreeEditor: React.FC<{
7
- tree: SanityTreeItem[];
7
+ tree: StoredTreeItem[];
8
8
  onChange: (patch: unknown) => void;
9
9
  options: TreeInputOptions;
10
10
  patchPrefix?: string;
@@ -30,7 +30,7 @@ const TreeEditor = (props) => {
30
30
  // Only include borderBottom if there's something to show in unadded items
31
31
  borderBottom: allItemsStatus !== 'success' || unaddedItems?.length > 0, children: _jsx(SortableTree, { maxDepth: props.options.maxDepth, onChange: () => {
32
32
  // Do nothing. onMoveNode will do all the work
33
- }, onVisibilityToggle: handleVisibilityToggle, onMoveNode: operations.handleMovedNode, treeData: localTree, ...getCommonTreeProps({
33
+ }, onVisibilityToggle: handleVisibilityToggle, onMoveNode: (data) => operations.handleMovedNode(data), treeData: localTree, ...getCommonTreeProps({
34
34
  placeholder: {
35
35
  title: 'Add items by dragging them here'
36
36
  }
@@ -5,6 +5,10 @@ interface TreeProps extends TreeDeskStructureProps {
5
5
  * Also used as the label in the desk list item.
6
6
  */
7
7
  title: string;
8
+ /**
9
+ * Optional icon for rendering the item in the desk structure.
10
+ */
11
+ icon?: any;
8
12
  }
9
13
  export default function createDeskHierarchy(props: TreeProps): import("@sanity/structure/dist/dts/ListItem").ListItemBuilder;
10
14
  export {};
@@ -16,7 +16,7 @@ export default function createDeskHierarchy(props) {
16
16
  const { documentId, referenceTo, referenceOptions } = props;
17
17
  let mainList = (referenceTo?.length === 1
18
18
  ? S.documentTypeList(referenceTo[0]).schemaType(referenceTo[0])
19
- : S.documentList())
19
+ : S.documentList().filter('_type in $types').params({ types: referenceTo }))
20
20
  .id(documentId)
21
21
  .menuItems((referenceTo || []).map((schemaType) => S.menuItem()
22
22
  .intent({
@@ -44,6 +44,7 @@ export default function createDeskHierarchy(props) {
44
44
  return S.listItem()
45
45
  .id(documentId)
46
46
  .title(props.title || documentId)
47
+ .icon(props.icon)
47
48
  .child(Object.assign(mainList.serialize(), {
48
49
  type: 'component',
49
50
  component: deskTreeValidator(props),
@@ -14,13 +14,19 @@ export default function createHierarchicalField({ name, title, options, ...rest
14
14
  type: 'object',
15
15
  fields: [
16
16
  { name: 'parent', type: 'string' },
17
- { name: 'nodeDocType', type: 'string' },
18
17
  {
19
- name: 'node',
20
- type: 'reference',
21
- weak: true,
22
- to: options.referenceTo.map((type) => ({ type })),
23
- options: options.referenceOptions
18
+ name: 'value',
19
+ type: 'object',
20
+ fields: [
21
+ { name: 'docType', type: 'string' },
22
+ {
23
+ name: 'reference',
24
+ type: 'reference',
25
+ weak: true,
26
+ to: options.referenceTo.map((type) => ({ type })),
27
+ options: options.referenceOptions
28
+ }
29
+ ]
24
30
  }
25
31
  ]
26
32
  }
@@ -1,8 +1,10 @@
1
- import createHierarchicalField from './createHierarchicalField';
1
+ import createHierarchicalField from '../createHierarchicalField';
2
2
  export default {
3
3
  name: 'hierarchy.tree',
4
4
  title: 'Hierarchical tree',
5
5
  type: 'document',
6
+ // The plugin needs to define a `schemaType` with liveEdit enabled so that
7
+ // `useDocumentOperation` in TreeDeskStructure.tsx doesn't create drafts at every patch.
6
8
  liveEdit: true,
7
9
  fields: [
8
10
  createHierarchicalField({
package/lib/types.d.ts ADDED
@@ -0,0 +1,122 @@
1
+ import { SanityDocument } from '@sanity/client';
2
+ import { ArraySchemaType } from '@sanity/types';
3
+ import { NodeRendererProps, TreeItem } from 'react-sortable-tree';
4
+ interface SanityReference {
5
+ _type: 'reference';
6
+ _ref: string;
7
+ _weak?: boolean;
8
+ }
9
+ /**
10
+ * Objects saved to tree documents in Sanity's Content Lake
11
+ */
12
+ export interface StoredTreeItem {
13
+ _key: string;
14
+ _type: 'hierarchy.node' | string;
15
+ value?: {
16
+ reference?: SanityReference;
17
+ docType?: string;
18
+ };
19
+ /**
20
+ * _key of parent node
21
+ */
22
+ parent?: string | null;
23
+ }
24
+ /**
25
+ * Tree items enhanced locally in the client with info from `allItems` and `visibilityMap`.
26
+ * `allItems` stop here and never become LocalTreeItems as they aren't added to react-sortable-tree.
27
+ *
28
+ * See `useLocalTree.ts` and `dataToEditorTree()`.
29
+ */
30
+ export interface EnhancedTreeItem extends StoredTreeItem {
31
+ expanded?: boolean | undefined;
32
+ /**
33
+ * Used by DocumentInNode to render the preview for drafts if they exist.
34
+ * Also informs document status icons.
35
+ */
36
+ draftId?: string;
37
+ draftUpdatedAt?: string;
38
+ /**
39
+ * If not present, DocumentInNode will show up an error for invalid document.
40
+ * - undefined `publishedId` could mean the document is either deleted, or it doesn't match GROQ filters anymore
41
+ */
42
+ publishedId?: string;
43
+ publishedUpdatedAt?: string;
44
+ }
45
+ /**
46
+ * Tree items as found in the sortable tree itself.
47
+ */
48
+ export declare type LocalTreeItem = EnhancedTreeItem & Pick<TreeItem, 'title' | 'children'>;
49
+ export interface TreeInputOptions {
50
+ /**
51
+ * What document types this hierarchy can refer to.
52
+ * Similar to the `to` property of the [reference field](https://www.sanity.io/docs/reference-type).
53
+ */
54
+ referenceTo: string[];
55
+ /**
56
+ * Used to provide fine-grained filtering for documents.
57
+ */
58
+ referenceOptions?: {
59
+ /**
60
+ * Static filter to apply to tree document queries.
61
+ */
62
+ filter?: string;
63
+ /**
64
+ * Parameters / variables to pass to the GROQ query ran to fetch documents.
65
+ */
66
+ filterParams?: Record<string, unknown>;
67
+ };
68
+ /**
69
+ * How deep should editors be allowed to nest items.
70
+ */
71
+ maxDepth?: number;
72
+ }
73
+ export interface TreeFieldSchema extends Omit<ArraySchemaType, 'of' | 'type' | 'inputComponent' | 'jsonType'> {
74
+ options: ArraySchemaType['options'] & TreeInputOptions;
75
+ }
76
+ export interface TreeDeskStructureProps extends TreeInputOptions {
77
+ /**
78
+ * _id of the document that will hold the tree data.
79
+ */
80
+ documentId: string;
81
+ /**
82
+ * (Optional)
83
+ * Schema type for your hierarchical documents.
84
+ * By default, the document will be of type `hierarchy.tree`, a type provided by the plugin.
85
+ */
86
+ documentType?: string;
87
+ /**
88
+ * (Optional)
89
+ * Key for the field representing the hierarchical tree inside the document.
90
+ * `tree` by default.
91
+ */
92
+ fieldKeyInDocument?: string;
93
+ }
94
+ export interface DocumentPair {
95
+ draft?: SanityDocument;
96
+ published?: SanityDocument;
97
+ }
98
+ export interface AllItems {
99
+ [publishedId: string]: DocumentPair | undefined;
100
+ }
101
+ declare type DocumentOperation<Payload = unknown> = {
102
+ execute?: (payload?: Payload) => void;
103
+ disabled?: boolean | string;
104
+ };
105
+ export interface DocumentOperations {
106
+ patch?: DocumentOperation<unknown[]>;
107
+ commit?: DocumentOperation;
108
+ del?: DocumentOperation;
109
+ delete?: DocumentOperation;
110
+ discardChanges?: DocumentOperation;
111
+ duplicate?: DocumentOperation;
112
+ restore?: DocumentOperation;
113
+ unpublish?: DocumentOperation;
114
+ publish?: DocumentOperation;
115
+ }
116
+ export interface VisibilityMap {
117
+ [_key: string]: boolean;
118
+ }
119
+ export interface NodeProps extends NodeRendererProps {
120
+ node: LocalTreeItem;
121
+ }
122
+ export {};
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
- import { SanityTreeItem } from '../types';
2
- interface TreeItemWithChildren extends SanityTreeItem {
1
+ import { StoredTreeItem } from '../types';
2
+ interface TreeItemWithChildren extends StoredTreeItem {
3
3
  children?: TreeItemWithChildren[];
4
4
  }
5
- export default function flatDataToTree(data: SanityTreeItem[]): TreeItemWithChildren[];
5
+ export default function flatDataToTree(data: StoredTreeItem[]): TreeItemWithChildren[];
6
6
  export {};
@@ -1,18 +1,18 @@
1
1
  import { SanityDocument } from '@sanity/client';
2
2
  import { TreeItem } from 'react-sortable-tree';
3
- import { AllItems, SanityTreeItem, VisibilityMap } from '../types';
3
+ import { AllItems, EnhancedTreeItem, LocalTreeItem, StoredTreeItem, VisibilityMap } from '../types';
4
4
  export declare const dataToEditorTree: ({ tree, allItems, visibilityMap }: {
5
- tree: SanityTreeItem[];
5
+ tree: StoredTreeItem[];
6
6
  allItems: AllItems;
7
7
  visibilityMap: VisibilityMap;
8
- }) => TreeItem[];
8
+ }) => LocalTreeItem[];
9
9
  export declare const flatTree: (tree: TreeItem[]) => TreeItem[];
10
10
  export interface FetchData {
11
- mainTree?: SanityTreeItem[];
11
+ mainTree?: LocalTreeItem[];
12
12
  allItems?: SanityDocument[];
13
13
  }
14
14
  export declare const getUnaddedItems: (data: {
15
15
  allItems: AllItems;
16
- tree: SanityTreeItem[];
17
- }) => SanityTreeItem[];
18
- export declare function normalizeNodeForStorage(item: TreeItem): SanityTreeItem;
16
+ tree: StoredTreeItem[];
17
+ }) => EnhancedTreeItem[];
18
+ export declare function normalizeNodeForStorage(item: LocalTreeItem): StoredTreeItem;
@@ -1,13 +1,15 @@
1
- import { FullTree, NodeData, NodeRendererProps, OnMovePreviousAndNextLocation, TreeItem } from 'react-sortable-tree';
2
- import { SanityTreeItem } from '../types';
3
- export declare type HandleMovedNodeData = Omit<NodeData & FullTree & OnMovePreviousAndNextLocation, 'prevPath' | 'prevTreeIndex' | 'path' | 'treeIndex'>;
1
+ import { FullTree, NodeData, OnMovePreviousAndNextLocation, TreeItem } from 'react-sortable-tree';
2
+ import { LocalTreeItem, NodeProps } from '../types';
3
+ export declare type HandleMovedNodeData = Omit<NodeData & FullTree & OnMovePreviousAndNextLocation, 'prevPath' | 'prevTreeIndex' | 'path' | 'treeIndex' | 'node'> & {
4
+ node: LocalTreeItem;
5
+ };
4
6
  export declare type HandleMovedNode = (moveData: HandleMovedNodeData) => void;
5
- export declare function getAddItemPatch(item: SanityTreeItem): unknown[];
6
- export declare function getDuplicateItemPatch(nodeProps: NodeRendererProps): unknown[];
7
- export declare function getRemoveItemPatch({ node }: Pick<NodeRendererProps, 'node'>): unknown[];
7
+ export declare function getAddItemPatch(item: LocalTreeItem): unknown[];
8
+ export declare function getDuplicateItemPatch(nodeProps: NodeProps): unknown[];
9
+ export declare function getRemoveItemPatch({ node }: Pick<NodeProps, 'node'>): unknown[];
8
10
  export declare function getMovedNodePatch(data: HandleMovedNodeData): unknown[];
9
- export declare function getMoveItemPatch({ nodeProps: { node, treeIndex, parentNode }, localTree, direction }: {
10
- nodeProps: NodeRendererProps;
11
+ export declare function getMoveItemPatch({ nodeProps: { node, treeIndex }, localTree, direction }: {
12
+ nodeProps: NodeProps;
11
13
  localTree: TreeItem[];
12
14
  direction: 'up' | 'down';
13
15
  }): unknown[];
@@ -61,9 +61,8 @@ export function getMovedNodePatch(data) {
61
61
  ? // After the sibling before it
62
62
  Patch.insert([normalizedNode], 'after', [{ _key: leadingNode.node._key }])
63
63
  : // Or before the sibling right after it, in case there's no leading sibling node
64
- Patch.insert([normalizedNode], 'before', [
65
- followingNode?.node?._key ? { _key: followingNode.node._key } : data.nextTreeIndex
66
- ]),
64
+ // prettier-ignore
65
+ Patch.insert([normalizedNode], 'before', [followingNode?.node?._key ? { _key: followingNode.node._key } : data.nextTreeIndex]),
67
66
  // 3. Patch the new node with its new `parent`
68
67
  nextParentNode
69
68
  ? // If it has a parent node, set that parent's _key
@@ -80,7 +79,7 @@ function getChildrenPaths(node) {
80
79
  .reduce((keyPaths, child) => [...keyPaths, child._key, ...getChildrenPaths(child)], [])
81
80
  .filter(Boolean);
82
81
  }
83
- export function getMoveItemPatch({ nodeProps: { node, treeIndex, parentNode }, localTree, direction = 'up' }) {
82
+ export function getMoveItemPatch({ nodeProps: { node, treeIndex }, localTree, direction = 'up' }) {
84
83
  const keyPath = { _key: node._key };
85
84
  const nextTreeIndex = treeIndex + (direction === 'up' ? -1 : 1);
86
85
  const flatTree = getFlatDataFromTree({
@@ -98,16 +97,6 @@ export function getMoveItemPatch({ nodeProps: { node, treeIndex, parentNode }, l
98
97
  treeIndex: nextTreeIndex
99
98
  });
100
99
  const normalizedNode = normalizeNodeForStorage(node);
101
- console.log(`Move ${direction}`, {
102
- node,
103
- treeIndex,
104
- parentNode,
105
- nextFlatTree,
106
- flatTree,
107
- localTree,
108
- leadingSibling: leadingNode,
109
- followingSibling: followingNode
110
- });
111
100
  // When moving up, look at following node to figure out what is the next parent.
112
101
  const nodeToInheritParent = direction === 'up' ? followingNode : leadingNode;
113
102
  const nextParentNode = nodeToInheritParent?.parentNode;
@@ -1,17 +1,17 @@
1
- import { OnVisibilityToggleData, TreeItem } from 'react-sortable-tree';
2
- import { AllItems, SanityTreeItem } from '../types';
1
+ import { OnVisibilityToggleData } from 'react-sortable-tree';
2
+ import { AllItems, LocalTreeItem, StoredTreeItem } from '../types';
3
3
  /**
4
4
  * Enhances tree data with information on:
5
5
  * - `expanded` - native property of react-sortable-tree to determine collapsing & expanding of a node's children
6
- * - `draftId` & `publishedId` - refer to SanityTreeItem's type annotations
6
+ * - `draftId` & `publishedId` - refer to LocalTreeItem's type annotations
7
7
  *
8
8
  * Doesn't modify the main tree or has side-effects on data.
9
9
  * Has the added benefit of being local to the user, so external changes won't affect local visibility.
10
10
  */
11
11
  export default function useLocalTree({ tree, allItems }: {
12
- tree: SanityTreeItem[];
12
+ tree: StoredTreeItem[];
13
13
  allItems: AllItems;
14
14
  }): {
15
15
  handleVisibilityToggle: (data: OnVisibilityToggleData) => void;
16
- localTree: TreeItem[];
16
+ localTree: LocalTreeItem[];
17
17
  };
@@ -3,7 +3,7 @@ import { dataToEditorTree } from './treeData';
3
3
  /**
4
4
  * Enhances tree data with information on:
5
5
  * - `expanded` - native property of react-sortable-tree to determine collapsing & expanding of a node's children
6
- * - `draftId` & `publishedId` - refer to SanityTreeItem's type annotations
6
+ * - `draftId` & `publishedId` - refer to LocalTreeItem's type annotations
7
7
  *
8
8
  * Doesn't modify the main tree or has side-effects on data.
9
9
  * Has the added benefit of being local to the user, so external changes won't affect local visibility.
@@ -1,15 +1,14 @@
1
- import { NodeRendererProps, TreeItem } from 'react-sortable-tree';
2
- import { SanityTreeItem } from '../types';
1
+ import { LocalTreeItem, NodeProps } from '../types';
3
2
  import { HandleMovedNode } from './treePatches';
4
3
  export default function useTreeOperationsProvider(props: {
5
4
  patchPrefix?: string;
6
5
  onChange: (patch: unknown) => void;
7
- localTree: TreeItem[];
6
+ localTree: LocalTreeItem[];
8
7
  }): {
9
8
  handleMovedNode: HandleMovedNode;
10
- addItem: (item: SanityTreeItem) => void;
11
- duplicateItem: (nodeProps: NodeRendererProps) => void;
12
- removeItem: (nodeProps: NodeRendererProps) => void;
13
- moveItemUp: (nodeProps: NodeRendererProps) => void;
14
- moveItemDown: (nodeProps: NodeRendererProps) => void;
9
+ addItem: (item: LocalTreeItem) => void;
10
+ duplicateItem: (nodeProps: NodeProps) => void;
11
+ removeItem: (nodeProps: NodeProps) => void;
12
+ moveItemUp: (nodeProps: NodeProps) => void;
13
+ moveItemDown: (nodeProps: NodeProps) => void;
15
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/hierarchical-document-list",
3
- "version": "0.1.0-next.1",
3
+ "version": "0.1.0-next.2",
4
4
  "author": "Sanity <hello@sanity.io>",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -19,6 +19,12 @@
19
19
  "lint": "eslint src"
20
20
  },
21
21
  "devDependencies": {
22
+ "@sanity/base": ">= 2.25.0",
23
+ "@sanity/color": "^2.1.6",
24
+ "@sanity/desk-tool": ">= 2.25.0",
25
+ "@sanity/form-builder": "^2.25.0",
26
+ "@sanity/icons": ">= 1.2.0",
27
+ "@sanity/ui": ">= 0.37.0",
22
28
  "@types/assert": "^1.5.6",
23
29
  "@types/react": "^17.0.38",
24
30
  "@types/react-dom": "^17.0.11",
@@ -30,25 +36,21 @@
30
36
  "eslint-config-prettier": "^8.3.0",
31
37
  "eslint-config-sanity": "^5.1.0",
32
38
  "prettier": "^2.5.1",
39
+ "react": "^17.0.2",
40
+ "react-dom": "^17.0.2",
33
41
  "styled-components": "^5.3.3",
34
42
  "typescript": "^4.5.5"
35
43
  },
36
44
  "type": "module",
37
45
  "dependencies": {
38
- "@sanity/base": ">= 2.25.0",
39
- "@sanity/color": "^2.1.6",
40
- "@sanity/desk-tool": ">= 2.25.0",
41
- "@sanity/form-builder": "^2.25.0",
42
- "@sanity/icons": ">= 1.2.0",
43
- "@sanity/ui": ">= 0.37.0",
44
46
  "assert": "^2.0.0",
45
- "react": "^17.0.2",
46
- "react-dom": "^17.0.2",
47
47
  "react-sortable-tree": "^2.8.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@sanity/base": ">= 2.25.0",
51
51
  "@sanity/desk-tool": ">= 2.25.0",
52
+ "react": "^17.0.2",
53
+ "react-dom": "^17.0.2",
52
54
  "styled-components": ">= 5.2.0"
53
55
  }
54
56
  }
package/sanity.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "parts": [
7
7
  {
8
8
  "implements": "part:@sanity/base/schema-type",
9
- "path": "hierarchy.tree.js"
9
+ "path": "schemas/hierarchy.tree.js"
10
10
  }
11
11
  ]
12
12
  }