@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.
- package/LICENSE +1 -1
- package/README.md +95 -59
- package/dist/index.d.ts +240 -0
- package/dist/index.esm.js +18 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -51
- package/sanity.json +2 -6
- package/src/TreeDeskStructure.tsx +80 -0
- package/src/TreeInputComponent.tsx +41 -0
- package/src/components/DeskWarning.tsx +40 -0
- package/src/components/DocumentInNode.tsx +133 -0
- package/src/components/DocumentPreviewStatus.tsx +70 -0
- package/src/components/NodeActions.tsx +85 -0
- package/src/components/NodeContentRenderer.tsx +141 -0
- package/src/components/PlaceholderDropzone.tsx +45 -0
- package/src/components/TreeEditor.tsx +167 -0
- package/{lib/components/TreeEditorErrorBoundary.d.ts → src/components/TreeEditorErrorBoundary.tsx} +2 -4
- package/src/components/TreeNodeRenderer.tsx +37 -0
- package/src/components/TreeNodeRendererScaffold.tsx +193 -0
- package/src/createDeskHierarchy.tsx +110 -0
- package/src/createHierarchicalSchemas.tsx +151 -0
- package/src/hooks/useAllItems.ts +119 -0
- package/src/hooks/useLocalTree.ts +40 -0
- package/src/hooks/useTreeOperations.ts +25 -0
- package/src/hooks/useTreeOperationsProvider.ts +86 -0
- package/src/index.ts +25 -0
- package/src/schemas/hierarchy.tree.ts +19 -0
- package/src/types.ts +148 -0
- package/src/utils/flatDataToTree.ts +20 -0
- package/src/utils/getAdjescentNodes.ts +30 -0
- package/src/utils/getCommonTreeProps.tsx +28 -0
- package/src/utils/getTreeHeight.ts +10 -0
- package/src/utils/gradientPatchAdapter.ts +43 -0
- package/src/utils/idUtils.ts +7 -0
- package/src/utils/injectNodeTypeInPatches.ts +60 -0
- package/src/utils/moveItemInArray.ts +26 -0
- package/src/utils/throwError.ts +9 -0
- package/src/utils/treeData.tsx +119 -0
- package/src/utils/treePatches.ts +171 -0
- package/v2-incompatible.js +11 -0
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -4
- package/.idea/hierarchical-document-list.iml +0 -11
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/vcs.xml +0 -6
- package/CHANGELOG.md +0 -15
- package/commitlint.config.js +0 -3
- package/lib/TreeDeskStructure.d.ts +0 -8
- package/lib/TreeDeskStructure.js +0 -96
- package/lib/TreeInputComponent.d.ts +0 -19
- package/lib/TreeInputComponent.js +0 -50
- package/lib/components/DeskWarning.d.ts +0 -6
- package/lib/components/DeskWarning.js +0 -46
- package/lib/components/DocumentInNode.d.ts +0 -11
- package/lib/components/DocumentInNode.js +0 -81
- package/lib/components/DocumentPreviewStatus.d.ts +0 -7
- package/lib/components/DocumentPreviewStatus.js +0 -39
- package/lib/components/NodeActions.d.ts +0 -10
- package/lib/components/NodeActions.js +0 -61
- package/lib/components/NodeContentRenderer.d.ts +0 -8
- package/lib/components/NodeContentRenderer.js +0 -105
- package/lib/components/PlaceholderDropzone.d.ts +0 -9
- package/lib/components/PlaceholderDropzone.js +0 -30
- package/lib/components/SuppressedDnDManager.d.ts +0 -2
- package/lib/components/SuppressedDnDManager.js +0 -59
- package/lib/components/TreeEditor.d.ts +0 -12
- package/lib/components/TreeEditor.js +0 -74
- package/lib/components/TreeEditorErrorBoundary.js +0 -74
- package/lib/components/TreeNodeRenderer.d.ts +0 -3
- package/lib/components/TreeNodeRenderer.js +0 -59
- package/lib/components/TreeNodeRendererScaffold.d.ts +0 -4
- package/lib/components/TreeNodeRendererScaffold.js +0 -44
- package/lib/createDeskHierarchy.d.ts +0 -14
- package/lib/createDeskHierarchy.js +0 -84
- package/lib/createHierarchicalSchemas.d.ts +0 -98
- package/lib/createHierarchicalSchemas.js +0 -138
- package/lib/hooks/useAllItems.d.ts +0 -7
- package/lib/hooks/useAllItems.js +0 -119
- package/lib/hooks/useLocalTree.d.ts +0 -17
- package/lib/hooks/useLocalTree.js +0 -59
- package/lib/hooks/useTreeOperations.d.ts +0 -9
- package/lib/hooks/useTreeOperations.js +0 -39
- package/lib/hooks/useTreeOperationsProvider.d.ts +0 -14
- package/lib/hooks/useTreeOperationsProvider.js +0 -85
- package/lib/index.d.ts +0 -3
- package/lib/index.js +0 -12
- package/lib/schemas/hierarchy.tree.d.ts +0 -13
- package/lib/schemas/hierarchy.tree.js +0 -19
- package/lib/types.d.ts +0 -128
- package/lib/types.js +0 -2
- package/lib/utils/flatDataToTree.d.ts +0 -6
- package/lib/utils/flatDataToTree.js +0 -26
- package/lib/utils/getAdjescentNodes.d.ts +0 -12
- package/lib/utils/getAdjescentNodes.js +0 -19
- package/lib/utils/getCommonTreeProps.d.ts +0 -7
- package/lib/utils/getCommonTreeProps.js +0 -33
- package/lib/utils/getTreeHeight.d.ts +0 -3
- package/lib/utils/getTreeHeight.js +0 -11
- package/lib/utils/gradientPatchAdapter.d.ts +0 -4
- package/lib/utils/gradientPatchAdapter.js +0 -40
- package/lib/utils/idUtils.d.ts +0 -2
- package/lib/utils/idUtils.js +0 -13
- package/lib/utils/injectNodeTypeInPatches.d.ts +0 -12
- package/lib/utils/injectNodeTypeInPatches.js +0 -59
- package/lib/utils/moveItemInArray.d.ts +0 -5
- package/lib/utils/moveItemInArray.js +0 -26
- package/lib/utils/throwError.d.ts +0 -7
- package/lib/utils/throwError.js +0 -12
- package/lib/utils/treeData.d.ts +0 -18
- package/lib/utils/treeData.js +0 -118
- package/lib/utils/treePatches.d.ts +0 -15
- package/lib/utils/treePatches.js +0 -171
- package/lint-staged.config.js +0 -4
- package/screenshot-1.jpg +0 -0
- package/tsconfig.json +0 -20
package/package.json
CHANGED
|
@@ -1,65 +1,94 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/hierarchical-document-list",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.0.0",
|
|
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
|
|
15
|
+
"url": "git@github.com:sanity-io/hierarchical-document-list.git"
|
|
10
16
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
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
|
-
"
|
|
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",
|
|
20
40
|
"prepare": "npm run build && husky install",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
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"
|
|
41
|
+
"build": "run-s clean && pkg-utils build --strict && pkg-utils --strict",
|
|
42
|
+
"prepublishOnly": "run-s build",
|
|
43
|
+
"clean": "rimraf dist",
|
|
44
|
+
"format": "prettier --write --cache --ignore-unknown .",
|
|
45
|
+
"link-watch": "plugin-kit link-watch",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"watch": "pkg-utils watch --strict"
|
|
53
48
|
},
|
|
54
49
|
"dependencies": {
|
|
55
|
-
"
|
|
56
|
-
"
|
|
50
|
+
"@nosferatu500/react-sortable-tree": "^4.4.0",
|
|
51
|
+
"@sanity/incompatible-plugin": "^1.0.4",
|
|
52
|
+
"@sanity/mutator": "^3.19.3",
|
|
53
|
+
"@sanity/ui": "^1.9.3",
|
|
54
|
+
"@sanity/util": "^3.19.3",
|
|
55
|
+
"react-dnd": "16.0.1",
|
|
56
|
+
"react-dnd-html5-backend": "16.0.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@commitlint/cli": "^18.4.3",
|
|
60
|
+
"@commitlint/config-conventional": "^18.4.3",
|
|
61
|
+
"@sanity/pkg-utils": "^3.3.2",
|
|
62
|
+
"@sanity/plugin-kit": "^3.1.10",
|
|
63
|
+
"@sanity/semantic-release-preset": "^4.1.6",
|
|
64
|
+
"@types/react": "^18.2.41",
|
|
65
|
+
"@types/styled-components": "^5.1.32",
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
67
|
+
"@typescript-eslint/parser": "^6.13.1",
|
|
68
|
+
"eslint": "^8.55.0",
|
|
69
|
+
"eslint-config-prettier": "^9.1.0",
|
|
70
|
+
"eslint-config-sanity": "^7.0.1",
|
|
71
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
72
|
+
"eslint-plugin-react": "^7.33.2",
|
|
73
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
74
|
+
"husky": "^8.0.3",
|
|
75
|
+
"lint-staged": "^15.2.0",
|
|
76
|
+
"npm-run-all": "^4.1.5",
|
|
77
|
+
"prettier": "^3.1.0",
|
|
78
|
+
"prettier-plugin-packagejson": "^2.4.7",
|
|
79
|
+
"react": "^18.2.0",
|
|
80
|
+
"react-dom": "^18.2.0",
|
|
81
|
+
"react-is": "^18.2.0",
|
|
82
|
+
"rimraf": "^5.0.5",
|
|
83
|
+
"sanity": "^3.20.2",
|
|
84
|
+
"styled-components": "^5.3.11",
|
|
85
|
+
"typescript": "^5.3.2"
|
|
57
86
|
},
|
|
58
87
|
"peerDependencies": {
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
88
|
+
"react": "^18",
|
|
89
|
+
"sanity": "^3"
|
|
90
|
+
},
|
|
91
|
+
"engines": {
|
|
92
|
+
"node": ">=14"
|
|
64
93
|
}
|
|
65
94
|
}
|
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/
|
|
9
|
-
"path": "
|
|
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
|