@sanity/hierarchical-document-list 1.1.0 → 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 (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 +151 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +151 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +82 -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/package.json CHANGED
@@ -1,65 +1,96 @@
1
1
  {
2
2
  "name": "@sanity/hierarchical-document-list",
3
- "version": "1.1.0",
4
- "author": "Sanity <hello@sanity.io>",
5
- "license": "MIT",
6
- "main": "lib/index.js",
3
+ "version": "2.0.1",
4
+ "description": "Sanity Plugin - Hierarchical Document List",
5
+ "keywords": [
6
+ "sanity",
7
+ "sanity-plugin"
8
+ ],
9
+ "homepage": "https://github.com/sanity-io/hierarchical-document-list#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/sanity-io/hierarchical-document-list/issues"
12
+ },
7
13
  "repository": {
8
14
  "type": "git",
9
- "url": "git+https://github.com/sanity-io/hierarchical-document-list.git"
15
+ "url": "git@github.com:sanity-io/hierarchical-document-list.git"
10
16
  },
11
- "bugs": {
12
- "url": "https://github.com/sanity-io/hierarchical-document-list/issues"
17
+ "license": "MIT",
18
+ "author": "Sanity <hello@sanity.io>",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "source": "./src/index.ts",
23
+ "import": "./dist/index.esm.js",
24
+ "require": "./dist/index.js",
25
+ "default": "./dist/index.esm.js"
26
+ },
27
+ "./package.json": "./package.json"
13
28
  },
14
- "homepage": "https://github.com/sanity-io/hierarchical-document-list#readme",
29
+ "main": "./dist/index.js",
30
+ "module": "./dist/index.esm.js",
31
+ "source": "./src/index.ts",
32
+ "types": "./dist/index.d.ts",
33
+ "files": [
34
+ "dist",
35
+ "sanity.json",
36
+ "src",
37
+ "v2-incompatible.js"
38
+ ],
15
39
  "scripts": {
16
- "dev": "tsc -w -d",
17
- "build": "tsc -d",
18
- "format": "prettier src -w",
19
- "lint": "eslint src",
40
+ "dev": "sanity dev",
20
41
  "prepare": "npm run build && husky install",
21
- "clean": "rimraf lib",
22
- "release": "standard-version",
23
- "prepublishOnly": "npm run clean && npm run lint && npm run build"
24
- },
25
- "devDependencies": {
26
- "@commitlint/cli": "^16.2.3",
27
- "@commitlint/config-conventional": "^16.2.1",
28
- "@sanity/base": ">= 2.25.0",
29
- "@sanity/color": "^2.1.6",
30
- "@sanity/desk-tool": ">= 2.25.0",
31
- "@sanity/form-builder": "^2.25.0",
32
- "@sanity/icons": ">= 1.2.0",
33
- "@sanity/ui": ">= 0.37.0",
34
- "@types/assert": "^1.5.6",
35
- "@types/react": "^17.0.38",
36
- "@types/react-dom": "^17.0.11",
37
- "@types/react-sortable-tree": "^0.3.14",
38
- "@types/styled-components": "^5.1.21",
39
- "@typescript-eslint/eslint-plugin": "^5.10.1",
40
- "@typescript-eslint/parser": "^5.10.1",
41
- "eslint": "^8.7.0",
42
- "eslint-config-prettier": "^8.3.0",
43
- "eslint-config-sanity": "^5.1.0",
44
- "husky": "^7.0.4",
45
- "lint-staged": "^12.3.7",
46
- "prettier": "^2.5.1",
47
- "react": "^17.0.2",
48
- "react-dom": "^17.0.2",
49
- "rimraf": "^3.0.2",
50
- "standard-version": "^9.3.2",
51
- "styled-components": "^5.3.3",
52
- "typescript": "^4.5.5"
42
+ "build": "run-s clean && pkg-utils build --strict && pkg-utils --strict",
43
+ "prepublishOnly": "run-s build",
44
+ "clean": "rimraf dist",
45
+ "format": "prettier --write --cache --ignore-unknown .",
46
+ "link-watch": "plugin-kit link-watch",
47
+ "lint": "eslint .",
48
+ "watch": "pkg-utils watch --strict"
53
49
  },
54
50
  "dependencies": {
55
- "assert": "^2.0.0",
56
- "react-sortable-tree": "^2.8.0"
51
+ "@nosferatu500/react-sortable-tree": "^4.4.0",
52
+ "@sanity/icons": "^3.7.0",
53
+ "@sanity/incompatible-plugin": "^1.0.5",
54
+ "@sanity/mutator": "^3.78.1",
55
+ "@sanity/ui": "^2.15.2",
56
+ "@sanity/util": "^3.78.1",
57
+ "react-dnd": "16.0.1",
58
+ "react-dnd-html5-backend": "16.0.1"
59
+ },
60
+ "devDependencies": {
61
+ "@commitlint/cli": "^18.4.3",
62
+ "@commitlint/config-conventional": "^18.4.3",
63
+ "@sanity/pkg-utils": "^3.3.2",
64
+ "@sanity/plugin-kit": "^3.1.10",
65
+ "@sanity/semantic-release-preset": "^4.1.6",
66
+ "@sanity/vision": "^3.78.1",
67
+ "@types/react": "^18.2.41",
68
+ "@typescript-eslint/eslint-plugin": "^6.13.1",
69
+ "@typescript-eslint/parser": "^6.13.1",
70
+ "eslint": "^8.55.0",
71
+ "eslint-config-prettier": "^9.1.0",
72
+ "eslint-config-sanity": "^7.0.1",
73
+ "eslint-plugin-prettier": "^5.0.1",
74
+ "eslint-plugin-react": "^7.33.2",
75
+ "eslint-plugin-react-hooks": "^4.6.0",
76
+ "husky": "^8.0.3",
77
+ "lint-staged": "^15.2.0",
78
+ "npm-run-all": "^4.1.5",
79
+ "prettier": "^3.1.0",
80
+ "prettier-plugin-packagejson": "^2.4.7",
81
+ "react": "^18.3.0",
82
+ "react-dom": "^18.3.0",
83
+ "react-is": "^18.3.0",
84
+ "rimraf": "^5.0.5",
85
+ "sanity": "^3.78.1",
86
+ "styled-components": "^6.1.15",
87
+ "typescript": "5.3.2"
57
88
  },
58
89
  "peerDependencies": {
59
- "@sanity/base": ">= 2.25.0",
60
- "@sanity/desk-tool": ">= 2.25.0",
61
- "react": "^17.0.2",
62
- "react-dom": "^17.0.2",
63
- "styled-components": ">= 5.2.0"
90
+ "react": "^18.3",
91
+ "sanity": "^3"
92
+ },
93
+ "engines": {
94
+ "node": ">=14"
64
95
  }
65
96
  }
package/sanity.json CHANGED
@@ -1,12 +1,8 @@
1
1
  {
2
- "paths": {
3
- "source": "./src",
4
- "compiled": "./lib"
5
- },
6
2
  "parts": [
7
3
  {
8
- "implements": "part:@sanity/base/schema-type",
9
- "path": "schemas/hierarchy.tree.js"
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
10
6
  }
11
7
  ]
12
8
  }
@@ -0,0 +1,80 @@
1
+ import {Box, Flex, Spinner} from '@sanity/ui'
2
+ import * as React from 'react'
3
+ import {PatchEvent, useDocumentOperation, useEditState} from 'sanity'
4
+ import DeskWarning from './components/DeskWarning'
5
+ import TreeEditor from './components/TreeEditor'
6
+ import {DocumentOperations, StoredTreeItem, TreeDeskStructureProps} from './types'
7
+ import {toGradient} from './utils/gradientPatchAdapter'
8
+ import injectNodeTypeInPatches, {DEFAULT_DOC_TYPE} from './utils/injectNodeTypeInPatches'
9
+
10
+ interface ComponentProps {
11
+ options: TreeDeskStructureProps
12
+ }
13
+
14
+ export const DEFAULT_FIELD_KEY = 'tree'
15
+
16
+ const TreeDeskStructure: React.FC<ComponentProps> = (props) => {
17
+ const treeDocType = props.options.documentType || DEFAULT_DOC_TYPE
18
+ const treeFieldKey = props.options.fieldKeyInDocument || DEFAULT_FIELD_KEY
19
+ const {published, draft, liveEdit} = useEditState(props.options.documentId, treeDocType)
20
+ const {patch} = useDocumentOperation(props.options.documentId, treeDocType) as DocumentOperations
21
+
22
+ const treeValue = (published?.[treeFieldKey] || []) as StoredTreeItem[]
23
+
24
+ const handleChange = React.useCallback(
25
+ (patchEvent: PatchEvent) => {
26
+ if (!patch?.execute) {
27
+ return
28
+ }
29
+ patch.execute(toGradient(injectNodeTypeInPatches(patchEvent.patches, treeDocType)))
30
+ },
31
+ [patch]
32
+ )
33
+
34
+ React.useEffect(() => {
35
+ if (!published?._id && patch?.execute && !patch?.disabled) {
36
+ // If no published document, create it
37
+ patch.execute([{setIfMissing: {[treeFieldKey]: []}}])
38
+ }
39
+ }, [published?._id, patch])
40
+
41
+ if (!liveEdit) {
42
+ return (
43
+ <DeskWarning
44
+ title="Invalid configuration"
45
+ 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."
46
+ />
47
+ )
48
+ }
49
+
50
+ if (draft?._id) {
51
+ return (
52
+ <DeskWarning
53
+ title="This hierarchy tree contains a draft"
54
+ subtitle="Click on the button below to publish your draft in order to continue editing the live
55
+ published document."
56
+ />
57
+ )
58
+ }
59
+
60
+ if (!published?._id) {
61
+ return (
62
+ <Flex padding={5} align={'center'} justify={'center'} height={'fill'}>
63
+ <Spinner width={4} muted />
64
+ </Flex>
65
+ )
66
+ }
67
+
68
+ return (
69
+ <Box paddingBottom={5} paddingRight={2}>
70
+ <TreeEditor
71
+ options={props.options}
72
+ tree={treeValue}
73
+ onChange={handleChange}
74
+ patchPrefix={treeFieldKey}
75
+ />
76
+ </Box>
77
+ )
78
+ }
79
+
80
+ export default TreeDeskStructure
@@ -0,0 +1,41 @@
1
+ import * as React from 'react'
2
+ import {FormField, FormNodePresence, PatchEvent, Path} from 'sanity'
3
+ import TreeEditor from './components/TreeEditor'
4
+ import {StoredTreeItem, TreeFieldSchema} from './types'
5
+ import injectNodeTypeInPatches, {DEFAULT_DOC_TYPE} from './utils/injectNodeTypeInPatches'
6
+
7
+ export interface TreeInputComponentProps {
8
+ type: TreeFieldSchema
9
+ value: StoredTreeItem[]
10
+ level: number
11
+ onChange: (event: unknown) => void
12
+ onFocus: (path: Path) => void
13
+ onBlur: () => void
14
+ focusPath: Path
15
+ readOnly: boolean
16
+ presence: FormNodePresence[]
17
+ }
18
+
19
+ const TreeInputComponent: React.FC<TreeInputComponentProps> = (props) => {
20
+ const documentType = props.type.options.documentType || DEFAULT_DOC_TYPE
21
+
22
+ const onChange = React.useCallback(
23
+ (patch: any) => {
24
+ const patches = injectNodeTypeInPatches(patch?.patches, documentType)
25
+ props.onChange(new PatchEvent(patches))
26
+ },
27
+ [props.onChange]
28
+ )
29
+
30
+ return (
31
+ <FormField
32
+ description={props.type.description} // Creates description from schema
33
+ title={props.type.title} // Creates label from schema title
34
+ __unstable_presence={props.presence} // Handles presence avatars
35
+ >
36
+ <TreeEditor options={props.type.options} tree={props.value || []} onChange={onChange} />
37
+ </FormField>
38
+ )
39
+ }
40
+
41
+ export default TreeInputComponent
@@ -0,0 +1,40 @@
1
+ import {Box, Card, Container, Heading, Stack, Text} from '@sanity/ui'
2
+ import * as React from 'react'
3
+
4
+ // React component that wraps text between two delimiters in a <pre> tag
5
+
6
+ const WrapCodeBlocks: React.FC<{text: string}> = ({text}) => {
7
+ return (
8
+ <>
9
+ {text.split('`').map((part, i) => (
10
+ <React.Fragment key={i}>{i % 2 === 0 ? part : <code>{part}</code>}</React.Fragment>
11
+ ))}
12
+ </>
13
+ )
14
+ }
15
+
16
+ const DeskWarning: React.FC<
17
+ React.PropsWithChildren<{
18
+ title: string
19
+ subtitle?: string
20
+ }>
21
+ > = (props) => {
22
+ return (
23
+ <Container padding={5} style={{maxWidth: '25rem'}} sizing={'content'}>
24
+ <Card padding={4} border radius={2} width={0} tone="caution">
25
+ <Stack space={3}>
26
+ <Heading size={1}>{props.title}</Heading>
27
+ {props.subtitle &&
28
+ props.subtitle.split('\\n').map((line: string) => (
29
+ <Text size={1}>
30
+ <WrapCodeBlocks text={line} />
31
+ </Text>
32
+ ))}
33
+ {props.children && <Box marginTop={2}>{props.children}</Box>}
34
+ </Stack>
35
+ </Card>
36
+ </Container>
37
+ )
38
+ }
39
+
40
+ export default DeskWarning
@@ -0,0 +1,133 @@
1
+ import {HelpCircleIcon} from '@sanity/icons'
2
+ import {Box, Card, Flex, Stack, Text, Tooltip} from '@sanity/ui'
3
+ import * as React from 'react'
4
+ import {Preview, SanityDocument, SchemaType, TextWithTone, useSchema} from 'sanity'
5
+ import {RouterPaneGroup, usePaneRouter} from 'sanity/desk'
6
+ import useTreeOperations from '../hooks/useTreeOperations'
7
+ import {LocalTreeItem} from '../types'
8
+ import DocumentPreviewStatus from './DocumentPreviewStatus'
9
+
10
+ /**
11
+ * Renders a preview for each referenced document.
12
+ * Nested inside TreeNode.tsx
13
+ */
14
+ const DocumentInNode: React.FC<{
15
+ item: LocalTreeItem
16
+ action?: React.ReactNode
17
+ }> = (props) => {
18
+ const {value: {reference, docType} = {}, draftId, publishedId} = props.item
19
+ const {routerPanesState, ChildLink} = usePaneRouter()
20
+ const {allItemsStatus} = useTreeOperations()
21
+ const schema = useSchema()
22
+ const isActive = React.useMemo(() => {
23
+ // If some pane is active with the current document `_id`, it's active
24
+ return routerPanesState.some((pane: RouterPaneGroup) =>
25
+ pane.some((group) => group.id === reference?._ref)
26
+ )
27
+ }, [routerPanesState])
28
+
29
+ const type = React.useMemo(() => {
30
+ return docType ? schema.get(docType) : undefined
31
+ }, [docType])
32
+
33
+ const LinkComponent = React.useMemo(
34
+ () =>
35
+ React.forwardRef((linkProps: any, ref: any) => (
36
+ <ChildLink
37
+ {...linkProps}
38
+ childId={reference?._ref}
39
+ ref={ref}
40
+ childParameters={{
41
+ type: docType
42
+ }}
43
+ />
44
+ )),
45
+ [ChildLink, reference?._ref]
46
+ )
47
+
48
+ if (!reference?._ref) {
49
+ return null
50
+ }
51
+
52
+ return (
53
+ <Flex gap={2} align="center" style={{flex: 1}}>
54
+ {/* Show loading preview while allItems aren't ready */}
55
+ {publishedId || allItemsStatus !== 'success' ? (
56
+ /* Card loosely copied from @sanity/desk-tool's PaneItem.tsx */
57
+ <Card
58
+ __unstable_focusRing
59
+ as={LinkComponent}
60
+ tone={isActive ? 'primary' : 'default'}
61
+ padding={1}
62
+ radius={2}
63
+ flex={1}
64
+ data-as="a"
65
+ data-ui="PaneItem"
66
+ >
67
+ <Preview
68
+ layout="default"
69
+ schemaType={type as SchemaType}
70
+ value={{_ref: draftId || reference?._ref}}
71
+ status={
72
+ <DocumentPreviewStatus
73
+ draft={
74
+ draftId
75
+ ? ({
76
+ _id: draftId,
77
+ _type: docType,
78
+ _updatedAt: props.item.draftUpdatedAt
79
+ } as SanityDocument)
80
+ : undefined
81
+ }
82
+ published={
83
+ {
84
+ _id: reference?._ref,
85
+ _type: docType,
86
+ _updatedAt: props.item.publishedUpdatedAt
87
+ } as SanityDocument
88
+ }
89
+ />
90
+ }
91
+ />
92
+ </Card>
93
+ ) : (
94
+ <Card padding={3} radius={1} flex={1}>
95
+ <Flex align="center">
96
+ <Text size={2} muted style={{flex: 1}}>
97
+ Invalid document
98
+ </Text>
99
+ <Tooltip
100
+ placement="left"
101
+ portal
102
+ content={
103
+ <Box padding={3}>
104
+ <Flex align="flex-start" gap={3}>
105
+ <TextWithTone tone="default" size={3}>
106
+ <HelpCircleIcon />
107
+ </TextWithTone>
108
+ <Stack space={3}>
109
+ <Text as="h2" size={1} weight="semibold">
110
+ This document is not valid
111
+ </Text>
112
+ {/* <Text size={1}>
113
+ It was deleted or it doesn't match the filters set by this hierarchy.
114
+ </Text> */}
115
+ <Text size={1}>ID: {reference?._ref}</Text>
116
+ </Stack>
117
+ </Flex>
118
+ </Box>
119
+ }
120
+ >
121
+ <TextWithTone tone="default" size={2}>
122
+ <HelpCircleIcon />
123
+ </TextWithTone>
124
+ </Tooltip>
125
+ </Flex>
126
+ </Card>
127
+ )}
128
+ {props.action}
129
+ </Flex>
130
+ )
131
+ }
132
+
133
+ export default DocumentInNode
@@ -0,0 +1,70 @@
1
+ import {EditIcon, PublishIcon} from '@sanity/icons'
2
+ import {Box, Inline, Text, Tooltip} from '@sanity/ui'
3
+ import * as React from 'react'
4
+ import {SanityDocument, TextWithTone, useTimeAgo} from 'sanity'
5
+ import {DocumentPair} from '../types'
6
+
7
+ export function TimeAgo({time}: {time: string | Date}) {
8
+ const timeAgo = useTimeAgo(time)
9
+
10
+ return (
11
+ <span title={timeAgo}>
12
+ {timeAgo}
13
+ {timeAgo.toLowerCase().trim().startsWith('just now') ? '' : ' ago'}
14
+ </span>
15
+ )
16
+ }
17
+
18
+ const PublishedStatus = ({document}: {document?: SanityDocument | null}) => (
19
+ <Tooltip
20
+ portal
21
+ content={
22
+ <Box padding={2}>
23
+ <Text size={1}>
24
+ {document ? (
25
+ <>Published {document._updatedAt && <TimeAgo time={document._updatedAt} />}</>
26
+ ) : (
27
+ <>Not published</>
28
+ )}
29
+ </Text>
30
+ </Box>
31
+ }
32
+ >
33
+ <TextWithTone tone="positive" dimmed={!document} muted={!document} size={1}>
34
+ <PublishIcon />
35
+ </TextWithTone>
36
+ </Tooltip>
37
+ )
38
+
39
+ const DraftStatus = ({document}: {document?: SanityDocument | null}) => (
40
+ <Tooltip
41
+ portal
42
+ content={
43
+ <Box padding={2}>
44
+ <Text size={1}>
45
+ {document ? (
46
+ <>Edited {document?._updatedAt && <TimeAgo time={document?._updatedAt} />}</>
47
+ ) : (
48
+ <>No unpublished edits</>
49
+ )}
50
+ </Text>
51
+ </Box>
52
+ }
53
+ >
54
+ <TextWithTone tone="caution" dimmed={!document} muted={!document} size={1}>
55
+ <EditIcon />
56
+ </TextWithTone>
57
+ </Tooltip>
58
+ )
59
+
60
+ // Adapted from @sanity\desk-tool\src\components\paneItem\helpers.tsx
61
+ const DocumentPreviewStatus: React.FC<DocumentPair> = ({draft, published}) => {
62
+ return (
63
+ <Inline space={4}>
64
+ <PublishedStatus document={published} />
65
+ <DraftStatus document={draft} />
66
+ </Inline>
67
+ )
68
+ }
69
+
70
+ export default DocumentPreviewStatus
@@ -0,0 +1,85 @@
1
+ import {CopyIcon, EllipsisVerticalIcon, LaunchIcon, RemoveCircleIcon} from '@sanity/icons'
2
+ import {Button, Menu, MenuButton, MenuDivider, MenuItem} from '@sanity/ui'
3
+ import * as React from 'react'
4
+ import {IntentButton as IntentLink} from 'sanity'
5
+ import useTreeOperations from '../hooks/useTreeOperations'
6
+ import {NodeProps} from '../types'
7
+
8
+ /**
9
+ * Applicable only to nodes inside the main tree.
10
+ * Unadded items have their actions defined in TreeEditor.
11
+ */
12
+ const NodeActions: React.FC<{nodeProps: NodeProps}> = ({nodeProps}) => {
13
+ const operations = useTreeOperations()
14
+ const {node} = nodeProps
15
+ const {reference, docType} = node?.value || {}
16
+
17
+ // Adapted from @sanity\form-builder\src\inputs\ReferenceInput\ArrayItemReferenceInput.tsx
18
+ const OpenLink = React.useMemo(
19
+ () =>
20
+ // eslint-disable-next-line @typescript-eslint/no-shadow
21
+ React.forwardRef(function OpenLinkInner(
22
+ restProps: React.ComponentProps<typeof IntentLink>,
23
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
24
+ _ref: React.ForwardedRef<HTMLAnchorElement>
25
+ ) {
26
+ return (
27
+ <IntentLink
28
+ {...restProps}
29
+ intent="edit"
30
+ params={{id: reference?._ref, type: docType}}
31
+ target="_blank"
32
+ rel="noopener noreferrer"
33
+ />
34
+ )
35
+ }),
36
+ [reference?._ref, docType]
37
+ )
38
+
39
+ const isValid = !!node.publishedId
40
+ return (
41
+ <MenuButton
42
+ button={<Button padding={2} mode="bleed" icon={EllipsisVerticalIcon} />}
43
+ id={`hiearchical-doc-list--${node._key}-menuButton`}
44
+ menu={
45
+ <Menu>
46
+ <MenuItem
47
+ text="Remove from list"
48
+ tone="critical"
49
+ icon={RemoveCircleIcon}
50
+ onClick={() => operations.removeItem(nodeProps)}
51
+ />
52
+ <MenuItem
53
+ text="Duplicate item"
54
+ icon={CopyIcon}
55
+ disabled={!isValid}
56
+ onClick={() => operations.duplicateItem(nodeProps)}
57
+ />
58
+ {/* <MenuItem
59
+ text="Move up"
60
+ icon={ArrowUpIcon}
61
+ disabled={!isValid}
62
+ onClick={() => operations.moveItemUp(nodeProps)}
63
+ />
64
+ <MenuItem
65
+ text="Move down"
66
+ icon={ArrowDownIcon}
67
+ disabled={!isValid}
68
+ onClick={() => operations.moveItemDown(nodeProps)}
69
+ /> */}
70
+ <MenuDivider />
71
+ <MenuItem
72
+ text="Open in new tab"
73
+ icon={LaunchIcon}
74
+ disabled={!isValid}
75
+ as={OpenLink}
76
+ data-as="a"
77
+ />
78
+ </Menu>
79
+ }
80
+ popover={{portal: true, tone: 'default', placement: 'right'}}
81
+ />
82
+ )
83
+ }
84
+
85
+ export default NodeActions